Apply black to all python files (bfxapi/**/*.py).

This commit is contained in:
Davide Casale
2024-02-26 19:43:14 +01:00
parent 2b7dfc5b8a
commit 38dbff1141
33 changed files with 1843 additions and 1335 deletions

View File

@@ -14,29 +14,35 @@ WSS_HOST = "wss://api.bitfinex.com/ws/2"
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2" PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2" PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
class Client: class Client:
def __init__( def __init__(
self, self,
api_key: Optional[str] = None, api_key: Optional[str] = None,
api_secret: Optional[str] = None, api_secret: Optional[str] = None,
*, *,
rest_host: str = REST_HOST, rest_host: str = REST_HOST,
wss_host: str = WSS_HOST, wss_host: str = WSS_HOST,
filters: Optional[List[str]] = None, filters: Optional[List[str]] = None,
timeout: Optional[int] = 60 * 15, timeout: Optional[int] = 60 * 15,
log_filename: Optional[str] = None log_filename: Optional[str] = None,
) -> None: ) -> None:
credentials: Optional["_Credentials"] = None credentials: Optional["_Credentials"] = None
if api_key and api_secret: if api_key and api_secret:
credentials = \ credentials = {
{ "api_key": api_key, "api_secret": api_secret, "filters": filters } "api_key": api_key,
"api_secret": api_secret,
"filters": filters,
}
elif api_key: elif api_key:
raise IncompleteCredentialError( \ raise IncompleteCredentialError(
"You must provide both an API-KEY and an API-SECRET (missing API-KEY).") "You must provide both an API-KEY and an API-SECRET (missing API-KEY)."
)
elif api_secret: elif api_secret:
raise IncompleteCredentialError( \ raise IncompleteCredentialError(
"You must provide both an API-KEY and an API-SECRET (missing API-SECRET).") "You must provide both an API-KEY and an API-SECRET (missing API-SECRET)."
)
self.rest = BfxRestInterface(rest_host, api_key, api_secret) self.rest = BfxRestInterface(rest_host, api_key, api_secret)
@@ -45,5 +51,6 @@ class Client:
if log_filename: if log_filename:
logger.register(filename=log_filename) logger.register(filename=log_filename)
self.wss = BfxWebSocketClient(wss_host, \ self.wss = BfxWebSocketClient(
credentials=credentials, timeout=timeout, logger=logger) wss_host, credentials=credentials, timeout=timeout, logger=logger
)

View File

@@ -6,8 +6,10 @@ from typing import Any, Dict
def _to_snake_case(string: str) -> str: def _to_snake_case(string: str) -> str:
return re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower() return re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
def _object_hook(data: Dict[str, Any]) -> Any: 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): class JSONDecoder(json.JSONDecoder):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:

View File

@@ -2,15 +2,16 @@ import json
from decimal import Decimal from decimal import Decimal
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
_ExtJSON = Union[Dict[str, "_ExtJSON"], List["_ExtJSON"], \ _ExtJSON = Union[
bool, int, float, str, Decimal, None] 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]: def _clear(dictionary: Dict[str, Any]) -> Dict[str, Any]:
return { key: value for key, value in dictionary.items() \ return {key: value for key, value in dictionary.items() if value is not None}
if value is not None }
def _adapter(data: _ExtJSON) -> _StrictJSON: def _adapter(data: _ExtJSON) -> _StrictJSON:
if isinstance(data, bool): if isinstance(data, bool):
@@ -21,12 +22,13 @@ def _adapter(data: _ExtJSON) -> _StrictJSON:
return format(data, "f") return format(data, "f")
if isinstance(data, list): 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): 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 return data
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
def encode(self, o: _ExtJSON) -> str: def encode(self, o: _ExtJSON) -> str:
return super().encode(_adapter(o)) return super().encode(_adapter(o))

View File

