mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-18 06:14:22 +01:00
Apply black to all python files (bfxapi/**/*.py).
This commit is contained in:
@@ -14,29 +14,35 @@ WSS_HOST = "wss://api.bitfinex.com/ws/2"
|
||||
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
|
||||
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
api_secret: Optional[str] = None,
|
||||
*,
|
||||
rest_host: str = REST_HOST,
|
||||
wss_host: str = WSS_HOST,
|
||||
filters: Optional[List[str]] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
log_filename: Optional[str] = None
|
||||
self,
|
||||
api_key: Optional[str] = None,
|
||||
api_secret: Optional[str] = None,
|
||||
*,
|
||||
rest_host: str = REST_HOST,
|
||||
wss_host: str = WSS_HOST,
|
||||
filters: Optional[List[str]] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
log_filename: Optional[str] = None,
|
||||
) -> None:
|
||||
credentials: Optional["_Credentials"] = None
|
||||
|
||||
if api_key and api_secret:
|
||||
credentials = \
|
||||
{ "api_key": api_key, "api_secret": api_secret, "filters": filters }
|
||||
credentials = {
|
||||
"api_key": api_key,
|
||||
"api_secret": api_secret,
|
||||
"filters": filters,
|
||||
}
|
||||
elif api_key:
|
||||
raise IncompleteCredentialError( \
|
||||
"You must provide both an API-KEY and an API-SECRET (missing API-KEY).")
|
||||
raise IncompleteCredentialError(
|
||||
"You must provide both an API-KEY and an API-SECRET (missing API-KEY)."
|
||||
)
|
||||
elif api_secret:
|
||||
raise IncompleteCredentialError( \
|
||||
"You must provide both an API-KEY and an API-SECRET (missing API-SECRET).")
|
||||
raise IncompleteCredentialError(
|
||||
"You must provide both an API-KEY and an API-SECRET (missing API-SECRET)."
|
||||
)
|
||||
|
||||
self.rest = BfxRestInterface(rest_host, api_key, api_secret)
|
||||
|
||||
@@ -45,5 +51,6 @@ class Client:
|
||||
if log_filename:
|
||||
logger.register(filename=log_filename)
|
||||
|
||||
self.wss = BfxWebSocketClient(wss_host, \
|
||||
credentials=credentials, timeout=timeout, logger=logger)
|
||||
self.wss = BfxWebSocketClient(
|
||||
wss_host, credentials=credentials, timeout=timeout, logger=logger
|
||||
)
|
||||
|
||||
@@ -6,8 +6,10 @@ from typing import Any, Dict
|
||||
def _to_snake_case(string: str) -> str:
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
|
||||
|
||||
|
||||
def _object_hook(data: Dict[str, Any]) -> Any:
|
||||
return { _to_snake_case(key): value for key, value in data.items() }
|
||||
return {_to_snake_case(key): value for key, value in data.items()}
|
||||
|
||||
|
||||
class JSONDecoder(json.JSONDecoder):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
|
||||
@@ -2,15 +2,16 @@ import json
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
_ExtJSON = Union[Dict[str, "_ExtJSON"], List["_ExtJSON"], \
|
||||
bool, int, float, str, Decimal, None]
|
||||
_ExtJSON = Union[
|
||||
Dict[str, "_ExtJSON"], List["_ExtJSON"], bool, int, float, str, Decimal, None
|
||||
]
|
||||
|
||||
_StrictJSON = Union[Dict[str, "_StrictJSON"], List["_StrictJSON"], int, str, None]
|
||||
|
||||
_StrictJSON = Union[Dict[str, "_StrictJSON"], List["_StrictJSON"], \
|
||||
int, str, None]
|
||||
|
||||
def _clear(dictionary: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return { key: value for key, value in dictionary.items() \
|
||||
if value is not None }
|
||||
return {key: value for key, value in dictionary.items() if value is not None}
|
||||
|
||||
|
||||
def _adapter(data: _ExtJSON) -> _StrictJSON:
|
||||
if isinstance(data, bool):
|
||||
@@ -21,12 +22,13 @@ def _adapter(data: _ExtJSON) -> _StrictJSON:
|
||||
return format(data, "f")
|
||||
|
||||
if isinstance(data, list):
|
||||
return [ _adapter(sub_data) for sub_data in data ]
|
||||
return [_adapter(sub_data) for sub_data in data]
|
||||
if isinstance(data, dict):
|
||||
return _clear({ key: _adapter(value) for key, value in data.items() })
|
||||
return _clear({key: _adapter(value) for key, value in data.items()})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
def encode(self, o: _ExtJSON) -> str:
|
||||
return super().encode(_adapter(o))
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
import sys
|
||||
from copy import copy
|
||||
|
||||
#pylint: disable-next=wildcard-import,unused-wildcard-import
|
||||
# pylint: disable-next=wildcard-import,unused-wildcard-import
|
||||
from logging import *
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_Level = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
|
||||
_BLACK, _RED, _GREEN, _YELLOW, \
|
||||
_BLUE, _MAGENTA, _CYAN, _WHITE = \
|
||||
[ f"\033[0;{90 + i}m" for i in range(8) ]
|
||||
_BLACK, _RED, _GREEN, _YELLOW, _BLUE, _MAGENTA, _CYAN, _WHITE = [
|
||||
f"\033[0;{90 + i}m" for i in range(8)
|
||||
]
|
||||
|
||||
_BOLD_BLACK, _BOLD_RED, _BOLD_GREEN, _BOLD_YELLOW, \
|
||||
_BOLD_BLUE, _BOLD_MAGENTA, _BOLD_CYAN, _BOLD_WHITE = \
|
||||
[ f"\033[1;{90 + i}m" for i in range(8) ]
|
||||
(
|
||||
_BOLD_BLACK,
|
||||
_BOLD_RED,
|
||||
_BOLD_GREEN,
|
||||
_BOLD_YELLOW,
|
||||
_BOLD_BLUE,
|
||||
_BOLD_MAGENTA,
|
||||
_BOLD_CYAN,
|
||||
_BOLD_WHITE,
|
||||
) = [f"\033[1;{90 + i}m" for i in range(8)]
|
||||
|
||||
_NC = "\033[0m"
|
||||
|
||||
|
||||
class _ColorFormatter(Formatter):
|
||||
__LEVELS = {
|
||||
"INFO": _BLUE,
|
||||
"WARNING": _YELLOW,
|
||||
"ERROR": _RED,
|
||||
"CRITICAL": _BOLD_RED,
|
||||
"DEBUG": _BOLD_WHITE
|
||||
"DEBUG": _BOLD_WHITE,
|
||||
}
|
||||
|
||||
def format(self, record: LogRecord) -> str:
|
||||
@@ -34,7 +42,7 @@ class _ColorFormatter(Formatter):
|
||||
|
||||
return super().format(_record)
|
||||
|
||||
#pylint: disable-next=invalid-name
|
||||
# pylint: disable-next=invalid-name
|
||||
def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str:
|
||||
return _GREEN + super().formatTime(record, datefmt) + _NC
|
||||
|
||||
@@ -42,12 +50,14 @@ class _ColorFormatter(Formatter):
|
||||
def __format_level(level: str) -> str:
|
||||
return _ColorFormatter.__LEVELS[level] + level + _NC
|
||||
|
||||
|
||||
_FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s"
|
||||
|
||||
_DATE_FORMAT = "%d-%m-%Y %H:%M:%S"
|
||||
|
||||
|
||||
class ColorLogger(Logger):
|
||||
__FORMATTER = Formatter(_FORMAT,_DATE_FORMAT)
|
||||
__FORMATTER = Formatter(_FORMAT, _DATE_FORMAT)
|
||||
|
||||
def __init__(self, name: str, level: "_Level" = "NOTSET") -> None:
|
||||
super().__init__(name, level)
|
||||
|
||||
@@ -3,8 +3,10 @@ class BfxBaseException(Exception):
|
||||
Base class for every custom exception thrown by bitfinex-api-py.
|
||||
"""
|
||||
|
||||
|
||||
class IncompleteCredentialError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidCredentialError(BfxBaseException):
|
||||
pass
|
||||
|
||||
@@ -6,7 +6,9 @@ from .rest_public_endpoints import RestPublicEndpoints
|
||||
class BfxRestInterface:
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self, host, api_key = None, api_secret = None):
|
||||
def __init__(self, host, api_key=None, api_secret=None):
|
||||
self.public = RestPublicEndpoints(host=host)
|
||||
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret)
|
||||
self.merchant = RestMerchantEndpoints(host=host, api_key=api_key, api_secret=api_secret)
|
||||
self.merchant = RestMerchantEndpoints(
|
||||
host=host, api_key=api_key, api_secret=api_secret
|
||||
)
|
||||
|
||||
@@ -39,452 +39,590 @@ from ...types.serializers import _Notification
|
||||
from ..middleware import Middleware
|
||||
|
||||
|
||||
#pylint: disable-next=too-many-public-methods
|
||||
# pylint: disable-next=too-many-public-methods
|
||||
class RestAuthEndpoints(Middleware):
|
||||
def get_user_info(self) -> UserInfo:
|
||||
return serializers.UserInfo \
|
||||
.parse(*self._post("auth/r/info/user"))
|
||||
return serializers.UserInfo.parse(*self._post("auth/r/info/user"))
|
||||
|
||||
def get_login_history(self) -> List[LoginHistory]:
|
||||
return [ serializers.LoginHistory.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/logins/hist") ]
|
||||
return [
|
||||
serializers.LoginHistory.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/logins/hist")
|
||||
]
|
||||
|
||||
def get_balance_available_for_orders_or_offers(self,
|
||||
symbol: str,
|
||||
type: str,
|
||||
*,
|
||||
dir: Optional[int] = None,
|
||||
rate: Optional[str] = None,
|
||||
lev: Optional[str] = None) -> BalanceAvailable:
|
||||
body = {
|
||||
"symbol": symbol, "type": type, "dir": dir,
|
||||
"rate": rate, "lev": lev
|
||||
}
|
||||
def get_balance_available_for_orders_or_offers(
|
||||
self,
|
||||
symbol: str,
|
||||
type: str,
|
||||
*,
|
||||
dir: Optional[int] = None,
|
||||
rate: Optional[str] = None,
|
||||
lev: Optional[str] = None,
|
||||
) -> BalanceAvailable:
|
||||
body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev}
|
||||
|
||||
return serializers.BalanceAvailable \
|
||||
.parse(*self._post("auth/calc/order/avail", body=body))
|
||||
return serializers.BalanceAvailable.parse(
|
||||
*self._post("auth/calc/order/avail", body=body)
|
||||
)
|
||||
|
||||
def get_wallets(self) -> List[Wallet]:
|
||||
return [ serializers.Wallet.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/wallets") ]
|
||||
return [
|
||||
serializers.Wallet.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/wallets")
|
||||
]
|
||||
|
||||
def get_orders(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
ids: Optional[List[str]] = None) -> List[Order]:
|
||||
def get_orders(
|
||||
self, *, symbol: Optional[str] = None, ids: Optional[List[str]] = None
|
||||
) -> List[Order]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/orders"
|
||||
else: endpoint = f"auth/r/orders/{symbol}"
|
||||
else:
|
||||
endpoint = f"auth/r/orders/{symbol}"
|
||||
|
||||
return [ serializers.Order.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, body={ "id": ids }) ]
|
||||
return [
|
||||
serializers.Order.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body={"id": ids})
|
||||
]
|
||||
|
||||
def submit_order(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None) -> Notification[Order]:
|
||||
def submit_order(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> Notification[Order]:
|
||||
body = {
|
||||
"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
|
||||
"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,
|
||||
}
|
||||
|
||||
return _Notification[Order](serializers.Order) \
|
||||
.parse(*self._post("auth/w/order/submit", body=body))
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post("auth/w/order/submit", body=body)
|
||||
)
|
||||
|
||||
def update_order(self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None) -> Notification[Order]:
|
||||
def update_order(
|
||||
self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> Notification[Order]:
|
||||
body = {
|
||||
"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
|
||||
"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 _Notification[Order](serializers.Order) \
|
||||
.parse(*self._post("auth/w/order/update", body=body))
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post("auth/w/order/update", body=body)
|
||||
)
|
||||
|
||||
def cancel_order(self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None) -> Notification[Order]:
|
||||
return _Notification[Order](serializers.Order) \
|
||||
.parse(*self._post("auth/w/order/cancel", \
|
||||
body={ "id": id, "cid": cid, "cid_date": cid_date }))
|
||||
def cancel_order(
|
||||
self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
) -> Notification[Order]:
|
||||
return _Notification[Order](serializers.Order).parse(
|
||||
*self._post(
|
||||
"auth/w/order/cancel", body={"id": id, "cid": cid, "cid_date": cid_date}
|
||||
)
|
||||
)
|
||||
|
||||
def cancel_order_multi(self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None) -> Notification[List[Order]]:
|
||||
body = {
|
||||
"id": id, "cid": cid, "gid": gid,
|
||||
"all": all
|
||||
}
|
||||
def cancel_order_multi(
|
||||
self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None,
|
||||
) -> Notification[List[Order]]:
|
||||
body = {"id": id, "cid": cid, "gid": gid, "all": all}
|
||||
|
||||
return _Notification[List[Order]](serializers.Order, is_iterable=True) \
|
||||
.parse(*self._post("auth/w/order/cancel/multi", body=body))
|
||||
return _Notification[List[Order]](serializers.Order, is_iterable=True).parse(
|
||||
*self._post("auth/w/order/cancel/multi", body=body)
|
||||
)
|
||||
|
||||
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]:
|
||||
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 is None:
|
||||
endpoint = "auth/r/orders/hist"
|
||||
else: endpoint = f"auth/r/orders/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/orders/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"id": ids, "start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
body = {"id": ids, "start": start, "end": end, "limit": limit}
|
||||
|
||||
return [ serializers.Order.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, body=body) ]
|
||||
return [
|
||||
serializers.Order.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_order_trades(self,
|
||||
symbol: str,
|
||||
id: int) -> List[OrderTrade]:
|
||||
return [ serializers.OrderTrade.parse(*sub_data) \
|
||||
for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades") ]
|
||||
def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
|
||||
return [
|
||||
serializers.OrderTrade.parse(*sub_data)
|
||||
for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades")
|
||||
]
|
||||
|
||||
def get_trades_history(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Trade]:
|
||||
def get_trades_history(
|
||||
self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Trade]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/trades/hist"
|
||||
else: endpoint = f"auth/r/trades/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/trades/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"sort": sort, "start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
body = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
|
||||
return [ serializers.Trade.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, body=body) ]
|
||||
return [
|
||||
serializers.Trade.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_ledgers(self,
|
||||
currency: str,
|
||||
*,
|
||||
category: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Ledger]:
|
||||
body = {
|
||||
"category": category, "start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
def get_ledgers(
|
||||
self,
|
||||
currency: str,
|
||||
*,
|
||||
category: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Ledger]:
|
||||
body = {"category": category, "start": start, "end": end, "limit": limit}
|
||||
|
||||
return [ serializers.Ledger.parse(*sub_data) \
|
||||
for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body) ]
|
||||
return [
|
||||
serializers.Ledger.parse(*sub_data)
|
||||
for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body)
|
||||
]
|
||||
|
||||
def get_base_margin_info(self) -> BaseMarginInfo:
|
||||
return serializers.BaseMarginInfo \
|
||||
.parse(*(self._post("auth/r/info/margin/base")[1]))
|
||||
return serializers.BaseMarginInfo.parse(
|
||||
*(self._post("auth/r/info/margin/base")[1])
|
||||
)
|
||||
|
||||
def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
|
||||
return serializers.SymbolMarginInfo \
|
||||
.parse(*self._post(f"auth/r/info/margin/{symbol}"))
|
||||
return serializers.SymbolMarginInfo.parse(
|
||||
*self._post(f"auth/r/info/margin/{symbol}")
|
||||
)
|
||||
|
||||
def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
|
||||
return [ serializers.SymbolMarginInfo.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/info/margin/sym_all") ]
|
||||
return [
|
||||
serializers.SymbolMarginInfo.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/info/margin/sym_all")
|
||||
]
|
||||
|
||||
def get_positions(self) -> List[Position]:
|
||||
return [ serializers.Position.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/positions") ]
|
||||
return [
|
||||
serializers.Position.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/positions")
|
||||
]
|
||||
|
||||
def claim_position(self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None) -> Notification[PositionClaim]:
|
||||
return _Notification[PositionClaim](serializers.PositionClaim) \
|
||||
.parse(*self._post("auth/w/position/claim", \
|
||||
body={ "id": id, "amount": amount }))
|
||||
def claim_position(
|
||||
self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None
|
||||
) -> Notification[PositionClaim]:
|
||||
return _Notification[PositionClaim](serializers.PositionClaim).parse(
|
||||
*self._post("auth/w/position/claim", body={"id": id, "amount": amount})
|
||||
)
|
||||
|
||||
def increase_position(self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal]) -> Notification[PositionIncrease]:
|
||||
return _Notification[PositionIncrease](serializers.PositionIncrease) \
|
||||
.parse(*self._post("auth/w/position/increase", \
|
||||
body={ "symbol": symbol, "amount": amount }))
|
||||
def increase_position(
|
||||
self, symbol: str, amount: Union[str, float, Decimal]
|
||||
) -> Notification[PositionIncrease]:
|
||||
return _Notification[PositionIncrease](serializers.PositionIncrease).parse(
|
||||
*self._post(
|
||||
"auth/w/position/increase", body={"symbol": symbol, "amount": amount}
|
||||
)
|
||||
)
|
||||
|
||||
def get_increase_position_info(self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal]) -> PositionIncreaseInfo:
|
||||
return serializers.PositionIncreaseInfo \
|
||||
.parse(*self._post("auth/r/position/increase/info", \
|
||||
body={ "symbol": symbol, "amount": amount }))
|
||||
def get_increase_position_info(
|
||||
self, symbol: str, amount: Union[str, float, Decimal]
|
||||
) -> PositionIncreaseInfo:
|
||||
return serializers.PositionIncreaseInfo.parse(
|
||||
*self._post(
|
||||
"auth/r/position/increase/info",
|
||||
body={"symbol": symbol, "amount": amount},
|
||||
)
|
||||
)
|
||||
|
||||
def get_positions_history(self,
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[PositionHistory]:
|
||||
return [ serializers.PositionHistory.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/positions/hist", \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
def get_positions_history(
|
||||
self,
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[PositionHistory]:
|
||||
return [
|
||||
serializers.PositionHistory.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
"auth/r/positions/hist",
|
||||
body={"start": start, "end": end, "limit": limit},
|
||||
)
|
||||
]
|
||||
|
||||
def get_positions_snapshot(self,
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[PositionSnapshot]:
|
||||
return [ serializers.PositionSnapshot.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/positions/snap", \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
def get_positions_snapshot(
|
||||
self,
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[PositionSnapshot]:
|
||||
return [
|
||||
serializers.PositionSnapshot.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
"auth/r/positions/snap",
|
||||
body={"start": start, "end": end, "limit": limit},
|
||||
)
|
||||
]
|
||||
|
||||
def get_positions_audit(self,
|
||||
*,
|
||||
ids: Optional[List[int]] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[PositionAudit]:
|
||||
body = {
|
||||
"ids": ids, "start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
def get_positions_audit(
|
||||
self,
|
||||
*,
|
||||
ids: Optional[List[int]] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[PositionAudit]:
|
||||
body = {"ids": ids, "start": start, "end": end, "limit": limit}
|
||||
|
||||
return [ serializers.PositionAudit.parse(*sub_data) \
|
||||
for sub_data in self._post("auth/r/positions/audit", body=body) ]
|
||||
return [
|
||||
serializers.PositionAudit.parse(*sub_data)
|
||||
for sub_data in self._post("auth/r/positions/audit", body=body)
|
||||
]
|
||||
|
||||
def set_derivative_position_collateral(self,
|
||||
symbol: str,
|
||||
collateral: Union[str, float, Decimal]) -> DerivativePositionCollateral:
|
||||
return serializers.DerivativePositionCollateral \
|
||||
.parse(*(self._post("auth/w/deriv/collateral/set", \
|
||||
body={ "symbol": symbol, "collateral": collateral })[0]))
|
||||
def set_derivative_position_collateral(
|
||||
self, symbol: str, collateral: Union[str, float, Decimal]
|
||||
) -> DerivativePositionCollateral:
|
||||
return serializers.DerivativePositionCollateral.parse(
|
||||
*(
|
||||
self._post(
|
||||
"auth/w/deriv/collateral/set",
|
||||
body={"symbol": symbol, "collateral": collateral},
|
||||
)[0]
|
||||
)
|
||||
)
|
||||
|
||||
def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits:
|
||||
return serializers.DerivativePositionCollateralLimits \
|
||||
.parse(*self._post("auth/calc/deriv/collateral/limit", body={ "symbol": symbol }))
|
||||
def get_derivative_position_collateral_limits(
|
||||
self, symbol: str
|
||||
) -> DerivativePositionCollateralLimits:
|
||||
return serializers.DerivativePositionCollateralLimits.parse(
|
||||
*self._post("auth/calc/deriv/collateral/limit", body={"symbol": symbol})
|
||||
)
|
||||
|
||||
def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/offers"
|
||||
else: endpoint = f"auth/r/funding/offers/{symbol}"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/offers/{symbol}"
|
||||
|
||||
return [ serializers.FundingOffer.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint) ]
|
||||
return [
|
||||
serializers.FundingOffer.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
]
|
||||
|
||||
#pylint: disable-next=too-many-arguments
|
||||
def submit_funding_offer(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None) -> Notification[FundingOffer]:
|
||||
# pylint: disable-next=too-many-arguments
|
||||
def submit_funding_offer(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None,
|
||||
) -> Notification[FundingOffer]:
|
||||
body = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period, "flags": flags
|
||||
"type": type,
|
||||
"symbol": symbol,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"period": period,
|
||||
"flags": flags,
|
||||
}
|
||||
|
||||
return _Notification[FundingOffer](serializers.FundingOffer) \
|
||||
.parse(*self._post("auth/w/funding/offer/submit", body=body))
|
||||
return _Notification[FundingOffer](serializers.FundingOffer).parse(
|
||||
*self._post("auth/w/funding/offer/submit", body=body)
|
||||
)
|
||||
|
||||
def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
|
||||
return _Notification[FundingOffer](serializers.FundingOffer) \
|
||||
.parse(*self._post("auth/w/funding/offer/cancel", body={ "id": id }))
|
||||
return _Notification[FundingOffer](serializers.FundingOffer).parse(
|
||||
*self._post("auth/w/funding/offer/cancel", body={"id": id})
|
||||
)
|
||||
|
||||
def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None) \
|
||||
.parse(*self._post("auth/w/funding/offer/cancel/all", body={ "currency": currency }))
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post("auth/w/funding/offer/cancel/all", body={"currency": currency})
|
||||
)
|
||||
|
||||
def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None) \
|
||||
.parse(*self._post("auth/w/funding/close", body={ "id": id }))
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post("auth/w/funding/close", body={"id": id})
|
||||
)
|
||||
|
||||
def toggle_auto_renew(self,
|
||||
status: bool,
|
||||
currency: str,
|
||||
*,
|
||||
amount: Optional[str] = None,
|
||||
rate: Optional[int] = None,
|
||||
period: Optional[int] = None) -> Notification[FundingAutoRenew]:
|
||||
def toggle_auto_renew(
|
||||
self,
|
||||
status: bool,
|
||||
currency: str,
|
||||
*,
|
||||
amount: Optional[str] = None,
|
||||
rate: Optional[int] = None,
|
||||
period: Optional[int] = None,
|
||||
) -> Notification[FundingAutoRenew]:
|
||||
body = {
|
||||
"status": status, "currency": currency, "amount": amount,
|
||||
"rate": rate, "period": period
|
||||
"status": status,
|
||||
"currency": currency,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"period": period,
|
||||
}
|
||||
|
||||
return _Notification[FundingAutoRenew](serializers.FundingAutoRenew) \
|
||||
.parse(*self._post("auth/w/funding/auto", body=body))
|
||||
return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(
|
||||
*self._post("auth/w/funding/auto", body=body)
|
||||
)
|
||||
|
||||
def toggle_keep_funding(self,
|
||||
type: Literal["credit", "loan"],
|
||||
*,
|
||||
ids: Optional[List[int]] = None,
|
||||
changes: Optional[Dict[int, Literal[1, 2]]] = None) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None) \
|
||||
.parse(*self._post("auth/w/funding/keep", \
|
||||
body={ "type": type, "id": ids, "changes": changes }))
|
||||
def toggle_keep_funding(
|
||||
self,
|
||||
type: Literal["credit", "loan"],
|
||||
*,
|
||||
ids: Optional[List[int]] = None,
|
||||
changes: Optional[Dict[int, Literal[1, 2]]] = None,
|
||||
) -> Notification[Literal[None]]:
|
||||
return _Notification[Literal[None]](None).parse(
|
||||
*self._post(
|
||||
"auth/w/funding/keep",
|
||||
body={"type": type, "id": ids, "changes": changes},
|
||||
)
|
||||
)
|
||||
|
||||
def get_funding_offers_history(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[FundingOffer]:
|
||||
def get_funding_offers_history(
|
||||
self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[FundingOffer]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/offers/hist"
|
||||
else: endpoint = f"auth/r/funding/offers/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/offers/{symbol}/hist"
|
||||
|
||||
return [ serializers.FundingOffer.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
return [
|
||||
serializers.FundingOffer.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
|
||||
def get_funding_loans(self, *, symbol: Optional[str] = None) -> List[FundingLoan]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/loans"
|
||||
else: endpoint = f"auth/r/funding/loans/{symbol}"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/loans/{symbol}"
|
||||
|
||||
return [ serializers.FundingLoan.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint) ]
|
||||
return [
|
||||
serializers.FundingLoan.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
]
|
||||
|
||||
def get_funding_loans_history(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[FundingLoan]:
|
||||
def get_funding_loans_history(
|
||||
self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[FundingLoan]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/loans/hist"
|
||||
else: endpoint = f"auth/r/funding/loans/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/loans/{symbol}/hist"
|
||||
|
||||
return [ serializers.FundingLoan.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
return [
|
||||
serializers.FundingLoan.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
|
||||
def get_funding_credits(self, *, symbol: Optional[str] = None) -> List[FundingCredit]:
|
||||
def get_funding_credits(
|
||||
self, *, symbol: Optional[str] = None
|
||||
) -> List[FundingCredit]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/credits"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/credits/{symbol}"
|
||||
|
||||
return [ serializers.FundingCredit.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint) ]
|
||||
return [
|
||||
serializers.FundingCredit.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint)
|
||||
]
|
||||
|
||||
def get_funding_credits_history(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[FundingCredit]:
|
||||
def get_funding_credits_history(
|
||||
self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[FundingCredit]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/credits/hist"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/credits/{symbol}/hist"
|
||||
|
||||
return [ serializers.FundingCredit.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
return [
|
||||
serializers.FundingCredit.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
|
||||
def get_funding_trades_history(self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[FundingTrade]:
|
||||
def get_funding_trades_history(
|
||||
self,
|
||||
*,
|
||||
symbol: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[FundingTrade]:
|
||||
if symbol is None:
|
||||
endpoint = "auth/r/funding/trades/hist"
|
||||
else: endpoint = f"auth/r/funding/trades/{symbol}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/funding/trades/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"sort": sort, "start": start, "end": end,
|
||||
"limit": limit }
|
||||
body = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
|
||||
return [ serializers.FundingTrade.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, body=body) ]
|
||||
return [
|
||||
serializers.FundingTrade.parse(*sub_data)
|
||||
for sub_data in self._post(endpoint, body=body)
|
||||
]
|
||||
|
||||
def get_funding_info(self, key: str) -> FundingInfo:
|
||||
return serializers.FundingInfo \
|
||||
.parse(*(self._post(f"auth/r/info/funding/{key}")[2]))
|
||||
return serializers.FundingInfo.parse(
|
||||
*(self._post(f"auth/r/info/funding/{key}")[2])
|
||||
)
|
||||
|
||||
#pylint: disable-next=too-many-arguments
|
||||
def transfer_between_wallets(self,
|
||||
from_wallet: str,
|
||||
to_wallet: str,
|
||||
currency: str,
|
||||
currency_to: str,
|
||||
amount: Union[str, float, Decimal]) -> Notification[Transfer]:
|
||||
# pylint: disable-next=too-many-arguments
|
||||
def transfer_between_wallets(
|
||||
self,
|
||||
from_wallet: str,
|
||||
to_wallet: str,
|
||||
currency: str,
|
||||
currency_to: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
) -> Notification[Transfer]:
|
||||
body = {
|
||||
"from": from_wallet, "to": to_wallet, "currency": currency,
|
||||
"currency_to": currency_to, "amount": amount
|
||||
"from": from_wallet,
|
||||
"to": to_wallet,
|
||||
"currency": currency,
|
||||
"currency_to": currency_to,
|
||||
"amount": amount,
|
||||
}
|
||||
|
||||
return _Notification[Transfer](serializers.Transfer) \
|
||||
.parse(*self._post("auth/w/transfer", body=body))
|
||||
return _Notification[Transfer](serializers.Transfer).parse(
|
||||
*self._post("auth/w/transfer", body=body)
|
||||
)
|
||||
|
||||
def submit_wallet_withdrawal(self,
|
||||
wallet: str,
|
||||
method: str,
|
||||
address: str,
|
||||
amount: Union[str, float, Decimal]) -> Notification[Withdrawal]:
|
||||
def submit_wallet_withdrawal(
|
||||
self, wallet: str, method: str, address: str, amount: Union[str, float, Decimal]
|
||||
) -> Notification[Withdrawal]:
|
||||
body = {
|
||||
"wallet": wallet, "method": method, "address": address,
|
||||
"amount": amount
|
||||
"wallet": wallet,
|
||||
"method": method,
|
||||
"address": address,
|
||||
"amount": amount,
|
||||
}
|
||||
|
||||
return _Notification[Withdrawal](serializers.Withdrawal) \
|
||||
.parse(*self._post("auth/w/withdraw", body=body))
|
||||
return _Notification[Withdrawal](serializers.Withdrawal).parse(
|
||||
*self._post("auth/w/withdraw", body=body)
|
||||
)
|
||||
|
||||
def get_deposit_address(self,
|
||||
wallet: str,
|
||||
method: str,
|
||||
op_renew: bool = False) -> Notification[DepositAddress]:
|
||||
return _Notification[DepositAddress](serializers.DepositAddress) \
|
||||
.parse(*self._post("auth/w/deposit/address", \
|
||||
body={ "wallet": wallet, "method": method, "op_renew": op_renew }))
|
||||
def get_deposit_address(
|
||||
self, wallet: str, method: str, op_renew: bool = False
|
||||
) -> Notification[DepositAddress]:
|
||||
return _Notification[DepositAddress](serializers.DepositAddress).parse(
|
||||
*self._post(
|
||||
"auth/w/deposit/address",
|
||||
body={"wallet": wallet, "method": method, "op_renew": op_renew},
|
||||
)
|
||||
)
|
||||
|
||||
def generate_deposit_invoice(self,
|
||||
wallet: str,
|
||||
currency: str,
|
||||
amount: Union[str, float, Decimal]) -> LightningNetworkInvoice:
|
||||
return serializers.LightningNetworkInvoice \
|
||||
.parse(*self._post("auth/w/deposit/invoice", \
|
||||
body={ "wallet": wallet, "currency": currency, "amount": amount }))
|
||||
def generate_deposit_invoice(
|
||||
self, wallet: str, currency: str, amount: Union[str, float, Decimal]
|
||||
) -> LightningNetworkInvoice:
|
||||
return serializers.LightningNetworkInvoice.parse(
|
||||
*self._post(
|
||||
"auth/w/deposit/invoice",
|
||||
body={"wallet": wallet, "currency": currency, "amount": amount},
|
||||
)
|
||||
)
|
||||
|
||||
def get_movements(self,
|
||||
*,
|
||||
currency: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Movement]:
|
||||
def get_movements(
|
||||
self,
|
||||
*,
|
||||
currency: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[Movement]:
|
||||
if currency is None:
|
||||
endpoint = "auth/r/movements/hist"
|
||||
else: endpoint = f"auth/r/movements/{currency}/hist"
|
||||
else:
|
||||
endpoint = f"auth/r/movements/{currency}/hist"
|
||||
|
||||
return [ serializers.Movement.parse(*sub_data) \
|
||||
for sub_data in self._post(endpoint, \
|
||||
body={ "start": start, "end": end, "limit": limit }) ]
|
||||
return [
|
||||
serializers.Movement.parse(*sub_data)
|
||||
for sub_data in self._post(
|
||||
endpoint, body={"start": start, "end": end, "limit": limit}
|
||||
)
|
||||
]
|
||||
|
||||
@@ -11,99 +11,131 @@ from bfxapi.types import (
|
||||
MerchantUnlinkedDeposit,
|
||||
)
|
||||
|
||||
_CustomerInfo = TypedDict("_CustomerInfo", {
|
||||
"nationality": str,
|
||||
"resid_country": str,
|
||||
"resid_city": str,
|
||||
"resid_zip_code": str,
|
||||
"resid_street": str,
|
||||
"resid_building_no": str,
|
||||
"full_name": str,
|
||||
"email": str,
|
||||
"tos_accepted": bool
|
||||
})
|
||||
_CustomerInfo = TypedDict(
|
||||
"_CustomerInfo",
|
||||
{
|
||||
"nationality": str,
|
||||
"resid_country": str,
|
||||
"resid_city": str,
|
||||
"resid_zip_code": str,
|
||||
"resid_street": str,
|
||||
"resid_building_no": str,
|
||||
"full_name": str,
|
||||
"email": str,
|
||||
"tos_accepted": bool,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class RestMerchantEndpoints(Middleware):
|
||||
#pylint: disable-next=too-many-arguments
|
||||
def submit_invoice(self,
|
||||
amount: Union[str, float, Decimal],
|
||||
currency: str,
|
||||
order_id: str,
|
||||
customer_info: _CustomerInfo,
|
||||
pay_currencies: List[str],
|
||||
*,
|
||||
duration: Optional[int] = None,
|
||||
webhook: Optional[str] = None,
|
||||
redirect_url: Optional[str] = None) -> InvoiceSubmission:
|
||||
# pylint: disable-next=too-many-arguments
|
||||
def submit_invoice(
|
||||
self,
|
||||
amount: Union[str, float, Decimal],
|
||||
currency: str,
|
||||
order_id: str,
|
||||
customer_info: _CustomerInfo,
|
||||
pay_currencies: List[str],
|
||||
*,
|
||||
duration: Optional[int] = None,
|
||||
webhook: Optional[str] = None,
|
||||
redirect_url: Optional[str] = None,
|
||||
) -> InvoiceSubmission:
|
||||
body = {
|
||||
"amount": amount, "currency": currency, "orderId": order_id,
|
||||
"customerInfo": customer_info, "payCurrencies": pay_currencies, "duration": duration,
|
||||
"webhook": webhook, "redirectUrl": redirect_url
|
||||
"amount": amount,
|
||||
"currency": currency,
|
||||
"orderId": order_id,
|
||||
"customerInfo": customer_info,
|
||||
"payCurrencies": pay_currencies,
|
||||
"duration": duration,
|
||||
"webhook": webhook,
|
||||
"redirectUrl": redirect_url,
|
||||
}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/create", body=body)
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def get_invoices(self,
|
||||
*,
|
||||
id: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[InvoiceSubmission]:
|
||||
body = {
|
||||
"id": id, "start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
def get_invoices(
|
||||
self,
|
||||
*,
|
||||
id: Optional[str] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[InvoiceSubmission]:
|
||||
body = {"id": id, "start": start, "end": end, "limit": limit}
|
||||
|
||||
data = self._post("auth/r/ext/pay/invoices", body=body)
|
||||
|
||||
return [ InvoiceSubmission.parse(sub_data) for sub_data in data ]
|
||||
return [InvoiceSubmission.parse(sub_data) for sub_data in data]
|
||||
|
||||
def get_invoices_paginated(self,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
sort: Literal["asc", "desc"] = "asc",
|
||||
sort_field: Literal["t", "amount", "status"] = "t",
|
||||
*,
|
||||
status: Optional[List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]]] = None,
|
||||
fiat: Optional[List[str]] = None,
|
||||
crypto: Optional[List[str]] = None,
|
||||
id: Optional[str] = None,
|
||||
order_id: Optional[str] = None) -> InvoicePage:
|
||||
def get_invoices_paginated(
|
||||
self,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
sort: Literal["asc", "desc"] = "asc",
|
||||
sort_field: Literal["t", "amount", "status"] = "t",
|
||||
*,
|
||||
status: Optional[
|
||||
List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]]
|
||||
] = None,
|
||||
fiat: Optional[List[str]] = None,
|
||||
crypto: Optional[List[str]] = None,
|
||||
id: Optional[str] = None,
|
||||
order_id: Optional[str] = None,
|
||||
) -> InvoicePage:
|
||||
body = {
|
||||
"page": page, "pageSize": page_size, "sort": sort,
|
||||
"sortField": sort_field, "status": status, "fiat": fiat,
|
||||
"crypto": crypto, "id": id, "orderId": order_id
|
||||
"page": page,
|
||||
"pageSize": page_size,
|
||||
"sort": sort,
|
||||
"sortField": sort_field,
|
||||
"status": status,
|
||||
"fiat": fiat,
|
||||
"crypto": crypto,
|
||||
"id": id,
|
||||
"orderId": order_id,
|
||||
}
|
||||
|
||||
data = self._post("auth/r/ext/pay/invoices/paginated", body=body)
|
||||
|
||||
return InvoicePage.parse(data)
|
||||
|
||||
def get_invoice_count_stats(self,
|
||||
status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"],
|
||||
format: str) -> List[InvoiceStats]:
|
||||
return [ InvoiceStats(**sub_data) for sub_data in \
|
||||
self._post("auth/r/ext/pay/invoice/stats/count", \
|
||||
body={ "status": status, "format": format }) ]
|
||||
def get_invoice_count_stats(
|
||||
self, status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], format: str
|
||||
) -> List[InvoiceStats]:
|
||||
return [
|
||||
InvoiceStats(**sub_data)
|
||||
for sub_data in self._post(
|
||||
"auth/r/ext/pay/invoice/stats/count",
|
||||
body={"status": status, "format": format},
|
||||
)
|
||||
]
|
||||
|
||||
def get_invoice_earning_stats(self,
|
||||
currency: str,
|
||||
format: str) -> List[InvoiceStats]:
|
||||
return [ InvoiceStats(**sub_data) for sub_data in \
|
||||
self._post("auth/r/ext/pay/invoice/stats/earning", \
|
||||
body={ "currency": currency, "format": format }) ]
|
||||
def get_invoice_earning_stats(
|
||||
self, currency: str, format: str
|
||||
) -> List[InvoiceStats]:
|
||||
return [
|
||||
InvoiceStats(**sub_data)
|
||||
for sub_data in self._post(
|
||||
"auth/r/ext/pay/invoice/stats/earning",
|
||||
body={"currency": currency, "format": format},
|
||||
)
|
||||
]
|
||||
|
||||
def complete_invoice(self,
|
||||
id: str,
|
||||
pay_currency: str,
|
||||
*,
|
||||
deposit_id: Optional[int] = None,
|
||||
ledger_id: Optional[int] = None) -> InvoiceSubmission:
|
||||
def complete_invoice(
|
||||
self,
|
||||
id: str,
|
||||
pay_currency: str,
|
||||
*,
|
||||
deposit_id: Optional[int] = None,
|
||||
ledger_id: Optional[int] = None,
|
||||
) -> InvoiceSubmission:
|
||||
body = {
|
||||
"id": id, "payCcy": pay_currency, "depositId": deposit_id,
|
||||
"ledgerId": ledger_id
|
||||
"id": id,
|
||||
"payCcy": pay_currency,
|
||||
"depositId": deposit_id,
|
||||
"ledgerId": ledger_id,
|
||||
}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/complete", body=body)
|
||||
@@ -111,65 +143,65 @@ class RestMerchantEndpoints(Middleware):
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def expire_invoice(self, id: str) -> InvoiceSubmission:
|
||||
body = { "id": id }
|
||||
body = {"id": id}
|
||||
|
||||
data = self._post("auth/w/ext/pay/invoice/expire", body=body)
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def get_currency_conversion_list(self) -> List[CurrencyConversion]:
|
||||
return [ CurrencyConversion(**sub_data) \
|
||||
for sub_data in self._post("auth/r/ext/pay/settings/convert/list") ]
|
||||
return [
|
||||
CurrencyConversion(**sub_data)
|
||||
for sub_data in self._post("auth/r/ext/pay/settings/convert/list")
|
||||
]
|
||||
|
||||
def add_currency_conversion(self,
|
||||
base_ccy: str,
|
||||
convert_ccy: str) -> bool:
|
||||
return bool(self._post("auth/w/ext/pay/settings/convert/create", \
|
||||
body={ "baseCcy": base_ccy, "convertCcy": convert_ccy }))
|
||||
def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
|
||||
return bool(
|
||||
self._post(
|
||||
"auth/w/ext/pay/settings/convert/create",
|
||||
body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
|
||||
)
|
||||
)
|
||||
|
||||
def remove_currency_conversion(self,
|
||||
base_ccy: str,
|
||||
convert_ccy: str) -> bool:
|
||||
return bool(self._post("auth/w/ext/pay/settings/convert/remove", \
|
||||
body={ "baseCcy": base_ccy, "convertCcy": convert_ccy }))
|
||||
def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
|
||||
return bool(
|
||||
self._post(
|
||||
"auth/w/ext/pay/settings/convert/remove",
|
||||
body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
|
||||
)
|
||||
)
|
||||
|
||||
def set_merchant_settings(self,
|
||||
key: str,
|
||||
val: Any) -> bool:
|
||||
return bool(self._post("auth/w/ext/pay/settings/set", \
|
||||
body={ "key": key, "val": val }))
|
||||
def set_merchant_settings(self, key: str, val: Any) -> bool:
|
||||
return bool(
|
||||
self._post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
|
||||
)
|
||||
|
||||
def get_merchant_settings(self, key: str) -> Any:
|
||||
return self._post("auth/r/ext/pay/settings/get", body={ "key": key })
|
||||
return self._post("auth/r/ext/pay/settings/get", body={"key": key})
|
||||
|
||||
#pylint: disable-next=dangerous-default-value
|
||||
# pylint: disable-next=dangerous-default-value
|
||||
def list_merchant_settings(self, keys: List[str] = []) -> Dict[str, Any]:
|
||||
return self._post("auth/r/ext/pay/settings/list", body={ "keys": keys })
|
||||
return self._post("auth/r/ext/pay/settings/list", body={"keys": keys})
|
||||
|
||||
def get_deposits(self,
|
||||
start: int,
|
||||
to: int,
|
||||
*,
|
||||
ccy: Optional[str] = None,
|
||||
unlinked: Optional[bool] = None) -> List[MerchantDeposit]:
|
||||
body = {
|
||||
"from": start, "to": to, "ccy": ccy,
|
||||
"unlinked": unlinked
|
||||
}
|
||||
def get_deposits(
|
||||
self,
|
||||
start: int,
|
||||
to: int,
|
||||
*,
|
||||
ccy: Optional[str] = None,
|
||||
unlinked: Optional[bool] = None,
|
||||
) -> List[MerchantDeposit]:
|
||||
body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked}
|
||||
|
||||
data = self._post("auth/r/ext/pay/deposits", body=body)
|
||||
|
||||
return [ MerchantDeposit(**sub_data) for sub_data in data ]
|
||||
return [MerchantDeposit(**sub_data) for sub_data in data]
|
||||
|
||||
def get_unlinked_deposits(self,
|
||||
ccy: str,
|
||||
*,
|
||||
start: Optional[int] = None,
|
||||
end: Optional[int] = None) -> List[MerchantUnlinkedDeposit]:
|
||||
body = {
|
||||
"ccy": ccy, "start": start, "end": end
|
||||
}
|
||||
def get_unlinked_deposits(
|
||||
self, ccy: str, *, start: Optional[int] = None, end: Optional[int] = None
|
||||
) -> List[MerchantUnlinkedDeposit]:
|
||||
body = {"ccy": ccy, "start": start, "end": end}
|
||||
|
||||
data = self._post("/auth/r/ext/pay/deposits/unlinked", body=body)
|
||||
|
||||
return [ MerchantUnlinkedDeposit(**sub_data) for sub_data in data ]
|
||||
return [MerchantUnlinkedDeposit(**sub_data) for sub_data in data]
|
||||
|
||||
@@ -28,7 +28,7 @@ from ...types import (
|
||||
from ..middleware import Middleware
|
||||
|
||||
|
||||
#pylint: disable-next=too-many-public-methods
|
||||
# pylint: disable-next=too-many-public-methods
|
||||
class RestPublicEndpoints(Middleware):
|
||||
def conf(self, config: str) -> Any:
|
||||
return self._get(f"conf/{config}")[0]
|
||||
@@ -36,35 +36,47 @@ class RestPublicEndpoints(Middleware):
|
||||
def get_platform_status(self) -> PlatformStatus:
|
||||
return serializers.PlatformStatus.parse(*self._get("platform/status"))
|
||||
|
||||
def get_tickers(self, symbols: List[str]) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._get("tickers", params={ "symbols": ",".join(symbols) })
|
||||
def get_tickers(
|
||||
self, symbols: List[str]
|
||||
) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._get("tickers", params={"symbols": ",".join(symbols)})
|
||||
|
||||
parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
|
||||
|
||||
return {
|
||||
symbol: cast(Union[TradingPairTicker, FundingCurrencyTicker],
|
||||
parsers[symbol[0]](*sub_data)) for sub_data in data
|
||||
if (symbol := sub_data.pop(0))
|
||||
parsers = {
|
||||
"t": serializers.TradingPairTicker.parse,
|
||||
"f": serializers.FundingCurrencyTicker.parse,
|
||||
}
|
||||
|
||||
def get_t_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, TradingPairTicker]:
|
||||
return {
|
||||
symbol: cast(
|
||||
Union[TradingPairTicker, FundingCurrencyTicker],
|
||||
parsers[symbol[0]](*sub_data),
|
||||
)
|
||||
for sub_data in data
|
||||
if (symbol := sub_data.pop(0))
|
||||
}
|
||||
|
||||
def get_t_tickers(
|
||||
self, symbols: Union[List[str], Literal["ALL"]]
|
||||
) -> Dict[str, TradingPairTicker]:
|
||||
if isinstance(symbols, str) and symbols == "ALL":
|
||||
return {
|
||||
symbol: cast(TradingPairTicker, sub_data)
|
||||
for symbol, sub_data in self.get_tickers([ "ALL" ]).items()
|
||||
if symbol.startswith("t")
|
||||
for symbol, sub_data in self.get_tickers(["ALL"]).items()
|
||||
if symbol.startswith("t")
|
||||
}
|
||||
|
||||
data = self.get_tickers(list(symbols))
|
||||
|
||||
return cast(Dict[str, TradingPairTicker], data)
|
||||
|
||||
def get_f_tickers(self, symbols: Union[List[str], Literal["ALL"]]) -> Dict[str, FundingCurrencyTicker]:
|
||||
def get_f_tickers(
|
||||
self, symbols: Union[List[str], Literal["ALL"]]
|
||||
) -> Dict[str, FundingCurrencyTicker]:
|
||||
if isinstance(symbols, str) and symbols == "ALL":
|
||||
return {
|
||||
symbol: cast(FundingCurrencyTicker, sub_data)
|
||||
for symbol, sub_data in self.get_tickers([ "ALL" ]).items()
|
||||
if symbol.startswith("f")
|
||||
for symbol, sub_data in self.get_tickers(["ALL"]).items()
|
||||
if symbol.startswith("f")
|
||||
}
|
||||
|
||||
data = self.get_tickers(list(symbols))
|
||||
@@ -77,230 +89,292 @@ class RestPublicEndpoints(Middleware):
|
||||
def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker:
|
||||
return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}"))
|
||||
|
||||
def get_tickers_history(self,
|
||||
symbols: List[str],
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[TickersHistory]:
|
||||
return [ serializers.TickersHistory.parse(*sub_data) for sub_data in self._get("tickers/hist", params={
|
||||
"symbols": ",".join(symbols),
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}) ]
|
||||
def get_tickers_history(
|
||||
self,
|
||||
symbols: List[str],
|
||||
*,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[TickersHistory]:
|
||||
return [
|
||||
serializers.TickersHistory.parse(*sub_data)
|
||||
for sub_data in self._get(
|
||||
"tickers/hist",
|
||||
params={
|
||||
"symbols": ",".join(symbols),
|
||||
"start": start,
|
||||
"end": end,
|
||||
"limit": limit,
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
def get_t_trades(self,
|
||||
pair: str,
|
||||
*,
|
||||
limit: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
sort: Optional[int] = None) -> List[TradingPairTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
def get_t_trades(
|
||||
self,
|
||||
pair: str,
|
||||
*,
|
||||
limit: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
) -> List[TradingPairTrade]:
|
||||
params = {"limit": limit, "start": start, "end": end, "sort": sort}
|
||||
data = self._get(f"trades/{pair}/hist", params=params)
|
||||
return [ serializers.TradingPairTrade.parse(*sub_data) for sub_data in data ]
|
||||
return [serializers.TradingPairTrade.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_f_trades(self,
|
||||
currency: str,
|
||||
*,
|
||||
limit: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
sort: Optional[int] = None) -> List[FundingCurrencyTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
def get_f_trades(
|
||||
self,
|
||||
currency: str,
|
||||
*,
|
||||
limit: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
sort: Optional[int] = None,
|
||||
) -> List[FundingCurrencyTrade]:
|
||||
params = {"limit": limit, "start": start, "end": end, "sort": sort}
|
||||
data = self._get(f"trades/{currency}/hist", params=params)
|
||||
return [ serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data ]
|
||||
return [serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data 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(*sub_data) \
|
||||
for sub_data in self._get(f"book/{pair}/{precision}", params={ "len": len }) ]
|
||||
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(*sub_data)
|
||||
for sub_data in self._get(f"book/{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(*sub_data) \
|
||||
for sub_data in self._get(f"book/{currency}/{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(*sub_data)
|
||||
for sub_data in self._get(
|
||||
f"book/{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(*sub_data) \
|
||||
for sub_data in self._get(f"book/{pair}/R0", params={ "len": len }) ]
|
||||
def get_t_raw_book(
|
||||
self, pair: str, *, len: Optional[Literal[1, 25, 100]] = None
|
||||
) -> List[TradingPairRawBook]:
|
||||
return [
|
||||
serializers.TradingPairRawBook.parse(*sub_data)
|
||||
for sub_data in self._get(f"book/{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(*sub_data) \
|
||||
for sub_data in self._get(f"book/{currency}/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(*sub_data)
|
||||
for sub_data in self._get(f"book/{currency}/R0", params={"len": len})
|
||||
]
|
||||
|
||||
def get_stats_hist(self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Statistic]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_stats_hist(
|
||||
self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = 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(*sub_data) for sub_data in data ]
|
||||
return [serializers.Statistic.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_stats_last(self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> Statistic:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_stats_last(
|
||||
self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = 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,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Candle]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_candles_hist(
|
||||
self,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = 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/trade:{tf}:{symbol}/hist", params=params)
|
||||
return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
|
||||
return [serializers.Candle.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_candles_last(self,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> Candle:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_candles_last(
|
||||
self,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = 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/trade:{tf}:{symbol}/last", params=params)
|
||||
return serializers.Candle.parse(*data)
|
||||
|
||||
def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> Dict[str, DerivativesStatus]:
|
||||
def get_derivatives_status(
|
||||
self, keys: Union[List[str], Literal["ALL"]]
|
||||
) -> Dict[str, DerivativesStatus]:
|
||||
if keys == "ALL":
|
||||
params = { "keys": "ALL" }
|
||||
else: params = { "keys": ",".join(keys) }
|
||||
params = {"keys": "ALL"}
|
||||
else:
|
||||
params = {"keys": ",".join(keys)}
|
||||
|
||||
data = self._get("status/deriv", params=params)
|
||||
|
||||
return {
|
||||
key: serializers.DerivativesStatus.parse(*sub_data)
|
||||
for sub_data in data
|
||||
if (key := sub_data.pop(0))
|
||||
for sub_data in data
|
||||
if (key := sub_data.pop(0))
|
||||
}
|
||||
|
||||
def get_derivatives_status_history(self,
|
||||
key: str,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[DerivativesStatus]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_derivatives_status_history(
|
||||
self,
|
||||
key: str,
|
||||
*,
|
||||
sort: Optional[int] = 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/deriv/{key}/hist", params=params)
|
||||
return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ]
|
||||
return [serializers.DerivativesStatus.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_liquidations(self,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Liquidation]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_liquidations(
|
||||
self,
|
||||
*,
|
||||
sort: Optional[int] = 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(*sub_data[0]) for sub_data in data ]
|
||||
return [serializers.Liquidation.parse(*sub_data[0]) for sub_data in data]
|
||||
|
||||
def get_seed_candles(self,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Candle]:
|
||||
def get_seed_candles(
|
||||
self,
|
||||
symbol: str,
|
||||
tf: str = "1m",
|
||||
*,
|
||||
sort: Optional[int] = 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/trade:{tf}:{symbol}/hist", params=params)
|
||||
return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
|
||||
return [serializers.Candle.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_leaderboards_hist(self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[Leaderboard]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_leaderboards_hist(
|
||||
self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = 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(*sub_data) for sub_data in data ]
|
||||
return [serializers.Leaderboard.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_leaderboards_last(self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = None,
|
||||
start: Optional[str] = None,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> Leaderboard:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
def get_leaderboards_last(
|
||||
self,
|
||||
resource: str,
|
||||
*,
|
||||
sort: Optional[int] = 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 }
|
||||
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(*sub_data) for sub_data in data ]
|
||||
return [serializers.FundingStatistic.parse(*sub_data) for sub_data in data]
|
||||
|
||||
def get_pulse_profile_details(self, nickname: str) -> PulseProfile:
|
||||
return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}"))
|
||||
|
||||
def get_pulse_message_history(self,
|
||||
*,
|
||||
end: Optional[str] = None,
|
||||
limit: Optional[int] = None) -> List[PulseMessage]:
|
||||
def get_pulse_message_history(
|
||||
self, *, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[PulseMessage]:
|
||||
messages = []
|
||||
|
||||
for sub_data in self._get("pulse/hist", params={ "end": end, "limit": limit }):
|
||||
for sub_data in self._get("pulse/hist", params={"end": end, "limit": limit}):
|
||||
sub_data[18] = sub_data[18][0]
|
||||
message = serializers.PulseMessage.parse(*sub_data)
|
||||
messages.append(message)
|
||||
|
||||
return messages
|
||||
|
||||
def get_trading_market_average_price(self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
*,
|
||||
price_limit: Optional[Union[str, float, Decimal]] = None
|
||||
) -> TradingMarketAveragePrice:
|
||||
return serializers.TradingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={
|
||||
"symbol": symbol, "amount": amount, "price_limit": price_limit
|
||||
}))
|
||||
def get_trading_market_average_price(
|
||||
self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
*,
|
||||
price_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
) -> TradingMarketAveragePrice:
|
||||
return serializers.TradingMarketAveragePrice.parse(
|
||||
*self._post(
|
||||
"calc/trade/avg",
|
||||
body={"symbol": symbol, "amount": amount, "price_limit": price_limit},
|
||||
)
|
||||
)
|
||||
|
||||
def get_funding_market_average_price(self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
rate_limit: Optional[Union[str, float, Decimal]] = None
|
||||
) -> FundingMarketAveragePrice:
|
||||
return serializers.FundingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={
|
||||
"symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit
|
||||
}))
|
||||
def get_funding_market_average_price(
|
||||
self,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
rate_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
) -> FundingMarketAveragePrice:
|
||||
return serializers.FundingMarketAveragePrice.parse(
|
||||
*self._post(
|
||||
"calc/trade/avg",
|
||||
body={
|
||||
"symbol": symbol,
|
||||
"amount": amount,
|
||||
"period": period,
|
||||
"rate_limit": rate_limit,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
|
||||
return serializers.FxRate.parse(*self._post("calc/fx", body={ "ccy1": ccy1, "ccy2": ccy2 }))
|
||||
return serializers.FxRate.parse(
|
||||
*self._post("calc/fx", body={"ccy1": ccy1, "ccy2": ccy2})
|
||||
)
|
||||
|
||||
@@ -4,8 +4,10 @@ from bfxapi.exceptions import BfxBaseException
|
||||
class NotFoundError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class RequestParametersError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownGenericError(BfxBaseException):
|
||||
pass
|
||||
|
||||
@@ -16,45 +16,47 @@ from ..exceptions import NotFoundError, RequestParametersError, UnknownGenericEr
|
||||
if TYPE_CHECKING:
|
||||
from requests.sessions import _Params
|
||||
|
||||
|
||||
class _Error(IntEnum):
|
||||
ERR_UNK = 10000
|
||||
ERR_GENERIC = 10001
|
||||
ERR_PARAMS = 10020
|
||||
ERR_AUTH_FAIL = 10100
|
||||
|
||||
|
||||
class Middleware:
|
||||
TIMEOUT = 30
|
||||
|
||||
def __init__(self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None):
|
||||
def __init__(
|
||||
self, host: str, api_key: Optional[str] = None, api_secret: Optional[str] = None
|
||||
):
|
||||
self.host, self.api_key, self.api_secret = host, api_key, api_secret
|
||||
|
||||
def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
|
||||
assert isinstance(self.api_key, str) and isinstance(self.api_secret, str), \
|
||||
"API_KEY and API_SECRET must be both str to call __build_authentication_headers"
|
||||
assert isinstance(self.api_key, str) and isinstance(
|
||||
self.api_secret, str
|
||||
), "API_KEY and API_SECRET must be both str to call __build_authentication_headers"
|
||||
|
||||
nonce = str(round(time.time() * 1_000_000))
|
||||
|
||||
if data is None:
|
||||
path = f"/api/v2/{endpoint}{nonce}"
|
||||
else: path = f"/api/v2/{endpoint}{nonce}{data}"
|
||||
else:
|
||||
path = f"/api/v2/{endpoint}{nonce}{data}"
|
||||
|
||||
signature = hmac.new(
|
||||
self.api_secret.encode("utf8"),
|
||||
path.encode("utf8"),
|
||||
hashlib.sha384
|
||||
self.api_secret.encode("utf8"), path.encode("utf8"), hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
return {
|
||||
"bfx-nonce": nonce,
|
||||
"bfx-signature": signature,
|
||||
"bfx-apikey": self.api_key
|
||||
"bfx-apikey": self.api_key,
|
||||
}
|
||||
|
||||
def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
|
||||
response = requests.get(
|
||||
url=f"{self.host}/{endpoint}",
|
||||
params=params,
|
||||
timeout=Middleware.TIMEOUT
|
||||
url=f"{self.host}/{endpoint}", params=params, timeout=Middleware.TIMEOUT
|
||||
)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
@@ -64,30 +66,43 @@ class Middleware:
|
||||
|
||||
if len(data) and data[0] == "error":
|
||||
if data[1] == _Error.ERR_PARAMS:
|
||||
raise RequestParametersError("The request was rejected with the " \
|
||||
f"following parameter error: <{data[2]}>")
|
||||
raise RequestParametersError(
|
||||
"The request was rejected with the "
|
||||
f"following parameter error: <{data[2]}>"
|
||||
)
|
||||
|
||||
if data[1] is None or data[1] == _Error.ERR_UNK or data[1] == _Error.ERR_GENERIC:
|
||||
raise UnknownGenericError("The server replied to the request with " \
|
||||
f"a generic error with message: <{data[2]}>.")
|
||||
if (
|
||||
data[1] is None
|
||||
or data[1] == _Error.ERR_UNK
|
||||
or data[1] == _Error.ERR_GENERIC
|
||||
):
|
||||
raise UnknownGenericError(
|
||||
"The server replied to the request with "
|
||||
f"a generic error with message: <{data[2]}>."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def _post(self, endpoint: str, params: Optional["_Params"] = None,
|
||||
body: Optional[Any] = None, _ignore_authentication_headers: bool = False) -> Any:
|
||||
def _post(
|
||||
self,
|
||||
endpoint: str,
|
||||
params: Optional["_Params"] = None,
|
||||
body: Optional[Any] = None,
|
||||
_ignore_authentication_headers: bool = False,
|
||||
) -> Any:
|
||||
data = body and json.dumps(body, cls=JSONEncoder) or None
|
||||
|
||||
headers = { "Content-Type": "application/json" }
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if self.api_key and self.api_secret and not _ignore_authentication_headers:
|
||||
headers = { **headers, **self.__build_authentication_headers(endpoint, data) }
|
||||
headers = {**headers, **self.__build_authentication_headers(endpoint, data)}
|
||||
|
||||
response = requests.post(
|
||||
url=f"{self.host}/{endpoint}",
|
||||
params=params,
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=Middleware.TIMEOUT
|
||||
timeout=Middleware.TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
@@ -97,14 +112,24 @@ class Middleware:
|
||||
|
||||
if isinstance(data, list) and len(data) and data[0] == "error":
|
||||
if data[1] == _Error.ERR_PARAMS:
|
||||
raise RequestParametersError("The request was rejected with the " \
|
||||
f"following parameter error: <{data[2]}>")
|
||||
raise RequestParametersError(
|
||||
"The request was rejected with the "
|
||||
f"following parameter error: <{data[2]}>"
|
||||
)
|
||||
|
||||
if data[1] == _Error.ERR_AUTH_FAIL:
|
||||
raise InvalidCredentialError("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
raise InvalidCredentialError(
|
||||
"Cannot authenticate with given API-KEY and API-SECRET."
|
||||
)
|
||||
|
||||
if data[1] is None or data[1] == _Error.ERR_UNK or data[1] == _Error.ERR_GENERIC:
|
||||
raise UnknownGenericError("The server replied to the request with " \
|
||||
f"a generic error with message: <{data[2]}>.")
|
||||
if (
|
||||
data[1] is None
|
||||
or data[1] == _Error.ERR_UNK
|
||||
or data[1] == _Error.ERR_GENERIC
|
||||
):
|
||||
raise UnknownGenericError(
|
||||
"The server replied to the request with "
|
||||
f"a generic error with message: <{data[2]}>."
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@@ -3,12 +3,14 @@ from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from .labeler import _Type, compose, partial
|
||||
|
||||
#region Dataclass definitions for types of public use
|
||||
# region Dataclass definitions for types of public use
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlatformStatus(_Type):
|
||||
status: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingPairTicker(_Type):
|
||||
bid: float
|
||||
@@ -22,6 +24,7 @@ class TradingPairTicker(_Type):
|
||||
high: float
|
||||
low: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTicker(_Type):
|
||||
frr: float
|
||||
@@ -39,6 +42,7 @@ class FundingCurrencyTicker(_Type):
|
||||
low: float
|
||||
frr_amount_available: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class TickersHistory(_Type):
|
||||
symbol: str
|
||||
@@ -46,6 +50,7 @@ class TickersHistory(_Type):
|
||||
ask: float
|
||||
mts: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingPairTrade(_Type):
|
||||
id: int
|
||||
@@ -53,6 +58,7 @@ class TradingPairTrade(_Type):
|
||||
amount: float
|
||||
price: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTrade(_Type):
|
||||
id: int
|
||||
@@ -61,12 +67,14 @@ class FundingCurrencyTrade(_Type):
|
||||
rate: float
|
||||
period: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingPairBook(_Type):
|
||||
price: float
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyBook(_Type):
|
||||
rate: float
|
||||
@@ -74,12 +82,14 @@ class FundingCurrencyBook(_Type):
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingPairRawBook(_Type):
|
||||
order_id: int
|
||||
price: float
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyRawBook(_Type):
|
||||
offer_id: int
|
||||
@@ -87,11 +97,13 @@ class FundingCurrencyRawBook(_Type):
|
||||
rate: float
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Statistic(_Type):
|
||||
mts: int
|
||||
value: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Candle(_Type):
|
||||
mts: int
|
||||
@@ -101,6 +113,7 @@ class Candle(_Type):
|
||||
low: int
|
||||
volume: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class DerivativesStatus(_Type):
|
||||
mts: int
|
||||
@@ -116,6 +129,7 @@ class DerivativesStatus(_Type):
|
||||
clamp_min: float
|
||||
clamp_max: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Liquidation(_Type):
|
||||
pos_id: int
|
||||
@@ -127,6 +141,7 @@ class Liquidation(_Type):
|
||||
is_market_sold: int
|
||||
liquidation_price: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Leaderboard(_Type):
|
||||
mts: int
|
||||
@@ -135,6 +150,7 @@ class Leaderboard(_Type):
|
||||
value: float
|
||||
twitter_handle: Optional[str]
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingStatistic(_Type):
|
||||
mts: int
|
||||
@@ -144,6 +160,7 @@ class FundingStatistic(_Type):
|
||||
funding_amount_used: float
|
||||
funding_below_threshold: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class PulseProfile(_Type):
|
||||
puid: str
|
||||
@@ -156,6 +173,7 @@ class PulseProfile(_Type):
|
||||
following: int
|
||||
tipping_status: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class PulseMessage(_Type):
|
||||
pid: str
|
||||
@@ -173,23 +191,28 @@ class PulseMessage(_Type):
|
||||
profile: PulseProfile
|
||||
comments: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingMarketAveragePrice(_Type):
|
||||
price_avg: float
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingMarketAveragePrice(_Type):
|
||||
rate_avg: float
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class FxRate(_Type):
|
||||
current_rate: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dataclass definitions for types of auth use
|
||||
# endregion
|
||||
|
||||
# region Dataclass definitions for types of auth use
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserInfo(_Type):
|
||||
@@ -222,6 +245,7 @@ class UserInfo(_Type):
|
||||
compl_countries_resid: List[str]
|
||||
is_merchant_enterprise: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoginHistory(_Type):
|
||||
id: int
|
||||
@@ -229,10 +253,12 @@ class LoginHistory(_Type):
|
||||
ip: str
|
||||
extra_info: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BalanceAvailable(_Type):
|
||||
amount: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Order(_Type):
|
||||
id: int
|
||||
@@ -258,6 +284,7 @@ class Order(_Type):
|
||||
routing: str
|
||||
meta: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Position(_Type):
|
||||
symbol: str
|
||||
@@ -278,6 +305,7 @@ class Position(_Type):
|
||||
collateral_min: float
|
||||
meta: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Trade(_Type):
|
||||
id: int
|
||||
@@ -288,11 +316,12 @@ class Trade(_Type):
|
||||
exec_price: float
|
||||
order_type: str
|
||||
order_price: float
|
||||
maker:int
|
||||
maker: int
|
||||
fee: float
|
||||
fee_currency: str
|
||||
cid: int
|
||||
|
||||
|
||||
@dataclass()
|
||||
class FundingTrade(_Type):
|
||||
id: int
|
||||
@@ -303,6 +332,7 @@ class FundingTrade(_Type):
|
||||
rate: float
|
||||
period: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrderTrade(_Type):
|
||||
id: int
|
||||
@@ -311,11 +341,12 @@ class OrderTrade(_Type):
|
||||
order_id: int
|
||||
exec_amount: float
|
||||
exec_price: float
|
||||
maker:int
|
||||
maker: int
|
||||
fee: float
|
||||
fee_currency: str
|
||||
cid: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Ledger(_Type):
|
||||
id: int
|
||||
@@ -325,6 +356,7 @@ class Ledger(_Type):
|
||||
balance: float
|
||||
description: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingOffer(_Type):
|
||||
id: int
|
||||
@@ -342,6 +374,7 @@ class FundingOffer(_Type):
|
||||
hidden: int
|
||||
renew: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingCredit(_Type):
|
||||
id: int
|
||||
@@ -363,6 +396,7 @@ class FundingCredit(_Type):
|
||||
no_close: int
|
||||
position_pair: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingLoan(_Type):
|
||||
id: int
|
||||
@@ -383,6 +417,7 @@ class FundingLoan(_Type):
|
||||
renew: int
|
||||
no_close: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundingAutoRenew(_Type):
|
||||
currency: str
|
||||
@@ -390,6 +425,7 @@ class FundingAutoRenew(_Type):
|
||||
rate: float
|
||||
threshold: float
|
||||
|
||||
|
||||
@dataclass()
|
||||
class FundingInfo(_Type):
|
||||
yield_loan: float
|
||||
@@ -397,6 +433,7 @@ class FundingInfo(_Type):
|
||||
duration_loan: float
|
||||
duration_lend: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Wallet(_Type):
|
||||
wallet_type: str
|
||||
@@ -407,6 +444,7 @@ class Wallet(_Type):
|
||||
last_change: str
|
||||
trade_details: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Transfer(_Type):
|
||||
mts: int
|
||||
@@ -416,6 +454,7 @@ class Transfer(_Type):
|
||||
currency_to: str
|
||||
amount: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Withdrawal(_Type):
|
||||
withdrawal_id: int
|
||||
@@ -425,6 +464,7 @@ class Withdrawal(_Type):
|
||||
amount: float
|
||||
withdrawal_fee: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class DepositAddress(_Type):
|
||||
method: str
|
||||
@@ -432,12 +472,14 @@ class DepositAddress(_Type):
|
||||
address: str
|
||||
pool_address: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class LightningNetworkInvoice(_Type):
|
||||
invoice_hash: str
|
||||
invoice: str
|
||||
amount: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Movement(_Type):
|
||||
id: str
|
||||
@@ -452,6 +494,7 @@ class Movement(_Type):
|
||||
transaction_id: str
|
||||
withdraw_transaction_note: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class SymbolMarginInfo(_Type):
|
||||
symbol: str
|
||||
@@ -460,6 +503,7 @@ class SymbolMarginInfo(_Type):
|
||||
buy: float
|
||||
sell: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseMarginInfo(_Type):
|
||||
user_pl: float
|
||||
@@ -468,6 +512,7 @@ class BaseMarginInfo(_Type):
|
||||
margin_net: float
|
||||
margin_min: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionClaim(_Type):
|
||||
symbol: str
|
||||
@@ -484,6 +529,7 @@ class PositionClaim(_Type):
|
||||
min_collateral: str
|
||||
meta: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionIncreaseInfo(_Type):
|
||||
max_pos: int
|
||||
@@ -499,12 +545,14 @@ class PositionIncreaseInfo(_Type):
|
||||
funding_value_currency: str
|
||||
funding_required_currency: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionIncrease(_Type):
|
||||
symbol: str
|
||||
amount: float
|
||||
base_price: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionHistory(_Type):
|
||||
symbol: str
|
||||
@@ -517,6 +565,7 @@ class PositionHistory(_Type):
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionSnapshot(_Type):
|
||||
symbol: str
|
||||
@@ -529,6 +578,7 @@ class PositionSnapshot(_Type):
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class PositionAudit(_Type):
|
||||
symbol: str
|
||||
@@ -545,18 +595,22 @@ class PositionAudit(_Type):
|
||||
collateral_min: float
|
||||
meta: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class DerivativePositionCollateral(_Type):
|
||||
status: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class DerivativePositionCollateralLimits(_Type):
|
||||
min_collateral: float
|
||||
max_collateral: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dataclass definitions for types of merchant use
|
||||
# endregion
|
||||
|
||||
# region Dataclass definitions for types of merchant use
|
||||
|
||||
|
||||
@compose(dataclass, partial)
|
||||
class InvoiceSubmission(_Type):
|
||||
@@ -580,7 +634,9 @@ class InvoiceSubmission(_Type):
|
||||
@classmethod
|
||||
def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission":
|
||||
if "customer_info" in data and data["customer_info"] is not None:
|
||||
data["customer_info"] = InvoiceSubmission.CustomerInfo(**data["customer_info"])
|
||||
data["customer_info"] = InvoiceSubmission.CustomerInfo(
|
||||
**data["customer_info"]
|
||||
)
|
||||
|
||||
for index, invoice in enumerate(data["invoices"]):
|
||||
data["invoices"][index] = InvoiceSubmission.Invoice(**invoice)
|
||||
@@ -590,7 +646,9 @@ class InvoiceSubmission(_Type):
|
||||
|
||||
if "additional_payments" in data and data["additional_payments"] is not None:
|
||||
for index, additional_payment in enumerate(data["additional_payments"]):
|
||||
data["additional_payments"][index] = InvoiceSubmission.Payment(**additional_payment)
|
||||
data["additional_payments"][index] = InvoiceSubmission.Payment(
|
||||
**additional_payment
|
||||
)
|
||||
|
||||
return InvoiceSubmission(**data)
|
||||
|
||||
@@ -631,6 +689,7 @@ class InvoiceSubmission(_Type):
|
||||
force_completed: bool
|
||||
amount_diff: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvoicePage(_Type):
|
||||
page: int
|
||||
@@ -648,17 +707,20 @@ class InvoicePage(_Type):
|
||||
|
||||
return InvoicePage(**data)
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvoiceStats(_Type):
|
||||
time: str
|
||||
count: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class CurrencyConversion(_Type):
|
||||
base_ccy: str
|
||||
convert_ccy: str
|
||||
created: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class MerchantDeposit(_Type):
|
||||
id: int
|
||||
@@ -672,6 +734,7 @@ class MerchantDeposit(_Type):
|
||||
method: str
|
||||
pay_method: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class MerchantUnlinkedDeposit(_Type):
|
||||
id: int
|
||||
@@ -687,4 +750,5 @@ class MerchantUnlinkedDeposit(_Type):
|
||||
status: str
|
||||
note: Optional[str]
|
||||
|
||||
#endregion
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import Any, Dict, Generic, Iterable, List, Tuple, Type, TypeVar, cas
|
||||
|
||||
T = TypeVar("T", bound="_Type")
|
||||
|
||||
|
||||
def compose(*decorators):
|
||||
def wrapper(function):
|
||||
for decorator in reversed(decorators):
|
||||
@@ -10,30 +11,37 @@ def compose(*decorators):
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def partial(cls):
|
||||
def __init__(self, **kwargs):
|
||||
for annotation in self.__annotations__.keys():
|
||||
if annotation not in kwargs:
|
||||
self.__setattr__(annotation, None)
|
||||
else: self.__setattr__(annotation, kwargs[annotation])
|
||||
else:
|
||||
self.__setattr__(annotation, kwargs[annotation])
|
||||
|
||||
kwargs.pop(annotation, None)
|
||||
|
||||
if len(kwargs) != 0:
|
||||
raise TypeError(f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
|
||||
raise TypeError(
|
||||
f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'"
|
||||
)
|
||||
|
||||
cls.__init__ = __init__
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class _Type:
|
||||
"""
|
||||
Base class for any dataclass serializable by the _Serializer generic class.
|
||||
"""
|
||||
|
||||
|
||||
class _Serializer(Generic[T]):
|
||||
def __init__(self, name: str, klass: Type[_Type], labels: List[str],
|
||||
*, flat: bool = False):
|
||||
def __init__(
|
||||
self, name: str, klass: Type[_Type], labels: List[str], *, flat: bool = False
|
||||
):
|
||||
self.name, self.klass, self.__labels, self.__flat = name, klass, labels, flat
|
||||
|
||||
def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]:
|
||||
@@ -41,8 +49,10 @@ class _Serializer(Generic[T]):
|
||||
args = tuple(_Serializer.__flatten(list(args)))
|
||||
|
||||
if len(self.__labels) > len(args):
|
||||
raise AssertionError(f"{self.name} -> <labels> and <*args> " \
|
||||
"arguments should contain the same amount of elements.")
|
||||
raise AssertionError(
|
||||
f"{self.name} -> <labels> and <*args> "
|
||||
"arguments should contain the same amount of elements."
|
||||
)
|
||||
|
||||
for index, label in enumerate(self.__labels):
|
||||
if label != "_PLACEHOLDER":
|
||||
@@ -52,7 +62,7 @@ class _Serializer(Generic[T]):
|
||||
return cast(T, self.klass(**dict(self._serialize(*values))))
|
||||
|
||||
def get_labels(self) -> List[str]:
|
||||
return [ label for label in self.__labels if label != "_PLACEHOLDER" ]
|
||||
return [label for label in self.__labels if label != "_PLACEHOLDER"]
|
||||
|
||||
@classmethod
|
||||
def __flatten(cls, array: List[Any]) -> List[Any]:
|
||||
@@ -64,10 +74,17 @@ class _Serializer(Generic[T]):
|
||||
|
||||
return array[:1] + cls.__flatten(array[1:])
|
||||
|
||||
|
||||
class _RecursiveSerializer(_Serializer, Generic[T]):
|
||||
def __init__(self, name: str, klass: Type[_Type], labels: List[str],
|
||||
*, serializers: Dict[str, _Serializer[Any]],
|
||||
flat: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
klass: Type[_Type],
|
||||
labels: List[str],
|
||||
*,
|
||||
serializers: Dict[str, _Serializer[Any]],
|
||||
flat: bool = False,
|
||||
):
|
||||
super().__init__(name, klass, labels, flat=flat)
|
||||
|
||||
self.serializers = serializers
|
||||
@@ -81,15 +98,21 @@ class _RecursiveSerializer(_Serializer, Generic[T]):
|
||||
|
||||
return cast(T, self.klass(**serialization))
|
||||
|
||||
def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str],
|
||||
*, flat: bool = False
|
||||
) -> _Serializer[T]:
|
||||
return _Serializer[T](name, klass, labels, \
|
||||
flat=flat)
|
||||
|
||||
def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str],
|
||||
*, serializers: Dict[str, _Serializer[Any]],
|
||||
flat: bool = False
|
||||
) -> _RecursiveSerializer[T]:
|
||||
return _RecursiveSerializer[T](name, klass, labels, \
|
||||
serializers=serializers, flat=flat)
|
||||
def generate_labeler_serializer(
|
||||
name: str, klass: Type[T], labels: List[str], *, flat: bool = False
|
||||
) -> _Serializer[T]:
|
||||
return _Serializer[T](name, klass, labels, flat=flat)
|
||||
|
||||
|
||||
def generate_recursive_serializer(
|
||||
name: str,
|
||||
klass: Type[T],
|
||||
labels: List[str],
|
||||
*,
|
||||
serializers: Dict[str, _Serializer[Any]],
|
||||
flat: bool = False,
|
||||
) -> _RecursiveSerializer[T]:
|
||||
return _RecursiveSerializer[T](
|
||||
name, klass, labels, serializers=serializers, flat=flat
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ from .labeler import _Serializer, _Type
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Notification(_Type, Generic[T]):
|
||||
mts: int
|
||||
@@ -15,16 +16,30 @@ class Notification(_Type, Generic[T]):
|
||||
status: str
|
||||
text: str
|
||||
|
||||
class _Notification(_Serializer, Generic[T]):
|
||||
__LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "data", "code", "status", "text" ]
|
||||
|
||||
def __init__(self, serializer: Optional[_Serializer] = None, is_iterable: bool = False):
|
||||
class _Notification(_Serializer, Generic[T]):
|
||||
__LABELS = [
|
||||
"mts",
|
||||
"type",
|
||||
"message_id",
|
||||
"_PLACEHOLDER",
|
||||
"data",
|
||||
"code",
|
||||
"status",
|
||||
"text",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self, serializer: Optional[_Serializer] = None, is_iterable: bool = False
|
||||
):
|
||||
super().__init__("Notification", Notification, _Notification.__LABELS)
|
||||
|
||||
self.serializer, self.is_iterable = serializer, is_iterable
|
||||
|
||||
def parse(self, *values: Any) -> Notification[T]:
|
||||
notification = cast(Notification[T], Notification(**dict(self._serialize(*values))))
|
||||
notification = cast(
|
||||
Notification[T], Notification(**dict(self._serialize(*values)))
|
||||
)
|
||||
|
||||
if isinstance(self.serializer, _Serializer):
|
||||
data = cast(List[Any], notification.data)
|
||||
@@ -34,6 +49,9 @@ class _Notification(_Serializer, Generic[T]):
|
||||
data = data[0]
|
||||
|
||||
notification.data = self.serializer.parse(*data)
|
||||
else: notification.data = cast(T, [ self.serializer.parse(*sub_data) for sub_data in data ])
|
||||
else:
|
||||
notification.data = cast(
|
||||
T, [self.serializer.parse(*sub_data) for sub_data in data]
|
||||
)
|
||||
|
||||
return notification
|
||||
|
||||
@@ -1,44 +1,73 @@
|
||||
from . import dataclasses
|
||||
|
||||
#pylint: disable-next=unused-import
|
||||
# pylint: disable-next=unused-import
|
||||
from .labeler import (
|
||||
_Serializer,
|
||||
generate_labeler_serializer,
|
||||
generate_recursive_serializer,
|
||||
)
|
||||
|
||||
#pylint: disable-next=unused-import
|
||||
# pylint: disable-next=unused-import
|
||||
from .notification import _Notification
|
||||
|
||||
__serializers__ = [
|
||||
"PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker",
|
||||
"TickersHistory", "TradingPairTrade", "FundingCurrencyTrade",
|
||||
"TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook",
|
||||
"FundingCurrencyRawBook", "Statistic", "Candle",
|
||||
"DerivativesStatus", "Liquidation", "Leaderboard",
|
||||
"FundingStatistic", "PulseProfile", "PulseMessage",
|
||||
"TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate",
|
||||
|
||||
"UserInfo", "LoginHistory", "BalanceAvailable",
|
||||
"Order", "Position", "Trade",
|
||||
"FundingTrade", "OrderTrade", "Ledger",
|
||||
"FundingOffer", "FundingCredit", "FundingLoan",
|
||||
"FundingAutoRenew", "FundingInfo", "Wallet",
|
||||
"Transfer", "Withdrawal", "DepositAddress",
|
||||
"LightningNetworkInvoice", "Movement", "SymbolMarginInfo",
|
||||
"BaseMarginInfo", "PositionClaim", "PositionIncreaseInfo",
|
||||
"PositionIncrease", "PositionHistory", "PositionSnapshot",
|
||||
"PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits",
|
||||
"PlatformStatus",
|
||||
"TradingPairTicker",
|
||||
"FundingCurrencyTicker",
|
||||
"TickersHistory",
|
||||
"TradingPairTrade",
|
||||
"FundingCurrencyTrade",
|
||||
"TradingPairBook",
|
||||
"FundingCurrencyBook",
|
||||
"TradingPairRawBook",
|
||||
"FundingCurrencyRawBook",
|
||||
"Statistic",
|
||||
"Candle",
|
||||
"DerivativesStatus",
|
||||
"Liquidation",
|
||||
"Leaderboard",
|
||||
"FundingStatistic",
|
||||
"PulseProfile",
|
||||
"PulseMessage",
|
||||
"TradingMarketAveragePrice",
|
||||
"FundingMarketAveragePrice",
|
||||
"FxRate",
|
||||
"UserInfo",
|
||||
"LoginHistory",
|
||||
"BalanceAvailable",
|
||||
"Order",
|
||||
"Position",
|
||||
"Trade",
|
||||
"FundingTrade",
|
||||
"OrderTrade",
|
||||
"Ledger",
|
||||
"FundingOffer",
|
||||
"FundingCredit",
|
||||
"FundingLoan",
|
||||
"FundingAutoRenew",
|
||||
"FundingInfo",
|
||||
"Wallet",
|
||||
"Transfer",
|
||||
"Withdrawal",
|
||||
"DepositAddress",
|
||||
"LightningNetworkInvoice",
|
||||
"Movement",
|
||||
"SymbolMarginInfo",
|
||||
"BaseMarginInfo",
|
||||
"PositionClaim",
|
||||
"PositionIncreaseInfo",
|
||||
"PositionIncrease",
|
||||
"PositionHistory",
|
||||
"PositionSnapshot",
|
||||
"PositionAudit",
|
||||
"DerivativePositionCollateral",
|
||||
"DerivativePositionCollateralLimits",
|
||||
]
|
||||
|
||||
#region Serializer definitions for types of public use
|
||||
# region Serializer definitions for types of public use
|
||||
|
||||
PlatformStatus = generate_labeler_serializer(
|
||||
name="PlatformStatus",
|
||||
klass=dataclasses.PlatformStatus,
|
||||
labels=[
|
||||
"status"
|
||||
]
|
||||
name="PlatformStatus", klass=dataclasses.PlatformStatus, labels=["status"]
|
||||
)
|
||||
|
||||
TradingPairTicker = generate_labeler_serializer(
|
||||
@@ -54,8 +83,8 @@ TradingPairTicker = generate_labeler_serializer(
|
||||
"last_price",
|
||||
"volume",
|
||||
"high",
|
||||
"low"
|
||||
]
|
||||
"low",
|
||||
],
|
||||
)
|
||||
|
||||
FundingCurrencyTicker = generate_labeler_serializer(
|
||||
@@ -77,8 +106,8 @@ FundingCurrencyTicker = generate_labeler_serializer(
|
||||
"low",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"frr_amount_available"
|
||||
]
|
||||
"frr_amount_available",
|
||||
],
|
||||
)
|
||||
|
||||
TickersHistory = generate_labeler_serializer(
|
||||
@@ -97,95 +126,54 @@ TickersHistory = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"mts"
|
||||
]
|
||||
"mts",
|
||||
],
|
||||
)
|
||||
|
||||
TradingPairTrade = generate_labeler_serializer(
|
||||
name="TradingPairTrade",
|
||||
klass=dataclasses.TradingPairTrade,
|
||||
labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"price"
|
||||
]
|
||||
labels=["id", "mts", "amount", "price"],
|
||||
)
|
||||
|
||||
FundingCurrencyTrade = generate_labeler_serializer(
|
||||
name="FundingCurrencyTrade",
|
||||
klass=dataclasses.FundingCurrencyTrade,
|
||||
labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"rate",
|
||||
"period"
|
||||
]
|
||||
labels=["id", "mts", "amount", "rate", "period"],
|
||||
)
|
||||
|
||||
TradingPairBook = generate_labeler_serializer(
|
||||
name="TradingPairBook",
|
||||
klass=dataclasses.TradingPairBook,
|
||||
labels=[
|
||||
"price",
|
||||
"count",
|
||||
"amount"
|
||||
]
|
||||
labels=["price", "count", "amount"],
|
||||
)
|
||||
|
||||
FundingCurrencyBook = generate_labeler_serializer(
|
||||
name="FundingCurrencyBook",
|
||||
klass=dataclasses.FundingCurrencyBook,
|
||||
labels=[
|
||||
"rate",
|
||||
"period",
|
||||
"count",
|
||||
"amount"
|
||||
]
|
||||
labels=["rate", "period", "count", "amount"],
|
||||
)
|
||||
|
||||
TradingPairRawBook = generate_labeler_serializer(
|
||||
name="TradingPairRawBook",
|
||||
klass=dataclasses.TradingPairRawBook,
|
||||
labels=[
|
||||
"order_id",
|
||||
"price",
|
||||
"amount"
|
||||
]
|
||||
labels=["order_id", "price", "amount"],
|
||||
)
|
||||
|
||||
FundingCurrencyRawBook = generate_labeler_serializer(
|
||||
name="FundingCurrencyRawBook",
|
||||
klass=dataclasses.FundingCurrencyRawBook,
|
||||
labels=[
|
||||
"offer_id",
|
||||
"period",
|
||||
"rate",
|
||||
"amount"
|
||||
]
|
||||
labels=["offer_id", "period", "rate", "amount"],
|
||||
)
|
||||
|
||||
Statistic = generate_labeler_serializer(
|
||||
name="Statistic",
|
||||
klass=dataclasses.Statistic,
|
||||
labels=[
|
||||
"mts",
|
||||
"value"
|
||||
]
|
||||
name="Statistic", klass=dataclasses.Statistic, labels=["mts", "value"]
|
||||
)
|
||||
|
||||
Candle = generate_labeler_serializer(
|
||||
name="Candle",
|
||||
klass=dataclasses.Candle,
|
||||
labels=[
|
||||
"mts",
|
||||
"open",
|
||||
"close",
|
||||
"high",
|
||||
"low",
|
||||
"volume"
|
||||
]
|
||||
labels=["mts", "open", "close", "high", "low", "volume"],
|
||||
)
|
||||
|
||||
DerivativesStatus = generate_labeler_serializer(
|
||||
@@ -193,7 +181,7 @@ DerivativesStatus = generate_labeler_serializer(
|
||||
klass=dataclasses.DerivativesStatus,
|
||||
labels=[
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"deriv_price",
|
||||
"spot_price",
|
||||
"_PLACEHOLDER",
|
||||
@@ -214,8 +202,8 @@ DerivativesStatus = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"clamp_min",
|
||||
"clamp_max"
|
||||
]
|
||||
"clamp_max",
|
||||
],
|
||||
)
|
||||
|
||||
Liquidation = generate_labeler_serializer(
|
||||
@@ -233,8 +221,8 @@ Liquidation = generate_labeler_serializer(
|
||||
"is_match",
|
||||
"is_market_sold",
|
||||
"_PLACEHOLDER",
|
||||
"liquidation_price"
|
||||
]
|
||||
"liquidation_price",
|
||||
],
|
||||
)
|
||||
|
||||
Leaderboard = generate_labeler_serializer(
|
||||
@@ -250,8 +238,8 @@ Leaderboard = generate_labeler_serializer(
|
||||
"value",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"twitter_handle"
|
||||
]
|
||||
"twitter_handle",
|
||||
],
|
||||
)
|
||||
|
||||
FundingStatistic = generate_labeler_serializer(
|
||||
@@ -269,8 +257,8 @@ FundingStatistic = generate_labeler_serializer(
|
||||
"funding_amount_used",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"funding_below_threshold"
|
||||
]
|
||||
"funding_below_threshold",
|
||||
],
|
||||
)
|
||||
|
||||
PulseProfile = generate_labeler_serializer(
|
||||
@@ -293,14 +281,14 @@ PulseProfile = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"tipping_status"
|
||||
]
|
||||
"tipping_status",
|
||||
],
|
||||
)
|
||||
|
||||
PulseMessage = generate_recursive_serializer(
|
||||
name="PulseMessage",
|
||||
klass=dataclasses.PulseMessage,
|
||||
serializers={ "profile": PulseProfile },
|
||||
serializers={"profile": PulseProfile},
|
||||
labels=[
|
||||
"pid",
|
||||
"mts",
|
||||
@@ -314,7 +302,7 @@ PulseMessage = generate_recursive_serializer(
|
||||
"is_pin",
|
||||
"is_public",
|
||||
"comments_disabled",
|
||||
"tags",
|
||||
"tags",
|
||||
"attachments",
|
||||
"meta",
|
||||
"likes",
|
||||
@@ -323,39 +311,29 @@ PulseMessage = generate_recursive_serializer(
|
||||
"profile",
|
||||
"comments",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER"
|
||||
]
|
||||
"_PLACEHOLDER",
|
||||
],
|
||||
)
|
||||
|
||||
TradingMarketAveragePrice = generate_labeler_serializer(
|
||||
name="TradingMarketAveragePrice",
|
||||
klass=dataclasses.TradingMarketAveragePrice,
|
||||
labels=[
|
||||
"price_avg",
|
||||
"amount"
|
||||
]
|
||||
labels=["price_avg", "amount"],
|
||||
)
|
||||
|
||||
FundingMarketAveragePrice = generate_labeler_serializer(
|
||||
name="FundingMarketAveragePrice",
|
||||
klass=dataclasses.FundingMarketAveragePrice,
|
||||
labels=[
|
||||
"rate_avg",
|
||||
"amount"
|
||||
]
|
||||
labels=["rate_avg", "amount"],
|
||||
)
|
||||
|
||||
FxRate = generate_labeler_serializer(
|
||||
name="FxRate",
|
||||
klass=dataclasses.FxRate,
|
||||
labels=[
|
||||
"current_rate"
|
||||
]
|
||||
name="FxRate", klass=dataclasses.FxRate, labels=["current_rate"]
|
||||
)
|
||||
|
||||
#endregion
|
||||
# endregion
|
||||
|
||||
#region Serializer definitions for types of auth use
|
||||
# region Serializer definitions for types of auth use
|
||||
|
||||
UserInfo = generate_labeler_serializer(
|
||||
name="UserInfo",
|
||||
@@ -415,8 +393,8 @@ UserInfo = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"is_merchant_enterprise"
|
||||
]
|
||||
"is_merchant_enterprise",
|
||||
],
|
||||
)
|
||||
|
||||
LoginHistory = generate_labeler_serializer(
|
||||
@@ -430,16 +408,12 @@ LoginHistory = generate_labeler_serializer(
|
||||
"ip",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"extra_info"
|
||||
]
|
||||
"extra_info",
|
||||
],
|
||||
)
|
||||
|
||||
BalanceAvailable = generate_labeler_serializer(
|
||||
name="BalanceAvailable",
|
||||
klass=dataclasses.BalanceAvailable,
|
||||
labels=[
|
||||
"amount"
|
||||
]
|
||||
name="BalanceAvailable", klass=dataclasses.BalanceAvailable, labels=["amount"]
|
||||
)
|
||||
|
||||
Order = generate_labeler_serializer(
|
||||
@@ -450,10 +424,10 @@ Order = generate_labeler_serializer(
|
||||
"gid",
|
||||
"cid",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"order_type",
|
||||
"type_prev",
|
||||
"mts_tif",
|
||||
@@ -470,26 +444,26 @@ Order = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"notify",
|
||||
"hidden",
|
||||
"hidden",
|
||||
"placed_id",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"routing",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"meta"
|
||||
]
|
||||
"meta",
|
||||
],
|
||||
)
|
||||
|
||||
Position = generate_labeler_serializer(
|
||||
name="Position",
|
||||
klass=dataclasses.Position,
|
||||
labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"margin_funding",
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"margin_funding",
|
||||
"margin_funding_type",
|
||||
"pl",
|
||||
"pl_perc",
|
||||
@@ -504,41 +478,33 @@ Position = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"collateral_min",
|
||||
"meta"
|
||||
]
|
||||
"meta",
|
||||
],
|
||||
)
|
||||
|
||||
Trade = generate_labeler_serializer(
|
||||
name="Trade",
|
||||
klass=dataclasses.Trade,
|
||||
labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"order_id",
|
||||
"exec_amount",
|
||||
"exec_price",
|
||||
"order_type",
|
||||
"order_price",
|
||||
"maker",
|
||||
"fee",
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"order_id",
|
||||
"exec_amount",
|
||||
"exec_price",
|
||||
"order_type",
|
||||
"order_price",
|
||||
"maker",
|
||||
"fee",
|
||||
"fee_currency",
|
||||
"cid"
|
||||
]
|
||||
"cid",
|
||||
],
|
||||
)
|
||||
|
||||
FundingTrade = generate_labeler_serializer(
|
||||
name="FundingTrade",
|
||||
klass=dataclasses.FundingTrade,
|
||||
labels=[
|
||||
"id",
|
||||
"currency",
|
||||
"mts_create",
|
||||
"offer_id",
|
||||
"amount",
|
||||
"rate",
|
||||
"period"
|
||||
]
|
||||
labels=["id", "currency", "mts_create", "offer_id", "amount", "rate", "period"],
|
||||
)
|
||||
|
||||
OrderTrade = generate_labeler_serializer(
|
||||
@@ -556,8 +522,8 @@ OrderTrade = generate_labeler_serializer(
|
||||
"maker",
|
||||
"fee",
|
||||
"fee_currency",
|
||||
"cid"
|
||||
]
|
||||
"cid",
|
||||
],
|
||||
)
|
||||
|
||||
Ledger = generate_labeler_serializer(
|
||||
@@ -572,8 +538,8 @@ Ledger = generate_labeler_serializer(
|
||||
"amount",
|
||||
"balance",
|
||||
"_PLACEHOLDER",
|
||||
"description"
|
||||
]
|
||||
"description",
|
||||
],
|
||||
)
|
||||
|
||||
FundingOffer = generate_labeler_serializer(
|
||||
@@ -600,8 +566,8 @@ FundingOffer = generate_labeler_serializer(
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"renew",
|
||||
"_PLACEHOLDER"
|
||||
]
|
||||
"_PLACEHOLDER",
|
||||
],
|
||||
)
|
||||
|
||||
FundingCredit = generate_labeler_serializer(
|
||||
@@ -629,8 +595,8 @@ FundingCredit = generate_labeler_serializer(
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close",
|
||||
"position_pair"
|
||||
]
|
||||
"position_pair",
|
||||
],
|
||||
)
|
||||
|
||||
FundingLoan = generate_labeler_serializer(
|
||||
@@ -657,44 +623,34 @@ FundingLoan = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close"
|
||||
]
|
||||
"no_close",
|
||||
],
|
||||
)
|
||||
|
||||
FundingAutoRenew = generate_labeler_serializer(
|
||||
name="FundingAutoRenew",
|
||||
klass=dataclasses.FundingAutoRenew,
|
||||
labels=[
|
||||
"currency",
|
||||
"period",
|
||||
"rate",
|
||||
"threshold"
|
||||
]
|
||||
labels=["currency", "period", "rate", "threshold"],
|
||||
)
|
||||
|
||||
FundingInfo = generate_labeler_serializer(
|
||||
name="FundingInfo",
|
||||
klass=dataclasses.FundingInfo,
|
||||
labels=[
|
||||
"yield_loan",
|
||||
"yield_lend",
|
||||
"duration_loan",
|
||||
"duration_lend"
|
||||
]
|
||||
labels=["yield_loan", "yield_lend", "duration_loan", "duration_lend"],
|
||||
)
|
||||
|
||||
Wallet = generate_labeler_serializer(
|
||||
name="Wallet",
|
||||
klass=dataclasses.Wallet,
|
||||
labels=[
|
||||
"wallet_type",
|
||||
"currency",
|
||||
"balance",
|
||||
"wallet_type",
|
||||
"currency",
|
||||
"balance",
|
||||
"unsettled_interest",
|
||||
"available_balance",
|
||||
"last_change",
|
||||
"trade_details"
|
||||
]
|
||||
"trade_details",
|
||||
],
|
||||
)
|
||||
|
||||
Transfer = generate_labeler_serializer(
|
||||
@@ -708,8 +664,8 @@ Transfer = generate_labeler_serializer(
|
||||
"currency",
|
||||
"currency_to",
|
||||
"_PLACEHOLDER",
|
||||
"amount"
|
||||
]
|
||||
"amount",
|
||||
],
|
||||
)
|
||||
|
||||
Withdrawal = generate_labeler_serializer(
|
||||
@@ -724,8 +680,8 @@ Withdrawal = generate_labeler_serializer(
|
||||
"amount",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"withdrawal_fee"
|
||||
]
|
||||
"withdrawal_fee",
|
||||
],
|
||||
)
|
||||
|
||||
DepositAddress = generate_labeler_serializer(
|
||||
@@ -737,20 +693,14 @@ DepositAddress = generate_labeler_serializer(
|
||||
"currency_code",
|
||||
"_PLACEHOLDER",
|
||||
"address",
|
||||
"pool_address"
|
||||
]
|
||||
"pool_address",
|
||||
],
|
||||
)
|
||||
|
||||
LightningNetworkInvoice = generate_labeler_serializer(
|
||||
name="LightningNetworkInvoice",
|
||||
klass=dataclasses.LightningNetworkInvoice,
|
||||
labels=[
|
||||
"invoice_hash",
|
||||
"invoice",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"amount"
|
||||
]
|
||||
labels=["invoice_hash", "invoice", "_PLACEHOLDER", "_PLACEHOLDER", "amount"],
|
||||
)
|
||||
|
||||
Movement = generate_labeler_serializer(
|
||||
@@ -778,8 +728,8 @@ Movement = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"transaction_id",
|
||||
"withdraw_transaction_note"
|
||||
]
|
||||
"withdraw_transaction_note",
|
||||
],
|
||||
)
|
||||
|
||||
SymbolMarginInfo = generate_labeler_serializer(
|
||||
@@ -791,22 +741,15 @@ SymbolMarginInfo = generate_labeler_serializer(
|
||||
"tradable_balance",
|
||||
"gross_balance",
|
||||
"buy",
|
||||
"sell"
|
||||
"sell",
|
||||
],
|
||||
|
||||
flat=True
|
||||
flat=True,
|
||||
)
|
||||
|
||||
BaseMarginInfo = generate_labeler_serializer(
|
||||
name="BaseMarginInfo",
|
||||
klass=dataclasses.BaseMarginInfo,
|
||||
labels=[
|
||||
"user_pl",
|
||||
"user_swaps",
|
||||
"margin_balance",
|
||||
"margin_net",
|
||||
"margin_min"
|
||||
]
|
||||
labels=["user_pl", "user_swaps", "margin_balance", "margin_net", "margin_min"],
|
||||
)
|
||||
|
||||
PositionClaim = generate_labeler_serializer(
|
||||
@@ -832,8 +775,8 @@ PositionClaim = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"min_collateral",
|
||||
"meta"
|
||||
]
|
||||
"meta",
|
||||
],
|
||||
)
|
||||
|
||||
PositionIncreaseInfo = generate_labeler_serializer(
|
||||
@@ -857,21 +800,15 @@ PositionIncreaseInfo = generate_labeler_serializer(
|
||||
"funding_value",
|
||||
"funding_required",
|
||||
"funding_value_currency",
|
||||
"funding_required_currency"
|
||||
"funding_required_currency",
|
||||
],
|
||||
|
||||
flat=True
|
||||
flat=True,
|
||||
)
|
||||
|
||||
PositionIncrease = generate_labeler_serializer(
|
||||
name="PositionIncrease",
|
||||
klass=dataclasses.PositionIncrease,
|
||||
labels=[
|
||||
"symbol",
|
||||
"_PLACEHOLDER",
|
||||
"amount",
|
||||
"base_price"
|
||||
]
|
||||
labels=["symbol", "_PLACEHOLDER", "amount", "base_price"],
|
||||
)
|
||||
|
||||
PositionHistory = generate_labeler_serializer(
|
||||
@@ -891,8 +828,8 @@ PositionHistory = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update"
|
||||
]
|
||||
"mts_update",
|
||||
],
|
||||
)
|
||||
|
||||
PositionSnapshot = generate_labeler_serializer(
|
||||
@@ -912,8 +849,8 @@ PositionSnapshot = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update"
|
||||
]
|
||||
"mts_update",
|
||||
],
|
||||
)
|
||||
|
||||
PositionAudit = generate_labeler_serializer(
|
||||
@@ -939,25 +876,20 @@ PositionAudit = generate_labeler_serializer(
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"collateral_min",
|
||||
"meta"
|
||||
]
|
||||
"meta",
|
||||
],
|
||||
)
|
||||
|
||||
DerivativePositionCollateral = generate_labeler_serializer(
|
||||
name="DerivativePositionCollateral",
|
||||
klass=dataclasses.DerivativePositionCollateral,
|
||||
labels=[
|
||||
"status"
|
||||
]
|
||||
labels=["status"],
|
||||
)
|
||||
|
||||
DerivativePositionCollateralLimits = generate_labeler_serializer(
|
||||
name="DerivativePositionCollateralLimits",
|
||||
klass=dataclasses.DerivativePositionCollateralLimits,
|
||||
labels=[
|
||||
"min_collateral",
|
||||
"max_collateral"
|
||||
]
|
||||
labels=["min_collateral", "max_collateral"],
|
||||
)
|
||||
|
||||
#endregion
|
||||
# endregion
|
||||
|
||||
@@ -13,9 +13,10 @@ from bfxapi.websocket.subscriptions import Subscription
|
||||
|
||||
_CHECKSUM_FLAG_VALUE = 131_072
|
||||
|
||||
|
||||
def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]:
|
||||
return { key: value for key, value in message.items() \
|
||||
if not key in keys }
|
||||
return {key: value for key, value in message.items() if not key in keys}
|
||||
|
||||
|
||||
class BfxWebSocketBucket(Connection):
|
||||
__MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
|
||||
@@ -24,28 +25,26 @@ class BfxWebSocketBucket(Connection):
|
||||
super().__init__(host)
|
||||
|
||||
self.__event_emitter = event_emitter
|
||||
self.__pendings: List[Dict[str, Any]] = [ ]
|
||||
self.__subscriptions: Dict[int, Subscription] = { }
|
||||
self.__pendings: List[Dict[str, Any]] = []
|
||||
self.__subscriptions: Dict[int, Subscription] = {}
|
||||
|
||||
self.__condition = asyncio.locks.Condition()
|
||||
|
||||
self.__handler = PublicChannelsHandler( \
|
||||
event_emitter=self.__event_emitter)
|
||||
self.__handler = PublicChannelsHandler(event_emitter=self.__event_emitter)
|
||||
|
||||
@property
|
||||
def count(self) -> int:
|
||||
return len(self.__pendings) + \
|
||||
len(self.__subscriptions)
|
||||
return len(self.__pendings) + len(self.__subscriptions)
|
||||
|
||||
@property
|
||||
def is_full(self) -> bool:
|
||||
return self.count == \
|
||||
BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
|
||||
return self.count == BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
|
||||
|
||||
@property
|
||||
def ids(self) -> List[str]:
|
||||
return [ pending["subId"] for pending in self.__pendings ] + \
|
||||
[ subscription["sub_id"] for subscription in self.__subscriptions.values() ]
|
||||
return [pending["subId"] for pending in self.__pendings] + [
|
||||
subscription["sub_id"] for subscription in self.__subscriptions.values()
|
||||
]
|
||||
|
||||
async def start(self) -> None:
|
||||
async with websockets.client.connect(self._host) as websocket:
|
||||
@@ -64,20 +63,25 @@ class BfxWebSocketBucket(Connection):
|
||||
self.__on_subscribed(message)
|
||||
|
||||
if isinstance(message, list):
|
||||
if (chan_id := cast(int, message[0])) and \
|
||||
(subscription := self.__subscriptions.get(chan_id)) and \
|
||||
(message[1] != Connection._HEARTBEAT):
|
||||
if (
|
||||
(chan_id := cast(int, message[0]))
|
||||
and (subscription := self.__subscriptions.get(chan_id))
|
||||
and (message[1] != Connection._HEARTBEAT)
|
||||
):
|
||||
self.__handler.handle(subscription, message[1:])
|
||||
|
||||
def __on_subscribed(self, message: Dict[str, Any]) -> None:
|
||||
chan_id = cast(int, message["chan_id"])
|
||||
|
||||
subscription = cast(Subscription, _strip(message, \
|
||||
keys=["chan_id", "event", "pair", "currency"]))
|
||||
subscription = cast(
|
||||
Subscription, _strip(message, keys=["chan_id", "event", "pair", "currency"])
|
||||
)
|
||||
|
||||
self.__pendings = [ pending \
|
||||
for pending in self.__pendings \
|
||||
if pending["subId"] != message["sub_id"] ]
|
||||
self.__pendings = [
|
||||
pending
|
||||
for pending in self.__pendings
|
||||
if pending["subId"] != message["sub_id"]
|
||||
]
|
||||
|
||||
self.__subscriptions[chan_id] = subscription
|
||||
|
||||
@@ -85,47 +89,43 @@ class BfxWebSocketBucket(Connection):
|
||||
|
||||
async def __recover_state(self) -> None:
|
||||
for pending in self.__pendings:
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(pending))
|
||||
await self._websocket.send(message=json.dumps(pending))
|
||||
|
||||
for chan_id in list(self.__subscriptions.keys()):
|
||||
subscription = self.__subscriptions.pop(chan_id)
|
||||
|
||||
await self.subscribe(**subscription)
|
||||
|
||||
await self.__set_config([ _CHECKSUM_FLAG_VALUE ])
|
||||
await self.__set_config([_CHECKSUM_FLAG_VALUE])
|
||||
|
||||
async def __set_config(self, flags: List[int]) -> None:
|
||||
await self._websocket.send(json.dumps( \
|
||||
{ "event": "conf", "flags": sum(flags) }))
|
||||
await self._websocket.send(json.dumps({"event": "conf", "flags": sum(flags)}))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def subscribe(self,
|
||||
channel: str,
|
||||
sub_id: Optional[str] = None,
|
||||
**kwargs: Any) -> None:
|
||||
subscription: Dict[str, Any] = \
|
||||
{ **kwargs, "event": "subscribe", "channel": channel }
|
||||
async def subscribe(
|
||||
self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
|
||||
) -> None:
|
||||
subscription: Dict[str, Any] = {
|
||||
**kwargs,
|
||||
"event": "subscribe",
|
||||
"channel": channel,
|
||||
}
|
||||
|
||||
subscription["subId"] = sub_id or str(uuid.uuid4())
|
||||
|
||||
self.__pendings.append(subscription)
|
||||
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(subscription))
|
||||
await self._websocket.send(message=json.dumps(subscription))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def unsubscribe(self, sub_id: str) -> None:
|
||||
for chan_id, subscription in list(self.__subscriptions.items()):
|
||||
if subscription["sub_id"] == sub_id:
|
||||
unsubscription = {
|
||||
"event": "unsubscribe",
|
||||
"chanId": chan_id }
|
||||
unsubscription = {"event": "unsubscribe", "chanId": chan_id}
|
||||
|
||||
del self.__subscriptions[chan_id]
|
||||
|
||||
await self._websocket.send(message = \
|
||||
json.dumps(unsubscription))
|
||||
await self._websocket.send(message=json.dumps(unsubscription))
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def resubscribe(self, sub_id: str) -> None:
|
||||
@@ -148,5 +148,4 @@ class BfxWebSocketBucket(Connection):
|
||||
|
||||
async def wait(self) -> None:
|
||||
async with self.__condition:
|
||||
await self.__condition \
|
||||
.wait_for(lambda: self.open)
|
||||
await self.__condition.wait_for(lambda: self.open)
|
||||
|
||||
@@ -28,14 +28,17 @@ from bfxapi.websocket.exceptions import (
|
||||
from .bfx_websocket_bucket import BfxWebSocketBucket
|
||||
from .bfx_websocket_inputs import BfxWebSocketInputs
|
||||
|
||||
_Credentials = TypedDict("_Credentials", \
|
||||
{ "api_key": str, "api_secret": str, "filters": Optional[List[str]] })
|
||||
_Credentials = TypedDict(
|
||||
"_Credentials", {"api_key": str, "api_secret": str, "filters": Optional[List[str]]}
|
||||
)
|
||||
|
||||
_Reconnection = TypedDict("_Reconnection",
|
||||
{ "attempts": int, "reason": str, "timestamp": datetime })
|
||||
_Reconnection = TypedDict(
|
||||
"_Reconnection", {"attempts": int, "reason": str, "timestamp": datetime}
|
||||
)
|
||||
|
||||
_DEFAULT_LOGGER = Logger("bfxapi.websocket._client", level=0)
|
||||
|
||||
|
||||
class _Delay:
|
||||
__BACKOFF_MIN = 1.92
|
||||
|
||||
@@ -54,59 +57,61 @@ class _Delay:
|
||||
return _backoff_delay
|
||||
|
||||
def peek(self) -> float:
|
||||
return (self.__backoff_delay == _Delay.__BACKOFF_MIN) \
|
||||
and self.__initial_delay or self.__backoff_delay
|
||||
return (
|
||||
(self.__backoff_delay == _Delay.__BACKOFF_MIN)
|
||||
and self.__initial_delay
|
||||
or self.__backoff_delay
|
||||
)
|
||||
|
||||
def reset(self) -> None:
|
||||
self.__backoff_delay = _Delay.__BACKOFF_MIN
|
||||
|
||||
#pylint: disable-next=too-many-instance-attributes
|
||||
|
||||
# pylint: disable-next=too-many-instance-attributes
|
||||
class BfxWebSocketClient(Connection):
|
||||
def __init__(self,
|
||||
host: str,
|
||||
*,
|
||||
credentials: Optional[_Credentials] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
logger: Logger = _DEFAULT_LOGGER) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
*,
|
||||
credentials: Optional[_Credentials] = None,
|
||||
timeout: Optional[int] = 60 * 15,
|
||||
logger: Logger = _DEFAULT_LOGGER,
|
||||
) -> None:
|
||||
super().__init__(host)
|
||||
|
||||
self.__credentials, self.__timeout, self.__logger = \
|
||||
credentials, \
|
||||
timeout, \
|
||||
logger
|
||||
self.__credentials, self.__timeout, self.__logger = credentials, timeout, logger
|
||||
|
||||
self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = { }
|
||||
self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = {}
|
||||
|
||||
self.__reconnection: Optional[_Reconnection] = None
|
||||
|
||||
self.__event_emitter = BfxEventEmitter(loop=None)
|
||||
|
||||
self.__handler = AuthEventsHandler( \
|
||||
event_emitter=self.__event_emitter)
|
||||
self.__handler = AuthEventsHandler(event_emitter=self.__event_emitter)
|
||||
|
||||
self.__inputs = BfxWebSocketInputs( \
|
||||
handle_websocket_input=self.__handle_websocket_input)
|
||||
self.__inputs = BfxWebSocketInputs(
|
||||
handle_websocket_input=self.__handle_websocket_input
|
||||
)
|
||||
|
||||
@self.__event_emitter.listens_to("error")
|
||||
def error(exception: Exception) -> None:
|
||||
header = f"{type(exception).__name__}: {str(exception)}"
|
||||
|
||||
stack_trace = traceback.format_exception( \
|
||||
type(exception), exception, exception.__traceback__)
|
||||
stack_trace = traceback.format_exception(
|
||||
type(exception), exception, exception.__traceback__
|
||||
)
|
||||
|
||||
#pylint: disable-next=logging-not-lazy
|
||||
self.__logger.critical(header + "\n" + \
|
||||
str().join(stack_trace)[:-1])
|
||||
# pylint: disable-next=logging-not-lazy
|
||||
self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1])
|
||||
|
||||
@property
|
||||
def inputs(self) -> BfxWebSocketInputs:
|
||||
return self.__inputs
|
||||
|
||||
def run(self) -> None:
|
||||
return asyncio.get_event_loop() \
|
||||
.run_until_complete(self.start())
|
||||
return asyncio.get_event_loop().run_until_complete(self.start())
|
||||
|
||||
#pylint: disable-next=too-many-branches
|
||||
# pylint: disable-next=too-many-branches
|
||||
async def start(self) -> None:
|
||||
_delay = _Delay(backoff_factor=1.618)
|
||||
|
||||
@@ -119,19 +124,20 @@ class BfxWebSocketClient(Connection):
|
||||
|
||||
while True:
|
||||
if self.__reconnection:
|
||||
_sleep = asyncio.create_task( \
|
||||
asyncio.sleep(int(_delay.next())))
|
||||
_sleep = asyncio.create_task(asyncio.sleep(int(_delay.next())))
|
||||
|
||||
try:
|
||||
await _sleep
|
||||
except asyncio.CancelledError:
|
||||
raise ReconnectionTimeoutError("Connection has been offline for too long " \
|
||||
f"without being able to reconnect (timeout: {self.__timeout}s).") \
|
||||
from None
|
||||
raise ReconnectionTimeoutError(
|
||||
"Connection has been offline for too long "
|
||||
f"without being able to reconnect (timeout: {self.__timeout}s)."
|
||||
) from None
|
||||
|
||||
try:
|
||||
await self.__connect()
|
||||
except (ConnectionClosedError, InvalidStatusCode, gaierror) as error:
|
||||
|
||||
async def _cancel(task: Task) -> None:
|
||||
task.cancel()
|
||||
|
||||
@@ -150,69 +156,87 @@ class BfxWebSocketClient(Connection):
|
||||
|
||||
await _cancel(task)
|
||||
|
||||
if isinstance(error, ConnectionClosedError) and error.code in (1006, 1012):
|
||||
if isinstance(error, ConnectionClosedError) and error.code in (
|
||||
1006,
|
||||
1012,
|
||||
):
|
||||
if error.code == 1006:
|
||||
self.__logger.error("Connection lost: trying to reconnect...")
|
||||
|
||||
if error.code == 1012:
|
||||
self.__logger.warning("WSS server is restarting: all " \
|
||||
"clients need to reconnect (server sent 20051).")
|
||||
self.__logger.warning(
|
||||
"WSS server is restarting: all "
|
||||
"clients need to reconnect (server sent 20051)."
|
||||
)
|
||||
|
||||
if self.__timeout:
|
||||
asyncio.get_event_loop().call_later(
|
||||
self.__timeout, _on_timeout)
|
||||
asyncio.get_event_loop().call_later(self.__timeout, _on_timeout)
|
||||
|
||||
self.__reconnection = \
|
||||
{ "attempts": 1, "reason": error.reason, "timestamp": datetime.now() }
|
||||
self.__reconnection = {
|
||||
"attempts": 1,
|
||||
"reason": error.reason,
|
||||
"timestamp": datetime.now(),
|
||||
}
|
||||
|
||||
self._authentication = False
|
||||
|
||||
_delay.reset()
|
||||
elif ((isinstance(error, InvalidStatusCode) and error.status_code == 408) or \
|
||||
isinstance(error, gaierror)) and self.__reconnection:
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning("Reconnection attempt unsuccessful (no." \
|
||||
f"{self.__reconnection['attempts']}): next attempt in " \
|
||||
f"~{int(_delay.peek())}.0s.")
|
||||
elif (
|
||||
(isinstance(error, InvalidStatusCode) and error.status_code == 408)
|
||||
or isinstance(error, gaierror)
|
||||
) and self.__reconnection:
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning(
|
||||
"Reconnection attempt unsuccessful (no."
|
||||
f"{self.__reconnection['attempts']}): next attempt in "
|
||||
f"~{int(_delay.peek())}.0s."
|
||||
)
|
||||
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.info(f"The client has been offline for " \
|
||||
f"{datetime.now() - self.__reconnection['timestamp']}.")
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.info(
|
||||
f"The client has been offline for "
|
||||
f"{datetime.now() - self.__reconnection['timestamp']}."
|
||||
)
|
||||
|
||||
self.__reconnection["attempts"] += 1
|
||||
else:
|
||||
raise error
|
||||
|
||||
if not self.__reconnection:
|
||||
self.__event_emitter.emit("disconnected",
|
||||
self._websocket.close_code, \
|
||||
self._websocket.close_reason)
|
||||
self.__event_emitter.emit(
|
||||
"disconnected",
|
||||
self._websocket.close_code,
|
||||
self._websocket.close_reason,
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
async def __connect(self) -> None:
|
||||
async with websockets.client.connect(self._host) as websocket:
|
||||
if self.__reconnection:
|
||||
#pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning("Reconnection attempt successful (no." \
|
||||
f"{self.__reconnection['attempts']}): recovering " \
|
||||
"connection state...")
|
||||
# pylint: disable-next=logging-fstring-interpolation
|
||||
self.__logger.warning(
|
||||
"Reconnection attempt successful (no."
|
||||
f"{self.__reconnection['attempts']}): recovering "
|
||||
"connection state..."
|
||||
)
|
||||
|
||||
self.__reconnection = None
|
||||
|
||||
self._websocket = websocket
|
||||
|
||||
for bucket in self.__buckets:
|
||||
self.__buckets[bucket] = \
|
||||
asyncio.create_task(bucket.start())
|
||||
self.__buckets[bucket] = asyncio.create_task(bucket.start())
|
||||
|
||||
if len(self.__buckets) == 0 or \
|
||||
(await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])):
|
||||
if len(self.__buckets) == 0 or (
|
||||
await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])
|
||||
):
|
||||
self.__event_emitter.emit("open")
|
||||
|
||||
if self.__credentials:
|
||||
authentication = Connection. \
|
||||
_get_authentication_message(**self.__credentials)
|
||||
authentication = Connection._get_authentication_message(
|
||||
**self.__credentials
|
||||
)
|
||||
|
||||
await self._websocket.send(authentication)
|
||||
|
||||
@@ -222,61 +246,64 @@ class BfxWebSocketClient(Connection):
|
||||
if isinstance(message, dict):
|
||||
if message["event"] == "info" and "version" in message:
|
||||
if message["version"] != 2:
|
||||
raise VersionMismatchError("Mismatch between the client and the server version: " + \
|
||||
"please update bitfinex-api-py to the latest version to resolve this error " + \
|
||||
f"(client version: 2, server version: {message['version']}).")
|
||||
raise VersionMismatchError(
|
||||
"Mismatch between the client and the server version: "
|
||||
+ "please update bitfinex-api-py to the latest version to resolve this error "
|
||||
+ f"(client version: 2, server version: {message['version']})."
|
||||
)
|
||||
elif message["event"] == "info" and message["code"] == 20051:
|
||||
rcvd = websockets.frames.Close( \
|
||||
1012, "Stop/Restart WebSocket Server (please reconnect).")
|
||||
rcvd = websockets.frames.Close(
|
||||
1012, "Stop/Restart WebSocket Server (please reconnect)."
|
||||
)
|
||||
|
||||
raise ConnectionClosedError(rcvd=rcvd, sent=None)
|
||||
elif message["event"] == "auth":
|
||||
if message["status"] != "OK":
|
||||
raise InvalidCredentialError("Can't authenticate " + \
|
||||
"with given API-KEY and API-SECRET.")
|
||||
raise InvalidCredentialError(
|
||||
"Can't authenticate "
|
||||
+ "with given API-KEY and API-SECRET."
|
||||
)
|
||||
|
||||
self.__event_emitter.emit("authenticated", message)
|
||||
|
||||
self._authentication = True
|
||||
|
||||
if isinstance(message, list) and \
|
||||
message[0] == 0 and message[1] != Connection._HEARTBEAT:
|
||||
if (
|
||||
isinstance(message, list)
|
||||
and message[0] == 0
|
||||
and message[1] != Connection._HEARTBEAT
|
||||
):
|
||||
self.__handler.handle(message[1], message[2])
|
||||
|
||||
async def __new_bucket(self) -> BfxWebSocketBucket:
|
||||
bucket = BfxWebSocketBucket( \
|
||||
self._host, self.__event_emitter)
|
||||
bucket = BfxWebSocketBucket(self._host, self.__event_emitter)
|
||||
|
||||
self.__buckets[bucket] = asyncio \
|
||||
.create_task(bucket.start())
|
||||
self.__buckets[bucket] = asyncio.create_task(bucket.start())
|
||||
|
||||
await bucket.wait()
|
||||
|
||||
return bucket
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def subscribe(self,
|
||||
channel: str,
|
||||
sub_id: Optional[str] = None,
|
||||
**kwargs: Any) -> None:
|
||||
async def subscribe(
|
||||
self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
|
||||
) -> None:
|
||||
if not channel in ["ticker", "trades", "book", "candles", "status"]:
|
||||
raise UnknownChannelError("Available channels are: " + \
|
||||
"ticker, trades, book, candles and status.")
|
||||
raise UnknownChannelError(
|
||||
"Available channels are: " + "ticker, trades, book, candles and status."
|
||||
)
|
||||
|
||||
for bucket in self.__buckets:
|
||||
if sub_id in bucket.ids:
|
||||
raise SubIdError("sub_id must be " + \
|
||||
"unique for all subscriptions.")
|
||||
raise SubIdError("sub_id must be " + "unique for all subscriptions.")
|
||||
|
||||
for bucket in self.__buckets:
|
||||
if not bucket.is_full:
|
||||
return await bucket.subscribe( \
|
||||
channel, sub_id, **kwargs)
|
||||
return await bucket.subscribe(channel, sub_id, **kwargs)
|
||||
|
||||
bucket = await self.__new_bucket()
|
||||
|
||||
return await bucket.subscribe( \
|
||||
channel, sub_id, **kwargs)
|
||||
return await bucket.subscribe(channel, sub_id, **kwargs)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def unsubscribe(self, sub_id: str) -> None:
|
||||
@@ -286,13 +313,13 @@ class BfxWebSocketClient(Connection):
|
||||
if bucket.count == 1:
|
||||
del self.__buckets[bucket]
|
||||
|
||||
return await bucket.close( \
|
||||
code=1001, reason="Going Away")
|
||||
return await bucket.close(code=1001, reason="Going Away")
|
||||
|
||||
return await bucket.unsubscribe(sub_id)
|
||||
|
||||
raise UnknownSubscriptionError("Unable to find " + \
|
||||
f"a subscription with sub_id <{sub_id}>.")
|
||||
raise UnknownSubscriptionError(
|
||||
"Unable to find " + f"a subscription with sub_id <{sub_id}>."
|
||||
)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def resubscribe(self, sub_id: str) -> None:
|
||||
@@ -300,8 +327,9 @@ class BfxWebSocketClient(Connection):
|
||||
if bucket.has(sub_id):
|
||||
return await bucket.resubscribe(sub_id)
|
||||
|
||||
raise UnknownSubscriptionError("Unable to find " + \
|
||||
f"a subscription with sub_id <{sub_id}>.")
|
||||
raise UnknownSubscriptionError(
|
||||
"Unable to find " + f"a subscription with sub_id <{sub_id}>."
|
||||
)
|
||||
|
||||
@Connection._require_websocket_connection
|
||||
async def close(self, code: int = 1000, reason: str = str()) -> None:
|
||||
@@ -309,22 +337,21 @@ class BfxWebSocketClient(Connection):
|
||||
await bucket.close(code=code, reason=reason)
|
||||
|
||||
if self._websocket.open:
|
||||
await self._websocket.close( \
|
||||
code=code, reason=reason)
|
||||
await self._websocket.close(code=code, reason=reason)
|
||||
|
||||
@Connection._require_websocket_authentication
|
||||
async def notify(self,
|
||||
info: Any,
|
||||
message_id: Optional[int] = None,
|
||||
**kwargs: Any) -> None:
|
||||
async def notify(
|
||||
self, info: Any, message_id: Optional[int] = None, **kwargs: Any
|
||||
) -> None:
|
||||
await self._websocket.send(
|
||||
json.dumps([ 0, "n", message_id,
|
||||
{ "type": "ucm-test", "info": info, **kwargs } ]))
|
||||
json.dumps(
|
||||
[0, "n", message_id, {"type": "ucm-test", "info": info, **kwargs}]
|
||||
)
|
||||
)
|
||||
|
||||
@Connection._require_websocket_authentication
|
||||
async def __handle_websocket_input(self, event: str, data: Any) -> None:
|
||||
await self._websocket.send(json.dumps( \
|
||||
[ 0, event, None, data], cls=JSONEncoder))
|
||||
await self._websocket.send(json.dumps([0, event, None, data], cls=JSONEncoder))
|
||||
|
||||
def on(self, event, callback = None):
|
||||
def on(self, event, callback=None):
|
||||
return self.__event_emitter.on(event, callback)
|
||||
|
||||
@@ -3,89 +3,127 @@ from typing import Any, Awaitable, Callable, List, Optional, Tuple, Union
|
||||
|
||||
_Handler = Callable[[str, Any], Awaitable[None]]
|
||||
|
||||
|
||||
class BfxWebSocketInputs:
|
||||
def __init__(self, handle_websocket_input: _Handler) -> None:
|
||||
self.__handle_websocket_input = handle_websocket_input
|
||||
|
||||
async def submit_order(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("on", {
|
||||
"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
|
||||
})
|
||||
async def submit_order(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
price: Union[str, float, Decimal],
|
||||
*,
|
||||
lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_oco_stop: Optional[Union[str, float, Decimal]] = None,
|
||||
gid: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"on",
|
||||
{
|
||||
"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,
|
||||
},
|
||||
)
|
||||
|
||||
async def update_order(self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("ou", {
|
||||
"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
|
||||
})
|
||||
async def update_order(
|
||||
self,
|
||||
id: int,
|
||||
*,
|
||||
amount: Optional[Union[str, float, Decimal]] = None,
|
||||
price: Optional[Union[str, float, Decimal]] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
gid: Optional[int] = None,
|
||||
flags: Optional[int] = None,
|
||||
lev: Optional[int] = None,
|
||||
delta: Optional[Union[str, float, Decimal]] = None,
|
||||
price_aux_limit: Optional[Union[str, float, Decimal]] = None,
|
||||
price_trailing: Optional[Union[str, float, Decimal]] = None,
|
||||
tif: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"ou",
|
||||
{
|
||||
"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,
|
||||
},
|
||||
)
|
||||
|
||||
async def cancel_order(self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None) -> None:
|
||||
await self.__handle_websocket_input("oc", {
|
||||
"id": id, "cid": cid, "cid_date": cid_date
|
||||
})
|
||||
async def cancel_order(
|
||||
self,
|
||||
*,
|
||||
id: Optional[int] = None,
|
||||
cid: Optional[int] = None,
|
||||
cid_date: Optional[str] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"oc", {"id": id, "cid": cid, "cid_date": cid_date}
|
||||
)
|
||||
|
||||
async def cancel_order_multi(self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None) -> None:
|
||||
await self.__handle_websocket_input("oc_multi", {
|
||||
"id": id, "cid": cid, "gid": gid,
|
||||
"all": all
|
||||
})
|
||||
async def cancel_order_multi(
|
||||
self,
|
||||
*,
|
||||
id: Optional[List[int]] = None,
|
||||
cid: Optional[List[Tuple[int, str]]] = None,
|
||||
gid: Optional[List[int]] = None,
|
||||
all: Optional[bool] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"oc_multi", {"id": id, "cid": cid, "gid": gid, "all": all}
|
||||
)
|
||||
|
||||
#pylint: disable-next=too-many-arguments
|
||||
async def submit_funding_offer(self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None) -> None:
|
||||
await self.__handle_websocket_input("fon", {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period, "flags": flags
|
||||
})
|
||||
# pylint: disable-next=too-many-arguments
|
||||
async def submit_funding_offer(
|
||||
self,
|
||||
type: str,
|
||||
symbol: str,
|
||||
amount: Union[str, float, Decimal],
|
||||
rate: Union[str, float, Decimal],
|
||||
period: int,
|
||||
*,
|
||||
flags: Optional[int] = None,
|
||||
) -> None:
|
||||
await self.__handle_websocket_input(
|
||||
"fon",
|
||||
{
|
||||
"type": type,
|
||||
"symbol": symbol,
|
||||
"amount": amount,
|
||||
"rate": rate,
|
||||
"period": period,
|
||||
"flags": flags,
|
||||
},
|
||||
)
|
||||
|
||||
async def cancel_funding_offer(self, id: int) -> None:
|
||||
await self.__handle_websocket_input("foc", { "id": id })
|
||||
await self.__handle_websocket_input("foc", {"id": id})
|
||||
|
||||
async def calc(self, *args: str) -> None:
|
||||
await self.__handle_websocket_input("calc",
|
||||
list(map(lambda arg: [arg], args)))
|
||||
await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
|
||||
|
||||
@@ -18,6 +18,7 @@ _R = TypeVar("_R")
|
||||
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
|
||||
class Connection(ABC):
|
||||
_HEARTBEAT = "hb"
|
||||
|
||||
@@ -30,8 +31,7 @@ class Connection(ABC):
|
||||
|
||||
@property
|
||||
def open(self) -> bool:
|
||||
return self.__protocol is not None and \
|
||||
self.__protocol.open
|
||||
return self.__protocol is not None and self.__protocol.open
|
||||
|
||||
@property
|
||||
def authentication(self) -> bool:
|
||||
@@ -46,12 +46,11 @@ class Connection(ABC):
|
||||
self.__protocol = protocol
|
||||
|
||||
@abstractmethod
|
||||
async def start(self) -> None:
|
||||
...
|
||||
async def start(self) -> None: ...
|
||||
|
||||
@staticmethod
|
||||
def _require_websocket_connection(
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]]
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]],
|
||||
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
|
||||
@wraps(function)
|
||||
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
|
||||
@@ -64,13 +63,15 @@ class Connection(ABC):
|
||||
|
||||
@staticmethod
|
||||
def _require_websocket_authentication(
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]]
|
||||
function: Callable[Concatenate[_S, _P], Awaitable[_R]],
|
||||
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
|
||||
@wraps(function)
|
||||
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
|
||||
if not self.authentication:
|
||||
raise ActionRequiresAuthentication("To perform this action you need to " \
|
||||
"authenticate using your API_KEY and API_SECRET.")
|
||||
raise ActionRequiresAuthentication(
|
||||
"To perform this action you need to "
|
||||
"authenticate using your API_KEY and API_SECRET."
|
||||
)
|
||||
|
||||
internal = Connection._require_websocket_connection(function)
|
||||
|
||||
@@ -80,12 +81,13 @@ class Connection(ABC):
|
||||
|
||||
@staticmethod
|
||||
def _get_authentication_message(
|
||||
api_key: str,
|
||||
api_secret: str,
|
||||
filters: Optional[List[str]] = None
|
||||
api_key: str, api_secret: str, filters: Optional[List[str]] = None
|
||||
) -> str:
|
||||
message: Dict[str, Any] = \
|
||||
{ "event": "auth", "filter": filters, "apiKey": api_key }
|
||||
message: Dict[str, Any] = {
|
||||
"event": "auth",
|
||||
"filter": filters,
|
||||
"apiKey": api_key,
|
||||
}
|
||||
|
||||
message["authNonce"] = round(datetime.now().timestamp() * 1_000_000)
|
||||
|
||||
@@ -94,7 +96,7 @@ class Connection(ABC):
|
||||
auth_sig = hmac.new(
|
||||
key=api_secret.encode("utf8"),
|
||||
msg=message["authPayload"].encode("utf8"),
|
||||
digestmod=hashlib.sha384
|
||||
digestmod=hashlib.sha384,
|
||||
)
|
||||
|
||||
message["authSig"] = auth_sig.hexdigest()
|
||||
|
||||
@@ -9,57 +9,86 @@ from bfxapi.websocket.exceptions import UnknownEventError
|
||||
_Handler = TypeVar("_Handler", bound=Callable[..., None])
|
||||
|
||||
_ONCE_PER_CONNECTION = [
|
||||
"open", "authenticated", "order_snapshot",
|
||||
"position_snapshot", "funding_offer_snapshot", "funding_credit_snapshot",
|
||||
"funding_loan_snapshot", "wallet_snapshot"
|
||||
"open",
|
||||
"authenticated",
|
||||
"order_snapshot",
|
||||
"position_snapshot",
|
||||
"funding_offer_snapshot",
|
||||
"funding_credit_snapshot",
|
||||
"funding_loan_snapshot",
|
||||
"wallet_snapshot",
|
||||
]
|
||||
|
||||
_ONCE_PER_SUBSCRIPTION = [
|
||||
"subscribed", "t_trades_snapshot", "f_trades_snapshot",
|
||||
"t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot",
|
||||
"f_raw_book_snapshot", "candles_snapshot"
|
||||
"subscribed",
|
||||
"t_trades_snapshot",
|
||||
"f_trades_snapshot",
|
||||
"t_book_snapshot",
|
||||
"f_book_snapshot",
|
||||
"t_raw_book_snapshot",
|
||||
"f_raw_book_snapshot",
|
||||
"candles_snapshot",
|
||||
]
|
||||
|
||||
_COMMON = [
|
||||
"disconnected", "t_ticker_update", "f_ticker_update",
|
||||
"t_trade_execution", "t_trade_execution_update", "f_trade_execution",
|
||||
"f_trade_execution_update", "t_book_update", "f_book_update",
|
||||
"t_raw_book_update", "f_raw_book_update", "candles_update",
|
||||
"derivatives_status_update", "liquidation_feed_update", "checksum",
|
||||
"order_new", "order_update", "order_cancel",
|
||||
"position_new", "position_update", "position_close",
|
||||
"funding_offer_new", "funding_offer_update", "funding_offer_cancel",
|
||||
"funding_credit_new", "funding_credit_update", "funding_credit_close",
|
||||
"funding_loan_new", "funding_loan_update", "funding_loan_close",
|
||||
"trade_execution", "trade_execution_update", "wallet_update",
|
||||
"notification", "on-req-notification", "ou-req-notification",
|
||||
"oc-req-notification", "fon-req-notification", "foc-req-notification"
|
||||
"disconnected",
|
||||
"t_ticker_update",
|
||||
"f_ticker_update",
|
||||
"t_trade_execution",
|
||||
"t_trade_execution_update",
|
||||
"f_trade_execution",
|
||||
"f_trade_execution_update",
|
||||
"t_book_update",
|
||||
"f_book_update",
|
||||
"t_raw_book_update",
|
||||
"f_raw_book_update",
|
||||
"candles_update",
|
||||
"derivatives_status_update",
|
||||
"liquidation_feed_update",
|
||||
"checksum",
|
||||
"order_new",
|
||||
"order_update",
|
||||
"order_cancel",
|
||||
"position_new",
|
||||
"position_update",
|
||||
"position_close",
|
||||
"funding_offer_new",
|
||||
"funding_offer_update",
|
||||
"funding_offer_cancel",
|
||||
"funding_credit_new",
|
||||
"funding_credit_update",
|
||||
"funding_credit_close",
|
||||
"funding_loan_new",
|
||||
"funding_loan_update",
|
||||
"funding_loan_close",
|
||||
"trade_execution",
|
||||
"trade_execution_update",
|
||||
"wallet_update",
|
||||
"notification",
|
||||
"on-req-notification",
|
||||
"ou-req-notification",
|
||||
"oc-req-notification",
|
||||
"fon-req-notification",
|
||||
"foc-req-notification",
|
||||
]
|
||||
|
||||
|
||||
class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
_EVENTS = _ONCE_PER_CONNECTION + \
|
||||
_ONCE_PER_SUBSCRIPTION + \
|
||||
_COMMON
|
||||
_EVENTS = _ONCE_PER_CONNECTION + _ONCE_PER_SUBSCRIPTION + _COMMON
|
||||
|
||||
def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None:
|
||||
super().__init__(loop)
|
||||
|
||||
self._connection: List[str] = [ ]
|
||||
self._connection: List[str] = []
|
||||
|
||||
self._subscriptions: Dict[str, List[str]] = \
|
||||
defaultdict(lambda: [ ])
|
||||
self._subscriptions: Dict[str, List[str]] = defaultdict(lambda: [])
|
||||
|
||||
def emit(
|
||||
self,
|
||||
event: str,
|
||||
*args: Any,
|
||||
**kwargs: Any
|
||||
) -> bool:
|
||||
def emit(self, event: str, *args: Any, **kwargs: Any) -> bool:
|
||||
if event in _ONCE_PER_CONNECTION:
|
||||
if event in self._connection:
|
||||
return self._has_listeners(event)
|
||||
|
||||
self._connection += [ event ]
|
||||
self._connection += [event]
|
||||
|
||||
if event in _ONCE_PER_SUBSCRIPTION:
|
||||
sub_id = args[0]["sub_id"]
|
||||
@@ -67,7 +96,7 @@ class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
if event in self._subscriptions[sub_id]:
|
||||
return self._has_listeners(event)
|
||||
|
||||
self._subscriptions[sub_id] += [ event ]
|
||||
self._subscriptions[sub_id] += [event]
|
||||
|
||||
return super().emit(event, *args, **kwargs)
|
||||
|
||||
@@ -75,8 +104,10 @@ class BfxEventEmitter(AsyncIOEventEmitter):
|
||||
self, event: str, f: Optional[_Handler] = None
|
||||
) -> Union[_Handler, Callable[[_Handler], _Handler]]:
|
||||
if event not in BfxEventEmitter._EVENTS:
|
||||
raise UnknownEventError(f"Can't register to unknown event: <{event}> " + \
|
||||
"(to get a full list of available events see https://docs.bitfinex.com/).")
|
||||
raise UnknownEventError(
|
||||
f"Can't register to unknown event: <{event}> "
|
||||
+ "(to get a full list of available events see https://docs.bitfinex.com/)."
|
||||
)
|
||||
|
||||
return super().on(event, f)
|
||||
|
||||
|
||||
@@ -9,14 +9,30 @@ from bfxapi.types.serializers import _Notification
|
||||
|
||||
class AuthEventsHandler:
|
||||
__ABBREVIATIONS = {
|
||||
"os": "order_snapshot", "on": "order_new", "ou": "order_update",
|
||||
"oc": "order_cancel", "ps": "position_snapshot", "pn": "position_new",
|
||||
"pu": "position_update", "pc": "position_close", "te": "trade_execution",
|
||||
"tu": "trade_execution_update", "fos": "funding_offer_snapshot", "fon": "funding_offer_new",
|
||||
"fou": "funding_offer_update", "foc": "funding_offer_cancel", "fcs": "funding_credit_snapshot",
|
||||
"fcn": "funding_credit_new", "fcu": "funding_credit_update", "fcc": "funding_credit_close",
|
||||
"fls": "funding_loan_snapshot", "fln": "funding_loan_new", "flu": "funding_loan_update",
|
||||
"flc": "funding_loan_close", "ws": "wallet_snapshot", "wu": "wallet_update"
|
||||
"os": "order_snapshot",
|
||||
"on": "order_new",
|
||||
"ou": "order_update",
|
||||
"oc": "order_cancel",
|
||||
"ps": "position_snapshot",
|
||||
"pn": "position_new",
|
||||
"pu": "position_update",
|
||||
"pc": "position_close",
|
||||
"te": "trade_execution",
|
||||
"tu": "trade_execution_update",
|
||||
"fos": "funding_offer_snapshot",
|
||||
"fon": "funding_offer_new",
|
||||
"fou": "funding_offer_update",
|
||||
"foc": "funding_offer_cancel",
|
||||
"fcs": "funding_credit_snapshot",
|
||||
"fcn": "funding_credit_new",
|
||||
"fcu": "funding_credit_update",
|
||||
"fcc": "funding_credit_close",
|
||||
"fls": "funding_loan_snapshot",
|
||||
"fln": "funding_loan_new",
|
||||
"flu": "funding_loan_update",
|
||||
"flc": "funding_loan_close",
|
||||
"ws": "wallet_snapshot",
|
||||
"wu": "wallet_update",
|
||||
}
|
||||
|
||||
__SERIALIZERS: Dict[Tuple[str, ...], serializers._Serializer] = {
|
||||
@@ -26,7 +42,7 @@ class AuthEventsHandler:
|
||||
("fos", "fon", "fou", "foc"): serializers.FundingOffer,
|
||||
("fcs", "fcn", "fcu", "fcc"): serializers.FundingCredit,
|
||||
("fls", "fln", "flu", "flc"): serializers.FundingLoan,
|
||||
("ws", "wu"): serializers.Wallet
|
||||
("ws", "wu"): serializers.Wallet,
|
||||
}
|
||||
|
||||
def __init__(self, event_emitter: EventEmitter) -> None:
|
||||
@@ -41,7 +57,7 @@ class AuthEventsHandler:
|
||||
event = AuthEventsHandler.__ABBREVIATIONS[abbrevation]
|
||||
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream):
|
||||
data = [ serializer.parse(*sub_stream) for sub_stream in stream ]
|
||||
data = [serializer.parse(*sub_stream) for sub_stream in stream]
|
||||
else:
|
||||
data = serializer.parse(*stream)
|
||||
|
||||
@@ -53,11 +69,13 @@ class AuthEventsHandler:
|
||||
serializer: _Notification = _Notification[None](serializer=None)
|
||||
|
||||
if stream[1] in ("on-req", "ou-req", "oc-req"):
|
||||
event, serializer = f"{stream[1]}-notification", \
|
||||
_Notification[Order](serializer=serializers.Order)
|
||||
event, serializer = f"{stream[1]}-notification", _Notification[Order](
|
||||
serializer=serializers.Order
|
||||
)
|
||||
|
||||
if stream[1] in ("fon-req", "foc-req"):
|
||||
event, serializer = f"{stream[1]}-notification", \
|
||||
_Notification[FundingOffer](serializer=serializers.FundingOffer)
|
||||
event, serializer = f"{stream[1]}-notification", _Notification[
|
||||
FundingOffer
|
||||
](serializer=serializers.FundingOffer)
|
||||
|
||||
self.__event_emitter.emit(event, serializer.parse(*stream))
|
||||
|
||||
@@ -14,6 +14,7 @@ from bfxapi.websocket.subscriptions import (
|
||||
|
||||
_CHECKSUM = "cs"
|
||||
|
||||
|
||||
class PublicChannelsHandler:
|
||||
def __init__(self, event_emitter: EventEmitter) -> None:
|
||||
self.__event_emitter = event_emitter
|
||||
@@ -38,101 +39,167 @@ class PublicChannelsHandler:
|
||||
elif subscription["channel"] == "status":
|
||||
self.__status_channel_handler(cast(Status, subscription), stream)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit("t_ticker_update", subscription, \
|
||||
serializers.TradingPairTicker.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_ticker_update",
|
||||
subscription,
|
||||
serializers.TradingPairTicker.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit("f_ticker_update", subscription, \
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_ticker_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __trades_channel_handler(self, subscription: Trades, stream: List[Any]):
|
||||
if (event := stream[0]) and event in [ "te", "tu", "fte", "ftu" ]:
|
||||
events = { "te": "t_trade_execution", "tu": "t_trade_execution_update", \
|
||||
"fte": "f_trade_execution", "ftu": "f_trade_execution_update" }
|
||||
if (event := stream[0]) and event in ["te", "tu", "fte", "ftu"]:
|
||||
events = {
|
||||
"te": "t_trade_execution",
|
||||
"tu": "t_trade_execution_update",
|
||||
"fte": "f_trade_execution",
|
||||
"ftu": "f_trade_execution_update",
|
||||
}
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit(events[event], subscription, \
|
||||
serializers.TradingPairTrade.parse(*stream[1]))
|
||||
return self.__event_emitter.emit(
|
||||
events[event],
|
||||
subscription,
|
||||
serializers.TradingPairTrade.parse(*stream[1]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit(events[event], subscription, \
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1]))
|
||||
return self.__event_emitter.emit(
|
||||
events[event],
|
||||
subscription,
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.__event_emitter.emit("t_trades_snapshot", subscription, \
|
||||
[ serializers.TradingPairTrade.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_trades_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairTrade.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.__event_emitter.emit("f_trades_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyTrade.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_trades_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyTrade.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __book_channel_handler(self, subscription: Book, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("t_book_snapshot", subscription, \
|
||||
[ serializers.TradingPairBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("t_book_update", subscription, \
|
||||
serializers.TradingPairBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_book_update",
|
||||
subscription,
|
||||
serializers.TradingPairBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("f_book_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("f_book_update", subscription, \
|
||||
serializers.FundingCurrencyBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_book_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("t_raw_book_snapshot", subscription, \
|
||||
[ serializers.TradingPairRawBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"t_raw_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.TradingPairRawBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("t_raw_book_update", subscription, \
|
||||
serializers.TradingPairRawBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"t_raw_book_update",
|
||||
subscription,
|
||||
serializers.TradingPairRawBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("f_raw_book_snapshot", subscription, \
|
||||
[ serializers.FundingCurrencyRawBook.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"f_raw_book_snapshot",
|
||||
subscription,
|
||||
[
|
||||
serializers.FundingCurrencyRawBook.parse(*sub_stream)
|
||||
for sub_stream in stream[0]
|
||||
],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("f_raw_book_update", subscription, \
|
||||
serializers.FundingCurrencyRawBook.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"f_raw_book_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyRawBook.parse(*stream[0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __candles_channel_handler(self, subscription: Candles, stream: List[Any]):
|
||||
if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
|
||||
return self.__event_emitter.emit("candles_snapshot", subscription, \
|
||||
[ serializers.Candle.parse(*sub_stream) \
|
||||
for sub_stream in stream[0] ])
|
||||
return self.__event_emitter.emit(
|
||||
"candles_snapshot",
|
||||
subscription,
|
||||
[serializers.Candle.parse(*sub_stream) for sub_stream in stream[0]],
|
||||
)
|
||||
|
||||
return self.__event_emitter.emit("candles_update", subscription, \
|
||||
serializers.Candle.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"candles_update", subscription, serializers.Candle.parse(*stream[0])
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __status_channel_handler(self, subscription: Status, stream: List[Any]):
|
||||
if subscription["key"].startswith("deriv:"):
|
||||
return self.__event_emitter.emit("derivatives_status_update", subscription, \
|
||||
serializers.DerivativesStatus.parse(*stream[0]))
|
||||
return self.__event_emitter.emit(
|
||||
"derivatives_status_update",
|
||||
subscription,
|
||||
serializers.DerivativesStatus.parse(*stream[0]),
|
||||
)
|
||||
|
||||
if subscription["key"].startswith("liq:"):
|
||||
return self.__event_emitter.emit("liquidation_feed_update", subscription, \
|
||||
serializers.Liquidation.parse(*stream[0][0]))
|
||||
return self.__event_emitter.emit(
|
||||
"liquidation_feed_update",
|
||||
subscription,
|
||||
serializers.Liquidation.parse(*stream[0][0]),
|
||||
)
|
||||
|
||||
#pylint: disable-next=inconsistent-return-statements
|
||||
# pylint: disable-next=inconsistent-return-statements
|
||||
def __checksum_handler(self, subscription: Book, value: int):
|
||||
return self.__event_emitter.emit( \
|
||||
"checksum", subscription, value & 0xFFFFFFFF)
|
||||
return self.__event_emitter.emit("checksum", subscription, value & 0xFFFFFFFF)
|
||||
|
||||
@@ -4,23 +4,30 @@ from bfxapi.exceptions import BfxBaseException
|
||||
class ConnectionNotOpen(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class ActionRequiresAuthentication(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class ReconnectionTimeoutError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class VersionMismatchError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class SubIdError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownChannelError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownEventError(BfxBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSubscriptionError(BfxBaseException):
|
||||
pass
|
||||
|
||||
@@ -4,16 +4,19 @@ Subscription = Union["Ticker", "Trades", "Book", "Candles", "Status"]
|
||||
|
||||
Channel = Literal["ticker", "trades", "book", "candles", "status"]
|
||||
|
||||
|
||||
class Ticker(TypedDict):
|
||||
channel: Literal["ticker"]
|
||||
sub_id: str
|
||||
symbol: str
|
||||
|
||||
|
||||
class Trades(TypedDict):
|
||||
channel: Literal["trades"]
|
||||
sub_id: str
|
||||
symbol: str
|
||||
|
||||
|
||||
class Book(TypedDict):
|
||||
channel: Literal["book"]
|
||||
sub_id: str
|
||||
@@ -22,11 +25,13 @@ class Book(TypedDict):
|
||||
freq: Literal["F0", "F1"]
|
||||
len: Literal["1", "25", "100", "250"]
|
||||
|
||||
|
||||
class Candles(TypedDict):
|
||||
channel: Literal["candles"]
|
||||
sub_id: str
|
||||
key: str
|
||||
|
||||
|
||||
class Status(TypedDict):
|
||||
channel: Literal["status"]
|
||||
sub_id: str
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
from bfxapi import Client
|
||||
from bfxapi.types import Notification, PositionClaim
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
# Claims all active positions
|
||||
for position in bfx.rest.auth.get_positions():
|
||||
|
||||
@@ -13,10 +13,7 @@ from bfxapi.types import (
|
||||
Withdrawal,
|
||||
)
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
# Gets all user's available wallets
|
||||
wallets: List[Wallet] = bfx.rest.auth.get_wallets()
|
||||
|
||||
@@ -8,10 +8,7 @@ from bfxapi.types import (
|
||||
DerivativePositionCollateralLimits,
|
||||
)
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
submit_order_notification = bfx.rest.auth.submit_order(
|
||||
type="LIMIT", symbol="tBTCF0:USTF0", amount="0.015", price="16700", lev=10
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
from bfxapi import Client
|
||||
from bfxapi.types import FundingOffer, Notification
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
# Submit a new funding offer
|
||||
notification: Notification[FundingOffer] = bfx.rest.auth.submit_funding_offer(
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
from bfxapi import Client
|
||||
from bfxapi.types import Notification, Order
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
# Submit a new order
|
||||
submit_order_notification: Notification[Order] = bfx.rest.auth.submit_order(
|
||||
|
||||
@@ -6,10 +6,7 @@ from typing import List
|
||||
from bfxapi import Client
|
||||
from bfxapi.types import FundingLoan, Notification
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
loans: List[FundingLoan] = bfx.rest.auth.get_funding_loans(symbol="fUSD")
|
||||
|
||||
|
||||
@@ -4,10 +4,7 @@ import os
|
||||
|
||||
from bfxapi import Client
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
if not bfx.rest.merchant.set_merchant_settings("bfx_pay_recommend_store", 1):
|
||||
print("Cannot set <bfx_pay_recommend_store> to <1>.")
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
from bfxapi import Client
|
||||
from bfxapi.types import InvoiceSubmission
|
||||
|
||||
bfx = Client(
|
||||
api_key=os.getenv("BFX_API_KEY"),
|
||||
api_secret=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
|
||||
|
||||
customer_info = {
|
||||
"nationality": "DE",
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
||||
[tool.black]
|
||||
target-version = ["py38", "py39", "py310", "py311"]
|
||||
|
||||
preview = true
|
||||
Reference in New Issue
Block a user