Rewrite bfxapi/rest/_Requests.py with type hinting. Add None values erasement in bfxapi/utils/JSONEncoder.py. Update code with new improvements.

This commit is contained in:
Davide Casale
2023-02-06 19:15:58 +01:00
parent 929ae62d2f
commit c588d9f20c
6 changed files with 86 additions and 96 deletions

View File

@@ -1,7 +1,5 @@
from typing import List, Dict, Union, Optional, Any, TypedDict, Generic, TypeVar, cast from typing import List, Dict, Union, Optional, Any, TypedDict, Generic, TypeVar, cast
from dataclasses import dataclass from dataclasses import dataclass
from .labeler import _Type, _Serializer from .labeler import _Type, _Serializer
T = TypeVar("T") T = TypeVar("T")
@@ -19,10 +17,10 @@ class Notification(_Type, Generic[T]):
class _Notification(_Serializer, Generic[T]): class _Notification(_Serializer, Generic[T]):
__LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "notify_info", "code", "status", "text" ] __LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "notify_info", "code", "status", "text" ]
def __init__(self, serializer: Optional[_Serializer] = None, iterate: bool = False): def __init__(self, serializer: Optional[_Serializer] = None, is_iterable: bool = False):
super().__init__("Notification", Notification, _Notification.__LABELS, IGNORE = [ "_PLACEHOLDER" ]) super().__init__("Notification", Notification, _Notification.__LABELS, IGNORE = [ "_PLACEHOLDER" ])
self.serializer, self.iterate = serializer, iterate self.serializer, self.is_iterable = serializer, is_iterable
def parse(self, *values: Any, skip: Optional[List[str]] = None) -> Notification[T]: def parse(self, *values: Any, skip: Optional[List[str]] = None) -> Notification[T]:
notification = cast(Notification[T], Notification(**dict(self._serialize(*values)))) notification = cast(Notification[T], Notification(**dict(self._serialize(*values))))
@@ -30,7 +28,7 @@ class _Notification(_Serializer, Generic[T]):
if isinstance(self.serializer, _Serializer): if isinstance(self.serializer, _Serializer):
NOTIFY_INFO = cast(List[Any], notification.notify_info) NOTIFY_INFO = cast(List[Any], notification.notify_info)
if self.iterate == False: if self.is_iterable == False:
if len(NOTIFY_INFO) == 1 and isinstance(NOTIFY_INFO[0], list): if len(NOTIFY_INFO) == 1 and isinstance(NOTIFY_INFO[0], list):
NOTIFY_INFO = NOTIFY_INFO[0] NOTIFY_INFO = NOTIFY_INFO[0]

View File