@@ -1,30 +1,38 @@
import sys import sys
from copy import copy from copy import copy
#pylint: disable-next=wildcard-import,unused-wildcard-import # pylint: disable-next=wildcard-import,unused-wildcard-import
from logging import * from logging import *
from typing import TYPE_CHECKING, Literal, Optional from typing import TYPE_CHECKING, Literal, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
_Level = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] _Level = Literal["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
_BLACK, _RED, _GREEN, _YELLOW, \ _BLACK, _RED, _GREEN, _YELLOW, _BLUE, _MAGENTA, _CYAN, _WHITE = [
_BLUE, _MAGENTA, _CYAN, _WHITE = \ f"\033[0;{90 + i}m" for i in range(8)
[ 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 = \ _BOLD_BLACK,
[ f"\033[1;{90 + i}m" for i in range(8) ] _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" _NC = "\033[0m"
class _ColorFormatter(Formatter): class _ColorFormatter(Formatter):
__LEVELS = { __LEVELS = {
"INFO": _BLUE, "INFO": _BLUE,
"WARNING": _YELLOW, "WARNING": _YELLOW,
"ERROR": _RED, "ERROR": _RED,
"CRITICAL": _BOLD_RED, "CRITICAL": _BOLD_RED,
"DEBUG": _BOLD_WHITE "DEBUG": _BOLD_WHITE,
} }
def format(self, record: LogRecord) -> str: def format(self, record: LogRecord) -> str:
@@ -34,7 +42,7 @@ class _ColorFormatter(Formatter):
return super().format(_record) return super().format(_record)
#pylint: disable-next=invalid-name # pylint: disable-next=invalid-name
def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str:
return _GREEN + super().formatTime(record, datefmt) + _NC return _GREEN + super().formatTime(record, datefmt) + _NC
@@ -42,12 +50,14 @@ class _ColorFormatter(Formatter):
def __format_level(level: str) -> str: def __format_level(level: str) -> str:
return _ColorFormatter.__LEVELS[level] + level + _NC return _ColorFormatter.__LEVELS[level] + level + _NC
_FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s" _FORMAT = "%(asctime)s %(name)s %(levelname)s %(message)s"
_DATE_FORMAT = "%d-%m-%Y %H:%M:%S" _DATE_FORMAT = "%d-%m-%Y %H:%M:%S"
class ColorLogger(Logger): class ColorLogger(Logger):
__FORMATTER = Formatter(_FORMAT,_DATE_FORMAT) __FORMATTER = Formatter(_FORMAT, _DATE_FORMAT)
def __init__(self, name: str, level: "_Level" = "NOTSET") -> None: def __init__(self, name: str, level: "_Level" = "NOTSET") -> None:
super().__init__(name, level) super().__init__(name, level)

View File

@@ -3,8 +3,10 @@ class BfxBaseException(Exception):
Base class for every custom exception thrown by bitfinex-api-py. Base class for every custom exception thrown by bitfinex-api-py.
""" """
class IncompleteCredentialError(BfxBaseException): class IncompleteCredentialError(BfxBaseException):
pass pass
class InvalidCredentialError(BfxBaseException): class InvalidCredentialError(BfxBaseException):
pass pass

View File

@@ -6,7 +6,9 @@ from .rest_public_endpoints import RestPublicEndpoints
class BfxRestInterface: class BfxRestInterface:
VERSION = 2 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.public = RestPublicEndpoints(host=host)
self.auth = RestAuthEndpoints(host=host, api_key=api_key, api_secret=api_secret) 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
)

View File

@@ -39,452 +39,590 @@ from ...types.serializers import _Notification
from ..middleware import Middleware from ..middleware import Middleware
#pylint: disable-next=too-many-public-methods # pylint: disable-next=too-many-public-methods
class RestAuthEndpoints(Middleware): class RestAuthEndpoints(Middleware):
def get_user_info(self) -> UserInfo: def get_user_info(self) -> UserInfo:
return serializers.UserInfo \ return serializers.UserInfo.parse(*self._post("auth/r/info/user"))
.parse(*self._post("auth/r/info/user"))
def get_login_history(self) -> List[LoginHistory]: def get_login_history(self) -> List[LoginHistory]:
return [ serializers.LoginHistory.parse(*sub_data) return [
for sub_data in self._post("auth/r/logins/hist") ] serializers.LoginHistory.parse(*sub_data)
for sub_data in self._post("auth/r/logins/hist")
]
def get_balance_available_for_orders_or_offers(self, def get_balance_available_for_orders_or_offers(
symbol: str, self,
type: str, symbol: str,
*, type: str,
dir: Optional[int] = None, *,
rate: Optional[str] = None, dir: Optional[int] = None,
lev: Optional[str] = None) -> BalanceAvailable: rate: Optional[str] = None,
body = { lev: Optional[str] = None,
"symbol": symbol, "type": type, "dir": dir, ) -> BalanceAvailable:
"rate": rate, "lev": lev body = {"symbol": symbol, "type": type, "dir": dir, "rate": rate, "lev": lev}
}
return serializers.BalanceAvailable \ return serializers.BalanceAvailable.parse(
.parse(*self._post("auth/calc/order/avail", body=body)) *self._post("auth/calc/order/avail", body=body)
)
def get_wallets(self) -> List[Wallet]: def get_wallets(self) -> List[Wallet]:
return [ serializers.Wallet.parse(*sub_data) \ return [
for sub_data in self._post("auth/r/wallets") ] serializers.Wallet.parse(*sub_data)
for sub_data in self._post("auth/r/wallets")
]
def get_orders(self, def get_orders(
*, self, *, symbol: Optional[str] = None, ids: Optional[List[str]] = None
symbol: Optional[str] = None, ) -> List[Order]:
ids: Optional[List[str]] = None) -> List[Order]:
if symbol is None: if symbol is None:
endpoint = "auth/r/orders" endpoint = "auth/r/orders"
else: endpoint = f"auth/r/orders/{symbol}" else:
endpoint = f"auth/r/orders/{symbol}"
return [ serializers.Order.parse(*sub_data) \ return [
for sub_data in self._post(endpoint, body={ "id": ids }) ] serializers.Order.parse(*sub_data)
for sub_data in self._post(endpoint, body={"id": ids})
]
def submit_order(self, def submit_order(
type: str, self,
symbol: str, type: str,
amount: Union[str, float, Decimal], symbol: str,
price: Union[str, float, Decimal], amount: Union[str, float, Decimal],
*, price: Union[str, float, Decimal],
lev: Optional[int] = None, *,
price_trailing: Optional[Union[str, float, Decimal]] = None, lev: Optional[int] = None,
price_aux_limit: Optional[Union[str, float, Decimal]] = None, price_trailing: Optional[Union[str, float, Decimal]] = None,
price_oco_stop: Optional[Union[str, float, Decimal]] = None, price_aux_limit: Optional[Union[str, float, Decimal]] = None,
gid: Optional[int] = None, price_oco_stop: Optional[Union[str, float, Decimal]] = None,
cid: Optional[int] = None, gid: Optional[int] = None,
flags: Optional[int] = None, cid: Optional[int] = None,
tif: Optional[str] = None) -> Notification[Order]: flags: Optional[int] = None,
tif: Optional[str] = None,
) -> Notification[Order]:
body = { body = {
"type": type, "symbol": symbol, "amount": amount, "type": type,
"price": price, "lev": lev, "price_trailing": price_trailing, "symbol": symbol,
"price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid, "amount": amount,
"cid": cid, "flags": flags, "tif": tif "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) \ return _Notification[Order](serializers.Order).parse(
.parse(*self._post("auth/w/order/submit", body=body)) *self._post("auth/w/order/submit", body=body)
)
def update_order(self, def update_order(
id: int, self,
*, id: int,
amount: Optional[Union[str, float, Decimal]] = None, *,
price: Optional[Union[str, float, Decimal]] = None, amount: Optional[Union[str, float, Decimal]] = None,
cid: Optional[int] = None, price: Optional[Union[str, float, Decimal]] = None,
cid_date: Optional[str] = None, cid: Optional[int] = None,
gid: Optional[int] = None, cid_date: Optional[str] = None,
flags: Optional[int] = None, gid: Optional[int] = None,
lev: Optional[int] = None, flags: Optional[int] = None,
delta: Optional[Union[str, float, Decimal]] = None, lev: Optional[int] = None,
price_aux_limit: Optional[Union[str, float, Decimal]] = None, delta: Optional[Union[str, float, Decimal]] = None,
price_trailing: Optional[Union[str, float, Decimal]] = None, price_aux_limit: Optional[Union[str, float, Decimal]] = None,
tif: Optional[str] = None) -> Notification[Order]: price_trailing: Optional[Union[str, float, Decimal]] = None,
tif: Optional[str] = None,
) -> Notification[Order]:
body = { body = {
"id": id, "amount": amount, "price": price, "id": id,
"cid": cid, "cid_date": cid_date, "gid": gid, "amount": amount,
"flags": flags, "lev": lev, "delta": delta, "price": price,
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif "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) \ return _Notification[Order](serializers.Order).parse(
.parse(*self._post("auth/w/order/update", body=body)) *self._post("auth/w/order/update", body=body)
)
def cancel_order(self, def cancel_order(
*, self,
id: Optional[int] = None, *,
cid: Optional[int] = None, id: Optional[int] = None,
cid_date: Optional[str] = None) -> Notification[Order]: cid: Optional[int] = None,
return _Notification[Order](serializers.Order) \ cid_date: Optional[str] = None,
.parse(*self._post("auth/w/order/cancel", \ ) -> Notification[Order]:
body={ "id": id, "cid": cid, "cid_date": cid_date })) 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, def cancel_order_multi(
*, self,
id: Optional[List[int]] = None, *,
cid: Optional[List[Tuple[int, str]]] = None, id: Optional[List[int]] = None,
gid: Optional[List[int]] = None, cid: Optional[List[Tuple[int, str]]] = None,
all: Optional[bool] = None) -> Notification[List[Order]]: gid: Optional[List[int]] = None,
body = { all: Optional[bool] = None,
"id": id, "cid": cid, "gid": gid, ) -> Notification[List[Order]]:
"all": all body = {"id": id, "cid": cid, "gid": gid, "all": all}
}
return _Notification[List[Order]](serializers.Order, is_iterable=True) \ return _Notification[List[Order]](serializers.Order, is_iterable=True).parse(
.parse(*self._post("auth/w/order/cancel/multi", body=body)) *self._post("auth/w/order/cancel/multi", body=body)
)
def get_orders_history(self, def get_orders_history(
*, self,
symbol: Optional[str] = None, *,
ids: Optional[List[int]] = None, symbol: Optional[str] = None,
start: Optional[str] = None, ids: Optional[List[int]] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Order]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[Order]:
if symbol is None: if symbol is None:
endpoint = "auth/r/orders/hist" endpoint = "auth/r/orders/hist"
else: endpoint = f"auth/r/orders/{symbol}/hist" else:
endpoint = f"auth/r/orders/{symbol}/hist"
body = { body = {"id": ids, "start": start, "end": end, "limit": limit}
"id": ids, "start": start, "end": end,
"limit": limit
}
return [ serializers.Order.parse(*sub_data) \ return [
for sub_data in self._post(endpoint, body=body) ] serializers.Order.parse(*sub_data)
for sub_data in self._post(endpoint, body=body)
]
def get_order_trades(self, def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
symbol: str, return [
id: int) -> List[OrderTrade]: serializers.OrderTrade.parse(*sub_data)
return [ serializers.OrderTrade.parse(*sub_data) \ for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades")
for sub_data in self._post(f"auth/r/order/{symbol}:{id}/trades") ] ]
def get_trades_history(self, def get_trades_history(
*, self,
symbol: Optional[str] = None, *,
sort: Optional[int] = None, symbol: Optional[str] = None,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Trade]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[Trade]:
if symbol is None: if symbol is None:
endpoint = "auth/r/trades/hist" endpoint = "auth/r/trades/hist"
else: endpoint = f"auth/r/trades/{symbol}/hist" else:
endpoint = f"auth/r/trades/{symbol}/hist"
body = { body = {"sort": sort, "start": start, "end": end, "limit": limit}
"sort": sort, "start": start, "end": end,
"limit": limit
}
return [ serializers.Trade.parse(*sub_data) \ return [
for sub_data in self._post(endpoint, body=body) ] serializers.Trade.parse(*sub_data)
for sub_data in self._post(endpoint, body=body)
]
def get_ledgers(self, def get_ledgers(
currency: str, self,
*, currency: str,
category: Optional[int] = None, *,
start: Optional[str] = None, category: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Ledger]: end: Optional[str] = None,
body = { limit: Optional[int] = None,
"category": category, "start": start, "end": end, ) -> List[Ledger]:
"limit": limit body = {"category": category, "start": start, "end": end, "limit": limit}
}
return [ serializers.Ledger.parse(*sub_data) \ return [
for sub_data in self._post(f"auth/r/ledgers/{currency}/hist", body=body) ] 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: def get_base_margin_info(self) -> BaseMarginInfo:
return serializers.BaseMarginInfo \ return serializers.BaseMarginInfo.parse(
.parse(*(self._post("auth/r/info/margin/base")[1])) *(self._post("auth/r/info/margin/base")[1])
)
def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo: def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
return serializers.SymbolMarginInfo \ return serializers.SymbolMarginInfo.parse(
.parse(*self._post(f"auth/r/info/margin/{symbol}")) *self._post(f"auth/r/info/margin/{symbol}")
)
def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]: def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
return [ serializers.SymbolMarginInfo.parse(*sub_data) \ return [
for sub_data in self._post("auth/r/info/margin/sym_all") ] serializers.SymbolMarginInfo.parse(*sub_data)
for sub_data in self._post("auth/r/info/margin/sym_all")
]
def get_positions(self) -> List[Position]: def get_positions(self) -> List[Position]:
return [ serializers.Position.parse(*sub_data) \ return [
for sub_data in self._post("auth/r/positions") ] serializers.Position.parse(*sub_data)
for sub_data in self._post("auth/r/positions")
]
def claim_position(self, def claim_position(
id: int, self, id: int, *, amount: Optional[Union[str, float, Decimal]] = None
*, ) -> Notification[PositionClaim]:
amount: Optional[Union[str, float, Decimal]] = None) -> Notification[PositionClaim]: return _Notification[PositionClaim](serializers.PositionClaim).parse(
return _Notification[PositionClaim](serializers.PositionClaim) \ *self._post("auth/w/position/claim", body={"id": id, "amount": amount})
.parse(*self._post("auth/w/position/claim", \ )
body={ "id": id, "amount": amount }))
def increase_position(self, def increase_position(
symbol: str, self, symbol: str, amount: Union[str, float, Decimal]
amount: Union[str, float, Decimal]) -> Notification[PositionIncrease]: ) -> Notification[PositionIncrease]:
return _Notification[PositionIncrease](serializers.PositionIncrease) \ return _Notification[PositionIncrease](serializers.PositionIncrease).parse(
.parse(*self._post("auth/w/position/increase", \ *self._post(
body={ "symbol": symbol, "amount": amount })) "auth/w/position/increase", body={"symbol": symbol, "amount": amount}
)
)
def get_increase_position_info(self, def get_increase_position_info(
symbol: str, self, symbol: str, amount: Union[str, float, Decimal]
amount: Union[str, float, Decimal]) -> PositionIncreaseInfo: ) -> PositionIncreaseInfo:
return serializers.PositionIncreaseInfo \ return serializers.PositionIncreaseInfo.parse(
.parse(*self._post("auth/r/position/increase/info", \ *self._post(
body={ "symbol": symbol, "amount": amount })) "auth/r/position/increase/info",
body={"symbol": symbol, "amount": amount},
)
)
def get_positions_history(self, def get_positions_history(
*, self,
start: Optional[str] = None, *,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[PositionHistory]: end: Optional[str] = None,
return [ serializers.PositionHistory.parse(*sub_data) \ limit: Optional[int] = None,
for sub_data in self._post("auth/r/positions/hist", \ ) -> List[PositionHistory]:
body={ "start": start, "end": end, "limit": limit }) ] 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, def get_positions_snapshot(
*, self,
start: Optional[str] = None, *,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[PositionSnapshot]: end: Optional[str] = None,
return [ serializers.PositionSnapshot.parse(*sub_data) \ limit: Optional[int] = None,
for sub_data in self._post("auth/r/positions/snap", \ ) -> List[PositionSnapshot]:
body={ "start": start, "end": end, "limit": limit }) ] 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, def get_positions_audit(
*, self,
ids: Optional[List[int]] = None, *,
start: Optional[str] = None, ids: Optional[List[int]] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[PositionAudit]: end: Optional[str] = None,
body = { limit: Optional[int] = None,
"ids": ids, "start": start, "end": end, ) -> List[PositionAudit]:
"limit": limit body = {"ids": ids, "start": start, "end": end, "limit": limit}
}
return [ serializers.PositionAudit.parse(*sub_data) \ return [
for sub_data in self._post("auth/r/positions/audit", body=body) ] serializers.PositionAudit.parse(*sub_data)
for sub_data in self._post("auth/r/positions/audit", body=body)
]
def set_derivative_position_collateral(self, def set_derivative_position_collateral(
symbol: str, self, symbol: str, collateral: Union[str, float, Decimal]
collateral: Union[str, float, Decimal]) -> DerivativePositionCollateral: ) -> DerivativePositionCollateral:
return serializers.DerivativePositionCollateral \ return serializers.DerivativePositionCollateral.parse(
.parse(*(self._post("auth/w/deriv/collateral/set", \ *(
body={ "symbol": symbol, "collateral": collateral })[0])) self._post(
"auth/w/deriv/collateral/set",
body={"symbol": symbol, "collateral": collateral},
)[0]
)
)
def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits: def get_derivative_position_collateral_limits(
return serializers.DerivativePositionCollateralLimits \ self, symbol: str
.parse(*self._post("auth/calc/deriv/collateral/limit", body={ "symbol": symbol })) ) -> 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]: def get_funding_offers(self, *, symbol: Optional[str] = None) -> List[FundingOffer]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/offers" 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) \ return [
for sub_data in self._post(endpoint) ] serializers.FundingOffer.parse(*sub_data)
for sub_data in self._post(endpoint)
]
#pylint: disable-next=too-many-arguments # pylint: disable-next=too-many-arguments
def submit_funding_offer(self, def submit_funding_offer(
type: str, self,
symbol: str, type: str,
amount: Union[str, float, Decimal], symbol: str,
rate: Union[str, float, Decimal], amount: Union[str, float, Decimal],
period: int, rate: Union[str, float, Decimal],
*, period: int,
flags: Optional[int] = None) -> Notification[FundingOffer]: *,
flags: Optional[int] = None,
) -> Notification[FundingOffer]:
body = { body = {
"type": type, "symbol": symbol, "amount": amount, "type": type,
"rate": rate, "period": period, "flags": flags "symbol": symbol,
"amount": amount,
"rate": rate,
"period": period,
"flags": flags,
} }
return _Notification[FundingOffer](serializers.FundingOffer) \ return _Notification[FundingOffer](serializers.FundingOffer).parse(
.parse(*self._post("auth/w/funding/offer/submit", body=body)) *self._post("auth/w/funding/offer/submit", body=body)
)
def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]: def cancel_funding_offer(self, id: int) -> Notification[FundingOffer]:
return _Notification[FundingOffer](serializers.FundingOffer) \ return _Notification[FundingOffer](serializers.FundingOffer).parse(
.parse(*self._post("auth/w/funding/offer/cancel", body={ "id": id })) *self._post("auth/w/funding/offer/cancel", body={"id": id})
)
def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]: def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
return _Notification[Literal[None]](None) \ return _Notification[Literal[None]](None).parse(
.parse(*self._post("auth/w/funding/offer/cancel/all", body={ "currency": currency })) *self._post("auth/w/funding/offer/cancel/all", body={"currency": currency})
)
def submit_funding_close(self, id: int) -> Notification[Literal[None]]: def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
return _Notification[Literal[None]](None) \ return _Notification[Literal[None]](None).parse(
.parse(*self._post("auth/w/funding/close", body={ "id": id })) *self._post("auth/w/funding/close", body={"id": id})
)
def toggle_auto_renew(self, def toggle_auto_renew(
status: bool, self,
currency: str, status: bool,
*, currency: str,
amount: Optional[str] = None, *,
rate: Optional[int] = None, amount: Optional[str] = None,
period: Optional[int] = None) -> Notification[FundingAutoRenew]: rate: Optional[int] = None,
period: Optional[int] = None,
) -> Notification[FundingAutoRenew]:
body = { body = {
"status": status, "currency": currency, "amount": amount, "status": status,
"rate": rate, "period": period "currency": currency,
"amount": amount,
"rate": rate,
"period": period,
} }
return _Notification[FundingAutoRenew](serializers.FundingAutoRenew) \ return _Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(
.parse(*self._post("auth/w/funding/auto", body=body)) *self._post("auth/w/funding/auto", body=body)
)
def toggle_keep_funding(self, def toggle_keep_funding(
type: Literal["credit", "loan"], self,
*, type: Literal["credit", "loan"],
ids: Optional[List[int]] = None, *,
changes: Optional[Dict[int, Literal[1, 2]]] = None) -> Notification[Literal[None]]: ids: Optional[List[int]] = None,
return _Notification[Literal[None]](None) \ changes: Optional[Dict[int, Literal[1, 2]]] = None,
.parse(*self._post("auth/w/funding/keep", \ ) -> Notification[Literal[None]]:
body={ "type": type, "id": ids, "changes": changes })) 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, def get_funding_offers_history(
*, self,
symbol: Optional[str] = None, *,
start: Optional[str] = None, symbol: Optional[str] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[FundingOffer]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[FundingOffer]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/offers/hist" 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) \ return [
for sub_data in self._post(endpoint, \ serializers.FundingOffer.parse(*sub_data)
body={ "start": start, "end": end, "limit": limit }) ] 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]: def get_funding_loans(self, *, symbol: Optional[str] = None) -> List[FundingLoan]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/loans" 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) \ return [
for sub_data in self._post(endpoint) ] serializers.FundingLoan.parse(*sub_data)
for sub_data in self._post(endpoint)
]
def get_funding_loans_history(self, def get_funding_loans_history(
*, self,
symbol: Optional[str] = None, *,
start: Optional[str] = None, symbol: Optional[str] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[FundingLoan]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[FundingLoan]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/loans/hist" 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) \ return [
for sub_data in self._post(endpoint, \ serializers.FundingLoan.parse(*sub_data)
body={ "start": start, "end": end, "limit": limit }) ] 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: if symbol is None:
endpoint = "auth/r/funding/credits" 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) \ return [
for sub_data in self._post(endpoint) ] serializers.FundingCredit.parse(*sub_data)
for sub_data in self._post(endpoint)
]
def get_funding_credits_history(self, def get_funding_credits_history(
*, self,
symbol: Optional[str] = None, *,
start: Optional[str] = None, symbol: Optional[str] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[FundingCredit]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[FundingCredit]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/credits/hist" 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) \ return [
for sub_data in self._post(endpoint, \ serializers.FundingCredit.parse(*sub_data)
body={ "start": start, "end": end, "limit": limit }) ] for sub_data in self._post(
endpoint, body={"start": start, "end": end, "limit": limit}
)
]
def get_funding_trades_history(self, def get_funding_trades_history(
*, self,
symbol: Optional[str] = None, *,
sort: Optional[int] = None, symbol: Optional[str] = None,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[FundingTrade]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[FundingTrade]:
if symbol is None: if symbol is None:
endpoint = "auth/r/funding/trades/hist" 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 = { body = {"sort": sort, "start": start, "end": end, "limit": limit}
"sort": sort, "start": start, "end": end,
"limit": limit }
return [ serializers.FundingTrade.parse(*sub_data) \ return [
for sub_data in self._post(endpoint, body=body) ] serializers.FundingTrade.parse(*sub_data)
for sub_data in self._post(endpoint, body=body)
]
def get_funding_info(self, key: str) -> FundingInfo: def get_funding_info(self, key: str) -> FundingInfo:
return serializers.FundingInfo \ return serializers.FundingInfo.parse(
.parse(*(self._post(f"auth/r/info/funding/{key}")[2])) *(self._post(f"auth/r/info/funding/{key}")[2])
)
#pylint: disable-next=too-many-arguments # pylint: disable-next=too-many-arguments
def transfer_between_wallets(self, def transfer_between_wallets(
from_wallet: str, self,
to_wallet: str, from_wallet: str,
currency: str, to_wallet: str,
currency_to: str, currency: str,
amount: Union[str, float, Decimal]) -> Notification[Transfer]: currency_to: str,
amount: Union[str, float, Decimal],
) -> Notification[Transfer]:
body = { body = {
"from": from_wallet, "to": to_wallet, "currency": currency, "from": from_wallet,
"currency_to": currency_to, "amount": amount "to": to_wallet,
"currency": currency,
"currency_to": currency_to,
"amount": amount,
} }
return _Notification[Transfer](serializers.Transfer) \ return _Notification[Transfer](serializers.Transfer).parse(
.parse(*self._post("auth/w/transfer", body=body)) *self._post("auth/w/transfer", body=body)
)
def submit_wallet_withdrawal(self, def submit_wallet_withdrawal(
wallet: str, self, wallet: str, method: str, address: str, amount: Union[str, float, Decimal]
method: str, ) -> Notification[Withdrawal]:
address: str,
amount: Union[str, float, Decimal]) -> Notification[Withdrawal]:
body = { body = {
"wallet": wallet, "method": method, "address": address, "wallet": wallet,
"amount": amount "method": method,
"address": address,
"amount": amount,
} }
return _Notification[Withdrawal](serializers.Withdrawal) \ return _Notification[Withdrawal](serializers.Withdrawal).parse(
.parse(*self._post("auth/w/withdraw", body=body)) *self._post("auth/w/withdraw", body=body)
)
def get_deposit_address(self, def get_deposit_address(
wallet: str, self, wallet: str, method: str, op_renew: bool = False
method: str, ) -> Notification[DepositAddress]:
op_renew: bool = False) -> Notification[DepositAddress]: return _Notification[DepositAddress](serializers.DepositAddress).parse(
return _Notification[DepositAddress](serializers.DepositAddress) \ *self._post(
.parse(*self._post("auth/w/deposit/address", \ "auth/w/deposit/address",
body={ "wallet": wallet, "method": method, "op_renew": op_renew })) body={"wallet": wallet, "method": method, "op_renew": op_renew},
)
)
def generate_deposit_invoice(self, def generate_deposit_invoice(
wallet: str, self, wallet: str, currency: str, amount: Union[str, float, Decimal]
currency: str, ) -> LightningNetworkInvoice:
amount: Union[str, float, Decimal]) -> LightningNetworkInvoice: return serializers.LightningNetworkInvoice.parse(
return serializers.LightningNetworkInvoice \ *self._post(
.parse(*self._post("auth/w/deposit/invoice", \ "auth/w/deposit/invoice",
body={ "wallet": wallet, "currency": currency, "amount": amount })) body={"wallet": wallet, "currency": currency, "amount": amount},
)
)
def get_movements(self, def get_movements(
*, self,
currency: Optional[str] = None, *,
start: Optional[str] = None, currency: Optional[str] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Movement]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[Movement]:
if currency is None: if currency is None:
endpoint = "auth/r/movements/hist" 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) \ return [
for sub_data in self._post(endpoint, \ serializers.Movement.parse(*sub_data)
body={ "start": start, "end": end, "limit": limit }) ] for sub_data in self._post(
endpoint, body={"start": start, "end": end, "limit": limit}
)
]

View File

@@ -11,99 +11,131 @@ from bfxapi.types import (
MerchantUnlinkedDeposit, MerchantUnlinkedDeposit,
) )
_CustomerInfo = TypedDict("_CustomerInfo", { _CustomerInfo = TypedDict(
"nationality": str, "_CustomerInfo",
"resid_country": str, {
"resid_city": str, "nationality": str,
"resid_zip_code": str, "resid_country": str,
"resid_street": str, "resid_city": str,
"resid_building_no": str, "resid_zip_code": str,
"full_name": str, "resid_street": str,
"email": str, "resid_building_no": str,
"tos_accepted": bool "full_name": str,
}) "email": str,
"tos_accepted": bool,
},
)
class RestMerchantEndpoints(Middleware): class RestMerchantEndpoints(Middleware):
#pylint: disable-next=too-many-arguments # pylint: disable-next=too-many-arguments
def submit_invoice(self, def submit_invoice(
amount: Union[str, float, Decimal], self,
currency: str, amount: Union[str, float, Decimal],
order_id: str, currency: str,
customer_info: _CustomerInfo, order_id: str,
pay_currencies: List[str], customer_info: _CustomerInfo,
*, pay_currencies: List[str],
duration: Optional[int] = None, *,
webhook: Optional[str] = None, duration: Optional[int] = None,
redirect_url: Optional[str] = None) -> InvoiceSubmission: webhook: Optional[str] = None,
redirect_url: Optional[str] = None,
) -> InvoiceSubmission:
body = { body = {
"amount": amount, "currency": currency, "orderId": order_id, "amount": amount,
"customerInfo": customer_info, "payCurrencies": pay_currencies, "duration": duration, "currency": currency,
"webhook": webhook, "redirectUrl": redirect_url "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) data = self._post("auth/w/ext/pay/invoice/create", body=body)
return InvoiceSubmission.parse(data) return InvoiceSubmission.parse(data)
def get_invoices(self, def get_invoices(
*, self,
id: Optional[str] = None, *,
start: Optional[str] = None, id: Optional[str] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[InvoiceSubmission]: end: Optional[str] = None,
body = { limit: Optional[int] = None,
"id": id, "start": start, "end": end, ) -> List[InvoiceSubmission]:
"limit": limit body = {"id": id, "start": start, "end": end, "limit": limit}
}
data = self._post("auth/r/ext/pay/invoices", body=body) 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, def get_invoices_paginated(
page: int = 1, self,
page_size: int = 10, page: int = 1,
sort: Literal["asc", "desc"] = "asc", page_size: int = 10,
sort_field: Literal["t", "amount", "status"] = "t", 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, status: Optional[
crypto: Optional[List[str]] = None, List[Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]]
id: Optional[str] = None, ] = None,
order_id: Optional[str] = None) -> InvoicePage: fiat: Optional[List[str]] = None,
crypto: Optional[List[str]] = None,
id: Optional[str] = None,
order_id: Optional[str] = None,
) -> InvoicePage:
body = { body = {
"page": page, "pageSize": page_size, "sort": sort, "page": page,
"sortField": sort_field, "status": status, "fiat": fiat, "pageSize": page_size,
"crypto": crypto, "id": id, "orderId": order_id "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) data = self._post("auth/r/ext/pay/invoices/paginated", body=body)
return InvoicePage.parse(data) return InvoicePage.parse(data)
def get_invoice_count_stats(self, def get_invoice_count_stats(
status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], self, status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], format: str
format: str) -> List[InvoiceStats]: ) -> List[InvoiceStats]:
return [ InvoiceStats(**sub_data) for sub_data in \ return [
self._post("auth/r/ext/pay/invoice/stats/count", \ InvoiceStats(**sub_data)
body={ "status": status, "format": format }) ] for sub_data in self._post(
"auth/r/ext/pay/invoice/stats/count",
body={"status": status, "format": format},
)
]
def get_invoice_earning_stats(self, def get_invoice_earning_stats(
currency: str, self, currency: str, format: str
format: str) -> List[InvoiceStats]: ) -> List[InvoiceStats]:
return [ InvoiceStats(**sub_data) for sub_data in \ return [
self._post("auth/r/ext/pay/invoice/stats/earning", \ InvoiceStats(**sub_data)
body={ "currency": currency, "format": format }) ] for sub_data in self._post(
"auth/r/ext/pay/invoice/stats/earning",
body={"currency": currency, "format": format},
)
]
def complete_invoice(self, def complete_invoice(
id: str, self,
pay_currency: str, id: str,
*, pay_currency: str,
deposit_id: Optional[int] = None, *,
ledger_id: Optional[int] = None) -> InvoiceSubmission: deposit_id: Optional[int] = None,
ledger_id: Optional[int] = None,
) -> InvoiceSubmission:
body = { body = {
"id": id, "payCcy": pay_currency, "depositId": deposit_id, "id": id,
"ledgerId": ledger_id "payCcy": pay_currency,
"depositId": deposit_id,
"ledgerId": ledger_id,
} }
data = self._post("auth/w/ext/pay/invoice/complete", body=body) data = self._post("auth/w/ext/pay/invoice/complete", body=body)
@@ -111,65 +143,65 @@ class RestMerchantEndpoints(Middleware):
return InvoiceSubmission.parse(data) return InvoiceSubmission.parse(data)
def expire_invoice(self, id: str) -> InvoiceSubmission: def expire_invoice(self, id: str) -> InvoiceSubmission:
body = { "id": id } body = {"id": id}
data = self._post("auth/w/ext/pay/invoice/expire", body=body) data = self._post("auth/w/ext/pay/invoice/expire", body=body)
return InvoiceSubmission.parse(data) return InvoiceSubmission.parse(data)
def get_currency_conversion_list(self) -> List[CurrencyConversion]: def get_currency_conversion_list(self) -> List[CurrencyConversion]:
return [ CurrencyConversion(**sub_data) \ return [
for sub_data in self._post("auth/r/ext/pay/settings/convert/list") ] CurrencyConversion(**sub_data)
for sub_data in self._post("auth/r/ext/pay/settings/convert/list")
]
def add_currency_conversion(self, def add_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
base_ccy: str, return bool(
convert_ccy: str) -> bool: self._post(
return bool(self._post("auth/w/ext/pay/settings/convert/create", \ "auth/w/ext/pay/settings/convert/create",
body={ "baseCcy": base_ccy, "convertCcy": convert_ccy })) body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
)
)
def remove_currency_conversion(self, def remove_currency_conversion(self, base_ccy: str, convert_ccy: str) -> bool:
base_ccy: str, return bool(
convert_ccy: str) -> bool: self._post(
return bool(self._post("auth/w/ext/pay/settings/convert/remove", \ "auth/w/ext/pay/settings/convert/remove",
body={ "baseCcy": base_ccy, "convertCcy": convert_ccy })) body={"baseCcy": base_ccy, "convertCcy": convert_ccy},
)
)
def set_merchant_settings(self, def set_merchant_settings(self, key: str, val: Any) -> bool:
key: str, return bool(
val: Any) -> bool: self._post("auth/w/ext/pay/settings/set", body={"key": key, "val": val})
return bool(self._post("auth/w/ext/pay/settings/set", \ )
body={ "key": key, "val": val }))
def get_merchant_settings(self, key: str) -> Any: 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]: 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, def get_deposits(
start: int, self,
to: int, start: int,
*, to: int,
ccy: Optional[str] = None, *,
unlinked: Optional[bool] = None) -> List[MerchantDeposit]: ccy: Optional[str] = None,
body = { unlinked: Optional[bool] = None,
"from": start, "to": to, "ccy": ccy, ) -> List[MerchantDeposit]:
"unlinked": unlinked body = {"from": start, "to": to, "ccy": ccy, "unlinked": unlinked}
}
data = self._post("auth/r/ext/pay/deposits", body=body) 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, def get_unlinked_deposits(
ccy: str, self, ccy: str, *, start: Optional[int] = None, end: Optional[int] = None
*, ) -> List[MerchantUnlinkedDeposit]:
start: Optional[int] = None, body = {"ccy": ccy, "start": start, "end": end}
end: Optional[int] = None) -> List[MerchantUnlinkedDeposit]:
body = {
"ccy": ccy, "start": start, "end": end
}
data = self._post("/auth/r/ext/pay/deposits/unlinked", body=body) 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]

