mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2026-02-16 02:54:29 +01:00
Merge pull request #36 from Davi0kProgramsThings/feature/rest
Merge branch `feature/rest` in branch `master`.
This commit is contained in:
1
LICENSE
1
LICENSE
@@ -1,3 +1,4 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
from .client import Client, Constants
|
||||
from .client import Client
|
||||
|
||||
from .urls import REST_HOST, PUB_REST_HOST, STAGING_REST_HOST, \
|
||||
WSS_HOST, PUB_WSS_HOST, STAGING_WSS_HOST
|
||||
|
||||
NAME = "bfxapi"
|
||||
@@ -1,35 +1,31 @@
|
||||
from .rest import BfxRestInterface
|
||||
from .websocket import BfxWebsocketClient
|
||||
from .urls import REST_HOST, WSS_HOST
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class Constants(str, Enum):
|
||||
REST_HOST = "https://api.bitfinex.com/v2"
|
||||
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
|
||||
|
||||
WSS_HOST = "wss://api.bitfinex.com/ws/2"
|
||||
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
|
||||
from typing import List, Optional
|
||||
|
||||
class Client(object):
|
||||
def __init__(
|
||||
self,
|
||||
REST_HOST: str = Constants.REST_HOST,
|
||||
WSS_HOST: str = Constants.WSS_HOST,
|
||||
REST_HOST: str = REST_HOST,
|
||||
WSS_HOST: str = WSS_HOST,
|
||||
API_KEY: Optional[str] = None,
|
||||
API_SECRET: Optional[str] = None,
|
||||
log_level: str = "WARNING"
|
||||
filter: Optional[List[str]] = None,
|
||||
log_level: str = "INFO"
|
||||
):
|
||||
credentials = None
|
||||
|
||||
if API_KEY and API_SECRET:
|
||||
credentials = { "API_KEY": API_KEY, "API_SECRET": API_SECRET, "filter": filter }
|
||||
|
||||
self.rest = BfxRestInterface(
|
||||
host=REST_HOST,
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET
|
||||
credentials=credentials
|
||||
)
|
||||
|
||||
self.wss = BfxWebsocketClient(
|
||||
host=WSS_HOST,
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
credentials=credentials,
|
||||
log_level=log_level
|
||||
)
|
||||
@@ -1,9 +1,7 @@
|
||||
__all__ = [
|
||||
"BfxBaseException",
|
||||
|
||||
|
||||
"LabelerSerializerException",
|
||||
"IntegerUnderflowError",
|
||||
"IntegerOverflowflowError"
|
||||
]
|
||||
|
||||
class BfxBaseException(Exception):
|
||||
@@ -18,18 +16,4 @@ class LabelerSerializerException(BfxBaseException):
|
||||
This exception indicates an error thrown by the _Serializer class in bfxapi/labeler.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class IntegerUnderflowError(BfxBaseException):
|
||||
"""
|
||||
This error indicates an underflow in one of the integer types defined in bfxapi/utils/integers.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class IntegerOverflowflowError(BfxBaseException):
|
||||
"""
|
||||
This error indicates an overflow in one of the integer types defined in bfxapi/utils/integers.py.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,22 +1,77 @@
|
||||
from .exceptions import LabelerSerializerException
|
||||
|
||||
from typing import Generic, TypeVar, Iterable, Optional, List, Tuple, Any, cast
|
||||
from typing import Type, Generic, TypeVar, Iterable, Optional, Dict, List, Tuple, Any, cast
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar("T", bound="_Type")
|
||||
|
||||
def compose(*decorators):
|
||||
def wrapper(function):
|
||||
for decorator in reversed(decorators):
|
||||
function = decorator(function)
|
||||
return function
|
||||
|
||||
return wrapper
|
||||
|
||||
def partial(cls):
|
||||
def __init__(self, **kwargs):
|
||||
for annotation in self.__annotations__.keys():
|
||||
if annotation not in kwargs:
|
||||
self.__setattr__(annotation, None)
|
||||
else: self.__setattr__(annotation, kwargs[annotation])
|
||||
|
||||
kwargs.pop(annotation, None)
|
||||
|
||||
if len(kwargs) != 0:
|
||||
raise TypeError(f"{cls.__name__}.__init__() got an unexpected keyword argument '{list(kwargs.keys())[0]}'")
|
||||
|
||||
cls.__init__ = __init__
|
||||
|
||||
return cls
|
||||
|
||||
class _Type(object):
|
||||
"""
|
||||
Base class for any dataclass serializable by the _Serializer generic class.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class _Serializer(Generic[T]):
|
||||
def __init__(self, name: str, labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]):
|
||||
self.name, self.__labels, self.__IGNORE = name, labels, IGNORE
|
||||
def __init__(self, name: str, klass: Type[_Type], labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]):
|
||||
self.name, self.klass, self.__labels, self.__IGNORE = name, klass, labels, IGNORE
|
||||
|
||||
def _serialize(self, *args: Any, skip: Optional[List[str]] = None) -> Iterable[Tuple[str, Any]]:
|
||||
labels = list(filter(lambda label: label not in (skip or list()), self.__labels))
|
||||
|
||||
if len(labels) > len(args):
|
||||
raise LabelerSerializerException("<labels> and <*args> arguments should contain the same amount of elements.")
|
||||
raise LabelerSerializerException(f"{self.name} -> <labels> 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, skip: Optional[List[str]] = None) -> T:
|
||||
return cast(T, dict(self._serialize(*values, skip=skip)))
|
||||
return cast(T, self.klass(**dict(self._serialize(*values, skip=skip))))
|
||||
|
||||
def get_labels(self) -> List[str]:
|
||||
return [ label for label in self.__labels if label not in self.__IGNORE ]
|
||||
|
||||
class _RecursiveSerializer(_Serializer, Generic[T]):
|
||||
def __init__(self, name: str, klass: Type[_Type], labels: List[str], serializers: Dict[str, _Serializer[Any]], IGNORE: List[str] = ["_PLACEHOLDER"]):
|
||||
super().__init__(name, klass, labels, IGNORE)
|
||||
|
||||
self.serializers = serializers
|
||||
|
||||
def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T:
|
||||
serialization = dict(self._serialize(*values, skip=skip))
|
||||
|
||||
for key in serialization:
|
||||
if key in self.serializers.keys():
|
||||
serialization[key] = self.serializers[key].parse(*serialization[key], skip=skip)
|
||||
|
||||
return cast(T, self.klass(**serialization))
|
||||
|
||||
def generate_labeler_serializer(name: str, klass: Type[T], labels: List[str], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _Serializer[T]:
|
||||
return _Serializer[T](name, klass, labels, IGNORE)
|
||||
|
||||
def generate_recursive_serializer(name: str, klass: Type[T], labels: List[str], serializers: Dict[str, _Serializer[Any]], IGNORE: List[str] = [ "_PLACEHOLDER" ]) -> _RecursiveSerializer[T]:
|
||||
return _RecursiveSerializer[T](name, klass, labels, serializers, IGNORE)
|
||||
@@ -1,35 +1,38 @@
|
||||
from typing import List, Dict, Union, Optional, Any, TypedDict, cast
|
||||
from typing import List, Dict, Union, Optional, Any, TypedDict, Generic, TypeVar, cast
|
||||
from dataclasses import dataclass
|
||||
from .labeler import _Type, _Serializer
|
||||
|
||||
from .labeler import _Serializer
|
||||
T = TypeVar("T")
|
||||
|
||||
class Notification(TypedDict):
|
||||
MTS: int
|
||||
TYPE: str
|
||||
MESSAGE_ID: Optional[int]
|
||||
NOTIFY_INFO: Union[Dict[str, Any], List[Dict[str, Any]]]
|
||||
CODE: Optional[int]
|
||||
STATUS: str
|
||||
TEXT: str
|
||||
@dataclass
|
||||
class Notification(_Type, Generic[T]):
|
||||
mts: int
|
||||
type: str
|
||||
message_id: Optional[int]
|
||||
notify_info: T
|
||||
code: Optional[int]
|
||||
status: str
|
||||
text: str
|
||||
|
||||
class _Notification(_Serializer):
|
||||
__LABELS = [ "MTS", "TYPE", "MESSAGE_ID", "_PLACEHOLDER", "NOTIFY_INFO", "CODE", "STATUS", "TEXT" ]
|
||||
class _Notification(_Serializer, Generic[T]):
|
||||
__LABELS = [ "mts", "type", "message_id", "_PLACEHOLDER", "notify_info", "code", "status", "text" ]
|
||||
|
||||
def __init__(self, serializer: Optional[_Serializer] = None, iterate: bool = False):
|
||||
super().__init__("Notification", _Notification.__LABELS, IGNORE = [ "_PLACEHOLDER" ])
|
||||
def __init__(self, serializer: Optional[_Serializer] = None, is_iterable: bool = False):
|
||||
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:
|
||||
notification = dict(self._serialize(*values))
|
||||
def parse(self, *values: Any, skip: Optional[List[str]] = None) -> Notification[T]:
|
||||
notification = cast(Notification[T], Notification(**dict(self._serialize(*values))))
|
||||
|
||||
if isinstance(self.serializer, _Serializer):
|
||||
if self.iterate == False:
|
||||
NOTIFY_INFO = notification["NOTIFY_INFO"]
|
||||
NOTIFY_INFO = cast(List[Any], notification.notify_info)
|
||||
|
||||
if self.is_iterable == False:
|
||||
if len(NOTIFY_INFO) == 1 and isinstance(NOTIFY_INFO[0], list):
|
||||
NOTIFY_INFO = NOTIFY_INFO[0]
|
||||
|
||||
notification["NOTIFY_INFO"] = dict(self.serializer._serialize(*NOTIFY_INFO, skip=skip))
|
||||
else: notification["NOTIFY_INFO"] = [ dict(self.serializer._serialize(*data, skip=skip)) for data in notification["NOTIFY_INFO"] ]
|
||||
notification.notify_info = cast(T, self.serializer.klass(**dict(self.serializer._serialize(*NOTIFY_INFO, skip=skip))))
|
||||
else: notification.notify_info = cast(T, [ self.serializer.klass(**dict(self.serializer._serialize(*data, skip=skip))) for data in NOTIFY_INFO ])
|
||||
|
||||
return cast(Notification, notification)
|
||||
return notification
|
||||
@@ -1,398 +0,0 @@
|
||||
import time, hmac, hashlib, json, requests
|
||||
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
from http import HTTPStatus
|
||||
|
||||
from typing import List, Union, Literal, Optional, Any, cast
|
||||
|
||||
from . import serializers
|
||||
|
||||
from .typings import *
|
||||
from .enums import Config, Sort, OrderType, FundingOfferType, Error
|
||||
from .exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError
|
||||
|
||||
from .. utils.encoder import JSONEncoder
|
||||
|
||||
class BfxRestInterface(object):
|
||||
def __init__(self, host, API_KEY = None, API_SECRET = None):
|
||||
self.public = _RestPublicEndpoints(host=host)
|
||||
|
||||
self.auth = _RestAuthenticatedEndpoints(host=host, API_KEY=API_KEY, API_SECRET=API_SECRET)
|
||||
|
||||
class _Requests(object):
|
||||
def __init__(self, host, API_KEY = None, API_SECRET = None):
|
||||
self.host, self.API_KEY, self.API_SECRET = host, API_KEY, API_SECRET
|
||||
|
||||
def __build_authentication_headers(self, endpoint, data):
|
||||
nonce = str(int(time.time()) * 1000)
|
||||
|
||||
path = f"/api/v2/{endpoint}{nonce}"
|
||||
|
||||
if data != None: path += data
|
||||
|
||||
signature = hmac.new(
|
||||
self.API_SECRET.encode("utf8"),
|
||||
path.encode("utf8"),
|
||||
hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
return {
|
||||
"bfx-nonce": nonce,
|
||||
"bfx-signature": signature,
|
||||
"bfx-apikey": self.API_KEY
|
||||
}
|
||||
|
||||
def _GET(self, endpoint, params = None):
|
||||
response = requests.get(f"{self.host}/{endpoint}", params=params)
|
||||
|
||||
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] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError("The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
|
||||
def _POST(self, endpoint, params = None, data = None, _append_authentication_headers = True):
|
||||
headers = { "Content-Type": "application/json" }
|
||||
|
||||
if isinstance(data, dict):
|
||||
data = json.dumps({ key: value for key, value in data.items() if value != None}, cls=JSONEncoder)
|
||||
|
||||
if _append_authentication_headers:
|
||||
headers = { **headers, **self.__build_authentication_headers(endpoint, data) }
|
||||
|
||||
response = requests.post(f"{self.host}/{endpoint}", params=params, data=data, headers=headers)
|
||||
|
||||
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] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == Error.ERR_AUTH_FAIL:
|
||||
raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError(f"The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
|
||||
class _RestPublicEndpoints(_Requests):
|
||||
def get_platform_status(self) -> PlatformStatus:
|
||||
return serializers.PlatformStatus.parse(*self._GET("platform/status"))
|
||||
|
||||
def get_tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._GET("tickers", params={ "symbols": ",".join(symbols) })
|
||||
|
||||
parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
|
||||
|
||||
return [ parsers[subdata[0][0]](*subdata) for subdata in data ]
|
||||
|
||||
def get_t_tickers(self, pairs: Union[List[str], Literal["ALL"]]) -> List[TradingPairTicker]:
|
||||
if isinstance(pairs, str) and pairs == "ALL":
|
||||
return [ cast(TradingPairTicker, subdata) for subdata in self.get_tickers([ "ALL" ]) if cast(str, subdata["SYMBOL"]).startswith("t") ]
|
||||
|
||||
data = self.get_tickers([ "t" + pair for pair in pairs ])
|
||||
|
||||
return cast(List[TradingPairTicker], data)
|
||||
|
||||
def get_f_tickers(self, currencies: Union[List[str], Literal["ALL"]]) -> List[FundingCurrencyTicker]:
|
||||
if isinstance(currencies, str) and currencies == "ALL":
|
||||
return [ cast(FundingCurrencyTicker, subdata) for subdata in self.get_tickers([ "ALL" ]) if cast(str, subdata["SYMBOL"]).startswith("f") ]
|
||||
|
||||
data = self.get_tickers([ "f" + currency for currency in currencies ])
|
||||
|
||||
return cast(List[FundingCurrencyTicker], data)
|
||||
|
||||
def get_t_ticker(self, pair: str) -> TradingPairTicker:
|
||||
return serializers.TradingPairTicker.parse(*self._GET(f"ticker/t{pair}"), skip=["SYMBOL"])
|
||||
|
||||
def get_f_ticker(self, currency: str) -> FundingCurrencyTicker:
|
||||
return serializers.FundingCurrencyTicker.parse(*self._GET(f"ticker/f{currency}"), skip=["SYMBOL"])
|
||||
|
||||
def get_tickers_history(self, symbols: List[str], start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[TickersHistory]:
|
||||
params = {
|
||||
"symbols": ",".join(symbols),
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
data = self._GET("tickers/hist", params=params)
|
||||
|
||||
return [ serializers.TickersHistory.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_t_trades(self, pair: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[TradingPairTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{'t' + pair}/hist", params=params)
|
||||
return [ serializers.TradingPairTrade.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_f_trades(self, currency: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[FundingCurrencyTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{'f' + currency}/hist", params=params)
|
||||
return [ serializers.FundingCurrencyTrade.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_t_book(self, pair: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]:
|
||||
return [ serializers.TradingPairBook.parse(*subdata) for subdata in self._GET(f"book/{'t' + pair}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_f_book(self, currency: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]:
|
||||
return [ serializers.FundingCurrencyBook.parse(*subdata) for subdata in self._GET(f"book/{'f' + currency}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_t_raw_book(self, pair: str, len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]:
|
||||
return [ serializers.TradingPairRawBook.parse(*subdata) for subdata in self._GET(f"book/{'t' + pair}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_f_raw_book(self, currency: str, len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]:
|
||||
return [ serializers.FundingCurrencyRawBook.parse(*subdata) for subdata in self._GET(f"book/{'f' + currency}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_stats_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Statistic]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/hist", params=params)
|
||||
return [ serializers.Statistic.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_stats_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Statistic:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/last", params=params)
|
||||
return serializers.Statistic.parse(*data)
|
||||
|
||||
def get_candles_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Candle]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/{resource}/hist", params=params)
|
||||
return [ serializers.Candle.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_candles_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Candle:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/{resource}/last", params=params)
|
||||
return serializers.Candle.parse(*data)
|
||||
|
||||
def get_derivatives_status(self, type: str, keys: List[str]) -> List[DerivativesStatus]:
|
||||
params = { "keys": ",".join(keys) }
|
||||
|
||||
data = self._GET(f"status/{type}", params=params)
|
||||
|
||||
return [ serializers.DerivativesStatus.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_derivatives_status_history(
|
||||
self,
|
||||
type: str, symbol: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[DerivativesStatus]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET(f"status/{type}/{symbol}/hist", params=params)
|
||||
|
||||
return [ serializers.DerivativesStatus.parse(*subdata, skip=[ "KEY" ]) for subdata in data ]
|
||||
|
||||
def get_liquidations(self, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Liquidation]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET("liquidations/hist", params=params)
|
||||
|
||||
return [ serializers.Liquidation.parse(*subdata[0]) for subdata in data ]
|
||||
|
||||
def get_leaderboards_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Leaderboard]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/hist", params=params)
|
||||
return [ serializers.Leaderboard.parse(*subdata) for subdata in data ]
|
||||
|
||||
def get_leaderboards_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Leaderboard:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/last", params=params)
|
||||
return serializers.Leaderboard.parse(*data)
|
||||
|
||||
def get_funding_stats(self, symbol: str, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingStatistic]:
|
||||
params = { "start": start, "end": end, "limit": limit }
|
||||
|
||||
data = self._GET(f"funding/stats/{symbol}/hist", params=params)
|
||||
|
||||
return [ serializers.FundingStatistic.parse(*subdata) for subdata in data ]
|
||||
|
||||
def conf(self, config: Config) -> Any:
|
||||
return self._GET(f"conf/{config}")[0]
|
||||
|
||||
class _RestAuthenticatedEndpoints(_Requests):
|
||||
def get_wallets(self) -> List[Wallet]:
|
||||
return [ serializers.Wallet.parse(*subdata) for subdata in self._POST("auth/r/wallets") ]
|
||||
|
||||
def get_orders(self, symbol: Optional[str] = None, ids: Optional[List[str]] = None) -> List[Order]:
|
||||
endpoint = "auth/r/orders"
|
||||
|
||||
if symbol != None:
|
||||
endpoint += f"/{symbol}"
|
||||
|
||||
return [ serializers.Order.parse(*subdata) for subdata in self._POST(endpoint, data={ "id": ids }) ]
|
||||
|
||||
def get_positions(self) -> List[Position]:
|
||||
return [ serializers.Position.parse(*subdata) for subdata in self._POST("auth/r/positions") ]
|
||||
|
||||
def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str],
|
||||
price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None,
|
||||
gid: Optional[int] = None, cid: Optional[int] = None,
|
||||
flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification:
|
||||
data = {
|
||||
"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, "meta": meta
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/submit", data=data))
|
||||
|
||||
def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, str]] = 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, str]] = None,
|
||||
price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None) -> Notification:
|
||||
data = {
|
||||
"id": id, "amount": amount, "price": price,
|
||||
"cid": cid, "cid_date": cid_date, "gid": gid,
|
||||
"flags": flags, "lev": lev, "delta": delta,
|
||||
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/update", data=data))
|
||||
|
||||
def cancel_order(self, id: Optional[int] = None, cid: Optional[int] = None, cid_date: Optional[str] = None) -> Notification:
|
||||
data = {
|
||||
"id": id,
|
||||
"cid": cid,
|
||||
"cid_date": cid_date
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order).parse(*self._POST("auth/w/order/cancel", data=data))
|
||||
|
||||
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:
|
||||
data = {
|
||||
"ids": ids,
|
||||
"cids": cids,
|
||||
"gids": gids,
|
||||
|
||||
"all": int(all)
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.Order, iterate=True).parse(*self._POST("auth/w/order/cancel/multi", data=data))
|
||||
|
||||
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:
|
||||
endpoint = "auth/r/orders/hist"
|
||||
else: endpoint = f"auth/r/orders/{symbol}/hist"
|
||||
|
||||
data = {
|
||||
"id": ids,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Order.parse(*subdata) for subdata in self._POST(endpoint, data=data) ]
|
||||
|
||||
def get_trades_history(self, symbol: Optional[str] = None, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Trade]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/trades/hist"
|
||||
else: endpoint = f"auth/r/trades/{symbol}/hist"
|
||||
|
||||
data = {
|
||||
"sort": sort,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Trade.parse(*subdata) for subdata in self._POST(endpoint, data=data) ]
|
||||
|
||||
def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
|
||||
return [ serializers.OrderTrade.parse(*subdata) for subdata in self._POST(f"auth/r/order/{symbol}:{id}/trades") ]
|
||||
|
||||
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 = {
|
||||
"category": category,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Ledger.parse(*subdata) for subdata in self._POST(f"auth/r/ledgers/{currency}/hist", data=data) ]
|
||||
|
||||
def get_funding_offers(self, symbol: Optional[str] = None) -> List[FundingOffer]:
|
||||
endpoint = "auth/r/funding/offers"
|
||||
|
||||
if symbol != None:
|
||||
endpoint += f"/{symbol}"
|
||||
|
||||
return [ serializers.FundingOffer.parse(*subdata) for subdata in self._POST(endpoint) ]
|
||||
|
||||
def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str],
|
||||
rate: Union[Decimal, str], period: int,
|
||||
flags: Optional[int] = 0) -> Notification:
|
||||
data = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period,
|
||||
"flags": flags
|
||||
}
|
||||
|
||||
return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/submit", data=data))
|
||||
|
||||
def cancel_funding_offer(self, id: int) -> Notification:
|
||||
return serializers._Notification(serializer=serializers.FundingOffer).parse(*self._POST("auth/w/funding/offer/cancel", data={ "id": id }))
|
||||
|
||||
def get_funding_offers_history(self, symbol: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingOffer]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/offers/hist"
|
||||
else: endpoint = f"auth/r/funding/offers/{symbol}/hist"
|
||||
|
||||
data = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.FundingOffer.parse(*subdata) for subdata in self._POST(endpoint, data=data) ]
|
||||
|
||||
def get_funding_credits(self, symbol: Optional[str] = None) -> List[FundingCredit]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/credits"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}"
|
||||
|
||||
return [ serializers.FundingCredit.parse(*subdata) for subdata in self._POST(endpoint) ]
|
||||
|
||||
def get_funding_credits_history(self, symbol: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingCredit]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/credits/hist"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}/hist"
|
||||
|
||||
data = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.FundingCredit.parse(*subdata) for subdata in self._POST(endpoint, data=data) ]
|
||||
@@ -1 +1,4 @@
|
||||
from .BfxRestInterface import BfxRestInterface
|
||||
from .endpoints import BfxRestInterface, RestPublicEndpoints, RestAuthenticatedEndpoints, \
|
||||
RestMerchantEndpoints
|
||||
|
||||
NAME = "rest"
|
||||
7
bfxapi/rest/endpoints/__init__.py
Normal file
7
bfxapi/rest/endpoints/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .bfx_rest_interface import BfxRestInterface
|
||||
|
||||
from .rest_public_endpoints import RestPublicEndpoints
|
||||
from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
|
||||
from .rest_merchant_endpoints import RestMerchantEndpoints
|
||||
|
||||
NAME = "endpoints"
|
||||
16
bfxapi/rest/endpoints/bfx_rest_interface.py
Normal file
16
bfxapi/rest/endpoints/bfx_rest_interface.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from typing import Optional
|
||||
|
||||
from .rest_public_endpoints import RestPublicEndpoints
|
||||
from .rest_authenticated_endpoints import RestAuthenticatedEndpoints
|
||||
from .rest_merchant_endpoints import RestMerchantEndpoints
|
||||
|
||||
class BfxRestInterface(object):
|
||||
VERSION = 2
|
||||
|
||||
def __init__(self, host, credentials = None):
|
||||
API_KEY, API_SECRET = credentials and \
|
||||
(credentials["API_KEY"], credentials["API_SECRET"]) or (None, None)
|
||||
|
||||
self.public = RestPublicEndpoints(host=host)
|
||||
self.auth = RestAuthenticatedEndpoints(host=host, API_KEY=API_KEY, API_SECRET=API_SECRET)
|
||||
self.merchant = RestMerchantEndpoints(host=host, API_KEY=API_KEY, API_SECRET=API_SECRET)
|
||||
321
bfxapi/rest/endpoints/rest_authenticated_endpoints.py
Normal file
321
bfxapi/rest/endpoints/rest_authenticated_endpoints.py
Normal file
@@ -0,0 +1,321 @@
|
||||
from typing import List, Tuple, Union, Literal, Optional
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
from .. types import *
|
||||
|
||||
from .. import serializers
|
||||
from .. enums import Sort, OrderType, FundingOfferType
|
||||
from .. middleware import Middleware
|
||||
|
||||
class RestAuthenticatedEndpoints(Middleware):
|
||||
def get_user_info(self) -> UserInfo:
|
||||
return serializers.UserInfo.parse(*self._POST(f"auth/r/info/user"))
|
||||
|
||||
def get_login_history(self) -> List[LoginHistory]:
|
||||
return [ serializers.LoginHistory.parse(*sub_data) for sub_data in self._POST("auth/r/logins/hist") ]
|
||||
|
||||
def get_balance_available_for_orders_or_offers(self, symbol: str, type: str, dir: Optional[int] = None, rate: Optional[str] = None, lev: Optional[str] = None) -> BalanceAvailable:
|
||||
return serializers.BalanceAvailable.parse(*self._POST("auth/calc/order/avail", body={
|
||||
"symbol": symbol, "type": type, "dir": dir,
|
||||
"rate": rate, "lev": lev
|
||||
}))
|
||||
|
||||
def get_wallets(self) -> List[Wallet]:
|
||||
return [ serializers.Wallet.parse(*sub_data) for sub_data in self._POST("auth/r/wallets") ]
|
||||
|
||||
def get_orders(self, symbol: Optional[str] = None, ids: Optional[List[str]] = None) -> List[Order]:
|
||||
endpoint = "auth/r/orders"
|
||||
|
||||
if symbol != None:
|
||||
endpoint += f"/{symbol}"
|
||||
|
||||
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],
|
||||
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,
|
||||
gid: Optional[int] = None, cid: Optional[int] = None,
|
||||
flags: Optional[int] = 0, tif: Optional[Union[datetime, str]] = None, meta: Optional[JSON] = None) -> Notification[Order]:
|
||||
body = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"price": price, "lev": lev,
|
||||
"price_trailing": price_trailing, "price_aux_limit": price_aux_limit, "price_oco_stop": price_oco_stop,
|
||||
"gid": gid, "cid": cid,
|
||||
"flags": flags, "tif": tif, "meta": meta
|
||||
}
|
||||
|
||||
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,
|
||||
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,
|
||||
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]:
|
||||
body = {
|
||||
"id": id, "amount": amount, "price": price,
|
||||
"cid": cid, "cid_date": cid_date, "gid": gid,
|
||||
"flags": flags, "lev": lev, "delta": delta,
|
||||
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
|
||||
}
|
||||
|
||||
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]:
|
||||
body = {
|
||||
"id": id,
|
||||
"cid": cid,
|
||||
"cid_date": cid_date
|
||||
}
|
||||
|
||||
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]]:
|
||||
body = {
|
||||
"ids": ids,
|
||||
"cids": cids,
|
||||
"gids": gids,
|
||||
|
||||
"all": int(all)
|
||||
}
|
||||
|
||||
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]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/orders/hist"
|
||||
else: endpoint = f"auth/r/orders/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"id": ids,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Order.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
|
||||
|
||||
def get_order_trades(self, symbol: str, id: int) -> List[OrderTrade]:
|
||||
return [ serializers.OrderTrade.parse(*sub_data) for sub_data in self._POST(f"auth/r/order/{symbol}:{id}/trades") ]
|
||||
|
||||
def get_trades_history(self, symbol: Optional[str] = None, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Trade]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/trades/hist"
|
||||
else: endpoint = f"auth/r/trades/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"sort": sort,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Trade.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
|
||||
|
||||
def get_ledgers(self, currency: str, category: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Ledger]:
|
||||
body = {
|
||||
"category": category,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Ledger.parse(*sub_data) for sub_data in self._POST(f"auth/r/ledgers/{currency}/hist", body=body) ]
|
||||
|
||||
def get_base_margin_info(self) -> BaseMarginInfo:
|
||||
return serializers.BaseMarginInfo.parse(*(self._POST(f"auth/r/info/margin/base")[1]))
|
||||
|
||||
def get_symbol_margin_info(self, symbol: str) -> SymbolMarginInfo:
|
||||
response = self._POST(f"auth/r/info/margin/{symbol}")
|
||||
data = [response[1]] + response[2]
|
||||
return serializers.SymbolMarginInfo.parse(*data)
|
||||
|
||||
def get_all_symbols_margin_info(self) -> List[SymbolMarginInfo]:
|
||||
return [ serializers.SymbolMarginInfo.parse(*([sub_data[1]] + sub_data[2])) for sub_data in self._POST(f"auth/r/info/margin/sym_all") ]
|
||||
|
||||
def get_positions(self) -> List[Position]:
|
||||
return [ serializers.Position.parse(*sub_data) for sub_data in self._POST("auth/r/positions") ]
|
||||
|
||||
def claim_position(self, id: int, amount: Optional[Union[Decimal, float, str]] = None) -> Notification[PositionClaim]:
|
||||
return serializers._Notification[PositionClaim](serializers.PositionClaim).parse(
|
||||
*self._POST("auth/w/position/claim", body={ "id": id, "amount": amount })
|
||||
)
|
||||
|
||||
def increase_position(self, symbol: str, amount: Union[Decimal, float, str]) -> Notification[PositionIncrease]:
|
||||
return serializers._Notification[PositionIncrease](serializers.PositionIncrease).parse(
|
||||
*self._POST("auth/w/position/increase", body={ "symbol": symbol, "amount": amount })
|
||||
)
|
||||
|
||||
def get_increase_position_info(self, symbol: str, amount: Union[Decimal, float, str]) -> PositionIncreaseInfo:
|
||||
response = self._POST(f"auth/r/position/increase/info", body={ "symbol": symbol, "amount": amount })
|
||||
data = response[0] + [response[1][0]] + response[1][1] + [response[1][2]] + response[4] + response[5]
|
||||
return serializers.PositionIncreaseInfo.parse(*data)
|
||||
|
||||
def get_positions_history(self, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionHistory]:
|
||||
return [ serializers.PositionHistory.parse(*sub_data) for sub_data in self._POST("auth/r/positions/hist", body={ "start": start, "end": end, "limit": limit }) ]
|
||||
|
||||
def get_positions_snapshot(self, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[PositionSnapshot]:
|
||||
return [ serializers.PositionSnapshot.parse(*sub_data) for sub_data in self._POST("auth/r/positions/snap", body={ "start": start, "end": end, "limit": limit }) ]
|
||||
|
||||
def get_positions_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", body={ "ids": ids, "start": start, "end": end, "limit": limit }) ]
|
||||
|
||||
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", body={ "symbol": symbol, "collateral": collateral })[0]))
|
||||
|
||||
def get_derivative_position_collateral_limits(self, symbol: str) -> DerivativePositionCollateralLimits:
|
||||
return serializers.DerivativePositionCollateralLimits.parse(*self._POST("auth/calc/deriv/collateral/limits", body={ "symbol": symbol }))
|
||||
|
||||
def get_funding_offers(self, symbol: Optional[str] = None) -> List[FundingOffer]:
|
||||
endpoint = "auth/r/funding/offers"
|
||||
|
||||
if symbol != None:
|
||||
endpoint += f"/{symbol}"
|
||||
|
||||
return [ serializers.FundingOffer.parse(*sub_data) for sub_data in self._POST(endpoint) ]
|
||||
|
||||
def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str],
|
||||
rate: Union[Decimal, float, str], period: int,
|
||||
flags: Optional[int] = 0) -> Notification[FundingOffer]:
|
||||
body = {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period,
|
||||
"flags": flags
|
||||
}
|
||||
|
||||
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]:
|
||||
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]]:
|
||||
return serializers._Notification[Literal[None]](None).parse(
|
||||
*self._POST("auth/w/funding/offer/cancel/all", body={ "currency": currency })
|
||||
)
|
||||
|
||||
def submit_funding_close(self, id: int) -> Notification[Literal[None]]:
|
||||
return serializers._Notification[Literal[None]](None).parse(
|
||||
*self._POST("auth/w/funding/close", body={ "id": id })
|
||||
)
|
||||
|
||||
def toggle_auto_renew(self, status: bool, currency: str, amount: Optional[str] = None, rate: Optional[int] = None, period: Optional[int] = None) -> Notification[FundingAutoRenew]:
|
||||
return serializers._Notification[FundingAutoRenew](serializers.FundingAutoRenew).parse(*self._POST("auth/w/funding/auto", body={
|
||||
"status": int(status),
|
||||
"currency": currency, "amount": amount,
|
||||
"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]]:
|
||||
return serializers._Notification[Literal[None]](None).parse(*self._POST("auth/w/funding/keep", body={
|
||||
"type": type,
|
||||
"id": ids,
|
||||
"changes": changes
|
||||
}))
|
||||
|
||||
def get_funding_offers_history(self, symbol: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingOffer]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/offers/hist"
|
||||
else: endpoint = f"auth/r/funding/offers/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
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]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/loans"
|
||||
else: endpoint = f"auth/r/funding/loans/{symbol}"
|
||||
|
||||
return [ serializers.FundingLoan.parse(*sub_data) for sub_data in self._POST(endpoint) ]
|
||||
|
||||
def get_funding_loans_history(self, symbol: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingLoan]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/loans/hist"
|
||||
else: endpoint = f"auth/r/funding/loans/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
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]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/credits"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}"
|
||||
|
||||
return [ serializers.FundingCredit.parse(*sub_data) for sub_data in self._POST(endpoint) ]
|
||||
|
||||
def get_funding_credits_history(self, symbol: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingCredit]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/credits/hist"
|
||||
else: endpoint = f"auth/r/funding/credits/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
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]:
|
||||
if symbol == None:
|
||||
endpoint = "auth/r/funding/trades/hist"
|
||||
else: endpoint = f"auth/r/funding/trades/{symbol}/hist"
|
||||
|
||||
body = {
|
||||
"sort": sort,
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.FundingTrade.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
|
||||
|
||||
def get_funding_info(self, key: str) -> FundingInfo:
|
||||
response = self._POST(f"auth/r/info/funding/{key}")
|
||||
data = [response[1]] + response[2]
|
||||
return serializers.FundingInfo.parse(*data)
|
||||
|
||||
def transfer_between_wallets(self, from_wallet: str, to_wallet: str, currency: str, currency_to: str, amount: Union[Decimal, float, str]) -> Notification[Transfer]:
|
||||
body = {
|
||||
"from": from_wallet, "to": to_wallet,
|
||||
"currency": currency, "currency_to": currency_to,
|
||||
"amount": amount
|
||||
}
|
||||
|
||||
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]:
|
||||
return serializers._Notification[Withdrawal](serializers.Withdrawal).parse(*self._POST("auth/w/withdraw", body={
|
||||
"wallet": wallet, "method": method,
|
||||
"address": address, "amount": amount,
|
||||
}))
|
||||
|
||||
def get_deposit_address(self, wallet: str, method: str, renew: bool = False) -> Notification[DepositAddress]:
|
||||
body = {
|
||||
"wallet": wallet,
|
||||
"method": method,
|
||||
"renew": int(renew)
|
||||
}
|
||||
|
||||
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]) -> LightningNetworkInvoice:
|
||||
body = {
|
||||
"wallet": wallet, "currency": currency,
|
||||
"amount": amount
|
||||
}
|
||||
|
||||
return serializers.LightningNetworkInvoice.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]:
|
||||
if currency == None:
|
||||
endpoint = "auth/r/movements/hist"
|
||||
else: endpoint = f"auth/r/movements/{currency}/hist"
|
||||
|
||||
body = {
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}
|
||||
|
||||
return [ serializers.Movement.parse(*sub_data) for sub_data in self._POST(endpoint, body=body) ]
|
||||
69
bfxapi/rest/endpoints/rest_merchant_endpoints.py
Normal file
69
bfxapi/rest/endpoints/rest_merchant_endpoints.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from typing import TypedDict, List, Union, Literal, Optional
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from .. types import *
|
||||
from .. middleware import Middleware
|
||||
from ...utils.camel_and_snake_case_helpers import to_snake_case_keys, to_camel_case_keys
|
||||
|
||||
_CustomerInfo = TypedDict("_CustomerInfo", {
|
||||
"nationality": str, "resid_country": str, "resid_city": str,
|
||||
"resid_zip_code": str, "resid_street": str, "resid_building_no": str,
|
||||
"full_name": str, "email": str, "tos_accepted": bool
|
||||
})
|
||||
|
||||
class RestMerchantEndpoints(Middleware):
|
||||
def submit_invoice(self, amount: Union[Decimal, float, str], currency: str, order_id: str,
|
||||
customer_info: _CustomerInfo, pay_currencies: List[str], duration: Optional[int] = None,
|
||||
webhook: Optional[str] = None, redirect_url: Optional[str] = None) -> InvoiceSubmission:
|
||||
body = to_camel_case_keys({
|
||||
"amount": amount, "currency": currency, "order_id": order_id,
|
||||
"customer_info": customer_info, "pay_currencies": pay_currencies, "duration": duration,
|
||||
"webhook": webhook, "redirect_url": redirect_url
|
||||
})
|
||||
|
||||
data = to_snake_case_keys(self._POST("auth/w/ext/pay/invoice/create", body=body))
|
||||
|
||||
return InvoiceSubmission.parse(data)
|
||||
|
||||
def get_invoices(self, id: Optional[str] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[InvoiceSubmission]:
|
||||
return [ InvoiceSubmission.parse(sub_data) for sub_data in to_snake_case_keys(self._POST("auth/r/ext/pay/invoices", body={
|
||||
"id": id, "start": start, "end": end,
|
||||
"limit": limit
|
||||
})) ]
|
||||
|
||||
def get_invoice_count_stats(self, status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"], format: str) -> List[InvoiceStats]:
|
||||
return [ InvoiceStats(**sub_data) for sub_data in self._POST("auth/r/ext/pay/invoice/stats/count", body={ "status": status, "format": format }) ]
|
||||
|
||||
def get_invoice_earning_stats(self, currency: str, format: str) -> List[InvoiceStats]:
|
||||
return [ InvoiceStats(**sub_data) for sub_data in self._POST("auth/r/ext/pay/invoice/stats/earning", body={ "currency": currency, "format": format }) ]
|
||||
|
||||
def complete_invoice(self, id: str, pay_currency: str, deposit_id: Optional[int] = None, ledger_id: Optional[int] = None) -> InvoiceSubmission:
|
||||
return InvoiceSubmission.parse(to_snake_case_keys(self._POST("auth/w/ext/pay/invoice/complete", body={
|
||||
"id": id, "payCcy": pay_currency, "depositId": deposit_id,
|
||||
"ledgerId": ledger_id
|
||||
})))
|
||||
|
||||
def expire_invoice(self, id: str) -> InvoiceSubmission:
|
||||
return InvoiceSubmission.parse(to_snake_case_keys(self._POST("auth/w/ext/pay/invoice/expire", body={ "id": id })))
|
||||
|
||||
def get_currency_conversion_list(self) -> List[CurrencyConversion]:
|
||||
return [
|
||||
CurrencyConversion(
|
||||
base_currency=sub_data["baseCcy"],
|
||||
convert_currency=sub_data["convertCcy"],
|
||||
created=sub_data["created"]
|
||||
) for sub_data in self._POST("auth/r/ext/pay/settings/convert/list")
|
||||
]
|
||||
|
||||
def add_currency_conversion(self, base_currency: str, convert_currency: str) -> bool:
|
||||
return bool(self._POST("auth/w/ext/pay/settings/convert/create", body={
|
||||
"baseCcy": base_currency,
|
||||
"convertCcy": convert_currency
|
||||
}))
|
||||
|
||||
def remove_currency_conversion(self, base_currency: str, convert_currency: str) -> bool:
|
||||
return bool(self._POST("auth/w/ext/pay/settings/convert/remove", body={
|
||||
"baseCcy": base_currency,
|
||||
"convertCcy": convert_currency
|
||||
}))
|
||||
186
bfxapi/rest/endpoints/rest_public_endpoints.py
Normal file
186
bfxapi/rest/endpoints/rest_public_endpoints.py
Normal file
@@ -0,0 +1,186 @@
|
||||
from typing import List, Union, Literal, Optional, Any, cast
|
||||
from decimal import Decimal
|
||||
|
||||
from .. types import *
|
||||
|
||||
from .. import serializers
|
||||
from .. enums import Config, Sort
|
||||
from .. middleware import Middleware
|
||||
|
||||
class RestPublicEndpoints(Middleware):
|
||||
def conf(self, config: Config) -> Any:
|
||||
return self._GET(f"conf/{config}")[0]
|
||||
|
||||
def get_platform_status(self) -> PlatformStatus:
|
||||
return serializers.PlatformStatus.parse(*self._GET("platform/status"))
|
||||
|
||||
def get_tickers(self, symbols: List[str]) -> List[Union[TradingPairTicker, FundingCurrencyTicker]]:
|
||||
data = self._GET("tickers", params={ "symbols": ",".join(symbols) })
|
||||
|
||||
parsers = { "t": serializers.TradingPairTicker.parse, "f": serializers.FundingCurrencyTicker.parse }
|
||||
|
||||
return [ cast(Union[TradingPairTicker, FundingCurrencyTicker], parsers[sub_data[0][0]](*sub_data)) for sub_data in data ]
|
||||
|
||||
def get_t_tickers(self, pairs: Union[List[str], Literal["ALL"]]) -> List[TradingPairTicker]:
|
||||
if isinstance(pairs, str) and pairs == "ALL":
|
||||
return [ cast(TradingPairTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) if cast(str, sub_data.symbol).startswith("t") ]
|
||||
|
||||
data = self.get_tickers([ pair for pair in pairs ])
|
||||
|
||||
return cast(List[TradingPairTicker], data)
|
||||
|
||||
def get_f_tickers(self, currencies: Union[List[str], Literal["ALL"]]) -> List[FundingCurrencyTicker]:
|
||||
if isinstance(currencies, str) and currencies == "ALL":
|
||||
return [ cast(FundingCurrencyTicker, sub_data) for sub_data in self.get_tickers([ "ALL" ]) if cast(str, sub_data.symbol).startswith("f") ]
|
||||
|
||||
data = self.get_tickers([ currency for currency in currencies ])
|
||||
|
||||
return cast(List[FundingCurrencyTicker], data)
|
||||
|
||||
def get_t_ticker(self, pair: str) -> TradingPairTicker:
|
||||
return serializers.TradingPairTicker.parse(*self._GET(f"ticker/t{pair}"), skip=["SYMBOL"])
|
||||
|
||||
def get_f_ticker(self, currency: str) -> FundingCurrencyTicker:
|
||||
return serializers.FundingCurrencyTicker.parse(*self._GET(f"ticker/f{currency}"), skip=["SYMBOL"])
|
||||
|
||||
def get_tickers_history(self, symbols: List[str], start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[TickersHistory]:
|
||||
return [ serializers.TickersHistory.parse(*sub_data) for sub_data in self._GET("tickers/hist", params={
|
||||
"symbols": ",".join(symbols),
|
||||
"start": start, "end": end,
|
||||
"limit": limit
|
||||
}) ]
|
||||
|
||||
def get_t_trades(self, pair: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[TradingPairTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{pair}/hist", params=params)
|
||||
return [ serializers.TradingPairTrade.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_f_trades(self, currency: str, limit: Optional[int] = None, start: Optional[str] = None, end: Optional[str] = None, sort: Optional[Sort] = None) -> List[FundingCurrencyTrade]:
|
||||
params = { "limit": limit, "start": start, "end": end, "sort": sort }
|
||||
data = self._GET(f"trades/{currency}/hist", params=params)
|
||||
return [ serializers.FundingCurrencyTrade.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_t_book(self, pair: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairBook]:
|
||||
return [ serializers.TradingPairBook.parse(*sub_data) for sub_data in self._GET(f"book/{pair}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_f_book(self, currency: str, precision: Literal["P0", "P1", "P2", "P3", "P4"], len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyBook]:
|
||||
return [ serializers.FundingCurrencyBook.parse(*sub_data) for sub_data in self._GET(f"book/{currency}/{precision}", params={ "len": len }) ]
|
||||
|
||||
def get_t_raw_book(self, pair: str, len: Optional[Literal[1, 25, 100]] = None) -> List[TradingPairRawBook]:
|
||||
return [ serializers.TradingPairRawBook.parse(*sub_data) for sub_data in self._GET(f"book/{pair}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_f_raw_book(self, currency: str, len: Optional[Literal[1, 25, 100]] = None) -> List[FundingCurrencyRawBook]:
|
||||
return [ serializers.FundingCurrencyRawBook.parse(*sub_data) for sub_data in self._GET(f"book/{currency}/R0", params={ "len": len }) ]
|
||||
|
||||
def get_stats_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Statistic]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/hist", params=params)
|
||||
return [ serializers.Statistic.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_stats_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Statistic:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"stats1/{resource}/last", params=params)
|
||||
return serializers.Statistic.parse(*data)
|
||||
|
||||
def get_candles_hist(
|
||||
self,
|
||||
symbol: str, tf: str = "1m",
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Candle]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/trade:{tf}:{symbol}/hist", params=params)
|
||||
return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_candles_last(
|
||||
self,
|
||||
symbol: str, tf: str = "1m",
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Candle:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"candles/trade:{tf}:{symbol}/last", params=params)
|
||||
return serializers.Candle.parse(*data)
|
||||
|
||||
def get_derivatives_status(self, keys: Union[List[str], Literal["ALL"]]) -> List[DerivativesStatus]:
|
||||
if keys == "ALL":
|
||||
params = { "keys": "ALL" }
|
||||
else: params = { "keys": ",".join(keys) }
|
||||
|
||||
data = self._GET(f"status/deriv", params=params)
|
||||
|
||||
return [ serializers.DerivativesStatus.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_derivatives_status_history(
|
||||
self,
|
||||
type: str, symbol: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[DerivativesStatus]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"status/{type}/{symbol}/hist", params=params)
|
||||
return [ serializers.DerivativesStatus.parse(*sub_data, skip=[ "KEY" ]) for sub_data in data ]
|
||||
|
||||
def get_liquidations(self, sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Liquidation]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET("liquidations/hist", params=params)
|
||||
return [ serializers.Liquidation.parse(*sub_data[0]) for sub_data in data ]
|
||||
|
||||
def get_seed_candles(self, symbol: str, tf: str = '1m', sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[Candle]:
|
||||
params = {"sort": sort, "start": start, "end": end, "limit": limit}
|
||||
data = self._GET(f"candles/trade:{tf}:{symbol}/hist?limit={limit}&start={start}&end={end}&sort={sort}", params=params)
|
||||
return [ serializers.Candle.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_leaderboards_hist(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> List[Leaderboard]:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/hist", params=params)
|
||||
return [ serializers.Leaderboard.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_leaderboards_last(
|
||||
self,
|
||||
resource: str,
|
||||
sort: Optional[Sort] = None, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None
|
||||
) -> Leaderboard:
|
||||
params = { "sort": sort, "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"rankings/{resource}/last", params=params)
|
||||
return serializers.Leaderboard.parse(*data)
|
||||
|
||||
def get_funding_stats(self, symbol: str, start: Optional[str] = None, end: Optional[str] = None, limit: Optional[int] = None) -> List[FundingStatistic]:
|
||||
params = { "start": start, "end": end, "limit": limit }
|
||||
data = self._GET(f"funding/stats/{symbol}/hist", params=params)
|
||||
return [ serializers.FundingStatistic.parse(*sub_data) for sub_data in data ]
|
||||
|
||||
def get_pulse_profile(self, nickname: str) -> PulseProfile:
|
||||
return serializers.PulseProfile.parse(*self._GET(f"pulse/profile/{nickname}"))
|
||||
|
||||
def get_pulse_history(self, end: Optional[str] = None, limit: Optional[int] = None) -> List[PulseMessage]:
|
||||
messages = list()
|
||||
|
||||
for subdata in self._GET("pulse/hist", params={ "end": end, "limit": limit }):
|
||||
subdata[18] = subdata[18][0]
|
||||
message = serializers.PulseMessage.parse(*subdata)
|
||||
messages.append(message)
|
||||
|
||||
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:
|
||||
return serializers.TradingMarketAveragePrice.parse(*self._POST("calc/trade/avg", body={
|
||||
"symbol": symbol, "amount": amount, "price_limit": price_limit
|
||||
}))
|
||||
|
||||
def get_funding_market_average_price(self, symbol: str, amount: Union[Decimal, float, str], period: int, rate_limit: Optional[Union[Decimal, float, str]] = None) -> FundingMarketAveragePrice:
|
||||
return serializers.FundingMarketAveragePrice.parse(*self._POST("calc/trade/avg", body={
|
||||
"symbol": symbol, "amount": amount, "period": period, "rate_limit": rate_limit
|
||||
}))
|
||||
|
||||
def get_fx_rate(self, ccy1: str, ccy2: str) -> FxRate:
|
||||
return serializers.FxRate.parse(*self._POST("calc/fx", body={ "ccy1": ccy1, "ccy2": ccy2 }))
|
||||
@@ -3,6 +3,7 @@ from .. exceptions import BfxBaseException
|
||||
__all__ = [
|
||||
"BfxRestException",
|
||||
|
||||
"ResourceNotFound",
|
||||
"RequestParametersError",
|
||||
"ResourceNotFound",
|
||||
"InvalidAuthenticationCredentials"
|
||||
|
||||
3
bfxapi/rest/middleware/__init__.py
Normal file
3
bfxapi/rest/middleware/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .middleware import Middleware
|
||||
|
||||
NAME = "middleware"
|
||||
82
bfxapi/rest/middleware/middleware.py
Normal file
82
bfxapi/rest/middleware/middleware.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import time, hmac, hashlib, json, requests
|
||||
|
||||
from typing import TYPE_CHECKING, Optional, Any, cast
|
||||
|
||||
from http import HTTPStatus
|
||||
from ..enums import Error
|
||||
from ..exceptions import ResourceNotFound, RequestParametersError, InvalidAuthenticationCredentials, UnknownGenericError
|
||||
|
||||
from ...utils.JSONEncoder import JSONEncoder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from requests.sessions import _Params
|
||||
|
||||
class Middleware(object):
|
||||
def __init__(self, host: str, API_KEY: Optional[str] = None, API_SECRET: Optional[str] = None):
|
||||
self.host, self.API_KEY, self.API_SECRET = host, API_KEY, API_SECRET
|
||||
|
||||
def __build_authentication_headers(self, endpoint: str, data: Optional[str] = None):
|
||||
assert isinstance(self.API_KEY, str) and isinstance(self.API_SECRET, str), \
|
||||
"API_KEY and API_SECRET must be both str to call __build_authentication_headers"
|
||||
|
||||
nonce = str(int(time.time()) * 1000)
|
||||
|
||||
if data == None:
|
||||
path = f"/api/v2/{endpoint}{nonce}"
|
||||
else: path = f"/api/v2/{endpoint}{nonce}{data}"
|
||||
|
||||
signature = hmac.new(
|
||||
self.API_SECRET.encode("utf8"),
|
||||
path.encode("utf8"),
|
||||
hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
return {
|
||||
"bfx-nonce": nonce,
|
||||
"bfx-signature": signature,
|
||||
"bfx-apikey": self.API_KEY
|
||||
}
|
||||
|
||||
def _GET(self, endpoint: str, params: Optional["_Params"] = None) -> Any:
|
||||
response = requests.get(f"{self.host}/{endpoint}", params=params)
|
||||
|
||||
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] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError(f"The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
|
||||
def _POST(self, endpoint: str, params: Optional["_Params"] = None, body: Optional[Any] = None, _ignore_authentication_headers: bool = False) -> Any:
|
||||
data = body and json.dumps(body, cls=JSONEncoder) or None
|
||||
|
||||
headers = { "Content-Type": "application/json" }
|
||||
|
||||
if self.API_KEY and self.API_SECRET and _ignore_authentication_headers == False:
|
||||
headers = { **headers, **self.__build_authentication_headers(endpoint, data) }
|
||||
|
||||
response = requests.post(f"{self.host}/{endpoint}", params=params, data=data, headers=headers)
|
||||
|
||||
if response.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise ResourceNotFound(f"No resources found at endpoint <{endpoint}>.")
|
||||
|
||||
data = response.json()
|
||||
|
||||
if isinstance(data, list) and len(data) and data[0] == "error":
|
||||
if data[1] == Error.ERR_PARAMS:
|
||||
raise RequestParametersError(f"The request was rejected with the following parameter error: <{data[2]}>")
|
||||
|
||||
if data[1] == Error.ERR_AUTH_FAIL:
|
||||
raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
|
||||
if data[1] == None or data[1] == Error.ERR_UNK or data[1] == Error.ERR_GENERIC:
|
||||
raise UnknownGenericError(f"The server replied to the request with a generic error with message: <{data[2]}>.")
|
||||
|
||||
return data
|
||||
@@ -1,54 +1,75 @@
|
||||
from . import typings
|
||||
from . import types
|
||||
|
||||
from .. labeler import _Serializer
|
||||
from .. labeler import generate_labeler_serializer, generate_recursive_serializer
|
||||
|
||||
from .. notification import _Notification
|
||||
|
||||
__serializers__ = [
|
||||
"PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker",
|
||||
"TickersHistory", "TradingPairTrade", "FundingCurrencyTrade",
|
||||
"TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook",
|
||||
"FundingCurrencyRawBook", "Statistic", "Candle",
|
||||
"DerivativesStatus", "Liquidation", "Leaderboard",
|
||||
"FundingStatistic", "PulseProfile", "PulseMessage",
|
||||
"TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate",
|
||||
|
||||
"UserInfo", "LoginHistory", "BalanceAvailable",
|
||||
"Order", "Position", "Trade",
|
||||
"FundingTrade", "OrderTrade", "Ledger",
|
||||
"FundingOffer", "FundingCredit", "FundingLoan",
|
||||
"FundingAutoRenew", "FundingInfo", "Wallet",
|
||||
"Transfer", "Withdrawal", "DepositAddress",
|
||||
"LightningNetworkInvoice", "Movement", "SymbolMarginInfo",
|
||||
"BaseMarginInfo", "PositionClaim", "PositionIncreaseInfo",
|
||||
"PositionIncrease", "PositionHistory", "PositionSnapshot",
|
||||
"PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits",
|
||||
]
|
||||
|
||||
#region Serializers definition for Rest Public Endpoints
|
||||
|
||||
PlatformStatus = _Serializer[typings.PlatformStatus]("PlatformStatus", labels=[
|
||||
"OPERATIVE"
|
||||
PlatformStatus = generate_labeler_serializer("PlatformStatus", klass=types.PlatformStatus, labels=[
|
||||
"status"
|
||||
])
|
||||
|
||||
TradingPairTicker = _Serializer[typings.TradingPairTicker]("TradingPairTicker", labels=[
|
||||
"SYMBOL",
|
||||
"BID",
|
||||
"BID_SIZE",
|
||||
"ASK",
|
||||
"ASK_SIZE",
|
||||
"DAILY_CHANGE",
|
||||
"DAILY_CHANGE_RELATIVE",
|
||||
"LAST_PRICE",
|
||||
"VOLUME",
|
||||
"HIGH",
|
||||
"LOW"
|
||||
TradingPairTicker = generate_labeler_serializer("TradingPairTicker", klass=types.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",
|
||||
FundingCurrencyTicker = generate_labeler_serializer("FundingCurrencyTicker", klass=types.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"
|
||||
"frr_amount_available"
|
||||
])
|
||||
|
||||
TickersHistory = _Serializer[typings.TickersHistory]("TickersHistory", labels=[
|
||||
"SYMBOL",
|
||||
"BID",
|
||||
TickersHistory = generate_labeler_serializer("TickersHistory", klass=types.TickersHistory, labels=[
|
||||
"symbol",
|
||||
"bid",
|
||||
"_PLACEHOLDER",
|
||||
"ASK",
|
||||
"ask",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
@@ -57,295 +78,672 @@ TickersHistory = _Serializer[typings.TickersHistory]("TickersHistory", labels=[
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MTS"
|
||||
"mts"
|
||||
])
|
||||
|
||||
TradingPairTrade = _Serializer[typings.TradingPairTrade]("TradingPairTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"PRICE"
|
||||
TradingPairTrade = generate_labeler_serializer("TradingPairTrade", klass=types.TradingPairTrade, labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"price"
|
||||
])
|
||||
|
||||
FundingCurrencyTrade = _Serializer[typings.FundingCurrencyTrade]("FundingCurrencyTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"RATE",
|
||||
"PERIOD"
|
||||
FundingCurrencyTrade = generate_labeler_serializer("FundingCurrencyTrade", klass=types.FundingCurrencyTrade, labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"rate",
|
||||
"period"
|
||||
])
|
||||
|
||||
TradingPairBook = _Serializer[typings.TradingPairBook]("TradingPairBook", labels=[
|
||||
"PRICE",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
TradingPairBook = generate_labeler_serializer("TradingPairBook", klass=types.TradingPairBook, labels=[
|
||||
"price",
|
||||
"count",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingCurrencyBook = _Serializer[typings.FundingCurrencyBook]("FundingCurrencyBook", labels=[
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
FundingCurrencyBook = generate_labeler_serializer("FundingCurrencyBook", klass=types.FundingCurrencyBook, labels=[
|
||||
"rate",
|
||||
"period",
|
||||
"count",
|
||||
"amount"
|
||||
])
|
||||
|
||||
TradingPairRawBook = _Serializer[typings.TradingPairRawBook]("TradingPairRawBook", labels=[
|
||||
"ORDER_ID",
|
||||
"PRICE",
|
||||
"AMOUNT"
|
||||
TradingPairRawBook = generate_labeler_serializer("TradingPairRawBook", klass=types.TradingPairRawBook, labels=[
|
||||
"order_id",
|
||||
"price",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingCurrencyRawBook = _Serializer[typings.FundingCurrencyRawBook]("FundingCurrencyRawBook", labels=[
|
||||
"OFFER_ID",
|
||||
"PERIOD",
|
||||
"RATE",
|
||||
"AMOUNT"
|
||||
FundingCurrencyRawBook = generate_labeler_serializer("FundingCurrencyRawBook", klass=types.FundingCurrencyRawBook, labels=[
|
||||
"offer_id",
|
||||
"period",
|
||||
"rate",
|
||||
"amount"
|
||||
])
|
||||
|
||||
Statistic = _Serializer[typings.Statistic]("Statistic", labels=[
|
||||
"MTS",
|
||||
"VALUE"
|
||||
Statistic = generate_labeler_serializer("Statistic", klass=types.Statistic, labels=[
|
||||
"mts",
|
||||
"value"
|
||||
])
|
||||
|
||||
Candle = _Serializer[typings.Candle]("Candle", labels=[
|
||||
"MTS",
|
||||
"OPEN",
|
||||
"CLOSE",
|
||||
"HIGH",
|
||||
"LOW",
|
||||
"VOLUME"
|
||||
Candle = generate_labeler_serializer("Candle", klass=types.Candle, labels=[
|
||||
"mts",
|
||||
"open",
|
||||
"close",
|
||||
"high",
|
||||
"low",
|
||||
"volume"
|
||||
])
|
||||
|
||||
DerivativesStatus = _Serializer[typings.DerivativesStatus]("DerivativesStatus", labels=[
|
||||
"KEY",
|
||||
"MTS",
|
||||
DerivativesStatus = generate_labeler_serializer("DerivativesStatus", klass=types.DerivativesStatus, labels=[
|
||||
"key",
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"DERIV_PRICE",
|
||||
"SPOT_PRICE",
|
||||
"deriv_price",
|
||||
"spot_price",
|
||||
"_PLACEHOLDER",
|
||||
"INSURANCE_FUND_BALANCE",
|
||||
"insurance_fund_balance",
|
||||
"_PLACEHOLDER",
|
||||
"NEXT_FUNDING_EVT_TIMESTAMP_MS",
|
||||
"NEXT_FUNDING_ACCRUED",
|
||||
"NEXT_FUNDING_STEP",
|
||||
"next_funding_evt_timestamp_ms",
|
||||
"next_funding_accrued",
|
||||
"next_funding_step",
|
||||
"_PLACEHOLDER",
|
||||
"CURRENT_FUNDING",
|
||||
"current_funding",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MARK_PRICE",
|
||||
"mark_price",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"OPEN_INTEREST",
|
||||
"open_interest",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"CLAMP_MIN",
|
||||
"CLAMP_MAX"
|
||||
"clamp_min",
|
||||
"clamp_max"
|
||||
])
|
||||
|
||||
Liquidation = _Serializer[typings.Liquidation]("Liquidation", labels=[
|
||||
Liquidation = generate_labeler_serializer("Liquidation", klass=types.Liquidation, labels=[
|
||||
"_PLACEHOLDER",
|
||||
"POS_ID",
|
||||
"MTS",
|
||||
"pos_id",
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"SYMBOL",
|
||||
"AMOUNT",
|
||||
"BASE_PRICE",
|
||||
"symbol",
|
||||
"amount",
|
||||
"base_price",
|
||||
"_PLACEHOLDER",
|
||||
"IS_MATCH",
|
||||
"IS_MARKET_SOLD",
|
||||
"is_match",
|
||||
"is_market_sold",
|
||||
"_PLACEHOLDER",
|
||||
"PRICE_ACQUIRED"
|
||||
"price_acquired"
|
||||
])
|
||||
|
||||
Leaderboard = _Serializer[typings.Leaderboard]("Leaderboard", labels=[
|
||||
"MTS",
|
||||
Leaderboard = generate_labeler_serializer("Leaderboard", klass=types.Leaderboard, labels=[
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"USERNAME",
|
||||
"RANKING",
|
||||
"username",
|
||||
"ranking",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"VALUE",
|
||||
"value",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"TWITTER_HANDLE"
|
||||
"twitter_handle"
|
||||
])
|
||||
|
||||
FundingStatistic = _Serializer[typings.FundingStatistic]("FundingStatistic", labels=[
|
||||
"TIMESTAMP",
|
||||
FundingStatistic = generate_labeler_serializer("FundingStatistic", klass=types.FundingStatistic, labels=[
|
||||
"timestamp",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FRR",
|
||||
"AVG_PERIOD",
|
||||
"frr",
|
||||
"avg_period",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FUNDING_AMOUNT",
|
||||
"FUNDING_AMOUNT_USED",
|
||||
"funding_amount",
|
||||
"funding_amount_used",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FUNDING_BELOW_THRESHOLD"
|
||||
"funding_below_threshold"
|
||||
])
|
||||
|
||||
PulseProfile = generate_labeler_serializer("PulseProfile", klass=types.PulseProfile, labels=[
|
||||
"puid",
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"nickname",
|
||||
"_PLACEHOLDER",
|
||||
"picture",
|
||||
"text",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"twitter_handle",
|
||||
"_PLACEHOLDER",
|
||||
"followers",
|
||||
"following",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"tipping_status"
|
||||
])
|
||||
|
||||
PulseMessage = generate_recursive_serializer("PulseMessage", klass=types.PulseMessage, serializers={ "profile": PulseProfile }, labels=[
|
||||
"pid",
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"puid",
|
||||
"_PLACEHOLDER",
|
||||
"title",
|
||||
"content",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"is_pin",
|
||||
"is_public",
|
||||
"comments_disabled",
|
||||
"tags",
|
||||
"attachments",
|
||||
"meta",
|
||||
"likes",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"profile",
|
||||
"comments",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER"
|
||||
])
|
||||
|
||||
TradingMarketAveragePrice = generate_labeler_serializer("TradingMarketAveragePrice", klass=types.TradingMarketAveragePrice, labels=[
|
||||
"price_avg",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingMarketAveragePrice = generate_labeler_serializer("FundingMarketAveragePrice", klass=types.FundingMarketAveragePrice, labels=[
|
||||
"rate_avg",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FxRate = generate_labeler_serializer("FxRate", klass=types.FxRate, labels=[
|
||||
"current_rate"
|
||||
])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serializers definition for Rest Authenticated Endpoints
|
||||
|
||||
Wallet = _Serializer[typings.Wallet]("Wallet", labels=[
|
||||
"WALLET_TYPE",
|
||||
"CURRENCY",
|
||||
"BALANCE",
|
||||
"UNSETTLED_INTEREST",
|
||||
"AVAILABLE_BALANCE",
|
||||
"LAST_CHANGE",
|
||||
"TRADE_DETAILS"
|
||||
UserInfo = generate_labeler_serializer("UserInfo", klass=types.UserInfo, labels=[
|
||||
"id",
|
||||
"email",
|
||||
"username",
|
||||
"mts_account_create",
|
||||
"verified",
|
||||
"verification_level",
|
||||
"_PLACEHOLDER",
|
||||
"timezone",
|
||||
"locale",
|
||||
"company",
|
||||
"email_verified",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"mts_master_account_create",
|
||||
"group_id",
|
||||
"master_account_id",
|
||||
"inherit_master_account_verification",
|
||||
"is_group_master",
|
||||
"group_withdraw_enabled",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"ppt_enabled",
|
||||
"merchant_enabled",
|
||||
"competition_enabled",
|
||||
"two_factors_authentication_modes",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"is_securities_master",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"securities_enabled",
|
||||
"allow_disable_ctxswitch",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"time_last_login",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"ctxtswitch_disabled",
|
||||
"_PLACEHOLDER",
|
||||
"comp_countries",
|
||||
"compl_countries_resid",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"is_merchant_enterprise"
|
||||
])
|
||||
|
||||
Order = _Serializer[typings.Order]("Order", labels=[
|
||||
"ID",
|
||||
"GID",
|
||||
"CID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"ORDER_TYPE",
|
||||
"TYPE_PREV",
|
||||
"MTS_TIF",
|
||||
LoginHistory = generate_labeler_serializer("LoginHistory", klass=types.LoginHistory, labels=[
|
||||
"id",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"ORDER_STATUS",
|
||||
"time",
|
||||
"_PLACEHOLDER",
|
||||
"ip",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"PRICE",
|
||||
"PRICE_AVG",
|
||||
"PRICE_TRAILING",
|
||||
"PRICE_AUX_LIMIT",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"PLACED_ID",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"ROUTING",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"META"
|
||||
"extra_info"
|
||||
])
|
||||
|
||||
Position = _Serializer[typings.Position]("Position", labels=[
|
||||
"SYMBOL",
|
||||
"STATUS",
|
||||
"AMOUNT",
|
||||
"BASE_PRICE",
|
||||
"FUNDING",
|
||||
"FUNDING_TYPE",
|
||||
"PL",
|
||||
"PL_PERC",
|
||||
"PRICE_LIQ",
|
||||
"LEVERAGE",
|
||||
"_PLACEHOLDER",
|
||||
"POSITION_ID",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"_PLACEHOLDER",
|
||||
"TYPE",
|
||||
"_PLACEHOLDER",
|
||||
"COLLATERAL",
|
||||
"COLLATERAL_MIN",
|
||||
"META"
|
||||
BalanceAvailable = generate_labeler_serializer("BalanceAvailable", klass=types.BalanceAvailable, labels=[
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingOffer = _Serializer[typings.FundingOffer]("FundingOffer", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATED",
|
||||
"MTS_UPDATED",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"OFFER_TYPE",
|
||||
Order = generate_labeler_serializer("Order", klass=types.Order, labels=[
|
||||
"id",
|
||||
"gid",
|
||||
"cid",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"order_type",
|
||||
"type_prev",
|
||||
"mts_tif",
|
||||
"_PLACEHOLDER",
|
||||
"flags",
|
||||
"order_status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"OFFER_STATUS",
|
||||
"price",
|
||||
"price_avg",
|
||||
"price_trailing",
|
||||
"price_aux_limit",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"notify",
|
||||
"hidden",
|
||||
"placed_id",
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"_PLACEHOLDER",
|
||||
"routing",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"meta"
|
||||
])
|
||||
|
||||
Position = generate_labeler_serializer("Position", klass=types.Position, labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"margin_funding",
|
||||
"margin_funding_type",
|
||||
"pl",
|
||||
"pl_perc",
|
||||
"price_liq",
|
||||
"leverage",
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"_PLACEHOLDER",
|
||||
"type",
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"collateral_min",
|
||||
"meta"
|
||||
])
|
||||
|
||||
Trade = generate_labeler_serializer("Trade", klass=types.Trade, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"order_id",
|
||||
"exec_amount",
|
||||
"exec_price",
|
||||
"order_type",
|
||||
"order_price",
|
||||
"maker",
|
||||
"fee",
|
||||
"fee_currency",
|
||||
"cid"
|
||||
])
|
||||
|
||||
FundingTrade = generate_labeler_serializer("FundingTrade", klass=types.FundingTrade, labels=[
|
||||
"id",
|
||||
"currency",
|
||||
"mts_create",
|
||||
"offer_id",
|
||||
"amount",
|
||||
"rate",
|
||||
"period"
|
||||
])
|
||||
|
||||
OrderTrade = generate_labeler_serializer("OrderTrade", klass=types.OrderTrade, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"order_id",
|
||||
"exec_amount",
|
||||
"exec_price",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"maker",
|
||||
"fee",
|
||||
"fee_currency",
|
||||
"cid"
|
||||
])
|
||||
|
||||
Ledger = generate_labeler_serializer("Ledger", klass=types.Ledger, labels=[
|
||||
"id",
|
||||
"currency",
|
||||
"_PLACEHOLDER",
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"amount",
|
||||
"balance",
|
||||
"_PLACEHOLDER",
|
||||
"description"
|
||||
])
|
||||
|
||||
FundingOffer = generate_labeler_serializer("FundingOffer", klass=types.FundingOffer, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"offer_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"flags",
|
||||
"offer_status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"rate",
|
||||
"period",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"renew",
|
||||
"_PLACEHOLDER"
|
||||
])
|
||||
|
||||
Trade = _Serializer[typings.Trade]("Trade", labels=[
|
||||
"ID",
|
||||
"PAIR",
|
||||
"MTS_CREATE",
|
||||
"ORDER_ID",
|
||||
"EXEC_AMOUNT",
|
||||
"EXEC_PRICE",
|
||||
"ORDER_TYPE",
|
||||
"ORDER_PRICE",
|
||||
"MAKER",
|
||||
"FEE",
|
||||
"FEE_CURRENCY",
|
||||
"CID"
|
||||
FundingCredit = generate_labeler_serializer("FundingCredit", klass=types.FundingCredit, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"side",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"flags",
|
||||
"status",
|
||||
"rate_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"rate",
|
||||
"period",
|
||||
"mts_opening",
|
||||
"mts_last_payout",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close",
|
||||
"position_pair"
|
||||
])
|
||||
|
||||
OrderTrade = _Serializer[typings.OrderTrade]("OrderTrade", labels=[
|
||||
"ID",
|
||||
"PAIR",
|
||||
"MTS_CREATE",
|
||||
"ORDER_ID",
|
||||
"EXEC_AMOUNT",
|
||||
"EXEC_PRICE",
|
||||
FundingLoan = generate_labeler_serializer("FundingLoan", klass=types.FundingLoan, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"side",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"flags",
|
||||
"status",
|
||||
"rate_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MAKER",
|
||||
"FEE",
|
||||
"FEE_CURRENCY",
|
||||
"CID"
|
||||
"rate",
|
||||
"period",
|
||||
"mts_opening",
|
||||
"mts_last_payout",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close"
|
||||
])
|
||||
|
||||
Ledger = _Serializer[typings.Ledger]("Ledger", labels=[
|
||||
"ID",
|
||||
"CURRENCY",
|
||||
"_PLACEHOLDER",
|
||||
"MTS",
|
||||
"_PLACEHOLDER",
|
||||
"AMOUNT",
|
||||
"BALANCE",
|
||||
"_PLACEHOLDER",
|
||||
"DESCRIPTION"
|
||||
FundingAutoRenew = generate_labeler_serializer("FundingAutoRenew", klass=types.FundingAutoRenew, labels=[
|
||||
"currency",
|
||||
"period",
|
||||
"rate",
|
||||
"threshold"
|
||||
])
|
||||
|
||||
FundingCredit = _Serializer[typings.FundingCredit]("FundingCredit", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"SIDE",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"FLAGS",
|
||||
"STATUS",
|
||||
"RATE_TYPE",
|
||||
FundingInfo = generate_labeler_serializer("FundingInfo", klass=types.FundingInfo, labels=[
|
||||
"symbol",
|
||||
"yield_loan",
|
||||
"yield_lend",
|
||||
"duration_loan",
|
||||
"duration_lend"
|
||||
])
|
||||
|
||||
Wallet = generate_labeler_serializer("Wallet", klass=types.Wallet, labels=[
|
||||
"wallet_type",
|
||||
"currency",
|
||||
"balance",
|
||||
"unsettled_interest",
|
||||
"available_balance",
|
||||
"last_change",
|
||||
"trade_details"
|
||||
])
|
||||
|
||||
Transfer = generate_labeler_serializer("Transfer", klass=types.Transfer, labels=[
|
||||
"mts",
|
||||
"wallet_from",
|
||||
"wallet_to",
|
||||
"_PLACEHOLDER",
|
||||
"currency",
|
||||
"currency_to",
|
||||
"_PLACEHOLDER",
|
||||
"amount"
|
||||
])
|
||||
|
||||
Withdrawal = generate_labeler_serializer("Withdrawal", klass=types.Withdrawal, labels=[
|
||||
"withdrawal_id",
|
||||
"_PLACEHOLDER",
|
||||
"method",
|
||||
"payment_id",
|
||||
"wallet",
|
||||
"amount",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"MTS_OPENING",
|
||||
"MTS_LAST_PAYOUT",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"withdrawal_fee"
|
||||
])
|
||||
|
||||
DepositAddress = generate_labeler_serializer("DepositAddress", klass=types.DepositAddress, labels=[
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"method",
|
||||
"currency_code",
|
||||
"_PLACEHOLDER",
|
||||
"NO_CLOSE",
|
||||
"POSITION_PAIR"
|
||||
"address",
|
||||
"pool_address"
|
||||
])
|
||||
|
||||
LightningNetworkInvoice = generate_labeler_serializer("LightningNetworkInvoice", klass=types.LightningNetworkInvoice, labels=[
|
||||
"invoice_hash",
|
||||
"invoice",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"amount"
|
||||
])
|
||||
|
||||
Movement = generate_labeler_serializer("Movement", klass=types.Movement, labels=[
|
||||
"id",
|
||||
"currency",
|
||||
"currency_name",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"mts_start",
|
||||
"mts_update",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"amount",
|
||||
"fees",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"destination_address",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"transaction_id",
|
||||
"withdraw_transaction_note"
|
||||
])
|
||||
|
||||
SymbolMarginInfo = generate_labeler_serializer("SymbolMarginInfo", klass=types.SymbolMarginInfo, labels=[
|
||||
"symbol",
|
||||
"tradable_balance",
|
||||
"gross_balance",
|
||||
"buy",
|
||||
"sell"
|
||||
])
|
||||
|
||||
BaseMarginInfo = generate_labeler_serializer("BaseMarginInfo", klass=types.BaseMarginInfo, labels=[
|
||||
"user_pl",
|
||||
"user_swaps",
|
||||
"margin_balance",
|
||||
"margin_net",
|
||||
"margin_min"
|
||||
])
|
||||
|
||||
PositionClaim = generate_labeler_serializer("PositionClaim", klass=types.PositionClaim, labels=[
|
||||
"symbol",
|
||||
"position_status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"margin_funding",
|
||||
"margin_funding_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"_PLACEHOLDER",
|
||||
"pos_type",
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"min_collateral",
|
||||
"meta"
|
||||
])
|
||||
|
||||
PositionIncreaseInfo = generate_labeler_serializer("PositionIncreaseInfo", klass=types.PositionIncreaseInfo, labels=[
|
||||
"max_pos",
|
||||
"current_pos",
|
||||
"base_currency_balance",
|
||||
"tradable_balance_quote_currency",
|
||||
"tradable_balance_quote_total",
|
||||
"tradable_balance_base_currency",
|
||||
"tradable_balance_base_total",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"funding_avail",
|
||||
"funding_value",
|
||||
"funding_required",
|
||||
"funding_value_currency",
|
||||
"funding_required_currency"
|
||||
])
|
||||
|
||||
PositionIncrease = generate_labeler_serializer("PositionIncrease", klass=types.PositionIncrease, labels=[
|
||||
"symbol",
|
||||
"_PLACEHOLDER",
|
||||
"amount",
|
||||
"base_price"
|
||||
])
|
||||
|
||||
PositionHistory = generate_labeler_serializer("PositionHistory", klass=types.PositionHistory, labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"funding",
|
||||
"funding_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update"
|
||||
])
|
||||
|
||||
PositionSnapshot = generate_labeler_serializer("PositionSnapshot", klass=types.PositionSnapshot, labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"funding",
|
||||
"funding_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update"
|
||||
])
|
||||
|
||||
PositionAudit = generate_labeler_serializer("PositionAudit", klass=types.PositionAudit, labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"funding",
|
||||
"funding_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"_PLACEHOLDER",
|
||||
"type",
|
||||
"_PLACEHOLDER",
|
||||
"collateral",
|
||||
"collateral_min",
|
||||
"meta"
|
||||
])
|
||||
|
||||
DerivativePositionCollateral = generate_labeler_serializer("DerivativePositionCollateral", klass=types.DerivativePositionCollateral, labels=[
|
||||
"status"
|
||||
])
|
||||
|
||||
DerivativePositionCollateralLimits = generate_labeler_serializer("DerivativePositionCollateralLimits", klass=types.DerivativePositionCollateralLimits, labels=[
|
||||
"min_collateral",
|
||||
"max_collateral"
|
||||
])
|
||||
|
||||
#endregion
|
||||
652
bfxapi/rest/types.py
Normal file
652
bfxapi/rest/types.py
Normal file
@@ -0,0 +1,652 @@
|
||||
from typing import List, Dict, Optional, Literal, Any
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .. labeler import _Type, partial, compose
|
||||
from .. notification import Notification
|
||||
from .. utils.JSONEncoder import JSON
|
||||
|
||||
#region Type hinting for Rest Public Endpoints
|
||||
|
||||
@dataclass
|
||||
class PlatformStatus(_Type):
|
||||
status: int
|
||||
|
||||
@dataclass
|
||||
class TradingPairTicker(_Type):
|
||||
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
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTicker(_Type):
|
||||
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
|
||||
|
||||
@dataclass
|
||||
class TickersHistory(_Type):
|
||||
symbol: str
|
||||
bid: float
|
||||
ask: float
|
||||
mts: int
|
||||
|
||||
@dataclass
|
||||
class TradingPairTrade(_Type):
|
||||
id: int
|
||||
mts: int
|
||||
amount: float
|
||||
price: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTrade(_Type):
|
||||
id: int
|
||||
mts: int
|
||||
amount: float
|
||||
rate: float
|
||||
period: int
|
||||
|
||||
@dataclass
|
||||
class TradingPairBook(_Type):
|
||||
price: float
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyBook(_Type):
|
||||
rate: float
|
||||
period: int
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class TradingPairRawBook(_Type):
|
||||
order_id: int
|
||||
price: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyRawBook(_Type):
|
||||
offer_id: int
|
||||
period: int
|
||||
rate: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class Statistic(_Type):
|
||||
mts: int
|
||||
value: float
|
||||
|
||||
@dataclass
|
||||
class Candle(_Type):
|
||||
mts: int
|
||||
open: float
|
||||
close: float
|
||||
high: float
|
||||
low: float
|
||||
volume: float
|
||||
|
||||
@dataclass
|
||||
class DerivativesStatus(_Type):
|
||||
key: Optional[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
|
||||
|
||||
@dataclass
|
||||
class Liquidation(_Type):
|
||||
pos_id: int
|
||||
mts: int
|
||||
symbol: str
|
||||
amount: float
|
||||
base_price: float
|
||||
is_match: int
|
||||
is_market_sold: int
|
||||
price_acquired: float
|
||||
|
||||
@dataclass
|
||||
class Leaderboard(_Type):
|
||||
mts: int
|
||||
username: str
|
||||
ranking: int
|
||||
value: float
|
||||
twitter_handle: Optional[str]
|
||||
|
||||
@dataclass
|
||||
class FundingStatistic(_Type):
|
||||
timestamp: int
|
||||
frr: float
|
||||
avg_period: float
|
||||
funding_amount: float
|
||||
funding_amount_used: float
|
||||
funding_below_threshold: float
|
||||
|
||||
@dataclass
|
||||
class PulseProfile(_Type):
|
||||
puid: str
|
||||
mts: int
|
||||
nickname: str
|
||||
picture: str
|
||||
text: str
|
||||
twitter_handle: str
|
||||
followers: int
|
||||
following: int
|
||||
tipping_status: int
|
||||
|
||||
@dataclass
|
||||
class PulseMessage(_Type):
|
||||
pid: str
|
||||
mts: int
|
||||
puid: str
|
||||
title: str
|
||||
content: str
|
||||
is_pin: int
|
||||
is_public: int
|
||||
comments_disabled: int
|
||||
tags: List[str]
|
||||
attachments: List[str]
|
||||
meta: List[JSON]
|
||||
likes: int
|
||||
profile: PulseProfile
|
||||
comments: int
|
||||
|
||||
@dataclass
|
||||
class TradingMarketAveragePrice(_Type):
|
||||
price_avg: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FundingMarketAveragePrice(_Type):
|
||||
rate_avg: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FxRate(_Type):
|
||||
current_rate: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Rest Authenticated Endpoints
|
||||
|
||||
@dataclass
|
||||
class UserInfo(_Type):
|
||||
id: int
|
||||
email: str
|
||||
username: str
|
||||
mts_account_create: int
|
||||
verified: int
|
||||
verification_level: int
|
||||
timezone: str
|
||||
locale: str
|
||||
company: str
|
||||
email_verified: int
|
||||
mts_master_account_create: int
|
||||
group_id: int
|
||||
master_account_id: int
|
||||
inherit_master_account_verification: int
|
||||
is_group_master: int
|
||||
group_withdraw_enabled: int
|
||||
ppt_enabled: int
|
||||
merchant_enabled: int
|
||||
competition_enabled: int
|
||||
two_factors_authentication_modes: List[str]
|
||||
is_securities_master: int
|
||||
securities_enabled: int
|
||||
allow_disable_ctxswitch: int
|
||||
time_last_login: int
|
||||
ctxtswitch_disabled: int
|
||||
comp_countries: List[str]
|
||||
compl_countries_resid: List[str]
|
||||
is_merchant_enterprise: int
|
||||
|
||||
@dataclass
|
||||
class LoginHistory(_Type):
|
||||
id: int
|
||||
time: int
|
||||
ip: str
|
||||
extra_info: JSON
|
||||
|
||||
@dataclass
|
||||
class BalanceAvailable(_Type):
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class Order(_Type):
|
||||
id: int
|
||||
gid: int
|
||||
cid: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
amount_orig: float
|
||||
order_type: str
|
||||
type_prev: str
|
||||
mts_tif: int
|
||||
flags: int
|
||||
order_status: str
|
||||
price: float
|
||||
price_avg: float
|
||||
price_trailing: float
|
||||
price_aux_limit: float
|
||||
notify: int
|
||||
hidden: int
|
||||
placed_id: int
|
||||
routing: str
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class Position(_Type):
|
||||
symbol: str
|
||||
status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
margin_funding: float
|
||||
margin_funding_type: int
|
||||
pl: float
|
||||
pl_perc: float
|
||||
price_liq: float
|
||||
leverage: float
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
type: int
|
||||
collateral: float
|
||||
collateral_min: float
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class Trade(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
order_id: int
|
||||
exec_amount: float
|
||||
exec_price: float
|
||||
order_type: str
|
||||
order_price: float
|
||||
maker:int
|
||||
fee: float
|
||||
fee_currency: str
|
||||
cid: int
|
||||
|
||||
@dataclass()
|
||||
class FundingTrade(_Type):
|
||||
id: int
|
||||
currency: str
|
||||
mts_create: int
|
||||
offer_id: int
|
||||
amount: float
|
||||
rate: float
|
||||
period: int
|
||||
|
||||
@dataclass
|
||||
class OrderTrade(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
order_id: int
|
||||
exec_amount: float
|
||||
exec_price: float
|
||||
maker:int
|
||||
fee: float
|
||||
fee_currency: str
|
||||
cid: int
|
||||
|
||||
@dataclass
|
||||
class Ledger(_Type):
|
||||
id: int
|
||||
currency: str
|
||||
mts: int
|
||||
amount: float
|
||||
balance: float
|
||||
description: str
|
||||
|
||||
@dataclass
|
||||
class FundingOffer(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
amount_orig: float
|
||||
offer_type: str
|
||||
flags: int
|
||||
offer_status: str
|
||||
rate: float
|
||||
period: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
|
||||
@dataclass
|
||||
class FundingCredit(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
side: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
flags: int
|
||||
status: str
|
||||
rate_type: str
|
||||
rate: float
|
||||
period: int
|
||||
mts_opening: int
|
||||
mts_last_payout: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
no_close: int
|
||||
position_pair: str
|
||||
|
||||
@dataclass
|
||||
class FundingLoan(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
side: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
flags: int
|
||||
status: str
|
||||
rate_type: str
|
||||
rate: float
|
||||
period: int
|
||||
mts_opening: int
|
||||
mts_last_payout: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
no_close: int
|
||||
|
||||
@dataclass
|
||||
class FundingAutoRenew(_Type):
|
||||
currency: str
|
||||
period: int
|
||||
rate: float
|
||||
threshold: float
|
||||
|
||||
@dataclass()
|
||||
class FundingInfo(_Type):
|
||||
symbol: str
|
||||
yield_loan: float
|
||||
yield_lend: float
|
||||
duration_loan: float
|
||||
duration_lend: float
|
||||
|
||||
@dataclass
|
||||
class Wallet(_Type):
|
||||
wallet_type: str
|
||||
currency: str
|
||||
balance: float
|
||||
unsettled_interest: float
|
||||
available_balance: float
|
||||
last_change: str
|
||||
trade_details: JSON
|
||||
|
||||
@dataclass
|
||||
class Transfer(_Type):
|
||||
mts: int
|
||||
wallet_from: str
|
||||
wallet_to: str
|
||||
currency: str
|
||||
currency_to: str
|
||||
amount: int
|
||||
|
||||
@dataclass
|
||||
class Withdrawal(_Type):
|
||||
withdrawal_id: int
|
||||
method: str
|
||||
payment_id: str
|
||||
wallet: str
|
||||
amount: float
|
||||
withdrawal_fee: float
|
||||
|
||||
@dataclass
|
||||
class DepositAddress(_Type):
|
||||
method: str
|
||||
currency_code: str
|
||||
address: str
|
||||
pool_address: str
|
||||
|
||||
@dataclass
|
||||
class LightningNetworkInvoice(_Type):
|
||||
invoice_hash: str
|
||||
invoice: str
|
||||
amount: str
|
||||
|
||||
@dataclass
|
||||
class Movement(_Type):
|
||||
id: str
|
||||
currency: str
|
||||
currency_name: str
|
||||
mts_start: int
|
||||
mts_update: int
|
||||
status: str
|
||||
amount: int
|
||||
fees: int
|
||||
destination_address: str
|
||||
transaction_id: str
|
||||
withdraw_transaction_note: str
|
||||
|
||||
@dataclass
|
||||
class SymbolMarginInfo(_Type):
|
||||
symbol: str
|
||||
tradable_balance: float
|
||||
gross_balance: float
|
||||
buy: float
|
||||
sell: float
|
||||
|
||||
@dataclass
|
||||
class BaseMarginInfo(_Type):
|
||||
user_pl: float
|
||||
user_swaps: float
|
||||
margin_balance: float
|
||||
margin_net: float
|
||||
margin_min: float
|
||||
|
||||
@dataclass
|
||||
class PositionClaim(_Type):
|
||||
symbol: str
|
||||
position_status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
margin_funding: float
|
||||
margin_funding_type: int
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
pos_type: int
|
||||
collateral: str
|
||||
min_collateral: str
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class PositionIncreaseInfo(_Type):
|
||||
max_pos: int
|
||||
current_pos: float
|
||||
base_currency_balance: float
|
||||
tradable_balance_quote_currency: float
|
||||
tradable_balance_quote_total: float
|
||||
tradable_balance_base_currency: float
|
||||
tradable_balance_base_total: float
|
||||
funding_avail: float
|
||||
funding_value: float
|
||||
funding_required: float
|
||||
funding_value_currency: str
|
||||
funding_required_currency: str
|
||||
|
||||
@dataclass
|
||||
class PositionIncrease(_Type):
|
||||
symbol: str
|
||||
amount: float
|
||||
base_price: float
|
||||
|
||||
@dataclass
|
||||
class PositionHistory(_Type):
|
||||
symbol: str
|
||||
status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
funding: float
|
||||
funding_type: int
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
|
||||
@dataclass
|
||||
class PositionSnapshot(_Type):
|
||||
symbol: str
|
||||
status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
funding: float
|
||||
funding_type: int
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
|
||||
@dataclass
|
||||
class PositionAudit(_Type):
|
||||
symbol: str
|
||||
status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
funding: float
|
||||
funding_type: int
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
type: int
|
||||
collateral: float
|
||||
collateral_min: float
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class DerivativePositionCollateral(_Type):
|
||||
status: int
|
||||
|
||||
@dataclass
|
||||
class DerivativePositionCollateralLimits(_Type):
|
||||
min_collateral: float
|
||||
max_collateral: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Rest Merchant Endpoints
|
||||
|
||||
@compose(dataclass, partial)
|
||||
class InvoiceSubmission(_Type):
|
||||
id: str
|
||||
t: int
|
||||
type: Literal["ECOMMERCE", "POS"]
|
||||
duration: int
|
||||
amount: float
|
||||
currency: str
|
||||
order_id: str
|
||||
pay_currencies: List[str]
|
||||
webhook: str
|
||||
redirect_url: str
|
||||
status: Literal["CREATED", "PENDING", "COMPLETED", "EXPIRED"]
|
||||
customer_info: "CustomerInfo"
|
||||
invoices: List["Invoice"]
|
||||
payment: "Payment"
|
||||
additional_payments: List["Payment"]
|
||||
merchant_name: str
|
||||
|
||||
@classmethod
|
||||
def parse(cls, data: Dict[str, Any]) -> "InvoiceSubmission":
|
||||
if "customer_info" in data and data["customer_info"] != None:
|
||||
data["customer_info"] = InvoiceSubmission.CustomerInfo(**data["customer_info"])
|
||||
|
||||
for index, invoice in enumerate(data["invoices"]):
|
||||
data["invoices"][index] = InvoiceSubmission.Invoice(**invoice)
|
||||
|
||||
if "payment" in data and data["payment"] != None:
|
||||
data["payment"] = InvoiceSubmission.Payment(**data["payment"])
|
||||
|
||||
if "additional_payments" in data and data["additional_payments"] != None:
|
||||
for index, additional_payment in enumerate(data["additional_payments"]):
|
||||
data["additional_payments"][index] = InvoiceSubmission.Payment(**additional_payment)
|
||||
|
||||
return InvoiceSubmission(**data)
|
||||
|
||||
@compose(dataclass, partial)
|
||||
class CustomerInfo:
|
||||
nationality: str
|
||||
resid_country: str
|
||||
resid_state: str
|
||||
resid_city: str
|
||||
resid_zip_code: str
|
||||
resid_street: str
|
||||
resid_building_no: str
|
||||
full_name: str
|
||||
email: str
|
||||
tos_accepted: bool
|
||||
|
||||
@compose(dataclass, partial)
|
||||
class Invoice:
|
||||
amount: float
|
||||
currency: str
|
||||
pay_currency: str
|
||||
pool_currency: str
|
||||
address: str
|
||||
ext: JSON
|
||||
|
||||
@compose(dataclass, partial)
|
||||
class Payment:
|
||||
txid: str
|
||||
amount: float
|
||||
currency: str
|
||||
method: str
|
||||
status: Literal["CREATED", "COMPLETED", "PROCESSING"]
|
||||
confirmations: int
|
||||
created_at: str
|
||||
updated_at: str
|
||||
deposit_id: int
|
||||
ledger_id: int
|
||||
force_completed: bool
|
||||
amount_diff: str
|
||||
|
||||
@dataclass
|
||||
class InvoiceStats(_Type):
|
||||
time: str
|
||||
count: float
|
||||
|
||||
@dataclass
|
||||
class CurrencyConversion(_Type):
|
||||
base_currency: str
|
||||
convert_currency: str
|
||||
created: int
|
||||
|
||||
#endregion
|
||||
@@ -1,261 +0,0 @@
|
||||
from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any
|
||||
|
||||
from .. notification import Notification
|
||||
|
||||
JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
|
||||
|
||||
#region Type hinting for Rest Public Endpoints
|
||||
|
||||
class PlatformStatus(TypedDict):
|
||||
OPERATIVE: int
|
||||
|
||||
class TradingPairTicker(TypedDict):
|
||||
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
|
||||
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
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
|
||||
|
||||
class TickersHistory(TypedDict):
|
||||
SYMBOL: str
|
||||
BID: float
|
||||
ASK: float
|
||||
MTS: int
|
||||
|
||||
class TradingPairTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
PRICE: float
|
||||
|
||||
class FundingCurrencyTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
|
||||
class TradingPairBook(TypedDict):
|
||||
PRICE: float
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyBook(TypedDict):
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class TradingPairRawBook(TypedDict):
|
||||
ORDER_ID: int
|
||||
PRICE: float
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyRawBook(TypedDict):
|
||||
OFFER_ID: int
|
||||
PERIOD: int
|
||||
RATE: float
|
||||
AMOUNT: float
|
||||
|
||||
class Statistic(TypedDict):
|
||||
MTS: int
|
||||
VALUE: float
|
||||
|
||||
class Candle(TypedDict):
|
||||
MTS: int
|
||||
OPEN: float
|
||||
CLOSE: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
VOLUME: float
|
||||
|
||||
class DerivativesStatus(TypedDict):
|
||||
KEY: Optional[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
|
||||
|
||||
class Liquidation(TypedDict):
|
||||
POS_ID: int
|
||||
MTS: int
|
||||
SYMBOL: str
|
||||
AMOUNT: float
|
||||
BASE_PRICE: float
|
||||
IS_MATCH: int
|
||||
IS_MARKET_SOLD: int
|
||||
PRICE_ACQUIRED: float
|
||||
|
||||
class Leaderboard(TypedDict):
|
||||
MTS: int
|
||||
USERNAME: str
|
||||
RANKING: int
|
||||
VALUE: float
|
||||
TWITTER_HANDLE: Optional[str]
|
||||
|
||||
class FundingStatistic(TypedDict):
|
||||
TIMESTAMP: int
|
||||
FRR: float
|
||||
AVG_PERIOD: float
|
||||
FUNDING_AMOUNT: float
|
||||
FUNDING_AMOUNT_USED: float
|
||||
FUNDING_BELOW_THRESHOLD: float
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Rest Authenticated Endpoints
|
||||
|
||||
class Wallet(TypedDict):
|
||||
WALLET_TYPE: str
|
||||
CURRENCY: str
|
||||
BALANCE: float
|
||||
UNSETTLED_INTEREST: float
|
||||
AVAILABLE_BALANCE: float
|
||||
LAST_CHANGE: str
|
||||
TRADE_DETAILS: JSON
|
||||
|
||||
class Order(TypedDict):
|
||||
ID: int
|
||||
GID: int
|
||||
CID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
ORDER_TYPE: str
|
||||
TYPE_PREV: str
|
||||
MTS_TIF: int
|
||||
FLAGS: int
|
||||
ORDER_STATUS: str
|
||||
PRICE: float
|
||||
PRICE_AVG: float
|
||||
PRICE_TRAILING: float
|
||||
PRICE_AUX_LIMIT: float
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
PLACED_ID: int
|
||||
ROUTING: str
|
||||
META: JSON
|
||||
|
||||
class Position(TypedDict):
|
||||
SYMBOL: str
|
||||
STATUS: str
|
||||
AMOUNT: float
|
||||
BASE_PRICE: float
|
||||
MARGIN_FUNDING: float
|
||||
MARGIN_FUNDING_TYPE: int
|
||||
PL: float
|
||||
PL_PERC: float
|
||||
PRICE_LIQ: float
|
||||
LEVERAGE: float
|
||||
POSITION_ID: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
TYPE: int
|
||||
COLLATERAL: float
|
||||
COLLATERAL_MIN: float
|
||||
META: JSON
|
||||
|
||||
class FundingOffer(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
OFFER_TYPE: str
|
||||
FLAGS: int
|
||||
OFFER_STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
NOTIFY: bool
|
||||
HIDDEN: int
|
||||
RENEW: bool
|
||||
|
||||
class Trade(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
FEE: float
|
||||
FEE_CURRENCY: str
|
||||
CID: int
|
||||
|
||||
class OrderTrade(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
MAKER:int
|
||||
FEE: float
|
||||
FEE_CURRENCY: str
|
||||
CID: int
|
||||
|
||||
class Ledger(TypedDict):
|
||||
ID: int
|
||||
CURRENCY: str
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
BALANCE: float
|
||||
description: str
|
||||
|
||||
class FundingCredit(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
SIDE: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
MTS_OPENING: int
|
||||
MTS_LAST_PAYOUT: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
RATE_REAL: float
|
||||
NO_CLOSE: int
|
||||
POSITION_PAIR: str
|
||||
|
||||
#endregion
|
||||
18
bfxapi/tests/__init__.py
Normal file
18
bfxapi/tests/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import unittest
|
||||
from .test_rest_serializers import TestRestSerializers
|
||||
from .test_websocket_serializers import TestWebsocketSerializers
|
||||
from .test_labeler import TestLabeler
|
||||
from .test_notification import TestNotification
|
||||
|
||||
NAME = "tests"
|
||||
|
||||
def suite():
|
||||
return unittest.TestSuite([
|
||||
unittest.makeSuite(TestRestSerializers),
|
||||
unittest.makeSuite(TestWebsocketSerializers),
|
||||
unittest.makeSuite(TestLabeler),
|
||||
unittest.makeSuite(TestNotification),
|
||||
])
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.TextTestRunner().run(suite())
|
||||
56
bfxapi/tests/test_labeler.py
Normal file
56
bfxapi/tests/test_labeler.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import unittest
|
||||
|
||||
from dataclasses import dataclass
|
||||
from ..exceptions import LabelerSerializerException
|
||||
from ..labeler import _Type, generate_labeler_serializer, generate_recursive_serializer
|
||||
|
||||
class TestLabeler(unittest.TestCase):
|
||||
def test_generate_labeler_serializer(self):
|
||||
@dataclass
|
||||
class Test(_Type):
|
||||
A: int
|
||||
B: float
|
||||
C: str
|
||||
|
||||
labels = [ "A", "_PLACEHOLDER", "B", "_PLACEHOLDER", "C" ]
|
||||
|
||||
serializer = generate_labeler_serializer("Test", Test, labels)
|
||||
|
||||
self.assertEqual(serializer.parse(5, None, 65.0, None, "X"), Test(5, 65.0, "X"),
|
||||
msg="_Serializer should produce the right result.")
|
||||
|
||||
self.assertEqual(serializer.parse(5, 65.0, "X", skip=[ "_PLACEHOLDER" ]), Test(5, 65.0, "X"),
|
||||
msg="_Serializer should produce the right result when skip parameter is given.")
|
||||
|
||||
self.assertListEqual(serializer.get_labels(), [ "A", "B", "C" ],
|
||||
msg="_Serializer::get_labels() should return the right list of labels.")
|
||||
|
||||
with self.assertRaises(LabelerSerializerException,
|
||||
msg="_Serializer should raise LabelerSerializerException if given fewer arguments than the serializer labels."):
|
||||
serializer.parse(5, 65.0, "X")
|
||||
|
||||
def test_generate_recursive_serializer(self):
|
||||
@dataclass
|
||||
class Outer(_Type):
|
||||
A: int
|
||||
B: float
|
||||
C: "Middle"
|
||||
|
||||
@dataclass
|
||||
class Middle(_Type):
|
||||
D: str
|
||||
E: "Inner"
|
||||
|
||||
@dataclass
|
||||
class Inner(_Type):
|
||||
F: bool
|
||||
|
||||
inner = generate_labeler_serializer("Inner", Inner, ["F"])
|
||||
middle = generate_recursive_serializer("Middle", Middle, ["D", "E"], { "E": inner })
|
||||
outer = generate_recursive_serializer("Outer", Outer, ["A", "B", "C"], { "C": middle })
|
||||
|
||||
self.assertEqual(outer.parse(10, 45.5, [ "Y", [ True ] ]), Outer(10, 45.5, Middle("Y", Inner(True))),
|
||||
msg="_RecursiveSerializer should produce the right result.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
25
bfxapi/tests/test_notification.py
Normal file
25
bfxapi/tests/test_notification.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import unittest
|
||||
|
||||
from dataclasses import dataclass
|
||||
from ..labeler import generate_labeler_serializer
|
||||
from ..notification import _Type, _Notification, Notification
|
||||
|
||||
class TestNotification(unittest.TestCase):
|
||||
def test_notification(self):
|
||||
@dataclass
|
||||
class Test(_Type):
|
||||
A: int
|
||||
B: float
|
||||
C: str
|
||||
|
||||
test = generate_labeler_serializer("Test", Test,
|
||||
[ "A", "_PLACEHOLDER", "B", "_PLACEHOLDER", "C" ])
|
||||
|
||||
notification = _Notification[Test](test)
|
||||
|
||||
self.assertEqual(notification.parse(*[1675787861506, "test", None, None, [ 5, None, 65.0, None, "X" ], 0, "SUCCESS", "This is just a test notification."]),
|
||||
Notification[Test](1675787861506, "test", None, Test(5, 65.0, "X"), 0, "SUCCESS", "This is just a test notification."),
|
||||
msg="_Notification should produce the right notification.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
17
bfxapi/tests/test_rest_serializers.py
Normal file
17
bfxapi/tests/test_rest_serializers.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import unittest
|
||||
|
||||
from ..labeler import _Type
|
||||
|
||||
from ..rest import serializers
|
||||
|
||||
class TestRestSerializers(unittest.TestCase):
|
||||
def test_rest_serializers(self):
|
||||
for serializer in map(serializers.__dict__.get, serializers.__serializers__):
|
||||
self.assertTrue(issubclass(serializer.klass, _Type),
|
||||
f"_Serializer <{serializer.name}>: .klass field must be a subclass of _Type (got {serializer.klass}).")
|
||||
|
||||
self.assertListEqual(serializer.get_labels(), list(serializer.klass.__annotations__),
|
||||
f"_Serializer <{serializer.name}> and _Type <{serializer.klass.__name__}> must have matching labels and fields.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
17
bfxapi/tests/test_websocket_serializers.py
Normal file
17
bfxapi/tests/test_websocket_serializers.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import unittest
|
||||
|
||||
from ..labeler import _Type
|
||||
|
||||
from ..websocket import serializers
|
||||
|
||||
class TestWebsocketSerializers(unittest.TestCase):
|
||||
def test_websocket_serializers(self):
|
||||
for serializer in map(serializers.__dict__.get, serializers.__serializers__):
|
||||
self.assertTrue(issubclass(serializer.klass, _Type),
|
||||
f"_Serializer <{serializer.name}>: .klass field must be a subclass of _Type (got {serializer.klass}).")
|
||||
|
||||
self.assertListEqual(serializer.get_labels(), list(serializer.klass.__annotations__),
|
||||
f"_Serializer <{serializer.name}> and _Type <{serializer.klass.__name__}> must have matching labels and fields.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
7
bfxapi/urls.py
Normal file
7
bfxapi/urls.py
Normal file
@@ -0,0 +1,7 @@
|
||||
REST_HOST = "https://api.bitfinex.com/v2"
|
||||
PUB_REST_HOST = "https://api-pub.bitfinex.com/v2"
|
||||
STAGING_REST_HOST = "https://api.staging.bitfinex.com/v2"
|
||||
|
||||
WSS_HOST = "wss://api.bitfinex.com/ws/2"
|
||||
PUB_WSS_HOST = "wss://api-pub.bitfinex.com/ws/2"
|
||||
STAGING_WSS_HOST = "wss://api.staging.bitfinex.com/ws/2"
|
||||
29
bfxapi/utils/JSONEncoder.py
Normal file
29
bfxapi/utils/JSONEncoder.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
from typing import Type, List, Dict, Union, Any
|
||||
|
||||
JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
|
||||
|
||||
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:
|
||||
if isinstance(data, float):
|
||||
return format(Decimal(repr(data)), "f")
|
||||
elif isinstance(data, list):
|
||||
return [ _convert_float_to_str(sub_data) for sub_data in data ]
|
||||
elif isinstance(data, dict):
|
||||
return _strip({ key: _convert_float_to_str(value) for key, value in data.items() })
|
||||
else: return data
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
def encode(self, obj: JSON) -> str:
|
||||
return json.JSONEncoder.encode(self, _convert_float_to_str(obj))
|
||||
|
||||
def default(self, obj: Any) -> Any:
|
||||
if isinstance(obj, Decimal): return format(obj, "f")
|
||||
elif isinstance(obj, datetime): return str(obj)
|
||||
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
22
bfxapi/utils/camel_and_snake_case_helpers.py
Normal file
22
bfxapi/utils/camel_and_snake_case_helpers.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import re
|
||||
|
||||
from typing import TypeVar, Callable, Dict, Any, cast
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
_to_snake_case: Callable[[str], str] = lambda string: re.sub(r"(?<!^)(?=[A-Z])", "_", string).lower()
|
||||
|
||||
_to_camel_case: Callable[[str], str] = lambda string: (components := string.split("_"))[0] + str().join(c.title() for c in components[1:])
|
||||
|
||||
def _scheme(data: T, adapter: Callable[[str], str]) -> T:
|
||||
if isinstance(data, list):
|
||||
return cast(T, [ _scheme(sub_data, adapter) for sub_data in data ])
|
||||
elif isinstance(data, dict):
|
||||
return cast(T, { adapter(key): _scheme(value, adapter) for key, value in data.items() })
|
||||
else: return data
|
||||
|
||||
def to_snake_case_keys(dictionary: T) -> T:
|
||||
return _scheme(dictionary, _to_snake_case)
|
||||
|
||||
def to_camel_case_keys(dictionary: T) -> T:
|
||||
return _scheme(dictionary, _to_camel_case)
|
||||
@@ -1,4 +0,0 @@
|
||||
import time
|
||||
|
||||
def generate_unique_cid(multiplier: int = 1000) -> int:
|
||||
return int(round(time.time() * multiplier))
|
||||
@@ -1,9 +0,0 @@
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
class JSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal) or isinstance(obj, datetime):
|
||||
return str(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
@@ -1,29 +0,0 @@
|
||||
from .. enums import Flag
|
||||
|
||||
def calculate_order_flags(
|
||||
hidden : bool = False,
|
||||
close : bool = False,
|
||||
reduce_only : bool = False,
|
||||
post_only : bool = False,
|
||||
oco : bool = False,
|
||||
no_var_rates: bool = False
|
||||
) -> int:
|
||||
flags = 0
|
||||
|
||||
if hidden: flags += Flag.HIDDEN
|
||||
if close: flags += Flag.CLOSE
|
||||
if reduce_only: flags += Flag.REDUCE_ONLY
|
||||
if post_only: flags += Flag.POST_ONLY
|
||||
if oco: flags += Flag.OCO
|
||||
if no_var_rates: flags += Flag.NO_VAR_RATES
|
||||
|
||||
return flags
|
||||
|
||||
def calculate_offer_flags(
|
||||
hidden : bool = False
|
||||
) -> int:
|
||||
flags = 0
|
||||
|
||||
if hidden: flags += Flag.HIDDEN
|
||||
|
||||
return flags
|
||||
@@ -1,43 +0,0 @@
|
||||
from typing import cast, TypeVar, Union
|
||||
|
||||
from .. exceptions import IntegerUnderflowError, IntegerOverflowflowError
|
||||
|
||||
__all__ = [ "Int16", "Int32", "Int45", "Int64" ]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class _Int(int):
|
||||
def __new__(cls: T, integer: int) -> T:
|
||||
assert hasattr(cls, "_BITS"), "_Int must be extended by a class that has a static member _BITS (indicating the number of bits with which to represent the integers)."
|
||||
|
||||
bits = cls._BITS - 1
|
||||
|
||||
min, max = -(2 ** bits), (2 ** bits) - 1
|
||||
|
||||
if integer < min:
|
||||
raise IntegerUnderflowError(f"Underflow. Cannot store <{integer}> in {cls._BITS} bits integer. The min and max bounds are {min} and {max}.")
|
||||
|
||||
if integer > max:
|
||||
raise IntegerOverflowflowError(f"Overflow. Cannot store <{integer}> in {cls._BITS} bits integer. The min and max bounds are {min} and {max}.")
|
||||
|
||||
return cast(T, super().__new__(int, integer))
|
||||
|
||||
class Int16(_Int):
|
||||
_BITS = 16
|
||||
|
||||
int16 = Union[Int16, int]
|
||||
|
||||
class Int32(_Int):
|
||||
_BITS = 32
|
||||
|
||||
int32 = Union[Int32, int]
|
||||
|
||||
class Int45(_Int):
|
||||
_BITS = 45
|
||||
|
||||
int45 = Union[Int45, int]
|
||||
|
||||
class Int64(_Int):
|
||||
_BITS = 64
|
||||
|
||||
int64 = Union[Int64, int]
|
||||
@@ -1,99 +1,52 @@
|
||||
"""
|
||||
Module used to describe all of the different data types
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
|
||||
RESET_SEQ = "\033[0m"
|
||||
|
||||
COLOR_SEQ = "\033[1;%dm"
|
||||
ITALIC_COLOR_SEQ = "\033[3;%dm"
|
||||
UNDERLINE_COLOR_SEQ = "\033[4;%dm"
|
||||
|
||||
BOLD_SEQ = "\033[1m"
|
||||
UNDERLINE_SEQ = "\033[04m"
|
||||
|
||||
YELLOW = '\033[93m'
|
||||
WHITE = '\33[37m'
|
||||
BLUE = '\033[34m'
|
||||
LIGHT_BLUE = '\033[94m'
|
||||
RED = '\033[91m'
|
||||
GREY = '\33[90m'
|
||||
|
||||
KEYWORD_COLORS = {
|
||||
'WARNING': YELLOW,
|
||||
'INFO': LIGHT_BLUE,
|
||||
'DEBUG': WHITE,
|
||||
'CRITICAL': YELLOW,
|
||||
'ERROR': RED,
|
||||
'TRADE': '\33[102m\33[30m'
|
||||
}
|
||||
|
||||
def formatter_message(message, use_color = True):
|
||||
"""
|
||||
Syntax highlight certain keywords
|
||||
"""
|
||||
if use_color:
|
||||
message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)
|
||||
else:
|
||||
message = message.replace("$RESET", "").replace("$BOLD", "")
|
||||
return message
|
||||
|
||||
def format_word(message, word, color_seq, bold=False, underline=False):
|
||||
"""
|
||||
Surround the given word with a sequence
|
||||
"""
|
||||
replacer = color_seq + word + RESET_SEQ
|
||||
if underline:
|
||||
replacer = UNDERLINE_SEQ + replacer
|
||||
if bold:
|
||||
replacer = BOLD_SEQ + replacer
|
||||
return message.replace(word, replacer)
|
||||
COLORS = {
|
||||
"DEBUG": CYAN,
|
||||
"INFO": BLUE,
|
||||
"WARNING": YELLOW,
|
||||
"ERROR": RED
|
||||
}
|
||||
|
||||
class Formatter(logging.Formatter):
|
||||
"""
|
||||
This Formatted simply colors in the levelname i.e 'INFO', 'DEBUG'
|
||||
"""
|
||||
def __init__(self, msg, use_color = True):
|
||||
logging.Formatter.__init__(self, msg)
|
||||
self.use_color = use_color
|
||||
class _ColoredFormatter(logging.Formatter):
|
||||
def __init__(self, msg, use_color = True):
|
||||
logging.Formatter.__init__(self, msg, "%d-%m-%Y %H:%M:%S")
|
||||
self.use_color = use_color
|
||||
|
||||
def format(self, record):
|
||||
"""
|
||||
Format and highlight certain keywords
|
||||
"""
|
||||
levelname = record.levelname
|
||||
if self.use_color and levelname in KEYWORD_COLORS:
|
||||
levelname_color = KEYWORD_COLORS[levelname] + levelname + RESET_SEQ
|
||||
record.levelname = levelname_color
|
||||
record.name = GREY + record.name + RESET_SEQ
|
||||
return logging.Formatter.format(self, record)
|
||||
def format(self, record):
|
||||
levelname = record.levelname
|
||||
if self.use_color and levelname in COLORS:
|
||||
levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
|
||||
record.levelname = levelname_color
|
||||
record.name = ITALIC_COLOR_SEQ % (30 + BLACK) + record.name + RESET_SEQ
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
class CustomLogger(logging.Logger):
|
||||
"""
|
||||
This adds extra logging functions such as logger.trade and also
|
||||
sets the logger to use the custom formatter
|
||||
"""
|
||||
FORMAT = "[$BOLD%(name)s$RESET] [%(levelname)s] %(message)s"
|
||||
class ColoredLogger(logging.Logger):
|
||||
FORMAT = "[$BOLD%(name)s$RESET] [%(asctime)s] [%(levelname)s] %(message)s"
|
||||
|
||||
COLOR_FORMAT = formatter_message(FORMAT, True)
|
||||
TRADE = 50
|
||||
|
||||
def __init__(self, name, level):
|
||||
logging.Logger.__init__(self, name, level)
|
||||
|
||||
def __init__(self, name, logLevel='DEBUG'):
|
||||
logging.Logger.__init__(self, name, logLevel)
|
||||
color_formatter = Formatter(self.COLOR_FORMAT)
|
||||
colored_formatter = _ColoredFormatter(self.COLOR_FORMAT)
|
||||
console = logging.StreamHandler()
|
||||
console.setFormatter(color_formatter)
|
||||
self.addHandler(console)
|
||||
logging.addLevelName(self.TRADE, "TRADE")
|
||||
return
|
||||
console.setFormatter(colored_formatter)
|
||||
|
||||
def set_level(self, level):
|
||||
logging.Logger.setLevel(self, level)
|
||||
|
||||
def trade(self, message, *args, **kws):
|
||||
"""
|
||||
Print a syntax highlighted trade signal
|
||||
"""
|
||||
if self.isEnabledFor(self.TRADE):
|
||||
message = format_word(message, 'CLOSED ', YELLOW, bold=True)
|
||||
message = format_word(message, 'OPENED ', LIGHT_BLUE, bold=True)
|
||||
message = format_word(message, 'UPDATED ', BLUE, bold=True)
|
||||
message = format_word(message, 'CLOSED_ALL ', RED, bold=True)
|
||||
# Yes, logger takes its '*args' as 'args'.
|
||||
self._log(self.TRADE, message, args, **kws)
|
||||
self.addHandler(console)
|
||||
@@ -1,231 +0,0 @@
|
||||
import traceback, json, asyncio, hmac, hashlib, time, uuid, websockets
|
||||
|
||||
from typing import Literal, TypeVar, Callable, cast
|
||||
|
||||
from pyee.asyncio import AsyncIOEventEmitter
|
||||
|
||||
from ._BfxWebsocketInputs import _BfxWebsocketInputs
|
||||
from .handlers import Channels, PublicChannelsHandler, AuthenticatedChannelsHandler
|
||||
from .exceptions import ConnectionNotOpen, TooManySubscriptions, WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion
|
||||
|
||||
from ..utils.encoder import JSONEncoder
|
||||
|
||||
from ..utils.logger import Formatter, CustomLogger
|
||||
|
||||
_HEARTBEAT = "hb"
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Literal[None]])
|
||||
|
||||
def _require_websocket_connection(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.websocket == None or self.websocket.open == False:
|
||||
raise ConnectionNotOpen("No open connection with the server.")
|
||||
|
||||
await function(self, *args, **kwargs)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
def _require_websocket_authentication(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.authentication == False:
|
||||
raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.")
|
||||
|
||||
await _require_websocket_connection(function)(self, *args, **kwargs)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
class BfxWebsocketClient(object):
|
||||
VERSION = 2
|
||||
|
||||
MAXIMUM_BUCKETS_AMOUNT = 20
|
||||
|
||||
EVENTS = [
|
||||
"open", "subscribed", "authenticated", "wss-error",
|
||||
*PublicChannelsHandler.EVENTS,
|
||||
*AuthenticatedChannelsHandler.EVENTS
|
||||
]
|
||||
|
||||
def __init__(self, host, buckets=5, log_level = "WARNING", API_KEY=None, API_SECRET=None, filter=None):
|
||||
self.host, self.websocket, self.event_emitter = host, None, AsyncIOEventEmitter()
|
||||
|
||||
self.event_emitter.add_listener("error",
|
||||
lambda exception: self.logger.error("\n" + str().join(traceback.format_exception(type(exception), exception, exception.__traceback__))[:-1])
|
||||
)
|
||||
|
||||
self.API_KEY, self.API_SECRET, self.filter, self.authentication = API_KEY, API_SECRET, filter, False
|
||||
|
||||
self.handler = AuthenticatedChannelsHandler(event_emitter=self.event_emitter)
|
||||
|
||||
self.buckets = [ _BfxWebsocketBucket(self.host, self.event_emitter, self.__bucket_open_signal) for _ in range(buckets) ]
|
||||
|
||||
self.inputs = _BfxWebsocketInputs(self.__handle_websocket_input)
|
||||
|
||||
self.logger = CustomLogger("BfxWebsocketClient", logLevel=log_level)
|
||||
|
||||
if buckets > BfxWebsocketClient.MAXIMUM_BUCKETS_AMOUNT:
|
||||
self.logger.warning(f"It is not safe to use more than {BfxWebsocketClient.MAXIMUM_BUCKETS_AMOUNT} buckets from the same connection ({buckets} in use), the server could momentarily block the client with <429 Too Many Requests>.")
|
||||
|
||||
def run(self):
|
||||
return asyncio.run(self.start())
|
||||
|
||||
async def start(self):
|
||||
tasks = [ bucket._connect(index) for index, bucket in enumerate(self.buckets) ]
|
||||
|
||||
if self.API_KEY != None and self.API_SECRET != None:
|
||||
tasks.append(self.__connect(self.API_KEY, self.API_SECRET, self.filter))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
async def __connect(self, API_KEY, API_SECRET, filter=None):
|
||||
async for websocket in websockets.connect(self.host):
|
||||
self.websocket = websocket
|
||||
|
||||
await self.__authenticate(API_KEY, API_SECRET, filter)
|
||||
|
||||
try:
|
||||
async for message in websocket:
|
||||
message = json.loads(message)
|
||||
|
||||
if isinstance(message, dict) and message["event"] == "auth":
|
||||
if message["status"] == "OK":
|
||||
self.event_emitter.emit("authenticated", message); self.authentication = True
|
||||
else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
elif isinstance(message, dict) and message["event"] == "error":
|
||||
self.event_emitter.emit("wss-error", message["code"], message["msg"])
|
||||
elif isinstance(message, list) and (chanId := message[0]) == 0 and message[1] != _HEARTBEAT:
|
||||
self.handler.handle(message[1], message[2])
|
||||
except websockets.ConnectionClosedError: continue
|
||||
finally: await self.websocket.wait_closed(); break
|
||||
|
||||
async def __authenticate(self, API_KEY, API_SECRET, filter=None):
|
||||
data = { "event": "auth", "filter": filter, "apiKey": API_KEY }
|
||||
|
||||
data["authNonce"] = int(time.time()) * 1000
|
||||
|
||||
data["authPayload"] = "AUTH" + str(data["authNonce"])
|
||||
|
||||
data["authSig"] = hmac.new(
|
||||
API_SECRET.encode("utf8"),
|
||||
data["authPayload"].encode("utf8"),
|
||||
hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
await self.websocket.send(json.dumps(data))
|
||||
|
||||
async def subscribe(self, channel, **kwargs):
|
||||
counters = [ len(bucket.pendings) + len(bucket.subscriptions) for bucket in self.buckets ]
|
||||
|
||||
index = counters.index(min(counters))
|
||||
|
||||
await self.buckets[index]._subscribe(channel, **kwargs)
|
||||
|
||||
async def unsubscribe(self, chanId):
|
||||
for bucket in self.buckets:
|
||||
if chanId in bucket.subscriptions.keys():
|
||||
await bucket._unsubscribe(chanId=chanId)
|
||||
|
||||
async def close(self, code=1000, reason=str()):
|
||||
if self.websocket != None and self.websocket.open == True:
|
||||
await self.websocket.close(code=code, reason=reason)
|
||||
|
||||
for bucket in self.buckets:
|
||||
await bucket._close(code=code, reason=reason)
|
||||
|
||||
@_require_websocket_authentication
|
||||
async def notify(self, info, MESSAGE_ID=None, **kwargs):
|
||||
await self.websocket.send(json.dumps([ 0, "n", MESSAGE_ID, { "type": "ucm-test", "info": info, **kwargs } ]))
|
||||
|
||||
@_require_websocket_authentication
|
||||
async def __handle_websocket_input(self, input, data):
|
||||
await self.websocket.send(json.dumps([ 0, input, None, data], cls=JSONEncoder))
|
||||
|
||||
def __bucket_open_signal(self, index):
|
||||
if all(bucket.websocket != None and bucket.websocket.open == True for bucket in self.buckets):
|
||||
self.event_emitter.emit("open")
|
||||
|
||||
def on(self, event, callback = None):
|
||||
if event not in BfxWebsocketClient.EVENTS:
|
||||
raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS")
|
||||
|
||||
if callback != None:
|
||||
return self.event_emitter.on(event, callback)
|
||||
|
||||
def handler(function):
|
||||
self.event_emitter.on(event, function)
|
||||
return handler
|
||||
|
||||
def once(self, event, callback = None):
|
||||
if event not in BfxWebsocketClient.EVENTS:
|
||||
raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS")
|
||||
|
||||
if callback != None:
|
||||
return self.event_emitter.once(event, callback)
|
||||
|
||||
def handler(function):
|
||||
self.event_emitter.once(event, function)
|
||||
return handler
|
||||
|
||||
class _BfxWebsocketBucket(object):
|
||||
MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
|
||||
|
||||
def __init__(self, host, event_emitter, __bucket_open_signal):
|
||||
self.host, self.event_emitter, self.__bucket_open_signal = host, event_emitter, __bucket_open_signal
|
||||
|
||||
self.websocket, self.subscriptions, self.pendings = None, dict(), list()
|
||||
|
||||
self.handler = PublicChannelsHandler(event_emitter=self.event_emitter)
|
||||
|
||||
async def _connect(self, index):
|
||||
async for websocket in websockets.connect(self.host):
|
||||
self.websocket = websocket
|
||||
|
||||
self.__bucket_open_signal(index)
|
||||
|
||||
try:
|
||||
async for message in websocket:
|
||||
message = json.loads(message)
|
||||
|
||||
if isinstance(message, dict) and message["event"] == "info" and "version" in message:
|
||||
if BfxWebsocketClient.VERSION != message["version"]:
|
||||
raise OutdatedClientVersion(f"Mismatch between the client version and the server version. Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, server version: {message['version']}).")
|
||||
elif isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]):
|
||||
self.pendings = [ pending for pending in self.pendings if pending["subId"] != message["subId"] ]
|
||||
self.subscriptions[chanId] = message
|
||||
self.event_emitter.emit("subscribed", message)
|
||||
elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]):
|
||||
if message["status"] == "OK":
|
||||
del self.subscriptions[chanId]
|
||||
elif isinstance(message, dict) and message["event"] == "error":
|
||||
self.event_emitter.emit("wss-error", message["code"], message["msg"])
|
||||
elif isinstance(message, list) and (chanId := message[0]) and message[1] != _HEARTBEAT:
|
||||
self.handler.handle(self.subscriptions[chanId], *message[1:])
|
||||
except websockets.ConnectionClosedError: continue
|
||||
finally: await self.websocket.wait_closed(); break
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _subscribe(self, channel, subId=None, **kwargs):
|
||||
if len(self.subscriptions) + len(self.pendings) == _BfxWebsocketBucket.MAXIMUM_SUBSCRIPTIONS_AMOUNT:
|
||||
raise TooManySubscriptions("The client has reached the maximum number of subscriptions.")
|
||||
|
||||
subscription = {
|
||||
"event": "subscribe",
|
||||
"channel": channel,
|
||||
"subId": subId or str(uuid.uuid4()),
|
||||
|
||||
**kwargs
|
||||
}
|
||||
|
||||
self.pendings.append(subscription)
|
||||
|
||||
await self.websocket.send(json.dumps(subscription))
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _unsubscribe(self, chanId):
|
||||
await self.websocket.send(json.dumps({
|
||||
"event": "unsubscribe",
|
||||
"chanId": chanId
|
||||
}))
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _close(self, code=1000, reason=str()):
|
||||
await self.websocket.close(code=code, reason=reason)
|
||||
@@ -1 +1,3 @@
|
||||
from .BfxWebsocketClient import BfxWebsocketClient
|
||||
from .client import BfxWebsocketClient, BfxWebsocketBucket, BfxWebsocketInputs
|
||||
|
||||
NAME = "websocket"
|
||||
5
bfxapi/websocket/client/__init__.py
Normal file
5
bfxapi/websocket/client/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .bfx_websocket_client import BfxWebsocketClient
|
||||
from .bfx_websocket_bucket import BfxWebsocketBucket
|
||||
from .bfx_websocket_inputs import BfxWebsocketInputs
|
||||
|
||||
NAME = "client"
|
||||
107
bfxapi/websocket/client/bfx_websocket_bucket.py
Normal file
107
bfxapi/websocket/client/bfx_websocket_bucket.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import json, uuid, websockets
|
||||
|
||||
from typing import Literal, TypeVar, Callable, cast
|
||||
|
||||
from ..handlers import PublicChannelsHandler
|
||||
|
||||
from ..exceptions import ConnectionNotOpen, TooManySubscriptions, OutdatedClientVersion
|
||||
|
||||
_HEARTBEAT = "hb"
|
||||
|
||||
F = TypeVar("F", bound=Callable[..., Literal[None]])
|
||||
|
||||
def _require_websocket_connection(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if self.websocket == None or self.websocket.open == False:
|
||||
raise ConnectionNotOpen("No open connection with the server.")
|
||||
|
||||
await function(self, *args, **kwargs)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
class BfxWebsocketBucket(object):
|
||||
VERSION = 2
|
||||
|
||||
MAXIMUM_SUBSCRIPTIONS_AMOUNT = 25
|
||||
|
||||
def __init__(self, host, event_emitter, on_open_event):
|
||||
self.host, self.event_emitter, self.on_open_event = host, event_emitter, on_open_event
|
||||
|
||||
self.websocket, self.subscriptions, self.pendings = None, dict(), list()
|
||||
|
||||
self.handler = PublicChannelsHandler(event_emitter=self.event_emitter)
|
||||
|
||||
async def _connect(self, index):
|
||||
reconnection = False
|
||||
|
||||
async for websocket in websockets.connect(self.host):
|
||||
self.websocket = websocket
|
||||
|
||||
self.on_open_event.set()
|
||||
|
||||
if reconnection == True or (reconnection := False):
|
||||
for pending in self.pendings:
|
||||
await self.websocket.send(json.dumps(pending))
|
||||
|
||||
for _, subscription in self.subscriptions.items():
|
||||
await self._subscribe(**subscription)
|
||||
|
||||
self.subscriptions.clear()
|
||||
|
||||
try:
|
||||
async for message in websocket:
|
||||
message = json.loads(message)
|
||||
|
||||
if isinstance(message, dict) and message["event"] == "subscribed" and (chanId := message["chanId"]):
|
||||
self.pendings = [ pending for pending in self.pendings if pending["subId"] != message["subId"] ]
|
||||
self.subscriptions[chanId] = message
|
||||
self.event_emitter.emit("subscribed", message)
|
||||
elif isinstance(message, dict) and message["event"] == "unsubscribed" and (chanId := message["chanId"]):
|
||||
if message["status"] == "OK":
|
||||
del self.subscriptions[chanId]
|
||||
elif isinstance(message, dict) and message["event"] == "error":
|
||||
self.event_emitter.emit("wss-error", message["code"], message["msg"])
|
||||
elif isinstance(message, list) and (chanId := message[0]) and message[1] != _HEARTBEAT:
|
||||
self.handler.handle(self.subscriptions[chanId], *message[1:])
|
||||
except websockets.ConnectionClosedError as error:
|
||||
if error.code == 1006:
|
||||
self.on_open_event.clear()
|
||||
reconnection = True
|
||||
continue
|
||||
|
||||
raise error
|
||||
|
||||
break
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _subscribe(self, channel, subId=None, **kwargs):
|
||||
if len(self.subscriptions) + len(self.pendings) == BfxWebsocketBucket.MAXIMUM_SUBSCRIPTIONS_AMOUNT:
|
||||
raise TooManySubscriptions("The client has reached the maximum number of subscriptions.")
|
||||
|
||||
subscription = {
|
||||
**kwargs,
|
||||
|
||||
"event": "subscribe",
|
||||
"channel": channel,
|
||||
"subId": subId or str(uuid.uuid4()),
|
||||
}
|
||||
|
||||
self.pendings.append(subscription)
|
||||
|
||||
await self.websocket.send(json.dumps(subscription))
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _unsubscribe(self, chanId):
|
||||
await self.websocket.send(json.dumps({
|
||||
"event": "unsubscribe",
|
||||
"chanId": chanId
|
||||
}))
|
||||
|
||||
@_require_websocket_connection
|
||||
async def _close(self, code=1000, reason=str()):
|
||||
await self.websocket.close(code=code, reason=reason)
|
||||
|
||||
def _get_chan_id(self, subId):
|
||||
for subscription in self.subscriptions.values():
|
||||
if subscription["subId"] == subId:
|
||||
return subscription["chanId"]
|
||||
246
bfxapi/websocket/client/bfx_websocket_client.py
Normal file
246
bfxapi/websocket/client/bfx_websocket_client.py
Normal file
@@ -0,0 +1,246 @@
|
||||
import traceback, json, asyncio, hmac, hashlib, time, websockets, socket, random
|
||||
|
||||
from typing import cast
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pyee.asyncio import AsyncIOEventEmitter
|
||||
|
||||
from .bfx_websocket_bucket import _HEARTBEAT, F, _require_websocket_connection, BfxWebsocketBucket
|
||||
|
||||
from .bfx_websocket_inputs import BfxWebsocketInputs
|
||||
from ..handlers import PublicChannelsHandler, AuthenticatedChannelsHandler
|
||||
from ..exceptions import WebsocketAuthenticationRequired, InvalidAuthenticationCredentials, EventNotSupported, OutdatedClientVersion
|
||||
|
||||
from ...utils.JSONEncoder import JSONEncoder
|
||||
|
||||
from ...utils.logger import ColoredLogger
|
||||
|
||||
def _require_websocket_authentication(function: F) -> F:
|
||||
async def wrapper(self, *args, **kwargs):
|
||||
if hasattr(self, "authentication") and self.authentication == False:
|
||||
raise WebsocketAuthenticationRequired("To perform this action you need to authenticate using your API_KEY and API_SECRET.")
|
||||
|
||||
await _require_websocket_connection(function)(self, *args, **kwargs)
|
||||
|
||||
return cast(F, wrapper)
|
||||
|
||||
class BfxWebsocketClient(object):
|
||||
VERSION = BfxWebsocketBucket.VERSION
|
||||
|
||||
MAXIMUM_CONNECTIONS_AMOUNT = 20
|
||||
|
||||
EVENTS = [
|
||||
"open", "subscribed", "authenticated", "wss-error",
|
||||
*PublicChannelsHandler.EVENTS,
|
||||
*AuthenticatedChannelsHandler.EVENTS
|
||||
]
|
||||
|
||||
def __init__(self, host, credentials = None, log_level = "INFO"):
|
||||
self.websocket = None
|
||||
|
||||
self.host, self.credentials, self.event_emitter = host, credentials, AsyncIOEventEmitter()
|
||||
|
||||
self.inputs = BfxWebsocketInputs(handle_websocket_input=self.__handle_websocket_input)
|
||||
|
||||
self.handler = AuthenticatedChannelsHandler(event_emitter=self.event_emitter)
|
||||
|
||||
self.logger = ColoredLogger("BfxWebsocketClient", level=log_level)
|
||||
|
||||
self.event_emitter.add_listener("error",
|
||||
lambda exception: self.logger.error(f"{type(exception).__name__}: {str(exception)}" + "\n" +
|
||||
str().join(traceback.format_exception(type(exception), exception, exception.__traceback__))[:-1])
|
||||
)
|
||||
|
||||
def run(self, connections = 5):
|
||||
return asyncio.run(self.start(connections))
|
||||
|
||||
async def start(self, connections = 5):
|
||||
if connections > BfxWebsocketClient.MAXIMUM_CONNECTIONS_AMOUNT:
|
||||
self.logger.warning(f"It is not safe to use more than {BfxWebsocketClient.MAXIMUM_CONNECTIONS_AMOUNT} buckets from the same " +
|
||||
f"connection ({connections} in use), the server could momentarily block the client with <429 Too Many Requests>.")
|
||||
|
||||
self.on_open_events = [ asyncio.Event() for _ in range(connections) ]
|
||||
|
||||
self.buckets = [
|
||||
BfxWebsocketBucket(self.host, self.event_emitter, self.on_open_events[index])
|
||||
for index in range(connections)
|
||||
]
|
||||
|
||||
tasks = [ bucket._connect(index) for index, bucket in enumerate(self.buckets) ]
|
||||
|
||||
tasks.append(self.__connect(self.credentials))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
async def __connect(self, credentials = None):
|
||||
Reconnection = namedtuple("Reconnection", ["status", "attempts", "timestamp"])
|
||||
|
||||
reconnection, delay = Reconnection(status=False, attempts=0, timestamp=None), None
|
||||
|
||||
async def _connection():
|
||||
nonlocal reconnection
|
||||
|
||||
async with websockets.connect(self.host) as websocket:
|
||||
if reconnection.status == True:
|
||||
self.logger.info(f"Reconnect attempt successful (attempt no.{reconnection.attempts}): The " +
|
||||
f"client has been offline for a total of {datetime.now() - reconnection.timestamp} " +
|
||||
f"(connection lost at: {reconnection.timestamp:%d-%m-%Y at %H:%M:%S}).")
|
||||
|
||||
reconnection = Reconnection(status=False, attempts=0, timestamp=None)
|
||||
|
||||
self.websocket, self.authentication = websocket, False
|
||||
|
||||
if await asyncio.gather(*[on_open_event.wait() for on_open_event in self.on_open_events]):
|
||||
self.event_emitter.emit("open")
|
||||
|
||||
if self.credentials:
|
||||
await self.__authenticate(**self.credentials)
|
||||
|
||||
async for message in websocket:
|
||||
message = json.loads(message)
|
||||
|
||||
if isinstance(message, dict) and message["event"] == "info" and "version" in message:
|
||||
if BfxWebsocketClient.VERSION != message["version"]:
|
||||
raise OutdatedClientVersion(f"Mismatch between the client version and the server version. " +
|
||||
f"Update the library to the latest version to continue (client version: {BfxWebsocketClient.VERSION}, " +
|
||||
f"server version: {message['version']}).")
|
||||
elif isinstance(message, dict) and message["event"] == "info" and message["code"] == 20051:
|
||||
rcvd = websockets.frames.Close(code=1012, reason="Stop/Restart Websocket Server (please reconnect).")
|
||||
|
||||
raise websockets.ConnectionClosedError(rcvd=rcvd, sent=None)
|
||||
elif isinstance(message, dict) and message["event"] == "auth":
|
||||
if message["status"] == "OK":
|
||||
self.event_emitter.emit("authenticated", message); self.authentication = True
|
||||
else: raise InvalidAuthenticationCredentials("Cannot authenticate with given API-KEY and API-SECRET.")
|
||||
elif isinstance(message, dict) and message["event"] == "error":
|
||||
self.event_emitter.emit("wss-error", message["code"], message["msg"])
|
||||
elif isinstance(message, list) and (chanId := message[0]) == 0 and message[1] != _HEARTBEAT:
|
||||
self.handler.handle(message[1], message[2])
|
||||
|
||||
class _Delay:
|
||||
BACKOFF_MIN, BACKOFF_MAX = 1.92, 60.0
|
||||
|
||||
BACKOFF_INITIAL = 5.0
|
||||
|
||||
def __init__(self, backoff_factor):
|
||||
self.__backoff_factor = backoff_factor
|
||||
self.__backoff_delay = _Delay.BACKOFF_MIN
|
||||
self.__initial_delay = random.random() * _Delay.BACKOFF_INITIAL
|
||||
|
||||
def next(self):
|
||||
backoff_delay = self.peek()
|
||||
__backoff_delay = self.__backoff_delay * self.__backoff_factor
|
||||
self.__backoff_delay = min(__backoff_delay, _Delay.BACKOFF_MAX)
|
||||
|
||||
return backoff_delay
|
||||
|
||||
def peek(self):
|
||||
return (self.__backoff_delay == _Delay.BACKOFF_MIN) \
|
||||
and self.__initial_delay or self.__backoff_delay
|
||||
|
||||
while True:
|
||||
if reconnection.status == True:
|
||||
await asyncio.sleep(delay.next())
|
||||
|
||||
try:
|
||||
await _connection()
|
||||
except (websockets.ConnectionClosedError, socket.gaierror) as error:
|
||||
if isinstance(error, websockets.ConnectionClosedError) and (error.code == 1006 or error.code == 1012):
|
||||
if error.code == 1006:
|
||||
self.logger.error("Connection lost: no close frame received "
|
||||
+ "or sent (1006). Attempting to reconnect...")
|
||||
|
||||
if error.code == 1012:
|
||||
self.logger.info("WSS server is about to restart, reconnection "
|
||||
+ "required (client received 20051). Attempt in progress...")
|
||||
|
||||
reconnection = Reconnection(status=True, attempts=1, timestamp=datetime.now());
|
||||
|
||||
delay = _Delay(backoff_factor=1.618)
|
||||
elif isinstance(error, socket.gaierror) and reconnection.status == True:
|
||||
self.logger.warning(f"Reconnection attempt no.{reconnection.attempts} has failed. "
|
||||
+ f"Next reconnection attempt in ~{round(delay.peek()):.1f} seconds."
|
||||
+ f"(at the moment the client has been offline for {datetime.now() - reconnection.timestamp})")
|
||||
|
||||
reconnection = reconnection._replace(attempts=reconnection.attempts + 1)
|
||||
else: raise error
|
||||
|
||||
if reconnection.status == False:
|
||||
break
|
||||
|
||||
async def __authenticate(self, API_KEY, API_SECRET, filter=None):
|
||||
data = { "event": "auth", "filter": filter, "apiKey": API_KEY }
|
||||
|
||||
data["authNonce"] = int(time.time()) * 1000
|
||||
|
||||
data["authPayload"] = "AUTH" + str(data["authNonce"])
|
||||
|
||||
data["authSig"] = hmac.new(
|
||||
API_SECRET.encode("utf8"),
|
||||
data["authPayload"].encode("utf8"),
|
||||
hashlib.sha384
|
||||
).hexdigest()
|
||||
|
||||
await self.websocket.send(json.dumps(data))
|
||||
|
||||
async def subscribe(self, channel, **kwargs):
|
||||
counters = [ len(bucket.pendings) + len(bucket.subscriptions) for bucket in self.buckets ]
|
||||
|
||||
index = counters.index(min(counters))
|
||||
|
||||
await self.buckets[index]._subscribe(channel, **kwargs)
|
||||
|
||||
async def unsubscribe(self, subId):
|
||||
for bucket in self.buckets:
|
||||
if (chanId := bucket._get_chan_id(subId)):
|
||||
await bucket._unsubscribe(chanId=chanId)
|
||||
|
||||
async def close(self, code=1000, reason=str()):
|
||||
if self.websocket != None and self.websocket.open == True:
|
||||
await self.websocket.close(code=code, reason=reason)
|
||||
|
||||
for bucket in self.buckets:
|
||||
await bucket._close(code=code, reason=reason)
|
||||
|
||||
@_require_websocket_authentication
|
||||
async def notify(self, info, MESSAGE_ID=None, **kwargs):
|
||||
await self.websocket.send(json.dumps([ 0, "n", MESSAGE_ID, { "type": "ucm-test", "info": info, **kwargs } ]))
|
||||
|
||||
@_require_websocket_authentication
|
||||
async def __handle_websocket_input(self, input, data):
|
||||
await self.websocket.send(json.dumps([ 0, input, None, data], cls=JSONEncoder))
|
||||
|
||||
def on(self, *events, callback = None):
|
||||
for event in events:
|
||||
if event not in BfxWebsocketClient.EVENTS:
|
||||
raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS")
|
||||
|
||||
if callback != None:
|
||||
for event in events:
|
||||
self.event_emitter.on(event, callback)
|
||||
|
||||
if callback == None:
|
||||
def handler(function):
|
||||
for event in events:
|
||||
self.event_emitter.on(event, function)
|
||||
|
||||
return handler
|
||||
|
||||
def once(self, *events, callback = None):
|
||||
for event in events:
|
||||
if event not in BfxWebsocketClient.EVENTS:
|
||||
raise EventNotSupported(f"Event <{event}> is not supported. To get a list of available events print BfxWebsocketClient.EVENTS")
|
||||
|
||||
if callback != None:
|
||||
for event in events:
|
||||
self.event_emitter.once(event, callback)
|
||||
|
||||
if callback == None:
|
||||
def handler(function):
|
||||
for event in events:
|
||||
self.event_emitter.once(event, function)
|
||||
|
||||
return handler
|
||||
@@ -2,77 +2,59 @@ from decimal import Decimal
|
||||
from datetime import datetime
|
||||
|
||||
from typing import Union, Optional, List, Tuple
|
||||
from .typings import JSON
|
||||
from .enums import OrderType, FundingOfferType
|
||||
from .. enums import OrderType, FundingOfferType
|
||||
from ... utils.JSONEncoder import JSON
|
||||
|
||||
def _strip(dictionary):
|
||||
return { key: value for key, value in dictionary.items() if value != None}
|
||||
class BfxWebsocketInputs(object):
|
||||
def __init__(self, handle_websocket_input):
|
||||
self.handle_websocket_input = handle_websocket_input
|
||||
|
||||
class _BfxWebsocketInputs(object):
|
||||
def __init__(self, __handle_websocket_input):
|
||||
self.__handle_websocket_input = __handle_websocket_input
|
||||
|
||||
async def submit_order(self, type: OrderType, symbol: str, amount: Union[Decimal, str],
|
||||
price: Optional[Union[Decimal, str]] = None, lev: Optional[int] = None,
|
||||
price_trailing: Optional[Union[Decimal, str]] = None, price_aux_limit: Optional[Union[Decimal, str]] = None, price_oco_stop: Optional[Union[Decimal, str]] = None,
|
||||
async 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_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,
|
||||
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,
|
||||
"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, "meta": meta
|
||||
})
|
||||
|
||||
await self.__handle_websocket_input("on", data)
|
||||
|
||||
async def update_order(self, id: int, amount: Optional[Union[Decimal, str]] = None, price: Optional[Union[Decimal, 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,
|
||||
flags: Optional[int] = 0, lev: Optional[int] = None, delta: Optional[Union[Decimal, str]] = None,
|
||||
price_aux_limit: Optional[Union[Decimal, str]] = None, price_trailing: Optional[Union[Decimal, str]] = None, tif: Optional[Union[datetime, str]] = None):
|
||||
data = _strip({
|
||||
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):
|
||||
await self.handle_websocket_input("ou", {
|
||||
"id": id, "amount": amount, "price": price,
|
||||
"cid": cid, "cid_date": cid_date, "gid": gid,
|
||||
"flags": flags, "lev": lev, "delta": delta,
|
||||
"price_aux_limit": price_aux_limit, "price_trailing": price_trailing, "tif": tif
|
||||
})
|
||||
|
||||
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):
|
||||
data = _strip({
|
||||
"id": id,
|
||||
"cid": cid,
|
||||
"cid_date": cid_date
|
||||
await self.handle_websocket_input("oc", {
|
||||
"id": id, "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):
|
||||
data = _strip({
|
||||
"ids": ids,
|
||||
"cids": cids,
|
||||
"gids": gids,
|
||||
|
||||
await self.handle_websocket_input("oc_multi", {
|
||||
"ids": ids, "cids": cids, "gids": gids,
|
||||
"all": int(all)
|
||||
})
|
||||
|
||||
await self.__handle_websocket_input("oc_multi", data)
|
||||
|
||||
async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, str],
|
||||
rate: Union[Decimal, str], period: int,
|
||||
async def submit_funding_offer(self, type: FundingOfferType, symbol: str, amount: Union[Decimal, float, str],
|
||||
rate: Union[Decimal, float, str], period: int,
|
||||
flags: Optional[int] = 0):
|
||||
data = {
|
||||
await self.handle_websocket_input("fon", {
|
||||
"type": type, "symbol": symbol, "amount": amount,
|
||||
"rate": rate, "period": period,
|
||||
"flags": flags
|
||||
}
|
||||
|
||||
await self.__handle_websocket_input("fon", data)
|
||||
})
|
||||
|
||||
async def cancel_funding_offer(self, id: int):
|
||||
await self.__handle_websocket_input("foc", { "id": id })
|
||||
await self.handle_websocket_input("foc", { "id": id })
|
||||
|
||||
async def calc(self, *args: str):
|
||||
await self.__handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
|
||||
await self.handle_websocket_input("calc", list(map(lambda arg: [arg], args)))
|
||||
@@ -1,6 +1,6 @@
|
||||
from ..enums import *
|
||||
from .. enums import *
|
||||
|
||||
class Channels(str, Enum):
|
||||
class Channel(str, Enum):
|
||||
TICKER = "ticker"
|
||||
TRADES = "trades"
|
||||
BOOK = "book"
|
||||
|
||||
@@ -58,4 +58,11 @@ class InvalidAuthenticationCredentials(BfxWebsocketException):
|
||||
This error indicates that the user has provided incorrect credentials (API-KEY and API-SECRET) for authentication.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
class HandlerNotFound(BfxWebsocketException):
|
||||
"""
|
||||
This error indicates that a handler was not found for an incoming message.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,186 +0,0 @@
|
||||
from . import serializers
|
||||
from .enums import Channels
|
||||
from .exceptions import BfxWebsocketException
|
||||
|
||||
def _get_sub_dictionary(dictionary, keys):
|
||||
return { key: dictionary[key] for key in dictionary if key in keys }
|
||||
|
||||
class PublicChannelsHandler(object):
|
||||
EVENTS = [
|
||||
"t_ticker_update", "f_ticker_update",
|
||||
"t_trade_executed", "t_trade_execution_update", "f_trade_executed", "f_trade_execution_update", "t_trades_snapshot", "f_trades_snapshot",
|
||||
"t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot", "f_raw_book_snapshot", "t_book_update", "f_book_update", "t_raw_book_update", "f_raw_book_update",
|
||||
"candles_snapshot", "candles_update",
|
||||
"derivatives_status_update",
|
||||
]
|
||||
|
||||
def __init__(self, event_emitter):
|
||||
self.event_emitter = event_emitter
|
||||
|
||||
self.__handlers = {
|
||||
Channels.TICKER: self.__ticker_channel_handler,
|
||||
Channels.TRADES: self.__trades_channel_handler,
|
||||
Channels.BOOK: self.__book_channel_handler,
|
||||
Channels.CANDLES: self.__candles_channel_handler,
|
||||
Channels.STATUS: self.__status_channel_handler
|
||||
}
|
||||
|
||||
def handle(self, subscription, *stream):
|
||||
if channel := subscription["channel"] or channel in self.__handlers.keys():
|
||||
return self.__handlers[channel](subscription, *stream)
|
||||
|
||||
def __ticker_channel_handler(self, subscription, *stream):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
"t_ticker_update",
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "pair" ]),
|
||||
serializers.TradingPairTicker.parse(*stream[0])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
"f_ticker_update",
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "currency" ]),
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0])
|
||||
)
|
||||
|
||||
def __trades_channel_handler(self, subscription, *stream):
|
||||
if type := stream[0] or type in [ "te", "tu", "fte", "ftu" ]:
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
{ "te": "t_trade_executed", "tu": "t_trade_execution_update" }[type],
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "pair" ]),
|
||||
serializers.TradingPairTrade.parse(*stream[1])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
{ "fte": "f_trade_executed", "ftu": "f_trade_execution_update" }[type],
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "currency" ]),
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
"t_trades_snapshot",
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "pair" ]),
|
||||
[ serializers.TradingPairTrade.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
"f_trades_snapshot",
|
||||
_get_sub_dictionary(subscription, [ "chanId", "symbol", "currency" ]),
|
||||
[ serializers.FundingCurrencyTrade.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
def __book_channel_handler(self, subscription, *stream):
|
||||
subscription = _get_sub_dictionary(subscription, [ "chanId", "symbol", "prec", "freq", "len", "subId", "pair" ])
|
||||
|
||||
type = subscription["symbol"][0]
|
||||
|
||||
if subscription["prec"] == "R0":
|
||||
_trading_pair_serializer, _funding_currency_serializer, IS_RAW_BOOK = serializers.TradingPairRawBook, serializers.FundingCurrencyRawBook, True
|
||||
else: _trading_pair_serializer, _funding_currency_serializer, IS_RAW_BOOK = serializers.TradingPairBook, serializers.FundingCurrencyBook, False
|
||||
|
||||
if all(isinstance(substream, list) for substream in stream[0]):
|
||||
return self.event_emitter.emit(
|
||||
type + "_" + (IS_RAW_BOOK and "raw_book" or "book") + "_snapshot",
|
||||
subscription,
|
||||
[ { "t": _trading_pair_serializer, "f": _funding_currency_serializer }[type].parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
return self.event_emitter.emit(
|
||||
type + "_" + (IS_RAW_BOOK and "raw_book" or "book") + "_update",
|
||||
subscription,
|
||||
{ "t": _trading_pair_serializer, "f": _funding_currency_serializer }[type].parse(*stream[0])
|
||||
)
|
||||
|
||||
def __candles_channel_handler(self, subscription, *stream):
|
||||
subscription = _get_sub_dictionary(subscription, [ "chanId", "key" ])
|
||||
|
||||
if all(isinstance(substream, list) for substream in stream[0]):
|
||||
return self.event_emitter.emit(
|
||||
"candles_snapshot",
|
||||
subscription,
|
||||
[ serializers.Candle.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
return self.event_emitter.emit(
|
||||
"candles_update",
|
||||
subscription,
|
||||
serializers.Candle.parse(*stream[0])
|
||||
)
|
||||
|
||||
def __status_channel_handler(self, subscription, *stream):
|
||||
subscription = _get_sub_dictionary(subscription, [ "chanId", "key" ])
|
||||
|
||||
if subscription["key"].startswith("deriv:"):
|
||||
return self.event_emitter.emit(
|
||||
"derivatives_status_update",
|
||||
subscription,
|
||||
serializers.DerivativesStatus.parse(*stream[0])
|
||||
)
|
||||
|
||||
class AuthenticatedChannelsHandler(object):
|
||||
__abbreviations = {
|
||||
"os": "order_snapshot", "on": "order_new", "ou": "order_update", "oc": "order_cancel",
|
||||
"ps": "position_snapshot", "pn": "position_new", "pu": "position_update", "pc": "position_close",
|
||||
"te": "trade_executed", "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",
|
||||
"bu": "balance_update",
|
||||
}
|
||||
|
||||
__serializers = {
|
||||
("os", "on", "ou", "oc",): serializers.Order,
|
||||
("ps", "pn", "pu", "pc",): serializers.Position,
|
||||
("te",): serializers.TradeExecuted,
|
||||
("tu",): serializers.TradeExecutionUpdate,
|
||||
("fos", "fon", "fou", "foc",): serializers.FundingOffer,
|
||||
("fcs", "fcn", "fcu", "fcc",): serializers.FundingCredit,
|
||||
("fls", "fln", "flu", "flc",): serializers.FundingLoan,
|
||||
("ws", "wu",): serializers.Wallet,
|
||||
("bu",): serializers.BalanceInfo
|
||||
}
|
||||
|
||||
EVENTS = [
|
||||
"notification",
|
||||
"on-req-notification", "ou-req-notification", "oc-req-notification",
|
||||
"oc_multi-notification",
|
||||
"fon-req-notification", "foc-req-notification",
|
||||
*list(__abbreviations.values())
|
||||
]
|
||||
|
||||
def __init__(self, event_emitter, strict = False):
|
||||
self.event_emitter, self.strict = event_emitter, strict
|
||||
|
||||
def handle(self, type, stream):
|
||||
if type == "n":
|
||||
return self.__notification(stream)
|
||||
|
||||
for types, serializer in AuthenticatedChannelsHandler.__serializers.items():
|
||||
if type in types:
|
||||
event = AuthenticatedChannelsHandler.__abbreviations[type]
|
||||
|
||||
if all(isinstance(substream, list) for substream in stream):
|
||||
return self.event_emitter.emit(event, [ serializer.parse(*substream) for substream in stream ])
|
||||
|
||||
return self.event_emitter.emit(event, serializer.parse(*stream))
|
||||
|
||||
if self.strict == True:
|
||||
raise BfxWebsocketException(f"Event of type <{type}> not found in self.__handlers.")
|
||||
|
||||
def __notification(self, stream):
|
||||
if stream[1] == "on-req" or stream[1] == "ou-req" or stream[1] == "oc-req":
|
||||
return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order).parse(*stream))
|
||||
|
||||
if stream[1] == "oc_multi-req":
|
||||
return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order, iterate=True).parse(*stream))
|
||||
|
||||
if stream[1] == "fon-req" or stream[1] == "foc-req":
|
||||
return self.event_emitter.emit(f"{stream[1]}-notification", serializers._Notification(serializer=serializers.FundingOffer).parse(*stream))
|
||||
|
||||
return self.event_emitter.emit("notification", serializers._Notification(serializer=None).parse(*stream))
|
||||
4
bfxapi/websocket/handlers/__init__.py
Normal file
4
bfxapi/websocket/handlers/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .public_channels_handler import PublicChannelsHandler
|
||||
from .authenticated_channels_handler import AuthenticatedChannelsHandler
|
||||
|
||||
NAME = "handlers"
|
||||
69
bfxapi/websocket/handlers/authenticated_channels_handler.py
Normal file
69
bfxapi/websocket/handlers/authenticated_channels_handler.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from .. import serializers
|
||||
|
||||
from .. types import *
|
||||
|
||||
from .. exceptions import HandlerNotFound
|
||||
|
||||
class AuthenticatedChannelsHandler(object):
|
||||
__abbreviations = {
|
||||
"os": "order_snapshot", "on": "order_new", "ou": "order_update", "oc": "order_cancel",
|
||||
"ps": "position_snapshot", "pn": "position_new", "pu": "position_update", "pc": "position_close",
|
||||
"te": "trade_executed", "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",
|
||||
"bu": "balance_update",
|
||||
}
|
||||
|
||||
__serializers = {
|
||||
("os", "on", "ou", "oc",): serializers.Order,
|
||||
("ps", "pn", "pu", "pc",): serializers.Position,
|
||||
("te", "tu"): serializers.Trade,
|
||||
("fos", "fon", "fou", "foc",): serializers.FundingOffer,
|
||||
("fcs", "fcn", "fcu", "fcc",): serializers.FundingCredit,
|
||||
("fls", "fln", "flu", "flc",): serializers.FundingLoan,
|
||||
("ws", "wu",): serializers.Wallet,
|
||||
("bu",): serializers.Balance
|
||||
}
|
||||
|
||||
EVENTS = [
|
||||
"notification",
|
||||
"on-req-notification", "ou-req-notification", "oc-req-notification",
|
||||
"oc_multi-notification",
|
||||
"fon-req-notification", "foc-req-notification",
|
||||
*list(__abbreviations.values())
|
||||
]
|
||||
|
||||
def __init__(self, event_emitter, strict = True):
|
||||
self.event_emitter, self.strict = event_emitter, strict
|
||||
|
||||
def handle(self, type, stream):
|
||||
if type == "n":
|
||||
return self.__notification(stream)
|
||||
|
||||
for types, serializer in AuthenticatedChannelsHandler.__serializers.items():
|
||||
if type in types:
|
||||
event = AuthenticatedChannelsHandler.__abbreviations[type]
|
||||
|
||||
if all(isinstance(substream, list) for substream in stream):
|
||||
return self.event_emitter.emit(event, [ serializer.parse(*substream) for substream in stream ])
|
||||
|
||||
return self.event_emitter.emit(event, serializer.parse(*stream))
|
||||
|
||||
if self.strict:
|
||||
raise HandlerNotFound(f"No handler found for event of type <{type}>.")
|
||||
|
||||
def __notification(self, stream):
|
||||
type, serializer = "notification", serializers._Notification(serializer=None)
|
||||
|
||||
if stream[1] == "on-req" or stream[1] == "ou-req" or stream[1] == "oc-req":
|
||||
type, serializer = f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order)
|
||||
|
||||
if stream[1] == "oc_multi-req":
|
||||
type, serializer = f"{stream[1]}-notification", serializers._Notification(serializer=serializers.Order, iterate=True)
|
||||
|
||||
if stream[1] == "fon-req" or stream[1] == "foc-req":
|
||||
type, serializer = f"{stream[1]}-notification", serializers._Notification(serializer=serializers.FundingOffer)
|
||||
|
||||
return self.event_emitter.emit(type, serializer.parse(*stream))
|
||||
121
bfxapi/websocket/handlers/public_channels_handler.py
Normal file
121
bfxapi/websocket/handlers/public_channels_handler.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from .. import serializers
|
||||
|
||||
from .. types import *
|
||||
|
||||
from .. exceptions import HandlerNotFound
|
||||
|
||||
class PublicChannelsHandler(object):
|
||||
EVENTS = [
|
||||
"t_ticker_update", "f_ticker_update",
|
||||
"t_trade_executed", "t_trade_execution_update", "f_trade_executed", "f_trade_execution_update", "t_trades_snapshot", "f_trades_snapshot",
|
||||
"t_book_snapshot", "f_book_snapshot", "t_raw_book_snapshot", "f_raw_book_snapshot", "t_book_update", "f_book_update", "t_raw_book_update", "f_raw_book_update",
|
||||
"candles_snapshot", "candles_update",
|
||||
"derivatives_status_update",
|
||||
]
|
||||
|
||||
def __init__(self, event_emitter, strict = True):
|
||||
self.event_emitter, self.strict = event_emitter, strict
|
||||
|
||||
self.__handlers = {
|
||||
"ticker": self.__ticker_channel_handler,
|
||||
"trades": self.__trades_channel_handler,
|
||||
"book": self.__book_channel_handler,
|
||||
"candles": self.__candles_channel_handler,
|
||||
"status": self.__status_channel_handler
|
||||
}
|
||||
|
||||
def handle(self, subscription, *stream):
|
||||
_clear = lambda dictionary, *args: { key: value for key, value in dictionary.items() if key not in args }
|
||||
|
||||
if (channel := subscription["channel"]) and channel in self.__handlers.keys():
|
||||
return self.__handlers[channel](_clear(subscription, "event", "channel", "chanId"), *stream)
|
||||
|
||||
if self.strict:
|
||||
raise HandlerNotFound(f"No handler found for channel <{subscription['channel']}>.")
|
||||
|
||||
def __ticker_channel_handler(self, subscription, *stream):
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
"t_ticker_update",
|
||||
subscription,
|
||||
serializers.TradingPairTicker.parse(*stream[0])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
"f_ticker_update",
|
||||
subscription,
|
||||
serializers.FundingCurrencyTicker.parse(*stream[0])
|
||||
)
|
||||
|
||||
def __trades_channel_handler(self, subscription, *stream):
|
||||
if (type := stream[0]) and type in [ "te", "tu", "fte", "ftu" ]:
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
{ "te": "t_trade_executed", "tu": "t_trade_execution_update" }[type],
|
||||
subscription,
|
||||
serializers.TradingPairTrade.parse(*stream[1])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
{ "fte": "f_trade_executed", "ftu": "f_trade_execution_update" }[type],
|
||||
subscription,
|
||||
serializers.FundingCurrencyTrade.parse(*stream[1])
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("t"):
|
||||
return self.event_emitter.emit(
|
||||
"t_trades_snapshot",
|
||||
subscription,
|
||||
[ serializers.TradingPairTrade.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
if subscription["symbol"].startswith("f"):
|
||||
return self.event_emitter.emit(
|
||||
"f_trades_snapshot",
|
||||
subscription,
|
||||
[ serializers.FundingCurrencyTrade.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
def __book_channel_handler(self, subscription, *stream):
|
||||
type = subscription["symbol"][0]
|
||||
|
||||
if subscription["prec"] == "R0":
|
||||
_trading_pair_serializer, _funding_currency_serializer, IS_RAW_BOOK = serializers.TradingPairRawBook, serializers.FundingCurrencyRawBook, True
|
||||
else: _trading_pair_serializer, _funding_currency_serializer, IS_RAW_BOOK = serializers.TradingPairBook, serializers.FundingCurrencyBook, False
|
||||
|
||||
if all(isinstance(substream, list) for substream in stream[0]):
|
||||
return self.event_emitter.emit(
|
||||
type + "_" + (IS_RAW_BOOK and "raw_book" or "book") + "_snapshot",
|
||||
subscription,
|
||||
[ { "t": _trading_pair_serializer, "f": _funding_currency_serializer }[type].parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
return self.event_emitter.emit(
|
||||
type + "_" + (IS_RAW_BOOK and "raw_book" or "book") + "_update",
|
||||
subscription,
|
||||
{ "t": _trading_pair_serializer, "f": _funding_currency_serializer }[type].parse(*stream[0])
|
||||
)
|
||||
|
||||
def __candles_channel_handler(self, subscription, *stream):
|
||||
if all(isinstance(substream, list) for substream in stream[0]):
|
||||
return self.event_emitter.emit(
|
||||
"candles_snapshot",
|
||||
subscription,
|
||||
[ serializers.Candle.parse(*substream) for substream in stream[0] ]
|
||||
)
|
||||
|
||||
return self.event_emitter.emit(
|
||||
"candles_update",
|
||||
subscription,
|
||||
serializers.Candle.parse(*stream[0])
|
||||
)
|
||||
|
||||
def __status_channel_handler(self, subscription, *stream):
|
||||
if subscription["key"].startswith("deriv:"):
|
||||
return self.event_emitter.emit(
|
||||
"derivatives_status_update",
|
||||
subscription,
|
||||
serializers.DerivativesStatus.parse(*stream[0])
|
||||
)
|
||||
@@ -1,297 +1,293 @@
|
||||
from . import typings
|
||||
from . import types
|
||||
|
||||
from .. labeler import _Serializer
|
||||
from .. labeler import generate_labeler_serializer
|
||||
|
||||
from .. notification import _Notification
|
||||
|
||||
__serializers__ = [
|
||||
"TradingPairTicker", "FundingCurrencyTicker", "TradingPairTrade",
|
||||
"FundingCurrencyTrade", "TradingPairBook", "FundingCurrencyBook",
|
||||
"TradingPairRawBook", "FundingCurrencyRawBook", "Candle",
|
||||
"DerivativesStatus",
|
||||
|
||||
"Order", "Position", "Trade",
|
||||
"FundingOffer", "FundingCredit", "FundingLoan",
|
||||
"Wallet", "Balance",
|
||||
]
|
||||
|
||||
#region Serializers definition for Websocket Public Channels
|
||||
|
||||
TradingPairTicker = _Serializer[typings.TradingPairTicker]("TradingPairTicker", labels=[
|
||||
"BID",
|
||||
"BID_SIZE",
|
||||
"ASK",
|
||||
"ASK_SIZE",
|
||||
"DAILY_CHANGE",
|
||||
"DAILY_CHANGE_RELATIVE",
|
||||
"LAST_PRICE",
|
||||
"VOLUME",
|
||||
"HIGH",
|
||||
"LOW"
|
||||
TradingPairTicker = generate_labeler_serializer("TradingPairTicker", klass=types.TradingPairTicker, labels=[
|
||||
"bid",
|
||||
"bid_size",
|
||||
"ask",
|
||||
"ask_size",
|
||||
"daily_change",
|
||||
"daily_change_relative",
|
||||
"last_price",
|
||||
"volume",
|
||||
"high",
|
||||
"low"
|
||||
])
|
||||
|
||||
FundingCurrencyTicker = _Serializer[typings.FundingCurrencyTicker]("FundingCurrencyTicker", labels=[
|
||||
"FRR",
|
||||
"BID",
|
||||
"BID_PERIOD",
|
||||
"BID_SIZE",
|
||||
"ASK",
|
||||
"ASK_PERIOD",
|
||||
"ASK_SIZE",
|
||||
"DAILY_CHANGE",
|
||||
"DAILY_CHANGE_RELATIVE",
|
||||
"LAST_PRICE",
|
||||
"VOLUME",
|
||||
"HIGH",
|
||||
"LOW"
|
||||
FundingCurrencyTicker = generate_labeler_serializer("FundingCurrencyTicker", klass=types.FundingCurrencyTicker, labels=[
|
||||
"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"
|
||||
"frr_amount_available"
|
||||
])
|
||||
|
||||
TradingPairTrade = _Serializer[typings.TradingPairTrade]("TradingPairTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"PRICE"
|
||||
TradingPairTrade = generate_labeler_serializer("TradingPairTrade", klass=types.TradingPairTrade, labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"price"
|
||||
])
|
||||
|
||||
FundingCurrencyTrade = _Serializer[typings.FundingCurrencyTrade]("FundingCurrencyTrade", labels=[
|
||||
"ID",
|
||||
"MTS",
|
||||
"AMOUNT",
|
||||
"RATE",
|
||||
"PERIOD"
|
||||
FundingCurrencyTrade = generate_labeler_serializer("FundingCurrencyTrade", klass=types.FundingCurrencyTrade, labels=[
|
||||
"id",
|
||||
"mts",
|
||||
"amount",
|
||||
"rate",
|
||||
"period"
|
||||
])
|
||||
|
||||
TradingPairBook = _Serializer[typings.TradingPairBook]("TradingPairBook", labels=[
|
||||
"PRICE",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
TradingPairBook = generate_labeler_serializer("TradingPairBook", klass=types.TradingPairBook, labels=[
|
||||
"price",
|
||||
"count",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingCurrencyBook = _Serializer[typings.FundingCurrencyBook]("FundingCurrencyBook", labels=[
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"COUNT",
|
||||
"AMOUNT"
|
||||
FundingCurrencyBook = generate_labeler_serializer("FundingCurrencyBook", klass=types.FundingCurrencyBook, labels=[
|
||||
"rate",
|
||||
"period",
|
||||
"count",
|
||||
"amount"
|
||||
])
|
||||
|
||||
TradingPairRawBook = _Serializer[typings.TradingPairRawBook]("TradingPairRawBook", labels=[
|
||||
"ORDER_ID",
|
||||
"PRICE",
|
||||
"AMOUNT"
|
||||
TradingPairRawBook = generate_labeler_serializer("TradingPairRawBook", klass=types.TradingPairRawBook, labels=[
|
||||
"order_id",
|
||||
"price",
|
||||
"amount"
|
||||
])
|
||||
|
||||
FundingCurrencyRawBook = _Serializer[typings.FundingCurrencyRawBook]("FundingCurrencyRawBook", labels=[
|
||||
"OFFER_ID",
|
||||
"PERIOD",
|
||||
"RATE",
|
||||
"AMOUNT"
|
||||
FundingCurrencyRawBook = generate_labeler_serializer("FundingCurrencyRawBook", klass=types.FundingCurrencyRawBook, labels=[
|
||||
"offer_id",
|
||||
"period",
|
||||
"rate",
|
||||
"amount"
|
||||
])
|
||||
|
||||
Candle = _Serializer[typings.Candle]("Candle", labels=[
|
||||
"MTS",
|
||||
"OPEN",
|
||||
"CLOSE",
|
||||
"HIGH",
|
||||
"LOW",
|
||||
"VOLUME"
|
||||
Candle = generate_labeler_serializer("Candle", klass=types.Candle, labels=[
|
||||
"mts",
|
||||
"open",
|
||||
"close",
|
||||
"high",
|
||||
"low",
|
||||
"volume"
|
||||
])
|
||||
|
||||
DerivativesStatus = _Serializer[typings.DerivativesStatus]("DerivativesStatus", labels=[
|
||||
"TIME_MS",
|
||||
DerivativesStatus = generate_labeler_serializer("DerivativesStatus", klass=types.DerivativesStatus, labels=[
|
||||
"mts",
|
||||
"_PLACEHOLDER",
|
||||
"DERIV_PRICE",
|
||||
"SPOT_PRICE",
|
||||
"deriv_price",
|
||||
"spot_price",
|
||||
"_PLACEHOLDER",
|
||||
"INSURANCE_FUND_BALANCE",
|
||||
"insurance_fund_balance",
|
||||
"_PLACEHOLDER",
|
||||
"NEXT_FUNDING_EVT_TIMESTAMP_MS",
|
||||
"NEXT_FUNDING_ACCRUED",
|
||||
"NEXT_FUNDING_STEP",
|
||||
"next_funding_evt_timestamp_ms",
|
||||
"next_funding_accrued",
|
||||
"next_funding_step",
|
||||
"_PLACEHOLDER",
|
||||
"CURRENT_FUNDING"
|
||||
"current_funding",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"MARK_PRICE",
|
||||
"mark_price",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"OPEN_INTEREST",
|
||||
"open_interest",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"CLAMP_MIN",
|
||||
"CLAMP_MAX"
|
||||
"clamp_min",
|
||||
"clamp_max"
|
||||
])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serializers definition for Websocket Authenticated Channels
|
||||
|
||||
Order = _Serializer[typings.Order]("Order", labels=[
|
||||
"ID",
|
||||
"GID",
|
||||
"CID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"ORDER_TYPE",
|
||||
"TYPE_PREV",
|
||||
"MTS_TIF",
|
||||
Order = generate_labeler_serializer("Order", klass=types.Order, labels=[
|
||||
"id",
|
||||
"gid",
|
||||
"cid",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"order_type",
|
||||
"type_prev",
|
||||
"mts_tif",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"ORDER_STATUS",
|
||||
"flags",
|
||||
"order_status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"PRICE",
|
||||
"PRICE_AVG",
|
||||
"PRICE_TRAILING",
|
||||
"PRICE_AUX_LIMIT",
|
||||
"price",
|
||||
"price_avg",
|
||||
"price_trailing",
|
||||
"price_aux_limit",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"PLACED_ID",
|
||||
"notify",
|
||||
"hidden",
|
||||
"placed_id",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"ROUTING",
|
||||
"routing",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"META"
|
||||
"meta"
|
||||
])
|
||||
|
||||
Position = _Serializer[typings.Position]("Position", labels=[
|
||||
"SYMBOL",
|
||||
"STATUS",
|
||||
"AMOUNT",
|
||||
"BASE_PRICE",
|
||||
"MARGIN_FUNDING",
|
||||
"MARGIN_FUNDING_TYPE",
|
||||
"PL",
|
||||
"PL_PERC",
|
||||
"PRICE_LIQ",
|
||||
"LEVERAGE",
|
||||
"FLAG",
|
||||
"POSITION_ID",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
Position = generate_labeler_serializer("Position", klass=types.Position, labels=[
|
||||
"symbol",
|
||||
"status",
|
||||
"amount",
|
||||
"base_price",
|
||||
"margin_funding",
|
||||
"margin_funding_type",
|
||||
"pl",
|
||||
"pl_perc",
|
||||
"price_liq",
|
||||
"leverage",
|
||||
"flag",
|
||||
"position_id",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"_PLACEHOLDER",
|
||||
"TYPE",
|
||||
"type",
|
||||
"_PLACEHOLDER",
|
||||
"COLLATERAL",
|
||||
"COLLATERAL_MIN",
|
||||
"META"
|
||||
"collateral",
|
||||
"collateral_min",
|
||||
"meta"
|
||||
])
|
||||
|
||||
TradeExecuted = _Serializer[typings.TradeExecuted]("TradeExecuted", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATE",
|
||||
"ORDER_ID",
|
||||
"EXEC_AMOUNT",
|
||||
"EXEC_PRICE",
|
||||
"ORDER_TYPE",
|
||||
"ORDER_PRICE",
|
||||
"MAKER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"CID"
|
||||
Trade = generate_labeler_serializer("Trade", klass=types.Trade, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"order_id",
|
||||
"exec_amount",
|
||||
"exec_price",
|
||||
"order_type",
|
||||
"order_price",
|
||||
"maker",
|
||||
"fee",
|
||||
"fee_currency",
|
||||
"cid"
|
||||
])
|
||||
|
||||
TradeExecutionUpdate = _Serializer[typings.TradeExecutionUpdate]("TradeExecutionUpdate", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATE",
|
||||
"ORDER_ID",
|
||||
"EXEC_AMOUNT",
|
||||
"EXEC_PRICE",
|
||||
"ORDER_TYPE",
|
||||
"ORDER_PRICE",
|
||||
"MAKER",
|
||||
"FEE",
|
||||
"FEE_CURRENCY",
|
||||
"CID"
|
||||
])
|
||||
|
||||
FundingOffer = _Serializer[typings.FundingOffer]("FundingOffer", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"MTS_CREATED",
|
||||
"MTS_UPDATED",
|
||||
"AMOUNT",
|
||||
"AMOUNT_ORIG",
|
||||
"OFFER_TYPE",
|
||||
FundingOffer = generate_labeler_serializer("FundingOffer", klass=types.FundingOffer, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"amount_orig",
|
||||
"offer_type",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"FLAGS",
|
||||
"STATUS",
|
||||
"flags",
|
||||
"offer_status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"rate",
|
||||
"period",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"renew",
|
||||
"_PLACEHOLDER"
|
||||
])
|
||||
|
||||
FundingCredit = _Serializer[typings.FundingCredit]("FundingCredit", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"SIDE",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"FLAGS",
|
||||
"STATUS",
|
||||
FundingCredit = generate_labeler_serializer("FundingCredit", klass=types.FundingCredit, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"side",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"flags",
|
||||
"status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"MTS_OPENING",
|
||||
"MTS_LAST_PAYOUT",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"rate",
|
||||
"period",
|
||||
"mts_opening",
|
||||
"mts_last_payout",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"RATE_REAL",
|
||||
"NO_CLOSE",
|
||||
"POSITION_PAIR"
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close",
|
||||
"position_pair"
|
||||
])
|
||||
|
||||
FundingLoan = _Serializer[typings.FundingLoan]("FundingLoan", labels=[
|
||||
"ID",
|
||||
"SYMBOL",
|
||||
"SIDE",
|
||||
"MTS_CREATE",
|
||||
"MTS_UPDATE",
|
||||
"AMOUNT",
|
||||
"FLAGS",
|
||||
"STATUS",
|
||||
FundingLoan = generate_labeler_serializer("FundingLoan", klass=types.FundingLoan, labels=[
|
||||
"id",
|
||||
"symbol",
|
||||
"side",
|
||||
"mts_create",
|
||||
"mts_update",
|
||||
"amount",
|
||||
"flags",
|
||||
"status",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"_PLACEHOLDER",
|
||||
"RATE",
|
||||
"PERIOD",
|
||||
"MTS_OPENING",
|
||||
"MTS_LAST_PAYOUT",
|
||||
"NOTIFY",
|
||||
"HIDDEN",
|
||||
"rate",
|
||||
"period",
|
||||
"mts_opening",
|
||||
"mts_last_payout",
|
||||
"notify",
|
||||
"hidden",
|
||||
"_PLACEHOLDER",
|
||||
"RENEW",
|
||||
"RATE_REAL",
|
||||
"NO_CLOSE"
|
||||
"renew",
|
||||
"_PLACEHOLDER",
|
||||
"no_close"
|
||||
])
|
||||
|
||||
Wallet = _Serializer[typings.Wallet]("Wallet", labels=[
|
||||
"WALLET_TYPE",
|
||||
"CURRENCY",
|
||||
"BALANCE",
|
||||
"UNSETTLED_INTEREST",
|
||||
"BALANCE_AVAILABLE",
|
||||
"DESCRIPTION",
|
||||
"META"
|
||||
Wallet = generate_labeler_serializer("Wallet", klass=types.Wallet, labels=[
|
||||
"wallet_type",
|
||||
"currency",
|
||||
"balance",
|
||||
"unsettled_interest",
|
||||
"available_balance",
|
||||
"last_change",
|
||||
"trade_details"
|
||||
])
|
||||
|
||||
BalanceInfo = _Serializer[typings.BalanceInfo]("BalanceInfo", labels=[
|
||||
"AUM",
|
||||
"AUM_NET",
|
||||
Balance = generate_labeler_serializer("Balance", klass=types.Balance, labels=[
|
||||
"aum",
|
||||
"aum_net",
|
||||
])
|
||||
|
||||
#endregion
|
||||
41
bfxapi/websocket/subscriptions.py
Normal file
41
bfxapi/websocket/subscriptions.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from typing import TypedDict, Union, Literal, Optional
|
||||
|
||||
__all__ = [
|
||||
"Subscription",
|
||||
|
||||
"Ticker",
|
||||
"Trades",
|
||||
"Book",
|
||||
"Candles",
|
||||
"Status"
|
||||
]
|
||||
|
||||
_Header = TypedDict("_Header", { "event": Literal["subscribed"], "channel": str, "chanId": int })
|
||||
|
||||
Subscription = Union["Ticker", "Trades", "Book", "Candles", "Status"]
|
||||
|
||||
class Ticker(TypedDict):
|
||||
subId: str; symbol: str
|
||||
pair: Optional[str]
|
||||
currency: Optional[str]
|
||||
|
||||
class Trades(TypedDict):
|
||||
subId: str; symbol: str
|
||||
pair: Optional[str]
|
||||
currency: Optional[str]
|
||||
|
||||
class Book(TypedDict):
|
||||
subId: str
|
||||
symbol: str
|
||||
prec: str
|
||||
freq: str
|
||||
len: str
|
||||
pair: str
|
||||
|
||||
class Candles(TypedDict):
|
||||
subId: str
|
||||
key: str
|
||||
|
||||
class Status(TypedDict):
|
||||
subId: str
|
||||
key: str
|
||||
241
bfxapi/websocket/types.py
Normal file
241
bfxapi/websocket/types.py
Normal file
@@ -0,0 +1,241 @@
|
||||
from typing import Optional
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .. labeler import _Type
|
||||
from .. notification import Notification
|
||||
from .. utils.JSONEncoder import JSON
|
||||
|
||||
#region Type hinting for Websocket Public Channels
|
||||
|
||||
@dataclass
|
||||
class TradingPairTicker(_Type):
|
||||
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
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTicker(_Type):
|
||||
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
|
||||
|
||||
@dataclass
|
||||
class TradingPairTrade(_Type):
|
||||
id: int
|
||||
mts: int
|
||||
amount: float
|
||||
price: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyTrade(_Type):
|
||||
id: int
|
||||
mts: int
|
||||
amount: float
|
||||
rate: float
|
||||
period: int
|
||||
|
||||
@dataclass
|
||||
class TradingPairBook(_Type):
|
||||
price: float
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyBook(_Type):
|
||||
rate: float
|
||||
period: int
|
||||
count: int
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class TradingPairRawBook(_Type):
|
||||
order_id: int
|
||||
price: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class FundingCurrencyRawBook(_Type):
|
||||
offer_id: int
|
||||
period: int
|
||||
rate: float
|
||||
amount: float
|
||||
|
||||
@dataclass
|
||||
class Candle(_Type):
|
||||
mts: int
|
||||
open: float
|
||||
close: float
|
||||
high: float
|
||||
low: float
|
||||
volume: float
|
||||
|
||||
@dataclass
|
||||
class DerivativesStatus(_Type):
|
||||
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
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Websocket Authenticated Channels
|
||||
@dataclass
|
||||
class Order(_Type):
|
||||
id: int
|
||||
gid: int
|
||||
cid: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
amount_orig: float
|
||||
order_type: str
|
||||
type_prev: str
|
||||
mts_tif: int
|
||||
flags: int
|
||||
order_status: str
|
||||
price: float
|
||||
price_avg: float
|
||||
price_trailing: float
|
||||
price_aux_limit: float
|
||||
notify: int
|
||||
hidden: int
|
||||
placed_id: int
|
||||
routing: str
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class Position(_Type):
|
||||
symbol: str
|
||||
status: str
|
||||
amount: float
|
||||
base_price: float
|
||||
margin_funding: float
|
||||
margin_funding_type: int
|
||||
pl: float
|
||||
pl_perc: float
|
||||
price_liq: float
|
||||
leverage: float
|
||||
flag: int
|
||||
position_id: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
type: int
|
||||
collateral: float
|
||||
collateral_min: float
|
||||
meta: JSON
|
||||
|
||||
@dataclass
|
||||
class Trade(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
order_id: int
|
||||
exec_amount: float
|
||||
exec_price: float
|
||||
order_type: str
|
||||
order_price: float
|
||||
maker:int
|
||||
fee: Optional[float]
|
||||
fee_currency: Optional[str]
|
||||
cid: int
|
||||
|
||||
@dataclass
|
||||
class FundingOffer(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
amount_orig: float
|
||||
offer_type: str
|
||||
flags: int
|
||||
offer_status: str
|
||||
rate: float
|
||||
period: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
|
||||
@dataclass
|
||||
class FundingCredit(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
side: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
flags: int
|
||||
status: str
|
||||
rate: float
|
||||
period: int
|
||||
mts_opening: int
|
||||
mts_last_payout: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
no_close: int
|
||||
position_pair: str
|
||||
|
||||
@dataclass
|
||||
class FundingLoan(_Type):
|
||||
id: int
|
||||
symbol: str
|
||||
side: int
|
||||
mts_create: int
|
||||
mts_update: int
|
||||
amount: float
|
||||
flags: int
|
||||
status: str
|
||||
rate: float
|
||||
period: int
|
||||
mts_opening: int
|
||||
mts_last_payout: int
|
||||
notify: int
|
||||
hidden: int
|
||||
renew: int
|
||||
no_close: int
|
||||
|
||||
@dataclass
|
||||
class Wallet(_Type):
|
||||
wallet_type: str
|
||||
currency: str
|
||||
balance: float
|
||||
unsettled_interest: float
|
||||
available_balance: float
|
||||
last_change: str
|
||||
trade_details: JSON
|
||||
|
||||
@dataclass
|
||||
class Balance(_Type):
|
||||
aum: float
|
||||
aum_net: float
|
||||
|
||||
#endregion
|
||||
@@ -1,277 +0,0 @@
|
||||
from typing import Type, Tuple, List, Dict, TypedDict, Union, Optional, Any
|
||||
|
||||
from .. notification import Notification
|
||||
|
||||
JSON = Union[Dict[str, "JSON"], List["JSON"], bool, int, float, str, Type[None]]
|
||||
|
||||
#region Type hinting for subscription objects
|
||||
|
||||
class Subscriptions:
|
||||
class TradingPairTicker(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
pair: str
|
||||
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
currency: str
|
||||
|
||||
class TradingPairTrades(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
pair: str
|
||||
|
||||
class FundingCurrencyTrades(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
currency: str
|
||||
|
||||
class Book(TypedDict):
|
||||
chanId: int
|
||||
symbol: str
|
||||
prec: str
|
||||
freq: str
|
||||
len: str
|
||||
subId: int
|
||||
pair: str
|
||||
|
||||
class Candles(TypedDict):
|
||||
chanId: int
|
||||
key: str
|
||||
|
||||
class DerivativesStatus(TypedDict):
|
||||
chanId: int
|
||||
key: str
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Websocket Public Channels
|
||||
|
||||
class TradingPairTicker(TypedDict):
|
||||
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
|
||||
|
||||
class FundingCurrencyTicker(TypedDict):
|
||||
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
|
||||
|
||||
class TradingPairTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
PRICE: float
|
||||
|
||||
class FundingCurrencyTrade(TypedDict):
|
||||
ID: int
|
||||
MTS: int
|
||||
AMOUNT: float
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
|
||||
class TradingPairBook(TypedDict):
|
||||
PRICE: float
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyBook(TypedDict):
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
COUNT: int
|
||||
AMOUNT: float
|
||||
|
||||
class TradingPairRawBook(TypedDict):
|
||||
ORDER_ID: int
|
||||
PRICE: float
|
||||
AMOUNT: float
|
||||
|
||||
class FundingCurrencyRawBook(TypedDict):
|
||||
OFFER_ID: int
|
||||
PERIOD: int
|
||||
RATE: float
|
||||
AMOUNT: float
|
||||
|
||||
class Candle(TypedDict):
|
||||
MTS: int
|
||||
OPEN: float
|
||||
CLOSE: float
|
||||
HIGH: float
|
||||
LOW: float
|
||||
VOLUME: float
|
||||
|
||||
class DerivativesStatus(TypedDict):
|
||||
TIME_MS: 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
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type hinting for Websocket Authenticated Channels
|
||||
|
||||
class Order(TypedDict):
|
||||
ID: int
|
||||
GID: int
|
||||
CID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
ORDER_TYPE: str
|
||||
TYPE_PREV: str
|
||||
MTS_TIF: int
|
||||
FLAGS: int
|
||||
ORDER_STATUS: str
|
||||
PRICE: float
|
||||
PRICE_AVG: float
|
||||
PRICE_TRAILING: float
|
||||
PRICE_AUX_LIMIT: float
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
PLACED_ID: int
|
||||
ROUTING: str
|
||||
META: JSON
|
||||
|
||||
class Position(TypedDict):
|
||||
SYMBOL: str
|
||||
STATUS: str
|
||||
AMOUNT: float
|
||||
BASE_PRICE: float
|
||||
MARGIN_FUNDING: float
|
||||
MARGIN_FUNDING_TYPE: int
|
||||
PL: float
|
||||
PL_PERC: float
|
||||
PRICE_LIQ: float
|
||||
LEVERAGE: float
|
||||
POSITION_ID: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
TYPE: int
|
||||
COLLATERAL: float
|
||||
COLLATERAL_MIN: float
|
||||
META: JSON
|
||||
|
||||
class TradeExecuted(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
CID: int
|
||||
|
||||
class TradeExecutionUpdate(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATE: int
|
||||
ORDER_ID: int
|
||||
EXEC_AMOUNT: float
|
||||
EXEC_PRICE: float
|
||||
ORDER_TYPE: str
|
||||
ORDER_PRICE: float
|
||||
MAKER:int
|
||||
FEE: float
|
||||
FEE_CURRENCY: str
|
||||
CID: int
|
||||
|
||||
class FundingOffer(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
MTS_CREATED: int
|
||||
MTS_UPDATED: int
|
||||
AMOUNT: float
|
||||
AMOUNT_ORIG: float
|
||||
OFFER_TYPE: str
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
|
||||
class FundingCredit(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
SIDE: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
MTS_OPENING: int
|
||||
MTS_LAST_PAYOUT: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
RATE_REAL: float
|
||||
NO_CLOSE: int
|
||||
POSITION_PAIR: str
|
||||
|
||||
class FundingLoan(TypedDict):
|
||||
ID: int
|
||||
SYMBOL: str
|
||||
SIDE: int
|
||||
MTS_CREATE: int
|
||||
MTS_UPDATE: int
|
||||
AMOUNT: float
|
||||
FLAGS: int
|
||||
STATUS: str
|
||||
RATE: float
|
||||
PERIOD: int
|
||||
MTS_OPENING: int
|
||||
MTS_LAST_PAYOUT: int
|
||||
NOTIFY: int
|
||||
HIDDEN: int
|
||||
RENEW: int
|
||||
RATE_REAL: float
|
||||
NO_CLOSE: int
|
||||
|
||||
class Wallet(TypedDict):
|
||||
WALLET_TYPE: str
|
||||
CURRENCY: str
|
||||
BALANCE: float
|
||||
UNSETTLED_INTEREST: float
|
||||
BALANCE_AVAILABLE: float
|
||||
DESCRIPTION: str
|
||||
META: JSON
|
||||
|
||||
class BalanceInfo(TypedDict):
|
||||
AUM: float
|
||||
AUM_NET: float
|
||||
|
||||
#endregion
|
||||
19
examples/rest/claim_position.py
Normal file
19
examples/rest/claim_position.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# python -c "import examples.rest.claim_position"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
open_margin_positions = bfx.rest.auth.get_positions()
|
||||
|
||||
# claim all positions
|
||||
for position in open_margin_positions:
|
||||
print(f"Position {position}")
|
||||
claim = bfx.rest.auth.claim_position(position.position_id, amount=0.000001)
|
||||
print(f"PositionClaim {claim.notify_info}")
|
||||
@@ -1,11 +1,12 @@
|
||||
# python -c "import examples.rest.create_funding_offer"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.enums import FundingOfferType
|
||||
from bfxapi.utils.flags import calculate_offer_flags
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
from bfxapi.enums import FundingOfferType, Flag
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=Constants.REST_HOST,
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
@@ -16,11 +17,16 @@ notification = bfx.rest.auth.submit_funding_offer(
|
||||
amount="123.45",
|
||||
rate="0.001",
|
||||
period=2,
|
||||
flags=calculate_offer_flags(hidden=True)
|
||||
flags=Flag.HIDDEN
|
||||
)
|
||||
|
||||
print("Offer notification:", notification)
|
||||
|
||||
offers = bfx.rest.auth.get_active_funding_offers(symbol="fUSD")
|
||||
offers = bfx.rest.auth.get_funding_offers(symbol="fUSD")
|
||||
|
||||
print("Offers:", offers)
|
||||
print("Offers:", offers)
|
||||
|
||||
# Cancel all funding offers
|
||||
notification = bfx.rest.auth.cancel_all_funding_offers(currency="fUSD")
|
||||
|
||||
print(notification)
|
||||
@@ -1,11 +1,11 @@
|
||||
import os
|
||||
# python -c "import examples.rest.create_order"
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.enums import OrderType
|
||||
from bfxapi.utils.flags import calculate_order_flags
|
||||
import os
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
from bfxapi.enums import OrderType, Flag
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=Constants.REST_HOST,
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
@@ -16,14 +16,14 @@ submitted_order = bfx.rest.auth.submit_order(
|
||||
symbol="tBTCUST",
|
||||
amount="0.015",
|
||||
price="10000",
|
||||
flags=calculate_order_flags(hidden=False)
|
||||
flags=Flag.HIDDEN + Flag.OCO + Flag.CLOSE
|
||||
)
|
||||
|
||||
print("Submit Order Notification:", submitted_order)
|
||||
|
||||
# Update it
|
||||
updated_order = bfx.rest.auth.update_order(
|
||||
id=submitted_order["NOTIFY_INFO"]["ID"],
|
||||
id=submitted_order.notify_info.id,
|
||||
amount="0.020",
|
||||
price="10100"
|
||||
)
|
||||
@@ -31,6 +31,6 @@ updated_order = bfx.rest.auth.update_order(
|
||||
print("Update Order Notification:", updated_order)
|
||||
|
||||
# Delete it
|
||||
canceled_order = bfx.rest.auth.cancel_order(id=submitted_order["NOTIFY_INFO"]["ID"])
|
||||
canceled_order = bfx.rest.auth.cancel_order(id=submitted_order.notify_info.id)
|
||||
|
||||
print("Cancel Order Notification:", canceled_order)
|
||||
|
||||
31
examples/rest/derivatives.py
Normal file
31
examples/rest/derivatives.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# python -c "import examples.rest.derivatives"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
# Create a new order
|
||||
submitted_order = bfx.rest.auth.submit_order(
|
||||
symbol="tBTCF0:USTF0",
|
||||
amount="0.015",
|
||||
price="16700",
|
||||
lev=10,
|
||||
type="LIMIT"
|
||||
)
|
||||
|
||||
print("Submit Order Notification:", submitted_order)
|
||||
|
||||
# Get position collateral limits
|
||||
limits = bfx.rest.auth.get_derivative_position_collateral_limits(symbol="tBTCF0:USTF0")
|
||||
print(f"Limits {limits}")
|
||||
|
||||
# Update position collateral
|
||||
response = bfx.rest.auth.set_derivative_position_collateral(symbol="tBTCF0:USTF0", collateral=50)
|
||||
print(response.status)
|
||||
|
||||
28
examples/rest/extra_calcs.py
Normal file
28
examples/rest/extra_calcs.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# python -c "import examples.rest.extra_calcs"
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST
|
||||
)
|
||||
|
||||
t_symbol_response = bfx.rest.public.get_trading_market_average_price(
|
||||
symbol="tBTCUSD",
|
||||
amount=-100,
|
||||
price_limit="20000.5"
|
||||
)
|
||||
|
||||
print(t_symbol_response.price_avg)
|
||||
|
||||
f_symbol_response = bfx.rest.public.get_funding_market_average_price(
|
||||
symbol="fUSD",
|
||||
amount=100,
|
||||
period=2,
|
||||
rate_limit="0.00015"
|
||||
)
|
||||
|
||||
print(f_symbol_response.rate_avg)
|
||||
|
||||
fx_rate = bfx.rest.public.get_fx_rate(ccy1="USD", ccy2="EUR")
|
||||
|
||||
print(fx_rate.current_rate)
|
||||
21
examples/rest/funding_auto_renew.py
Normal file
21
examples/rest/funding_auto_renew.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# python -c "import examples.rest.funding_auto_renew"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
notification = bfx.rest.auth.toggle_auto_renew(
|
||||
status=True,
|
||||
currency="USD",
|
||||
amount="150",
|
||||
rate="0", # FRR
|
||||
period=2
|
||||
)
|
||||
|
||||
print("Renew toggle notification:", notification)
|
||||
@@ -1,18 +1,29 @@
|
||||
# python -c "from examples.rest.get_authenticated_data import *"
|
||||
# python -c "import examples.rest.get_authenticated_data"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=Constants.REST_HOST,
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
|
||||
def log_user_info():
|
||||
user_info = bfx.rest.auth.get_user_info()
|
||||
print(user_info)
|
||||
|
||||
|
||||
def log_login_history():
|
||||
login_history = bfx.rest.auth.get_login_history()
|
||||
print(login_history)
|
||||
|
||||
|
||||
def log_wallets():
|
||||
wallets = bfx.rest.auth.get_wallets()
|
||||
print("Wallets:")
|
||||
@@ -38,14 +49,14 @@ def log_positions():
|
||||
|
||||
|
||||
def log_trades():
|
||||
trades = bfx.rest.auth.get_trades(symbol='tBTCUSD', start=0, end=now)
|
||||
trades = bfx.rest.auth.get_trades_history(symbol='tBTCUSD', start=0, end=now)
|
||||
print("Trades:")
|
||||
[print(t) for t in trades]
|
||||
|
||||
|
||||
def log_order_trades():
|
||||
order_id = 82406909127
|
||||
trades = bfx.rest.auth.get_order_trades(symbol='tBTCUSD', order_id=order_id)
|
||||
trades = bfx.rest.auth.get_order_trades(symbol='tBTCUSD', id=order_id)
|
||||
print("Trade orders:")
|
||||
[print(t) for t in trades]
|
||||
|
||||
@@ -69,7 +80,7 @@ def log_funding_loans():
|
||||
|
||||
|
||||
def log_funding_loans_history():
|
||||
loans = bfx.rest.auth.get_funding_loan_history(symbol='fUSD', start=0, end=now)
|
||||
loans = bfx.rest.auth.get_funding_loans_history(symbol='fUSD', start=0, end=now)
|
||||
print("Funding loan history:")
|
||||
[print(l) for l in loans]
|
||||
|
||||
@@ -85,8 +96,18 @@ def log_funding_credits_history():
|
||||
print("Funding credit history:")
|
||||
[print(c) for c in credit]
|
||||
|
||||
def log_margin_info():
|
||||
btcusd_margin_info = bfx.rest.auth.get_symbol_margin_info('tBTCUSD')
|
||||
print(f"tBTCUSD margin info {btcusd_margin_info}")
|
||||
|
||||
sym_all_margin_info = bfx.rest.auth.get_all_symbols_margin_info()
|
||||
print(f"Sym all margin info {sym_all_margin_info}")
|
||||
|
||||
base_margin_info = bfx.rest.auth.get_base_margin_info()
|
||||
print(f"Base margin info {base_margin_info}")
|
||||
|
||||
def run():
|
||||
log_user_info()
|
||||
log_wallets()
|
||||
log_orders()
|
||||
log_orders_history()
|
||||
@@ -97,5 +118,6 @@ def run():
|
||||
log_funding_offer_history()
|
||||
log_funding_credits()
|
||||
log_funding_credits_history()
|
||||
log_margin_info()
|
||||
|
||||
run()
|
||||
13
examples/rest/get_candles_hist.py
Normal file
13
examples/rest/get_candles_hist.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# python -c "import examples.rest.get_candles_hist"
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST
|
||||
)
|
||||
|
||||
print(f"Candles: {bfx.rest.public.get_candles_hist(symbol='tBTCUSD')}")
|
||||
|
||||
# Be sure to specify a period or aggregated period when retrieving funding candles.
|
||||
# If you wish to mimic the candles found in the UI, use the following setup to aggregate all funding candles: a30:p2:p30
|
||||
print(f"Candles: {bfx.rest.public.get_candles_hist(tf='15m', symbol='fUSD:a30:p2:p30')}")
|
||||
13
examples/rest/get_funding_info.py
Normal file
13
examples/rest/get_funding_info.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# python -c "import examples.rest.get_funding_info"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
print(bfx.rest.auth.get_funding_info(key="fUSD"))
|
||||
13
examples/rest/get_funding_trades_history.py
Normal file
13
examples/rest/get_funding_trades_history.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# python -c "import examples.rest.get_funding_trades_history"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
print(bfx.rest.auth.get_funding_trades_history())
|
||||
14
examples/rest/get_liquidations.py
Normal file
14
examples/rest/get_liquidations.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# python -c "import examples.rest.get_liquidations"
|
||||
|
||||
import time
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
liquidations = bfx.rest.public.get_liquidations(start=0, end=now)
|
||||
print(f"Liquidations: {liquidations}")
|
||||
23
examples/rest/get_positions.py
Normal file
23
examples/rest/get_positions.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# python -c "import examples.rest.get_positions"
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
positions_snapshot = bfx.rest.auth.get_positions_snapshot(end=now, limit=50)
|
||||
print(positions_snapshot)
|
||||
|
||||
positions_history = bfx.rest.auth.get_positions_history(end=now, limit=50)
|
||||
print(positions_history)
|
||||
|
||||
positions_audit = bfx.rest.auth.get_positions_audit(end=now, limit=50)
|
||||
print(positions_audit)
|
||||
52
examples/rest/get_public_data.py
Normal file
52
examples/rest/get_public_data.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# python -c "import examples.rest.get_public_data"
|
||||
|
||||
import time
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
|
||||
def log_historical_candles():
|
||||
candles = bfx.rest.public.get_candles_hist(start=0, end=now, resource='trade:1m:tBTCUSD')
|
||||
print("Candles:")
|
||||
[print(c) for c in candles]
|
||||
|
||||
|
||||
def log_historical_trades():
|
||||
trades = bfx.rest.public.get_t_trades(pair='tBTCUSD', start=0, end=now)
|
||||
print("Trades:")
|
||||
[print(t) for t in trades]
|
||||
|
||||
|
||||
def log_books():
|
||||
orders = bfx.rest.public.get_t_book(pair='BTCUSD', precision='P0')
|
||||
print("Order book:")
|
||||
[print(o) for o in orders]
|
||||
|
||||
|
||||
def log_tickers():
|
||||
tickers = bfx.rest.public.get_t_tickers(pairs=['BTCUSD'])
|
||||
print("Tickers:")
|
||||
print(tickers)
|
||||
|
||||
|
||||
def log_derivative_status():
|
||||
status = bfx.rest.public.get_derivatives_status('ALL')
|
||||
print("Deriv status:")
|
||||
print(status)
|
||||
|
||||
|
||||
def run():
|
||||
log_historical_candles()
|
||||
log_historical_trades()
|
||||
log_books()
|
||||
log_tickers()
|
||||
log_derivative_status()
|
||||
|
||||
|
||||
run()
|
||||
22
examples/rest/get_pulse_data.py
Normal file
22
examples/rest/get_pulse_data.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# python -c "import examples.rest.get_pulse_data"
|
||||
|
||||
import time
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
messages = bfx.rest.public.get_pulse_history(end=now, limit=100)
|
||||
|
||||
for message in messages:
|
||||
print(f"Message: {message}")
|
||||
print(message.content)
|
||||
print(message.profile.picture)
|
||||
|
||||
profile = bfx.rest.public.get_pulse_profile("News")
|
||||
print(f"Profile: {profile}")
|
||||
print(f"Profile picture: {profile.picture}")
|
||||
18
examples/rest/increase_position.py
Normal file
18
examples/rest/increase_position.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# python -c "import examples.rest.increase_position"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
increase_info = bfx.rest.auth.get_increase_position_info(symbol="tBTCUSD", amount=0.0001)
|
||||
print(increase_info)
|
||||
|
||||
# increase a margin position
|
||||
notification = bfx.rest.auth.increase_position(symbol="tBTCUSD", amount=0.0001)
|
||||
print(notification.notify_info)
|
||||
26
examples/rest/keep_taken_funding.py
Normal file
26
examples/rest/keep_taken_funding.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# python -c "import examples.rest.keep_taken_funding"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
loans = bfx.rest.auth.get_funding_loans(symbol="fUSD")
|
||||
|
||||
for loan in loans:
|
||||
print(f"Loan {loan}")
|
||||
|
||||
notification = bfx.rest.auth.toggle_keep(
|
||||
funding_type="loan",
|
||||
ids=[loan.id],
|
||||
changes={
|
||||
loan.id: 2 # (1 if true, 2 if false)
|
||||
}
|
||||
)
|
||||
|
||||
print("Funding keep notification:", notification)
|
||||
44
examples/rest/merchant.py
Normal file
44
examples/rest/merchant.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# python -c "import examples.rest.merchant"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
customer_info = {
|
||||
"nationality": "GB",
|
||||
"resid_country": "DE",
|
||||
"resid_city": "Berlin",
|
||||
"resid_zip_code": 1,
|
||||
"resid_street": "Timechain",
|
||||
"full_name": "Satoshi",
|
||||
"email": "satoshi3@bitfinex.com"
|
||||
}
|
||||
|
||||
invoice = bfx.rest.merchant.submit_invoice(
|
||||
amount=1,
|
||||
currency="USD",
|
||||
duration=864000,
|
||||
order_id="order123",
|
||||
customer_info=customer_info,
|
||||
pay_currencies=["ETH"]
|
||||
)
|
||||
|
||||
print(bfx.rest.merchant.get_invoices())
|
||||
|
||||
print(bfx.rest.merchant.get_invoice_count_stats(status="CREATED", format="Y"))
|
||||
|
||||
print(bfx.rest.merchant.get_invoice_earning_stats(currency="USD", format="Y"))
|
||||
|
||||
print(bfx.rest.merchant.get_currency_conversion_list())
|
||||
|
||||
print(bfx.rest.merchant.complete_invoice(
|
||||
id=invoice.id,
|
||||
pay_currency="ETH",
|
||||
deposit_id=1
|
||||
))
|
||||
22
examples/rest/return_taken_funding.py
Normal file
22
examples/rest/return_taken_funding.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# python -c "import examples.rest.return_taken_funding"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
loans = bfx.rest.auth.get_funding_loans(symbol="fUSD")
|
||||
|
||||
for loan in loans:
|
||||
print(f"Loan {loan}")
|
||||
|
||||
notification = bfx.rest.auth.submit_funding_close(
|
||||
id=loan.id
|
||||
)
|
||||
|
||||
print("Funding close notification:", notification)
|
||||
46
examples/rest/transfer_wallet.py
Normal file
46
examples/rest/transfer_wallet.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# python -c "import examples.rest.transfer_wallet"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
REST_HOST=REST_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
def transfer_wallet():
|
||||
response = bfx.rest.auth.transfer_between_wallets(from_wallet="exchange", to_wallet="funding", from_currency="ETH", to_currency="ETH", amount=0.001)
|
||||
print("Transfer:", response.notify_info)
|
||||
|
||||
def get_existing_deposit_address():
|
||||
response = bfx.rest.auth.get_deposit_address(wallet="exchange", method="bitcoin", renew=False)
|
||||
print("Address:", response.notify_info)
|
||||
|
||||
def create_new_deposit_address():
|
||||
response = bfx.rest.auth.get_deposit_address(wallet="exchange", method="bitcoin", renew=True)
|
||||
print("Address:", response.notify_info)
|
||||
|
||||
def withdraw():
|
||||
# tetheruse = Tether (ERC20)
|
||||
response = bfx.rest.auth.submit_wallet_withdrawal(wallet="exchange", method="tetheruse", amount=1, address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e")
|
||||
print("Address:", response.notify_info)
|
||||
|
||||
def create_lighting_network_deposit_address():
|
||||
invoice = bfx.rest.auth.generate_deposit_invoice(wallet="funding", currency="LNX", amount=0.001)
|
||||
print("Invoice:", invoice)
|
||||
|
||||
def get_movements():
|
||||
movements = bfx.rest.auth.get_movements(currency="BTC")
|
||||
print("Movements:", movements)
|
||||
|
||||
def run():
|
||||
transfer_wallet()
|
||||
get_existing_deposit_address()
|
||||
create_new_deposit_address()
|
||||
withdraw()
|
||||
create_lighting_network_deposit_address()
|
||||
get_movements()
|
||||
|
||||
run()
|
||||
@@ -1,13 +1,13 @@
|
||||
# python -c "from examples.websocket.create_order import *"
|
||||
# python -c "import examples.websocket.create_order"
|
||||
|
||||
import os
|
||||
|
||||
from bfxapi.client import Client, Constants
|
||||
from bfxapi.client import Client, WSS_HOST
|
||||
from bfxapi.websocket.enums import Error, OrderType
|
||||
from bfxapi.websocket.typings import Notification, Order
|
||||
from bfxapi.websocket.types import Notification, Order
|
||||
|
||||
bfx = Client(
|
||||
WSS_HOST=Constants.WSS_HOST,
|
||||
WSS_HOST=WSS_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
@@ -30,7 +30,7 @@ async def on_authenticated(event):
|
||||
print("The order has been sent.")
|
||||
|
||||
@bfx.wss.on("on-req-notification")
|
||||
async def on_notification(notification: Notification):
|
||||
async def on_notification(notification: Notification[Order]):
|
||||
print(f"Notification: {notification}.")
|
||||
|
||||
@bfx.wss.on("order_new")
|
||||
|
||||
23
examples/websocket/derivatives_status.py
Normal file
23
examples/websocket/derivatives_status.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# python -c "import examples.websocket.derivatives_status"
|
||||
|
||||
from bfxapi import Client, PUB_WSS_HOST
|
||||
from bfxapi.websocket.enums import Error, Channel
|
||||
from bfxapi.websocket.types import DerivativesStatus
|
||||
|
||||
from bfxapi.websocket import subscriptions
|
||||
|
||||
bfx = Client(WSS_HOST=PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("derivatives_status_update")
|
||||
def on_derivatives_status_update(subscription: subscriptions.Status, data: DerivativesStatus):
|
||||
print(f"{subscription}:", data)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
@bfx.wss.once("open")
|
||||
async def open():
|
||||
await bfx.wss.subscribe(Channel.STATUS, key="deriv:tBTCF0:USTF0")
|
||||
|
||||
bfx.wss.run()
|
||||
@@ -1,13 +1,14 @@
|
||||
# python -c "from examples.websocket.order_book import *"
|
||||
# python -c "import examples.websocket.order_book"
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from typing import List
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
from bfxapi import Client, PUB_WSS_HOST
|
||||
|
||||
from bfxapi.websocket.enums import Channels, Error
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairBook
|
||||
from bfxapi.websocket import subscriptions
|
||||
from bfxapi.websocket.enums import Channel, Error
|
||||
from bfxapi.websocket.types import TradingPairBook
|
||||
|
||||
class OrderBook(object):
|
||||
def __init__(self, symbols: List[str]):
|
||||
@@ -18,7 +19,7 @@ class OrderBook(object):
|
||||
}
|
||||
|
||||
def update(self, symbol: str, data: TradingPairBook) -> None:
|
||||
price, count, amount = data["PRICE"], data["COUNT"], data["AMOUNT"]
|
||||
price, count, amount = data.price, data.count, data.amount
|
||||
|
||||
kind = (amount > 0) and "bids" or "asks"
|
||||
|
||||
@@ -37,7 +38,7 @@ SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
|
||||
|
||||
order_book = OrderBook(symbols=SYMBOLS)
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
bfx = Client(WSS_HOST=PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
@@ -46,19 +47,19 @@ def on_wss_error(code: Error, msg: str):
|
||||
@bfx.wss.on("open")
|
||||
async def on_open():
|
||||
for symbol in SYMBOLS:
|
||||
await bfx.wss.subscribe(Channels.BOOK, symbol=symbol)
|
||||
await bfx.wss.subscribe(Channel.BOOK, symbol=symbol)
|
||||
|
||||
@bfx.wss.on("subscribed")
|
||||
def on_subscribed(subscription):
|
||||
print(f"Subscription successful for pair <{subscription['pair']}>")
|
||||
|
||||
@bfx.wss.on("t_book_snapshot")
|
||||
def on_t_book_snapshot(subscription: Subscriptions.Book, snapshot: List[TradingPairBook]):
|
||||
def on_t_book_snapshot(subscription: subscriptions.Book, snapshot: List[TradingPairBook]):
|
||||
for data in snapshot:
|
||||
order_book.update(subscription["symbol"], data)
|
||||
|
||||
@bfx.wss.on("t_book_update")
|
||||
def on_t_book_update(subscription: Subscriptions.Book, data: TradingPairBook):
|
||||
def on_t_book_update(subscription: subscriptions.Book, data: TradingPairBook):
|
||||
order_book.update(subscription["symbol"], data)
|
||||
|
||||
bfx.wss.run()
|
||||
@@ -1,13 +1,14 @@
|
||||
# python -c "from examples.websocket.raw_order_book import *"
|
||||
# python -c "import examples.websocket.raw_order_book"
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from typing import List
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
from bfxapi import Client, PUB_WSS_HOST
|
||||
|
||||
from bfxapi.websocket.enums import Channels, Error
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairRawBook
|
||||
from bfxapi.websocket import subscriptions
|
||||
from bfxapi.websocket.enums import Channel, Error
|
||||
from bfxapi.websocket.types import TradingPairRawBook
|
||||
|
||||
class RawOrderBook(object):
|
||||
def __init__(self, symbols: List[str]):
|
||||
@@ -18,7 +19,7 @@ class RawOrderBook(object):
|
||||
}
|
||||
|
||||
def update(self, symbol: str, data: TradingPairRawBook) -> None:
|
||||
order_id, price, amount = data["ORDER_ID"], data["PRICE"], data["AMOUNT"]
|
||||
order_id, price, amount = data.order_id, data.price, data.amount
|
||||
|
||||
kind = (amount > 0) and "bids" or "asks"
|
||||
|
||||
@@ -37,7 +38,7 @@ SYMBOLS = [ "tBTCUSD", "tLTCUSD", "tLTCBTC", "tETHUSD", "tETHBTC" ]
|
||||
|
||||
raw_order_book = RawOrderBook(symbols=SYMBOLS)
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
bfx = Client(WSS_HOST=PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
@@ -46,19 +47,19 @@ def on_wss_error(code: Error, msg: str):
|
||||
@bfx.wss.on("open")
|
||||
async def on_open():
|
||||
for symbol in SYMBOLS:
|
||||
await bfx.wss.subscribe(Channels.BOOK, symbol=symbol, prec="R0")
|
||||
await bfx.wss.subscribe(Channel.BOOK, symbol=symbol, prec="R0")
|
||||
|
||||
@bfx.wss.on("subscribed")
|
||||
def on_subscribed(subscription):
|
||||
print(f"Subscription successful for pair <{subscription['pair']}>")
|
||||
|
||||
@bfx.wss.on("t_raw_book_snapshot")
|
||||
def on_t_raw_book_snapshot(subscription: Subscriptions.Book, snapshot: List[TradingPairRawBook]):
|
||||
def on_t_raw_book_snapshot(subscription: subscriptions.Book, snapshot: List[TradingPairRawBook]):
|
||||
for data in snapshot:
|
||||
raw_order_book.update(subscription["symbol"], data)
|
||||
|
||||
@bfx.wss.on("t_raw_book_update")
|
||||
def on_t_raw_book_update(subscription: Subscriptions.Book, data: TradingPairRawBook):
|
||||
def on_t_raw_book_update(subscription: subscriptions.Book, data: TradingPairRawBook):
|
||||
raw_order_book.update(subscription["symbol"], data)
|
||||
|
||||
bfx.wss.run()
|
||||
@@ -1,21 +1,21 @@
|
||||
# python -c "from examples.websocket.ticker import *"
|
||||
# python -c "import examples.websocket.ticker"
|
||||
|
||||
import asyncio
|
||||
from bfxapi import Client, PUB_WSS_HOST
|
||||
|
||||
from bfxapi import Client, Constants
|
||||
from bfxapi.websocket.enums import Channels
|
||||
from bfxapi.websocket.typings import Subscriptions, TradingPairTicker
|
||||
from bfxapi.websocket import subscriptions
|
||||
from bfxapi.websocket.enums import Channel
|
||||
from bfxapi.websocket.types import TradingPairTicker
|
||||
|
||||
bfx = Client(WSS_HOST=Constants.PUB_WSS_HOST)
|
||||
bfx = Client(WSS_HOST=PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("t_ticker_update")
|
||||
def on_t_ticker_update(subscription: Subscriptions.TradingPairTicker, data: TradingPairTicker):
|
||||
print(f"Subscription with channel ID: {subscription['chanId']}")
|
||||
def on_t_ticker_update(subscription: subscriptions.Ticker, data: TradingPairTicker):
|
||||
print(f"Subscription with subId: {subscription['subId']}")
|
||||
|
||||
print(f"Data: {data}")
|
||||
|
||||
@bfx.wss.once("open")
|
||||
async def open():
|
||||
await bfx.wss.subscribe(Channels.TICKER, symbol="tBTCUSD")
|
||||
await bfx.wss.subscribe(Channel.TICKER, symbol="tBTCUSD")
|
||||
|
||||
bfx.wss.run()
|
||||
29
examples/websocket/trades.py
Normal file
29
examples/websocket/trades.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# python -c "import examples.websocket.trades"
|
||||
|
||||
from bfxapi import Client, PUB_WSS_HOST
|
||||
from bfxapi.websocket.enums import Error, Channel
|
||||
from bfxapi.websocket.types import Candle, TradingPairTrade
|
||||
|
||||
from bfxapi.websocket import subscriptions
|
||||
|
||||
bfx = Client(WSS_HOST=PUB_WSS_HOST)
|
||||
|
||||
@bfx.wss.on("candles_update")
|
||||
def on_candles_update(subscription: subscriptions.Candles, candle: Candle):
|
||||
print(f"New candle: {candle}")
|
||||
|
||||
@bfx.wss.on("t_trade_executed")
|
||||
def on_t_trade_executed(subscription: subscriptions.Trades, trade: TradingPairTrade):
|
||||
print(f"New trade: {trade}")
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
@bfx.wss.once("open")
|
||||
async def open():
|
||||
await bfx.wss.subscribe(Channel.CANDLES, key="trade:1m:tBTCUSD")
|
||||
|
||||
await bfx.wss.subscribe(Channel.TRADES, symbol="tBTCUSD")
|
||||
|
||||
bfx.wss.run()
|
||||
30
examples/websocket/wallet_balance.py
Normal file
30
examples/websocket/wallet_balance.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# python -c "import examples.websocket.wallet_balance"
|
||||
|
||||
import os
|
||||
|
||||
from typing import List
|
||||
|
||||
from bfxapi import Client, WSS_HOST
|
||||
from bfxapi.websocket.enums import Error
|
||||
from bfxapi.websocket.types import Wallet
|
||||
|
||||
bfx = Client(
|
||||
WSS_HOST=WSS_HOST,
|
||||
API_KEY=os.getenv("BFX_API_KEY"),
|
||||
API_SECRET=os.getenv("BFX_API_SECRET")
|
||||
)
|
||||
|
||||
@bfx.wss.on("wallet_snapshot")
|
||||
def log_snapshot(wallets: List[Wallet]):
|
||||
for wallet in wallets:
|
||||
print(f"Balance: {wallet}")
|
||||
|
||||
@bfx.wss.on("wallet_update")
|
||||
def log_update(wallet: Wallet):
|
||||
print(f"Balance update: {wallet}")
|
||||
|
||||
@bfx.wss.on("wss-error")
|
||||
def on_wss_error(code: Error, msg: str):
|
||||
print(code, msg)
|
||||
|
||||
bfx.wss.run()
|
||||
35
setup.py
35
setup.py
@@ -2,14 +2,36 @@ from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name="bitfinex-api-py",
|
||||
version="3.0.0",
|
||||
packages=[ "bfxapi", "bfxapi.websocket", "bfxapi.rest", "bfxapi.utils" ],
|
||||
version="3.0.0b1",
|
||||
description="Official Bitfinex Python API",
|
||||
long_description="A Python reference implementation of the Bitfinex API for both REST and websocket interaction",
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/bitfinexcom/bitfinex-api-py",
|
||||
license="OSI Approved :: Apache Software License",
|
||||
author="Bitfinex",
|
||||
author_email="support@bitfinex.com",
|
||||
description="Official Bitfinex Python API",
|
||||
license="Apache-2.0",
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
|
||||
"License :: OSI Approved :: Apache-2.0",
|
||||
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
],
|
||||
keywords="bitfinex,api,trading",
|
||||
project_urls={
|
||||
"Bug Reports": "https://github.com/bitfinexcom/bitfinex-api-py/issues",
|
||||
"Source": "https://github.com/bitfinexcom/bitfinex-api-py",
|
||||
},
|
||||
packages=[
|
||||
"bfxapi", "bfxapi.utils",
|
||||
"bfxapi.websocket", "bfxapi.websocket.client", "bfxapi.websocket.handlers",
|
||||
"bfxapi.rest", "bfxapi.rest.endpoints", "bfxapi.rest.middleware",
|
||||
],
|
||||
install_requires=[
|
||||
"certifi~=2022.12.7",
|
||||
"charset-normalizer~=2.1.1",
|
||||
@@ -25,8 +47,5 @@ setup(
|
||||
"urllib3~=1.26.13",
|
||||
"websockets~=10.4",
|
||||
],
|
||||
project_urls={
|
||||
"Bug Reports": "https://github.com/bitfinexcom/bitfinex-api-py/issues",
|
||||
"Source": "https://github.com/bitfinexcom/bitfinex-api-py",
|
||||
}
|
||||
python_requires=">=3.8"
|
||||
)
|
||||
Reference in New Issue
Block a user