@@ -1,21 +1,29 @@
import time, hmac, hashlib, json, requests import time, hmac, hashlib, json, requests
from typing import TYPE_CHECKING, Optional, Any
from http import HTTPStatus from http import HTTPStatus
from .enums import Error from .enums import Error
from .exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError from .exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError
from ..utils.JSONEncoder import JSONEncoder from ..utils.JSONEncoder import JSONEncoder
if TYPE_CHECKING:
from requests.sessions import _Params
class _Requests(object): class _Requests(object):
def __init__(self, host, API_KEY = None, API_SECRET = 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, data): def __build_authentication_headers(self, endpoint: str, data: str):
assert isinstance(self.API_KEY, str) and isinstance(self.API_SECRET, str), \
"API_KEY and API_SECRET must be both str to call __build_authentication_headers"
nonce = str(int(time.time()) * 1000) nonce = str(int(time.time()) * 1000)
path = f"/api/v2/{endpoint}{nonce}" if data == None:
path = f"/api/v2/{endpoint}{nonce}"
if data != None: path += 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"),
@@ -29,7 +37,7 @@ class _Requests(object):
"bfx-apikey": self.API_KEY "bfx-apikey": self.API_KEY
} }
def _GET(self, endpoint, params = None): def _GET(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
response = requests.get(f"{self.host}/{endpoint}", params=params) response = requests.get(f"{self.host}/{endpoint}", params=params)
if response.status_code == HTTPStatus.NOT_FOUND: if response.status_code == HTTPStatus.NOT_FOUND:
@@ -46,11 +54,10 @@ class _Requests(object):
return data return data
def _POST(self, endpoint, params = None, data = None, _ignore_authentication_headers = False): def _POST(self, endpoint: str, params: Optional["_Params"] = None, body: Optional[Any] = None, _ignore_authentication_headers: bool = False) -> Any:
headers = { "Content-Type": "application/json" } data = json.dumps(body, cls=JSONEncoder)
if isinstance(data, dict): headers = { "Content-Type": "application/json" }
data = json.dumps({ key: value for key, value in data.items() if value != None}, cls=JSONEncoder)
if self.API_KEY and self.API_SECRET and _ignore_authentication_headers == False: if self.API_KEY and self.API_SECRET and _ignore_authentication_headers == False:
headers = { **headers, **self.__build_authentication_headers(endpoint, data) } headers = { **headers, **self.__build_authentication_headers(endpoint, data) }

View File

@@ -20,14 +20,14 @@ class _RestAuthenticatedEndpoints(_Requests):
if symbol != None: if symbol != None:
endpoint += f"/{symbol}" endpoint += f"/{symbol}"
return [ serializers.Order.parse(*sub_data) for sub_data in self._POST(endpoint, data={ "id": ids }) ] return [ serializers.Order.parse(*sub_data) for sub_data in self._POST(endpoint, body={ "id": ids }) ]
def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, float, str], def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, float, str],
price: Optional[Union[Decimal, float, str]] = None, lev: Optional[int] = None, price: Optional[Union[Decimal, float, str]] = None, lev: Optional[int] = None,
price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None,
gid: Optional[int] = None, cid: Optional[int] = None, gid: Optional[int] = None, cid: Optional[int] = None,
flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification[Order]: flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification[Order]:
data = { body = {
"type": type, "symbol": symbol, "amount": amount, "type": type, "symbol": symbol, "amount": amount,
"price": price, "lev": lev, "price": price, "lev": lev,
"price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop,
@@ -35,32 +35,32 @@ class _RestAuthenticatedEndpoints(_Requests):
"flags": flags, "tif": tif, "meta": meta "flags": flags, "tif": tif, "meta": meta
} }
return serializers._Notification[Order](serializer=serializers.Order).parse(*self._POST("auth/w/order/submit", data=data)) return serializers._Notification[Order](serializers.Order).parse(*self._POST("auth/w/order/submit", body=body))
def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None, def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None,
cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None,
flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None, flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None,
price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification[Order]: price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification[Order]:
data = { body = {
"id": id, "amount": amount, "price": price, "id": id, "amount": amount, "price": price,
"cid": cid, "cid_date": cid_date, "gid": gid, "cid": cid, "cid_date": cid_date, "gid": gid,
"flags": flags, "lev": lev, "delta": delta, "flags": flags, "lev": lev, "delta": delta,
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
} }
return serializers._Notification[Order](serializer=serializers.Order).parse(*self._POST("auth/w/order/update", data=data)) return serializers._Notification[Order](serializers.Order).parse(*self._POST("auth/w/order/update", body=body))
def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None) -> Notification[Order]: def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None) -> Notification[Order]:
data = { body = {
"id": id, "id": id,
"cid": cid, "cid": cid,
"cid_date": cid_date "cid_date": cid_date
} }
return serializers._Notification[Order](serializer=serializers.Order).parse(*self._POST("auth/w/order/cancel", data=data)) return serializers._Notification[Order](serializers.Order).parse(*self._POST("auth/w/order/cancel", body=body))
def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False) -> Notification[List[Order]]: def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False) -> Notification[List[Order]]:
data = { body = {
"ids": ids, "ids": ids,
"cids": cids, "cids": cids,
"gids": gids, "gids": gids,
@@ -68,20 +68,20 @@ class _RestAuthenticatedEndpoints(_Requests):
"all": int(all) "all": int(all)
} }
return serializers._Notification[List[Order]](serializer=serializers.Order, iterate=True).parse(*self._POST("auth/w/order/cancel/multi", data=data)) return serializers._Notification[List[Order]](serializers.Order, is_iterable=True).parse(*self._POST("auth/w/order/cancel/multi", body=body))
def get_orders_history(self, symbol: Optional[str] = None, ids: Optional[List[int]] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Order]: def get_orders_history(self, symbol: Optional[str] = None, ids: Optional[List[int]] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Order]:
if symbol == None: if symbol == 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"
data = { body = {
"id": ids, "id": ids,
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.Order.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.Order.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]: def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
return [ serializers.OrderTrade.parse(*sub_data) for sub_data in self._POST(f"auth/r/order/{symbol}:{id}/trades") ] return [ serializers.OrderTrade.parse(*sub_data) for sub_data in self._POST(f"auth/r/order/{symbol}:{id}/trades") ]
@@ -91,22 +91,22 @@ class _RestAuthenticatedEndpoints(_Requests):
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"
data = { body = {
"sort": sort, "sort": sort,
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.Trade.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.Trade.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def get_ledgers(self, currency: str, category: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Ledger]: def get_ledgers(self, currency: str, category: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Ledger]:
data = { body = {
"category": category, "category": category,
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.Ledger.parse(*sub_data) for sub_data in self._POST(f"auth/r/ledgers/{currency}/hist", data=data) ] return [ serializers.Ledger.parse(*sub_data) for sub_data in self._POST(f"auth/r/ledgers/{currency}/hist", body=body) ]
def get_base_margin_info(self) -> BaseMarginInfo: def get_base_margin_info(self) -> BaseMarginInfo:
return serializers.BaseMarginInfo.parse(*(self._POST(f"auth/r/info/margin/base")[1])) return serializers.BaseMarginInfo.parse(*(self._POST(f"auth/r/info/margin/base")[1]))
@@ -123,36 +123,36 @@ class _RestAuthenticatedEndpoints(_Requests):
return [ serializers.Position.parse(*sub_data) for sub_data in self._POST("auth/r/positions") ] return [ serializers.Position.parse(*sub_data) for sub_data in self._POST("auth/r/positions") ]
def claim_position(self, id: int, amount: Optional[Union[Decimal, float, str]] = None) -> Notification[PositionClaim]: def claim_position(self, id: int, amount: Optional[Union[Decimal, float, str]] = None) -> Notification[PositionClaim]:
return serializers._Notification[PositionClaim](serializer=serializers.PositionClaim).parse( return serializers._Notification[PositionClaim](serializers.PositionClaim).parse(
*self._POST("auth/w/position/claim", data={ "id": id, "amount": amount }) *self._POST("auth/w/position/claim", body={ "id": id, "amount": amount })
) )
def increase_position(self, symbol: str, amount: Union[Decimal, float, str]) -> Notification[PositionIncrease]: def increase_position(self, symbol: str, amount: Union[Decimal, float, str]) -> Notification[PositionIncrease]:
return serializers._Notification[PositionIncrease](serializer=serializers.PositionIncrease).parse( return serializers._Notification[PositionIncrease](serializers.PositionIncrease).parse(
*self._POST("auth/w/position/increase", data={ "symbol": symbol, "amount": amount }) *self._POST("auth/w/position/increase", body={ "symbol": symbol, "amount": amount })
) )
def get_increase_position_info(self, symbol: str, amount: Union[Decimal, float, str]) -> PositionIncreaseInfo: def get_increase_position_info(self, symbol: str, amount: Union[Decimal, float, str]) -> PositionIncreaseInfo:
response = self._POST(f"auth/r/position/increase/info", data={ "symbol": symbol, "amount": amount }) response = self._POST(f"auth/r/position/increase/info", body={ "symbol": symbol, "amount": amount })
return serializers.PositionIncreaseInfo.parse(*( return serializers.PositionIncreaseInfo.parse(*(
response[0] + [response[1][0]] + response[1][1] + [response[1][2]] + response[4] + response[5] response[0] + [response[1][0]] + response[1][1] + [response[1][2]] + response[4] + response[5]
)) ))
def get_positions_history(self, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionHistory]: def get_positions_history(self, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionHistory]:
return [ serializers.PositionHistory.parse(*sub_data) for sub_data in self._POST("auth/r/positions/hist", data={ "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, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionSnapshot]: def get_positions_snapshot(self, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionSnapshot]:
return [ serializers.PositionSnapshot.parse(*sub_data) for sub_data in self._POST("auth/r/positions/snap", data={ "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, ids: Optional[List[int]] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionAudit]: def get_positions_audit(self, ids: Optional[List[int]] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionAudit]:
return [ serializers.PositionAudit.parse(*sub_data) for sub_data in self._POST("auth/r/positions/audit", data={ "ids": ids, "start": start, "end": end, "limit": limit }) ] return [ serializers.PositionAudit.parse(*sub_data) for sub_data in self._POST("auth/r/positions/audit", body={ "ids": ids, "start": start, "end": end, "limit": limit }) ]
def set_derivative_position_collateral(self, symbol: str, collateral: Union[Decimal, float, str]) -> DerivativePositionCollateral: def set_derivative_position_collateral(self, symbol: str, collateral: Union[Decimal, float, str]) -> DerivativePositionCollateral:
return serializers.DerivativePositionCollateral.parse(*(self._POST("auth/w/deriv/collateral/set", data={ "symbol": symbol, "collateral": collateral })[0])) return serializers.DerivativePositionCollateral.parse(*(self._POST("auth/w/deriv/collateral/set", body={ "symbol": symbol, "collateral": collateral })[0]))
def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits: def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits:
return serializers.DerivativePositionCollateralLimits.parse(*self._POST("auth/calc/deriv/collateral/limits", data={ "symbol": symbol })) return serializers.DerivativePositionCollateralLimits.parse(*self._POST("auth/calc/deriv/collateral/limits", 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]:
endpoint = "auth/r/funding/offers" endpoint = "auth/r/funding/offers"
@@ -165,36 +165,36 @@ class _RestAuthenticatedEndpoints(_Requests):
def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str], def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str],
rate: Union[Decimal, float, str], period: int, rate: Union[Decimal, float, str], period: int,
flags: Optional[int] = 0) -> Notification[FundingOffer]: flags: Optional[int] = 0) -> Notification[FundingOffer]:
data = { body = {
"type": type, "symbol": symbol, "amount": amount, "type": type, "symbol": symbol, "amount": amount,
"rate": rate, "period": period, "rate": rate, "period": period,
"flags": flags "flags": flags
} }
return serializers._Notification[FundingOffer](serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/submit", data=data)) return serializers._Notification[FundingOffer](serializers.FundingOffer).parse(*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 serializers._Notification[FundingOffer](serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/cancel", data={ "id": id })) return serializers._Notification[FundingOffer](serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/cancel", body={ "id": id }))
def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]: def cancel_all_funding_offers(self, currency: str) -> Notification[Literal[None]]:
return serializers._Notification[Literal[None]](serializer=None).parse( return serializers._Notification[Literal[None]](None).parse(
*self._POST("auth/w/funding/offer/cancel/all", data={ "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 serializers._Notification[Literal[None]](serializer=None).parse( return serializers._Notification[Literal[None]](None).parse(
*self._POST("auth/w/funding/close", data={ "id": id }) *self._POST("auth/w/funding/close", body={ "id": id })
) )
def toggle_auto_renew(self, status: bool, currency: str, amount: Optional[str] = None, rate: Optional[int] = None, period: Optional[int] = None) -> Notification[FundingAutoRenew]: def toggle_auto_renew(self, status: bool, currency: str, amount: Optional[str] = None, rate: Optional[int] = None, period: Optional[int] = None) -> Notification[FundingAutoRenew]:
return serializers._Notification[FundingAutoRenew](serializer=serializers.FundingAutoRenew).parse(*self._POST("auth/w/funding/auto", data={ return serializers._Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(*self._POST("auth/w/funding/auto", body={
"status": int(status), "status": int(status),
"currency": currency, "amount": amount, "currency": currency, "amount": amount,
"rate": rate, "period": period "rate": rate, "period": period
})) }))
def toggle_keep(self, type: Literal["credit", "loan"], ids: Optional[List[int]] = None, changes: Optional[Dict[int, bool]] = None) -> Notification[Literal[None]]: def toggle_keep(self, type: Literal["credit", "loan"], ids: Optional[List[int]] = None, changes: Optional[Dict[int, bool]] = None) -> Notification[Literal[None]]:
return serializers._Notification[Literal[None]](serializer=None).parse(*self._POST("auth/w/funding/keep", data={ return serializers._Notification[Literal[None]](None).parse(*self._POST("auth/w/funding/keep", body={
"type": type, "type": type,
"id": ids, "id": ids,
"changes": changes "changes": changes
@@ -205,12 +205,12 @@ class _RestAuthenticatedEndpoints(_Requests):
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"
data = { body = {
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.FundingOffer.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.FundingOffer.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def get_funding_loans(self, symbol: Optional[str] = None) -> List[FundingLoan]: def get_funding_loans(self, symbol: Optional[str] = None) -> List[FundingLoan]:
if symbol == None: if symbol == None:
@@ -224,12 +224,12 @@ class _RestAuthenticatedEndpoints(_Requests):
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"
data = { body = {
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.FundingLoan.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.FundingLoan.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def get_funding_credits(self, symbol: Optional[str] = None) -> List[FundingCredit]: def get_funding_credits(self, symbol: Optional[str] = None) -> List[FundingCredit]:
if symbol == None: if symbol == None:
@@ -243,25 +243,25 @@ class _RestAuthenticatedEndpoints(_Requests):
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"
data = { body = {
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.FundingCredit.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.FundingCredit.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
def get_funding_trades_history(self, symbol: Optional[str] = None, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingTrade]: def get_funding_trades_history(self, symbol: Optional[str] = None, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingTrade]:
if symbol == None: if symbol == 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"
data = { body = {
"sort": sort, "sort": sort,
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.FundingTrade.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ 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:
response = self._POST(f"auth/r/info/funding/{key}") response = self._POST(f"auth/r/info/funding/{key}")
@@ -269,45 +269,45 @@ class _RestAuthenticatedEndpoints(_Requests):
return serializers.FundingInfo.parse(*([response[1]] + response[2])) return serializers.FundingInfo.parse(*([response[1]] + response[2]))
def transfer_between_wallets(self, from_wallet: str, to_wallet: str, currency: str, currency_to: str, amount: Union[Decimal, float, str]) -> Notification[Transfer]: def transfer_between_wallets(self, from_wallet: str, to_wallet: str, currency: str, currency_to: str, amount: Union[Decimal, float, str]) -> Notification[Transfer]:
data = { body = {
"from": from_wallet, "to": to_wallet, "from": from_wallet, "to": to_wallet,
"currency": currency, "currency_to": currency_to, "currency": currency, "currency_to": currency_to,
"amount": amount "amount": amount
} }
return serializers._Notification[Transfer](serializer=serializers.Transfer).parse(*self._POST("auth/w/transfer", data=data)) return serializers._Notification[Transfer](serializers.Transfer).parse(*self._POST("auth/w/transfer", body=body))
def submit_wallet_withdrawal(self, wallet: str, method: str, address: str, amount: Union[Decimal, float, str]) -> Notification[Withdrawal]: def submit_wallet_withdrawal(self, wallet: str, method: str, address: str, amount: Union[Decimal, float, str]) -> Notification[Withdrawal]:
return serializers._Notification[Withdrawal](serializer=serializers.Withdrawal).parse(*self._POST("auth/w/withdraw", data={ return serializers._Notification[Withdrawal](serializers.Withdrawal).parse(*self._POST("auth/w/withdraw", body={
"wallet": wallet, "method": method, "wallet": wallet, "method": method,
"address": address, "amount": amount, "address": address, "amount": amount,
})) }))
def get_deposit_address(self, wallet: str, method: str, renew: bool = False) -> Notification[DepositAddress]: def get_deposit_address(self, wallet: str, method: str, renew: bool = False) -> Notification[DepositAddress]:
data = { body = {
"wallet": wallet, "wallet": wallet,
"method": method, "method": method,
"renew": int(renew) "renew": int(renew)
} }
return serializers._Notification[DepositAddress](serializer=serializers.DepositAddress).parse(*self._POST("auth/w/deposit/address", data=data)) return serializers._Notification[DepositAddress](serializers.DepositAddress).parse(*self._POST("auth/w/deposit/address", body=body))
def generate_deposit_invoice(self, wallet: str, currency: str, amount: Union[Decimal, float, str]) -> Invoice: def generate_deposit_invoice(self, wallet: str, currency: str, amount: Union[Decimal, float, str]) -> Invoice:
data = { body = {
"wallet": wallet, "currency": currency, "wallet": wallet, "currency": currency,
"amount": amount "amount": amount
} }
return serializers.Invoice.parse(*self._POST("auth/w/deposit/invoice", data=data)) return serializers.Invoice.parse(*self._POST("auth/w/deposit/invoice", body=body))
def get_movements(self, currency: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Movement]: def get_movements(self, currency: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Movement]:
if currency == None: if currency == 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"
data = { body = {
"start": start, "end": end, "start": start, "end": end,
"limit": limit "limit": limit
} }
return [ serializers.Movement.parse(*sub_data) for sub_data in self._POST(endpoint, data=data) ] return [ serializers.Movement.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]

View File

@@ -175,14 +175,14 @@ class _RestPublicEndpoints(_Requests):
return messages return messages
def get_trading_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], price_limit: Optional[Union[Decimal, float, str]] = None) -> TradingMarketAveragePrice: def get_trading_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], price_limit: Optional[Union[Decimal, float, str]] = None) -> TradingMarketAveragePrice:
return serializers.TradingMarketAveragePrice.parse(*self._POST("calc/trade/avg", data={ return serializers.TradingMarketAveragePrice.parse(*self._POST("calc/trade/avg", body={
"symbol": symbol, "amount": amount, "price_limit": price_limit "symbol": symbol, "amount": amount, "price_limit": price_limit
})) }))
def get_funding_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], period: int, rate_limit: Optional[Union[Decimal, float, str]] = None) -> FundingMarketAveragePrice: def get_funding_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], period: int, rate_limit: Optional[Union[Decimal, float, str]] = None) -> FundingMarketAveragePrice:
return serializers.FundingMarketAveragePrice.parse(*self._POST("calc/trade/avg", data={ return serializers.FundingMarketAveragePrice.parse(*self._POST("calc/trade/avg", body={
"symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit "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", data={ "ccy1": ccy1, "ccy2": ccy2 })) return serializers.FxRate.parse(*self._POST("calc/fx", body={ "ccy1": ccy1, "ccy2": ccy2 }))

View File

@@ -8,13 +8,16 @@ JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
class JSONEncoder(json.JSONEncoder): class JSONEncoder(json.JSONEncoder):
def encode(self, obj: JSON) -> str: def encode(self, obj: JSON) -> str:
def _strip(dictionary: Dict) -> Dict:
return { key: value for key, value in dictionary.items() if value != None}
def _convert_float_to_str(data: JSON) -> JSON: def _convert_float_to_str(data: JSON) -> JSON:
if isinstance(data, float): if isinstance(data, float):
return format(Decimal(repr(data)), "f") return format(Decimal(repr(data)), "f")
elif isinstance(data, list): elif isinstance(data, list):
return [ _convert_float_to_str(sub_data) for sub_data in data ] return [ _convert_float_to_str(sub_data) for sub_data in data ]
elif isinstance(data, dict): elif isinstance(data, dict):
return { key: _convert_float_to_str(value) for key, value in data.items() } return _strip({ key: _convert_float_to_str(value) for key, value in data.items() })
else: return data else: return data
data = _convert_float_to_str(obj) data = _convert_float_to_str(obj)

View File

@@ -5,9 +5,6 @@ from typing import Union, Optional, List, Tuple
from .types import JSON from .types import JSON
from .enums import OrderType, FundingOfferType from .enums import OrderType, FundingOfferType
def _strip(dictionary):
return { key: value for key, value in dictionary.items() if value != None}
class _BfxWebsocketInputs(object): class _BfxWebsocketInputs(object):
def __init__(self, __handle_websocket_input): def __init__(self, __handle_websocket_input):
self.__handle_websocket_input = __handle_websocket_input self.__handle_websocket_input = __handle_websocket_input
@@ -17,59 +14,44 @@ class _BfxWebsocketInputs(object):
price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_oco_stop: Optional[Union[Decimal, float, str]] = None,
gid: Optional[int] = None, cid: Optional[int] = None, gid: Optional[int] = None, cid: Optional[int] = None,
flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None): flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None):
data = _strip({ await self.__handle_websocket_input("on", {
"type": type, "symbol": symbol, "amount": amount, "type": type, "symbol": symbol, "amount": amount,
"price": price, "lev": lev, "price": price, "lev": lev,
"price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop, "price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop,
"gid": gid, "cid": cid, "gid": gid, "cid": cid,
"flags": flags, "tif": tif, "meta": meta "flags": flags, "tif": tif, "meta": meta
}) })
await self.__handle_websocket_input("on", data)
async def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None, async def update_order(self, id: int, amount: Optional[Union[Decimal, float, str]] = None, price: Optional[Union[Decimal, float, str]] = None,
cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None, gid: Optional[int] = None,
flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None, flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, float, str]] = None,
price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None): price_aux_limit: Optional[Union[Decimal, float, str]] = None, price_trailing: Optional[Union[Decimal, float, str]] = None, tif: Optional[Union[datetime, str]] = None):
data = _strip({ await self.__handle_websocket_input("ou", {
"id": id, "amount": amount, "price": price, "id": id, "amount": amount, "price": price,
"cid": cid, "cid_date": cid_date, "gid": gid, "cid": cid, "cid_date": cid_date, "gid": gid,
"flags": flags, "lev": lev, "delta": delta, "flags": flags, "lev": lev, "delta": delta,
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif "price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
}) })
await self.__handle_websocket_input("ou", data)
async def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None): async def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None):
data = _strip({ await self.__handle_websocket_input("oc", {
"id": id, "id": id, "cid": cid, "cid_date": cid_date
"cid": cid,
"cid_date": cid_date
}) })
await self.__handle_websocket_input("oc", data)
async def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False): async def cancel_order_multi(self, ids: Optional[List[int]] = None, cids: Optional[List[Tuple[int, str]]] = None, gids: Optional[List[int]] = None, all: bool = False):
data = _strip({ await self.__handle_websocket_input("oc_multi", {
"ids": ids, "ids": ids, "cids": cids, "gids": gids,
"cids": cids,
"gids": gids,
"all": int(all) "all": int(all)
}) })
await self.__handle_websocket_input("oc_multi", data)
async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str], async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str],
rate: Union[Decimal, float, str], period: int, rate: Union[Decimal, float, str], period: int,
flags: Optional[int] = 0): flags: Optional[int] = 0):
data = { await self.__handle_websocket_input("fon", {
"type": type, "symbol": symbol, "amount": amount, "type": type, "symbol": symbol, "amount": amount,
"rate": rate, "period": period, "rate": rate, "period": period,
"flags": flags "flags": flags
} })
await self.__handle_websocket_input("fon", data)
async def cancel_funding_offer(self, id: int): async def cancel_funding_offer(self, id: int):
await self.__handle_websocket_input("foc", { "id": id }) await self.__handle_websocket_input("foc", { "id": id })