View File

@@ -28,7 +28,7 @@ from ...types import (
from ..middleware import Middleware from ..middleware import Middleware
#pylint: disable-next=too-many-public-methods # pylint: disable-next=too-many-public-methods
class RestPublicEndpoints(Middleware): class RestPublicEndpoints(Middleware):
def conf(self, config: str) -> Any: def conf(self, config: str) -> Any:
return self._get(f"conf/{config}")[0] return self._get(f"conf/{config}")[0]
@@ -36,35 +36,47 @@ class RestPublicEndpoints(Middleware):
def get_platform_status(self) -> PlatformStatus: def get_platform_status(self) -> PlatformStatus:
return serializers.PlatformStatus.parse(*self._get("platform/status")) return serializers.PlatformStatus.parse(*self._get("platform/status"))
def get_tickers(self, symbols: List[str]) -> Dict[str, Union[TradingPairTicker, FundingCurrencyTicker]]: def get_tickers(
data = self._get("tickers", params={ "symbols": ",".join(symbols) }) 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 } parsers = {
"t": serializers.TradingPairTicker.parse,
return { "f": serializers.FundingCurrencyTicker.parse,
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]: 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": if isinstance(symbols, str) and symbols == "ALL":
return { return {
symbol: cast(TradingPairTicker, sub_data) symbol: cast(TradingPairTicker, sub_data)
for symbol, sub_data in self.get_tickers([ "ALL" ]).items() for symbol, sub_data in self.get_tickers(["ALL"]).items()
if symbol.startswith("t") if symbol.startswith("t")
} }
data = self.get_tickers(list(symbols)) data = self.get_tickers(list(symbols))
return cast(Dict[str, TradingPairTicker], data) 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": if isinstance(symbols, str) and symbols == "ALL":
return { return {
symbol: cast(FundingCurrencyTicker, sub_data) symbol: cast(FundingCurrencyTicker, sub_data)
for symbol, sub_data in self.get_tickers([ "ALL" ]).items() for symbol, sub_data in self.get_tickers(["ALL"]).items()
if symbol.startswith("f") if symbol.startswith("f")
} }
data = self.get_tickers(list(symbols)) data = self.get_tickers(list(symbols))
@@ -77,230 +89,292 @@ class RestPublicEndpoints(Middleware):
def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker: def get_f_ticker(self, symbol: str) -> FundingCurrencyTicker:
return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}")) return serializers.FundingCurrencyTicker.parse(*self._get(f"ticker/{symbol}"))
def get_tickers_history(self, def get_tickers_history(
symbols: List[str], self,
*, symbols: List[str],
start: Optional[str] = None, *,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[TickersHistory]: end: Optional[str] = None,
return [ serializers.TickersHistory.parse(*sub_data) for sub_data in self._get("tickers/hist", params={ limit: Optional[int] = None,
"symbols": ",".join(symbols), ) -> List[TickersHistory]:
"start": start, "end": end, return [
"limit": limit 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, def get_t_trades(
pair: str, self,
*, pair: str,
limit: Optional[int] = None, *,
start: Optional[str] = None, limit: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
sort: Optional[int] = None) -> List[TradingPairTrade]: end: Optional[str] = None,
params = { "limit": limit, "start": start, "end": end, "sort": sort } sort: Optional[int] = None,
) -> List[TradingPairTrade]:
params = {"limit": limit, "start": start, "end": end, "sort": sort}
data = self._get(f"trades/{pair}/hist", params=params) 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, def get_f_trades(
currency: str, self,
*, currency: str,
limit: Optional[int] = None, *,
start: Optional[str] = None, limit: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
sort: Optional[int] = None) -> List[FundingCurrencyTrade]: end: Optional[str] = None,
params = { "limit": limit, "start": start, "end": end, "sort": sort } sort: Optional[int] = None,
) -> List[FundingCurrencyTrade]:
params = {"limit": limit, "start": start, "end": end, "sort": sort}
data = self._get(f"trades/{currency}/hist", params=params) 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, def get_t_book(
pair: str, self,
precision: Literal["P0", "P1", "P2", "P3", "P4"], pair: str,
*, precision: Literal["P0", "P1", "P2", "P3", "P4"],
len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]: *,
return [ serializers.TradingPairBook.parse(*sub_data) \ len: Optional[Literal[1, 25, 100]] = None,
for sub_data in self._get(f"book/{pair}/{precision}", params={ "len": len }) ] ) -> 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, def get_f_book(
currency: str, self,
precision: Literal["P0", "P1", "P2", "P3", "P4"], currency: str,
*, precision: Literal["P0", "P1", "P2", "P3", "P4"],
len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]: *,
return [ serializers.FundingCurrencyBook.parse(*sub_data) \ len: Optional[Literal[1, 25, 100]] = None,
for sub_data in self._get(f"book/{currency}/{precision}", params={ "len": len }) ] ) -> 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, def get_t_raw_book(
pair: str, self, pair: str, *, len: Optional[Literal[1, 25, 100]] = None
*, ) -> List[TradingPairRawBook]:
len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]: return [
return [ serializers.TradingPairRawBook.parse(*sub_data) \ serializers.TradingPairRawBook.parse(*sub_data)
for sub_data in self._get(f"book/{pair}/R0", params={ "len": len }) ] for sub_data in self._get(f"book/{pair}/R0", params={"len": len})
]
def get_f_raw_book(self, def get_f_raw_book(
currency: str, self, currency: str, *, len: Optional[Literal[1, 25, 100]] = None
*, ) -> List[FundingCurrencyRawBook]:
len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]: return [
return [ serializers.FundingCurrencyRawBook.parse(*sub_data) \ serializers.FundingCurrencyRawBook.parse(*sub_data)
for sub_data in self._get(f"book/{currency}/R0", params={ "len": len }) ] for sub_data in self._get(f"book/{currency}/R0", params={"len": len})
]
def get_stats_hist(self, def get_stats_hist(
resource: str, self,
*, resource: str,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Statistic]: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> List[Statistic]:
params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get(f"stats1/{resource}/hist", params=params) 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, def get_stats_last(
resource: str, self,
*, resource: str,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> Statistic: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> Statistic:
params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get(f"stats1/{resource}/last", params=params) data = self._get(f"stats1/{resource}/last", params=params)
return serializers.Statistic.parse(*data) return serializers.Statistic.parse(*data)
def get_candles_hist(self, def get_candles_hist(
symbol: str, self,
tf: str = "1m", symbol: str,
*, tf: str = "1m",
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Candle]: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } 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) 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, def get_candles_last(
symbol: str, self,
tf: str = "1m", symbol: str,
*, tf: str = "1m",
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> Candle: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } 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) data = self._get(f"candles/trade:{tf}:{symbol}/last", params=params)
return serializers.Candle.parse(*data) 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": if keys == "ALL":
params = { "keys": "ALL" } params = {"keys": "ALL"}
else: params = { "keys": ",".join(keys) } else:
params = {"keys": ",".join(keys)}
data = self._get("status/deriv", params=params) data = self._get("status/deriv", params=params)
return { return {
key: serializers.DerivativesStatus.parse(*sub_data) key: serializers.DerivativesStatus.parse(*sub_data)
for sub_data in data for sub_data in data
if (key := sub_data.pop(0)) if (key := sub_data.pop(0))
} }
def get_derivatives_status_history(self, def get_derivatives_status_history(
key: str, self,
*, key: str,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[DerivativesStatus]: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } 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) 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, def get_liquidations(
*, self,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Liquidation]: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> List[Liquidation]:
params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get("liquidations/hist", params=params) 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, def get_seed_candles(
symbol: str, self,
tf: str = "1m", symbol: str,
*, tf: str = "1m",
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Candle]: end: Optional[str] = None,
limit: Optional[int] = None,
) -> List[Candle]:
params = {"sort": sort, "start": start, "end": end, "limit": limit} params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get(f"candles/trade:{tf}:{symbol}/hist", params=params) 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, def get_leaderboards_hist(
resource: str, self,
*, resource: str,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[Leaderboard]: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> List[Leaderboard]:
params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get(f"rankings/{resource}/hist", params=params) 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, def get_leaderboards_last(
resource: str, self,
*, resource: str,
sort: Optional[int] = None, *,
start: Optional[str] = None, sort: Optional[int] = None,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> Leaderboard: end: Optional[str] = None,
params = { "sort": sort, "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> Leaderboard:
params = {"sort": sort, "start": start, "end": end, "limit": limit}
data = self._get(f"rankings/{resource}/last", params=params) data = self._get(f"rankings/{resource}/last", params=params)
return serializers.Leaderboard.parse(*data) return serializers.Leaderboard.parse(*data)
def get_funding_stats(self, def get_funding_stats(
symbol: str, self,
*, symbol: str,
start: Optional[str] = None, *,
end: Optional[str] = None, start: Optional[str] = None,
limit: Optional[int] = None) -> List[FundingStatistic]: end: Optional[str] = None,
params = { "start": start, "end": end, "limit": limit } limit: Optional[int] = None,
) -> List[FundingStatistic]:
params = {"start": start, "end": end, "limit": limit}
data = self._get(f"funding/stats/{symbol}/hist", params=params) 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: def get_pulse_profile_details(self, nickname: str) -> PulseProfile:
return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}")) return serializers.PulseProfile.parse(*self._get(f"pulse/profile/{nickname}"))
def get_pulse_message_history(self, def get_pulse_message_history(
*, self, *, end: Optional[str] = None, limit: Optional[int] = None
end: Optional[str] = None, ) -> List[PulseMessage]:
limit: Optional[int] = None) -> List[PulseMessage]:
messages = [] 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] sub_data[18] = sub_data[18][0]
message = serializers.PulseMessage.parse(*sub_data) message = serializers.PulseMessage.parse(*sub_data)
messages.append(message) messages.append(message)
return messages return messages
def get_trading_market_average_price(self, def get_trading_market_average_price(
symbol: str, self,
amount: Union[str, float, Decimal], symbol: str,
*, amount: Union[str, float, Decimal],
price_limit: Optional[Union[str, float, Decimal]] = None *,
) -> TradingMarketAveragePrice: price_limit: Optional[Union[str, float, Decimal]] = None,
return serializers.TradingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={ ) -> TradingMarketAveragePrice:
"symbol": symbol, "amount": amount, "price_limit": price_limit 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, def get_funding_market_average_price(
symbol: str, self,
amount: Union[str, float, Decimal], symbol: str,
period: int, amount: Union[str, float, Decimal],
*, period: int,
rate_limit: Optional[Union[str, float, Decimal]] = None *,
) -> FundingMarketAveragePrice: rate_limit: Optional[Union[str, float, Decimal]] = None,
return serializers.FundingMarketAveragePrice.parse(*self._post("calc/trade/avg", body={ ) -> FundingMarketAveragePrice:
"symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit 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: 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})
)

View File

@@ -4,8 +4,10 @@ from bfxapi.exceptions import BfxBaseException
class NotFoundError(BfxBaseException): class NotFoundError(BfxBaseException):
pass pass
class RequestParametersError(BfxBaseException): class RequestParametersError(BfxBaseException):
pass pass
class UnknownGenericError(BfxBaseException): class UnknownGenericError(BfxBaseException):
pass pass

View File

@@ -16,45 +16,47 @@ from ..exceptions import NotFoundError, RequestParametersError, UnknownGenericEr
if TYPE_CHECKING: if TYPE_CHECKING:
from requests.sessions import _Params from requests.sessions import _Params
class _Error(IntEnum): class _Error(IntEnum):
ERR_UNK = 10000 ERR_UNK = 10000
ERR_GENERIC = 10001 ERR_GENERIC = 10001
ERR_PARAMS = 10020 ERR_PARAMS = 10020
ERR_AUTH_FAIL = 10100 ERR_AUTH_FAIL = 10100
class Middleware: class Middleware:
TIMEOUT = 30 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 self.host, self.api_key, self.api_secret = host, api_key, api_secret
def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None): def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
assert isinstance(self.api_key, str) and isinstance(self.api_secret, str), \ assert isinstance(self.api_key, str) and isinstance(
"API_KEY and API_SECRET must be both str to call __build_authentication_headers" 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)) nonce = str(round(time.time() * 1_000_000))
if data is None: if data is None:
path = f"/api/v2/{endpoint}{nonce}" 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( signature = hmac.new(
self.api_secret.encode("utf8"), self.api_secret.encode("utf8"), path.encode("utf8"), hashlib.sha384
path.encode("utf8"),
hashlib.sha384
).hexdigest() ).hexdigest()
return { return {
"bfx-nonce": nonce, "bfx-nonce": nonce,
"bfx-signature": signature, "bfx-signature": signature,
"bfx-apikey": self.api_key "bfx-apikey": self.api_key,
} }
def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any: def _get(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
response = requests.get( response = requests.get(
url=f"{self.host}/{endpoint}", url=f"{self.host}/{endpoint}", params=params, timeout=Middleware.TIMEOUT
params=params,
timeout=Middleware.TIMEOUT
) )
if response.status_code == HTTPStatus.NOT_FOUND: if response.status_code == HTTPStatus.NOT_FOUND:
@@ -64,30 +66,43 @@ class Middleware:
if len(data) and data[0] == "error": if len(data) and data[0] == "error":
if data[1] == _Error.ERR_PARAMS: if data[1] == _Error.ERR_PARAMS:
raise RequestParametersError("The request was rejected with the " \ raise RequestParametersError(
f"following parameter error: <{data[2]}>") "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: if (
raise UnknownGenericError("The server replied to the request with " \ data[1] is None
f"a generic error with message: <{data[2]}>.") 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 return data
def _post(self, endpoint: str, params: Optional["_Params"] = None, def _post(
body: Optional[Any] = None, _ignore_authentication_headers: bool = False) -> Any: 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 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: 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( response = requests.post(
url=f"{self.host}/{endpoint}", url=f"{self.host}/{endpoint}",
params=params, params=params,
data=data, data=data,
headers=headers, headers=headers,
timeout=Middleware.TIMEOUT timeout=Middleware.TIMEOUT,
) )
if response.status_code == HTTPStatus.NOT_FOUND: 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 isinstance(data, list) and len(data) and data[0] == "error":
if data[1] == _Error.ERR_PARAMS: if data[1] == _Error.ERR_PARAMS:
raise RequestParametersError("The request was rejected with the " \ raise RequestParametersError(
f"following parameter error: <{data[2]}>") "The request was rejected with the "
f"following parameter error: <{data[2]}>"
)
if data[1] == _Error.ERR_AUTH_FAIL: 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: if (
raise UnknownGenericError("The server replied to the request with " \ data[1] is None
f"a generic error with message: <{data[2]}>.") 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 return data

View File

@@ -3,12 +3,14 @@ from typing import Any, Dict, List, Literal, Optional
from .labeler import _Type, compose, partial from .labeler import _Type, compose, partial
#region Dataclass definitions for types of public use # region Dataclass definitions for types of public use
@dataclass @dataclass
class PlatformStatus(_Type): class PlatformStatus(_Type):
status: int status: int
@dataclass @dataclass
class TradingPairTicker(_Type): class TradingPairTicker(_Type):
bid: float bid: float
@@ -22,6 +24,7 @@ class TradingPairTicker(_Type):
high: float high: float
low: float low: float
@dataclass @dataclass
class FundingCurrencyTicker(_Type): class FundingCurrencyTicker(_Type):
frr: float frr: float
@@ -39,6 +42,7 @@ class FundingCurrencyTicker(_Type):
low: float low: float
frr_amount_available: float frr_amount_available: float
@dataclass @dataclass
class TickersHistory(_Type): class TickersHistory(_Type):
symbol: str symbol: str
@@ -46,6 +50,7 @@ class TickersHistory(_Type):
ask: float ask: float
mts: int mts: int
@dataclass @dataclass
class TradingPairTrade(_Type): class TradingPairTrade(_Type):
id: int id: int
@@ -53,6 +58,7 @@ class TradingPairTrade(_Type):
amount: float amount: float
price: float price: float
@dataclass @dataclass
class FundingCurrencyTrade(_Type): class FundingCurrencyTrade(_Type):
id: int id: int
@@ -61,12 +67,14 @@ class FundingCurrencyTrade(_Type):
rate: float rate: float
period: int period: int
@dataclass @dataclass
class TradingPairBook(_Type): class TradingPairBook(_Type):
price: float price: float
count: int count: int
amount: float amount: float
@dataclass @dataclass
class FundingCurrencyBook(_Type): class FundingCurrencyBook(_Type):
rate: float rate: float
@@ -74,12 +82,14 @@ class FundingCurrencyBook(_Type):
count: int count: int
amount: float amount: float
@dataclass @dataclass
class TradingPairRawBook(_Type): class TradingPairRawBook(_Type):
order_id: int order_id: int
price: float price: float
amount: float amount: float
@dataclass @dataclass
class FundingCurrencyRawBook(_Type): class FundingCurrencyRawBook(_Type):
offer_id: int offer_id: int
@@ -87,11 +97,13 @@ class FundingCurrencyRawBook(_Type):
rate: float rate: float
amount: float amount: float
@dataclass @dataclass
class Statistic(_Type): class Statistic(_Type):
mts: int mts: int
value: float value: float
@dataclass @dataclass
class Candle(_Type): class Candle(_Type):
mts: int mts: int
@@ -101,6 +113,7 @@ class Candle(_Type):
low: int low: int
volume: float volume: float
@dataclass @dataclass
class DerivativesStatus(_Type): class DerivativesStatus(_Type):
mts: int mts: int
@@ -116,6 +129,7 @@ class DerivativesStatus(_Type):
clamp_min: float clamp_min: float
clamp_max: float clamp_max: float
@dataclass @dataclass
class Liquidation(_Type): class Liquidation(_Type):
pos_id: int pos_id: int
@@ -127,6 +141,7 @@ class Liquidation(_Type):
is_market_sold: int is_market_sold: int
liquidation_price: float liquidation_price: float
@dataclass @dataclass
class Leaderboard(_Type): class Leaderboard(_Type):
mts: int mts: int
@@ -135,6 +150,7 @@ class Leaderboard(_Type):
value: float value: float
twitter_handle: Optional[str] twitter_handle: Optional[str]
@dataclass @dataclass
class FundingStatistic(_Type): class FundingStatistic(_Type):
mts: int mts: int
@@ -144,6 +160,7 @@ class FundingStatistic(_Type):
funding_amount_used: float funding_amount_used: float
funding_below_threshold: float funding_below_threshold: float
@dataclass @dataclass
class PulseProfile(_Type): class PulseProfile(_Type):
puid: str puid: str
@@ -156,6 +173,7 @@ class PulseProfile(_Type):
following: int following: int
tipping_status: int tipping_status: int
@dataclass @dataclass
class PulseMessage(_Type): class PulseMessage(_Type):
pid: str pid: str
@@ -173,23 +191,28 @@ class PulseMessage(_Type):
profile: PulseProfile profile: PulseProfile
comments: int comments: int
@dataclass @dataclass
class TradingMarketAveragePrice(_Type): class TradingMarketAveragePrice(_Type):
price_avg: float price_avg: float
amount: float amount: float
@dataclass @dataclass
class FundingMarketAveragePrice(_Type): class FundingMarketAveragePrice(_Type):
rate_avg: float rate_avg: float
amount: float amount: float
@dataclass @dataclass
class FxRate(_Type): class FxRate(_Type):
current_rate: float current_rate: float
#endregion
#region Dataclass definitions for types of auth use # endregion
# region Dataclass definitions for types of auth use
@dataclass @dataclass
class UserInfo(_Type): class UserInfo(_Type):
@@ -222,6 +245,7 @@ class UserInfo(_Type):
compl_countries_resid: List[str] compl_countries_resid: List[str]
is_merchant_enterprise: int is_merchant_enterprise: int
@dataclass @dataclass
class LoginHistory(_Type): class LoginHistory(_Type):
id: int id: int
@@ -229,10 +253,12 @@ class LoginHistory(_Type):
ip: str ip: str
extra_info: Dict[str, Any] extra_info: Dict[str, Any]
@dataclass @dataclass
class BalanceAvailable(_Type): class BalanceAvailable(_Type):
amount: float amount: float
@dataclass @dataclass
class Order(_Type): class Order(_Type):
id: int id: int
@@ -258,6 +284,7 @@ class Order(_Type):
routing: str routing: str
meta: Dict[str, Any] meta: Dict[str, Any]
@dataclass @dataclass
class Position(_Type): class Position(_Type):
symbol: str symbol: str
@@ -278,6 +305,7 @@ class Position(_Type):
collateral_min: float collateral_min: float
meta: Dict[str, Any] meta: Dict[str, Any]
@dataclass @dataclass
class Trade(_Type): class Trade(_Type):
id: int id: int
@@ -288,11 +316,12 @@ class Trade(_Type):
exec_price: float exec_price: float
order_type: str order_type: str
order_price: float order_price: float
maker:int maker: int
fee: float fee: float
fee_currency: str fee_currency: str
cid: int cid: int
@dataclass() @dataclass()
class FundingTrade(_Type): class FundingTrade(_Type):
id: int id: int
@@ -303,6 +332,7 @@ class FundingTrade(_Type):
rate: float rate: float
period: int period: int
@dataclass @dataclass
class OrderTrade(_Type): class OrderTrade(_Type):
id: int id: int
@@ -311,11 +341,12 @@ class OrderTrade(_Type):
order_id: int order_id: int
exec_amount: float exec_amount: float
exec_price: float exec_price: float
maker:int maker: int
fee: float fee: float
fee_currency: str fee_currency: str
cid: int cid: int
@dataclass @dataclass
class Ledger(_Type): class Ledger(_Type):
id: int id: int
@@ -325,6 +356,7 @@ class Ledger(_Type):
balance: float balance: float
description: str description: str
@dataclass @dataclass
class FundingOffer(_Type): class FundingOffer(_Type):
id: int id: int
@@ -342,6 +374,7 @@ class FundingOffer(_Type):
hidden: int hidden: int
renew: int renew: int
@dataclass @dataclass
class FundingCredit(_Type): class FundingCredit(_Type):
id: int id: int
@@ -363,6 +396,7 @@ class FundingCredit(_Type):
no_close: int no_close: int
position_pair: str position_pair: str
@dataclass @dataclass
class FundingLoan(_Type): class FundingLoan(_Type):
id: int id: int
@@ -383,6 +417,7 @@ class FundingLoan(_Type):
renew: int renew: int
no_close: int no_close: int
@dataclass @dataclass
class FundingAutoRenew(_Type): class FundingAutoRenew(_Type):
currency: str currency: str
@@ -390,6 +425,7 @@ class FundingAutoRenew(_Type):
rate: float rate: float
threshold: float threshold: float
@dataclass() @dataclass()
class FundingInfo(_Type): class FundingInfo(_Type):
yield_loan: float yield_loan: float
@@ -397,6 +433,7 @@ class FundingInfo(_Type):
duration_loan: float duration_loan: float
duration_lend: float duration_lend: float
@dataclass @dataclass
class Wallet(_Type): class Wallet(_Type):
wallet_type: str wallet_type: str
@@ -407,6 +444,7 @@ class Wallet(_Type):
last_change: str last_change: str
trade_details: Dict[str, Any] trade_details: Dict[str, Any]
@dataclass @dataclass
class Transfer(_Type): class Transfer(_Type):
mts: int mts: int
@@ -416,6 +454,7 @@ class Transfer(_Type):
currency_to: str currency_to: str
amount: int amount: int
@dataclass @dataclass
class Withdrawal(_Type): class Withdrawal(_Type):
withdrawal_id: int withdrawal_id: int
@@ -425,6 +464,7 @@ class Withdrawal(_Type):
amount: float amount: float
withdrawal_fee: float withdrawal_fee: float
@dataclass @dataclass
class DepositAddress(_Type): class DepositAddress(_Type):
method: str method: str
@@ -432,12 +472,14 @@ class DepositAddress(_Type):
address: str address: str
pool_address: str pool_address: str
@dataclass @dataclass
class LightningNetworkInvoice(_Type): class LightningNetworkInvoice(_Type):
invoice_hash: str invoice_hash: str
invoice: str invoice: str
amount: str amount: str
@dataclass @dataclass
class Movement(_Type): class Movement(_Type):
id: str id: str
@@ -452,6 +494,7 @@ class Movement(_Type):
transaction_id: str transaction_id: str
withdraw_transaction_note: str withdraw_transaction_note: str
@dataclass @dataclass
class SymbolMarginInfo(_Type): class SymbolMarginInfo(_Type):
symbol: str symbol: str
@@ -460,6 +503,7 @@ class SymbolMarginInfo(_Type):
buy: float buy: float
sell: float sell: float
@dataclass @dataclass
class BaseMarginInfo(_Type): class BaseMarginInfo(_Type):
user_pl: float user_pl: float
@@ -468,6 +512,7 @@ class BaseMarginInfo(_Type):
margin_net: float margin_net: float
margin_min: float margin_min: float
@dataclass @dataclass
class PositionClaim(_Type): class PositionClaim(_Type):
symbol: str symbol: str
@@ -484,6 +529,7 @@ class PositionClaim(_Type):
min_collateral: str min_collateral: str
meta: Dict[str, Any] meta: Dict[str, Any]
@dataclass @dataclass
class PositionIncreaseInfo(_Type): class PositionIncreaseInfo(_Type):
max_pos: int max_pos: int
@@ -499,12 +545,14 @@ class PositionIncreaseInfo(_Type):
funding_value_currency: str funding_value_currency: str
funding_required_currency: str funding_required_currency: str
@dataclass @dataclass
class PositionIncrease(_Type): class PositionIncrease(_Type):
symbol: str symbol: str
amount: float amount: float
base_price: float base_price: float
@dataclass @dataclass
class PositionHistory(_Type): class PositionHistory(_Type):
symbol: str symbol: str
@@ -517,6 +565,7 @@ class PositionHistory(_Type):
mts_create: int mts_create: int
mts_update: int mts_update: int
@dataclass @dataclass
class PositionSnapshot(_Type): class PositionSnapshot(_Type):
symbol: str symbol: str
@@ -529,6 +578,7 @@ class PositionSnapshot(_Type):
mts_create: int mts_create: int
mts_update: int mts_update: int
@dataclass @dataclass
class PositionAudit(_Type): class PositionAudit(_Type):
symbol: str symbol: str
@@ -545,18 +595,22 @@ class PositionAudit(_Type):
collateral_min: float collateral_min: float
meta: Dict[str, Any] meta: Dict[str, Any]
@dataclass @dataclass
class DerivativePositionCollateral(_Type): class DerivativePositionCollateral(_Type):
status: int status: int
@dataclass @dataclass
class DerivativePositionCollateralLimits(_Type): class DerivativePositionCollateralLimits(_Type):
min_collateral: float min_collateral: float
max_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) @compose(dataclass, partial)
class InvoiceSubmission(_Type): class InvoiceSubmission(_Type):
@@ -580,7 +634,9 @@ class InvoiceSubmission(_Type):
@classmethod @classmethod
def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission": def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission":
if "customer_info" in data and data["customer_info"] is not None: 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"]): for index, invoice in enumerate(data["invoices"]):
data["invoices"][index] = InvoiceSubmission.Invoice(**invoice) 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: if "additional_payments" in data and data["additional_payments"] is not None:
for index, additional_payment in enumerate(data["additional_payments"]): 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) return InvoiceSubmission(**data)
@@ -631,6 +689,7 @@ class InvoiceSubmission(_Type):
force_completed: bool force_completed: bool
amount_diff: str amount_diff: str
@dataclass @dataclass
class InvoicePage(_Type): class InvoicePage(_Type):
page: int page: int
@@ -648,17 +707,20 @@ class InvoicePage(_Type):
return InvoicePage(**data) return InvoicePage(**data)
@dataclass @dataclass
class InvoiceStats(_Type): class InvoiceStats(_Type):
time: str time: str
count: float count: float
@dataclass @dataclass
class CurrencyConversion(_Type): class CurrencyConversion(_Type):
base_ccy: str base_ccy: str
convert_ccy: str convert_ccy: str
created: int created: int
@dataclass @dataclass
class MerchantDeposit(_Type): class MerchantDeposit(_Type):
id: int id: int
@@ -672,6 +734,7 @@ class MerchantDeposit(_Type):
method: str method: str
pay_method: str pay_method: str
@dataclass @dataclass
class MerchantUnlinkedDeposit(_Type): class MerchantUnlinkedDeposit(_Type):
id: int id: int
@@ -687,4 +750,5 @@ class MerchantUnlinkedDeposit(_Type):
status: str status: str
note: Optional[str] note: Optional[str]
#endregion
# endregion

View File

@@ -2,6 +2,7 @@ from typing import Any, Dict, Generic, Iterable, List, Tuple, Type, TypeVar, cas
T = TypeVar("T", bound="_Type") T = TypeVar("T", bound="_Type")
def compose(*decorators): def compose(*decorators):
def wrapper(function): def wrapper(function):
for decorator in reversed(decorators): for decorator in reversed(decorators):
@@ -10,30 +11,37 @@ def compose(*decorators):
return wrapper return wrapper
def partial(cls): def partial(cls):
def __init__(self, **kwargs): def __init__(self, **kwargs):
for annotation in self.__annotations__.keys(): for annotation in self.__annotations__.keys():
if annotation not in kwargs: if annotation not in kwargs:
self.__setattr__(annotation, None) self.__setattr__(annotation, None)
else: self.__setattr__(annotation, kwargs[annotation]) else:
self.__setattr__(annotation, kwargs[annotation])
kwargs.pop(annotation, None) kwargs.pop(annotation, None)
if len(kwargs) != 0: 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__ cls.__init__ = __init__
return cls return cls
class _Type: class _Type:
""" """
Base class for any dataclass serializable by the _Serializer generic class. Base class for any dataclass serializable by the _Serializer generic class.
""" """
class _Serializer(Generic[T]): class _Serializer(Generic[T]):
def __init__(self, name: str, klass: Type[_Type], labels: List[str], def __init__(
*, flat: bool = False): self, name: str, klass: Type[_Type], labels: List[str], *, flat: bool = False
):
self.name, self.klass, self.__labels, self.__flat = name, klass, labels, flat self.name, self.klass, self.__labels, self.__flat = name, klass, labels, flat
def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]: def _serialize(self, *args: Any) -> Iterable[Tuple[str, Any]]:
@@ -41,8 +49,10 @@ class _Serializer(Generic[T]):
args = tuple(_Serializer.__flatten(list(args))) args = tuple(_Serializer.__flatten(list(args)))
if len(self.__labels) > len(args): if len(self.__labels) > len(args):
raise AssertionError(f"{self.name} -> <labels> and <*args> " \ raise AssertionError(
"arguments should contain the same amount of elements.") f"{self.name} -> <labels> and <*args> "
"arguments should contain the same amount of elements."
)
for index, label in enumerate(self.__labels): for index, label in enumerate(self.__labels):
if label != "_PLACEHOLDER": if label != "_PLACEHOLDER":
@@ -52,7 +62,7 @@ class _Serializer(Generic[T]):
return cast(T, self.klass(**dict(self._serialize(*values)))) return cast(T, self.klass(**dict(self._serialize(*values))))
def get_labels(self) -> List[str]: 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 @classmethod
def __flatten(cls, array: List[Any]) -> List[Any]: def __flatten(cls, array: List[Any]) -> List[Any]:
@@ -64,10 +74,17 @@ class _Serializer(Generic[T]):
return array[:1] + cls.__flatten(array[1:]) return array[:1] + cls.__flatten(array[1:])
class _RecursiveSerializer(_Serializer, Generic[T]): class _RecursiveSerializer(_Serializer, Generic[T]):
def __init__(self, name: str, klass: Type[_Type], labels: List[str], def __init__(
*, serializers: Dict[str, _Serializer[Any]], self,
flat: bool = False): name: str,
klass: Type[_Type],
labels: List[str],
*,
serializers: Dict[str, _Serializer[Any]],
flat: bool = False,
):
super().__init__(name, klass, labels, flat=flat) super().__init__(name, klass, labels, flat=flat)
self.serializers = serializers self.serializers = serializers
@@ -81,15 +98,21 @@ class _RecursiveSerializer(_Serializer, Generic[T]):
return cast(T, self.klass(**serialization)) 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], def generate_labeler_serializer(
*, serializers: Dict[str, _Serializer[Any]], name: str, klass: Type[T], labels: List[str], *, flat: bool = False
flat: bool = False ) -> _Serializer[T]:
) -> _RecursiveSerializer[T]: return _Serializer[T](name, klass, labels, flat=flat)
return _RecursiveSerializer[T](name, klass, labels, \
serializers=serializers, 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
)

