diff --git a/CHANGELOG.md b/CHANGELOG.md index df4b44f..e171e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html ## [Pending release] +## [4.0.0] - 2021-04-24 + +### Added + +- `binance` margin and USDS-M futures endpoints +- following existing classes, methods and types were renamed: + +``` +binance.enums.CandelstickInterval -> binance.enums.Interval +binance.BinanceWebsocket.BestOrderBookTickerSubscription -> binance.BinanceWebsocket.OrderBookTickerSubscription +binance.BinanceWebsocket.BestOrderBookSymbolTickerSubscription -> binance.BinanceWebsocket.OrderBookSymbolTickerSubscription +binance.BinanceClient.get_candelsticks -> binance.BinanceClient.get_candlesticks +binance.BinanceClient.get_best_orderbook_ticker -> binance.BinanceClient.get_orderbook_ticker +bitforex.enums.CandelstickInterval -> bitforex.enums.CandlestickInterval +bitvavo.enums.CandelstickInterval -> bitvavo.enums.CandlestickInterval +``` + ## [3.10.0] - 2021-04-21 ### Added @@ -113,7 +130,8 @@ The project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html The official release of `cryptoxlib-aio`. -[Pending release]: https://github.com/nardew/cryptoxlib-aio/compare/3.10.0...HEAD +[Pending release]: https://github.com/nardew/cryptoxlib-aio/compare/4.0.0...HEAD +[3.10.0]: https://github.com/nardew/cryptoxlib-aio/compare/3.10.0...4.0.0 [3.10.0]: https://github.com/nardew/cryptoxlib-aio/compare/3.9.0...3.10.0 [3.9.0]: https://github.com/nardew/cryptoxlib-aio/compare/3.8.1...3.9.0 [3.8.1]: https://github.com/nardew/cryptoxlib-aio/compare/3.8.0...3.8.1 diff --git a/README.md b/README.md index f46b1b9..310dd1d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# cryptoxlib-aio 3.10.0 +# cryptoxlib-aio 4.0.0 ![](https://img.shields.io/badge/python-3.6-blue.svg) ![](https://img.shields.io/badge/python-3.7-blue.svg) ![](https://img.shields.io/badge/python-3.8-blue.svg) @@ -8,13 +8,14 @@ --- -**[! ! !]** Binance USDS-M futures and margin endpoints (thank you @davidmcnabnz) now available under `binance_futures` branch (see PR [#44](https://github.com/nardew/cryptoxlib-aio/pull/44) for code changes). +**==>** Binance USDS-M futures and margin endpoints (thank you @davidmcnabnz) now available! --- -### What's new in version 3.10.0 +### What's been recently added -- `bitpanda` supports update of orders via streams (see `BitpandaWebsocket.py:UpdateOrderMessage`) +- `binance` margin and USDS-M futures endpoints! +- `bitpanda` supports update of orders via streams For the full history of changes see [CHANGELOG](https://github.com/nardew/cryptoxlib-aio/blob/master/CHANGELOG.md). @@ -50,7 +51,7 @@ As mentioned earlier, all exchanges listed below include full support for websoc | ![aax](https://raw.githubusercontent.com/nardew/cryptoxlib-aio/master/images/aax.png) | AAX | [API](https://www.aax.com/apidoc/index.html#introduction) | | ![bibox](https://user-images.githubusercontent.com/51840849/77257418-3262b000-6c85-11ea-8fb8-20bdf20b3592.jpg) | Bibox | [API](https://biboxcom.github.io/en/restful_intro.html#t0) | | ![bibox_europe](https://raw.githubusercontent.com/nardew/cryptoxlib-aio/master/images/bibox_europe.png) | BiboxEurope | [API](https://github.com/BiboxEurope/API_Docs_en) | -| ![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg) | Binance |[API](https://binance-docs.github.io/apidocs/spot/en/#change-log) | +| ![binance](https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg) | Binance (spot, margin, USDS-M futures) |[API](https://binance-docs.github.io/apidocs/spot/en/#change-log) | | ![bitforex](https://user-images.githubusercontent.com/1294454/44310033-69e9e600-a3d8-11e8-873d-54d74d1bc4e4.jpg) | Bitforex | [API](https://github.com/githubdev2020/API_Doc_en/wiki) | | ![bitpanda](https://raw.githubusercontent.com/nardew/cryptoxlib-aio/master/images/bitpanda.png) | Bitpanda Pro | [API](https://developers.bitpanda.com/exchange/) | | ![bitvavo](https://raw.githubusercontent.com/nardew/cryptoxlib-aio/master/images/bitvavo.png) | Bitvavo | [API](https://docs.bitvavo.com/#section/Introduction) | @@ -122,7 +123,7 @@ bitforex.compose_subscriptions([ # Another bundle of subscriptions bitforex.compose_subscriptions([ - TickerSubscription(pair = Pair('BTC', 'USDT'), size = "2", interval = enums.CandelstickInterval.I_1MIN, callbacks = [ticker_update]), + TickerSubscription(pair = Pair('BTC', 'USDT'), size = "2", interval = enums.Interval.I_1MIN, callbacks = [ticker_update]), Ticker24hSubscription(pair = Pair('BTC', 'USDT'), callbacks = [ticker24_update]) ]) diff --git a/cryptoxlib/CryptoXLib.py b/cryptoxlib/CryptoXLib.py index 156cf36..9989c00 100644 --- a/cryptoxlib/CryptoXLib.py +++ b/cryptoxlib/CryptoXLib.py @@ -4,6 +4,7 @@ from cryptoxlib.clients.bibox_europe.BiboxEuropeClient import BiboxEuropeClient from cryptoxlib.clients.bitpanda.BitpandaClient import BitpandaClient from cryptoxlib.clients.binance.BinanceClient import BinanceClient, BinanceTestnetClient +from cryptoxlib.clients.binance.BinanceFuturesClient import BinanceUSDSMFuturesClient, BinanceUSDSMFuturesTestnetClient from cryptoxlib.clients.binance import enums as binance_enums from cryptoxlib.clients.bitvavo.BitvavoClient import BitvavoClient from cryptoxlib.clients.btse.BtseClient import BtseClient @@ -42,6 +43,14 @@ def create_binance_client(api_key: str, sec_key: str, def create_binance_testnet_client(api_key: str, sec_key: str) -> BinanceTestnetClient: return BinanceTestnetClient(api_key, sec_key) + @staticmethod + def create_binance_usds_m_futures_client(api_key: str, sec_key: str) -> BinanceUSDSMFuturesClient: + return BinanceUSDSMFuturesClient(api_key, sec_key) + + @staticmethod + def create_binance_usds_m_futures_testnet_client(api_key: str, sec_key: str) -> BinanceUSDSMFuturesTestnetClient: + return BinanceUSDSMFuturesTestnetClient(api_key, sec_key) + @staticmethod def create_bitvavo_client(api_key: str, sec_key: str) -> BitvavoClient: return BitvavoClient(api_key, sec_key) diff --git a/cryptoxlib/CryptoXLibClient.py b/cryptoxlib/CryptoXLibClient.py index aa02d0e..a09f6b4 100644 --- a/cryptoxlib/CryptoXLibClient.py +++ b/cryptoxlib/CryptoXLibClient.py @@ -102,7 +102,7 @@ async def _create_rest_call(self, rest_call_type: RestCallType, resource: str, d else: raise Exception(f"Unsupported REST call type {rest_call_type}.") - LOG.debug(f"> rest type [{rest_call_type.name}], resource [{resource}], params [{params}], headers [{headers}], data [{data}]") + LOG.debug(f"> rest type [{rest_call_type.name}], uri [{resource_uri}], params [{params}], headers [{headers}], data [{data}]") async with rest_call as response: status_code = response.status headers = response.headers diff --git a/cryptoxlib/WebsocketMgr.py b/cryptoxlib/WebsocketMgr.py index 1f209da..082471e 100644 --- a/cryptoxlib/WebsocketMgr.py +++ b/cryptoxlib/WebsocketMgr.py @@ -305,8 +305,8 @@ async def run(self) -> None: for task in done: try: task.result() - except Exception: - LOG.debug("Websocket processing has led to an exception, all pending tasks will be cancelled.") + except Exception as e: + LOG.debug(f"Websocket processing has led to an exception, all pending tasks will be cancelled.") for task in pending: if not task.cancelled(): task.cancel() diff --git a/cryptoxlib/clients/binance/BinanceClient.py b/cryptoxlib/clients/binance/BinanceClient.py index 6095d7b..2208195 100644 --- a/cryptoxlib/clients/binance/BinanceClient.py +++ b/cryptoxlib/clients/binance/BinanceClient.py @@ -1,59 +1,21 @@ import ssl import logging -import hmac -import hashlib -from multidict import CIMultiDictProxy from typing import List, Optional -from cryptoxlib.CryptoXLibClient import CryptoXLibClient, RestCallType +from cryptoxlib.CryptoXLibClient import CryptoXLibClient +from cryptoxlib.clients.binance.BinanceCommonClient import BinanceCommonClient from cryptoxlib.clients.binance import enums -from cryptoxlib.clients.binance.exceptions import BinanceException from cryptoxlib.clients.binance.functions import map_pair from cryptoxlib.Pair import Pair from cryptoxlib.WebsocketMgr import WebsocketMgr, Subscription from cryptoxlib.clients.binance.BinanceWebsocket import BinanceWebsocket, BinanceTestnetWebsocket - LOG = logging.getLogger(__name__) -class BinanceDefaultClient(CryptoXLibClient): - def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, - ssl_context: ssl.SSLContext = None) -> None: - super().__init__(api_trace_log, ssl_context) - - self.api_key = api_key - self.sec_key = sec_key - - def _sign_payload(self, rest_call_type: RestCallType, resource: str, data: dict = None, params: dict = None, headers: dict = None) -> None: - params_string = "" - data_string = "" - - if params is not None: - params_string = '&'.join([f"{key}={val}" for key, val in params.items()]) - - if data is not None: - data_string = '&'.join(["{}={}".format(param[0], param[1]) for param in data]) - - m = hmac.new(self.sec_key.encode('utf-8'), (params_string + data_string).encode('utf-8'), hashlib.sha256) - - params['signature'] = m.hexdigest() - - def _preprocess_rest_response(self, status_code: int, headers: 'CIMultiDictProxy[str]', body: Optional[dict]) -> None: - if str(status_code)[0] != '2': - raise BinanceException(f"BinanceException: status [{status_code}], response [{body}]") - - def _get_header(self): - header = { - 'Accept': 'application/json', - "X-MBX-APIKEY": self.api_key - } - - return header - - -class BinanceClient(BinanceDefaultClient): +class BinanceClient(BinanceCommonClient): API_V3 = "api/v3/" + SAPI_V1 = "sapi/v1/" def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, api_cluster: enums.APICluster = enums.APICluster.CLUSTER_DEFAULT, @@ -80,7 +42,7 @@ async def get_time(self) -> dict: return await self._create_get("time", api_variable_path = BinanceClient.API_V3) async def get_orderbook(self, pair: Pair, limit: enums.DepthLimit = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), }) @@ -90,7 +52,7 @@ async def get_orderbook(self, pair: Pair, limit: enums.DepthLimit = None) -> dic return await self._create_get("depth", params = params, api_variable_path = BinanceClient.API_V3) async def get_trades(self, pair: Pair, limit: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "limit": limit }) @@ -98,7 +60,7 @@ async def get_trades(self, pair: Pair, limit: int = None) -> dict: return await self._create_get("trades", params = params, api_variable_path = BinanceClient.API_V3) async def get_historical_trades(self, pair: Pair, limit: int = None, from_id: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "limit": limit, "fromId": from_id @@ -108,7 +70,7 @@ async def get_historical_trades(self, pair: Pair, limit: int = None, from_id: in async def get_aggregate_trades(self, pair: Pair, limit: int = None, from_id: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "limit": limit, "fromId": from_id, @@ -118,9 +80,9 @@ async def get_aggregate_trades(self, pair: Pair, limit: int = None, from_id: int return await self._create_get("aggTrades", params = params, api_variable_path = BinanceClient.API_V3) - async def get_candelsticks(self, pair: Pair, limit: int = None, interval: enums.CandelstickInterval = None, + async def get_candlesticks(self, pair: Pair, limit: int = None, interval: enums.Interval = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "limit": limit, "startTime": start_tmstmp_ms, @@ -133,7 +95,7 @@ async def get_candelsticks(self, pair: Pair, limit: int = None, interval: enums. return await self._create_get("klines", params = params, api_variable_path = BinanceClient.API_V3) async def get_average_price(self, pair: Pair) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair) }) @@ -153,7 +115,7 @@ async def get_price_ticker(self, pair: Pair = None) -> dict: return await self._create_get("ticker/price", params = params, api_variable_path = BinanceClient.API_V3) - async def get_best_orderbook_ticker(self, pair: Optional[Pair] = None) -> dict: + async def get_orderbook_ticker(self, pair: Optional[Pair] = None) -> dict: params = {} if pair is not None: params['symbol'] = map_pair(pair) @@ -170,7 +132,7 @@ async def create_order(self, pair: Pair, side: enums.OrderSide, type: enums.Orde iceberg_quantity: str = None, new_order_response_type: enums.OrderResponseType = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "side": side.value, "type": type.value, @@ -202,7 +164,7 @@ async def create_test_order(self, pair: Pair, side: enums.OrderSide, type: enums iceberg_quantity: str = None, new_order_response_type: enums.OrderResponseType = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "side": side.value, "type": type.value, @@ -227,7 +189,7 @@ async def create_test_order(self, pair: Pair, side: enums.OrderSide, type: enums async def get_order(self, pair: Pair, order_id: int = None, orig_client_order_id: int = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "orderId": order_id, "origClientOrderId": orig_client_order_id, @@ -239,7 +201,7 @@ async def get_order(self, pair: Pair, order_id: int = None, orig_client_order_id async def cancel_order(self, pair: Pair, order_id: str = None, orig_client_order_id: str = None, new_client_order_id: str = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "orderId": order_id, "origClientOrderId": orig_client_order_id, @@ -251,7 +213,7 @@ async def cancel_order(self, pair: Pair, order_id: str = None, orig_client_order return await self._create_delete("order", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceClient.API_V3) async def get_open_orders(self, pair: Pair = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "recvWindow": recv_window_ms, "timestamp": self._get_current_timestamp_ms() }) @@ -264,7 +226,7 @@ async def get_open_orders(self, pair: Pair = None, recv_window_ms: int = None) - async def get_all_orders(self, pair: Pair, order_id: int = None, limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "orderId": order_id, "startTime": start_tmstmp_ms, @@ -289,7 +251,7 @@ async def create_oco_order(self, pair: Pair, side: enums.OrderSide, stop_limit_time_in_force: enums.TimeInForce = None, new_order_response_type: enums.OrderResponseType = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "side": side.value, "quantity": quantity, @@ -316,7 +278,7 @@ async def create_oco_order(self, pair: Pair, side: enums.OrderSide, async def cancel_oco_order(self, pair: Pair, order_list_id: str = None, list_client_order_id: str = None, new_client_order_id: str = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "orderListId": order_list_id, "listClientOrderId": list_client_order_id, @@ -330,7 +292,7 @@ async def cancel_oco_order(self, pair: Pair, order_list_id: str = None, list_cli async def get_oco_order(self, order_list_id: int = None, orig_client_order_id: int = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "orderListId": order_list_id, "origClientOrderId": orig_client_order_id, "recvWindow": recv_window_ms, @@ -341,7 +303,7 @@ async def get_oco_order(self, order_list_id: int = None, orig_client_order_id: i async def get_all_oco_orders(self, from_id: int = None, limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "fromId": from_id, "startTime": start_tmstmp_ms, "endTime": end_tmstmp_ms, @@ -354,7 +316,7 @@ async def get_all_oco_orders(self, from_id: int = None, limit: int = None, start signed = True, api_variable_path = BinanceClient.API_V3) async def get_open_oco_orders(self, recv_window_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "recvWindow": recv_window_ms, "timestamp": self._get_current_timestamp_ms() }) @@ -363,7 +325,7 @@ async def get_open_oco_orders(self, recv_window_ms: int = None) -> dict: signed = True, api_variable_path = BinanceClient.API_V3) async def get_account(self, recv_window_ms: Optional[int] = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "recvWindow": recv_window_ms, "timestamp": self._get_current_timestamp_ms() }) @@ -372,7 +334,7 @@ async def get_account(self, recv_window_ms: Optional[int] = None) -> dict: async def get_account_trades(self, pair: Pair, limit: int = None, from_id: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: - params = BinanceClient._clean_request_params({ + params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "limit": limit, "fromId": from_id, @@ -386,6 +348,1346 @@ async def get_account_trades(self, pair: Pair, limit: int = None, from_id: int = async def get_listen_key(self): return await self._create_post("userDataStream", headers = self._get_header(), api_variable_path = BinanceClient.API_V3) + ## MARGIN ENDPOINTS + + async def margin_transfer( + self, + asset: str, + amount: str, + transfer_type: enums.CrossMarginTransferType, + recv_window_ms: Optional[int] = None): + """ + Cross Margin Account Transfer (MARGIN) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/transfer (HMAC SHA256) + + asset STRING YES The asset being transferred, e.g., BTC + amount DECIMAL YES The amount to be transferred + type INT YES 1: transfer from main account to cross margin account + 2: transfer from cross margin account to main account + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + """ + params = { + "asset": asset, + "amount": amount, + "type": transfer_type.value, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/transfer", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def margin_borrow( + self, + asset: str, + amount: str, + pair: Optional[Pair] = None, + recv_window_ms: Optional[int] = None): + """ + Margin Account Borrow (MARGIN) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/loan (HMAC SHA256) + + asset STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + symbol STRING NO isolated symbol + amount DECIMAL YES + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If isIsolated = TRUE, symbol must be sent + isIsolated = FALSE for crossed margin loan + """ + params = { + "asset": asset, + "amount": amount, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is None: + params["isIsolated"] = "FALSE" + else: + params["isIsolated"] = "TRUE" + params["symbol"] = map_pair(pair) + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/loan", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def margin_repay( + self, + asset: str, + amount: str, + pair: Optional[Pair] = None, + recv_window_ms: Optional[int] = None): + """ + Margin Account Repay (MARGIN) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/repay (HMAC SHA256) + + Repay loan for margin account. + + asset STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + symbol STRING NO isolated symbol + amount DECIMAL YES + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If "isIsolated" = "TRUE", "symbol" must be sent + "isIsolated" = "FALSE" for crossed margin repay + """ + params = { + "asset": asset, + "amount": amount, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is None: + params["isIsolated"] = "FALSE" + else: + params["isIsolated"] = "TRUE" + params["symbol"] = map_pair(pair) + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/repay", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_asset(self, asset: str): + """ + Query Margin Asset (MARKET_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/asset + + asset STRING YES + """ + params = { + "asset": asset, + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/asset", + headers = self._get_header(), + params = params, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_pair(self, pair: Pair): + """ + Query Cross Margin Pair (MARKET_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/pair + + symbol STRING YES + """ + params = { + "symbol": map_pair(pair), + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/pair", + headers = self._get_header(), + params = params, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_all_assets(self): + """ + Get All Margin Assets (MARKET_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/allAssets + + Parameters: None + """ + + return await self._create_get( + "margin/allAssets", + headers = self._get_header(), + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_all_pairs(self): + """ + Get All Cross Margin Pairs (MARKET_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/allPairs + + Parameters: None + """ + + return await self._create_get( + "margin/allPairs", + headers = self._get_header(), + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_price_index(self, pair: Pair): + """ + Query Margin PriceIndex (MARKET_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/priceIndex + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory + symbol STRING YES + """ + params = { + "symbol": map_pair(pair), + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/priceIndex", + headers = self._get_header(), + params = params, + api_variable_path = BinanceClient.SAPI_V1) + + async def create_margin_order(self, + pair: Pair, + side: enums.OrderSide, + order_type: enums.OrderType, + quantity: str = None, + is_isolated: Optional[bool] = False, + quote_order_quantity: Optional[str] = None, + price: Optional[str] = None, + stop_price: Optional[str] = None, + new_client_order_id: Optional[str] = None, + iceberg_quantity: Optional[str] = None, + new_order_response_type: Optional[enums.OrderResponseType] = None, + side_effect_type: Optional[enums.SideEffectType] = None, + time_in_force: Optional[enums.TimeInForce] = None, + recv_window_ms: Optional[int] = None): + """ + Margin Account New Order (TRADE) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/order (HMAC SHA256) + + Post a new order for margin account. + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + side ENUM YES BUY SELL + type ENUM YES + quantity DECIMAL NO + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + quoteOrderQty DECIMAL NO + price DECIMAL NO + stopPrice DECIMAL NO Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, TAKE_PROFIT_LIMIT orders. + newClientOrderId STRING NO A unique id among open orders. Automatically generated if not sent. + icebergQty DECIMAL NO Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create + an iceberg order. + newOrderRespType ENUM NO Set the response JSON. ACK, RESULT, or FULL; + MARKET and LIMIT order types default to FULL, all other orders + default to ACK. + sideEffectType ENUM NO NO_SIDE_EFFECT, MARGIN_BUY, AUTO_REPAY; default NO_SIDE_EFFECT. + timeInForce ENUM NO GTC,IOC,FOK + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + """ + params = { + "symbol": map_pair(pair), + "side": side, + "type": order_type.value, + "quantity": quantity, + "quoteOrderQty": quote_order_quantity, + "price": price, + "stopPrice": stop_price, + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "newClientOrderId": new_client_order_id, + "icebergQty": iceberg_quantity, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + if time_in_force is not None: + params['timeInForce'] = time_in_force.value + + if new_order_response_type is not None: + params['newOrderRespType'] = new_order_response_type.value + + if side_effect_type is not None: + params["sideEffectType"] = side_effect_type.value + + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/order", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def cancel_margin_order(self, + pair: Pair, + is_isolated: Optional[bool] = False, + order_id: Optional[str] = None, + orig_client_order_id: Optional[str] = None, + new_client_order_id: Optional[str] = None, + recv_window_ms: Optional[int] = None): + """ + Margin Account Cancel Order (TRADE) + + From the official original Binance REST API documentation: + + DELETE /sapi/v1/margin/order (HMAC SHA256) + + Cancel an active order for margin account. + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + orderId LONG NO + origClientOrderId STRING NO + newClientOrderId STRING NO Used to uniquely identify this cancel. Automatically generated by default. + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + Either orderId or origClientOrderId must be sent. + """ + params = { + "symbol": map_pair(pair), + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "orderId": order_id, + "origClientOrderId": orig_client_order_id, + "newClientOrderId": new_client_order_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_delete( + "margin/order", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def cancel_margin_open_orders(self, pair: Pair, is_isolated: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Margin Account Cancel all Open Orders on a Symbol (TRADE) + + From the official original Binance REST API documentation: + + DELETE /sapi/v1/margin/openOrders (HMAC SHA256) + + Cancels all active orders on a symbol for margin account. + This includes OCO orders. + + Weight: 1 + + Official Binance REST API documentation: + Parameters + Name Type Mandatory Description + symbol STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + """ + params = { + "symbol": map_pair(pair), + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_delete( + "margin/openOrders", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_transfer_history( + self, + asset: Optional[str] = None, + transfer_type: Optional[enums.TransferType] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + is_archived: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Get Cross Margin Transfer History (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/transfer (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING No + type STRING NO Transfer Type: ROLL_IN, ROLL_OUT + startTime LONG NO + endTime LONG NO + current LONG NO Currently querying page. Start from 1. Default:1 + size LONG NO Default:10 Max:100 + archived STRING NO Default: false. Set to true for archived data from 6 months ago + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + Response in descending order + Returns data for last 7 days by default + Set archived to true to query data from 6 months ago + """ + params = { + "asset": asset, + "archived": ("TRUE" if is_archived else "FALSE"), + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": (1 if page_num is None else page_num), + "size": (10 if page_size is None else page_size), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + if transfer_type is not None: + params["type"] = transfer_type.value + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/transfer", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_loan(self, + asset: str, + pair: Optional[Pair] = None, + tx_id: Optional[int] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + is_archived: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Query Loan Record (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/loan (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES + isolatedSymbol STRING NO isolated symbol + txId LONG NO the tranId in POST /sapi/v1/margin/loan + startTime LONG NO + endTime LONG NO + current LONG NO Currently querying page. Start from 1. Default:1 + size LONG NO Default:10 Max:100 + archived STRING NO Default: false. Set to true for archived data from 6 months ago + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + txId or startTime must be sent. txId takes precedence. + Response in descending order + If isolatedSymbol is not sent, crossed margin data will be returned + Set archived to true to query data from 6 months ago + """ + params = { + "asset": asset, + "txId": tx_id, + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": (1 if page_num is None else page_num), + "size": (10 if page_size is None else page_size), + "archived": ("TRUE" if is_archived else "FALSE"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/loan", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_repay(self, + asset: str, + pair: Optional[Pair] = None, + tx_id: Optional[int] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + is_archived: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Query Repay Record (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/repay (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES + isolatedSymbol STRING NO isolated symbol + txId LONG NO return of /sapi/v1/margin/repay + startTime LONG NO + endTime LONG NO + current LONG NO Currently querying page. Start from 1. Default:1 + size LONG NO Default:10 Max:100 + archived STRING NO Default: false. Set to true for archived data from 6 months ago + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + txId or startTime must be sent. txId takes precedence. + Response in descending order + If isolatedSymbol is not sent, crossed margin data will be returned + Set archived to true to query data from 6 months ago + """ + params = { + "asset": asset, + "txId": tx_id, + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": (1 if page_num is None else page_num), + "size": (10 if page_size is None else page_size), + "archived": ("TRUE" if is_archived else "FALSE"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/repay", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_interest_history(self, + asset: str, + pair: Optional[Pair] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + is_archived: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Get Interest History (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/interestHistory (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING NO + isolatedSymbol STRING NO isolated symbol + startTime LONG NO + endTime LONG NO + current LONG NO Currently querying page. Start from 1. Default:1 + size LONG NO Default:10 Max:100 + archived STRING NO Default: false. Set to true for archived data from 6 months ago + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + Response in descending order + If isolatedSymbol is not sent, crossed margin data will be returned + Set archived to true to query data from 6 months ago + type in response has 4 enums: + PERIODIC interest charged per hour + ON_BORROW first interest charged on borrow + PERIODIC_CONVERTED interest charged per hour converted into BNB + ON_BORROW_CONVERTED first interest charged on borrow converted into BNB + """ + params = { + "asset": asset, + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": (1 if page_num is None else page_num), + "size": (10 if page_size is None else page_size), + "archived": ("TRUE" if is_archived else "FALSE"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/interestHistory", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_force_liquidation_record(self, + pair: Optional[Pair] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + page_num: Optional[int] = None, + page_size: Optional[int] = None, + recv_window_ms: Optional[int] = None): + """ + Get Force Liquidation Record (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/forceLiquidationRec (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + isolatedSymbol STRING NO + startTime LONG NO + endTime LONG NO + current LONG NO Currently querying page. Start from 1. Default:1 + size LONG NO Default:10 Max:100 + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + Response in descending order + """ + params = { + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": (1 if page_num is None else page_num), + "size": (10 if page_size is None else page_size), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/forceLiquidationRec", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_account(self, recv_window_ms: Optional[int] = None): + """ + Query Cross Margin Account Details (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/account (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + """ + params = { + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/account", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_order(self, + pair: Pair, + is_isolated: Optional[bool] = False, + order_id: Optional[str] = None, + orig_client_order_id: Optional[str] = None, + recv_window_ms: Optional[int] = None): + """ + Query Margin Account's Order (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/order (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + orderId STRING NO + origClientOrderId STRING NO + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + Either orderId or origClientOrderId must be sent. + For some historical orders cumulativeQuoteQty will be < 0, meaning the data is not available at this time. + """ + params = { + "symbol": map_pair(pair), + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "orderId": order_id, + "origClientOrderId": orig_client_order_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/order", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_open_orders(self, + pair: Optional[Pair] = None, + is_isolated: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Query Margin Account's Open Orders (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/openOrders (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING NO + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If the symbol is not sent, orders for all symbols will be returned in an array. + When all symbols are returned, the number of requests counted against the rate limiter + is equal to the number of symbols currently trading on the exchange. + If isIsolated ="TRUE", symbol must be sent + """ + params = { + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["symbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/openOrders", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_all_orders(self, + pair: Pair, + is_isolated: Optional[bool] = False, + order_id: Optional[int] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + limit: Optional[int] = None, + recv_window_ms: Optional[int] = None): + """ + Query Margin Account's All Orders (USER_DATA) + + GET /sapi/v1/margin/allOrders (HMAC SHA256) + + From the official original Binance REST API documentation: + + Weight: 1 + + Request Limit: 60times/min per IP + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + orderId LONG NO + startTime LONG NO + endTime LONG NO + limit INT NO Default 500; max 500. + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If orderId is set, it will get orders >= that orderId. Otherwise most recent orders are returned. + For some historical orders cumulativeQuoteQty will be < 0, meaning the data is not available at this time. + """ + params = { + "symbol": map_pair(pair), + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "orderId": order_id, + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "limit": limit, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/allOrders", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_my_trades( + self, + pair: Pair, + is_isolated: Optional[bool] = False, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + from_id: Optional[int] = None, + limit: Optional[int] = None, + recv_window_ms: Optional[int] = None): + """ + Query Margin Account's Trade List (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/myTrades (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + isIsolated STRING NO for isolated margin or not, "TRUE", "FALSE",default "FALSE" + startTime LONG NO + endTime LONG NO + fromId LONG NO TradeId to fetch from. Default gets most recent trades. + limit INT NO Default 500; max 1000. + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If fromId is set, it will get trades >= that fromId. Otherwise most recent trades are returned + """ + params = { + "symbol": map_pair(pair), + "isIsolated": ("TRUE" if is_isolated else "FALSE"), + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "limit": limit, + "fromId": from_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/myTrades", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_max_borrowable( + self, asset: str, pair: Optional[Pair] = None, recv_window_ms: Optional[int] = None): + """ + Query Max Borrow (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/maxBorrowable (HMAC SHA256) + + Weight: 5 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES + isolatedSymbol STRING NO isolated symbol + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If isolatedSymbol is not sent, crossed margin data will be sent. + borrowLimit is also available from https://www.binance.com/en/margin-fee + """ + params = { + "asset": asset, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/maxBorrowable", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_max_transferable( + self, + asset: str, + pair: Optional[Pair] = None, + recv_window_ms: Optional[int] = None): + """ + Query Max Transfer-Out Amount (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/maxTransferable (HMAC SHA256) + + Weight: 5 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES + isolatedSymbol STRING NO isolated symbol + recvWindow LONG NO The value cannot be greater than 60000 + timestamp LONG YES + + If isolatedSymbol is not sent, crossed margin data will be sent + """ + params = { + "asset": asset, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + if pair is not None: + params["isolatedSymbol"] = map_pair(pair) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/maxTransferable", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def create_isolated_margin_account(self, pair: Pair, recv_window_ms: Optional[int] = None): + """ + Create Isolated Margin Account (MARGIN) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/isolated/create (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + base STRING YES Base asset of symbol + quote STRING YES Quote asset of symbol + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "base": pair.base, + "quote": pair.quote, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/isolated/create", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def margin_isolated_transfer(self, + asset: str, + pair: Pair, + from_account_type: enums.AccountType, + to_account_type: enums.AccountType, + amount: str, + recv_window_ms: Optional[int] = None): + """ + Isolated Margin Account Transfer (MARGIN) + + From the official original Binance REST API documentation: + + POST /sapi/v1/margin/isolated/transfer (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES asset,such as BTC + symbol STRING YES + transFrom STRING YES "SPOT", "ISOLATED_MARGIN" + transTo STRING YES "SPOT", "ISOLATED_MARGIN" + amount DECIMAL YES + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "asset": asset, + "symbol": map_pair(pair), + "transFrom": from_account_type.value, + "transTo": to_account_type.value, + "amount": amount, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "margin/isolated/transfer", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_isolated_transfer(self, + asset: str, + pair: Pair, + from_account_type: Optional[enums.AccountType] = None, + to_account_type: Optional[enums.AccountType] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + current: Optional[int] = None, + size: Optional[int] = None, + recv_window_ms: Optional[int] = None): + """ + Get Isolated Margin Transfer History (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/isolated/transfer (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING NO + symbol STRING YES + transFrom STRING NO "SPOT", "ISOLATED_MARGIN" + transTo STRING NO "SPOT", "ISOLATED_MARGIN" + startTime LONG NO + endTime LONG NO + current LONG NO Current page, default 1 + size LONG NO Default 10, max 100 + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "symbol": map_pair(pair), + "asset": asset, + "recvWindow": recv_window_ms, + "starTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "current": current, + "size": size, + "timestamp": self._get_current_timestamp_ms() + } + + if from_account_type is not None: + params["transFrom"] = from_account_type.value + + if to_account_type is not None: + params["transTo"] = to_account_type.value + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/isolated/transfer", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_isolated_account(self, pairs: Optional[List[Pair]], recv_window_ms: Optional[int] = None): + """ + Query Isolated Margin Account Info (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/isolated/account (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbols STRING NO Max 5 symbols can be sent; separated by ",". e.g. "BTCUSDT,BNBUSDT,ADAUSDT" + recvWindow LONG NO No more than 60000 + timestamp LONG YES + + If "symbols" is not sent, all isolated assets will be returned. + If "symbols" is sent, only the isolated assets of the sent symbols will be returned. + """ + params = { + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + + if pairs is not None: + params["symbols"] = ",".join(map(map_pair, pairs)) + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/isolated/account", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_isolated_pair(self, pair: Pair, recv_window_ms: Optional[int] = None): + """ + Query Isolated Margin Symbol (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/isolated/pair (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + symbol STRING YES + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "symbol": map_pair(pair), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/isolated/pair", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_isolated_all_pairs(self, recv_window_ms: Optional[int] = None): + """ + Get All Isolated Margin Symbol(USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/isolated/allPairs (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/isolated/allPairs", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def toggle_bnb_burn( + self, + spot_bnb_burn: Optional[bool] = False, + interest_bnb_burn: Optional[bool] = False, + recv_window_ms: Optional[int] = None): + """ + Toggle BNB Burn On Spot Trade And Margin Interest (USER_DATA) + + From the official original Binance REST API documentation: + + POST /sapi/v1/bnbBurn (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + spotBNBBurn STRING NO "true" or "false"; Determines whether to use BNB to pay + for trading fees on SPOT + interestBNBBurn STRING NO "true" or "false"; Determines whether to use BNB to pay + for margin loan's interest + recvWindow LONG NO No more than 60000 + timestamp LONG YES + + spotBNBBurn and interestBNBBurn should be sent at least one. + """ + params = { + "spotBNBBurn": ("true" if spot_bnb_burn else "false"), + "interestBNBBurn": ("true" if interest_bnb_burn else "false"), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_post( + "bnbBurn", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_bnb_burn(self, recv_window_ms: Optional[int] = None): + """ + Get BNB Burn Status (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/bnbBurn (HMAC SHA256) + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + recvWindow LONG NO No more than 60000 + timestamp LONG YES + """ + params = { + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + } + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "bnbBurn", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + + async def get_margin_interest_rate_history( + self, + asset: str, + vip_level: Optional[str] = None, + start_timestamp_ms: Optional[int] = None, + end_timestamp_ms: Optional[int] = None, + limit: Optional[int] = None, + recv_window_ms: Optional[int] = None): + """ + Query Margin Interest Rate History (USER_DATA) + + From the official original Binance REST API documentation: + + GET /sapi/v1/margin/interestRateHistory + + Weight: 1 + + Official Binance REST API documentation: + Parameters: + Name Type Mandatory Description + asset STRING YES + vipLevel STRING NO Default: user's vip level + startTime LONG NO Default: 7 days ago + endTime LONG NO Default: present. Maximum range: 3 months. + limit INT NO Default: 20. Maximum: 100 + recvWindow LONG NO No more than 60000 + timestamp LONG YES + + X-MBX-APIKEY required + """ + params = { + "asset": asset, + "vipLevel": vip_level, + "startTime": start_timestamp_ms, + "endTime": end_timestamp_ms, + "recvWindow": recv_window_ms, + "limit": limit, + "timestamp": self._get_current_timestamp_ms() + } + + params = BinanceClient._clean_request_params(params) + + return await self._create_get( + "margin/interestRateHistory", + headers = self._get_header(), + params = params, + signed = True, + api_variable_path = BinanceClient.SAPI_V1) + class BinanceTestnetClient(BinanceClient): REST_API_URI = "https://testnet.binance.vision/" @@ -403,37 +1705,13 @@ def _get_websocket_mgr(self, subscriptions: List[Subscription], startup_delay_ms sec_key = self.sec_key, ssl_context = ssl_context) -class BinanceUSDSMFuturesClient(BinanceDefaultClient): - def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, - ssl_context: ssl.SSLContext = None) -> None: - raise Exception("USDS-M Futures are not implemented at the moment!") - - -class BinanceUSDSMFuturesTestnetClient(BinanceDefaultClient): - def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, - ssl_context: ssl.SSLContext = None) -> None: - raise Exception("USDS-M Futures are not implemented at the moment!") - - -class BinanceCOINMFuturesClient(BinanceDefaultClient): - def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, - ssl_context: ssl.SSLContext = None) -> None: - raise Exception("COIN-M Futures are not implemented at the moment!") - - -class BinanceCOINMFuturesTestnetClient(BinanceDefaultClient): - def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, - ssl_context: ssl.SSLContext = None) -> None: - raise Exception("COIN-M Futures are not implemented at the moment!") - - -class BinanceVanillaOptionsClient(BinanceDefaultClient): +class BinanceVanillaOptionsClient(BinanceCommonClient): def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, ssl_context: ssl.SSLContext = None) -> None: raise Exception("Vanilla Options are not implemented at the moment!") -class BinanceVanillaOptionsTestnetClient(BinanceDefaultClient): +class BinanceVanillaOptionsTestnetClient(BinanceCommonClient): def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, ssl_context: ssl.SSLContext = None) -> None: raise Exception("Vanilla Options are not implemented at the moment!") \ No newline at end of file diff --git a/cryptoxlib/clients/binance/BinanceCommonClient.py b/cryptoxlib/clients/binance/BinanceCommonClient.py new file mode 100644 index 0000000..689087e --- /dev/null +++ b/cryptoxlib/clients/binance/BinanceCommonClient.py @@ -0,0 +1,46 @@ +import ssl +import logging +import hmac +import hashlib +from multidict import CIMultiDictProxy +from typing import Optional + +from cryptoxlib.CryptoXLibClient import CryptoXLibClient, RestCallType +from cryptoxlib.clients.binance.exceptions import BinanceRestException + +LOG = logging.getLogger(__name__) + + +class BinanceCommonClient(CryptoXLibClient): + def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, + ssl_context: ssl.SSLContext = None) -> None: + super().__init__(api_trace_log, ssl_context) + + self.api_key = api_key + self.sec_key = sec_key + + def _sign_payload(self, rest_call_type: RestCallType, resource: str, data: dict = None, params: dict = None, headers: dict = None) -> None: + params_string = "" + data_string = "" + + if params is not None and len(params) > 0: + params_string = '&'.join([f"{key}={val}" for key, val in params.items()]) + + if data is not None and len(data) > 0: + data_string = '&'.join(["{}={}".format(param[0], param[1]) for param in data]) + + m = hmac.new(self.sec_key.encode('utf-8'), (params_string + data_string).encode('utf-8'), hashlib.sha256) + + params['signature'] = m.hexdigest() + + def _preprocess_rest_response(self, status_code: int, headers: 'CIMultiDictProxy[str]', body: Optional[dict]) -> None: + if str(status_code)[0] != '2': + raise BinanceRestException(status_code, body) + + def _get_header(self): + header = { + 'Accept': 'application/json', + "X-MBX-APIKEY": self.api_key + } + + return header \ No newline at end of file diff --git a/cryptoxlib/clients/binance/BinanceCommonWebsocket.py b/cryptoxlib/clients/binance/BinanceCommonWebsocket.py new file mode 100644 index 0000000..9aa9bd7 --- /dev/null +++ b/cryptoxlib/clients/binance/BinanceCommonWebsocket.py @@ -0,0 +1,75 @@ +import json +import logging +from typing import List, Any + +from cryptoxlib.WebsocketMgr import Subscription, WebsocketMgr, WebsocketMessage, Websocket, CallbacksType + +LOG = logging.getLogger(__name__) + + +class BinanceCommonWebsocket(WebsocketMgr): + SUBSCRIPTION_ID = 0 + + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + websocket_uri: str = None, ssl_context = None) -> None: + super().__init__(websocket_uri = websocket_uri, + subscriptions = subscriptions, + builtin_ping_interval = None, + ssl_context = ssl_context, + auto_reconnect = True) + + self.api_key = api_key + self.sec_key = sec_key + self.binance_client = binance_client + + def get_websocket_uri_variable_part(self): + return "stream?streams=" + "/".join([subscription.get_channel_name() for subscription in self.subscriptions]) + + async def initialize_subscriptions(self) -> None: + for subscription in self.subscriptions: + await subscription.initialize(binance_client = self.binance_client) + + async def _subscribe(self, websocket: Websocket): + BinanceCommonWebsocket.SUBSCRIPTION_ID += 1 + + subscription_message = { + "method": "SUBSCRIBE", + "params": [ + subscription.get_channel_name() for subscription in self.subscriptions + ], + "id": BinanceCommonWebsocket.SUBSCRIPTION_ID + } + + LOG.debug(f"> {subscription_message}") + await websocket.send(json.dumps(subscription_message)) + + @staticmethod + def _is_subscription_confirmation(response): + if 'result' in response and response['result'] is None: + return True + else: + return False + + async def _process_message(self, websocket: Websocket, message: str) -> None: + message = json.loads(message) + + if self._is_subscription_confirmation(message): + LOG.info(f"Subscription confirmed for id: {message['id']}") + else: + # regular message + await self.publish_message(WebsocketMessage(subscription_id = message['stream'], message = message)) + + +class BinanceSubscription(Subscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + @staticmethod + def get_channel_name(): + pass + + def get_subscription_message(self, **kwargs) -> dict: + pass + + def construct_subscription_id(self) -> Any: + return self.get_channel_name() \ No newline at end of file diff --git a/cryptoxlib/clients/binance/BinanceFuturesClient.py b/cryptoxlib/clients/binance/BinanceFuturesClient.py new file mode 100644 index 0000000..d00befd --- /dev/null +++ b/cryptoxlib/clients/binance/BinanceFuturesClient.py @@ -0,0 +1,637 @@ +import ssl +import logging +from typing import List, Optional + +from cryptoxlib.CryptoXLibClient import CryptoXLibClient +from cryptoxlib.clients.binance.BinanceCommonClient import BinanceCommonClient +from cryptoxlib.clients.binance.BinanceFuturesWebsocket import BinanceUSDSMFuturesWebsocket, BinanceUSDSMFuturesTestnetWebsocket +from cryptoxlib.clients.binance import enums +from cryptoxlib.clients.binance.functions import map_pair +from cryptoxlib.Pair import Pair +from cryptoxlib.WebsocketMgr import WebsocketMgr, Subscription + +LOG = logging.getLogger(__name__) + + +class BinanceUSDSMFuturesClient(BinanceCommonClient): + REST_API_URI = "https://fapi.binance.com/" + FAPI_V1 = "fapi/v1/" + FAPI_V2 = "fapi/v2/" + FUTURES = "futures/data/" + + def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, + ssl_context: ssl.SSLContext = None) -> None: + super().__init__(api_key = api_key, sec_key = sec_key, api_trace_log = api_trace_log, ssl_context = ssl_context) + + def _get_rest_api_uri(self) -> str: + return BinanceUSDSMFuturesClient.REST_API_URI + + def _get_websocket_mgr(self, subscriptions: List[Subscription], startup_delay_ms: int = 0, + ssl_context = None) -> WebsocketMgr: + return BinanceUSDSMFuturesWebsocket(subscriptions = subscriptions, binance_client = self, api_key = self.api_key, + sec_key = self.sec_key, ssl_context = ssl_context) + + async def ping(self) -> dict: + return await self._create_get("ping", api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_exchange_info(self) -> dict: + return await self._create_get("exchangeInfo", api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_time(self) -> dict: + return await self._create_get("time", api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_orderbook(self, pair: Pair, limit: enums.DepthLimit = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + }) + + if limit: + params['limit'] = limit.value + + return await self._create_get("depth", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_trades(self, pair: Pair, limit: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit + }) + + return await self._create_get("trades", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_historical_trades(self, pair: Pair, limit: int = None, from_id: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "fromId": from_id + }) + + return await self._create_get("historicalTrades", params = params, headers = self._get_header(), api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_aggregate_trades(self, pair: Pair, limit: int = None, from_id: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "fromId": from_id, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("aggTrades", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_candlesticks(self, pair: Pair, interval: enums.Interval, limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if interval: + params['interval'] = interval.value + + return await self._create_get("klines", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_cont_contract_candlesticks(self, pair: Pair, interval: enums.Interval, + contract_type: enums.ContractType, limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "pair": map_pair(pair), + "contractType": contract_type.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if interval: + params['interval'] = interval.value + + return await self._create_get("continuousKlines", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_index_price_candlesticks(self, pair: Pair, interval: enums.Interval, + limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "pair": map_pair(pair), + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if interval: + params['interval'] = interval.value + + return await self._create_get("indexPriceKlines", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_mark_price_candlesticks(self, pair: Pair, interval: enums.Interval, + limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if interval: + params['interval'] = interval.value + + return await self._create_get("markPriceKlines", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_mark_price(self, pair: Pair = None) -> dict: + params = {} + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("premiumIndex", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_fund_rate_history(self, pair: Pair = None, limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("fundingRate", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_24h_price_ticker(self, pair: Pair = None) -> dict: + params = {} + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("ticker/24hr", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_price_ticker(self, pair: Pair = None) -> dict: + params = {} + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("ticker/price", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_orderbook_ticker(self, pair: Pair = None) -> dict: + params = {} + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("ticker/bookTicker", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_all_liquidation_orders(self, pair: Pair = None, limit: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("allForceOrders", params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_open_interest(self, pair: Pair) -> dict: + params = { + "symbol": map_pair(pair) + } + + return await self._create_get("openInterest", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_open_interest_hist(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "period": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("openInterestHist", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FUTURES) + + async def get_top_long_short_account_ratio(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "period": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("topLongShortAccountRatio", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FUTURES) + + async def get_top_long_short_position_ratio(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "period": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("topLongShortPositionRatio", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FUTURES) + + async def get_global_long_short_account_ratio(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "period": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("globalLongShortAccountRatio", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FUTURES) + + async def get_taker_long_short_ratio(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "period": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("takerlongshortRatio", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FUTURES) + + async def get_blvt_candlesticks(self, pair: Pair, interval: enums.Interval, + limit: int = None, start_tmstmp_ms: int = None, end_tmstmp_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "interval": interval.value, + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms + }) + + return await self._create_get("lvtKlines", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_index_info(self, pair: Pair = None) -> dict: + params = {} + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("indexInfo", headers = self._get_header(), params = params, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def change_position_type(self, dual_side_position: bool, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "dualSidePosition": "true" if dual_side_position else "false", + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_post("positionSide/dual", headers = self._get_header(), params = params, signed = True, + api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_position_type(self, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("positionSide/dual", headers = self._get_header(), params = params, signed = True, + api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_all_orders(self, pair: Pair, order_id: int = None, limit: int = None, start_tmstmp_ms: int = None, + end_tmstmp_ms: int = None, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "orderId": order_id, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms, + "limit": limit, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("allOrders", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def create_order(self, pair: Pair, side: enums.OrderSide, type: enums.OrderType, + position_side: enums.PositionSide = None, + quantity: str = None, + price: str = None, + stop_price: str = None, + time_in_force: enums.TimeInForce = None, + new_client_order_id: str = None, + reduce_only: bool = None, + close_position: bool = None, + activation_price: str = None, + callback_rate: str = None, + working_type: enums.WorkingType = None, + price_protect: bool = None, + new_order_response_type: enums.OrderResponseType = None, + recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "side": side.value, + "type": type.value, + "quantity": quantity, + "price": price, + "stopPrice": stop_price, + "newClientOrderId": new_client_order_id, + "activationPrice": activation_price, + "callbackRate": callback_rate, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + if price_protect is not None: + params['priceProtect'] = "true" if price_protect else "false" + + if working_type is not None: + params['workingType'] = working_type.value + + if position_side is not None: + params['positionSide'] = position_side.value + + if reduce_only is not None: + params['reduceOnly'] = "true" if reduce_only else "false" + + if close_position is not None: + params['closePosition'] = "true" if close_position else "false" + + if time_in_force is not None: + params['timeInForce'] = time_in_force.value + + if new_order_response_type is not None: + params['newOrderRespType'] = new_order_response_type.value + + return await self._create_post("order", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_order(self, pair: Pair, order_id: int = None, orig_client_order_id: int = None, + recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "orderId": order_id, + "origClientOrderId": orig_client_order_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("order", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def cancel_order(self, pair: Pair, order_id: int = None, orig_client_order_id: str = None, + recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "orderId": order_id, + "origClientOrderId": orig_client_order_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_delete("order", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def cancel_all_orders(self, pair: Pair, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_delete("allOpenOrders", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def auto_cancel_orders(self, pair: Pair, countdown_time_ms: int, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "countdownTime": countdown_time_ms, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_delete("countdownCancelAll", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_open_order(self, pair: Pair, order_id: int = None, orig_client_order_id: int = None, + recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "orderId": order_id, + "origClientOrderId": orig_client_order_id, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("openOrder", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_all_open_orders(self, pair: Pair, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("openOrders", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_balance(self, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("balance", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V2) + + async def get_account(self, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("account", headers = self._get_header(), params = params, signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V2) + + async def change_init_leverage(self, pair: Pair, leverage: int, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "leverage": leverage, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_post("leverage", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def change_margin_type(self, pair: Pair, margin_type: enums.MarginType, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "marginType": margin_type.value, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_post("marginType", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def update_isolated_position_margin(self, pair: Pair, quantity: str, type: int, + position_side: enums.PositionSide = None, recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "amount": quantity, + "type": type, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + if position_side is not None: + params['positionSide'] = position_side.value + + return await self._create_post("positionMargin", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_position_margin_change_history(self, pair: Pair, limit: int = None, type: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, + recv_window_ms: int = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "type": type, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms, + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + return await self._create_get("positionMargin/history", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_position(self, pair: Pair = None, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "recvWindow": recv_window_ms, + "timestamp": self._get_current_timestamp_ms() + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("positionRisk", headers = self._get_header(), params = params, signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V2) + + async def get_account_trades(self, pair: Pair, limit: int = None, from_id: int = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, + recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "limit": limit, + "fromId": from_id, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms, + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + return await self._create_get("userTrades", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_income_history(self, pair: Pair = None, limit: int = None, + income_type: enums.IncomeType = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, + recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms, + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + if income_type is not None: + params['incomeType'] = income_type.value + + return await self._create_get("income", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_notional_and_leverage_brackets(self, pair: Pair = None, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("leverageBracket", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_adl_quantile(self, pair: Pair = None, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("adlQuantile", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_force_orders(self, pair: Pair = None, limit: int = None, auto_close_type: enums.AutoCloseType = None, + start_tmstmp_ms: int = None, end_tmstmp_ms: int = None, + recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "limit": limit, + "startTime": start_tmstmp_ms, + "endTime": end_tmstmp_ms, + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + if auto_close_type is not None: + params['autoCloseType'] = auto_close_type.value + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("forceOrders", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_api_trading_status(self, pair: Pair = None, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + if pair is not None: + params['symbol'] = map_pair(pair) + + return await self._create_get("apiTradingStatus", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_commission_rate(self, pair: Pair, recv_window_ms: Optional[int] = None) -> dict: + params = CryptoXLibClient._clean_request_params({ + "symbol": map_pair(pair), + "timestamp": self._get_current_timestamp_ms(), + "recvWindow": recv_window_ms + }) + + return await self._create_get("commissionRate", params = params, headers = self._get_header(), signed = True, api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def get_listen_key(self): + return await self._create_post("listenKey", headers = self._get_header(), api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + async def keep_alive_listen_key(self, listen_key: str): + return await self._create_put("listenKey", headers = self._get_header(), api_variable_path = BinanceUSDSMFuturesClient.FAPI_V1) + + +class BinanceUSDSMFuturesTestnetClient(BinanceUSDSMFuturesClient): + REST_API_URI = "https://testnet.binancefuture.com/" + + def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, + ssl_context: ssl.SSLContext = None) -> None: + super().__init__(api_key = api_key, sec_key = sec_key, api_trace_log = api_trace_log, ssl_context = ssl_context) + + def _get_rest_api_uri(self) -> str: + return BinanceUSDSMFuturesTestnetClient.REST_API_URI + + def _get_websocket_mgr(self, subscriptions: List[Subscription], startup_delay_ms: int = 0, + ssl_context = None) -> WebsocketMgr: + return BinanceUSDSMFuturesTestnetWebsocket(subscriptions = subscriptions, binance_client = self, + api_key = self.api_key, + sec_key = self.sec_key, ssl_context = ssl_context) + + +class BinanceCOINMFuturesClient(BinanceCommonClient): + def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, + ssl_context: ssl.SSLContext = None) -> None: + raise Exception("COIN-M Futures are not implemented at the moment!") + + +class BinanceCOINMFuturesTestnetClient(BinanceCommonClient): + def __init__(self, api_key: str = None, sec_key: str = None, api_trace_log: bool = False, + ssl_context: ssl.SSLContext = None) -> None: + raise Exception("COIN-M Futures are not implemented at the moment!") \ No newline at end of file diff --git a/cryptoxlib/clients/binance/BinanceFuturesWebsocket.py b/cryptoxlib/clients/binance/BinanceFuturesWebsocket.py new file mode 100644 index 0000000..dcba342 --- /dev/null +++ b/cryptoxlib/clients/binance/BinanceFuturesWebsocket.py @@ -0,0 +1,261 @@ +import json +import logging +from typing import List + +from cryptoxlib.WebsocketMgr import Subscription, CallbacksType +from cryptoxlib.Pair import Pair +from cryptoxlib.clients.binance.exceptions import BinanceException +from cryptoxlib.clients.binance.functions import map_ws_pair +from cryptoxlib.clients.binance.BinanceCommonWebsocket import BinanceCommonWebsocket, BinanceSubscription +from cryptoxlib.clients.binance import enums + +LOG = logging.getLogger(__name__) + + +class BinanceUSDSMFuturesWebsocket(BinanceCommonWebsocket): + WEBSOCKET_URI = "wss://fstream.binance.com/" + + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceUSDSMFuturesWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) + + +class BinanceUSDSMFuturesTestnetWebsocket(BinanceCommonWebsocket): + WEBSOCKET_URI = "wss://stream.binancefuture.com/" + + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceUSDSMFuturesTestnetWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) + + +class BinanceCOINMFuturesWebsocket(BinanceCommonWebsocket): + WEBSOCKET_URI = "https://dapi.binance.com/" + + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceUSDSMFuturesWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) + + +class BinanceCOINMFuturesTestnetWebsocket(BinanceCommonWebsocket): + WEBSOCKET_URI = "wss://dstream.binancefuture.com/" + + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceUSDSMFuturesTestnetWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) + + +class AggregateTradeSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return map_ws_pair(self.pair) + "@aggTrade" + + +class MarkPriceSubscription(BinanceSubscription): + def __init__(self, pair: Pair, frequency1sec: bool = False, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + self.frequency1sec = frequency1sec + + def get_channel_name(self): + return map_ws_pair(self.pair) + "@markPrice" + ("@1s" if self.frequency1sec else "") + + +class MarkPriceAllSubscription(BinanceSubscription): + def __init__(self, frequency1sec: bool = False, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.frequency1sec = frequency1sec + + def get_channel_name(self): + return "!markPrice@arr" + ("@1s" if self.frequency1sec else "") + + +class CandlestickSubscription(BinanceSubscription): + def __init__(self, pair: Pair, interval: enums.Interval, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + self.interval = interval + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@kline_{self.interval.value}" + + +class ContContractCandlestickSubscription(BinanceSubscription): + def __init__(self, pair: Pair, interval: enums.Interval, contract_type: enums.ContractType, callbacks: CallbacksType = None): + super().__init__(callbacks) + + if contract_type not in [enums.ContractType.PERPETUAL, enums.ContractType.CURRENT_QUARTER, enums.ContractType.NEXT_QUARTER]: + raise BinanceException(f"Level [{contract_type.value}] must be one of {enums.ContractType.PERPETUAL.value}, " + f"{enums.ContractType.CURRENT_QUARTER.value} or {enums.ContractType.NEXT_QUARTER.value}.") + + self.pair = pair + self.interval = interval + self.contract_type = contract_type + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}_{self.contract_type.value.lower()}@continuousKline_{self.interval.value}" + + +class AllMarketMiniTickersSubscription(BinanceSubscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + def get_channel_name(self): + return "!miniTicker@arr" + + +class MiniTickerSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@miniTicker" + + +class AllMarketTickersSubscription(BinanceSubscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + def get_channel_name(self): + return "!ticker@arr" + + +class TickerSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@ticker" + + +class OrderBookTickerSubscription(BinanceSubscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + def get_channel_name(self): + return "!bookTicker" + + +class OrderBookSymbolTickerSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@bookTicker" + + +class LiquidationOrdersSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@forceOrder" + + +class AllMarketLiquidationOrdersSubscription(BinanceSubscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + def get_channel_name(self): + return "!forceOrder@arr" + + +class DepthSubscription(BinanceSubscription): + DEFAULT_FREQUENCY = 250 + DEFAULT_LEVEL = 0 + + def __init__(self, pair: Pair, level: int = DEFAULT_LEVEL, frequency: int = DEFAULT_FREQUENCY, + callbacks: CallbacksType = None): + super().__init__(callbacks) + + if level not in [0, 5, 10, 20]: + raise BinanceException(f"Level [{level}] must be one of 0, 5, 10 or 20.") + + if frequency not in [100, 250, 500]: + raise BinanceException(f"Frequency [{frequency}] must be one of 100, 250 or 500.") + + self.pair = pair + self.level = level + self.frequency = frequency + + def get_channel_name(self): + if self.level == DepthSubscription.DEFAULT_LEVEL: + level_str = "" + else: + level_str = f"{self.level}" + + if self.frequency == DepthSubscription.DEFAULT_FREQUENCY: + frequency_str = "" + else: + frequency_str = f"@{self.frequency}ms" + + return f"{map_ws_pair(self.pair)}@depth{level_str}{frequency_str}" + + +class BlvtSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair).upper()}@tokenNav" + + +class BlvtCandlestickSubscription(BinanceSubscription): + def __init__(self, pair: Pair, interval: enums.Interval, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + self.interval = interval + + def get_channel_name(self): + return f"{map_ws_pair(self.pair).upper()}@nav_Kline_{self.interval.value}" + + +class CompositeIndexSubscription(BinanceSubscription): + def __init__(self, pair: Pair, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.pair = pair + + def get_channel_name(self): + return f"{map_ws_pair(self.pair)}@compositeIndex" + + +class AccountSubscription(BinanceSubscription): + def __init__(self, callbacks: CallbacksType = None): + super().__init__(callbacks) + + self.listen_key = None + + async def initialize(self, **kwargs): + binance_client = kwargs['binance_client'] + listen_key_response = await binance_client.get_listen_key() + self.listen_key = listen_key_response["response"]["listenKey"] + LOG.debug(f'Listen key: {self.listen_key}') + + def get_channel_name(self): + return self.listen_key \ No newline at end of file diff --git a/cryptoxlib/clients/binance/BinanceWebsocket.py b/cryptoxlib/clients/binance/BinanceWebsocket.py index 2291a32..b24cd4a 100644 --- a/cryptoxlib/clients/binance/BinanceWebsocket.py +++ b/cryptoxlib/clients/binance/BinanceWebsocket.py @@ -1,82 +1,34 @@ -import json import logging -from typing import List, Callable, Any, Optional +from typing import List -from cryptoxlib.WebsocketMgr import Subscription, WebsocketMgr, WebsocketMessage, Websocket, CallbacksType +from cryptoxlib.WebsocketMgr import Subscription, CallbacksType from cryptoxlib.Pair import Pair +from cryptoxlib.clients.binance.BinanceCommonWebsocket import BinanceCommonWebsocket +from cryptoxlib.clients.binance.BinanceCommonWebsocket import BinanceSubscription from cryptoxlib.clients.binance.functions import map_ws_pair -from cryptoxlib.clients.binance.enums import CandelstickInterval +from cryptoxlib.clients.binance.enums import Interval LOG = logging.getLogger(__name__) -class BinanceWebsocket(WebsocketMgr): +class BinanceWebsocket(BinanceCommonWebsocket): WEBSOCKET_URI = "wss://stream.binance.com:9443/" - SUBSCRIPTION_ID = 0 def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, - websocket_uri: str = None, ssl_context = None) -> None: - super().__init__(websocket_uri = websocket_uri if websocket_uri is not None else BinanceWebsocket.WEBSOCKET_URI, - subscriptions = subscriptions, - builtin_ping_interval = None, - ssl_context = ssl_context, - auto_reconnect = True) - - self.api_key = api_key - self.sec_key = sec_key - self.binance_client = binance_client - - def get_websocket_uri_variable_part(self): - return "stream?streams=" + "/".join([subscription.get_channel_name() for subscription in self.subscriptions]) - - async def initialize_subscriptions(self) -> None: - for subscription in self.subscriptions: - await subscription.initialize(binance_client = self.binance_client) - - async def _subscribe(self, websocket: Websocket): - BinanceWebsocket.SUBSCRIPTION_ID += 1 - - subscription_message = { - "method": "SUBSCRIBE", - "params": [ - subscription.get_channel_name() for subscription in self.subscriptions - ], - "id": BinanceWebsocket.SUBSCRIPTION_ID - } - - LOG.debug(f"> {subscription_message}") - await websocket.send(json.dumps(subscription_message)) - - @staticmethod - def _is_subscription_confirmation(response): - if 'result' in response and response['result'] is None: - return True - else: - return False - - async def _process_message(self, websocket: Websocket, message: str) -> None: - message = json.loads(message) - - if self._is_subscription_confirmation(message): - LOG.info(f"Subscription confirmed for id: {message['id']}") - else: - # regular message - await self.publish_message(WebsocketMessage(subscription_id = message['stream'], message = message)) - - -class BinanceSubscription(Subscription): - def __init__(self, callbacks: CallbacksType = None): - super().__init__(callbacks) + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) - @staticmethod - def get_channel_name(): - pass - def get_subscription_message(self, **kwargs) -> dict: - pass +class BinanceTestnetWebsocket(BinanceCommonWebsocket): + WEBSOCKET_URI = "wss://testnet.binance.vision/" - def construct_subscription_id(self) -> Any: - return self.get_channel_name() + def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, + ssl_context = None) -> None: + super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, + sec_key = sec_key, websocket_uri = BinanceTestnetWebsocket.WEBSOCKET_URI, + ssl_context = ssl_context) class AllMarketTickersSubscription(BinanceSubscription): @@ -87,7 +39,7 @@ def get_channel_name(self): return "!ticker@arr" -class BestOrderBookTickerSubscription(BinanceSubscription): +class OrderBookTickerSubscription(BinanceSubscription): def __init__(self, callbacks: CallbacksType = None): super().__init__(callbacks) @@ -95,7 +47,7 @@ def get_channel_name(self): return "!bookTicker" -class BestOrderBookSymbolTickerSubscription(BinanceSubscription): +class OrderBookSymbolTickerSubscription(BinanceSubscription): def __init__(self, pair: Pair, callbacks: CallbacksType = None): super().__init__(callbacks) @@ -126,7 +78,7 @@ def get_channel_name(self): class CandlestickSubscription(BinanceSubscription): - def __init__(self, pair: Pair, interval: CandelstickInterval, callbacks: CallbacksType = None): + def __init__(self, pair: Pair, interval: Interval, callbacks: CallbacksType = None): super().__init__(callbacks) self.pair = pair @@ -149,15 +101,4 @@ async def initialize(self, **kwargs): LOG.debug(f'Listen key: {self.listen_key}') def get_channel_name(self): - return self.listen_key - - -class BinanceTestnetWebsocket(BinanceWebsocket): - WEBSOCKET_URI = "wss://testnet.binance.vision/" - - def __init__(self, subscriptions: List[Subscription], binance_client, api_key: str = None, sec_key: str = None, - ssl_context = None) -> None: - super().__init__(subscriptions = subscriptions, binance_client = binance_client, api_key = api_key, - sec_key = sec_key, websocket_uri = BinanceTestnetWebsocket.WEBSOCKET_URI, - ssl_context = ssl_context) - + return self.listen_key \ No newline at end of file diff --git a/cryptoxlib/clients/binance/enums.py b/cryptoxlib/clients/binance/enums.py index ff1c0f9..b789221 100644 --- a/cryptoxlib/clients/binance/enums.py +++ b/cryptoxlib/clients/binance/enums.py @@ -14,6 +14,40 @@ class OrderType(enum.Enum): TAKE_PROFIT = 'TAKE_PROFIT' TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' LIMIT_MAKER = 'LIMIT_MAKER' + STOP = 'STOP' + STOP_MARKET = 'STOP_MARKET' + TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET' + TRAILING_STOP_MARKET = 'TRAILING_STOP_MARKET' + + +class PositionSide(enum.Enum): + BOTH = 'BOTH' + LONG = 'LONG' + SHORT = 'SHORT' + + +class WorkingType(enum.Enum): + MARK_PRICE = 'MARK_PRICE' + CONTRACT_PRICE = 'CONTRACT_PRICE' + + +class MarginType(enum.Enum): + ISOLATED = 'ISOLATED' + CROSSED = 'CROSSED' + + +class IncomeType(enum.Enum): + TRANSFER = "TRANSFER" + WELCOME_BONUS = "WELCOME_BONUS" + REALIZED_PNL = "REALIZED_PNL" + FUNDING_FEE = "FUNDING_FEE" + COMMISSION = "COMMISSION" + INSURANCE_CLEAR = "INSURANCE_CLEAR" + + +class AutoCloseType(enum.Enum): + LIQUIDATION = 'LIQUIDATION' + ADL = 'ADL' class DepthLimit(enum.Enum): @@ -27,7 +61,7 @@ class DepthLimit(enum.Enum): L_5000 = '5000' -class CandelstickInterval(enum.Enum): +class Interval(enum.Enum): I_1MIN = '1m' I_3MIN = '3m' I_5MIN = '5m' @@ -45,6 +79,14 @@ class CandelstickInterval(enum.Enum): I_1MONTH = '1M' +class ContractType(enum.Enum): + PERPETUAL = 'PERPETUAL' + CURRENT_MONTH = 'CURRENT_MONTH' + NEXT_MONTH = 'NEXT_MONTH' + CURRENT_QUARTER = 'CURRENT_QUARTER' + NEXT_QUARTER = 'NEXT_QUARTER' + + class OrderResponseType(enum.Enum): ACT = "ACK" RESULT = "RESULT" @@ -55,10 +97,32 @@ class TimeInForce(enum.Enum): GOOD_TILL_CANCELLED = "GTC" IMMEDIATE_OR_CANCELLED = "IOC" FILL_OR_KILL = "FOK" + GOOD_TILL_CROSSING = 'GTX' class APICluster(enum.Enum): CLUSTER_DEFAULT = "api" CLUSTER_1 = "api1" CLUSTER_2 = "api2" - CLUSTER_3 = "api3" \ No newline at end of file + CLUSTER_3 = "api3" + + +class CrossMarginTransferType(enum.Enum): + TO_CROSS_MARGIN_ACCOUNT = 1 + TO_MAIN_ACCOUNT = 2 + + +class SideEffectType(enum.Enum): + NO_SIDE_EFFECT = "NO_SIDE_EFFECT" + MARGIN_BUY = "MARGIN_BUY" + AUTO_REPAY = "AUTO_REPAY" + + +class TransferType(enum.Enum): + ROLL_IN = "ROLL_IN" + ROLL_OUT = "ROLL_OUT" + + +class AccountType(enum.Enum): + ISOLATED_MARGIN = "ISOLATED_MARGIN" + SPOT = "SPOT" \ No newline at end of file diff --git a/cryptoxlib/clients/binance/exceptions.py b/cryptoxlib/clients/binance/exceptions.py index fc8fd97..d1a31db 100644 --- a/cryptoxlib/clients/binance/exceptions.py +++ b/cryptoxlib/clients/binance/exceptions.py @@ -1,5 +1,15 @@ +from typing import Optional + from cryptoxlib.exceptions import CryptoXLibException class BinanceException(CryptoXLibException): - pass \ No newline at end of file + pass + + +class BinanceRestException(BinanceException): + def __init__(self, status_code: int, body: Optional[dict]): + super().__init__(f"Rest API exception: status [{status_code}], response [{body}]") + + self.status_code = status_code + self.body = body \ No newline at end of file diff --git a/cryptoxlib/clients/bitforex/BitforexClient.py b/cryptoxlib/clients/bitforex/BitforexClient.py index 4932c8b..b49a463 100644 --- a/cryptoxlib/clients/bitforex/BitforexClient.py +++ b/cryptoxlib/clients/bitforex/BitforexClient.py @@ -73,7 +73,7 @@ async def get_ticker(self, pair: Pair) -> dict: return await self._create_get("market/ticker", params = params, signed = True) - async def get_candlesticks(self, pair: Pair, interval: enums.CandelstickInterval, size: str = None) -> dict: + async def get_candlesticks(self, pair: Pair, interval: enums.CandlestickInterval, size: str = None) -> dict: params = CryptoXLibClient._clean_request_params({ "symbol": map_pair(pair), "ktype": interval.value, diff --git a/cryptoxlib/clients/bitforex/BitforexWebsocket.py b/cryptoxlib/clients/bitforex/BitforexWebsocket.py index a80bf0c..42f0436 100644 --- a/cryptoxlib/clients/bitforex/BitforexWebsocket.py +++ b/cryptoxlib/clients/bitforex/BitforexWebsocket.py @@ -114,7 +114,7 @@ def get_params(self): class TickerSubscription(BitforexSubscription): - def __init__(self, pair: Pair, size: str, interval: enums.CandelstickInterval, callbacks: List[Callable[[dict], Any]] = None): + def __init__(self, pair: Pair, size: str, interval: enums.CandlestickInterval, callbacks: List[Callable[[dict], Any]] = None): super().__init__(callbacks) self.pair = pair diff --git a/cryptoxlib/clients/bitforex/enums.py b/cryptoxlib/clients/bitforex/enums.py index 44820c0..00598c9 100644 --- a/cryptoxlib/clients/bitforex/enums.py +++ b/cryptoxlib/clients/bitforex/enums.py @@ -11,7 +11,7 @@ class OrderState(enum.Enum): COMPLETE = "1" -class CandelstickInterval(enum.Enum): +class CandlestickInterval(enum.Enum): I_1MIN = '1min' I_5MIN = '5min' I_15MIN = '15min' diff --git a/cryptoxlib/clients/bitvavo/BitvavoWebsocket.py b/cryptoxlib/clients/bitvavo/BitvavoWebsocket.py index f79741f..a621333 100644 --- a/cryptoxlib/clients/bitvavo/BitvavoWebsocket.py +++ b/cryptoxlib/clients/bitvavo/BitvavoWebsocket.py @@ -213,7 +213,7 @@ def get_subscription_message(self, **kwargs) -> dict: class CandlesticksSubscription(BitvavoSubscription): - def __init__(self, pairs : List[Pair], intervals: List[enums.CandelstickInterval], callbacks : List[Callable[[dict], Any]] = None): + def __init__(self, pairs : List[Pair], intervals: List[enums.CandlestickInterval], callbacks : List[Callable[[dict], Any]] = None): super().__init__(callbacks) self.pairs = pairs diff --git a/cryptoxlib/clients/bitvavo/enums.py b/cryptoxlib/clients/bitvavo/enums.py index a6f0ccf..f0e602c 100644 --- a/cryptoxlib/clients/bitvavo/enums.py +++ b/cryptoxlib/clients/bitvavo/enums.py @@ -24,7 +24,7 @@ class SelfTradePrevention(enum.Enum): CANCEL_BOTH = "cancelBoth" -class CandelstickInterval(enum.Enum): +class CandlestickInterval(enum.Enum): I_1MIN = '1m' I_5MIN = '5m' I_15MIN = '15m' diff --git a/examples/binance_margin_rest_api.py b/examples/binance_margin_rest_api.py new file mode 100644 index 0000000..895573c --- /dev/null +++ b/examples/binance_margin_rest_api.py @@ -0,0 +1,37 @@ +import logging +import os + +from cryptoxlib.CryptoXLib import CryptoXLib +from cryptoxlib.clients.binance import enums +from cryptoxlib.Pair import Pair +from cryptoxlib.clients.binance.exceptions import BinanceException +from cryptoxlib.version_conversions import async_run + +LOG = logging.getLogger("cryptoxlib") +LOG.setLevel(logging.DEBUG) +LOG.addHandler(logging.StreamHandler()) + +print(f"Available loggers: {[name for name in logging.root.manager.loggerDict]}") + +async def run(): + api_key = os.environ['APIKEY'] + sec_key = os.environ['SECKEY'] + + client = CryptoXLib.create_binance_client(api_key, sec_key) + + print("All margin assets:") + await client.get_margin_all_assets() + + print("Margin pair:") + await client.get_margin_pair(Pair('BTC', 'USDT')) + + print("Margin price index:") + await client.get_margin_price_index(Pair('BTC', 'USDT')) + + print("Margin account balance:") + await client.get_margin_account() + + await client.close() + +if __name__ == "__main__": + async_run(run()) diff --git a/examples/binance_rest_api.py b/examples/binance_rest_api.py index 270a5a5..85355ac 100644 --- a/examples/binance_rest_api.py +++ b/examples/binance_rest_api.py @@ -40,8 +40,8 @@ async def run(): print("Aggregate trades:") await client.get_aggregate_trades(pair=Pair('ETH', 'BTC'), limit = 5) - print("Candelsticks:") - await client.get_candelsticks(pair=Pair('ETH', 'BTC'), interval = enums.CandelstickInterval.I_1D, limit=5) + print("Candlesticks:") + await client.get_candlesticks(pair=Pair('ETH', 'BTC'), interval = enums.Interval.I_1D, limit=5) print("Average price:") await client.get_average_price(pair = Pair('ETH', 'BTC')) @@ -53,7 +53,7 @@ async def run(): await client.get_price_ticker(pair = Pair('ETH', 'BTC')) print("Best order book ticker:") - await client.get_best_orderbook_ticker(pair = Pair('ETH', 'BTC')) + await client.get_orderbook_ticker(pair = Pair('ETH', 'BTC')) print("Create test market order:") await client.create_test_order(Pair("ETH", "BTC"), side = enums.OrderSide.BUY, type = enums.OrderType.MARKET, diff --git a/examples/binance_testnet_rest_api.py b/examples/binance_testnet_rest_api.py index 703ee53..6dc28d3 100644 --- a/examples/binance_testnet_rest_api.py +++ b/examples/binance_testnet_rest_api.py @@ -40,8 +40,8 @@ async def run(): print("Aggregate trades:") await client.get_aggregate_trades(pair=Pair('ETH', 'BTC'), limit = 5) - print("Candelsticks:") - await client.get_candelsticks(pair=Pair('ETH', 'BTC'), interval = enums.CandelstickInterval.I_1D, limit=5) + print("Candlesticks:") + await client.get_candlesticks(pair=Pair('ETH', 'BTC'), interval = enums.Interval.I_1D, limit=5) print("Average price:") await client.get_average_price(pair = Pair('ETH', 'BTC')) @@ -53,7 +53,7 @@ async def run(): await client.get_price_ticker(pair = Pair('ETH', 'BTC')) print("Best order book ticker:") - await client.get_best_orderbook_ticker(pair = Pair('ETH', 'BTC')) + await client.get_orderbook_ticker(pair = Pair('ETH', 'BTC')) print("Create market order:") await client.create_order(Pair("ETH", "BTC"), side = enums.OrderSide.BUY, type = enums.OrderType.MARKET, diff --git a/examples/binance_testnet_ws.py b/examples/binance_testnet_ws.py index 2ece489..76e2936 100644 --- a/examples/binance_testnet_ws.py +++ b/examples/binance_testnet_ws.py @@ -3,9 +3,9 @@ from datetime import datetime from cryptoxlib.CryptoXLib import CryptoXLib -from cryptoxlib.clients.binance.BinanceWebsocket import AccountSubscription, BestOrderBookTickerSubscription, \ - TradeSubscription, BestOrderBookSymbolTickerSubscription, CandlestickSubscription -from cryptoxlib.clients.binance.enums import CandelstickInterval +from cryptoxlib.clients.binance.BinanceWebsocket import AccountSubscription, OrderBookTickerSubscription, \ + TradeSubscription, OrderBookSymbolTickerSubscription, CandlestickSubscription +from cryptoxlib.clients.binance.enums import Interval from cryptoxlib.Pair import Pair from cryptoxlib.version_conversions import async_run @@ -41,10 +41,10 @@ async def run(): # Bundle several subscriptions into a single websocket client.compose_subscriptions([ - BestOrderBookTickerSubscription(callbacks = [orderbook_ticker_update]), - BestOrderBookSymbolTickerSubscription(pair = Pair("BTC", "USDT"), callbacks = [orderbook_ticker_update]), + OrderBookTickerSubscription(callbacks = [orderbook_ticker_update]), + OrderBookSymbolTickerSubscription(pair = Pair("BTC", "USDT"), callbacks = [orderbook_ticker_update]), TradeSubscription(pair = Pair('ETH', 'BTC'), callbacks = [trade_update]), - CandlestickSubscription(Pair('BTC', 'USDT'), CandelstickInterval.I_1MIN, callbacks = [candlestick_update]) + CandlestickSubscription(Pair('BTC', 'USDT'), Interval.I_1MIN, callbacks = [candlestick_update]) ]) # Bundle another subscriptions into a separate websocket diff --git a/examples/binance_usds_m_futures_rest_api.py b/examples/binance_usds_m_futures_rest_api.py new file mode 100644 index 0000000..ec294dc --- /dev/null +++ b/examples/binance_usds_m_futures_rest_api.py @@ -0,0 +1,77 @@ +import logging +import os + +from cryptoxlib.CryptoXLib import CryptoXLib +from cryptoxlib.clients.binance import enums +from cryptoxlib.Pair import Pair +from cryptoxlib.clients.binance.exceptions import BinanceException +from cryptoxlib.version_conversions import async_run + +LOG = logging.getLogger("cryptoxlib") +LOG.setLevel(logging.DEBUG) +LOG.addHandler(logging.StreamHandler()) + +print(f"Available loggers: {[name for name in logging.root.manager.loggerDict]}") + +async def run(): + api_key = os.environ['APIKEY'] + sec_key = os.environ['SECKEY'] + + client = CryptoXLib.create_binance_usds_m_futures_client(api_key, sec_key) + + print("Ping:") + await client.ping() + + print("Server time:") + await client.get_time() + + print("Exchange info:") + await client.get_exchange_info() + + print("Order book:") + await client.get_orderbook(pair = Pair('BTC', 'USDT'), limit = enums.DepthLimit.L_5) + + print("Trades:") + await client.get_trades(pair=Pair('BTC', 'USDT'), limit = 5) + + print("Historical trades:") + await client.get_historical_trades(pair=Pair('BTC', 'USDT'), limit = 5) + + print("Aggregate trades:") + await client.get_aggregate_trades(pair=Pair('BTC', 'USDT'), limit = 5) + + print("Index proce candlesticks:") + await client.get_index_price_candlesticks(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1MIN) + + print("Index info:") + await client.get_index_info(pair = Pair('DEFI', 'USDT')) + + print("24hour price ticker:") + await client.get_24h_price_ticker(pair = Pair('BTC', 'USDT')) + + print("Price ticker:") + await client.get_price_ticker(pair = Pair('BTC', 'USDT')) + + print("Best order book ticker:") + await client.get_orderbook_ticker(pair = Pair('BTC', 'USDT')) + + print("Create limit order:") + try: + await client.create_order(Pair("BTC", "USDT"), side = enums.OrderSide.BUY, type = enums.OrderType.LIMIT, + quantity = "1", + price = "0", + time_in_force = enums.TimeInForce.GOOD_TILL_CANCELLED, + new_order_response_type = enums.OrderResponseType.FULL) + except BinanceException as e: + print(e) + + print("Account:") + await client.get_account(recv_window_ms = 5000) + + print("Account trades:") + await client.get_account_trades(pair = Pair('BTC', 'USDT')) + + await client.close() + +if __name__ == "__main__": + async_run(run()) diff --git a/examples/binance_usds_m_futures_ws.py b/examples/binance_usds_m_futures_ws.py new file mode 100644 index 0000000..5501355 --- /dev/null +++ b/examples/binance_usds_m_futures_ws.py @@ -0,0 +1,61 @@ +import logging +import os +from datetime import datetime + +from cryptoxlib.CryptoXLib import CryptoXLib +from cryptoxlib.clients.binance.BinanceFuturesWebsocket import AccountSubscription, OrderBookTickerSubscription, \ + OrderBookSymbolTickerSubscription, CandlestickSubscription, CompositeIndexSubscription +from cryptoxlib.clients.binance.enums import Interval +from cryptoxlib.Pair import Pair +from cryptoxlib.version_conversions import async_run + +LOG = logging.getLogger("cryptoxlib") +LOG.setLevel(logging.DEBUG) +LOG.addHandler(logging.StreamHandler()) + +print(f"Available loggers: {[name for name in logging.root.manager.loggerDict]}\n") + +async def account_update(response : dict) -> None: + print(f"Callback account_update: [{response}]") + +async def order_book_update(response : dict) -> None: + print(f"Callback order_book_update: [{response}]") + +async def candlestick_update(response : dict) -> None: + print(f"Callback candlestick_update: [{response}]") + +async def trade_update(response : dict) -> None: + local_timestamp_ms = int(datetime.now().timestamp() * 1000) + server_timestamp_ms = response['data']['E'] + print(f"Callback trade_update: trade update timestamp diff [ms]:" + f" {local_timestamp_ms - server_timestamp_ms}") + +async def orderbook_ticker_update(response : dict) -> None: + print(f"Callback orderbook_ticker_update: [{response}]") + +async def run(): + api_key = os.environ['APIKEY'] + sec_key = os.environ['SECKEY'] + + client = CryptoXLib.create_binance_usds_m_futures_client(api_key, sec_key) + + # Bundle several subscriptions into a single websocket + client.compose_subscriptions([ + OrderBookTickerSubscription(callbacks = [orderbook_ticker_update]), + OrderBookSymbolTickerSubscription(pair = Pair("BTC", "USDT"), callbacks = [orderbook_ticker_update]), + CandlestickSubscription(Pair('BTC', 'USDT'), Interval.I_1MIN, callbacks = [candlestick_update]) + ]) + + # Bundle another subscriptions into a separate websocket + client.compose_subscriptions([ + AccountSubscription(callbacks = [account_update]), + CompositeIndexSubscription(pair = Pair('DEFI', 'USDT')) + ]) + + # Execute all websockets asynchronously + await client.start_websockets() + + await client.close() + +if __name__ == "__main__": + async_run(run()) diff --git a/examples/binance_ws.py b/examples/binance_ws.py index aa27203..10432ea 100644 --- a/examples/binance_ws.py +++ b/examples/binance_ws.py @@ -3,9 +3,9 @@ from datetime import datetime from cryptoxlib.CryptoXLib import CryptoXLib -from cryptoxlib.clients.binance.BinanceWebsocket import AccountSubscription, BestOrderBookTickerSubscription, \ - TradeSubscription, BestOrderBookSymbolTickerSubscription, CandlestickSubscription -from cryptoxlib.clients.binance.enums import CandelstickInterval +from cryptoxlib.clients.binance.BinanceWebsocket import AccountSubscription, OrderBookTickerSubscription, \ + TradeSubscription, OrderBookSymbolTickerSubscription, CandlestickSubscription +from cryptoxlib.clients.binance.enums import Interval from cryptoxlib.Pair import Pair from cryptoxlib.version_conversions import async_run @@ -41,10 +41,10 @@ async def run(): # Bundle several subscriptions into a single websocket client.compose_subscriptions([ - BestOrderBookTickerSubscription(callbacks = [orderbook_ticker_update]), - BestOrderBookSymbolTickerSubscription(pair = Pair("BTC", "USDT"), callbacks = [orderbook_ticker_update]), + OrderBookTickerSubscription(callbacks = [orderbook_ticker_update]), + OrderBookSymbolTickerSubscription(pair = Pair("BTC", "USDT"), callbacks = [orderbook_ticker_update]), TradeSubscription(pair = Pair('ETH', 'BTC'), callbacks = [trade_update]), - CandlestickSubscription(Pair('BTC', 'USDT'), CandelstickInterval.I_1MIN, callbacks = [candlestick_update]) + CandlestickSubscription(Pair('BTC', 'USDT'), Interval.I_1MIN, callbacks = [candlestick_update]) ]) # Bundle another subscriptions into a separate websocket diff --git a/examples/bitforex_rest_api.py b/examples/bitforex_rest_api.py index d6314f0..239ddaa 100644 --- a/examples/bitforex_rest_api.py +++ b/examples/bitforex_rest_api.py @@ -40,8 +40,8 @@ async def run(): print("Trades:") await bitforex.get_trades(pair = Pair('ETH', 'BTC'), size = "1") - print("Candelsticks:") - await bitforex.get_candlesticks(pair = Pair('ETH', 'BTC'), interval = enums.CandelstickInterval.I_1W, size = "5") + print("Candlesticks:") + await bitforex.get_candlesticks(pair = Pair('ETH', 'BTC'), interval = enums.CandlestickInterval.I_1W, size = "5") print("Create order:") try: diff --git a/examples/bitforex_ws.py b/examples/bitforex_ws.py index bb65176..229e54c 100644 --- a/examples/bitforex_ws.py +++ b/examples/bitforex_ws.py @@ -52,7 +52,7 @@ async def run(): # Bundle subscriptions into a separate websocket bitforex.compose_subscriptions([ - TickerSubscription(pair = Pair('BTC', 'USDT'), size = "2", interval = enums.CandelstickInterval.I_1MIN, + TickerSubscription(pair = Pair('BTC', 'USDT'), size = "2", interval = enums.CandlestickInterval.I_1MIN, callbacks = [ticker_update]), Ticker24hSubscription(pair = Pair('BTC', 'USDT'), callbacks = [ticker24_update]) ]) diff --git a/examples/bitvavo_ws.py b/examples/bitvavo_ws.py index a36c9f2..4cfa8ef 100644 --- a/examples/bitvavo_ws.py +++ b/examples/bitvavo_ws.py @@ -46,7 +46,7 @@ async def run(): # Bundle another subscriptions into a separate websocket client.compose_subscriptions([ - CandlesticksSubscription(pairs = [Pair("BTC", "EUR")], intervals = [enums.CandelstickInterval.I_1MIN]) + CandlesticksSubscription(pairs = [Pair("BTC", "EUR")], intervals = [enums.CandlestickInterval.I_1MIN]) ]) # Execute all websockets asynchronously diff --git a/setup.py b/setup.py index 2f3bce7..77e6df3 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setuptools.setup( name="cryptoxlib-aio", - version="3.10.0", + version="4.0.0", author="nardew", author_email="cryptoxlib.aio@gmail.com", description="Cryptoexchange asynchronous python client", diff --git a/tests/e2e/CryptoXLibTest.py b/tests/e2e/CryptoXLibTest.py index ffe28cb..05ac98e 100644 --- a/tests/e2e/CryptoXLibTest.py +++ b/tests/e2e/CryptoXLibTest.py @@ -56,6 +56,7 @@ def check_target_reached(self): if target_reached: raise CryptoXLibWsSuccessException("Websocket message count reached successfully.") + class CryptoXLibTest(aiounittest.AsyncTestCase): print_logs: bool = True log_level: int = logging.DEBUG @@ -98,11 +99,11 @@ def get_event_loop(self): else: return self.loop - async def assertWsMessageCount(self, ws_message_counter: WsMessageCounter, timeout: float = 10.0): + async def assertWsMessageCount(self, ws_message_counter: WsMessageCounter, timeout: float = 25.0): try: await asyncio.wait_for(self.client.start_websockets(), timeout = timeout) except CryptoXLibWsSuccessException as e: - LOG.info(f"Exception: {e}") + LOG.info(f"SUCCESS exception: {e}") return True except asyncio.TimeoutError: msg_string = '' diff --git a/tests/e2e/binance.py b/tests/e2e/binance.py index cca2f8c..a91bd2e 100644 --- a/tests/e2e/binance.py +++ b/tests/e2e/binance.py @@ -14,6 +14,26 @@ sec_key = os.environ['BINANCESECKEY'] +class BinanceRestApi(CryptoXLibTest): + @classmethod + def initialize(cls) -> None: + cls.print_logs = True + cls.log_level = logging.DEBUG + + async def init_test(self): + self.client = CryptoXLib.create_binance_client(api_key, sec_key) + + async def clean_test(self): + await self.client.close() + + def check_positive_response(self, response): + return str(response['status_code'])[0] == '2' + + async def test_listen_key(self): + response = await self.client.get_listen_key() + self.assertTrue(self.check_positive_response(response)) + + class BinanceWs(CryptoXLibTest): @classmethod def initialize(cls) -> None: @@ -27,7 +47,7 @@ async def init_test(self): async def test_candlesticks_subscription(self): message_counter = WsMessageCounter() self.client.compose_subscriptions([ - CandlestickSubscription(Pair("BTC", "USDT"), enums.CandelstickInterval.I_1MIN, + CandlestickSubscription(Pair("BTC", "USDT"), enums.Interval.I_1MIN, callbacks = [message_counter.generate_callback(1)]) ]) diff --git a/tests/e2e/binance_usds_m_futures.py b/tests/e2e/binance_usds_m_futures.py new file mode 100644 index 0000000..01f6036 --- /dev/null +++ b/tests/e2e/binance_usds_m_futures.py @@ -0,0 +1,504 @@ +import unittest +import os +import logging + +from cryptoxlib.CryptoXLib import CryptoXLib +from cryptoxlib.clients.binance import enums +from cryptoxlib.clients.binance.BinanceFuturesWebsocket import AggregateTradeSubscription, MarkPriceSubscription, \ + MarkPriceAllSubscription, AllMarketLiquidationOrdersSubscription, AllMarketMiniTickersSubscription, \ + AllMarketTickersSubscription, MiniTickerSubscription, OrderBookTickerSubscription, \ + OrderBookSymbolTickerSubscription, LiquidationOrdersSubscription, BlvtCandlestickSubscription, \ + BlvtSubscription, CompositeIndexSubscription, DepthSubscription, CandlestickSubscription, \ + ContContractCandlestickSubscription, TickerSubscription, AccountSubscription +from cryptoxlib.clients.binance.exceptions import BinanceRestException +from cryptoxlib.Pair import Pair + +from CryptoXLibTest import CryptoXLibTest, WsMessageCounter + +api_key = os.environ['BINANCEAPIKEY'] +sec_key = os.environ['BINANCESECKEY'] +test_api_key = os.environ['BINANCEFUTURESTESTAPIKEY'] +test_sec_key = os.environ['BINANCEFUTURESTESTSECKEY'] + + +class BinanceUSDSMFuturesMarketRestApi(CryptoXLibTest): + @classmethod + def initialize(cls) -> None: + cls.print_logs = True + cls.log_level = logging.DEBUG + + async def init_test(self): + self.client = CryptoXLib.create_binance_usds_m_futures_client(api_key, sec_key) + + async def clean_test(self): + await self.client.close() + + def check_positive_response(self, response): + return str(response['status_code'])[0] == '2' + + async def test_get_ping(self): + response = await self.client.ping() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_time(self): + response = await self.client.get_time() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_exchange_info(self): + response = await self.client.get_exchange_info() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_order_book(self): + response = await self.client.get_orderbook(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_trades(self): + response = await self.client.get_trades(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_historical_trades(self): + response = await self.client.get_historical_trades(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_aggregate_trades(self): + response = await self.client.get_aggregate_trades(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_candlesticks(self): + response = await self.client.get_candlesticks(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1MIN) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_cont_contract_candlesticks(self): + response = await self.client.get_cont_contract_candlesticks(pair = Pair('BTC', 'USDT'), + interval = enums.Interval.I_1MIN, + contract_type = enums.ContractType.PERPETUAL) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_index_price_candlesticks(self): + response = await self.client.get_index_price_candlesticks(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1MIN) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_mark_price_candlesticks(self): + response = await self.client.get_mark_price_candlesticks(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1MIN) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_mark_price(self): + response = await self.client.get_mark_price(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_fund_rate_history(self): + response = await self.client.get_fund_rate_history(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_24h_price_ticker(self): + response = await self.client.get_24h_price_ticker(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_price_ticker(self): + response = await self.client.get_price_ticker(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_best_orderbook_ticker(self): + response = await self.client.get_orderbook_ticker(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_all_liquidation_orders(self): + response = await self.client.get_all_liquidation_orders(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_open_interest(self): + response = await self.client.get_open_interest(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_open_interest_hist(self): + response = await self.client.get_open_interest_hist(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_top_long_short_account_ratio(self): + response = await self.client.get_top_long_short_account_ratio(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_top_long_short_position_ratio(self): + response = await self.client.get_top_long_short_position_ratio(pair = Pair('BTC', 'UP'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_global_long_short_account_ratio(self): + response = await self.client.get_global_long_short_account_ratio(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_taker_long_short_ratio(self): + response = await self.client.get_taker_long_short_ratio(pair = Pair('BTC', 'USDT'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_blvt_candlesticks(self): + response = await self.client.get_blvt_candlesticks(pair = Pair('BTC', 'UP'), interval = enums.Interval.I_1D) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_index_info(self): + response = await self.client.get_index_info(pair = Pair('DEFI', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_listen_key(self): + response = await self.client.get_listen_key() + self.assertTrue(self.check_positive_response(response)) + + response = await self.client.keep_alive_listen_key(response['response']['listenKey']) + self.assertTrue(self.check_positive_response(response)) + + +class BinanceUSDSMFuturesAccountRestApi(CryptoXLibTest): + @classmethod + def initialize(cls) -> None: + cls.print_logs = True + cls.log_level = logging.DEBUG + + async def init_test(self): + self.client = CryptoXLib.create_binance_usds_m_futures_testnet_client(test_api_key, test_sec_key) + + async def clean_test(self): + await self.client.close() + + def check_positive_response(self, response): + return str(response['status_code'])[0] == '2' + + def check_error_code(self, e: BinanceRestException, status: str, code: str): + return str(e.status_code) == status and str(e.body['code']) == code + + async def test_get_ping(self): + response = await self.client.ping() + self.assertTrue(self.check_positive_response(response)) + + async def test_change_position_type(self): + # make sure some position exists in order for the change of position to fail + await self.client.create_order(pair = Pair('XRP', 'USDT'), side = enums.OrderSide.BUY, + type = enums.OrderType.MARKET, quantity = '10') + + with self.assertRaises(BinanceRestException) as cm: + current_type = await self.client.get_position_type() + response = await self.client.change_position_type(not current_type['response']['dualSidePosition']) + self.assertTrue(self.check_positive_response(response)) + e = cm.exception + + self.assertTrue(self.check_error_code(e, '400', '-4068')) + + async def test_get_position_type(self): + response = await self.client.get_position_type() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_all_orders(self): + response = await self.client.get_all_orders(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + # market order + async def test_create_order1(self): + current_position_type = await self.client.get_position_type() + if current_position_type['response']['dualSidePosition']: + await self.client.change_position_type(False) + + response = await self.client.create_order(pair = Pair('BTC', 'USDT'), side = enums.OrderSide.BUY, + type = enums.OrderType.MARKET, quantity = '0.001') + self.assertTrue(self.check_positive_response(response)) + + # limit order + async def test_create_order2(self): + current_position_type = await self.client.get_position_type() + if current_position_type['response']['dualSidePosition']: + await self.client.change_position_type(False) + + response = await self.client.create_order(pair = Pair('BTC', 'USDT'), side = enums.OrderSide.BUY, + type = enums.OrderType.LIMIT, quantity = '0.1', price = '200', + time_in_force = enums.TimeInForce.GOOD_TILL_CANCELLED) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_order(self): + with self.assertRaises(BinanceRestException) as cm: + await self.client.get_order(pair = Pair('BTC', 'USDT'), order_id = 1) + e = cm.exception + + self.assertTrue(self.check_error_code(e, '400', '-2013')) + + async def test_cancel_order(self): + with self.assertRaises(BinanceRestException) as cm: + await self.client.cancel_order(pair = Pair('BTC', 'USDT'), order_id = 1) + e = cm.exception + + self.assertTrue(self.check_error_code(e, '400', '-2011')) + + async def test_cancel_all_orders(self): + response = await self.client.cancel_all_orders(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + @unittest.expectedFailure + # not supported for testnet + async def test_auto_cancel_orders(self): + response = await self.client.auto_cancel_orders(pair = Pair('BTC', 'USDT'), countdown_time_ms = 0) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_open_order(self): + with self.assertRaises(BinanceRestException) as cm: + await self.client.get_open_order(pair = Pair('BTC', 'USDT'), order_id = 1) + e = cm.exception + + self.assertTrue(self.check_error_code(e, '400', '-2013')) + + async def test_get_all_open_orders(self): + response = await self.client.get_all_open_orders(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_balance(self): + response = await self.client.get_balance() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_account(self): + response = await self.client.get_account() + self.assertTrue(self.check_positive_response(response)) + + async def test_change_init_leverage(self): + response = await self.client.change_init_leverage(pair = Pair('BTC', 'USDT'), leverage = 1) + self.assertTrue(self.check_positive_response(response)) + + async def test_change_margin_type(self): + position = await self.client.get_position(pair = Pair('ETH', 'USDT')) + margin_type = position['response'][0]['marginType'] + if margin_type == 'cross': + new_margin_type = enums.MarginType.ISOLATED + else: + new_margin_type = enums.MarginType.CROSSED + + response = await self.client.change_margin_type(pair = Pair('ETH', 'USDT'), margin_type = new_margin_type) + self.assertTrue(self.check_positive_response(response)) + + async def test_update_isolated_position_margin(self): + position = await self.client.get_position(pair = Pair('BNB', 'USDT')) + margin_type = position['response'][0]['marginType'] + if margin_type == 'cross': + await self.client.change_margin_type(pair = Pair('BNB', 'USDT'), margin_type = enums.MarginType.ISOLATED) + + await self.client.create_order(pair = Pair('BNB', 'USDT'), side = enums.OrderSide.BUY, + type = enums.OrderType.MARKET, quantity = '0.1') + + response = await self.client.update_isolated_position_margin(pair = Pair('BNB', 'USDT'), quantity = '0.001', + type = 2) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_position_margin_change_history(self): + response = await self.client.get_position_margin_change_history(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_position(self): + response = await self.client.get_position() + self.assertTrue(self.check_positive_response(response)) + + async def test_get_position2(self): + response = await self.client.get_position(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_account_trades(self): + response = await self.client.get_account_trades(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_income_history(self): + response = await self.client.get_income_history(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_notional_and_leverage_brackets(self): + response = await self.client.get_notional_and_leverage_brackets(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_adl_quantile(self): + response = await self.client.get_adl_quantile(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_force_orders(self): + response = await self.client.get_force_orders(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_api_trading_status(self): + response = await self.client.get_api_trading_status(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + async def test_get_commission_rate(self): + response = await self.client.get_commission_rate(pair = Pair('BTC', 'USDT')) + self.assertTrue(self.check_positive_response(response)) + + +class BinanceUSDSMFuturesMarketWs(CryptoXLibTest): + @classmethod + def initialize(cls) -> None: + cls.print_logs = True + cls.log_level = logging.DEBUG + + async def init_test(self): + self.client = CryptoXLib.create_binance_usds_m_futures_client(api_key, sec_key) + + #async def clean_test(self): + # await self.client.close() + + async def test_aggregate_trade(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + AggregateTradeSubscription(Pair("BTC", "USDT"), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_mark_price(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + MarkPriceSubscription(Pair("BTC", "USDT"), True, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_mark_price_all(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + MarkPriceAllSubscription(True, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_candlestick(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + CandlestickSubscription(Pair("BTC", "USDT"), enums.Interval.I_1MIN, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_cont_contract_candlestick(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + ContContractCandlestickSubscription(Pair("BTC", "USDT"), enums.Interval.I_1MIN, enums.ContractType.PERPETUAL, + callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_all_market_mini_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + AllMarketMiniTickersSubscription(callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_mini_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + MiniTickerSubscription(Pair('BTC', 'USDT'), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_all_market_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + AllMarketTickersSubscription(callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + TickerSubscription(Pair('BTC', 'USDT'), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_best_orderbook_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + OrderBookTickerSubscription(callbacks = [message_counter.generate_callback(10)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_best_orderbook_symbol_ticker(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + OrderBookSymbolTickerSubscription(Pair('BTC', 'USDT'), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + # fails since normally there are no liquidation orders + async def liquidation_orders(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + LiquidationOrdersSubscription(Pair('BTC', 'USDT'), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_all_liquidation_orders(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + AllMarketLiquidationOrdersSubscription(callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_partial_detph(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + DepthSubscription(Pair('BTC', 'USDT'), 5, 100, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_partial_detph2(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + DepthSubscription(Pair('BTC', 'USDT'), 5, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_detph(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + DepthSubscription(Pair('BTC', 'USDT'), 0, 100, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_detph2(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + DepthSubscription(Pair('BTC', 'USDT'), 0, callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + async def test_blvt(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + BlvtSubscription(Pair('BTC', 'UP'), callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + # typically no data are received + async def blvt_candlesticks(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + BlvtCandlestickSubscription(Pair('BTC', 'UP'), enums.Interval.I_1MIN, + callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter, timeout = 60) + + async def test_composite_index(self): + message_counter = WsMessageCounter() + self.client.compose_subscriptions([ + CompositeIndexSubscription(Pair('DEFI', 'USDT'), + callbacks = [message_counter.generate_callback(1)]) + ]) + + await self.assertWsMessageCount(message_counter) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/e2e/bitforex.py b/tests/e2e/bitforex.py index 46a99b3..9e2940d 100644 --- a/tests/e2e/bitforex.py +++ b/tests/e2e/bitforex.py @@ -53,7 +53,7 @@ async def test_get_trades(self): self.assertTrue(self.check_positive_response(response)) async def test_get_candlesticks(self): - response = await self.client.get_candlesticks(pair = Pair('ETH', 'BTC'), interval = enums.CandelstickInterval.I_1W, size = "5") + response = await self.client.get_candlesticks(pair = Pair('ETH', 'BTC'), interval = enums.CandlestickInterval.I_1W, size = "5") self.assertTrue(self.check_positive_response(response)) async def test_create_order(self):