From cd5ef4211812ca146d13b35528acb37720a0004b Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Fri, 9 Dec 2022 16:16:15 +0100 Subject: [PATCH] Add support for new various endpoints. Add ResourceNotFound error in bfxapi/rest/exceptions.py. Fix bug in BfxRestInterface.__GET method. --- bfxapi/rest/BfxRestInterface.py | 64 +++++++++++++++++++++++++++++---- bfxapi/rest/exceptions.py | 10 +++++- bfxapi/rest/serializers.py | 41 +++++++++++++++++++++ bfxapi/rest/typings.py | 36 +++++++++++++++++++ 4 files changed, 143 insertions(+), 8 deletions(-) diff --git a/bfxapi/rest/BfxRestInterface.py b/bfxapi/rest/BfxRestInterface.py index 662a94c..e511692 100644 --- a/bfxapi/rest/BfxRestInterface.py +++ b/bfxapi/rest/BfxRestInterface.py @@ -2,20 +2,25 @@ import requests from http import HTTPStatus -from typing import List, Union, Optional +from typing import List, Union, Literal, Optional from . import serializers from .typings import * -from .exceptions import RequestParametersError +from .exceptions import RequestParametersError, ResourceNotFound 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() + response = requests.get(f"{self.host}/{endpoint}", params=params) - if data[0] == "error": + if response.status_code == HTTPStatus.NOT_FOUND: + raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.") + + data = response.json() + + if len(data) and data[0] == "error": if data[1] == 10020: raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>") @@ -40,7 +45,7 @@ class BfxRestInterface(object): "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) -> TickerHistories: + def tickers_history(self, symbols: List[str], start: Optional[int] = None, end: Optional[int] = None, limit: Optional[int] = None) -> TickerHistories: params = { "symbols": ",".join(symbols), "start": start, "end": end, @@ -61,7 +66,7 @@ class BfxRestInterface(object): for subdata in self.__GET(f"trades/{symbol}/hist", params=params) ] - def book(self, symbol: str, precision: str, len: Optional[int]) -> Union[TradingPairBooks, FundingCurrencyBooks, TradingPairRawBooks, FundingCurrencyRawBooks]: + def book(self, symbol: str, precision: str, len: Optional[int] = None) -> Union[TradingPairBooks, FundingCurrencyBooks, TradingPairRawBooks, FundingCurrencyRawBooks]: return [ { "t": precision == "R0" and serializers.TradingPairRawBook.parse or serializers.TradingPairBook.parse, @@ -69,4 +74,49 @@ class BfxRestInterface(object): }[symbol[0]](*subdata) for subdata in self.__GET(f"book/{symbol}/{precision}", params={ "len": len }) - ] \ No newline at end of file + ] + + def stats( + self, + resource: str, section: Literal["hist", "last"], + sort: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None + ) -> Union[Stat, Stats]: + endpoint = f"stats1/{resource}/{section}" + + params = { "sort": sort, "start": start, "end": end, "limit": limit } + + if section == "last": + return serializers.Stat.parse(*self.__GET(endpoint, params=params)) + return [ serializers.Stat.parse(*subdata) for subdata in self.__GET(endpoint, params=params) ] + + def candles( + self, + resource: str, section: Literal["hist", "last"], + sort: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None + ) -> Union[Candle, Candles]: + endpoint = f"candles/{resource}/{section}" + + params = { "sort": sort, "start": start, "end": end, "limit": limit } + + if section == "last": + return serializers.Candle.parse(*self.__GET(endpoint, params=params)) + return [ serializers.Candle.parse(*subdata) for subdata in self.__GET(endpoint, params=params) ] + + def derivatives_status(self, type: str, keys: Optional[List[str]] = None) -> DerivativeStatuses: + params = None + + if keys != None: + params = { "keys": ",".join(keys) } + + return [ serializers.DerivativesStatus.parse(*subdata) for subdata in self.__GET(f"status/{type}", params=params) ] + + def derivatives_status_history( + self, + type: str, symbol: str, + sort: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None + ) -> DerivativeStatuses: + endpoint = f"status/{type}/{symbol}/hist" + + params = { "sort": sort, "start": start, "end": end, "limit": limit } + + return [ serializers.DerivativesStatus.parse(*subdata, skip=[ "KEY" ]) for subdata in self.__GET(endpoint, params=params) ] \ No newline at end of file diff --git a/bfxapi/rest/exceptions.py b/bfxapi/rest/exceptions.py index 033848e..8afc74e 100644 --- a/bfxapi/rest/exceptions.py +++ b/bfxapi/rest/exceptions.py @@ -1,5 +1,6 @@ __all__ = [ - "RequestParametersError" + "RequestParametersError", + "ResourceNotFound" ] class BfxRestException(Exception): @@ -14,4 +15,11 @@ class RequestParametersError(BfxRestException): This error indicates that there are some invalid parameters sent along with an HTTP request. """ + pass + +class ResourceNotFound(BfxRestException): + """ + This error indicates a failed HTTP request to a non-existent resource. + """ + pass \ No newline at end of file diff --git a/bfxapi/rest/serializers.py b/bfxapi/rest/serializers.py index d1c64c2..43f6ace 100644 --- a/bfxapi/rest/serializers.py +++ b/bfxapi/rest/serializers.py @@ -120,4 +120,45 @@ FundingCurrencyRawBook = _Serializer[typings.FundingCurrencyRawBook]("FundingCur "AMOUNT" ]) +Stat = _Serializer[typings.Stat]("Stat", labels=[ + "MTS", + "VALUE" +]) + +Candle = _Serializer[typings.Candle]("Candle", labels=[ + "MTS", + "OPEN", + "CLOSE", + "HIGH", + "LOW", + "VOLUME" +]) + +DerivativesStatus = _Serializer[typings.DerivativesStatus]("DerivativesStatus", labels=[ + "KEY", + "MTS", + "_PLACEHOLDER", + "DERIV_PRICE", + "SPOT_PRICE", + "_PLACEHOLDER", + "INSURANCE_FUND_BALANCE", + "_PLACEHOLDER", + "NEXT_FUNDING_EVT_TIMESTAMP_MS", + "NEXT_FUNDING_ACCRUED", + "NEXT_FUNDING_STEP", + "_PLACEHOLDER", + "CURRENT_FUNDING", + "_PLACEHOLDER", + "_PLACEHOLDER", + "MARK_PRICE", + "_PLACEHOLDER", + "_PLACEHOLDER", + "OPEN_INTEREST", + "_PLACEHOLDER", + "_PLACEHOLDER", + "_PLACEHOLDER", + "CLAMP_MIN", + "CLAMP_MAX" +]) + #endregion \ No newline at end of file diff --git a/bfxapi/rest/typings.py b/bfxapi/rest/typings.py index 3b52a49..7c8f127 100644 --- a/bfxapi/rest/typings.py +++ b/bfxapi/rest/typings.py @@ -68,4 +68,40 @@ TickerHistories = List[TickerHistory] (TradingPairRawBooks, FundingCurrencyRawBooks) = (List[TradingPairRawBook], List[FundingCurrencyRawBook]) +Stat = TypedDict("Stat", { + "MTS": int, + "VALUE": float +}) + +Stats = List[Stat] + +Candle = TypedDict("Candle", { + "MTS": int, + "OPEN": float, + "CLOSE": float, + "HIGH": float, + "LOW": float, + "VOLUME": float +}) + +Candles = List[Candle] + +DerivativesStatus = TypedDict("DerivativesStatus", { + "KEY": str, + "MTS": int, + "DERIV_PRICE": float, + "SPOT_PRICE": float, + "INSURANCE_FUND_BALANCE": float, + "NEXT_FUNDING_EVT_TIMESTAMP_MS": int, + "NEXT_FUNDING_ACCRUED": float, + "NEXT_FUNDING_STEP": int, + "CURRENT_FUNDING": float, + "MARK_PRICE": float, + "OPEN_INTEREST": float, + "CLAMP_MIN": float, + "CLAMP_MAX": float +}) + +DerivativeStatuses = List[DerivativesStatus] + #endregion \ No newline at end of file