View File

@@ -5,6 +5,7 @@ from .labeler import _Serializer, _Type
T = TypeVar("T") T = TypeVar("T")
@dataclass @dataclass
class Notification(_Type, Generic[T]): class Notification(_Type, Generic[T]):
mts: int mts: int
@@ -15,16 +16,30 @@ class Notification(_Type, Generic[T]):
status: str status: str
text: 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) super().__init__("Notification", Notification, _Notification.__LABELS)
self.serializer, self.is_iterable = serializer, is_iterable self.serializer, self.is_iterable = serializer, is_iterable
def parse(self, *values: Any) -> Notification[T]: 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): if isinstance(self.serializer, _Serializer):
data = cast(List[Any], notification.data) data = cast(List[Any], notification.data)
@@ -34,6 +49,9 @@ class _Notification(_Serializer, Generic[T]):
data = data[0] data = data[0]
notification.data = self.serializer.parse(*data) 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 return notification

View File

@@ -1,44 +1,73 @@
from . import dataclasses from . import dataclasses
#pylint: disable-next=unused-import # pylint: disable-next=unused-import
from .labeler import ( from .labeler import (
_Serializer, _Serializer,
generate_labeler_serializer, generate_labeler_serializer,
generate_recursive_serializer, generate_recursive_serializer,
) )
#pylint: disable-next=unused-import # pylint: disable-next=unused-import
from .notification import _Notification from .notification import _Notification
__serializers__ = [ __serializers__ = [
"PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker", "PlatformStatus",
"TickersHistory", "TradingPairTrade", "FundingCurrencyTrade", "TradingPairTicker",
"TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook", "FundingCurrencyTicker",
"FundingCurrencyRawBook", "Statistic", "Candle", "TickersHistory",
"DerivativesStatus", "Liquidation", "Leaderboard", "TradingPairTrade",
"FundingStatistic", "PulseProfile", "PulseMessage", "FundingCurrencyTrade",
"TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate", "TradingPairBook",
"FundingCurrencyBook",
"UserInfo", "LoginHistory", "BalanceAvailable", "TradingPairRawBook",
"Order", "Position", "Trade", "FundingCurrencyRawBook",
"FundingTrade", "OrderTrade", "Ledger", "Statistic",
"FundingOffer", "FundingCredit", "FundingLoan", "Candle",
"FundingAutoRenew", "FundingInfo", "Wallet", "DerivativesStatus",
"Transfer", "Withdrawal", "DepositAddress", "Liquidation",
"LightningNetworkInvoice", "Movement", "SymbolMarginInfo", "Leaderboard",
"BaseMarginInfo", "PositionClaim", "PositionIncreaseInfo", "FundingStatistic",
"PositionIncrease", "PositionHistory", "PositionSnapshot", "PulseProfile",
"PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits", "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( PlatformStatus = generate_labeler_serializer(
name="PlatformStatus", name="PlatformStatus", klass=dataclasses.PlatformStatus, labels=["status"]
klass=dataclasses.PlatformStatus,
labels=[
"status"
]
) )
TradingPairTicker = generate_labeler_serializer( TradingPairTicker = generate_labeler_serializer(
@@ -54,8 +83,8 @@ TradingPairTicker = generate_labeler_serializer(
"last_price", "last_price",
"volume", "volume",
"high", "high",
"low" "low",
] ],
) )
FundingCurrencyTicker = generate_labeler_serializer( FundingCurrencyTicker = generate_labeler_serializer(
@@ -77,8 +106,8 @@ FundingCurrencyTicker = generate_labeler_serializer(
"low", "low",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"frr_amount_available" "frr_amount_available",
] ],
) )
TickersHistory = generate_labeler_serializer( TickersHistory = generate_labeler_serializer(
@@ -97,95 +126,54 @@ TickersHistory = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"mts" "mts",
] ],
) )
TradingPairTrade = generate_labeler_serializer( TradingPairTrade = generate_labeler_serializer(
name="TradingPairTrade", name="TradingPairTrade",
klass=dataclasses.TradingPairTrade, klass=dataclasses.TradingPairTrade,
labels=[ labels=["id", "mts", "amount", "price"],
"id",
"mts",
"amount",
"price"
]
) )
FundingCurrencyTrade = generate_labeler_serializer( FundingCurrencyTrade = generate_labeler_serializer(
name="FundingCurrencyTrade", name="FundingCurrencyTrade",
klass=dataclasses.FundingCurrencyTrade, klass=dataclasses.FundingCurrencyTrade,
labels=[ labels=["id", "mts", "amount", "rate", "period"],
"id",
"mts",
"amount",
"rate",
"period"
]
) )
TradingPairBook = generate_labeler_serializer( TradingPairBook = generate_labeler_serializer(
name="TradingPairBook", name="TradingPairBook",
klass=dataclasses.TradingPairBook, klass=dataclasses.TradingPairBook,
labels=[ labels=["price", "count", "amount"],
"price",
"count",
"amount"
]
) )
FundingCurrencyBook = generate_labeler_serializer( FundingCurrencyBook = generate_labeler_serializer(
name="FundingCurrencyBook", name="FundingCurrencyBook",
klass=dataclasses.FundingCurrencyBook, klass=dataclasses.FundingCurrencyBook,
labels=[ labels=["rate", "period", "count", "amount"],
"rate",
"period",
"count",
"amount"
]
) )
TradingPairRawBook = generate_labeler_serializer( TradingPairRawBook = generate_labeler_serializer(
name="TradingPairRawBook", name="TradingPairRawBook",
klass=dataclasses.TradingPairRawBook, klass=dataclasses.TradingPairRawBook,
labels=[ labels=["order_id", "price", "amount"],
"order_id",
"price",
"amount"
]
) )
FundingCurrencyRawBook = generate_labeler_serializer( FundingCurrencyRawBook = generate_labeler_serializer(
name="FundingCurrencyRawBook", name="FundingCurrencyRawBook",
klass=dataclasses.FundingCurrencyRawBook, klass=dataclasses.FundingCurrencyRawBook,
labels=[ labels=["offer_id", "period", "rate", "amount"],
"offer_id",
"period",
"rate",
"amount"
]
) )
Statistic = generate_labeler_serializer( Statistic = generate_labeler_serializer(
name="Statistic", name="Statistic", klass=dataclasses.Statistic, labels=["mts", "value"]
klass=dataclasses.Statistic,
labels=[
"mts",
"value"
]
) )
Candle = generate_labeler_serializer( Candle = generate_labeler_serializer(
name="Candle", name="Candle",
klass=dataclasses.Candle, klass=dataclasses.Candle,
labels=[ labels=["mts", "open", "close", "high", "low", "volume"],
"mts",
"open",
"close",
"high",
"low",
"volume"
]
) )
DerivativesStatus = generate_labeler_serializer( DerivativesStatus = generate_labeler_serializer(
@@ -193,7 +181,7 @@ DerivativesStatus = generate_labeler_serializer(
klass=dataclasses.DerivativesStatus, klass=dataclasses.DerivativesStatus,
labels=[ labels=[
"mts", "mts",
"_PLACEHOLDER", "_PLACEHOLDER",
"deriv_price", "deriv_price",
"spot_price", "spot_price",
"_PLACEHOLDER", "_PLACEHOLDER",
@@ -214,8 +202,8 @@ DerivativesStatus = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"clamp_min", "clamp_min",
"clamp_max" "clamp_max",
] ],
) )
Liquidation = generate_labeler_serializer( Liquidation = generate_labeler_serializer(
@@ -233,8 +221,8 @@ Liquidation = generate_labeler_serializer(
"is_match", "is_match",
"is_market_sold", "is_market_sold",
"_PLACEHOLDER", "_PLACEHOLDER",
"liquidation_price" "liquidation_price",
] ],
) )
Leaderboard = generate_labeler_serializer( Leaderboard = generate_labeler_serializer(
@@ -250,8 +238,8 @@ Leaderboard = generate_labeler_serializer(
"value", "value",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"twitter_handle" "twitter_handle",
] ],
) )
FundingStatistic = generate_labeler_serializer( FundingStatistic = generate_labeler_serializer(
@@ -269,8 +257,8 @@ FundingStatistic = generate_labeler_serializer(
"funding_amount_used", "funding_amount_used",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"funding_below_threshold" "funding_below_threshold",
] ],
) )
PulseProfile = generate_labeler_serializer( PulseProfile = generate_labeler_serializer(
@@ -293,14 +281,14 @@ PulseProfile = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"tipping_status" "tipping_status",
] ],
) )
PulseMessage = generate_recursive_serializer( PulseMessage = generate_recursive_serializer(
name="PulseMessage", name="PulseMessage",
klass=dataclasses.PulseMessage, klass=dataclasses.PulseMessage,
serializers={ "profile": PulseProfile }, serializers={"profile": PulseProfile},
labels=[ labels=[
"pid", "pid",
"mts", "mts",
@@ -314,7 +302,7 @@ PulseMessage = generate_recursive_serializer(
"is_pin", "is_pin",
"is_public", "is_public",
"comments_disabled", "comments_disabled",
"tags", "tags",
"attachments", "attachments",
"meta", "meta",
"likes", "likes",
@@ -323,39 +311,29 @@ PulseMessage = generate_recursive_serializer(
"profile", "profile",
"comments", "comments",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER" "_PLACEHOLDER",
] ],
) )
TradingMarketAveragePrice = generate_labeler_serializer( TradingMarketAveragePrice = generate_labeler_serializer(
name="TradingMarketAveragePrice", name="TradingMarketAveragePrice",
klass=dataclasses.TradingMarketAveragePrice, klass=dataclasses.TradingMarketAveragePrice,
labels=[ labels=["price_avg", "amount"],
"price_avg",
"amount"
]
) )
FundingMarketAveragePrice = generate_labeler_serializer( FundingMarketAveragePrice = generate_labeler_serializer(
name="FundingMarketAveragePrice", name="FundingMarketAveragePrice",
klass=dataclasses.FundingMarketAveragePrice, klass=dataclasses.FundingMarketAveragePrice,
labels=[ labels=["rate_avg", "amount"],
"rate_avg",
"amount"
]
) )
FxRate = generate_labeler_serializer( FxRate = generate_labeler_serializer(
name="FxRate", name="FxRate", klass=dataclasses.FxRate, labels=["current_rate"]
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( UserInfo = generate_labeler_serializer(
name="UserInfo", name="UserInfo",
@@ -415,8 +393,8 @@ UserInfo = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"is_merchant_enterprise" "is_merchant_enterprise",
] ],
) )
LoginHistory = generate_labeler_serializer( LoginHistory = generate_labeler_serializer(
@@ -430,16 +408,12 @@ LoginHistory = generate_labeler_serializer(
"ip", "ip",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"extra_info" "extra_info",
] ],
) )
BalanceAvailable = generate_labeler_serializer( BalanceAvailable = generate_labeler_serializer(
name="BalanceAvailable", name="BalanceAvailable", klass=dataclasses.BalanceAvailable, labels=["amount"]
klass=dataclasses.BalanceAvailable,
labels=[
"amount"
]
) )
Order = generate_labeler_serializer( Order = generate_labeler_serializer(
@@ -450,10 +424,10 @@ Order = generate_labeler_serializer(
"gid", "gid",
"cid", "cid",
"symbol", "symbol",
"mts_create", "mts_create",
"mts_update", "mts_update",
"amount", "amount",
"amount_orig", "amount_orig",
"order_type", "order_type",
"type_prev", "type_prev",
"mts_tif", "mts_tif",
@@ -470,26 +444,26 @@ Order = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"notify", "notify",
"hidden", "hidden",
"placed_id", "placed_id",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"routing", "routing",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"meta" "meta",
] ],
) )
Position = generate_labeler_serializer( Position = generate_labeler_serializer(
name="Position", name="Position",
klass=dataclasses.Position, klass=dataclasses.Position,
labels=[ labels=[
"symbol", "symbol",
"status", "status",
"amount", "amount",
"base_price", "base_price",
"margin_funding", "margin_funding",
"margin_funding_type", "margin_funding_type",
"pl", "pl",
"pl_perc", "pl_perc",
@@ -504,41 +478,33 @@ Position = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"collateral", "collateral",
"collateral_min", "collateral_min",
"meta" "meta",
] ],
) )
Trade = generate_labeler_serializer( Trade = generate_labeler_serializer(
name="Trade", name="Trade",
klass=dataclasses.Trade, klass=dataclasses.Trade,
labels=[ labels=[
"id", "id",
"symbol", "symbol",
"mts_create", "mts_create",
"order_id", "order_id",
"exec_amount", "exec_amount",
"exec_price", "exec_price",
"order_type", "order_type",
"order_price", "order_price",
"maker", "maker",
"fee", "fee",
"fee_currency", "fee_currency",
"cid" "cid",
] ],
) )
FundingTrade = generate_labeler_serializer( FundingTrade = generate_labeler_serializer(
name="FundingTrade", name="FundingTrade",
klass=dataclasses.FundingTrade, klass=dataclasses.FundingTrade,
labels=[ labels=["id", "currency", "mts_create", "offer_id", "amount", "rate", "period"],
"id",
"currency",
"mts_create",
"offer_id",
"amount",
"rate",
"period"
]
) )
OrderTrade = generate_labeler_serializer( OrderTrade = generate_labeler_serializer(
@@ -556,8 +522,8 @@ OrderTrade = generate_labeler_serializer(
"maker", "maker",
"fee", "fee",
"fee_currency", "fee_currency",
"cid" "cid",
] ],
) )
Ledger = generate_labeler_serializer( Ledger = generate_labeler_serializer(
@@ -572,8 +538,8 @@ Ledger = generate_labeler_serializer(
"amount", "amount",
"balance", "balance",
"_PLACEHOLDER", "_PLACEHOLDER",
"description" "description",
] ],
) )
FundingOffer = generate_labeler_serializer( FundingOffer = generate_labeler_serializer(
@@ -600,8 +566,8 @@ FundingOffer = generate_labeler_serializer(
"hidden", "hidden",
"_PLACEHOLDER", "_PLACEHOLDER",
"renew", "renew",
"_PLACEHOLDER" "_PLACEHOLDER",
] ],
) )
FundingCredit = generate_labeler_serializer( FundingCredit = generate_labeler_serializer(
@@ -629,8 +595,8 @@ FundingCredit = generate_labeler_serializer(
"renew", "renew",
"_PLACEHOLDER", "_PLACEHOLDER",
"no_close", "no_close",
"position_pair" "position_pair",
] ],
) )
FundingLoan = generate_labeler_serializer( FundingLoan = generate_labeler_serializer(
@@ -657,44 +623,34 @@ FundingLoan = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"renew", "renew",
"_PLACEHOLDER", "_PLACEHOLDER",
"no_close" "no_close",
] ],
) )
FundingAutoRenew = generate_labeler_serializer( FundingAutoRenew = generate_labeler_serializer(
name="FundingAutoRenew", name="FundingAutoRenew",
klass=dataclasses.FundingAutoRenew, klass=dataclasses.FundingAutoRenew,
labels=[ labels=["currency", "period", "rate", "threshold"],
"currency",
"period",
"rate",
"threshold"
]
) )
FundingInfo = generate_labeler_serializer( FundingInfo = generate_labeler_serializer(
name="FundingInfo", name="FundingInfo",
klass=dataclasses.FundingInfo, klass=dataclasses.FundingInfo,
labels=[ labels=["yield_loan", "yield_lend", "duration_loan", "duration_lend"],
"yield_loan",
"yield_lend",
"duration_loan",
"duration_lend"
]
) )
Wallet = generate_labeler_serializer( Wallet = generate_labeler_serializer(
name="Wallet", name="Wallet",
klass=dataclasses.Wallet, klass=dataclasses.Wallet,
labels=[ labels=[
"wallet_type", "wallet_type",
"currency", "currency",
"balance", "balance",
"unsettled_interest", "unsettled_interest",
"available_balance", "available_balance",
"last_change", "last_change",
"trade_details" "trade_details",
] ],
) )
Transfer = generate_labeler_serializer( Transfer = generate_labeler_serializer(
@@ -708,8 +664,8 @@ Transfer = generate_labeler_serializer(
"currency", "currency",
"currency_to", "currency_to",
"_PLACEHOLDER", "_PLACEHOLDER",
"amount" "amount",
] ],
) )
Withdrawal = generate_labeler_serializer( Withdrawal = generate_labeler_serializer(
@@ -724,8 +680,8 @@ Withdrawal = generate_labeler_serializer(
"amount", "amount",
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"withdrawal_fee" "withdrawal_fee",
] ],
) )
DepositAddress = generate_labeler_serializer( DepositAddress = generate_labeler_serializer(
@@ -737,20 +693,14 @@ DepositAddress = generate_labeler_serializer(
"currency_code", "currency_code",
"_PLACEHOLDER", "_PLACEHOLDER",
"address", "address",
"pool_address" "pool_address",
] ],
) )
LightningNetworkInvoice = generate_labeler_serializer( LightningNetworkInvoice = generate_labeler_serializer(
name="LightningNetworkInvoice", name="LightningNetworkInvoice",
klass=dataclasses.LightningNetworkInvoice, klass=dataclasses.LightningNetworkInvoice,
labels=[ labels=["invoice_hash", "invoice", "_PLACEHOLDER", "_PLACEHOLDER", "amount"],
"invoice_hash",
"invoice",
"_PLACEHOLDER",
"_PLACEHOLDER",
"amount"
]
) )
Movement = generate_labeler_serializer( Movement = generate_labeler_serializer(
@@ -778,8 +728,8 @@ Movement = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"_PLACEHOLDER", "_PLACEHOLDER",
"transaction_id", "transaction_id",
"withdraw_transaction_note" "withdraw_transaction_note",
] ],
) )
SymbolMarginInfo = generate_labeler_serializer( SymbolMarginInfo = generate_labeler_serializer(
@@ -791,22 +741,15 @@ SymbolMarginInfo = generate_labeler_serializer(
"tradable_balance", "tradable_balance",
"gross_balance", "gross_balance",
"buy", "buy",
"sell" "sell",
], ],
flat=True,
flat=True
) )
BaseMarginInfo = generate_labeler_serializer( BaseMarginInfo = generate_labeler_serializer(
name="BaseMarginInfo", name="BaseMarginInfo",
klass=dataclasses.BaseMarginInfo, klass=dataclasses.BaseMarginInfo,
labels=[ labels=["user_pl", "user_swaps", "margin_balance", "margin_net", "margin_min"],
"user_pl",
"user_swaps",
"margin_balance",
"margin_net",
"margin_min"
]
) )
PositionClaim = generate_labeler_serializer( PositionClaim = generate_labeler_serializer(
@@ -832,8 +775,8 @@ PositionClaim = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"collateral", "collateral",
"min_collateral", "min_collateral",
"meta" "meta",
] ],
) )
PositionIncreaseInfo = generate_labeler_serializer( PositionIncreaseInfo = generate_labeler_serializer(
@@ -857,21 +800,15 @@ PositionIncreaseInfo = generate_labeler_serializer(
"funding_value", "funding_value",
"funding_required", "funding_required",
"funding_value_currency", "funding_value_currency",
"funding_required_currency" "funding_required_currency",
], ],
flat=True,
flat=True
) )
PositionIncrease = generate_labeler_serializer( PositionIncrease = generate_labeler_serializer(
name="PositionIncrease", name="PositionIncrease",
klass=dataclasses.PositionIncrease, klass=dataclasses.PositionIncrease,
labels=[ labels=["symbol", "_PLACEHOLDER", "amount", "base_price"],
"symbol",
"_PLACEHOLDER",
"amount",
"base_price"
]
) )
PositionHistory = generate_labeler_serializer( PositionHistory = generate_labeler_serializer(
@@ -891,8 +828,8 @@ PositionHistory = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"position_id", "position_id",
"mts_create", "mts_create",
"mts_update" "mts_update",
] ],
) )
PositionSnapshot = generate_labeler_serializer( PositionSnapshot = generate_labeler_serializer(
@@ -912,8 +849,8 @@ PositionSnapshot = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"position_id", "position_id",
"mts_create", "mts_create",
"mts_update" "mts_update",
] ],
) )
PositionAudit = generate_labeler_serializer( PositionAudit = generate_labeler_serializer(
@@ -939,25 +876,20 @@ PositionAudit = generate_labeler_serializer(
"_PLACEHOLDER", "_PLACEHOLDER",
"collateral", "collateral",
"collateral_min", "collateral_min",
"meta" "meta",
] ],
) )
DerivativePositionCollateral = generate_labeler_serializer( DerivativePositionCollateral = generate_labeler_serializer(
name="DerivativePositionCollateral", name="DerivativePositionCollateral",
klass=dataclasses.DerivativePositionCollateral, klass=dataclasses.DerivativePositionCollateral,
labels=[ labels=["status"],
"status"
]
) )
DerivativePositionCollateralLimits = generate_labeler_serializer( DerivativePositionCollateralLimits = generate_labeler_serializer(
name="DerivativePositionCollateralLimits", name="DerivativePositionCollateralLimits",
klass=dataclasses.DerivativePositionCollateralLimits, klass=dataclasses.DerivativePositionCollateralLimits,
labels=[ labels=["min_collateral", "max_collateral"],
"min_collateral",
"max_collateral"
]
) )
#endregion # endregion

