From ea6044a5eb7ab7a6ed800c51ae02e2aac8d12231 Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Thu, 1 Dec 2022 17:48:50 +0100 Subject: [PATCH] Add support for new rest public endpoints (in BfxRestInterface.py, serializers.py and typings.py). --- bfxapi/rest/BfxRestInterface.py | 58 ++++++++++++++++++++-- bfxapi/rest/exceptions.py | 11 +++++ bfxapi/rest/serializers.py | 87 +++++++++++++++++++++++++++++---- bfxapi/rest/typings.py | 44 +++++++++++++++++ 4 files changed, 186 insertions(+), 14 deletions(-) diff --git a/bfxapi/rest/BfxRestInterface.py b/bfxapi/rest/BfxRestInterface.py index f7eed3b..7423c1a 100644 --- a/bfxapi/rest/BfxRestInterface.py +++ b/bfxapi/rest/BfxRestInterface.py @@ -1,12 +1,62 @@ import requests + +from http import HTTPStatus + +from typing import List, Union, Optional + from . import serializers -from .typings import PlatformStatus +from .typings import PlatformStatus, TradingPairTicker, FundingCurrencyTicker, TickerHistory, TradingPairTrade, FundingCurrencyTrade +from .exceptions import RequestParametersError class BfxRestInterface(object): def __init__(self, host): self.host = host + def __GET(self, endpoint, params = None): + data = requests.get(f"{self.host}/{endpoint}", params=params).json() + + if data[0] == "error": + if data[1] == 10020: + raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>") + + return data + def platform_status(self) -> PlatformStatus: - return serializers.PlatformStatus.parse( - *requests.get(f"{self.host}/platform/status").json() - ) + return serializers.PlatformStatus.parse(*self.__GET("platform/status")) + + def tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]: + return [ + { + "t": serializers.TradingPairTicker.parse, + "f": serializers.FundingCurrencyTicker.parse + }[subdata[0][0]](*subdata) + + for subdata in self.__GET("tickers", params={ "symbols": ",".join(symbols) }) + ] + + def ticker(self, symbol: str) -> Union[TradingPairTicker, FundingCurrencyTicker]: + return { + "t": serializers.TradingPairTicker.parse, + "f": serializers.FundingCurrencyTicker.parse + }[symbol[0]](*self.__GET(f"ticker/{symbol}"), skip=["SYMBOL"]) + + def tickers_hist(self, symbols: List[str], start: Optional[int] = None, end: Optional[int] = None, limit: Optional[int] = None) -> List[TickerHistory]: + params = { + "symbols": ",".join(symbols), + "start": start, "end": end, + "limit": limit + } + + return [ serializers.TickerHistory.parse(*subdata) for subdata in self.__GET("tickers/hist", params=params) ] + + def trades(self, symbol: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[int] = None) -> Union[List[TradingPairTrade], List[FundingCurrencyTicker]]: + params = { "symbol": symbol, "limit": limit, "start": start, "end": end, "sort": sort } + + return [ + { + "t": serializers.TradingPairTrade.parse, + "f": serializers.FundingCurrencyTrade.parse + }[symbol[0]](*subdata) + + for subdata in self.__GET(f"trades/{symbol}/hist", params=params) + ] \ No newline at end of file diff --git a/bfxapi/rest/exceptions.py b/bfxapi/rest/exceptions.py index 9aa8555..033848e 100644 --- a/bfxapi/rest/exceptions.py +++ b/bfxapi/rest/exceptions.py @@ -1,6 +1,17 @@ +__all__ = [ + "RequestParametersError" +] + class BfxRestException(Exception): """ Base class for all exceptions defined in bfxapi/rest/exceptions.py. """ + pass + +class RequestParametersError(BfxRestException): + """ + This error indicates that there are some invalid parameters sent along with an HTTP request. + """ + pass \ No newline at end of file diff --git a/bfxapi/rest/serializers.py b/bfxapi/rest/serializers.py index 5ffcb0e..a002cdf 100644 --- a/bfxapi/rest/serializers.py +++ b/bfxapi/rest/serializers.py @@ -1,4 +1,4 @@ -from typing import Generic, TypeVar, Iterable, List, Any +from typing import Generic, TypeVar, Iterable, Optional, List, Any from . import typings @@ -7,19 +7,21 @@ from .exceptions import BfxRestException T = TypeVar("T") class _Serializer(Generic[T]): - def __init__(self, name: str, labels: List[str]): - self.name, self.__labels = name, labels + def __init__(self, name: str, labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]): + self.name, self.__labels, self.__IGNORE = name, labels, IGNORE - def __serialize(self, *args: Any, IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> Iterable[T]: - if len(self.__labels) != len(args): - raise BfxRestException(" and <*args> arguments should contain the same amount of elements.") + def __serialize(self, *args: Any, skip: Optional[List[str]]) -> Iterable[T]: + labels = list(filter(lambda label: label not in (skip or list()), self.__labels)) - for index, label in enumerate(self.__labels): - if label not in IGNORE: + if len(labels) != len(args): + raise BfxRestException(" and <*args> arguments should contain the same amount of elements.") + + for index, label in enumerate(labels): + if label not in self.__IGNORE: yield label, args[index] - def parse(self, *values: Any) -> T: - return dict(self.__serialize(*values)) + def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T: + return dict(self.__serialize(*values, skip=skip)) #region Serializers definition for Rest Public Endpoints @@ -27,4 +29,69 @@ PlatformStatus = _Serializer[typings.PlatformStatus]("PlatformStatus", labels=[ "OPERATIVE" ]) +TradingPairTicker = _Serializer[typings.TradingPairTicker]("TradingPairTicker", labels=[ + "SYMBOL", + "BID", + "BID_SIZE", + "ASK", + "ASK_SIZE", + "DAILY_CHANGE", + "DAILY_CHANGE_RELATIVE", + "LAST_PRICE", + "VOLUME", + "HIGH", + "LOW" +]) + +FundingCurrencyTicker = _Serializer[typings.FundingCurrencyTicker]("FundingCurrencyTicker", labels=[ + "SYMBOL", + "FRR", + "BID", + "BID_PERIOD", + "BID_SIZE", + "ASK", + "ASK_PERIOD", + "ASK_SIZE", + "DAILY_CHANGE", + "DAILY_CHANGE_RELATIVE", + "LAST_PRICE", + "VOLUME", + "HIGH", + "LOW", + "_PLACEHOLDER", + "_PLACEHOLDER", + "FRR_AMOUNT_AVAILABLE" +]) + +TickerHistory = _Serializer[typings.TickerHistory]("TickerHistory", labels=[ + "SYMBOL", + "BID", + "_PLACEHOLDER", + "ASK", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "MTS" +]) + +TradingPairTrade = _Serializer[typings.TradingPairTrade]("TradingPairTrade", labels=[ + "ID", + "MTS", + "AMOUNT", + "PRICE" +]) + +FundingCurrencyTrade = _Serializer[typings.FundingCurrencyTrade]("FundingCurrencyTrade", labels=[ + "ID", + "MTS", + "AMOUNT", + "RATE", + "PERIOD" +]) + #endregion \ No newline at end of file diff --git a/bfxapi/rest/typings.py b/bfxapi/rest/typings.py index dd7732e..ebaf7c2 100644 --- a/bfxapi/rest/typings.py +++ b/bfxapi/rest/typings.py @@ -6,4 +6,48 @@ PlatformStatus = TypedDict("PlatformStatus", { "OPERATIVE": int }) +TradingPairTicker = TypedDict("TradingPairTicker", { + "SYMBOL": Optional[str], + "BID": float, + "BID_SIZE": float, + "ASK": float, + "ASK_SIZE": float, + "DAILY_CHANGE": float, + "DAILY_CHANGE_RELATIVE": float, + "LAST_PRICE": float, + "VOLUME": float, + "HIGH": float, + "LOW": float +}) + +FundingCurrencyTicker = TypedDict("FundingCurrencyTicker", { + "SYMBOL": Optional[str], + "FRR": float, + "BID": float, + "BID_PERIOD": int, + "BID_SIZE": float, + "ASK": float, + "ASK_PERIOD": int, + "ASK_SIZE": float, + "DAILY_CHANGE": float, + "DAILY_CHANGE_RELATIVE": float, + "LAST_PRICE": float, + "VOLUME": float, + "HIGH": float, + "LOW": float, + "FRR_AMOUNT_AVAILABLE": float +}) + +TickerHistory = TypedDict("TickerHistory", { + "SYMBOL": str, + "BID": float, + "ASK": float, + "MTS": int +}) + +(TradingPairTrade, FundingCurrencyTrade) = ( + TypedDict("TradingPairTrade", { "ID": int, "MTS": int, "AMOUNT": float, "PRICE": float }), + TypedDict("FundingCurrencyTrade", { "ID": int, "MTS": int, "AMOUNT": float, "RATE": float, "PERIOD": int }) +) + #endregion \ No newline at end of file