mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2026-02-04 21:34:21 +01:00
Merge pull request #12 from Davi0kProgramsThings/feature/rest
Merge branch `feature/rest` in branch `master`.
This commit is contained in:
@@ -1,13 +1,32 @@
|
||||
from .rest import BfxRestInterface
|
||||
from .websocket import BfxWebsocketClient
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class Constants(str, Enum):
|
||||
REST_HOST = "https://api.bitfinex.com/v2"
|
||||
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
|
||||
|
||||
WSS_HOST = "wss://api.bitfinex.com/ws/2"
|
||||
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, WSS_HOST: str = Constants.WSS_HOST, API_KEY: str = None, API_SECRET: str = None, log_level: str = "WARNING"):
|
||||
def __init__(
|
||||
self,
|
||||
REST_HOST: str = Constants.REST_HOST,
|
||||
WSS_HOST: str = Constants.WSS_HOST,
|
||||
API_KEY: Optional[str] = None,
|
||||
API_SECRET: Optional[str] = None,
|
||||
log_level: str = "WARNING"
|
||||
):
|
||||
self.rest = BfxRestInterface(
|
||||
host=REST_HOST,
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET
|
||||
)
|
||||
|
||||
self.wss = BfxWebsocketClient(
|
||||
host=WSS_HOST,
|
||||
API_KEY=API_KEY,
|
||||
|
||||
50
bfxapi/enums.py
Normal file
50
bfxapi/enums.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from enum import Enum
|
||||
|
||||
class OrderType(str, Enum):
|
||||
LIMIT = "LIMIT"
|
||||
EXCHANGE_LIMIT = "EXCHANGE LIMIT"
|
||||
MARKET = "MARKET"
|
||||
EXCHANGE_MARKET = "EXCHANGE MARKET"
|
||||
STOP = "STOP"
|
||||
EXCHANGE_STOP = "EXCHANGE STOP"
|
||||
STOP_LIMIT = "STOP LIMIT"
|
||||
EXCHANGE_STOP_LIMIT = "EXCHANGE STOP LIMIT"
|
||||
TRAILING_STOP = "TRAILING STOP"
|
||||
EXCHANGE_TRAILING_STOP = "EXCHANGE TRAILING STOP"
|
||||
FOK = "FOK"
|
||||
EXCHANGE_FOK = "EXCHANGE FOK"
|
||||
IOC = "IOC"
|
||||
EXCHANGE_IOC = "EXCHANGE IOC"
|
||||
|
||||
class FundingOfferType(str, Enum):
|
||||
LIMIT = "LIMIT"
|
||||
FRR_DELTA_FIX = "FRRDELTAFIX"
|
||||
FRR_DELTA_VAR = "FRRDELTAVAR"
|
||||
|
||||
class Flag(int, Enum):
|
||||
HIDDEN = 64
|
||||
CLOSE = 512
|
||||
REDUCE_ONLY = 1024
|
||||
POST_ONLY = 4096
|
||||
OCO = 16384
|
||||
NO_VAR_RATES = 524288
|
||||
|
||||
class Error(int, Enum):
|
||||
ERR_UNK = 10000
|
||||
ERR_GENERIC = 10001
|
||||
ERR_CONCURRENCY = 10008
|
||||
ERR_PARAMS = 10020
|
||||
ERR_CONF_FAIL = 10050
|
||||
ERR_AUTH_FAIL = 10100
|
||||
ERR_AUTH_PAYLOAD = 10111
|
||||
ERR_AUTH_SIG = 10112
|
||||
ERR_AUTH_HMAC = 10113
|
||||
ERR_AUTH_NONCE = 10114
|
||||
ERR_UNAUTH_FAIL = 10200
|
||||
ERR_SUB_FAIL = 10300
|
||||
ERR_SUB_MULTI = 10301
|
||||
ERR_SUB_UNK = 10302
|
||||
ERR_SUB_LIMIT = 10305
|
||||
ERR_UNSUB_FAIL = 10400
|
||||
ERR_UNSUB_NOT = 10401
|
||||
ERR_READY = 11000
|
||||
35
bfxapi/exceptions.py
Normal file
35
bfxapi/exceptions.py
Normal file
@@ -0,0 +1,35 @@
|
||||
__all__ = [
|
||||
"BfxBaseException",
|
||||
|
||||
"LabelerSerializerException",
|
||||
"IntegerUnderflowError",
|
||||
"IntegerOverflowflowError"
|
||||
]
|
||||
|
||||
class BfxBaseException(Exception):
|
||||
"""
|
||||
Base class for every custom exception in bfxapi/rest/exceptions.py and bfxapi/websocket/exceptions.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class LabelerSerializerException(BfxBaseException):
|
||||
"""
|
||||
This exception indicates an error thrown by the _Serializer class in bfxapi/labeler.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class IntegerUnderflowError(BfxBaseException):
|
||||
"""
|
||||
This error indicates an underflow in one of the integer types defined in bfxapi/utils/integers.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class IntegerOverflowflowError(BfxBaseException):
|
||||
"""
|
||||
This error indicates an overflow in one of the integer types defined in bfxapi/utils/integers.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
22
bfxapi/labeler.py
Normal file
22
bfxapi/labeler.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from .exceptions import LabelerSerializerException
|
||||
|
||||
from typing import Generic, TypeVar, Iterable, Optional, List, Tuple, Any, cast
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class _Serializer(Generic[T]):
|
||||
def __init__(self, name: str, labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]):
|
||||
self.name, self.__labels, self.__IGNORE = name, labels, IGNORE
|
||||
|
||||
def _serialize(self, *args: Any, skip: Optional[List[str]] = None) -> Iterable[Tuple[str, Any]]:
|
||||
labels = list(filter(lambda label: label not in (skip or list()), self.__labels))
|
||||
|
||||
if len(labels) > len(args):
|
||||
raise LabelerSerializerException("<labels> and <*args> arguments should contain the same amount of elements.")
|
||||
|
||||
for index, label in enumerate(labels):
|
||||
if label not in self.__IGNORE:
|
||||
yield label, args[index]
|
||||
|
||||
def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T:
|
||||
return cast(T, dict(self._serialize(*values, skip=skip)))
|
||||
35
bfxapi/notification.py
Normal file
35
bfxapi/notification.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from typing import List, Dict, Union, Optional, Any, TypedDict, cast
|
||||
|
||||
from .labeler import _Serializer
|
||||
|
||||
class Notification(TypedDict):
|
||||
MTS: int
|
||||
TYPE: str
|
||||
MESSAGE_ID: Optional[int]
|
||||
NOTIFY_INFO: Union[Dict[str, Any], List[Dict[str, Any]]]
|
||||
CODE: Optional[int]
|
||||
STATUS: str
|
||||
TEXT: str
|
||||
|
||||
class _Notification(_Serializer):
|
||||
__LABELS = [ "MTS", "TYPE", "MESSAGE_ID", "_PLACEHOLDER", "NOTIFY_INFO", "CODE", "STATUS", "TEXT" ]
|
||||
|
||||
def __init__(self, serializer: Optional[_Serializer] = None, iterate: bool = False):
|
||||
super().__init__("Notification", _Notification.__LABELS, IGNORE = [ "_PLACEHOLDER" ])
|
||||
|
||||
self.serializer, self.iterate = serializer, iterate
|
||||
|
||||
def parse(self, *values: Any, skip: Optional[List[str]] = None) -> Notification:
|
||||
notification = dict(self._serialize(*values))
|
||||
|
||||
if isinstance(self.serializer, _Serializer):
|
||||
if self.iterate == False:
|
||||
NOTIFY_INFO = notification["NOTIFY_INFO"]
|
||||
|
||||
if len(NOTIFY_INFO) == 1 and isinstance(NOTIFY_INFO[0], list):
|
||||
NOTIFY_INFO = NOTIFY_INFO[0]
|
||||
|
||||
notification["NOTIFY_INFO"] = dict(self.serializer._serialize(*NOTIFY_INFO, skip=skip))
|
||||
else: notification["NOTIFY_INFO"] = [ dict(self.serializer._serialize(*data, skip=skip)) for data in notification["NOTIFY_INFO"] ]
|
||||
|
||||
return cast(Notification, notification)
|
||||
343
bfxapi/rest/BfxRestInterface.py
Normal file
343
bfxapi/rest/BfxRestInterface.py
Normal file
@@ -0,0 +1,343 @@
|
||||
import time, hmac, hashlib, json, requests
|
||||
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
from typing import List, Union, Literal, Optional, Any, cast
|
||||
|
||||
from . import serializers
|
||||
|
||||
from .typings import *
|
||||
from .enums import Config, Sort, OrderType, FundingOfferType, Error
|
||||
from .exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError
|
||||
|
||||
from .. utils.encoder import JSONEncoder
|
||||
|
||||
class BfxRestInterface(object):
|
||||
def __init__(self, host, API_KEY = None, API_SECRET = None):
|
||||
self.public = _RestPublicEndpoints(host=host)
|
||||
|
||||
self.auth = _RestAuthenticatedEndpoints(host=host, API_KEY=API_KEY, API_SECRET=API_SECRET)
|
||||
|
||||
class _Requests(object):
|
||||
def __init__(self, host, API_KEY = None, API_SECRET = None):
|
||||
self.host, self.API_KEY, self.API_SECRET = host, API_KEY, API_SECRET
|
||||
|
||||
def __build_authentication_headers(self, endpoint, data):
|
||||
nonce = str(int(time.time()) * 1000)
|
||||
|
||||
path = f"/api/v2/{endpoint}{nonce}"
|
||||
|
||||
if data != None: path += data
|
||||
|
||||
signature = hmac.new(
|
||||
self.API_SECRET.encode("utf8"),
|
||||
path.encode("utf8"),
|
||||
hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
return {
|
||||
"bfx-nonce": nonce,
|
||||
"bfx-signature": signature,
|
||||
"bfx-apikey": self.API_KEY
|
||||
}
|
||||
|
||||
def _GET(self, endpoint, params = None):
|
||||
response = requests.get(f"{self.host}/{endpoint}", params=params)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.")
|
||||
|
||||
data = response.json()
|
||||
|
||||
if len(data) and data[0] == "error":
|
||||
if data[1] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError("The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
|
||||
def _POST(self, endpoint, params = None, data = None, _append_authentication_headers = True):
|
||||
headers = { "Content-Type": "application/json" }
|
||||
|
||||
if isinstance(data, dict):
|
||||
data = json.dumps({ key: value for key, value in data.items() if value != None}, cls=JSONEncoder)
|
||||
|
||||
if _append_authentication_headers:
|
||||
headers = { **headers, **self.__build_authentication_headers(endpoint, data) }
|
||||
|
||||
response = requests.post(f"{self.host}/{endpoint}", params=params, data=data, headers=headers)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.")
|
||||
|
||||
data = response.json()
|
||||
|
||||
if len(data) and data[0] == "error":
|
||||
if data[1] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == Error.ERR_AUTH_FAIL:
|
||||
raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError(f"The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
|
||||
class _RestPublicEndpoints(_Requests):
|
||||
def get_platform_status(self) -> PlatformStatus:
|
||||
return serializers.PlatformStatus.parse(*self._GET("platform/status"))
|
||||
|
||||
def get_tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._GET("tickers", params={ "symbols": ",".join(symbols) })
|
||||
|
||||
parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
|
||||
|
||||
return [ parsers[subdata[0][0]](*subdata) for subdata in data ]
|
||||
|
||||
def get_t_tickers(self, pairs: Union[List[str], Literal["ALL"]]) -> List[TradingPairTicker]:
|
||||
if isinstance(pairs, str) and pairs == "ALL":
|
||||
return [ cast(TradingPairTicker, subdata) for subdata in self.get_tickers([ "ALL" ]) if cast(str, subdata["SYMBOL"]).startswith("t") ]
|
||||
|
||||
data = self.get_tickers([ "t" + pair for pair in pairs ])
|
||||
|
||||
return cast(List[TradingPairTicker], data)
|
||||
|
||||
def get_f_tickers(self, currencies: Union[List[str], Literal["ALL"]]) -> List[FundingCurrencyTicker]:
|
||||
if isinstance(currencies, str) and currencies == "ALL":
|
||||
return [ cast(FundingCurrencyTicker, subdata) for subdata in self.get_tickers([ "ALL" ]) if cast(str, subdata["SYMBOL"]).startswith("f") ]
|
||||
|
||||
data = self.get_tickers([ "f" + currency for currency in currencies ])
|
||||
|
||||
return cast(List[FundingCurrencyTicker], data)
|
||||
|
||||
def get_t_ticker(self, pair: str) -> TradingPairTicker:
|
||||
return serializers.TradingPairTicker.parse(*self._GET(f"ticker/t{pair}"), skip=["SYMBOL"])
|
||||
|
||||
def get_f_ticker(self, currency: str) -> FundingCurrencyTicker:
|
||||
return serializers.FundingCurrencyTicker.parse(*self._GET(f"ticker/f{currency}"), skip=["SYMBOL"])
|
||||
|
||||
def get_tickers_history(self, symbols: List[str], start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[TickersHistory]:
|
||||
params = {
|
||||
"symbols": ",".join(symbols),
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
data = self._GET("tickers/hist", params=params)
|
||||
|
||||
return [ serializers.TickersHistory.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_t_trades(self, pair: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[TradingPairTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{'t' + pair}/hist", params=params)
|
||||
return [ serializers.TradingPairTrade.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_f_trades(self, currency: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[FundingCurrencyTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{'f' + currency}/hist", params=params)
|
||||
return [ serializers.FundingCurrencyTrade.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_t_book(self, pair: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]:
|
||||
return [ serializers.TradingPairBook.parse(*subdata) for subdata in self._GET(f"book/{'t' + pair}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_f_book(self, currency: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]:
|
||||
return [ serializers.FundingCurrencyBook.parse(*subdata) for subdata in self._GET(f"book/{'f' + currency}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_t_raw_book(self, pair: str, len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]:
|
||||
return [ serializers.TradingPairRawBook.parse(*subdata) for subdata in self._GET(f"book/{'t' + pair}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_f_raw_book(self, currency: str, len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]:
|
||||
return [ serializers.FundingCurrencyRawBook.parse(*subdata) for subdata in self._GET(f"book/{'f' + currency}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_stats_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Statistic]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/hist", params=params)
|
||||
return [ serializers.Statistic.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_stats_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Statistic:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/last", params=params)
|
||||
return serializers.Statistic.parse(*data)
|
||||
|
||||
def get_candles_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Candle]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/{resource}/hist", params=params)
|
||||
return [ serializers.Candle.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_candles_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Candle:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/{resource}/last", params=params)
|
||||
return serializers.Candle.parse(*data)
|
||||
|
||||
def get_derivatives_status(self, type: str, keys: List[str]) -> List[DerivativesStatus]:
|
||||
params = { "keys": ",".join(keys) }
|
||||
|
||||
data = self._GET(f"status/{type}", params=params)
|
||||
|
||||
return [ serializers.DerivativesStatus.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_derivatives_status_history(
|
||||
self,
|
||||
type: str, symbol: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[DerivativesStatus]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET(f"status/{type}/{symbol}/hist", params=params)
|
||||
|
||||
return [ serializers.DerivativesStatus.parse(*subdata, skip=[ "KEY" ]) for subdata in data ]
|
||||
|
||||
def get_liquidations(self, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Liquidation]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET("liquidations/hist", params=params)
|
||||
|
||||
return [ serializers.Liquidation.parse(*subdata[0]) for subdata in data ]
|
||||
|
||||
def get_leaderboards_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Leaderboard]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/hist", params=params)
|
||||
return [ serializers.Leaderboard.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_leaderboards_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Leaderboard:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/last", params=params)
|
||||
return serializers.Leaderboard.parse(*data)
|
||||
|
||||
def get_funding_stats(self, symbol: str, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingStatistic]:
|
||||
params = { "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET(f"funding/stats/{symbol}/hist", params=params)
|
||||
|
||||
return [ serializers.FundingStatistic.parse(*subdata) for subdata in data ]
|
||||
|
||||
def conf(self, config: Config) -> Any:
|
||||
return self._GET(f"conf/{config}")[0]
|
||||
|
||||
class _RestAuthenticatedEndpoints(_Requests):
|
||||
def get_wallets(self) -> List[Wallet]:
|
||||
return [ serializers.Wallet.parse(*subdata) for subdata in self._POST("auth/r/wallets") ]
|
||||
|
||||
def get_orders(self, ids: Optional[List[str]] = None) -> List[Order]:
|
||||
return [ serializers.Order.parse(*subdata) for subdata in self._POST("auth/r/orders", data={ "id": ids }) ]
|
||||
|
||||
def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str],
|
||||
price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None,
|
||||
gid: Optional[int] = None, cid: Optional[int] = None,
|
||||
flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification:
|
||||
data = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"price": price, "lev": lev,
|
||||
"price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop,
|
||||
"gid": gid, "cid": cid,
|
||||
"flags": flags, "tif": tif, "meta": meta
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/submit", data=data))
|
||||
|
||||
def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, str]] = None,
|
||||
cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None,
|
||||
flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, str]] = None,
|
||||
price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification:
|
||||
data = {
|
||||
"id": id, "amount": amount, "price": price,
|
||||
"cid": cid, "cid_date": cid_date, "gid": gid,
|
||||
"flags": flags, "lev": lev, "delta": delta,
|
||||
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/update", data=data))
|
||||
|
||||
def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None) -> Notification:
|
||||
data = {
|
||||
"id": id,
|
||||
"cid": cid,
|
||||
"cid_date": cid_date
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/cancel", data=data))
|
||||
|
||||
def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False) -> Notification:
|
||||
data = {
|
||||
"ids": ids,
|
||||
"cids": cids,
|
||||
"gids": gids,
|
||||
|
||||
"all": int(all)
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order, iterate=True).parse(*self._POST("auth/w/order/cancel/multi", data=data))
|
||||
|
||||
def get_orders_history(self, symbol: Optional[str] = None, ids: Optional[List[int]] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Order]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/orders/hist"
|
||||
else: endpoint = f"auth/r/orders/{symbol}/hist"
|
||||
|
||||
data = {
|
||||
"id": ids,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Order.parse(*subdata) for subdata in self._POST(endpoint, data=data) ]
|
||||
|
||||
def get_trades(self, symbol: str) -> List[Trade]:
|
||||
return [ serializers.Trade.parse(*subdata) for subdata in self._POST(f"auth/r/trades/{symbol}/hist") ]
|
||||
|
||||
def get_ledgers(self, currency: str, category: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Ledger]:
|
||||
data = {
|
||||
"category": category,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Ledger.parse(*subdata) for subdata in self._POST(f"auth/r/ledgers/{currency}/hist", data=data) ]
|
||||
|
||||
def get_active_funding_offers(self, symbol: Optional[str] = None) -> List[FundingOffer]:
|
||||
endpoint = "auth/r/funding/offers"
|
||||
|
||||
if symbol != None:
|
||||
endpoint += f"/{symbol}"
|
||||
|
||||
return [ serializers.FundingOffer.parse(*subdata) for subdata in self._POST(endpoint) ]
|
||||
|
||||
def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str],
|
||||
rate: Union[Decimal, str], period: int,
|
||||
flags: Optional[int] = 0) -> Notification:
|
||||
data = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period,
|
||||
"flags": flags
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/submit", data=data))
|
||||
1
bfxapi/rest/__init__.py
Normal file
1
bfxapi/rest/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .BfxRestInterface import BfxRestInterface
|
||||
36
bfxapi/rest/enums.py
Normal file
36
bfxapi/rest/enums.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from ..enums import *
|
||||
|
||||
class Config(str, Enum):
|
||||
MAP_CURRENCY_SYM = "pub:map:currency:sym"
|
||||
MAP_CURRENCY_LABEL = "pub:map:currency:label"
|
||||
MAP_CURRENCY_UNIT = "pub:map:currency:unit"
|
||||
MAP_CURRENCY_UNDL = "pub:map:currency:undl"
|
||||
MAP_CURRENCY_POOL = "pub:map:currency:pool"
|
||||
MAP_CURRENCY_EXPLORER = "pub:map:currency:explorer"
|
||||
MAP_CURRENCY_TX_FEE = "pub:map:currency:tx:fee"
|
||||
MAP_TX_METHOD = "pub:map:tx:method"
|
||||
|
||||
LIST_PAIR_EXCHANGE = "pub:list:pair:exchange"
|
||||
LIST_PAIR_MARGIN = "pub:list:pair:margin"
|
||||
LIST_PAIR_FUTURES = "pub:list:pair:futures"
|
||||
LIST_PAIR_SECURITIES = "pub:list:pair:securities"
|
||||
LIST_CURRENCY = "pub:list:currency"
|
||||
LIST_COMPETITIONS = "pub:list:competitions"
|
||||
|
||||
INFO_PAIR = "pub:info:pair"
|
||||
INFO_PAIR_FUTURES = "pub:info:pair:futures"
|
||||
INFO_TX_STATUS = "pub:info:tx:status"
|
||||
|
||||
SPEC_MARGIN = "pub:spec:margin",
|
||||
FEES = "pub:fees"
|
||||
|
||||
class Precision(str, Enum):
|
||||
P0 = "P0"
|
||||
P1 = "P1"
|
||||
P2 = "P2"
|
||||
P3 = "P3"
|
||||
P4 = "P4"
|
||||
|
||||
class Sort(int, Enum):
|
||||
ASCENDING = +1
|
||||
DESCENDING = -1
|
||||
44
bfxapi/rest/exceptions.py
Normal file
44
bfxapi/rest/exceptions.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from .. exceptions import BfxBaseException
|
||||
|
||||
__all__ = [
|
||||
"BfxRestException",
|
||||
|
||||
"RequestParametersError",
|
||||
"ResourceNotFound",
|
||||
"InvalidAuthenticationCredentials"
|
||||
]
|
||||
|
||||
class BfxRestException(BfxBaseException):
|
||||
"""
|
||||
Base class for all custom exceptions in bfxapi/rest/exceptions.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class ResourceNotFound(BfxRestException):
|
||||
"""
|
||||
This error indicates a failed HTTP request to a non-existent resource.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class RequestParametersError(BfxRestException):
|
||||
"""
|
||||
This error indicates that there are some invalid parameters sent along with an HTTP request.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class InvalidAuthenticationCredentials(BfxRestException):
|
||||
"""
|
||||
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class UnknownGenericError(BfxRestException):
|
||||
"""
|
||||
This error indicates an undefined problem processing an HTTP request sent to the APIs.
|
||||
"""
|
||||
|
||||
pass
|
||||
288
bfxapi/rest/serializers.py
Normal file
288
bfxapi/rest/serializers.py
Normal file
@@ -0,0 +1,288 @@
|
||||
from . import typings
|
||||
|
||||
from .. labeler import _Serializer
|
||||
|
||||
from .. notification import _Notification
|
||||
|
||||
#region Serializers definition for Rest Public Endpoints
|
||||
|
||||
PlatformStatus = _Serializer[typings.PlatformStatus]("PlatformStatus", labels=[
|
||||
"OPERATIVE"
|
||||
])
|
||||
|
||||
TradingPairTicker = _Serializer[typings.TradingPairTicker]("TradingPairTicker", labels=[
|
||||
"SYMBOL",
|
||||
"BID",
|
||||
"BID_SIZE",
|
||||
"ASK",
|
||||
"ASK_SIZE",
|
||||
"DAILY_CHANGE",
|
||||
"DAILY_CHANGE_RELATIVE",
|
||||
"LAST_PRICE",
|
||||
"VOLUME",
|
||||
"HIGH",
|
||||
"LOW"
|
||||
])
|
||||
|
||||
FundingCurrencyTicker = _Serializer[typings.FundingCurrencyTicker]("FundingCurrencyTicker", labels=[
|
||||
"SYMBOL",
|
||||
"FRR",
|
||||
"BID",
|
||||
"BID_PERIOD",
|
||||
"BID_SIZE",
|
||||
"ASK",
|
||||
"ASK_PERIOD",
|
||||
"ASK_SIZE",
|
||||
"DAILY_CHANGE",
|
||||
"DAILY_CHANGE_RELATIVE",
|
||||
"LAST_PRICE",
|
||||
"VOLUME",
|
||||
"HIGH",
|
||||
"LOW",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FRR_AMOUNT_AVAILABLE"
|
||||
])
|
||||
|
||||
TickersHistory = _Serializer[typings.TickersHistory]("TickersHistory", labels=[
|
||||
"SYMBOL",
|
||||
"BID",
|
||||
"_PLACEHOLDER",
|
||||
"ASK",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MTS"
|
||||
])
|
||||
|
||||
TradingPairTrade = _Serializer[typings.TradingPairTrade]("TradingPairTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"PRICE"
|
||||
])
|
||||
|
||||
FundingCurrencyTrade = _Serializer[typings.FundingCurrencyTrade]("FundingCurrencyTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"RATE",
|
||||
"PERIOD"
|
||||
])
|
||||
|
||||
TradingPairBook = _Serializer[typings.TradingPairBook]("TradingPairBook", labels=[
|
||||
"PRICE",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
])
|
||||
|
||||
FundingCurrencyBook = _Serializer[typings.FundingCurrencyBook]("FundingCurrencyBook", labels=[
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
])
|
||||
|
||||
TradingPairRawBook = _Serializer[typings.TradingPairRawBook]("TradingPairRawBook", labels=[
|
||||
"ORDER_ID",
|
||||
"PRICE",
|
||||
"AMOUNT"
|
||||
])
|
||||
|
||||
FundingCurrencyRawBook = _Serializer[typings.FundingCurrencyRawBook]("FundingCurrencyRawBook", labels=[
|
||||
"OFFER_ID",
|
||||
"PERIOD",
|
||||
"RATE",
|
||||
"AMOUNT"
|
||||
])
|
||||
|
||||
Statistic = _Serializer[typings.Statistic]("Statistic", labels=[
|
||||
"MTS",
|
||||
"VALUE"
|
||||
])
|
||||
|
||||
Candle = _Serializer[typings.Candle]("Candle", labels=[
|
||||
"MTS",
|
||||
"OPEN",
|
||||
"CLOSE",
|
||||
"HIGH",
|
||||
"LOW",
|
||||
"VOLUME"
|
||||
])
|
||||
|
||||
DerivativesStatus = _Serializer[typings.DerivativesStatus]("DerivativesStatus", labels=[
|
||||
"KEY",
|
||||
"MTS",
|
||||
"_PLACEHOLDER",
|
||||
"DERIV_PRICE",
|
||||
"SPOT_PRICE",
|
||||
"_PLACEHOLDER",
|
||||
"INSURANCE_FUND_BALANCE",
|
||||
"_PLACEHOLDER",
|
||||
"NEXT_FUNDING_EVT_TIMESTAMP_MS",
|
||||
"NEXT_FUNDING_ACCRUED",
|
||||
"NEXT_FUNDING_STEP",
|
||||
"_PLACEHOLDER",
|
||||
"CURRENT_FUNDING",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MARK_PRICE",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"OPEN_INTEREST",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"CLAMP_MIN",
|
||||
"CLAMP_MAX"
|
||||
])
|
||||
|
||||
Liquidation = _Serializer[typings.Liquidation]("Liquidation", labels=[
|
||||
"_PLACEHOLDER",
|
||||
"POS_ID",
|
||||
"MTS",
|
||||
"_PLACEHOLDER",
|
||||
"SYMBOL",
|
||||
"AMOUNT",
|
||||
"BASE_PRICE",
|
||||
"_PLACEHOLDER",
|
||||
"IS_MATCH",
|
||||
"IS_MARKET_SOLD",
|
||||
"_PLACEHOLDER",
|
||||
"PRICE_ACQUIRED"
|
||||
])
|
||||
|
||||
Leaderboard = _Serializer[typings.Leaderboard]("Leaderboard", labels=[
|
||||
"MTS",
|
||||
"_PLACEHOLDER",
|
||||
"USERNAME",
|
||||
"RANKING",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"VALUE",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"TWITTER_HANDLE"
|
||||
])
|
||||
|
||||
FundingStatistic = _Serializer[typings.FundingStatistic]("FundingStatistic", labels=[
|
||||
"TIMESTAMP",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FRR",
|
||||
"AVG_PERIOD",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FUNDING_AMOUNT",
|
||||
"FUNDING_AMOUNT_USED",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FUNDING_BELOW_THRESHOLD"
|
||||
])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serializers definition for Rest Authenticated Endpoints
|
||||
|
||||
Wallet = _Serializer[typings.Wallet]("Wallet", labels=[
|
||||
"WALLET_TYPE",
|
||||
"CURRENCY",
|
||||
"BALANCE",
|
||||
"UNSETTLED_INTEREST",
|
||||
"AVAILABLE_BALANCE",
|
||||
"LAST_CHANGE",
|
||||
"TRADE_DETAILS"
|
||||
])
|
||||
|
||||
Order = _Serializer[typings.Order]("Order", labels=[
|
||||
"ID",
|
||||
"GID",
|
||||
"CID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"ORDER_TYPE",
|
||||
"TYPE_PREV",
|
||||
"MTS_TIF",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"ORDER_STATUS",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"PRICE",
|
||||
"PRICE_AVG",
|
||||
"PRICE_TRAILING",
|
||||
"PRICE_AUX_LIMIT",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"PLACED_ID",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"ROUTING",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"META"
|
||||
])
|
||||
|
||||
FundingOffer = _Serializer[typings.FundingOffer]("FundingOffer", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATED",
|
||||
"MTS_UPDATED",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"OFFER_TYPE",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"OFFER_STATUS",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"_PLACEHOLDER"
|
||||
])
|
||||
|
||||
Trade = _Serializer[typings.Trade]("Trade", labels=[
|
||||
"ID",
|
||||
"PAIR",
|
||||
"MTS_CREATE",
|
||||
"ORDER_ID",
|
||||
"EXEC_AMOUNT",
|
||||
"EXEC_PRICE",
|
||||
"ORDER_TYPE",
|
||||
"ORDER_PRICE",
|
||||
"MAKER",
|
||||
"FEE",
|
||||
"FEE_CURRENCY",
|
||||
"CID"
|
||||
])
|
||||
|
||||
Ledger = _Serializer[typings.Ledger]("Ledger", labels=[
|
||||
"ID",
|
||||
"CURRENCY",
|
||||
"_PLACEHOLDER",
|
||||
"MTS",
|
||||
"_PLACEHOLDER",
|
||||
"AMOUNT",
|
||||
"BALANCE",
|
||||
"_PLACEHOLDER",
|
||||
"DESCRIPTION"
|
||||
])
|
||||
|
||||
#endregion
|
||||
210
bfxapi/rest/typings.py
Normal file
210
bfxapi/rest/typings.py
Normal file
@@ -0,0 +1,210 @@
|
||||
from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any
|
||||
|
||||
from .. notification import Notification
|
||||
|
||||
JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
|
||||
|
||||
#region Type hinting for Rest Public Endpoints
|
||||
|
||||
class PlatformStatus(TypedDict):
|
||||
OPERATIVE: int
|
||||
|
||||
class TradingPairTicker(TypedDict):
|
||||
SYMBOL: Optional[str]
|
||||
BID: float
|
||||
BID_SIZE: float
|
||||
ASK: float
|
||||
ASK_SIZE: float
|
||||
DAILY_CHANGE: float
|
||||
DAILY_CHANGE_RELATIVE: float
|
||||
LAST_PRICE: float
|
||||
VOLUME: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
SYMBOL: Optional[str]
|
||||
FRR: float
|
||||
BID: float
|
||||
BID_PERIOD: int
|
||||
BID_SIZE: float
|
||||
ASK: float
|
||||
ASK_PERIOD: int
|
||||
ASK_SIZE: float
|
||||
DAILY_CHANGE: float
|
||||
DAILY_CHANGE_RELATIVE: float
|
||||
LAST_PRICE: float
|
||||
VOLUME: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
FRR_AMOUNT_AVAILABLE: float
|
||||
|
||||
class TickersHistory(TypedDict):
|
||||
SYMBOL: str
|
||||
BID: float
|
||||
ASK: float
|
||||
MTS: int
|
||||
|
||||
class TradingPairTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
PRICE: float
|
||||
|
||||
class FundingCurrencyTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
|
||||
class TradingPairBook(TypedDict):
|
||||
PRICE: float
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyBook(TypedDict):
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class TradingPairRawBook(TypedDict):
|
||||
ORDER_ID: int
|
||||
PRICE: float
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyRawBook(TypedDict):
|
||||
OFFER_ID: int
|
||||
PERIOD: int
|
||||
RATE: float
|
||||
AMOUNT: float
|
||||
|
||||
class Statistic(TypedDict):
|
||||
MTS: int
|
||||
VALUE: float
|
||||
|
||||
class Candle(TypedDict):
|
||||
MTS: int
|
||||
OPEN: float
|
||||
CLOSE: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
VOLUME: float
|
||||
|
||||
class DerivativesStatus(TypedDict):
|
||||
KEY: Optional[str]
|
||||
MTS: int
|
||||
DERIV_PRICE: float
|
||||
SPOT_PRICE: float
|
||||
INSURANCE_FUND_BALANCE: float
|
||||
NEXT_FUNDING_EVT_TIMESTAMP_MS: int
|
||||
NEXT_FUNDING_ACCRUED: float
|
||||
NEXT_FUNDING_STEP: int
|
||||
CURRENT_FUNDING: float
|
||||
MARK_PRICE: float
|
||||
OPEN_INTEREST: float
|
||||
CLAMP_MIN: float
|
||||
CLAMP_MAX: float
|
||||
|
||||
class Liquidation(TypedDict):
|
||||
POS_ID: int
|
||||
MTS: int
|
||||
SYMBOL: str
|
||||
AMOUNT: float
|
||||
BASE_PRICE: float
|
||||
IS_MATCH: int
|
||||
IS_MARKET_SOLD: int
|
||||
PRICE_ACQUIRED: float
|
||||
|
||||
class Leaderboard(TypedDict):
|
||||
MTS: int
|
||||
USERNAME: str
|
||||
RANKING: int
|
||||
VALUE: float
|
||||
TWITTER_HANDLE: Optional[str]
|
||||
|
||||
class FundingStatistic(TypedDict):
|
||||
TIMESTAMP: int
|
||||
FRR: float
|
||||
AVG_PERIOD: float
|
||||
FUNDING_AMOUNT: float
|
||||
FUNDING_AMOUNT_USED: float
|
||||
FUNDING_BELOW_THRESHOLD: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Rest Authenticated Endpoints
|
||||
|
||||
class Wallet(TypedDict):
|
||||
WALLET_TYPE: str
|
||||
CURRENCY: str
|
||||
BALANCE: float
|
||||
UNSETTLED_INTEREST: float
|
||||
AVAILABLE_BALANCE: float
|
||||
LAST_CHANGE: str
|
||||
TRADE_DETAILS: JSON
|
||||
|
||||
class Order(TypedDict):
|
||||
ID: int
|
||||
GID: int
|
||||
CID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
ORDER_TYPE: str
|
||||
TYPE_PREV: str
|
||||
MTS_TIF: int
|
||||
FLAGS: int
|
||||
ORDER_STATUS: str
|
||||
PRICE: float
|
||||
PRICE_AVG: float
|
||||
PRICE_TRAILING: float
|
||||
PRICE_AUX_LIMIT: float
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
PLACED_ID: int
|
||||
ROUTING: str
|
||||
META: JSON
|
||||
|
||||
class FundingOffer(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
OFFER_TYPE: str
|
||||
FLAGS: int
|
||||
OFFER_STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
NOTIFY: bool
|
||||
HIDDEN: int
|
||||
RENEW: bool
|
||||
|
||||
class Trade(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
FEE: float
|
||||
FEE_CURRENCY: str
|
||||
CID: int
|
||||
|
||||
class Ledger(TypedDict):
|
||||
ID: int
|
||||
CURRENCY: str
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
BALANCE: float
|
||||
description: str
|
||||
|
||||
#endregion
|
||||
4
bfxapi/utils/cid.py
Normal file
4
bfxapi/utils/cid.py
Normal file
@@ -0,0 +1,4 @@
|
||||
import time
|
||||
|
||||
def generate_unique_cid(multiplier: int = 1000) -> int:
|
||||
return int(round(time.time() * multiplier))
|
||||
9
bfxapi/utils/encoder.py
Normal file
9
bfxapi/utils/encoder.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal) or isinstance(obj, datetime):
|
||||
return str(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
29
bfxapi/utils/flags.py
Normal file
29
bfxapi/utils/flags.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from .. enums import Flag
|
||||
|
||||
def calculate_order_flags(
|
||||
hidden : bool = False,
|
||||
close : bool = False,
|
||||
reduce_only : bool = False,
|
||||
post_only : bool = False,
|
||||
oco : bool = False,
|
||||
no_var_rates: bool = False
|
||||
) -> int:
|
||||
flags = 0
|
||||
|
||||
if hidden: flags += Flag.HIDDEN
|
||||
if close: flags += Flag.CLOSE
|
||||
if reduce_only: flags += Flag.REDUCE_ONLY
|
||||
if post_only: flags += Flag.POST_ONLY
|
||||
if oco: flags += Flag.OCO
|
||||
if no_var_rates: flags += Flag.NO_VAR_RATES
|
||||
|
||||
return flags
|
||||
|
||||
def calculate_offer_flags(
|
||||
hidden : bool = False
|
||||
) -> int:
|
||||
flags = 0
|
||||
|
||||
if hidden: flags += Flag.HIDDEN
|
||||
|
||||
return flags
|
||||
43
bfxapi/utils/integers.py
Normal file
43
bfxapi/utils/integers.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from typing import cast, TypeVar, Union
|
||||
|
||||
from .. exceptions import IntegerUnderflowError, IntegerOverflowflowError
|
||||
|
||||
__all__ = [ "Int16", "Int32", "Int45", "Int64" ]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class _Int(int):
|
||||
def __new__(cls: T, integer: int) -> T:
|
||||
assert hasattr(cls, "_BITS"), "_Int must be extended by a class that has a static member _BITS (indicating the number of bits with which to represent the integers)."
|
||||
|
||||
bits = cls._BITS - 1
|
||||
|
||||
min, max = -(2 ** bits), (2 ** bits) - 1
|
||||
|
||||
if integer < min:
|
||||
raise IntegerUnderflowError(f"Underflow. Cannot store <{integer}> in {cls._BITS} bits integer. The min and max bounds are {min} and {max}.")
|
||||
|
||||
if integer > max:
|
||||
raise IntegerOverflowflowError(f"Overflow. Cannot store <{integer}> in {cls._BITS} bits integer. The min and max bounds are {min} and {max}.")
|
||||
|
||||
return cast(T, super().__new__(int, integer))
|
||||
|
||||
class Int16(_Int):
|
||||
_BITS = 16
|
||||
|
||||
int16 = Union[Int16, int]
|
||||
|
||||
class Int32(_Int):
|
||||
_BITS = 32
|
||||
|
||||
int32 = Union[Int32, int]
|
||||
|
||||
class Int45(_Int):
|
||||
_BITS = 45
|
||||
|
||||
int45 = Union[Int45, int]
|
||||
|
||||
class Int64(_Int):
|
||||
_BITS = 64
|
||||
|
||||
int64 = Union[Int64, int]
|
||||
@@ -1,25 +1,40 @@
|
||||
import traceback, json, asyncio, hmac, hashlib, time, uuid, websockets
|
||||
|
||||
from typing import Tuple, Union, Literal, TypeVar, Callable, cast
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pyee.asyncio import AsyncIOEventEmitter
|
||||
|
||||
from .typings import Inputs, Tuple, Union
|
||||
from .typings import Inputs
|
||||
from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHandler
|
||||
from .exceptions import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion
|
||||
|
||||
from ..utils.encoder import JSONEncoder
|
||||
|
||||
from ..utils.logger import Formatter, CustomLogger
|
||||
|
||||
_HEARTBEAT = "hb"
|
||||
|
||||
def _require_websocket_connection(function):
|
||||
F = TypeVar("F", bound=Callable[..., Literal[None]])
|
||||
|
||||
def _require_websocket_connection(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.websocket == None or self.websocket.open == False:
|
||||
raise ConnectionNotOpen("No open connection with the server.")
|
||||
|
||||
await function(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return cast(F, wrapper)
|
||||
|
||||
def _require_websocket_authentication(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.authentication == False:
|
||||
raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.")
|
||||
|
||||
await _require_websocket_connection(function)(self, *args, **kwargs)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
class BfxWebsocketClient(object):
|
||||
VERSION = 2
|
||||
@@ -118,22 +133,13 @@ class BfxWebsocketClient(object):
|
||||
for bucket in self.buckets:
|
||||
await bucket._close(code=code, reason=reason)
|
||||
|
||||
def __require_websocket_authentication(function):
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.authentication == False:
|
||||
raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.")
|
||||
|
||||
await _require_websocket_connection(function)(self, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
@__require_websocket_authentication
|
||||
@_require_websocket_authentication
|
||||
async def notify(self, info, MESSAGE_ID=None, **kwargs):
|
||||
await self.websocket.send(json.dumps([ 0, "n", MESSAGE_ID, { "type": "ucm-test", "info": info, **kwargs } ]))
|
||||
|
||||
@__require_websocket_authentication
|
||||
@_require_websocket_authentication
|
||||
async def __handle_websocket_input(self, input, data):
|
||||
await self.websocket.send(json.dumps([ 0, input, None, data]))
|
||||
await self.websocket.send(json.dumps([ 0, input, None, data], cls=JSONEncoder))
|
||||
|
||||
def __bucket_open_signal(self, index):
|
||||
if all(bucket.websocket != None and bucket.websocket.open == True for bucket in self.buckets):
|
||||
|
||||
@@ -1,33 +1,8 @@
|
||||
from enum import Enum
|
||||
from ..enums import *
|
||||
|
||||
class Channels(str, Enum):
|
||||
TICKER = "ticker"
|
||||
TRADES = "trades"
|
||||
BOOK = "book"
|
||||
CANDLES = "candles"
|
||||
STATUS = "status"
|
||||
|
||||
class Flags(int, Enum):
|
||||
HIDDEN = 64
|
||||
CLOSE = 512
|
||||
REDUCE_ONLY = 1024
|
||||
POST_ONLY = 4096
|
||||
OCO = 16384
|
||||
NO_VAR_RATES = 524288
|
||||
|
||||
class Errors(int, Enum):
|
||||
ERR_UNK = 10000
|
||||
ERR_GENERIC = 10001
|
||||
ERR_CONCURRENCY = 10008
|
||||
ERR_PARAMS = 10020
|
||||
ERR_CONF_FAIL = 10050
|
||||
ERR_AUTH_FAIL = 10100
|
||||
ERR_AUTH_PAYLOAD = 10111
|
||||
ERR_AUTH_SIG = 10112
|
||||
ERR_AUTH_HMAC = 10113
|
||||
ERR_AUTH_NONCE = 10114
|
||||
ERR_UNAUTH_FAIL = 10200
|
||||
ERR_SUB_FAIL = 10300
|
||||
ERR_SUB_MULTI = 10301
|
||||
ERR_UNSUB_FAIL = 10400
|
||||
ERR_READY = 11000
|
||||
STATUS = "status"
|
||||
@@ -1,4 +1,8 @@
|
||||
from .. exceptions import BfxBaseException
|
||||
|
||||
__all__ = [
|
||||
"BfxWebsocketException",
|
||||
|
||||
"ConnectionNotOpen",
|
||||
"TooManySubscriptions",
|
||||
"WebsocketAuthenticationRequired",
|
||||
@@ -7,9 +11,9 @@ __all__ = [
|
||||
"OutdatedClientVersion"
|
||||
]
|
||||
|
||||
class BfxWebsocketException(Exception):
|
||||
class BfxWebsocketException(BfxBaseException):
|
||||
"""
|
||||
Base class for all exceptions defined in bfx/websocket/errors.py.
|
||||
Base class for all custom exceptions in bfxapi/websocket/exceptions.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -35,13 +39,6 @@ class WebsocketAuthenticationRequired(BfxWebsocketException):
|
||||
|
||||
pass
|
||||
|
||||
class InvalidAuthenticationCredentials(BfxWebsocketException):
|
||||
"""
|
||||
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class EventNotSupported(BfxWebsocketException):
|
||||
"""
|
||||
This error indicates a failed attempt to subscribe to an event not supported by the BfxWebsocketClient.
|
||||
@@ -54,4 +51,11 @@ class OutdatedClientVersion(BfxWebsocketException):
|
||||
This error indicates a mismatch between the client version and the server WSS version.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class InvalidAuthenticationCredentials(BfxWebsocketException):
|
||||
"""
|
||||
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,25 +1,6 @@
|
||||
from typing import Generic, TypeVar, Iterable, List, Any
|
||||
|
||||
from . import typings
|
||||
|
||||
from .exceptions import BfxWebsocketException
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class _Serializer(Generic[T]):
|
||||
def __init__(self, name: str, labels: List[str]):
|
||||
self.name, self.__labels = name, labels
|
||||
|
||||
def __serialize(self, *args: Any, IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> Iterable[T]:
|
||||
if len(self.__labels) != len(args):
|
||||
raise BfxWebsocketException("<self.__labels> and <*args> arguments should contain the same amount of elements.")
|
||||
|
||||
for index, label in enumerate(self.__labels):
|
||||
if label not in IGNORE:
|
||||
yield label, args[index]
|
||||
|
||||
def parse(self, *values: Any) -> T:
|
||||
return dict(self.__serialize(*values))
|
||||
from .. labeler import _Serializer
|
||||
|
||||
#region Serializers definition for Websocket Public Channels
|
||||
|
||||
@@ -315,7 +296,7 @@ BalanceInfo = _Serializer[typings.BalanceInfo]("BalanceInfo", labels=[
|
||||
|
||||
#region Serializers definition for Notifications channel
|
||||
|
||||
Notification = _Serializer("Notification", labels=[
|
||||
Notification = _Serializer[typings.Notification]("Notification", labels=[
|
||||
"MTS",
|
||||
"TYPE",
|
||||
"MESSAGE_ID",
|
||||
|
||||
@@ -4,299 +4,292 @@ from datetime import datetime
|
||||
|
||||
from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any
|
||||
|
||||
int16 = int32 = int45 = int64 = int
|
||||
from ..utils.integers import Int16, Int32, Int45, Int64
|
||||
|
||||
JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
|
||||
|
||||
#region Type hinting for subscription objects
|
||||
|
||||
class Subscriptions:
|
||||
TradingPairsTicker = TypedDict("Subscriptions.TradingPairsTicker", {
|
||||
"chanId": int,
|
||||
"symbol": str,
|
||||
"pair": str
|
||||
})
|
||||
class TradingPairTicker(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
pair: str
|
||||
|
||||
FundingCurrenciesTicker = TypedDict("Subscriptions.FundingCurrenciesTicker", {
|
||||
"chanId": int,
|
||||
"symbol": str,
|
||||
"currency": str
|
||||
})
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
currency: str
|
||||
|
||||
TradingPairsTrades = TypedDict("Subscriptions.TradingPairsTrades", {
|
||||
"chanId": int,
|
||||
"symbol": str,
|
||||
"pair": str
|
||||
})
|
||||
class TradingPairTrades(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
pair: str
|
||||
|
||||
FundingCurrenciesTrades = TypedDict("Subscriptions.FundingCurrenciesTrades", {
|
||||
"chanId": int,
|
||||
"symbol": str,
|
||||
"currency": str
|
||||
})
|
||||
class FundingCurrencyTrades(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
currency: str
|
||||
|
||||
Book = TypedDict("Subscriptions.Book", {
|
||||
"chanId": int,
|
||||
"symbol": str,
|
||||
"prec": str,
|
||||
"freq": str,
|
||||
"len": str,
|
||||
"subId": int,
|
||||
"pair": str
|
||||
})
|
||||
class Book(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
prec: str
|
||||
freq: str
|
||||
len: str
|
||||
subId: int
|
||||
pair: str
|
||||
|
||||
Candles = TypedDict("Subscriptions.Candles", {
|
||||
"chanId": int,
|
||||
"key": str
|
||||
})
|
||||
class Candles(TypedDict):
|
||||
chanId: int
|
||||
key: str
|
||||
|
||||
DerivativesStatus = TypedDict("Subscriptions.DerivativesStatus", {
|
||||
"chanId": int,
|
||||
"key": str
|
||||
})
|
||||
class DerivativesStatus(TypedDict):
|
||||
chanId: int
|
||||
key: str
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Websocket Public Channels
|
||||
|
||||
TradingPairTicker = TypedDict("TradingPairTicker", {
|
||||
"BID": float,
|
||||
"BID_SIZE": float,
|
||||
"ASK": float,
|
||||
"ASK_SIZE": float,
|
||||
"DAILY_CHANGE": float,
|
||||
"DAILY_CHANGE_RELATIVE": float,
|
||||
"LAST_PRICE": float,
|
||||
"VOLUME": float,
|
||||
"HIGH": float,
|
||||
"LOW": float
|
||||
})
|
||||
class TradingPairTicker(TypedDict):
|
||||
BID: float
|
||||
BID_SIZE: float
|
||||
ASK: float
|
||||
ASK_SIZE: float
|
||||
DAILY_CHANGE: float
|
||||
DAILY_CHANGE_RELATIVE: float
|
||||
LAST_PRICE: float
|
||||
VOLUME: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
|
||||
FundingCurrencyTicker = TypedDict("FundingCurrencyTicker", {
|
||||
"FRR": float,
|
||||
"BID": float,
|
||||
"BID_PERIOD": int,
|
||||
"BID_SIZE": float,
|
||||
"ASK": float,
|
||||
"ASK_PERIOD": int,
|
||||
"ASK_SIZE": float,
|
||||
"DAILY_CHANGE": float,
|
||||
"DAILY_CHANGE_RELATIVE": float,
|
||||
"LAST_PRICE": float,
|
||||
"VOLUME": float,
|
||||
"HIGH": float,
|
||||
"LOW": float,
|
||||
"FRR_AMOUNT_AVAILABLE": float
|
||||
})
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
FRR: float
|
||||
BID: float
|
||||
BID_PERIOD: int
|
||||
BID_SIZE: float
|
||||
ASK: float
|
||||
ASK_PERIOD: int
|
||||
ASK_SIZE: float
|
||||
DAILY_CHANGE: float
|
||||
DAILY_CHANGE_RELATIVE: float
|
||||
LAST_PRICE: float
|
||||
VOLUME: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
FRR_AMOUNT_AVAILABLE: float
|
||||
|
||||
(TradingPairTrade, FundingCurrencyTrade) = (
|
||||
TypedDict("TradingPairTrade", { "ID": int, "MTS": int, "AMOUNT": float, "PRICE": float }),
|
||||
TypedDict("FundingCurrencyTrade", { "ID": int, "MTS": int, "AMOUNT": float, "RATE": float, "PERIOD": int })
|
||||
)
|
||||
class TradingPairTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
PRICE: float
|
||||
|
||||
(TradingPairTrades, FundingCurrencyTrades) = (List[TradingPairTrade], List[FundingCurrencyTrade])
|
||||
class FundingCurrencyTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
|
||||
(TradingPairBook, FundingCurrencyBook) = (
|
||||
TypedDict("TradingPairBook", { "PRICE": float, "COUNT": int, "AMOUNT": float }),
|
||||
TypedDict("FundingCurrencyBook", { "RATE": float, "PERIOD": int, "COUNT": int, "AMOUNT": float })
|
||||
)
|
||||
class TradingPairBook(TypedDict):
|
||||
PRICE: float
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyBook(TypedDict):
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class TradingPairRawBook(TypedDict):
|
||||
ORDER_ID: int
|
||||
PRICE: float
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyRawBook(TypedDict):
|
||||
OFFER_ID: int
|
||||
PERIOD: int
|
||||
RATE: float
|
||||
AMOUNT: float
|
||||
|
||||
(TradingPairBooks, FundingCurrencyBooks) = (List[TradingPairBook], List[FundingCurrencyBook])
|
||||
class Candle(TypedDict):
|
||||
MTS: int
|
||||
OPEN: float
|
||||
CLOSE: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
VOLUME: float
|
||||
|
||||
(TradingPairRawBook, FundingCurrencyRawBook) = (
|
||||
TypedDict("TradingPairRawBook", { "ORDER_ID": int, "PRICE": float, "AMOUNT": float }),
|
||||
TypedDict("FundingCurrencyRawBook", { "OFFER_ID": int, "PERIOD": int, "RATE": float, "AMOUNT": float }),
|
||||
)
|
||||
|
||||
(TradingPairRawBooks, FundingCurrencyRawBooks) = (List[TradingPairRawBook], List[FundingCurrencyRawBook])
|
||||
|
||||
Candle = TypedDict("Candle", {
|
||||
"MTS": int,
|
||||
"OPEN": float,
|
||||
"CLOSE": float,
|
||||
"HIGH": float,
|
||||
"LOW": float,
|
||||
"VOLUME": float
|
||||
})
|
||||
|
||||
Candles = List[Candle]
|
||||
|
||||
DerivativesStatus = TypedDict("DerivativesStatus", {
|
||||
"TIME_MS": int,
|
||||
"DERIV_PRICE": float,
|
||||
"SPOT_PRICE": float,
|
||||
"INSURANCE_FUND_BALANCE": float,
|
||||
"NEXT_FUNDING_EVT_TIMESTAMP_MS": int,
|
||||
"NEXT_FUNDING_ACCRUED": float,
|
||||
"NEXT_FUNDING_STEP": int,
|
||||
"CURRENT_FUNDING": float,
|
||||
"MARK_PRICE": float,
|
||||
"OPEN_INTEREST": float,
|
||||
"CLAMP_MIN": float,
|
||||
"CLAMP_MAX": float
|
||||
})
|
||||
class DerivativesStatus(TypedDict):
|
||||
TIME_MS: int
|
||||
DERIV_PRICE: float
|
||||
SPOT_PRICE: float
|
||||
INSURANCE_FUND_BALANCE: float
|
||||
NEXT_FUNDING_EVT_TIMESTAMP_MS: int
|
||||
NEXT_FUNDING_ACCRUED: float
|
||||
NEXT_FUNDING_STEP: int
|
||||
CURRENT_FUNDING: float
|
||||
MARK_PRICE: float
|
||||
OPEN_INTEREST: float
|
||||
CLAMP_MIN: float
|
||||
CLAMP_MAX: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Websocket Authenticated Channels
|
||||
|
||||
Order = TypedDict("Order", {
|
||||
"ID": int,
|
||||
"GID": int,
|
||||
"CID": int,
|
||||
"SYMBOL": str,
|
||||
"MTS_CREATE": int,
|
||||
"MTS_UPDATE": int,
|
||||
"AMOUNT": float,
|
||||
"AMOUNT_ORIG": float,
|
||||
"ORDER_TYPE": str,
|
||||
"TYPE_PREV": str,
|
||||
"MTS_TIF": int,
|
||||
"FLAGS": int,
|
||||
"ORDER_STATUS": str,
|
||||
"PRICE": float,
|
||||
"PRICE_AVG": float,
|
||||
"PRICE_TRAILING": float,
|
||||
"PRICE_AUX_LIMIT": float,
|
||||
"NOTIFY": int,
|
||||
"HIDDEN": int,
|
||||
"PLACED_ID": int,
|
||||
"ROUTING": str,
|
||||
"META": JSON
|
||||
})
|
||||
class Order(TypedDict):
|
||||
ID: int
|
||||
GID: int
|
||||
CID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
ORDER_TYPE: str
|
||||
TYPE_PREV: str
|
||||
MTS_TIF: int
|
||||
FLAGS: int
|
||||
ORDER_STATUS: str
|
||||
PRICE: float
|
||||
PRICE_AVG: float
|
||||
PRICE_TRAILING: float
|
||||
PRICE_AUX_LIMIT: float
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
PLACED_ID: int
|
||||
ROUTING: str
|
||||
META: JSON
|
||||
|
||||
Orders = List[Order]
|
||||
class Position(TypedDict):
|
||||
SYMBOL: str
|
||||
STATUS: str
|
||||
AMOUNT: float
|
||||
BASE_PRICE: float
|
||||
MARGIN_FUNDING: float
|
||||
MARGIN_FUNDING_TYPE: int
|
||||
PL: float
|
||||
PL_PERC: float
|
||||
PRICE_LIQ: float
|
||||
LEVERAGE: float
|
||||
POSITION_ID: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
TYPE: int
|
||||
COLLATERAL: float
|
||||
COLLATERAL_MIN: float
|
||||
META: JSON
|
||||
|
||||
Position = TypedDict("Position", {
|
||||
"SYMBOL": str,
|
||||
"STATUS": str,
|
||||
"AMOUNT": float,
|
||||
"BASE_PRICE": float,
|
||||
"MARGIN_FUNDING": float,
|
||||
"MARGIN_FUNDING_TYPE": int,
|
||||
"PL": float,
|
||||
"PL_PERC": float,
|
||||
"PRICE_LIQ": float,
|
||||
"LEVERAGE": float,
|
||||
"POSITION_ID": int,
|
||||
"MTS_CREATE": int,
|
||||
"MTS_UPDATE": int,
|
||||
"TYPE": int,
|
||||
"COLLATERAL": float,
|
||||
"COLLATERAL_MIN": float,
|
||||
"META": JSON,
|
||||
})
|
||||
class TradeExecuted(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
CID: int
|
||||
|
||||
Positions = List[Position]
|
||||
class TradeExecutionUpdate(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
FEE: float
|
||||
FEE_CURRENCY: str
|
||||
CID: int
|
||||
|
||||
TradeExecuted = TypedDict("TradeExecuted", {
|
||||
"ID": int,
|
||||
"SYMBOL": str,
|
||||
"MTS_CREATE": int,
|
||||
"ORDER_ID": int,
|
||||
"EXEC_AMOUNT": float,
|
||||
"EXEC_PRICE": float,
|
||||
"ORDER_TYPE": str,
|
||||
"ORDER_PRICE": float,
|
||||
"MAKER":int,
|
||||
"CID": int
|
||||
})
|
||||
class FundingOffer(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATED: int
|
||||
MTS_UPDATED: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
OFFER_TYPE: str
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
|
||||
TradeExecutionUpdate = TypedDict("TradeExecutionUpdate", {
|
||||
"ID": int,
|
||||
"SYMBOL": str,
|
||||
"MTS_CREATE": int,
|
||||
"ORDER_ID": int,
|
||||
"EXEC_AMOUNT": float,
|
||||
"EXEC_PRICE": float,
|
||||
"ORDER_TYPE": str,
|
||||
"ORDER_PRICE": float,
|
||||
"MAKER":int,
|
||||
"FEE": float,
|
||||
"FEE_CURRENCY": str,
|
||||
"CID": int
|
||||
})
|
||||
class FundingCredit(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
SIDE: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
MTS_OPENING: int
|
||||
MTS_LAST_PAYOUT: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
RATE_REAL: float
|
||||
NO_CLOSE: int
|
||||
POSITION_PAIR: str
|
||||
|
||||
FundingOffer = TypedDict("FundingOffer", {
|
||||
"ID": int,
|
||||
"SYMBOL": str,
|
||||
"MTS_CREATED": int,
|
||||
"MTS_UPDATED": int,
|
||||
"AMOUNT": float,
|
||||
"AMOUNT_ORIG": float,
|
||||
"OFFER_TYPE": str,
|
||||
"FLAGS": int,
|
||||
"STATUS": str,
|
||||
"RATE": float,
|
||||
"PERIOD": int,
|
||||
"NOTIFY": int,
|
||||
"HIDDEN": int,
|
||||
"RENEW": int,
|
||||
})
|
||||
class FundingLoan(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
SIDE: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
MTS_OPENING: int
|
||||
MTS_LAST_PAYOUT: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
RATE_REAL: float
|
||||
NO_CLOSE: int
|
||||
|
||||
FundingOffers = List[FundingOffer]
|
||||
class Wallet(TypedDict):
|
||||
WALLET_TYPE: str
|
||||
CURRENCY: str
|
||||
BALANCE: float
|
||||
UNSETTLED_INTEREST: float
|
||||
BALANCE_AVAILABLE: float
|
||||
DESCRIPTION: str
|
||||
META: JSON
|
||||
|
||||
FundingCredit = TypedDict("FundingCredit", {
|
||||
"ID": int,
|
||||
"SYMBOL": str,
|
||||
"SIDE": int,
|
||||
"MTS_CREATE": int,
|
||||
"MTS_UPDATE": int,
|
||||
"AMOUNT": float,
|
||||
"FLAGS": int,
|
||||
"STATUS": str,
|
||||
"RATE": float,
|
||||
"PERIOD": int,
|
||||
"MTS_OPENING": int,
|
||||
"MTS_LAST_PAYOUT": int,
|
||||
"NOTIFY": int,
|
||||
"HIDDEN": int,
|
||||
"RENEW": int,
|
||||
"RATE_REAL": float,
|
||||
"NO_CLOSE": int,
|
||||
"POSITION_PAIR": str
|
||||
})
|
||||
class BalanceInfo(TypedDict):
|
||||
AUM: float
|
||||
AUM_NET: float
|
||||
|
||||
FundingCredits = List[FundingCredit]
|
||||
#endregion
|
||||
|
||||
FundingLoan = TypedDict("FundingLoan", {
|
||||
"ID": int,
|
||||
"SYMBOL": str,
|
||||
"SIDE": int,
|
||||
"MTS_CREATE": int,
|
||||
"MTS_UPDATE": int,
|
||||
"AMOUNT": float,
|
||||
"FLAGS": int,
|
||||
"STATUS": str,
|
||||
"RATE": float,
|
||||
"PERIOD": int,
|
||||
"MTS_OPENING": int,
|
||||
"MTS_LAST_PAYOUT": int,
|
||||
"NOTIFY": int,
|
||||
"HIDDEN": int,
|
||||
"RENEW": int,
|
||||
"RATE_REAL": float,
|
||||
"NO_CLOSE": int
|
||||
})
|
||||
#region Type hinting for Notifications channel
|
||||
|
||||
FundingLoans = List[FundingLoan]
|
||||
|
||||
Wallet = TypedDict("Wallet", {
|
||||
"WALLET_TYPE": str,
|
||||
"CURRENCY": str,
|
||||
"BALANCE": float,
|
||||
"UNSETTLED_INTEREST": float,
|
||||
"BALANCE_AVAILABLE": float,
|
||||
"DESCRIPTION": str,
|
||||
"META": JSON
|
||||
})
|
||||
|
||||
Wallets = List[Wallet]
|
||||
|
||||
BalanceInfo = TypedDict("BalanceInfo", {
|
||||
"AUM": float,
|
||||
"AUM_NET": float
|
||||
})
|
||||
class Notification(TypedDict):
|
||||
MTS: int
|
||||
TYPE: str
|
||||
MESSAGE_ID: int
|
||||
NOTIFY_INFO: JSON
|
||||
CODE: int
|
||||
STATUS: str
|
||||
TEXT: str
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -304,55 +297,50 @@ BalanceInfo = TypedDict("BalanceInfo", {
|
||||
|
||||
class Inputs:
|
||||
class Order:
|
||||
New = TypedDict("Inputs.Order.New", {
|
||||
"gid": Optional[int32],
|
||||
"cid": int45,
|
||||
"type": str,
|
||||
"symbol": str,
|
||||
"amount": Union[Decimal, str],
|
||||
"price": Union[Decimal, str],
|
||||
"lev": int,
|
||||
"price_trailing": Union[Decimal, str],
|
||||
"price_aux_limit": Union[Decimal, str],
|
||||
"price_oco_stop": Union[Decimal, str],
|
||||
"flags": int16,
|
||||
"tif": Union[datetime, str],
|
||||
"meta": JSON
|
||||
})
|
||||
class New(TypedDict, total=False):
|
||||
gid: Union[Int32, int]
|
||||
cid: Union[Int45, int]
|
||||
type: str
|
||||
symbol: str
|
||||
amount: Union[Decimal, str]
|
||||
price: Union[Decimal, str]
|
||||
lev: Union[Int32, int]
|
||||
price_trailing: Union[Decimal, str]
|
||||
price_aux_limit: Union[Decimal, str]
|
||||
price_oco_stop: Union[Decimal, str]
|
||||
flags: Union[Int16, int]
|
||||
tif: Union[datetime, str]
|
||||
meta: JSON
|
||||
|
||||
Update = TypedDict("Inputs.Order.Update", {
|
||||
"id": int64,
|
||||
"cid": int45,
|
||||
"cid_date": str,
|
||||
"gid": int32,
|
||||
"price": Union[Decimal, str],
|
||||
"amount": Union[Decimal, str],
|
||||
"lev": int,
|
||||
"delta": Union[Decimal, str],
|
||||
"price_aux_limit": Union[Decimal, str],
|
||||
"price_trailing": Union[Decimal, str],
|
||||
"flags": int16,
|
||||
"tif": Union[datetime, str]
|
||||
})
|
||||
class Update(TypedDict, total=False):
|
||||
id: Union[Int64, int]
|
||||
cid: Union[Int45, int]
|
||||
cid_date: str
|
||||
gid: Union[Int32, int]
|
||||
price: Union[Decimal, str]
|
||||
amount: Union[Decimal, str]
|
||||
lev: Union[Int32, int]
|
||||
delta: Union[Decimal, str]
|
||||
price_aux_limit: Union[Decimal, str]
|
||||
price_trailing: Union[Decimal, str]
|
||||
flags: Union[Int16, int]
|
||||
tif: Union[datetime, str]
|
||||
|
||||
Cancel = TypedDict("Inputs.Order.Cancel", {
|
||||
"id": int64,
|
||||
"cid": int45,
|
||||
"cid_date": str
|
||||
})
|
||||
class Cancel(TypedDict, total=False):
|
||||
id: Union[Int64, int]
|
||||
cid: Union[Int45, int]
|
||||
cid_date: Union[datetime, str]
|
||||
|
||||
class Offer:
|
||||
New = TypedDict("Inputs.Offer.New", {
|
||||
"type": str,
|
||||
"symbol": str,
|
||||
"amount": Union[Decimal, str],
|
||||
"rate": Union[Decimal, str],
|
||||
"period": int,
|
||||
"flags": int16
|
||||
})
|
||||
class New(TypedDict, total=False):
|
||||
type: str
|
||||
symbol: str
|
||||
amount: Union[Decimal, str]
|
||||
rate: Union[Decimal, str]
|
||||
period: Union[Int32, int]
|
||||
flags: Union[Int16, int]
|
||||
|
||||
Cancel = TypedDict("Inputs.Offer.Cancel", {
|
||||
"id": int
|
||||
})
|
||||
class Cancel(TypedDict, total=False):
|
||||
id: Union[Int32, int]
|
||||
|
||||
#endregion
|
||||
26
examples/rest/create_funding_offer.py
Normal file
26
examples/rest/create_funding_offer.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.enums import FundingOfferType
|
||||
from bfxapi.utils.flags import calculate_offer_flags
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=Constants.REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
notification = bfx.rest.auth.submit_funding_offer(
|
||||
type=FundingOfferType.LIMIT,
|
||||
symbol="fUSD",
|
||||
amount="123.45",
|
||||
rate="0.001",
|
||||
period=2,
|
||||
flags=calculate_offer_flags(hidden=True)
|
||||
)
|
||||
|
||||
print("Offer notification:", notification)
|
||||
|
||||
offers = bfx.rest.auth.get_active_funding_offers()
|
||||
|
||||
print("Offers:", offers)
|
||||
36
examples/rest/create_order.py
Normal file
36
examples/rest/create_order.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.enums import OrderType
|
||||
from bfxapi.utils.flags import calculate_order_flags
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=Constants.REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
# Create a new order
|
||||
submitted_order = bfx.rest.auth.submit_order(
|
||||
type=OrderType.EXCHANGE_LIMIT,
|
||||
symbol="tBTCUST",
|
||||
amount="0.015",
|
||||
price="10000",
|
||||
flags=calculate_order_flags(hidden=False)
|
||||
)
|
||||
|
||||
print("Submit Order Notification:", submitted_order)
|
||||
|
||||
# Update it
|
||||
updated_order = bfx.rest.auth.update_order(
|
||||
id=submitted_order["NOTIFY_INFO"]["ID"],
|
||||
amount="0.020",
|
||||
price="10100"
|
||||
)
|
||||
|
||||
print("Update Order Notification:", updated_order)
|
||||
|
||||
# Delete it
|
||||
canceled_order = bfx.rest.auth.cancel_order(id=submitted_order["NOTIFY_INFO"]["ID"])
|
||||
|
||||
print("Cancel Order Notification:", canceled_order)
|
||||
47
examples/websocket/create_order.py
Normal file
47
examples/websocket/create_order.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# python -c "from examples.websocket.create_order import *"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.utils.cid import generate_unique_cid
|
||||
from bfxapi.websocket.enums import Error, OrderType
|
||||
from bfxapi.websocket.typings import Inputs
|
||||
|
||||
bfx = Client(
|
||||
WSS_HOST=Constants.WSS_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
@bfx.wss.on("authenticated")
|
||||
async def on_open(event):
|
||||
print(f"Auth event {event}")
|
||||
|
||||
order: Inputs.Order.New = {
|
||||
"gid": generate_unique_cid(),
|
||||
"type": OrderType.EXCHANGE_LIMIT,
|
||||
"symbol": "tBTCUST",
|
||||
"amount": "0.1",
|
||||
"price": "10000.0"
|
||||
}
|
||||
await bfx.wss.inputs.order_new(order)
|
||||
|
||||
print(f"Order sent")
|
||||
|
||||
@bfx.wss.on("notification")
|
||||
async def on_notification(notification):
|
||||
print(f"Notification {notification}")
|
||||
|
||||
@bfx.wss.on("order_new")
|
||||
async def on_order_new(order_new: Inputs.Order.New):
|
||||
print(f"Order new {order_new}")
|
||||
|
||||
@bfx.wss.on("subscribed")
|
||||
def on_subscribed(subscription):
|
||||
print(f"Subscription successful <{subscription}>")
|
||||
|
||||
bfx.wss.run()
|
||||
64
examples/websocket/order_book.py
Normal file
64
examples/websocket/order_book.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# python -c "from examples.websocket.order_book import *"
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from typing import List
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
|
||||
from bfxapi.websocket.enums import Channels, Error
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairBook
|
||||
|
||||
class OrderBook(object):
|
||||
def __init__(self, symbols: List[str]):
|
||||
self.__order_book = {
|
||||
symbol: {
|
||||
"bids": OrderedDict(), "asks": OrderedDict()
|
||||
} for symbol in symbols
|
||||
}
|
||||
|
||||
def update(self, symbol: str, data: TradingPairBook) -> None:
|
||||
price, count, amount = data["PRICE"], data["COUNT"], data["AMOUNT"]
|
||||
|
||||
kind = (amount > 0) and "bids" or "asks"
|
||||
|
||||
if count > 0:
|
||||
self.__order_book[symbol][kind][price] = {
|
||||
"price": price,
|
||||
"count": count,
|
||||
"amount": amount
|
||||
}
|
||||
|
||||
if count == 0:
|
||||
if price in self.__order_book[symbol][kind]:
|
||||
del self.__order_book[symbol][kind][price]
|
||||
|
||||
SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
|
||||
|
||||
order_book = OrderBook(symbols=SYMBOLS)
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
@bfx.wss.on("open")
|
||||
async def on_open():
|
||||
for symbol in SYMBOLS:
|
||||
await bfx.wss.subscribe(Channels.BOOK, symbol=symbol)
|
||||
|
||||
@bfx.wss.on("subscribed")
|
||||
def on_subscribed(subscription):
|
||||
print(f"Subscription successful for pair <{subscription['pair']}>")
|
||||
|
||||
@bfx.wss.on("t_book_snapshot")
|
||||
def on_t_book_snapshot(subscription: Subscriptions.Book, snapshot: List[TradingPairBook]):
|
||||
for data in snapshot:
|
||||
order_book.update(subscription["symbol"], data)
|
||||
|
||||
@bfx.wss.on("t_book_update")
|
||||
def on_t_book_update(subscription: Subscriptions.Book, data: TradingPairBook):
|
||||
order_book.update(subscription["symbol"], data)
|
||||
|
||||
bfx.wss.run()
|
||||
64
examples/websocket/raw_order_book.py
Normal file
64
examples/websocket/raw_order_book.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# python -c "from examples.websocket.raw_order_book import *"
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from typing import List
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
|
||||
from bfxapi.websocket.enums import Channels, Error
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairRawBook
|
||||
|
||||
class RawOrderBook(object):
|
||||
def __init__(self, symbols: List[str]):
|
||||
self.__raw_order_book = {
|
||||
symbol: {
|
||||
"bids": OrderedDict(), "asks": OrderedDict()
|
||||
} for symbol in symbols
|
||||
}
|
||||
|
||||
def update(self, symbol: str, data: TradingPairRawBook) -> None:
|
||||
order_id, price, amount = data["ORDER_ID"], data["PRICE"], data["AMOUNT"]
|
||||
|
||||
kind = (amount > 0) and "bids" or "asks"
|
||||
|
||||
if price > 0:
|
||||
self.__raw_order_book[symbol][kind][order_id] = {
|
||||
"order_id": order_id,
|
||||
"price": price,
|
||||
"amount": amount
|
||||
}
|
||||
|
||||
if price == 0:
|
||||
if order_id in self.__raw_order_book[symbol][kind]:
|
||||
del self.__raw_order_book[symbol][kind][order_id]
|
||||
|
||||
SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
|
||||
|
||||
raw_order_book = RawOrderBook(symbols=SYMBOLS)
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
@bfx.wss.on("open")
|
||||
async def on_open():
|
||||
for symbol in SYMBOLS:
|
||||
await bfx.wss.subscribe(Channels.BOOK, symbol=symbol, prec="R0")
|
||||
|
||||
@bfx.wss.on("subscribed")
|
||||
def on_subscribed(subscription):
|
||||
print(f"Subscription successful for pair <{subscription['pair']}>")
|
||||
|
||||
@bfx.wss.on("t_raw_book_snapshot")
|
||||
def on_t_raw_book_snapshot(subscription: Subscriptions.Book, snapshot: List[TradingPairRawBook]):
|
||||
for data in snapshot:
|
||||
raw_order_book.update(subscription["symbol"], data)
|
||||
|
||||
@bfx.wss.on("t_raw_book_update")
|
||||
def on_t_raw_book_update(subscription: Subscriptions.Book, data: TradingPairRawBook):
|
||||
raw_order_book.update(subscription["symbol"], data)
|
||||
|
||||
bfx.wss.run()
|
||||
@@ -1,14 +1,16 @@
|
||||
# python -c "from examples.websocket.ticker import *"
|
||||
|
||||
import asyncio
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
from bfxapi.websocket import Channels
|
||||
from bfxapi.websocket.enums import Channels
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairTicker
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("t_ticker_update")
|
||||
def on_t_ticker_update(subscription: Subscriptions.TradingPairsTicker, data: TradingPairTicker):
|
||||
print(f"Subscription channel ID: {subscription['chanId']}")
|
||||
def on_t_ticker_update(subscription: Subscriptions.TradingPairTicker, data: TradingPairTicker):
|
||||
print(f"Subscription with channel ID: {subscription['chanId']}")
|
||||
|
||||
print(f"Data: {data}")
|
||||
|
||||
@@ -16,4 +18,4 @@ def on_t_ticker_update(subscription: Subscriptions.TradingPairsTicker, data: Tra
|
||||
async def open():
|
||||
await bfx.wss.subscribe(Channels.TICKER, symbol="tBTCUSD")
|
||||
|
||||
asyncio.run(bfx.wss.start())
|
||||
bfx.wss.run()
|
||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
12
setup.py
12
setup.py
@@ -3,7 +3,7 @@ from distutils.core import setup
|
||||
setup(
|
||||
name="bitfinex-api-py",
|
||||
version="3.0.0",
|
||||
packages=[ "bfxapi", "bfxapi.websocket", "bfxapi.utils" ],
|
||||
packages=[ "bfxapi", "bfxapi.websocket", "bfxapi.rest", "bfxapi.utils" ],
|
||||
url="https://github.com/bitfinexcom/bitfinex-api-py",
|
||||
license="OSI Approved :: Apache Software License",
|
||||
author="Bitfinex",
|
||||
@@ -11,8 +11,18 @@ setup(
|
||||
description="Official Bitfinex Python API",
|
||||
keywords="bitfinex,api,trading",
|
||||
install_requires=[
|
||||
"certifi~=2022.12.7",
|
||||
"charset-normalizer~=2.1.1",
|
||||
"idna~=3.4",
|
||||
"mypy~=0.991",
|
||||
"mypy-extensions~=0.4.3",
|
||||
"pyee~=9.0.4",
|
||||
"requests~=2.28.1",
|
||||
"tomli~=2.0.1",
|
||||
"types-requests~=2.28.11.5",
|
||||
"types-urllib3~=1.26.25.4",
|
||||
"typing_extensions~=4.4.0",
|
||||
"urllib3~=1.26.13",
|
||||
"websockets~=10.4",
|
||||
],
|
||||
project_urls={
|
||||
|
||||
Reference in New Issue
Block a user