View File

@@ -13,9 +13,10 @@ from bfxapi.websocket.subscriptions import Subscription
_CHECKSUM_FLAG_VALUE = 131_072 _CHECKSUM_FLAG_VALUE = 131_072
def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]: def _strip(message: Dict[str, Any], keys: List[str]) -> Dict[str, Any]:
return { key: value for key, value in message.items() \ return {key: value for key, value in message.items() if not key in keys}
if not key in keys }
class BfxWebSocketBucket(Connection): class BfxWebSocketBucket(Connection):
__MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25 __MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
@@ -24,28 +25,26 @@ class BfxWebSocketBucket(Connection):
super().__init__(host) super().__init__(host)
self.__event_emitter = event_emitter self.__event_emitter = event_emitter
self.__pendings: List[Dict[str, Any]] = [ ] self.__pendings: List[Dict[str, Any]] = []
self.__subscriptions: Dict[int, Subscription] = { } self.__subscriptions: Dict[int, Subscription] = {}
self.__condition = asyncio.locks.Condition() self.__condition = asyncio.locks.Condition()
self.__handler = PublicChannelsHandler( \ self.__handler = PublicChannelsHandler(event_emitter=self.__event_emitter)
event_emitter=self.__event_emitter)
@property @property
def count(self) -> int: def count(self) -> int:
return len(self.__pendings) + \ return len(self.__pendings) + len(self.__subscriptions)
len(self.__subscriptions)
@property @property
def is_full(self) -> bool: def is_full(self) -> bool:
return self.count == \ return self.count == BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
BfxWebSocketBucket.__MAXIMUM_SUBSCRIPTIONS_AMOUNT
@property @property
def ids(self) -> List[str]: def ids(self) -> List[str]:
return [ pending["subId"] for pending in self.__pendings ] + \ return [pending["subId"] for pending in self.__pendings] + [
[ subscription["sub_id"] for subscription in self.__subscriptions.values() ] subscription["sub_id"] for subscription in self.__subscriptions.values()
]
async def start(self) -> None: async def start(self) -> None:
async with websockets.client.connect(self._host) as websocket: async with websockets.client.connect(self._host) as websocket:
@@ -64,20 +63,25 @@ class BfxWebSocketBucket(Connection):
self.__on_subscribed(message) self.__on_subscribed(message)
if isinstance(message, list): if isinstance(message, list):
if (chan_id := cast(int, message[0])) and \ if (
(subscription := self.__subscriptions.get(chan_id)) and \ (chan_id := cast(int, message[0]))
(message[1] != Connection._HEARTBEAT): and (subscription := self.__subscriptions.get(chan_id))
and (message[1] != Connection._HEARTBEAT)
):
self.__handler.handle(subscription, message[1:]) self.__handler.handle(subscription, message[1:])
def __on_subscribed(self, message: Dict[str, Any]) -> None: def __on_subscribed(self, message: Dict[str, Any]) -> None:
chan_id = cast(int, message["chan_id"]) chan_id = cast(int, message["chan_id"])
subscription = cast(Subscription, _strip(message, \ subscription = cast(
keys=["chan_id", "event", "pair", "currency"])) Subscription, _strip(message, keys=["chan_id", "event", "pair", "currency"])
)
self.__pendings = [ pending \ self.__pendings = [
for pending in self.__pendings \ pending
if pending["subId"] != message["sub_id"] ] for pending in self.__pendings
if pending["subId"] != message["sub_id"]
]
self.__subscriptions[chan_id] = subscription self.__subscriptions[chan_id] = subscription
@@ -85,47 +89,43 @@ class BfxWebSocketBucket(Connection):
async def __recover_state(self) -> None: async def __recover_state(self) -> None:
for pending in self.__pendings: for pending in self.__pendings:
await self._websocket.send(message = \ await self._websocket.send(message=json.dumps(pending))
json.dumps(pending))
for chan_id in list(self.__subscriptions.keys()): for chan_id in list(self.__subscriptions.keys()):
subscription = self.__subscriptions.pop(chan_id) subscription = self.__subscriptions.pop(chan_id)
await self.subscribe(**subscription) 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: async def __set_config(self, flags: List[int]) -> None:
await self._websocket.send(json.dumps( \ await self._websocket.send(json.dumps({"event": "conf", "flags": sum(flags)}))
{ "event": "conf", "flags": sum(flags) }))
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def subscribe(self, async def subscribe(
channel: str, self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
sub_id: Optional[str] = None, ) -> None:
**kwargs: Any) -> None: subscription: Dict[str, Any] = {
subscription: Dict[str, Any] = \ **kwargs,
{ **kwargs, "event": "subscribe", "channel": channel } "event": "subscribe",
"channel": channel,
}
subscription["subId"] = sub_id or str(uuid.uuid4()) subscription["subId"] = sub_id or str(uuid.uuid4())
self.__pendings.append(subscription) self.__pendings.append(subscription)
await self._websocket.send(message = \ await self._websocket.send(message=json.dumps(subscription))
json.dumps(subscription))
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def unsubscribe(self, sub_id: str) -> None: async def unsubscribe(self, sub_id: str) -> None:
for chan_id, subscription in list(self.__subscriptions.items()): for chan_id, subscription in list(self.__subscriptions.items()):
if subscription["sub_id"] == sub_id: if subscription["sub_id"] == sub_id:
unsubscription = { unsubscription = {"event": "unsubscribe", "chanId": chan_id}
"event": "unsubscribe",
"chanId": chan_id }
del self.__subscriptions[chan_id] del self.__subscriptions[chan_id]
await self._websocket.send(message = \ await self._websocket.send(message=json.dumps(unsubscription))
json.dumps(unsubscription))
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def resubscribe(self, sub_id: str) -> None: async def resubscribe(self, sub_id: str) -> None:
@@ -148,5 +148,4 @@ class BfxWebSocketBucket(Connection):
async def wait(self) -> None: async def wait(self) -> None:
async with self.__condition: async with self.__condition:
await self.__condition \ await self.__condition.wait_for(lambda: self.open)
.wait_for(lambda: self.open)

View File

@@ -28,14 +28,17 @@ from bfxapi.websocket.exceptions import (
from .bfx_websocket_bucket import BfxWebSocketBucket from .bfx_websocket_bucket import BfxWebSocketBucket
from .bfx_websocket_inputs import BfxWebSocketInputs from .bfx_websocket_inputs import BfxWebSocketInputs
_Credentials = TypedDict("_Credentials", \ _Credentials = TypedDict(
{ "api_key": str, "api_secret": str, "filters": Optional[List[str]] }) "_Credentials", {"api_key": str, "api_secret": str, "filters": Optional[List[str]]}
)
_Reconnection = TypedDict("_Reconnection", _Reconnection = TypedDict(
{ "attempts": int, "reason": str, "timestamp": datetime }) "_Reconnection", {"attempts": int, "reason": str, "timestamp": datetime}
)
_DEFAULT_LOGGER = Logger("bfxapi.websocket._client", level=0) _DEFAULT_LOGGER = Logger("bfxapi.websocket._client", level=0)
class _Delay: class _Delay:
__BACKOFF_MIN = 1.92 __BACKOFF_MIN = 1.92
@@ -54,59 +57,61 @@ class _Delay:
return _backoff_delay return _backoff_delay
def peek(self) -> float: def peek(self) -> float:
return (self.__backoff_delay == _Delay.__BACKOFF_MIN) \ return (
and self.__initial_delay or self.__backoff_delay (self.__backoff_delay == _Delay.__BACKOFF_MIN)
and self.__initial_delay
or self.__backoff_delay
)
def reset(self) -> None: def reset(self) -> None:
self.__backoff_delay = _Delay.__BACKOFF_MIN self.__backoff_delay = _Delay.__BACKOFF_MIN
#pylint: disable-next=too-many-instance-attributes
# pylint: disable-next=too-many-instance-attributes
class BfxWebSocketClient(Connection): class BfxWebSocketClient(Connection):
def __init__(self, def __init__(
host: str, self,
*, host: str,
credentials: Optional[_Credentials] = None, *,
timeout: Optional[int] = 60 * 15, credentials: Optional[_Credentials] = None,
logger: Logger = _DEFAULT_LOGGER) -> None: timeout: Optional[int] = 60 * 15,
logger: Logger = _DEFAULT_LOGGER,
) -> None:
super().__init__(host) super().__init__(host)
self.__credentials, self.__timeout, self.__logger = \ self.__credentials, self.__timeout, self.__logger = credentials, timeout, logger
credentials, \
timeout, \
logger
self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = { } self.__buckets: Dict[BfxWebSocketBucket, Optional[Task]] = {}
self.__reconnection: Optional[_Reconnection] = None self.__reconnection: Optional[_Reconnection] = None
self.__event_emitter = BfxEventEmitter(loop=None) self.__event_emitter = BfxEventEmitter(loop=None)
self.__handler = AuthEventsHandler( \ self.__handler = AuthEventsHandler(event_emitter=self.__event_emitter)
event_emitter=self.__event_emitter)
self.__inputs = BfxWebSocketInputs( \ self.__inputs = BfxWebSocketInputs(
handle_websocket_input=self.__handle_websocket_input) handle_websocket_input=self.__handle_websocket_input
)
@self.__event_emitter.listens_to("error") @self.__event_emitter.listens_to("error")
def error(exception: Exception) -> None: def error(exception: Exception) -> None:
header = f"{type(exception).__name__}: {str(exception)}" header = f"{type(exception).__name__}: {str(exception)}"
stack_trace = traceback.format_exception( \ stack_trace = traceback.format_exception(
type(exception), exception, exception.__traceback__) type(exception), exception, exception.__traceback__
)
#pylint: disable-next=logging-not-lazy # pylint: disable-next=logging-not-lazy
self.__logger.critical(header + "\n" + \ self.__logger.critical(header + "\n" + str().join(stack_trace)[:-1])
str().join(stack_trace)[:-1])
@property @property
def inputs(self) -> BfxWebSocketInputs: def inputs(self) -> BfxWebSocketInputs:
return self.__inputs return self.__inputs
def run(self) -> None: def run(self) -> None:
return asyncio.get_event_loop() \ return asyncio.get_event_loop().run_until_complete(self.start())
.run_until_complete(self.start())
#pylint: disable-next=too-many-branches # pylint: disable-next=too-many-branches
async def start(self) -> None: async def start(self) -> None:
_delay = _Delay(backoff_factor=1.618) _delay = _Delay(backoff_factor=1.618)
@@ -119,19 +124,20 @@ class BfxWebSocketClient(Connection):
while True: while True:
if self.__reconnection: if self.__reconnection:
_sleep = asyncio.create_task( \ _sleep = asyncio.create_task(asyncio.sleep(int(_delay.next())))
asyncio.sleep(int(_delay.next())))
try: try:
await _sleep await _sleep
except asyncio.CancelledError: except asyncio.CancelledError:
raise ReconnectionTimeoutError("Connection has been offline for too long " \ raise ReconnectionTimeoutError(
f"without being able to reconnect (timeout: {self.__timeout}s).") \ "Connection has been offline for too long "
from None f"without being able to reconnect (timeout: {self.__timeout}s)."
) from None
try: try:
await self.__connect() await self.__connect()
except (ConnectionClosedError, InvalidStatusCode, gaierror) as error: except (ConnectionClosedError, InvalidStatusCode, gaierror) as error:
async def _cancel(task: Task) -> None: async def _cancel(task: Task) -> None:
task.cancel() task.cancel()
@@ -150,69 +156,87 @@ class BfxWebSocketClient(Connection):
await _cancel(task) 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: if error.code == 1006:
self.__logger.error("Connection lost: trying to reconnect...") self.__logger.error("Connection lost: trying to reconnect...")
if error.code == 1012: if error.code == 1012:
self.__logger.warning("WSS server is restarting: all " \ self.__logger.warning(
"clients need to reconnect (server sent 20051).") "WSS server is restarting: all "
"clients need to reconnect (server sent 20051)."
)
if self.__timeout: if self.__timeout:
asyncio.get_event_loop().call_later( asyncio.get_event_loop().call_later(self.__timeout, _on_timeout)
self.__timeout, _on_timeout)
self.__reconnection = \ self.__reconnection = {
{ "attempts": 1, "reason": error.reason, "timestamp": datetime.now() } "attempts": 1,
"reason": error.reason,
"timestamp": datetime.now(),
}
self._authentication = False self._authentication = False
_delay.reset() _delay.reset()
elif ((isinstance(error, InvalidStatusCode) and error.status_code == 408) or \ elif (
isinstance(error, gaierror)) and self.__reconnection: (isinstance(error, InvalidStatusCode) and error.status_code == 408)
#pylint: disable-next=logging-fstring-interpolation or isinstance(error, gaierror)
self.__logger.warning("Reconnection attempt unsuccessful (no." \ ) and self.__reconnection:
f"{self.__reconnection['attempts']}): next attempt in " \ # pylint: disable-next=logging-fstring-interpolation
f"~{int(_delay.peek())}.0s.") 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 # pylint: disable-next=logging-fstring-interpolation
self.__logger.info(f"The client has been offline for " \ self.__logger.info(
f"{datetime.now() - self.__reconnection['timestamp']}.") f"The client has been offline for "
f"{datetime.now() - self.__reconnection['timestamp']}."
)
self.__reconnection["attempts"] += 1 self.__reconnection["attempts"] += 1
else: else:
raise error raise error
if not self.__reconnection: if not self.__reconnection:
self.__event_emitter.emit("disconnected", self.__event_emitter.emit(
self._websocket.close_code, \ "disconnected",
self._websocket.close_reason) self._websocket.close_code,
self._websocket.close_reason,
)
break break
async def __connect(self) -> None: async def __connect(self) -> None:
async with websockets.client.connect(self._host) as websocket: async with websockets.client.connect(self._host) as websocket:
if self.__reconnection: if self.__reconnection:
#pylint: disable-next=logging-fstring-interpolation # pylint: disable-next=logging-fstring-interpolation
self.__logger.warning("Reconnection attempt successful (no." \ self.__logger.warning(
f"{self.__reconnection['attempts']}): recovering " \ "Reconnection attempt successful (no."
"connection state...") f"{self.__reconnection['attempts']}): recovering "
"connection state..."
)
self.__reconnection = None self.__reconnection = None
self._websocket = websocket self._websocket = websocket
for bucket in self.__buckets: for bucket in self.__buckets:
self.__buckets[bucket] = \ self.__buckets[bucket] = asyncio.create_task(bucket.start())
asyncio.create_task(bucket.start())
if len(self.__buckets) == 0 or \ if len(self.__buckets) == 0 or (
(await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])): await asyncio.gather(*[bucket.wait() for bucket in self.__buckets])
):
self.__event_emitter.emit("open") self.__event_emitter.emit("open")
if self.__credentials: if self.__credentials:
authentication = Connection. \ authentication = Connection._get_authentication_message(
_get_authentication_message(**self.__credentials) **self.__credentials
)
await self._websocket.send(authentication) await self._websocket.send(authentication)
@@ -222,61 +246,64 @@ class BfxWebSocketClient(Connection):
if isinstance(message, dict): if isinstance(message, dict):
if message["event"] == "info" and "version" in message: if message["event"] == "info" and "version" in message:
if message["version"] != 2: if message["version"] != 2:
raise VersionMismatchError("Mismatch between the client and the server version: " + \ raise VersionMismatchError(
"please update bitfinex-api-py to the latest version to resolve this error " + \ "Mismatch between the client and the server version: "
f"(client version: 2, server version: {message['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: elif message["event"] == "info" and message["code"] == 20051:
rcvd = websockets.frames.Close( \ rcvd = websockets.frames.Close(
1012, "Stop/Restart WebSocket Server (please reconnect).") 1012, "Stop/Restart WebSocket Server (please reconnect)."
)
raise ConnectionClosedError(rcvd=rcvd, sent=None) raise ConnectionClosedError(rcvd=rcvd, sent=None)
elif message["event"] == "auth": elif message["event"] == "auth":
if message["status"] != "OK": if message["status"] != "OK":
raise InvalidCredentialError("Can't authenticate " + \ raise InvalidCredentialError(
"with given API-KEY and API-SECRET.") "Can't authenticate "
+ "with given API-KEY and API-SECRET."
)
self.__event_emitter.emit("authenticated", message) self.__event_emitter.emit("authenticated", message)
self._authentication = True self._authentication = True
if isinstance(message, list) and \ if (
message[0] == 0 and message[1] != Connection._HEARTBEAT: isinstance(message, list)
and message[0] == 0
and message[1] != Connection._HEARTBEAT
):
self.__handler.handle(message[1], message[2]) self.__handler.handle(message[1], message[2])
async def __new_bucket(self) -> BfxWebSocketBucket: async def __new_bucket(self) -> BfxWebSocketBucket:
bucket = BfxWebSocketBucket( \ bucket = BfxWebSocketBucket(self._host, self.__event_emitter)
self._host, self.__event_emitter)
self.__buckets[bucket] = asyncio \ self.__buckets[bucket] = asyncio.create_task(bucket.start())
.create_task(bucket.start())
await bucket.wait() await bucket.wait()
return bucket return bucket
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def subscribe(self, async def subscribe(
channel: str, self, channel: str, sub_id: Optional[str] = None, **kwargs: Any
sub_id: Optional[str] = None, ) -> None:
**kwargs: Any) -> None:
if not channel in ["ticker", "trades", "book", "candles", "status"]: if not channel in ["ticker", "trades", "book", "candles", "status"]:
raise UnknownChannelError("Available channels are: " + \ raise UnknownChannelError(
"ticker, trades, book, candles and status.") "Available channels are: " + "ticker, trades, book, candles and status."
)
for bucket in self.__buckets: for bucket in self.__buckets:
if sub_id in bucket.ids: if sub_id in bucket.ids:
raise SubIdError("sub_id must be " + \ raise SubIdError("sub_id must be " + "unique for all subscriptions.")
"unique for all subscriptions.")
for bucket in self.__buckets: for bucket in self.__buckets:
if not bucket.is_full: if not bucket.is_full:
return await bucket.subscribe( \ return await bucket.subscribe(channel, sub_id, **kwargs)
channel, sub_id, **kwargs)
bucket = await self.__new_bucket() bucket = await self.__new_bucket()
return await bucket.subscribe( \ return await bucket.subscribe(channel, sub_id, **kwargs)
channel, sub_id, **kwargs)
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def unsubscribe(self, sub_id: str) -> None: async def unsubscribe(self, sub_id: str) -> None:
@@ -286,13 +313,13 @@ class BfxWebSocketClient(Connection):
if bucket.count == 1: if bucket.count == 1:
del self.__buckets[bucket] del self.__buckets[bucket]
return await bucket.close( \ return await bucket.close(code=1001, reason="Going Away")
code=1001, reason="Going Away")
return await bucket.unsubscribe(sub_id) return await bucket.unsubscribe(sub_id)
raise UnknownSubscriptionError("Unable to find " + \ raise UnknownSubscriptionError(
f"a subscription with sub_id <{sub_id}>.") "Unable to find " + f"a subscription with sub_id <{sub_id}>."
)
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def resubscribe(self, sub_id: str) -> None: async def resubscribe(self, sub_id: str) -> None:
@@ -300,8 +327,9 @@ class BfxWebSocketClient(Connection):
if bucket.has(sub_id): if bucket.has(sub_id):
return await bucket.resubscribe(sub_id) return await bucket.resubscribe(sub_id)
raise UnknownSubscriptionError("Unable to find " + \ raise UnknownSubscriptionError(
f"a subscription with sub_id <{sub_id}>.") "Unable to find " + f"a subscription with sub_id <{sub_id}>."
)
@Connection._require_websocket_connection @Connection._require_websocket_connection
async def close(self, code: int = 1000, reason: str = str()) -> None: 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) await bucket.close(code=code, reason=reason)
if self._websocket.open: if self._websocket.open:
await self._websocket.close( \ await self._websocket.close(code=code, reason=reason)
code=code, reason=reason)
@Connection._require_websocket_authentication @Connection._require_websocket_authentication
async def notify(self, async def notify(
info: Any, self, info: Any, message_id: Optional[int] = None, **kwargs: Any
message_id: Optional[int] = None, ) -> None:
**kwargs: Any) -> None:
await self._websocket.send( await self._websocket.send(
json.dumps([ 0, "n", message_id, json.dumps(
{ "type": "ucm-test", "info": info, **kwargs } ])) [0, "n", message_id, {"type": "ucm-test", "info": info, **kwargs}]
)
)
@Connection._require_websocket_authentication @Connection._require_websocket_authentication
async def __handle_websocket_input(self, event: str, data: Any) -> None: async def __handle_websocket_input(self, event: str, data: Any) -> None:
await self._websocket.send(json.dumps( \ await self._websocket.send(json.dumps([0, event, None, data], cls=JSONEncoder))
[ 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) return self.__event_emitter.on(event, callback)

View File

@@ -3,89 +3,127 @@ from typing import Any, Awaitable, Callable, List, Optional, Tuple, Union
_Handler = Callable[[str, Any], Awaitable[None]] _Handler = Callable[[str, Any], Awaitable[None]]
class BfxWebSocketInputs: class BfxWebSocketInputs:
def __init__(self, handle_websocket_input: _Handler) -> None: def __init__(self, handle_websocket_input: _Handler) -> None:
self.__handle_websocket_input = handle_websocket_input self.__handle_websocket_input = handle_websocket_input
async def submit_order(self, async def submit_order(
type: str, self,
symbol: str, type: str,
amount: Union[str, float, Decimal], symbol: str,
price: Union[str, float, Decimal], amount: Union[str, float, Decimal],
*, price: Union[str, float, Decimal],
lev: Optional[int] = None, *,
price_trailing: Optional[Union[str, float, Decimal]] = None, lev: Optional[int] = None,
price_aux_limit: Optional[Union[str, float, Decimal]] = None, price_trailing: Optional[Union[str, float, Decimal]] = None,
price_oco_stop: Optional[Union[str, float, Decimal]] = None, price_aux_limit: Optional[Union[str, float, Decimal]] = None,
gid: Optional[int] = None, price_oco_stop: Optional[Union[str, float, Decimal]] = None,
cid: Optional[int] = None, gid: Optional[int] = None,
flags: Optional[int] = None, cid: Optional[int] = None,
tif: Optional[str] = None) -> None: flags: Optional[int] = None,
await self.__handle_websocket_input("on", { tif: Optional[str] = None,
"type": type, "symbol": symbol, "amount": amount, ) -> None:
"price": price, "lev": lev, "price_trailing": price_trailing, await self.__handle_websocket_input(
"price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "gid": gid, "on",
"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,
},
)
async def update_order(self, async def update_order(
id: int, self,
*, id: int,
amount: Optional[Union[str, float, Decimal]] = None, *,
price: Optional[Union[str, float, Decimal]] = None, amount: Optional[Union[str, float, Decimal]] = None,
cid: Optional[int] = None, price: Optional[Union[str, float, Decimal]] = None,
cid_date: Optional[str] = None, cid: Optional[int] = None,
gid: Optional[int] = None, cid_date: Optional[str] = None,
flags: Optional[int] = None, gid: Optional[int] = None,
lev: Optional[int] = None, flags: Optional[int] = None,
delta: Optional[Union[str, float, Decimal]] = None, lev: Optional[int] = None,
price_aux_limit: Optional[Union[str, float, Decimal]] = None, delta: Optional[Union[str, float, Decimal]] = None,
price_trailing: Optional[Union[str, float, Decimal]] = None, price_aux_limit: Optional[Union[str, float, Decimal]] = None,
tif: Optional[str] = None) -> None: price_trailing: Optional[Union[str, float, Decimal]] = None,
await self.__handle_websocket_input("ou", { tif: Optional[str] = None,
"id": id, "amount": amount, "price": price, ) -> None:
"cid": cid, "cid_date": cid_date, "gid": gid, await self.__handle_websocket_input(
"flags": flags, "lev": lev, "delta": delta, "ou",
"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,
},
)
async def cancel_order(self, async def cancel_order(
*, self,
id: Optional[int] = None, *,
cid: Optional[int] = None, id: Optional[int] = None,
cid_date: Optional[str] = None) -> None: cid: Optional[int] = None,
await self.__handle_websocket_input("oc", { cid_date: Optional[str] = None,
"id": id, "cid": cid, "cid_date": cid_date ) -> None:
}) await self.__handle_websocket_input(
"oc", {"id": id, "cid": cid, "cid_date": cid_date}
)
async def cancel_order_multi(self, async def cancel_order_multi(
*, self,
id: Optional[List[int]] = None, *,
cid: Optional[List[Tuple[int, str]]] = None, id: Optional[List[int]] = None,
gid: Optional[List[int]] = None, cid: Optional[List[Tuple[int, str]]] = None,
all: Optional[bool] = None) -> None: gid: Optional[List[int]] = None,
await self.__handle_websocket_input("oc_multi", { all: Optional[bool] = None,
"id": id, "cid": cid, "gid": gid, ) -> None:
"all": all await self.__handle_websocket_input(
}) "oc_multi", {"id": id, "cid": cid, "gid": gid, "all": all}
)
#pylint: disable-next=too-many-arguments # pylint: disable-next=too-many-arguments
async def submit_funding_offer(self, async def submit_funding_offer(
type: str, self,
symbol: str, type: str,
amount: Union[str, float, Decimal], symbol: str,
rate: Union[str, float, Decimal], amount: Union[str, float, Decimal],
period: int, rate: Union[str, float, Decimal],
*, period: int,
flags: Optional[int] = None) -> None: *,
await self.__handle_websocket_input("fon", { flags: Optional[int] = None,
"type": type, "symbol": symbol, "amount": amount, ) -> None:
"rate": rate, "period": period, "flags": flags 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: 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: async def calc(self, *args: str) -> None:
await self.__handle_websocket_input("calc", await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
list(map(lambda arg: [arg], args)))

View File

@@ -18,6 +18,7 @@ _R = TypeVar("_R")
_P = ParamSpec("_P") _P = ParamSpec("_P")
class Connection(ABC): class Connection(ABC):
_HEARTBEAT = "hb" _HEARTBEAT = "hb"
@@ -30,8 +31,7 @@ class Connection(ABC):
@property @property
def open(self) -> bool: def open(self) -> bool:
return self.__protocol is not None and \ return self.__protocol is not None and self.__protocol.open
self.__protocol.open
@property @property
def authentication(self) -> bool: def authentication(self) -> bool:
@@ -46,12 +46,11 @@ class Connection(ABC):
self.__protocol = protocol self.__protocol = protocol
@abstractmethod @abstractmethod
async def start(self) -> None: async def start(self) -> None: ...
...
@staticmethod @staticmethod
def _require_websocket_connection( def _require_websocket_connection(
function: Callable[Concatenate[_S, _P], Awaitable[_R]] function: Callable[Concatenate[_S, _P], Awaitable[_R]],
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]: ) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
@wraps(function) @wraps(function)
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
@@ -64,13 +63,15 @@ class Connection(ABC):
@staticmethod @staticmethod
def _require_websocket_authentication( def _require_websocket_authentication(
function: Callable[Concatenate[_S, _P], Awaitable[_R]] function: Callable[Concatenate[_S, _P], Awaitable[_R]],
) -> Callable[Concatenate[_S, _P], Awaitable[_R]]: ) -> Callable[Concatenate[_S, _P], Awaitable[_R]]:
@wraps(function) @wraps(function)
async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R: async def wrapper(self: _S, *args: Any, **kwargs: Any) -> _R:
if not self.authentication: if not self.authentication:
raise ActionRequiresAuthentication("To perform this action you need to " \ raise ActionRequiresAuthentication(
"authenticate using your API_KEY and API_SECRET.") "To perform this action you need to "
"authenticate using your API_KEY and API_SECRET."
)
internal = Connection._require_websocket_connection(function) internal = Connection._require_websocket_connection(function)
@@ -80,12 +81,13 @@ class Connection(ABC):
@staticmethod @staticmethod
def _get_authentication_message( def _get_authentication_message(
api_key: str, api_key: str, api_secret: str, filters: Optional[List[str]] = None
api_secret: str,
filters: Optional[List[str]] = None
) -> str: ) -> str:
message: Dict[str, Any] = \ message: Dict[str, Any] = {
{ "event": "auth", "filter": filters, "apiKey": api_key } "event": "auth",
"filter": filters,
"apiKey": api_key,
}
message["authNonce"] = round(datetime.now().timestamp() * 1_000_000) message["authNonce"] = round(datetime.now().timestamp() * 1_000_000)
@@ -94,7 +96,7 @@ class Connection(ABC):
auth_sig = hmac.new( auth_sig = hmac.new(
key=api_secret.encode("utf8"), key=api_secret.encode("utf8"),
msg=message["authPayload"].encode("utf8"), msg=message["authPayload"].encode("utf8"),
digestmod=hashlib.sha384 digestmod=hashlib.sha384,
) )
message["authSig"] = auth_sig.hexdigest() message["authSig"] = auth_sig.hexdigest()

View File

@@ -9,57 +9,86 @@ from bfxapi.websocket.exceptions import UnknownEventError
_Handler = TypeVar("_Handler", bound=Callable[..., None]) _Handler = TypeVar("_Handler", bound=Callable[..., None])
_ONCE_PER_CONNECTION = [ _ONCE_PER_CONNECTION = [
"open", "authenticated", "order_snapshot", "open",
"position_snapshot", "funding_offer_snapshot", "funding_credit_snapshot", "authenticated",
"funding_loan_snapshot", "wallet_snapshot" "order_snapshot",
"position_snapshot",
"funding_offer_snapshot",
"funding_credit_snapshot",
"funding_loan_snapshot",
"wallet_snapshot",
] ]
_ONCE_PER_SUBSCRIPTION = [ _ONCE_PER_SUBSCRIPTION = [
"subscribed", "t_trades_snapshot", "f_trades_snapshot", "subscribed",
"t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot", "t_trades_snapshot",
"f_raw_book_snapshot", "candles_snapshot" "f_trades_snapshot",
"t_book_snapshot",
"f_book_snapshot",
"t_raw_book_snapshot",
"f_raw_book_snapshot",
"candles_snapshot",
] ]
_COMMON = [ _COMMON = [
"disconnected", "t_ticker_update", "f_ticker_update", "disconnected",
"t_trade_execution", "t_trade_execution_update", "f_trade_execution", "t_ticker_update",
"f_trade_execution_update", "t_book_update", "f_book_update", "f_ticker_update",
"t_raw_book_update", "f_raw_book_update", "candles_update", "t_trade_execution",
"derivatives_status_update", "liquidation_feed_update", "checksum", "t_trade_execution_update",
"order_new", "order_update", "order_cancel", "f_trade_execution",
"position_new", "position_update", "position_close", "f_trade_execution_update",
"funding_offer_new", "funding_offer_update", "funding_offer_cancel", "t_book_update",
"funding_credit_new", "funding_credit_update", "funding_credit_close", "f_book_update",
"funding_loan_new", "funding_loan_update", "funding_loan_close", "t_raw_book_update",
"trade_execution", "trade_execution_update", "wallet_update", "f_raw_book_update",
"notification", "on-req-notification", "ou-req-notification", "candles_update",
"oc-req-notification", "fon-req-notification", "foc-req-notification" "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): class BfxEventEmitter(AsyncIOEventEmitter):
_EVENTS = _ONCE_PER_CONNECTION + \ _EVENTS = _ONCE_PER_CONNECTION + _ONCE_PER_SUBSCRIPTION + _COMMON
_ONCE_PER_SUBSCRIPTION + \
_COMMON
def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None: def __init__(self, loop: Optional[AbstractEventLoop] = None) -> None:
super().__init__(loop) super().__init__(loop)
self._connection: List[str] = [ ] self._connection: List[str] = []
self._subscriptions: Dict[str, List[str]] = \ self._subscriptions: Dict[str, List[str]] = defaultdict(lambda: [])
defaultdict(lambda: [ ])
def emit( def emit(self, event: str, *args: Any, **kwargs: Any) -> bool:
self,
event: str,
*args: Any,
**kwargs: Any
) -> bool:
if event in _ONCE_PER_CONNECTION: if event in _ONCE_PER_CONNECTION:
if event in self._connection: if event in self._connection:
return self._has_listeners(event) return self._has_listeners(event)
self._connection += [ event ] self._connection += [event]
if event in _ONCE_PER_SUBSCRIPTION: if event in _ONCE_PER_SUBSCRIPTION:
sub_id = args[0]["sub_id"] sub_id = args[0]["sub_id"]
@@ -67,7 +96,7 @@ class BfxEventEmitter(AsyncIOEventEmitter):
if event in self._subscriptions[sub_id]: if event in self._subscriptions[sub_id]:
return self._has_listeners(event) return self._has_listeners(event)
self._subscriptions[sub_id] += [ event ] self._subscriptions[sub_id] += [event]
return super().emit(event, *args, **kwargs) return super().emit(event, *args, **kwargs)
@@ -75,8 +104,10 @@ class BfxEventEmitter(AsyncIOEventEmitter):
self, event: str, f: Optional[_Handler] = None self, event: str, f: Optional[_Handler] = None
) -> Union[_Handler, Callable[[_Handler], _Handler]]: ) -> Union[_Handler, Callable[[_Handler], _Handler]]:
if event not in BfxEventEmitter._EVENTS: if event not in BfxEventEmitter._EVENTS:
raise UnknownEventError(f"Can't register to unknown event: <{event}> " + \ raise UnknownEventError(
"(to get a full list of available events see https://docs.bitfinex.com/).") 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) return super().on(event, f)

View File

@@ -9,14 +9,30 @@ from bfxapi.types.serializers import _Notification
class AuthEventsHandler: class AuthEventsHandler:
__ABBREVIATIONS = { __ABBREVIATIONS = {
"os": "order_snapshot", "on": "order_new", "ou": "order_update", "os": "order_snapshot",
"oc": "order_cancel", "ps": "position_snapshot", "pn": "position_new", "on": "order_new",
"pu": "position_update", "pc": "position_close", "te": "trade_execution", "ou": "order_update",
"tu": "trade_execution_update", "fos": "funding_offer_snapshot", "fon": "funding_offer_new", "oc": "order_cancel",
"fou": "funding_offer_update", "foc": "funding_offer_cancel", "fcs": "funding_credit_snapshot", "ps": "position_snapshot",
"fcn": "funding_credit_new", "fcu": "funding_credit_update", "fcc": "funding_credit_close", "pn": "position_new",
"fls": "funding_loan_snapshot", "fln": "funding_loan_new", "flu": "funding_loan_update", "pu": "position_update",
"flc": "funding_loan_close", "ws": "wallet_snapshot", "wu": "wallet_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] = { __SERIALIZERS: Dict[Tuple[str, ...], serializers._Serializer] = {
@@ -26,7 +42,7 @@ class AuthEventsHandler:
("fos", "fon", "fou", "foc"): serializers.FundingOffer, ("fos", "fon", "fou", "foc"): serializers.FundingOffer,
("fcs", "fcn", "fcu", "fcc"): serializers.FundingCredit, ("fcs", "fcn", "fcu", "fcc"): serializers.FundingCredit,
("fls", "fln", "flu", "flc"): serializers.FundingLoan, ("fls", "fln", "flu", "flc"): serializers.FundingLoan,
("ws", "wu"): serializers.Wallet ("ws", "wu"): serializers.Wallet,
} }
def __init__(self, event_emitter: EventEmitter) -> None: def __init__(self, event_emitter: EventEmitter) -> None:
@@ -41,7 +57,7 @@ class AuthEventsHandler:
event = AuthEventsHandler.__ABBREVIATIONS[abbrevation] event = AuthEventsHandler.__ABBREVIATIONS[abbrevation]
if all(isinstance(sub_stream, list) for sub_stream in stream): 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: else:
data = serializer.parse(*stream) data = serializer.parse(*stream)
@@ -53,11 +69,13 @@ class AuthEventsHandler:
serializer: _Notification = _Notification[None](serializer=None) serializer: _Notification = _Notification[None](serializer=None)
if stream[1] in ("on-req", "ou-req", "oc-req"): if stream[1] in ("on-req", "ou-req", "oc-req"):
event, serializer = f"{stream[1]}-notification", \ event, serializer = f"{stream[1]}-notification", _Notification[Order](
_Notification[Order](serializer=serializers.Order) serializer=serializers.Order
)
if stream[1] in ("fon-req", "foc-req"): if stream[1] in ("fon-req", "foc-req"):
event, serializer = f"{stream[1]}-notification", \ event, serializer = f"{stream[1]}-notification", _Notification[
_Notification[FundingOffer](serializer=serializers.FundingOffer) FundingOffer
](serializer=serializers.FundingOffer)
self.__event_emitter.emit(event, serializer.parse(*stream)) self.__event_emitter.emit(event, serializer.parse(*stream))

View File

@@ -14,6 +14,7 @@ from bfxapi.websocket.subscriptions import (
_CHECKSUM = "cs" _CHECKSUM = "cs"
class PublicChannelsHandler: class PublicChannelsHandler:
def __init__(self, event_emitter: EventEmitter) -> None: def __init__(self, event_emitter: EventEmitter) -> None:
self.__event_emitter = event_emitter self.__event_emitter = event_emitter
@@ -38,101 +39,167 @@ class PublicChannelsHandler:
elif subscription["channel"] == "status": elif subscription["channel"] == "status":
self.__status_channel_handler(cast(Status, subscription), stream) 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]): def __ticker_channel_handler(self, subscription: Ticker, stream: List[Any]):
if subscription["symbol"].startswith("t"): if subscription["symbol"].startswith("t"):
return self.__event_emitter.emit("t_ticker_update", subscription, \ return self.__event_emitter.emit(
serializers.TradingPairTicker.parse(*stream[0])) "t_ticker_update",
subscription,
serializers.TradingPairTicker.parse(*stream[0]),
)
if subscription["symbol"].startswith("f"): if subscription["symbol"].startswith("f"):
return self.__event_emitter.emit("f_ticker_update", subscription, \ return self.__event_emitter.emit(
serializers.FundingCurrencyTicker.parse(*stream[0])) "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]): def __trades_channel_handler(self, subscription: Trades, stream: List[Any]):
if (event := stream[0]) and event in [ "te", "tu", "fte", "ftu" ]: if (event := stream[0]) and event in ["te", "tu", "fte", "ftu"]:
events = { "te": "t_trade_execution", "tu": "t_trade_execution_update", \ events = {
"fte": "f_trade_execution", "ftu": "f_trade_execution_update" } "te": "t_trade_execution",
"tu": "t_trade_execution_update",
"fte": "f_trade_execution",
"ftu": "f_trade_execution_update",
}
if subscription["symbol"].startswith("t"): if subscription["symbol"].startswith("t"):
return self.__event_emitter.emit(events[event], subscription, \ return self.__event_emitter.emit(
serializers.TradingPairTrade.parse(*stream[1])) events[event],
subscription,
serializers.TradingPairTrade.parse(*stream[1]),
)
if subscription["symbol"].startswith("f"): if subscription["symbol"].startswith("f"):
return self.__event_emitter.emit(events[event], subscription, \ return self.__event_emitter.emit(
serializers.FundingCurrencyTrade.parse(*stream[1])) events[event],
subscription,
serializers.FundingCurrencyTrade.parse(*stream[1]),
)
if subscription["symbol"].startswith("t"): if subscription["symbol"].startswith("t"):
return self.__event_emitter.emit("t_trades_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.TradingPairTrade.parse(*sub_stream) \ "t_trades_snapshot",
for sub_stream in stream[0] ]) subscription,
[
serializers.TradingPairTrade.parse(*sub_stream)
for sub_stream in stream[0]
],
)
if subscription["symbol"].startswith("f"): if subscription["symbol"].startswith("f"):
return self.__event_emitter.emit("f_trades_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.FundingCurrencyTrade.parse(*sub_stream) \ "f_trades_snapshot",
for sub_stream in stream[0] ]) 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]): def __book_channel_handler(self, subscription: Book, stream: List[Any]):
if subscription["symbol"].startswith("t"): if subscription["symbol"].startswith("t"):
if all(isinstance(sub_stream, list) for sub_stream in stream[0]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
return self.__event_emitter.emit("t_book_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.TradingPairBook.parse(*sub_stream) \ "t_book_snapshot",
for sub_stream in stream[0] ]) subscription,
[
serializers.TradingPairBook.parse(*sub_stream)
for sub_stream in stream[0]
],
)
return self.__event_emitter.emit("t_book_update", subscription, \ return self.__event_emitter.emit(
serializers.TradingPairBook.parse(*stream[0])) "t_book_update",
subscription,
serializers.TradingPairBook.parse(*stream[0]),
)
if subscription["symbol"].startswith("f"): if subscription["symbol"].startswith("f"):
if all(isinstance(sub_stream, list) for sub_stream in stream[0]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
return self.__event_emitter.emit("f_book_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.FundingCurrencyBook.parse(*sub_stream) \ "f_book_snapshot",
for sub_stream in stream[0] ]) subscription,
[
serializers.FundingCurrencyBook.parse(*sub_stream)
for sub_stream in stream[0]
],
)
return self.__event_emitter.emit("f_book_update", subscription, \ return self.__event_emitter.emit(
serializers.FundingCurrencyBook.parse(*stream[0])) "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]): def __raw_book_channel_handler(self, subscription: Book, stream: List[Any]):
if subscription["symbol"].startswith("t"): if subscription["symbol"].startswith("t"):
if all(isinstance(sub_stream, list) for sub_stream in stream[0]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
return self.__event_emitter.emit("t_raw_book_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.TradingPairRawBook.parse(*sub_stream) \ "t_raw_book_snapshot",
for sub_stream in stream[0] ]) subscription,
[
serializers.TradingPairRawBook.parse(*sub_stream)
for sub_stream in stream[0]
],
)
return self.__event_emitter.emit("t_raw_book_update", subscription, \ return self.__event_emitter.emit(
serializers.TradingPairRawBook.parse(*stream[0])) "t_raw_book_update",
subscription,
serializers.TradingPairRawBook.parse(*stream[0]),
)
if subscription["symbol"].startswith("f"): if subscription["symbol"].startswith("f"):
if all(isinstance(sub_stream, list) for sub_stream in stream[0]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
return self.__event_emitter.emit("f_raw_book_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.FundingCurrencyRawBook.parse(*sub_stream) \ "f_raw_book_snapshot",
for sub_stream in stream[0] ]) subscription,
[
serializers.FundingCurrencyRawBook.parse(*sub_stream)
for sub_stream in stream[0]
],
)
return self.__event_emitter.emit("f_raw_book_update", subscription, \ return self.__event_emitter.emit(
serializers.FundingCurrencyRawBook.parse(*stream[0])) "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]): def __candles_channel_handler(self, subscription: Candles, stream: List[Any]):
if all(isinstance(sub_stream, list) for sub_stream in stream[0]): if all(isinstance(sub_stream, list) for sub_stream in stream[0]):
return self.__event_emitter.emit("candles_snapshot", subscription, \ return self.__event_emitter.emit(
[ serializers.Candle.parse(*sub_stream) \ "candles_snapshot",
for sub_stream in stream[0] ]) subscription,
[serializers.Candle.parse(*sub_stream) for sub_stream in stream[0]],
)
return self.__event_emitter.emit("candles_update", subscription, \ return self.__event_emitter.emit(
serializers.Candle.parse(*stream[0])) "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]): def __status_channel_handler(self, subscription: Status, stream: List[Any]):
if subscription["key"].startswith("deriv:"): if subscription["key"].startswith("deriv:"):
return self.__event_emitter.emit("derivatives_status_update", subscription, \ return self.__event_emitter.emit(
serializers.DerivativesStatus.parse(*stream[0])) "derivatives_status_update",
subscription,
serializers.DerivativesStatus.parse(*stream[0]),
)
if subscription["key"].startswith("liq:"): if subscription["key"].startswith("liq:"):
return self.__event_emitter.emit("liquidation_feed_update", subscription, \ return self.__event_emitter.emit(
serializers.Liquidation.parse(*stream[0][0])) "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): def __checksum_handler(self, subscription: Book, value: int):
return self.__event_emitter.emit( \ return self.__event_emitter.emit("checksum", subscription, value & 0xFFFFFFFF)
"checksum", subscription, value & 0xFFFFFFFF)

View File

@@ -4,23 +4,30 @@ from bfxapi.exceptions import BfxBaseException
class ConnectionNotOpen(BfxBaseException): class ConnectionNotOpen(BfxBaseException):
pass pass
class ActionRequiresAuthentication(BfxBaseException): class ActionRequiresAuthentication(BfxBaseException):
pass pass
class ReconnectionTimeoutError(BfxBaseException): class ReconnectionTimeoutError(BfxBaseException):
pass pass
class VersionMismatchError(BfxBaseException): class VersionMismatchError(BfxBaseException):
pass pass
class SubIdError(BfxBaseException): class SubIdError(BfxBaseException):
pass pass
class UnknownChannelError(BfxBaseException): class UnknownChannelError(BfxBaseException):
pass pass
class UnknownEventError(BfxBaseException): class UnknownEventError(BfxBaseException):
pass pass
class UnknownSubscriptionError(BfxBaseException): class UnknownSubscriptionError(BfxBaseException):
pass pass

View File

@@ -4,16 +4,19 @@ Subscription = Union["Ticker", "Trades", "Book", "Candles", "Status"]
Channel = Literal["ticker", "trades", "book", "candles", "status"] Channel = Literal["ticker", "trades", "book", "candles", "status"]
class Ticker(TypedDict): class Ticker(TypedDict):
channel: Literal["ticker"] channel: Literal["ticker"]
sub_id: str sub_id: str
symbol: str symbol: str
class Trades(TypedDict): class Trades(TypedDict):
channel: Literal["trades"] channel: Literal["trades"]
sub_id: str sub_id: str
symbol: str symbol: str
class Book(TypedDict): class Book(TypedDict):
channel: Literal["book"] channel: Literal["book"]
sub_id: str sub_id: str
@@ -22,11 +25,13 @@ class Book(TypedDict):
freq: Literal["F0", "F1"] freq: Literal["F0", "F1"]
len: Literal["1", "25", "100", "250"] len: Literal["1", "25", "100", "250"]
class Candles(TypedDict): class Candles(TypedDict):
channel: Literal["candles"] channel: Literal["candles"]
sub_id: str sub_id: str
key: str key: str
class Status(TypedDict): class Status(TypedDict):
channel: Literal["status"] channel: Literal["status"]
sub_id: str sub_id: str

View File

@@ -5,10 +5,7 @@ import os
from bfxapi import Client from bfxapi import Client
from bfxapi.types import Notification, PositionClaim from bfxapi.types import Notification, PositionClaim
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
# Claims all active positions # Claims all active positions
for position in bfx.rest.auth.get_positions(): for position in bfx.rest.auth.get_positions():

View File

@@ -13,10 +13,7 @@ from bfxapi.types import (
Withdrawal, Withdrawal,
) )
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
# Gets all user's available wallets # Gets all user's available wallets
wallets: List[Wallet] = bfx.rest.auth.get_wallets() wallets: List[Wallet] = bfx.rest.auth.get_wallets()

View File

@@ -8,10 +8,7 @@ from bfxapi.types import (
DerivativePositionCollateralLimits, DerivativePositionCollateralLimits,
) )
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
submit_order_notification = bfx.rest.auth.submit_order( submit_order_notification = bfx.rest.auth.submit_order(
type="LIMIT", symbol="tBTCF0:USTF0", amount="0.015", price="16700", lev=10 type="LIMIT", symbol="tBTCF0:USTF0", amount="0.015", price="16700", lev=10

View File

@@ -5,10 +5,7 @@ import os
from bfxapi import Client from bfxapi import Client
from bfxapi.types import FundingOffer, Notification from bfxapi.types import FundingOffer, Notification
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
# Submit a new funding offer # Submit a new funding offer
notification: Notification[FundingOffer] = bfx.rest.auth.submit_funding_offer( notification: Notification[FundingOffer] = bfx.rest.auth.submit_funding_offer(

View File

@@ -5,10 +5,7 @@ import os
from bfxapi import Client from bfxapi import Client
from bfxapi.types import Notification, Order from bfxapi.types import Notification, Order
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
# Submit a new order # Submit a new order
submit_order_notification: Notification[Order] = bfx.rest.auth.submit_order( submit_order_notification: Notification[Order] = bfx.rest.auth.submit_order(

View File

@@ -6,10 +6,7 @@ from typing import List
from bfxapi import Client from bfxapi import Client
from bfxapi.types import FundingLoan, Notification from bfxapi.types import FundingLoan, Notification
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
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") loans: List[FundingLoan] = bfx.rest.auth.get_funding_loans(symbol="fUSD")

View File

@@ -4,10 +4,7 @@ import os
from bfxapi import Client from bfxapi import Client
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
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): if not bfx.rest.merchant.set_merchant_settings("bfx_pay_recommend_store", 1):
print("Cannot set <bfx_pay_recommend_store> to <1>.") print("Cannot set <bfx_pay_recommend_store> to <1>.")

View File

@@ -5,10 +5,7 @@ import os
from bfxapi import Client from bfxapi import Client
from bfxapi.types import InvoiceSubmission from bfxapi.types import InvoiceSubmission
bfx = Client( bfx = Client(api_key=os.getenv("BFX_API_KEY"), api_secret=os.getenv("BFX_API_SECRET"))
api_key=os.getenv("BFX_API_KEY"),
api_secret=os.getenv("BFX_API_SECRET")
)
customer_info = { customer_info = {
"nationality": "DE", "nationality": "DE",

View File

@@ -1,2 +1,7 @@
[tool.isort] [tool.isort]
profile = "black" profile = "black"
[tool.black]
target-version = ["py38", "py39", "py310", "py311"]
preview = true