From 3deccc551f17ff4ee36e4de7c6bae66d934212d5 Mon Sep 17 00:00:00 2001 From: Pablo Criado-Perez Date: Wed, 20 Nov 2024 05:36:03 -0500 Subject: [PATCH] Feat: Add crudWs (#1464) * first commit * ruff check fix * ruff format fix * fix lint warnings and error * type fix for 3.7 * add testnet to test_Ws_api * change to testnet * ruff@ * return test to async in test * format with ruff * remove blank line * remove default tif * add ws_futures, refactor and add tests * lint * fix tests * ruff * add tests * remove utils * ruff format and pr comments * ruff format * fix live tests and add env vars for testnet * github action * add tox env * fix and test without tox * move tox command * fix test * fix pyright * type ignore * jump test until whitelist * remove print * add examples * improve docs * lint and format * lint and format * add tests for failed requests * fix for 3.7 * merge master * lint and format * pyright --------- Co-authored-by: carlosmiei <43336371+carlosmiei@users.noreply.github.com> --- .github/workflows/python-app.yml | 5 + README.rst | 11 +- binance/__init__.py | 17 +- binance/async_client.py | 3596 ++++++++++++++++ binance/base_client.py | 473 ++ binance/client.py | 3809 ++--------------- binance/exceptions.py | 3 +- binance/ws/constants.py | 10 + binance/{ => ws}/depthcache.py | 2 +- binance/ws/keepalive_websocket.py | 93 + binance/ws/reconnecting_websocket.py | 229 + binance/{ => ws}/streams.py | 328 +- binance/{ => ws}/threaded_stream.py | 7 +- binance/ws/websocket_api.py | 132 + docs/conf.py | 76 +- docs/websockets.rst | 23 +- examples/create_order_async.py | 2 +- examples/save_historical_data.py | 16 +- examples/websocket.py | 16 +- examples/ws_create_order.py | 27 + examples/ws_create_order_async.py | 29 + pyproject.toml | 6 +- setup.py | 63 +- test-requirements.txt | 2 - tests/conftest.py | 68 +- tests/test_async_client_ws_api.py | 76 + .../test_async_client_ws_futures_requests.py | 122 + tests/test_client.py | 168 +- tests/test_client_ws_api.py | 62 + tests/test_client_ws_futures_requests.py | 76 + tests/test_cryptography.py | 6 +- tests/test_depth_cache.py | 2 +- tests/test_get_order_book.py | 35 +- tests/test_ids.py | 18 +- tests/test_order.py | 5 + tests/test_ping.py | 21 +- tests/test_streams.py | 4 +- tests/test_ws_api.py | 153 + tests/utils.py | 16 + tox.ini | 19 +- 40 files changed, 5857 insertions(+), 3969 deletions(-) create mode 100644 binance/async_client.py create mode 100644 binance/base_client.py create mode 100644 binance/ws/constants.py rename binance/{ => ws}/depthcache.py (99%) create mode 100644 binance/ws/keepalive_websocket.py create mode 100644 binance/ws/reconnecting_websocket.py rename binance/{ => ws}/streams.py (81%) rename binance/{ => ws}/threaded_stream.py (94%) create mode 100644 binance/ws/websocket_api.py create mode 100644 examples/ws_create_order.py create mode 100644 examples/ws_create_order_async.py create mode 100644 tests/test_async_client_ws_api.py create mode 100644 tests/test_async_client_ws_futures_requests.py create mode 100644 tests/test_client_ws_api.py create mode 100644 tests/test_client_ws_futures_requests.py create mode 100644 tests/test_order.py create mode 100644 tests/test_ws_api.py create mode 100644 tests/utils.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 18c24b787..0c49f2342 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -36,6 +36,11 @@ jobs: runs-on: ubuntu-latest env: PROXY: "http://51.83.140.52:16301" + TEST_TESTNET: "true" + TEST_API_KEY: "u4L8MG2DbshTfTzkx2Xm7NfsHHigvafxeC29HrExEmah1P8JhxXkoOu6KntLICUc" + TEST_API_SECRET: "hBZEqhZUUS6YZkk7AIckjJ3iLjrgEFr5CRtFPp5gjzkrHKKC9DAv4OH25PlT6yq5" + TEST_FUTURES_API_KEY: "227719da8d8499e8d3461587d19f259c0b39c2b462a77c9b748a6119abd74401" + TEST_FUTURES_API_SECRET: "b14b935f9cfacc5dec829008733c40da0588051f29a44625c34967b45c11d73c" strategy: matrix: python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] diff --git a/README.rst b/README.rst index 8626ac3a8..6adf35d4b 100755 --- a/README.rst +++ b/README.rst @@ -68,6 +68,7 @@ Features - No need to generate timestamps yourself, the wrapper does it for you - Response exception handling - Websocket handling with reconnection and multiplexed connections +- CRUDE over websockets, create/fetch/edit through websockets for minimum latency. - Symbol Depth Cache - Historical Kline/Candle fetching function - Withdraw functionality @@ -160,6 +161,9 @@ pass `testnet=True` when creating the client. # fetch weekly klines since it listed klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + # create order through websockets + order_ws = client.ws_create_order( symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + # socket manager using threads twm = ThreadedWebsocketManager() twm.start() @@ -237,10 +241,13 @@ for more information. print(kline) # fetch 30 minute klines for the last month of 2017 - klines = client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") + klines = await client.get_historical_klines("ETHBTC", Client.KLINE_INTERVAL_30MINUTE, "1 Dec, 2017", "1 Jan, 2018") # fetch weekly klines since it listed - klines = client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + klines = await client.get_historical_klines("NEOBTC", Client.KLINE_INTERVAL_1WEEK, "1 Jan, 2017") + + # create order through websockets + order_ws = await client.ws_create_order( symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) # setup an async context the Depth Cache and exit after 5 messages async with DepthCacheManager(client, symbol='ETHBTC') as dcm_socket: diff --git a/binance/__init__.py b/binance/__init__.py index fe3d2f325..60356640d 100755 --- a/binance/__init__.py +++ b/binance/__init__.py @@ -6,7 +6,16 @@ __version__ = "1.0.22" -from binance.client import Client, AsyncClient # noqa -from binance.depthcache import DepthCacheManager, OptionsDepthCacheManager, ThreadedDepthCacheManager # noqa -from binance.streams import BinanceSocketManager, ThreadedWebsocketManager, BinanceSocketType # noqa -from binance.enums import * # noqa \ No newline at end of file +from binance.async_client import AsyncClient # noqa +from binance.client import Client # noqa +from binance.ws.depthcache import ( + DepthCacheManager, # noqa + OptionsDepthCacheManager, # noqa + ThreadedDepthCacheManager, # noqa +) +from binance.ws.streams import ( + BinanceSocketManager, # noqa + ThreadedWebsocketManager, # noqa + BinanceSocketType, # noqa +) +from binance.enums import * # noqa diff --git a/binance/async_client.py b/binance/async_client.py new file mode 100644 index 000000000..a82b9cf8f --- /dev/null +++ b/binance/async_client.py @@ -0,0 +1,3596 @@ +import asyncio +from pathlib import Path +from typing import Any, Dict, List, Optional, Union +from urllib.parse import urlencode +import time +import aiohttp + +from binance.enums import HistoricalKlinesType +from binance.exceptions import ( + BinanceAPIException, + BinanceRequestException, + NotImplementedException, +) +from binance.helpers import convert_ts_str, get_loop, interval_to_milliseconds +from .base_client import BaseClient +from .client import Client + + +class AsyncClient(BaseClient): + def __init__( + self, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, + tld: str = "com", + base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, + testnet: bool = False, + loop=None, + session_params: Optional[Dict[str, Any]] = None, + private_key: Optional[Union[str, Path]] = None, + private_key_pass: Optional[str] = None, + https_proxy: Optional[str] = None, + ): + self.https_proxy = https_proxy + self.loop = loop or get_loop() + self._session_params: Dict[str, Any] = session_params or {} + super().__init__( + api_key, + api_secret, + requests_params, + tld, + base_endpoint, + testnet, + private_key, + private_key_pass, + ) + + @classmethod + async def create( + cls, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, + tld: str = "com", + base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, + testnet: bool = False, + loop=None, + session_params: Optional[Dict[str, Any]] = None, + https_proxy: Optional[str] = None, + ): + self = cls( + api_key, + api_secret, + requests_params, + tld, + base_endpoint, + testnet, + loop, + session_params, + ) + self.https_proxy = https_proxy # move this to the constructor + + try: + await self.ping() + + # calculate timestamp offset between local and binance server + res = await self.get_server_time() + self.timestamp_offset = res["serverTime"] - int(time.time() * 1000) + + return self + except Exception: + # If ping throw an exception, the current self must be cleaned + # else, we can receive a "asyncio:Unclosed client session" + await self.close_connection() + raise + + def _init_session(self) -> aiohttp.ClientSession: + session = aiohttp.ClientSession( + loop=self.loop, headers=self._get_headers(), **self._session_params + ) + return session + + async def close_connection(self): + if self.session: + assert self.session + await self.session.close() + if self.ws_api: + await self.ws_api.close() + self._ws_api = None + + async def _request( + self, method, uri: str, signed: bool, force_params: bool = False, **kwargs + ): + kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) + + async with getattr(self.session, method)( + uri, proxy=self.https_proxy, **kwargs + ) as response: + self.response = response + return await self._handle_response(response) + + async def _handle_response(self, response: aiohttp.ClientResponse): + """Internal helper for handling API responses from the Binance server. + Raises the appropriate exceptions when necessary; otherwise, returns the + response. + """ + if not str(response.status).startswith("2"): + raise BinanceAPIException(response, response.status, await response.text()) + try: + return await response.json() + except ValueError: + txt = await response.text() + raise BinanceRequestException(f"Invalid Response: {txt}") + + async def _request_api( + self, + method, + path, + signed=False, + version=BaseClient.PUBLIC_API_VERSION, + **kwargs, + ): + uri = self._create_api_uri(path, signed, version) + return await self._request(method, uri, signed, **kwargs) + + async def _request_futures_api( + self, method, path, signed=False, version=1, **kwargs + ) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_api_uri(path, version=version) + + return await self._request(method, uri, signed, False, **kwargs) + + async def _request_futures_data_api( + self, method, path, signed=False, **kwargs + ) -> Dict: + uri = self._create_futures_data_api_uri(path) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_futures_coin_api( + self, method, path, signed=False, version=1, **kwargs + ) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_api_url(path, version=version) + + return await self._request(method, uri, signed, False, **kwargs) + + async def _request_futures_coin_data_api( + self, method, path, signed=False, version=1, **kwargs + ) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_futures_coin_data_api_url(path, version=version) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_options_api_uri(path) + + return await self._request(method, uri, signed, True, **kwargs) + + async def _request_margin_api( + self, method, path, signed=False, version=1, **kwargs + ) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_margin_api_uri(path, version) + + return await self._request(method, uri, signed, **kwargs) + + async def _request_papi_api( + self, method, path, signed=False, version=1, **kwargs + ) -> Dict: + version = self._get_version(version, **kwargs) + uri = self._create_papi_api_uri(path, version) + + return await self._request(method, uri, signed, **kwargs) + + async def _request_website(self, method, path, signed=False, **kwargs) -> Dict: + uri = self._create_website_uri(path) + return await self._request(method, uri, signed, **kwargs) + + async def _get( + self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs + ): + return await self._request_api("get", path, signed, version, **kwargs) + + async def _post( + self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs + ) -> Dict: + return await self._request_api("post", path, signed, version, **kwargs) + + async def _put( + self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs + ) -> Dict: + return await self._request_api("put", path, signed, version, **kwargs) + + async def _delete( + self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs + ) -> Dict: + return await self._request_api("delete", path, signed, version, **kwargs) + + # Exchange Endpoints + + async def get_products(self) -> Dict: + products = await self._request_website( + "get", + "bapi/asset/v2/public/asset-service/product/get-products?includeEtf=true", + ) + return products + + get_products.__doc__ = Client.get_products.__doc__ + + async def get_exchange_info(self) -> Dict: + return await self._get("exchangeInfo", version=self.PRIVATE_API_VERSION) + + get_exchange_info.__doc__ = Client.get_exchange_info.__doc__ + + async def get_symbol_info(self, symbol) -> Optional[Dict]: + res = await self.get_exchange_info() + + for item in res["symbols"]: + if item["symbol"] == symbol.upper(): + return item + + return None + + get_symbol_info.__doc__ = Client.get_symbol_info.__doc__ + + # General Endpoints + + async def ping(self) -> Dict: + return await self._get("ping", version=self.PRIVATE_API_VERSION) + + ping.__doc__ = Client.ping.__doc__ + + async def get_server_time(self) -> Dict: + return await self._get("time", version=self.PRIVATE_API_VERSION) + + get_server_time.__doc__ = Client.get_server_time.__doc__ + + # Market Data Endpoints + + async def get_all_tickers( + self, symbol: Optional[str] = None + ) -> List[Dict[str, str]]: + params = {} + if symbol: + params["symbol"] = symbol + return await self._get( + "ticker/price", version=self.PRIVATE_API_VERSION, data=params + ) + + get_all_tickers.__doc__ = Client.get_all_tickers.__doc__ + + async def get_orderbook_tickers(self, **params) -> Dict: + data = {} + if "symbol" in params: + data["symbol"] = params["symbol"] + elif "symbols" in params: + data["symbols"] = params["symbols"] + return await self._get( + "ticker/bookTicker", data=data, version=self.PRIVATE_API_VERSION + ) + + get_orderbook_tickers.__doc__ = Client.get_orderbook_tickers.__doc__ + + async def get_order_book(self, **params) -> Dict: + return await self._get("depth", data=params, version=self.PRIVATE_API_VERSION) + + get_order_book.__doc__ = Client.get_order_book.__doc__ + + async def get_recent_trades(self, **params) -> Dict: + return await self._get("trades", data=params) + + get_recent_trades.__doc__ = Client.get_recent_trades.__doc__ + + async def get_historical_trades(self, **params) -> Dict: + return await self._get( + "historicalTrades", data=params, version=self.PRIVATE_API_VERSION + ) + + get_historical_trades.__doc__ = Client.get_historical_trades.__doc__ + + async def get_aggregate_trades(self, **params) -> Dict: + return await self._get( + "aggTrades", data=params, version=self.PRIVATE_API_VERSION + ) + + get_aggregate_trades.__doc__ = Client.get_aggregate_trades.__doc__ + + async def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): + if start_str is not None and last_id is not None: + raise ValueError( + "start_time and last_id may not be simultaneously specified." + ) + + # If there's no last_id, get one. + if last_id is None: + # Without a last_id, we actually need the first trade. Normally, + # we'd get rid of it. See the next loop. + if start_str is None: + trades = await self.get_aggregate_trades(symbol=symbol, fromId=0) + else: + # The difference between startTime and endTime should be less + # or equal than an hour and the result set should contain at + # least one trade. + start_ts = convert_ts_str(start_str) + # If the resulting set is empty (i.e. no trades in that interval) + # then we just move forward hour by hour until we find at least one + # trade or reach present moment + while True: + end_ts = start_ts + (60 * 60 * 1000) + trades = await self.get_aggregate_trades( + symbol=symbol, startTime=start_ts, endTime=end_ts + ) + if len(trades) > 0: + break + # If we reach present moment and find no trades then there is + # nothing to iterate, so we're done + if end_ts > int(time.time() * 1000): + return + start_ts = end_ts + for t in trades: + yield t + last_id = trades[-1][self.AGG_ID] + + while True: + # There is no need to wait between queries, to avoid hitting the + # rate limit. We're using blocking IO, and as long as we're the + # only thread running calls like this, Binance will automatically + # add the right delay time on their end, forcing us to wait for + # data. That really simplifies this function's job. Binance is + # fucking awesome. + trades = await self.get_aggregate_trades(symbol=symbol, fromId=last_id) + # fromId=n returns a set starting with id n, but we already have + # that one. So get rid of the first item in the result set. + trades = trades[1:] + if len(trades) == 0: + return + for t in trades: + yield t + last_id = trades[-1][self.AGG_ID] + + aggregate_trade_iter.__doc__ = Client.aggregate_trade_iter.__doc__ + + async def get_klines(self, **params) -> Dict: + return await self._get("klines", data=params, version=self.PRIVATE_API_VERSION) + + get_klines.__doc__ = Client.get_klines.__doc__ + + async def _klines( + self, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, **params + ) -> Dict: + if "endTime" in params and not params["endTime"]: + del params["endTime"] + if HistoricalKlinesType.SPOT == klines_type: + return await self.get_klines(**params) + elif HistoricalKlinesType.FUTURES == klines_type: + return await self.futures_klines(**params) + elif HistoricalKlinesType.FUTURES_COIN == klines_type: + return await self.futures_coin_klines(**params) + else: + raise NotImplementedException(klines_type) + + _klines.__doc__ = Client._klines.__doc__ + + async def _get_earliest_valid_timestamp( + self, + symbol, + interval, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, + ): + kline = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=1, + startTime=0, + endTime=int(time.time() * 1000), + ) + return kline[0][0] + + _get_earliest_valid_timestamp.__doc__ = Client._get_earliest_valid_timestamp.__doc__ + + async def get_historical_klines( + self, + symbol, + interval, + start_str=None, + end_str=None, + limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, + ): + return await self._historical_klines( + symbol, + interval, + start_str, + end_str=end_str, + limit=limit, + klines_type=klines_type, + ) + + get_historical_klines.__doc__ = Client.get_historical_klines.__doc__ + + async def _historical_klines( + self, + symbol, + interval, + start_str=None, + end_str=None, + limit=None, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, + ): + initial_limit_set = True + if limit is None: + limit = 1000 + initial_limit_set = False + + # init our list + output_data = [] + + # convert interval to useful value in seconds + timeframe = interval_to_milliseconds(interval) + + # establish first available start timestamp + start_ts = convert_ts_str(start_str) + if start_ts is not None: + first_valid_ts = await self._get_earliest_valid_timestamp( + symbol, interval, klines_type + ) + start_ts = max(start_ts, first_valid_ts) + + # if an end time was passed convert it + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return output_data + + idx = 0 + while True: + # fetch the klines from start_ts up to max 500 entries or the end_ts if set + temp_data = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=limit, + startTime=start_ts, + endTime=end_ts, + ) + + # append this loops data to our output data + if temp_data: + output_data += temp_data + + # check if output_data is greater than limit and truncate if needed and break loop + if initial_limit_set and len(output_data) > limit: + output_data = output_data[:limit] + break + + # handle the case where exactly the limit amount of data was returned last loop + # or check if we received less than the required limit and exit the loop + if not len(temp_data) or len(temp_data) < limit: + # exit the while loop + break + + # set our start timestamp using the last value in the array + # and increment next call by our timeframe + start_ts = temp_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break + + # sleep after every 3rd call to be kind to the API + idx += 1 + if idx % 3 == 0: + await asyncio.sleep(1) + + return output_data + + _historical_klines.__doc__ = Client._historical_klines.__doc__ + + async def get_historical_klines_generator( + self, + symbol, + interval, + start_str=None, + end_str=None, + limit=1000, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, + ): + return self._historical_klines_generator( + symbol, + interval, + start_str, + end_str=end_str, + limit=limit, + klines_type=klines_type, + ) + + get_historical_klines_generator.__doc__ = ( + Client.get_historical_klines_generator.__doc__ + ) + + async def _historical_klines_generator( + self, + symbol, + interval, + start_str=None, + end_str=None, + limit=1000, + klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, + ): + # convert interval to useful value in seconds + timeframe = interval_to_milliseconds(interval) + + # if a start time was passed convert it + start_ts = convert_ts_str(start_str) + + # establish first available start timestamp + if start_ts is not None: + first_valid_ts = await self._get_earliest_valid_timestamp( + symbol, interval, klines_type + ) + start_ts = max(start_ts, first_valid_ts) + + # if an end time was passed convert it + end_ts = convert_ts_str(end_str) + if end_ts and start_ts and end_ts <= start_ts: + return + + idx = 0 + while True: + # fetch the klines from start_ts up to max 500 entries or the end_ts if set + output_data = await self._klines( + klines_type=klines_type, + symbol=symbol, + interval=interval, + limit=limit, + startTime=start_ts, + endTime=end_ts, + ) + + # yield data + if output_data: + for o in output_data: + yield o + + # handle the case where exactly the limit amount of data was returned last loop + # check if we received less than the required limit and exit the loop + if not len(output_data) or len(output_data) < limit: + # exit the while loop + break + + # increment next call by our timeframe + start_ts = output_data[-1][0] + timeframe + + # exit loop if we reached end_ts before reaching klines + if end_ts and start_ts >= end_ts: + break + + # sleep after every 3rd call to be kind to the API + idx += 1 + if idx % 3 == 0: + await asyncio.sleep(1) + + _historical_klines_generator.__doc__ = Client._historical_klines_generator.__doc__ + + async def get_avg_price(self, **params): + return await self._get( + "avgPrice", data=params, version=self.PRIVATE_API_VERSION + ) + + get_avg_price.__doc__ = Client.get_avg_price.__doc__ + + async def get_ticker(self, **params): + return await self._get( + "ticker/24hr", data=params, version=self.PRIVATE_API_VERSION + ) + + get_ticker.__doc__ = Client.get_ticker.__doc__ + + async def get_symbol_ticker(self, **params): + return await self._get( + "ticker/price", data=params, version=self.PRIVATE_API_VERSION + ) + + get_symbol_ticker.__doc__ = Client.get_symbol_ticker.__doc__ + + async def get_symbol_ticker_window(self, **params): + return await self._get("ticker", data=params, version=self.PRIVATE_API_VERSION) + + get_symbol_ticker_window.__doc__ = Client.get_symbol_ticker_window.__doc__ + + async def get_orderbook_ticker(self, **params): + return await self._get( + "ticker/bookTicker", data=params, version=self.PRIVATE_API_VERSION + ) + + get_orderbook_ticker.__doc__ = Client.get_orderbook_ticker.__doc__ + + # Account Endpoints + + async def create_order(self, **params): + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._post("order", True, data=params) + + create_order.__doc__ = Client.create_order.__doc__ + + async def order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + params.update({"type": self.ORDER_TYPE_LIMIT, "timeInForce": timeInForce}) + return await self.create_order(**params) + + order_limit.__doc__ = Client.order_limit.__doc__ + + async def order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + params.update({ + "side": self.SIDE_BUY, + }) + return await self.order_limit(timeInForce=timeInForce, **params) + + order_limit_buy.__doc__ = Client.order_limit_buy.__doc__ + + async def order_limit_sell( + self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params + ): + params.update({"side": self.SIDE_SELL}) + return await self.order_limit(timeInForce=timeInForce, **params) + + order_limit_sell.__doc__ = Client.order_limit_sell.__doc__ + + async def order_market(self, **params): + params.update({"type": self.ORDER_TYPE_MARKET}) + return await self.create_order(**params) + + order_market.__doc__ = Client.order_market.__doc__ + + async def order_market_buy(self, **params): + params.update({"side": self.SIDE_BUY}) + return await self.order_market(**params) + + order_market_buy.__doc__ = Client.order_market_buy.__doc__ + + async def order_market_sell(self, **params): + params.update({"side": self.SIDE_SELL}) + return await self.order_market(**params) + + order_market_sell.__doc__ = Client.order_market_sell.__doc__ + + async def order_oco_buy(self, **params): + params.update({"side": self.SIDE_BUY}) + return await self.create_oco_order(**params) + + order_oco_buy.__doc__ = Client.order_oco_buy.__doc__ + + async def order_oco_sell(self, **params): + params.update({"side": self.SIDE_SELL}) + return await self.create_oco_order(**params) + + order_oco_sell.__doc__ = Client.order_oco_sell.__doc__ + + async def create_test_order(self, **params): + return await self._post("order/test", True, data=params) + + create_test_order.__doc__ = Client.create_test_order.__doc__ + + async def get_order(self, **params): + return await self._get("order", True, data=params) + + get_order.__doc__ = Client.get_order.__doc__ + + async def get_all_orders(self, **params): + return await self._get("allOrders", True, data=params) + + get_all_orders.__doc__ = Client.get_all_orders.__doc__ + + async def cancel_order(self, **params): + return await self._delete("order", True, data=params) + + cancel_order.__doc__ = Client.cancel_order.__doc__ + + async def get_open_orders(self, **params): + return await self._get("openOrders", True, data=params) + + get_open_orders.__doc__ = Client.get_open_orders.__doc__ + + async def get_open_oco_orders(self, **params): + return await self._get("openOrderList", True, data=params) + + get_open_oco_orders.__doc__ = Client.get_open_oco_orders.__doc__ + + # User Stream Endpoints + async def get_account(self, **params): + return await self._get("account", True, data=params) + + get_account.__doc__ = Client.get_account.__doc__ + + async def get_asset_balance(self, asset, **params): + res = await self.get_account(**params) + # find asset balance in list of balances + if "balances" in res: + for bal in res["balances"]: + if bal["asset"].lower() == asset.lower(): + return bal + return None + + get_asset_balance.__doc__ = Client.get_asset_balance.__doc__ + + async def get_my_trades(self, **params): + return await self._get("myTrades", True, data=params) + + get_my_trades.__doc__ = Client.get_my_trades.__doc__ + + async def get_current_order_count(self, **params): + return await self._get("rateLimit/order", True, data=params) + + get_current_order_count.__doc__ = Client.get_current_order_count.__doc__ + + async def get_prevented_matches(self, **params): + return await self._get("myPreventedMatches", True, data=params) + + get_prevented_matches.__doc__ = Client.get_prevented_matches.__doc__ + + async def get_allocations(self, **params): + return await self._get("myAllocations", True, data=params) + + get_allocations.__doc__ = Client.get_allocations.__doc__ + + async def get_system_status(self): + return await self._request_margin_api("get", "system/status") + + get_system_status.__doc__ = Client.get_system_status.__doc__ + + async def get_account_status(self, **params): + return await self._request_margin_api( + "get", "account/status", True, data=params + ) + + get_account_status.__doc__ = Client.get_account_status.__doc__ + + async def get_account_api_trading_status(self, **params): + return await self._request_margin_api( + "get", "account/apiTradingStatus", True, data=params + ) + + get_account_api_trading_status.__doc__ = ( + Client.get_account_api_trading_status.__doc__ + ) + + async def get_account_api_permissions(self, **params): + return await self._request_margin_api( + "get", "account/apiRestrictions", True, data=params + ) + + get_account_api_permissions.__doc__ = Client.get_account_api_permissions.__doc__ + + async def get_dust_assets(self, **params): + return await self._request_margin_api( + "post", "asset/dust-btc", True, data=params + ) + + get_dust_assets.__doc__ = Client.get_dust_assets.__doc__ + + async def get_dust_log(self, **params): + return await self._request_margin_api( + "get", "asset/dribblet", True, data=params + ) + + get_dust_log.__doc__ = Client.get_dust_log.__doc__ + + async def transfer_dust(self, **params): + return await self._request_margin_api("post", "asset/dust", True, data=params) + + transfer_dust.__doc__ = Client.transfer_dust.__doc__ + + async def get_asset_dividend_history(self, **params): + return await self._request_margin_api( + "get", "asset/assetDividend", True, data=params + ) + + get_asset_dividend_history.__doc__ = Client.get_asset_dividend_history.__doc__ + + async def make_universal_transfer(self, **params): + return await self._request_margin_api( + "post", "asset/transfer", signed=True, data=params + ) + + make_universal_transfer.__doc__ = Client.make_universal_transfer.__doc__ + + async def query_universal_transfer_history(self, **params): + return await self._request_margin_api( + "get", "asset/transfer", signed=True, data=params + ) + + query_universal_transfer_history.__doc__ = ( + Client.query_universal_transfer_history.__doc__ + ) + + async def get_trade_fee(self, **params): + if self.tld == "us": + endpoint = "asset/query/trading-fee" + else: + endpoint = "asset/tradeFee" + return await self._request_margin_api("get", endpoint, True, data=params) + + get_trade_fee.__doc__ = Client.get_trade_fee.__doc__ + + async def get_asset_details(self, **params): + return await self._request_margin_api( + "get", "asset/assetDetail", True, data=params + ) + + get_asset_details.__doc__ = Client.get_asset_details.__doc__ + + async def get_spot_delist_schedule(self, **params): + return await self._request_margin_api( + "get", "/spot/delist-schedule", signed=True, data=params + ) + + # Withdraw Endpoints + + async def withdraw(self, **params): + # force a name for the withdrawal if one not set + if "coin" in params and "name" not in params: + params["name"] = params["coin"] + return await self._request_margin_api( + "post", "capital/withdraw/apply", True, data=params + ) + + withdraw.__doc__ = Client.withdraw.__doc__ + + async def get_deposit_history(self, **params): + return await self._request_margin_api( + "get", "capital/deposit/hisrec", True, data=params + ) + + get_deposit_history.__doc__ = Client.get_deposit_history.__doc__ + + async def get_withdraw_history(self, **params): + return await self._request_margin_api( + "get", "capital/withdraw/history", True, data=params + ) + + get_withdraw_history.__doc__ = Client.get_withdraw_history.__doc__ + + async def get_withdraw_history_id(self, withdraw_id, **params): + result = await self.get_withdraw_history(**params) + + for entry in result: + if "id" in entry and entry["id"] == withdraw_id: + return entry + + raise Exception("There is no entry with withdraw id", result) + + get_withdraw_history_id.__doc__ = Client.get_withdraw_history_id.__doc__ + + async def get_deposit_address( + self, coin: str, network: Optional[str] = None, **params + ): + params["coin"] = coin + if network: + params["network"] = network + return await self._request_margin_api( + "get", "capital/deposit/address", True, data=params + ) + + get_deposit_address.__doc__ = Client.get_deposit_address.__doc__ + + # User Stream Endpoints + + async def stream_get_listen_key(self): + res = await self._post("userDataStream", False, data={}) + return res["listenKey"] + + stream_get_listen_key.__doc__ = Client.stream_get_listen_key.__doc__ + + async def stream_keepalive(self, listenKey): + params = {"listenKey": listenKey} + return await self._put("userDataStream", False, data=params) + + stream_keepalive.__doc__ = Client.stream_keepalive.__doc__ + + async def stream_close(self, listenKey): + params = {"listenKey": listenKey} + return await self._delete("userDataStream", False, data=params) + + stream_close.__doc__ = Client.stream_close.__doc__ + + # Margin Trading Endpoints + async def get_margin_account(self, **params): + return await self._request_margin_api( + "get", "margin/account", True, data=params + ) + + get_margin_account.__doc__ = Client.get_margin_account.__doc__ + + async def get_isolated_margin_account(self, **params): + return await self._request_margin_api( + "get", "margin/isolated/account", True, data=params + ) + + get_isolated_margin_account.__doc__ = Client.get_isolated_margin_account.__doc__ + + async def enable_isolated_margin_account(self, **params): + return await self._request_margin_api( + "post", "margin/isolated/account", True, data=params + ) + + enable_isolated_margin_account.__doc__ = ( + Client.enable_isolated_margin_account.__doc__ + ) + + async def disable_isolated_margin_account(self, **params): + return await self._request_margin_api( + "delete", "margin/isolated/account", True, data=params + ) + + disable_isolated_margin_account.__doc__ = ( + Client.disable_isolated_margin_account.__doc__ + ) + + async def get_enabled_isolated_margin_account_limit(self, **params): + return await self._request_margin_api( + "get", "margin/isolated/accountLimit", True, data=params + ) + + get_enabled_isolated_margin_account_limit.__doc__ = ( + Client.get_enabled_isolated_margin_account_limit.__doc__ + ) + + async def get_margin_dustlog(self, **params): + return await self._request_margin_api( + "get", "margin/dribblet", True, data=params + ) + + get_margin_dustlog.__doc__ = Client.get_margin_dustlog.__doc__ + + async def get_margin_dust_assets(self, **params): + return await self._request_margin_api("get", "margin/dust", True, data=params) + + get_margin_dust_assets.__doc__ = Client.get_margin_dust_assets.__doc__ + + async def transfer_margin_dust(self, **params): + return await self._request_margin_api("post", "margin/dust", True, data=params) + + transfer_margin_dust.__doc__ = Client.transfer_margin_dust.__doc__ + + async def get_cross_margin_collateral_ratio(self, **params): + return await self._request_margin_api( + "get", "margin/crossMarginCollateralRatio", True, data=params + ) + + get_cross_margin_collateral_ratio.__doc__ = ( + Client.get_cross_margin_collateral_ratio.__doc__ + ) + + async def get_small_liability_exchange_assets(self, **params): + return await self._request_margin_api( + "get", "margin/exchange-small-liability", True, data=params + ) + + get_small_liability_exchange_assets.__doc__ = ( + Client.get_small_liability_exchange_assets.__doc__ + ) + + async def exchange_small_liability_assets(self, **params): + return await self._request_margin_api( + "post", "margin/exchange-small-liability", True, data=params + ) + + exchange_small_liability_assets.__doc__ = ( + Client.exchange_small_liability_assets.__doc__ + ) + + async def get_small_liability_exchange_history(self, **params): + return await self._request_margin_api( + "get", "margin/exchange-small-liability-history", True, data=params + ) + + get_small_liability_exchange_history.__doc__ = ( + Client.get_small_liability_exchange_history.__doc__ + ) + + async def get_future_hourly_interest_rate(self, **params): + return await self._request_margin_api( + "get", "margin/next-hourly-interest-rate", True, data=params + ) + + get_future_hourly_interest_rate.__doc__ = ( + Client.get_future_hourly_interest_rate.__doc__ + ) + + async def get_margin_capital_flow(self, **params): + return await self._request_margin_api( + "get", "margin/capital-flow", True, data=params + ) + + get_margin_capital_flow.__doc__ = Client.get_margin_capital_flow.__doc__ + + async def get_margin_delist_schedule(self, **params): + return await self._request_margin_api( + "get", "margin/delist-schedule", True, data=params + ) + + get_margin_delist_schedule.__doc__ = Client.get_margin_delist_schedule.__doc__ + + async def get_margin_asset(self, **params): + return await self._request_margin_api("get", "margin/asset", data=params) + + get_margin_asset.__doc__ = Client.get_margin_asset.__doc__ + + async def get_margin_symbol(self, **params): + return await self._request_margin_api("get", "margin/pair", data=params) + + get_margin_symbol.__doc__ = Client.get_margin_symbol.__doc__ + + async def get_margin_all_assets(self, **params): + return await self._request_margin_api("get", "margin/allAssets", data=params) + + get_margin_all_assets.__doc__ = Client.get_margin_all_assets.__doc__ + + async def get_margin_all_pairs(self, **params): + return await self._request_margin_api("get", "margin/allPairs", data=params) + + get_margin_all_pairs.__doc__ = Client.get_margin_all_pairs.__doc__ + + async def create_isolated_margin_account(self, **params): + return await self._request_margin_api( + "post", "margin/isolated/create", signed=True, data=params + ) + + create_isolated_margin_account.__doc__ = ( + Client.create_isolated_margin_account.__doc__ + ) + + async def get_isolated_margin_symbol(self, **params): + return await self._request_margin_api( + "get", "margin/isolated/pair", signed=True, data=params + ) + + get_isolated_margin_symbol.__doc__ = Client.get_isolated_margin_symbol.__doc__ + + async def get_all_isolated_margin_symbols(self, **params): + return await self._request_margin_api( + "get", "margin/isolated/allPairs", signed=True, data=params + ) + + get_all_isolated_margin_symbols.__doc__ = ( + Client.get_all_isolated_margin_symbols.__doc__ + ) + + async def get_isolated_margin_fee_data(self, **params): + return await self._request_margin_api( + "get", "margin/isolatedMarginData", True, data=params + ) + + get_isolated_margin_fee_data.__doc__ = Client.get_isolated_margin_fee_data.__doc__ + + async def get_isolated_margin_tier_data(self, **params): + return await self._request_margin_api( + "get", "margin/isolatedMarginTier", True, data=params + ) + + get_isolated_margin_tier_data.__doc__ = Client.get_isolated_margin_tier_data.__doc__ + + async def margin_manual_liquidation(self, **params): + return await self._request_margin_api( + "get", "margin/manual-liquidation", True, data=params + ) + + margin_manual_liquidation.__doc__ = Client.margin_manual_liquidation.__doc__ + + async def toggle_bnb_burn_spot_margin(self, **params): + return await self._request_margin_api( + "post", "bnbBurn", signed=True, data=params + ) + + toggle_bnb_burn_spot_margin.__doc__ = Client.toggle_bnb_burn_spot_margin.__doc__ + + async def get_bnb_burn_spot_margin(self, **params): + return await self._request_margin_api( + "get", "bnbBurn", signed=True, data=params + ) + + get_bnb_burn_spot_margin.__doc__ = Client.get_bnb_burn_spot_margin.__doc__ + + async def get_margin_price_index(self, **params): + return await self._request_margin_api("get", "margin/priceIndex", data=params) + + get_margin_price_index.__doc__ = Client.get_margin_price_index.__doc__ + + async def transfer_margin_to_spot(self, **params): + params["type"] = 2 + return await self._request_margin_api( + "post", "margin/transfer", signed=True, data=params + ) + + transfer_margin_to_spot.__doc__ = Client.transfer_margin_to_spot.__doc__ + + async def transfer_spot_to_margin(self, **params): + params["type"] = 1 + return await self._request_margin_api( + "post", "margin/transfer", signed=True, data=params + ) + + transfer_spot_to_margin.__doc__ = Client.transfer_spot_to_margin.__doc__ + + async def transfer_isolated_margin_to_spot(self, **params): + params["transFrom"] = "ISOLATED_MARGIN" + params["transTo"] = "SPOT" + return await self._request_margin_api( + "post", "margin/isolated/transfer", signed=True, data=params + ) + + transfer_isolated_margin_to_spot.__doc__ = ( + Client.transfer_isolated_margin_to_spot.__doc__ + ) + + async def transfer_spot_to_isolated_margin(self, **params): + params["transFrom"] = "SPOT" + params["transTo"] = "ISOLATED_MARGIN" + return await self._request_margin_api( + "post", "margin/isolated/transfer", signed=True, data=params + ) + + transfer_spot_to_isolated_margin.__doc__ = ( + Client.transfer_spot_to_isolated_margin.__doc__ + ) + + async def create_margin_loan(self, **params): + return await self._request_margin_api( + "post", "margin/loan", signed=True, data=params + ) + + create_margin_loan.__doc__ = Client.create_margin_loan.__doc__ + + async def repay_margin_loan(self, **params): + return await self._request_margin_api( + "post", "margin/repay", signed=True, data=params + ) + + repay_margin_loan.__doc__ = Client.repay_margin_loan.__doc__ + + async def create_margin_order(self, **params): + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._request_margin_api( + "post", "margin/order", signed=True, data=params + ) + + create_margin_order.__doc__ = Client.create_margin_order.__doc__ + + async def cancel_margin_order(self, **params): + return await self._request_margin_api( + "delete", "margin/order", signed=True, data=params + ) + + cancel_margin_order.__doc__ = Client.cancel_margin_order.__doc__ + + async def cancel_all_open_margin_orders(self, **params): + return await self._request_margin_api( + "delete", "margin/openOrders", signed=True, data=params + ) + + cancel_all_open_margin_orders.__doc__ = Client.cancel_all_open_margin_orders.__doc__ + + async def set_margin_max_leverage(self, **params): + return await self._request_margin_api( + "post", "margin/max-leverage", signed=True, data=params + ) + + set_margin_max_leverage.__doc__ = Client.set_margin_max_leverage.__doc__ + + async def get_margin_transfer_history(self, **params): + return await self._request_margin_api( + "get", "margin/transfer", signed=True, data=params + ) + + get_margin_transfer_history.__doc__ = Client.get_margin_transfer_history.__doc__ + + async def get_margin_loan_details(self, **params): + return await self._request_margin_api( + "get", "margin/loan", signed=True, data=params + ) + + get_margin_loan_details.__doc__ = Client.get_margin_loan_details.__doc__ + + async def get_margin_repay_details(self, **params): + return await self._request_margin_api( + "get", "margin/repay", signed=True, data=params + ) + + async def get_cross_margin_data(self, **params): + return await self._request_margin_api( + "get", "margin/crossMarginData", signed=True, data=params + ) + + async def get_margin_interest_history(self, **params): + return await self._request_margin_api( + "get", "margin/interestHistory", signed=True, data=params + ) + + async def get_margin_force_liquidation_rec(self, **params): + return await self._request_margin_api( + "get", "margin/forceLiquidationRec", signed=True, data=params + ) + + async def get_margin_order(self, **params): + return await self._request_margin_api( + "get", "margin/order", signed=True, data=params + ) + + async def get_open_margin_orders(self, **params): + return await self._request_margin_api( + "get", "margin/openOrders", signed=True, data=params + ) + + async def get_all_margin_orders(self, **params): + return await self._request_margin_api( + "get", "margin/allOrders", signed=True, data=params + ) + + async def get_margin_trades(self, **params): + return await self._request_margin_api( + "get", "margin/myTrades", signed=True, data=params + ) + + async def get_max_margin_loan(self, **params): + return await self._request_margin_api( + "get", "margin/maxBorrowable", signed=True, data=params + ) + + async def get_max_margin_transfer(self, **params): + return await self._request_margin_api( + "get", "margin/maxTransferable", signed=True, data=params + ) + + # Margin OCO + + async def create_margin_oco_order(self, **params): + return await self._request_margin_api( + "post", "margin/order/oco", signed=True, data=params + ) + + async def cancel_margin_oco_order(self, **params): + return await self._request_margin_api( + "delete", "margin/orderList", signed=True, data=params + ) + + async def get_margin_oco_order(self, **params): + return await self._request_margin_api( + "get", "margin/orderList", signed=True, data=params + ) + + async def get_open_margin_oco_orders(self, **params): + return await self._request_margin_api( + "get", "margin/openOrderList", signed=True, data=params + ) + + # Cross-margin + + async def margin_stream_get_listen_key(self): + res = await self._request_margin_api( + "post", "userDataStream", signed=False, data={} + ) + return res["listenKey"] + + async def margin_stream_keepalive(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_margin_api( + "put", "userDataStream", signed=False, data=params + ) + + async def margin_stream_close(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_margin_api( + "delete", "userDataStream", signed=False, data=params + ) + + # Isolated margin + + async def isolated_margin_stream_get_listen_key(self, symbol): + params = {"symbol": symbol} + res = await self._request_margin_api( + "post", "userDataStream/isolated", signed=False, data=params + ) + return res["listenKey"] + + async def isolated_margin_stream_keepalive(self, symbol, listenKey): + params = {"symbol": symbol, "listenKey": listenKey} + return await self._request_margin_api( + "put", "userDataStream/isolated", signed=False, data=params + ) + + async def isolated_margin_stream_close(self, symbol, listenKey): + params = {"symbol": symbol, "listenKey": listenKey} + return await self._request_margin_api( + "delete", "userDataStream/isolated", signed=False, data=params + ) + + # Simple Earn Endpoints + + async def get_simple_earn_flexible_product_list(self, **params): + return await self._request_margin_api( + "get", "simple-earn/flexible/list", signed=True, data=params + ) + + get_simple_earn_flexible_product_list.__doc__ = ( + Client.get_simple_earn_flexible_product_list.__doc__ + ) + + async def get_simple_earn_locked_product_list(self, **params): + return await self._request_margin_api( + "get", "simple-earn/locked/list", signed=True, data=params + ) + + get_simple_earn_locked_product_list.__doc__ = ( + Client.get_simple_earn_locked_product_list.__doc__ + ) + + async def subscribe_simple_earn_flexible_product(self, **params): + return await self._request_margin_api( + "post", "simple-earn/flexible/subscribe", signed=True, data=params + ) + + subscribe_simple_earn_flexible_product.__doc__ = ( + Client.subscribe_simple_earn_flexible_product.__doc__ + ) + + async def subscribe_simple_earn_locked_product(self, **params): + return await self._request_margin_api( + "post", "simple-earn/locked/subscribe", signed=True, data=params + ) + + subscribe_simple_earn_locked_product.__doc__ = ( + Client.subscribe_simple_earn_locked_product.__doc__ + ) + + async def redeem_simple_earn_flexible_product(self, **params): + return await self._request_margin_api( + "post", "simple-earn/flexible/redeem", signed=True, data=params + ) + + redeem_simple_earn_flexible_product.__doc__ = ( + Client.redeem_simple_earn_flexible_product.__doc__ + ) + + async def redeem_simple_earn_locked_product(self, **params): + return await self._request_margin_api( + "post", "simple-earn/locked/redeem", signed=True, data=params + ) + + redeem_simple_earn_locked_product.__doc__ = ( + Client.redeem_simple_earn_locked_product.__doc__ + ) + + async def get_simple_earn_flexible_product_position(self, **params): + return await self._request_margin_api( + "get", "simple-earn/flexible/position", signed=True, data=params + ) + + get_simple_earn_flexible_product_position.__doc__ = ( + Client.get_simple_earn_flexible_product_position.__doc__ + ) + + async def get_simple_earn_locked_product_position(self, **params): + return await self._request_margin_api( + "get", "simple-earn/locked/position", signed=True, data=params + ) + + get_simple_earn_locked_product_position.__doc__ = ( + Client.get_simple_earn_locked_product_position.__doc__ + ) + + async def get_simple_earn_account(self, **params): + return await self._request_margin_api( + "get", "simple-earn/account", signed=True, data=params + ) + + get_simple_earn_account.__doc__ = Client.get_simple_earn_account.__doc__ + + # Lending Endpoints + + async def get_fixed_activity_project_list(self, **params): + return await self._request_margin_api( + "get", "lending/project/list", signed=True, data=params + ) + + async def change_fixed_activity_to_daily_position(self, **params): + return await self._request_margin_api( + "post", "lending/positionChanged", signed=True, data=params + ) + + # Staking Endpoints + + async def get_staking_product_list(self, **params): + return await self._request_margin_api( + "get", "staking/productList", signed=True, data=params + ) + + async def purchase_staking_product(self, **params): + return await self._request_margin_api( + "post", "staking/purchase", signed=True, data=params + ) + + async def redeem_staking_product(self, **params): + return await self._request_margin_api( + "post", "staking/redeem", signed=True, data=params + ) + + async def get_staking_position(self, **params): + return await self._request_margin_api( + "get", "staking/position", signed=True, data=params + ) + + async def get_staking_purchase_history(self, **params): + return await self._request_margin_api( + "get", "staking/purchaseRecord", signed=True, data=params + ) + + async def set_auto_staking(self, **params): + return await self._request_margin_api( + "post", "staking/setAutoStaking", signed=True, data=params + ) + + async def get_personal_left_quota(self, **params): + return await self._request_margin_api( + "get", "staking/personalLeftQuota", signed=True, data=params + ) + + # US Staking Endpoints + + async def get_staking_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api("get", "staking/asset", True, data=params) + + get_staking_asset_us.__doc__ = Client.get_staking_asset_us.__doc__ + + async def stake_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api( + "post", "staking/stake", True, data=params + ) + + stake_asset_us.__doc__ = Client.stake_asset_us.__doc__ + + async def unstake_asset_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api( + "post", "staking/unstake", True, data=params + ) + + unstake_asset_us.__doc__ = Client.unstake_asset_us.__doc__ + + async def get_staking_balance_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api( + "get", "staking/stakingBalance", True, data=params + ) + + get_staking_balance_us.__doc__ = Client.get_staking_balance_us.__doc__ + + async def get_staking_history_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api( + "get", "staking/history", True, data=params + ) + + get_staking_history_us.__doc__ = Client.get_staking_history_us.__doc__ + + async def get_staking_rewards_history_us(self, **params): + assert self.tld == "us", "Endpoint only available on binance.us" + return await self._request_margin_api( + "get", "staking/stakingRewardsHistory", True, data=params + ) + + get_staking_rewards_history_us.__doc__ = ( + Client.get_staking_rewards_history_us.__doc__ + ) + + # Sub Accounts + + async def get_sub_account_list(self, **params): + return await self._request_margin_api( + "get", "sub-account/list", True, data=params + ) + + async def get_sub_account_transfer_history(self, **params): + return await self._request_margin_api( + "get", "sub-account/sub/transfer/history", True, data=params + ) + + async def get_sub_account_futures_transfer_history(self, **params): + return await self._request_margin_api( + "get", "sub-account/futures/internalTransfer", True, data=params + ) + + async def create_sub_account_futures_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/futures/internalTransfer", True, data=params + ) + + async def get_sub_account_assets(self, **params): + return await self._request_margin_api( + "get", "sub-account/assets", True, data=params, version=4 + ) + + async def query_subaccount_spot_summary(self, **params): + return await self._request_margin_api( + "get", "sub-account/spotSummary", True, data=params + ) + + async def get_subaccount_deposit_address(self, **params): + return await self._request_margin_api( + "get", "capital/deposit/subAddress", True, data=params + ) + + async def get_subaccount_deposit_history(self, **params): + return await self._request_margin_api( + "get", "capital/deposit/subHisrec", True, data=params + ) + + async def get_subaccount_futures_margin_status(self, **params): + return await self._request_margin_api( + "get", "sub-account/status", True, data=params + ) + + async def enable_subaccount_margin(self, **params): + return await self._request_margin_api( + "post", "sub-account/margin/enable", True, data=params + ) + + async def get_subaccount_margin_details(self, **params): + return await self._request_margin_api( + "get", "sub-account/margin/account", True, data=params + ) + + async def get_subaccount_margin_summary(self, **params): + return await self._request_margin_api( + "get", "sub-account/margin/accountSummary", True, data=params + ) + + async def enable_subaccount_futures(self, **params): + return await self._request_margin_api( + "post", "sub-account/futures/enable", True, data=params + ) + + async def get_subaccount_futures_details(self, **params): + return await self._request_margin_api( + "get", "sub-account/futures/account", True, data=params, version=2 + ) + + async def get_subaccount_futures_summary(self, **params): + return await self._request_margin_api( + "get", "sub-account/futures/accountSummary", True, data=params, version=2 + ) + + async def get_subaccount_futures_positionrisk(self, **params): + return await self._request_margin_api( + "get", "sub-account/futures/positionRisk", True, data=params, version=2 + ) + + async def make_subaccount_futures_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/futures/transfer", True, data=params + ) + + async def make_subaccount_margin_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/margin/transfer", True, data=params + ) + + async def make_subaccount_to_subaccount_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/transfer/subToSub", True, data=params + ) + + async def make_subaccount_to_master_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/transfer/subToMaster", True, data=params + ) + + async def get_subaccount_transfer_history(self, **params): + return await self._request_margin_api( + "get", "sub-account/transfer/subUserHistory", True, data=params + ) + + async def make_subaccount_universal_transfer(self, **params): + return await self._request_margin_api( + "post", "sub-account/universalTransfer", True, data=params + ) + + async def get_universal_transfer_history(self, **params): + return await self._request_margin_api( + "get", "sub-account/universalTransfer", True, data=params + ) + + # Futures API + + async def futures_ping(self): + return await self._request_futures_api("get", "ping") + + async def futures_time(self): + return await self._request_futures_api("get", "time") + + async def futures_exchange_info(self): + return await self._request_futures_api("get", "exchangeInfo") + + async def futures_order_book(self, **params): + return await self._request_futures_api("get", "depth", data=params) + + async def futures_recent_trades(self, **params): + return await self._request_futures_api("get", "trades", data=params) + + async def futures_historical_trades(self, **params): + return await self._request_futures_api("get", "historicalTrades", data=params) + + async def futures_aggregate_trades(self, **params): + return await self._request_futures_api("get", "aggTrades", data=params) + + async def futures_klines(self, **params): + return await self._request_futures_api("get", "klines", data=params) + + async def futures_continous_klines(self, **params): + return await self._request_futures_api("get", "continuousKlines", data=params) + + async def futures_historical_klines( + self, symbol, interval, start_str, end_str=None, limit=500 + ): + return await self._historical_klines( + symbol, + interval, + start_str, + end_str=end_str, + limit=limit, + klines_type=HistoricalKlinesType.FUTURES, + ) + + async def futures_historical_klines_generator( + self, symbol, interval, start_str, end_str=None + ): + return self._historical_klines_generator( + symbol, + interval, + start_str, + end_str=end_str, + klines_type=HistoricalKlinesType.FUTURES, + ) + + async def futures_mark_price(self, **params): + return await self._request_futures_api("get", "premiumIndex", data=params) + + async def futures_funding_rate(self, **params): + return await self._request_futures_api("get", "fundingRate", data=params) + + async def futures_top_longshort_account_ratio(self, **params): + return await self._request_futures_data_api( + "get", "topLongShortAccountRatio", data=params + ) + + async def futures_top_longshort_position_ratio(self, **params): + return await self._request_futures_data_api( + "get", "topLongShortPositionRatio", data=params + ) + + async def futures_global_longshort_ratio(self, **params): + return await self._request_futures_data_api( + "get", "globalLongShortAccountRatio", data=params + ) + + async def futures_ticker(self, **params): + return await self._request_futures_api("get", "ticker/24hr", data=params) + + async def futures_symbol_ticker(self, **params): + return await self._request_futures_api("get", "ticker/price", data=params) + + async def futures_orderbook_ticker(self, **params): + return await self._request_futures_api("get", "ticker/bookTicker", data=params) + + async def futures_liquidation_orders(self, **params): + return await self._request_futures_api( + "get", "forceOrders", signed=True, data=params + ) + + async def futures_api_trading_status(self, **params): + return await self._request_futures_api( + "get", "apiTradingStatus", signed=True, data=params + ) + + async def futures_commission_rate(self, **params): + return await self._request_futures_api( + "get", "commissionRate", signed=True, data=params + ) + + async def futures_adl_quantile_estimate(self, **params): + return await self._request_futures_api( + "get", "adlQuantile", signed=True, data=params + ) + + async def futures_open_interest(self, **params): + return await self._request_futures_api("get", "openInterest", data=params) + + async def futures_index_info(self, **params): + return await self._request_futures_api("get", "indexInfo", data=params) + + async def futures_open_interest_hist(self, **params): + return await self._request_futures_data_api( + "get", "openInterestHist", data=params + ) + + async def futures_leverage_bracket(self, **params): + return await self._request_futures_api( + "get", "leverageBracket", True, data=params + ) + + async def futures_account_transfer(self, **params): + return await self._request_margin_api( + "post", "futures/transfer", True, data=params + ) + + async def transfer_history(self, **params): + return await self._request_margin_api( + "get", "futures/transfer", True, data=params + ) + + async def futures_loan_borrow_history(self, **params): + return await self._request_margin_api( + "get", "futures/loan/borrow/history", True, data=params + ) + + async def futures_loan_repay_history(self, **params): + return await self._request_margin_api( + "get", "futures/loan/repay/history", True, data=params + ) + + async def futures_loan_wallet(self, **params): + return await self._request_margin_api( + "get", "futures/loan/wallet", True, data=params, version=2 + ) + + async def futures_cross_collateral_adjust_history(self, **params): + return await self._request_margin_api( + "get", "futures/loan/adjustCollateral/history", True, data=params + ) + + async def futures_cross_collateral_liquidation_history(self, **params): + return await self._request_margin_api( + "get", "futures/loan/liquidationHistory", True, data=params + ) + + async def futures_loan_interest_history(self, **params): + return await self._request_margin_api( + "get", "futures/loan/interestHistory", True, data=params + ) + + async def futures_create_order(self, **params): + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_futures_api("post", "order", True, data=params) + + async def futures_create_test_order(self, **params): + return await self._request_futures_api("post", "order/test", True, data=params) + + async def futures_place_batch_order(self, **params): + for order in params["batchOrders"]: + if "newClientOrderId" not in order: + order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace("%27", "%22") + params["batchOrders"] = query_string[12:] + return await self._request_futures_api("post", "batchOrders", True, data=params) + + async def futures_get_order(self, **params): + return await self._request_futures_api("get", "order", True, data=params) + + async def futures_get_open_orders(self, **params): + return await self._request_futures_api("get", "openOrders", True, data=params) + + async def futures_get_all_orders(self, **params): + return await self._request_futures_api("get", "allOrders", True, data=params) + + async def futures_cancel_order(self, **params): + return await self._request_futures_api("delete", "order", True, data=params) + + async def futures_cancel_all_open_orders(self, **params): + return await self._request_futures_api( + "delete", "allOpenOrders", True, data=params + ) + + async def futures_cancel_orders(self, **params): + return await self._request_futures_api( + "delete", "batchOrders", True, data=params + ) + + async def futures_countdown_cancel_all(self, **params): + return await self._request_futures_api( + "post", "countdownCancelAll", True, data=params + ) + + async def futures_account_balance(self, **params): + return await self._request_futures_api( + "get", "balance", True, version=3, data=params + ) + + async def futures_account(self, **params): + return await self._request_futures_api( + "get", "account", True, version=2, data=params + ) + + async def futures_change_leverage(self, **params): + return await self._request_futures_api("post", "leverage", True, data=params) + + async def futures_change_margin_type(self, **params): + return await self._request_futures_api("post", "marginType", True, data=params) + + async def futures_change_position_margin(self, **params): + return await self._request_futures_api( + "post", "positionMargin", True, data=params + ) + + async def futures_position_margin_history(self, **params): + return await self._request_futures_api( + "get", "positionMargin/history", True, data=params + ) + + async def futures_position_information(self, **params): + return await self._request_futures_api( + "get", "positionRisk", True, version=3, data=params + ) + + async def futures_account_trades(self, **params): + return await self._request_futures_api("get", "userTrades", True, data=params) + + async def futures_income_history(self, **params): + return await self._request_futures_api("get", "income", True, data=params) + + async def futures_change_position_mode(self, **params): + return await self._request_futures_api( + "post", "positionSide/dual", True, data=params + ) + + async def futures_get_position_mode(self, **params): + return await self._request_futures_api( + "get", "positionSide/dual", True, data=params + ) + + async def futures_change_multi_assets_mode(self, multiAssetsMargin: bool): + params = {"multiAssetsMargin": "true" if multiAssetsMargin else "false"} + return await self._request_futures_api( + "post", "multiAssetsMargin", True, data=params + ) + + async def futures_get_multi_assets_mode(self): + return await self._request_futures_api( + "get", "multiAssetsMargin", True, data={} + ) + + async def futures_stream_get_listen_key(self): + res = await self._request_futures_api( + "post", "listenKey", signed=False, data={} + ) + return res["listenKey"] + + async def futures_stream_keepalive(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_futures_api( + "put", "listenKey", signed=False, data=params + ) + + async def futures_stream_close(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_futures_api( + "delete", "listenKey", signed=False, data=params + ) + + # new methods + async def futures_account_config(self, **params): + return await self._request_futures_api( + "get", "accountConfig", signed=True, version=1, data=params + ) + + async def futures_symbol_config(self, **params): + return await self._request_futures_api( + "get", "symbolConfig", signed=True, version=1, data=params + ) + + # COIN Futures API + + async def futures_coin_ping(self): + return await self._request_futures_coin_api("get", "ping") + + async def futures_coin_time(self): + return await self._request_futures_coin_api("get", "time") + + async def futures_coin_exchange_info(self): + return await self._request_futures_coin_api("get", "exchangeInfo") + + async def futures_coin_order_book(self, **params): + return await self._request_futures_coin_api("get", "depth", data=params) + + async def futures_coin_recent_trades(self, **params): + return await self._request_futures_coin_api("get", "trades", data=params) + + async def futures_coin_historical_trades(self, **params): + return await self._request_futures_coin_api( + "get", "historicalTrades", data=params + ) + + async def futures_coin_aggregate_trades(self, **params): + return await self._request_futures_coin_api("get", "aggTrades", data=params) + + async def futures_coin_klines(self, **params): + return await self._request_futures_coin_api("get", "klines", data=params) + + async def futures_coin_continous_klines(self, **params): + return await self._request_futures_coin_api( + "get", "continuousKlines", data=params + ) + + async def futures_coin_index_price_klines(self, **params): + return await self._request_futures_coin_api( + "get", "indexPriceKlines", data=params + ) + + async def futures_coin_mark_price_klines(self, **params): + return await self._request_futures_coin_api( + "get", "markPriceKlines", data=params + ) + + async def futures_coin_mark_price(self, **params): + return await self._request_futures_coin_api("get", "premiumIndex", data=params) + + async def futures_coin_funding_rate(self, **params): + return await self._request_futures_coin_api("get", "fundingRate", data=params) + + async def futures_coin_ticker(self, **params): + return await self._request_futures_coin_api("get", "ticker/24hr", data=params) + + async def futures_coin_symbol_ticker(self, **params): + return await self._request_futures_coin_api("get", "ticker/price", data=params) + + async def futures_coin_orderbook_ticker(self, **params): + return await self._request_futures_coin_api( + "get", "ticker/bookTicker", data=params + ) + + async def futures_coin_liquidation_orders(self, **params): + return await self._request_futures_coin_api( + "get", "forceOrders", signed=True, data=params + ) + + async def futures_coin_open_interest(self, **params): + return await self._request_futures_coin_api("get", "openInterest", data=params) + + async def futures_coin_open_interest_hist(self, **params): + return await self._request_futures_coin_data_api( + "get", "openInterestHist", data=params + ) + + async def futures_coin_leverage_bracket(self, **params): + return await self._request_futures_coin_api( + "get", "leverageBracket", version=2, signed=True, data=params + ) + + async def new_transfer_history(self, **params): + return await self._request_margin_api( + "get", "asset/transfer", True, data=params + ) + + async def funding_wallet(self, **params): + return await self._request_margin_api( + "post", "asset/get-funding-asset", True, data=params + ) + + async def get_user_asset(self, **params): + return await self._request_margin_api( + "post", "asset/getUserAsset", True, data=params, version=3 + ) + + async def universal_transfer(self, **params): + return await self._request_margin_api( + "post", "asset/transfer", signed=True, data=params + ) + + async def futures_coin_create_order(self, **params): + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_futures_coin_api("post", "order", True, data=params) + + async def futures_coin_place_batch_order(self, **params): + for order in params["batchOrders"]: + if "newClientOrderId" not in order: + order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + query_string = urlencode(params) + query_string = query_string.replace("%27", "%22") + params["batchOrders"] = query_string[12:] + + return await self._request_futures_coin_api( + "post", "batchOrders", True, data=params + ) + + async def futures_coin_get_order(self, **params): + return await self._request_futures_coin_api("get", "order", True, data=params) + + async def futures_coin_get_open_orders(self, **params): + return await self._request_futures_coin_api( + "get", "openOrders", True, data=params + ) + + async def futures_coin_get_all_orders(self, **params): + return await self._request_futures_coin_api( + "get", "allOrders", signed=True, data=params + ) + + async def futures_coin_cancel_order(self, **params): + return await self._request_futures_coin_api( + "delete", "order", signed=True, data=params + ) + + async def futures_coin_cancel_all_open_orders(self, **params): + return await self._request_futures_coin_api( + "delete", "allOpenOrders", signed=True, data=params + ) + + async def futures_coin_cancel_orders(self, **params): + return await self._request_futures_coin_api( + "delete", "batchOrders", True, data=params + ) + + async def futures_coin_account_balance(self, **params): + return await self._request_futures_coin_api( + "get", "balance", signed=True, data=params + ) + + async def futures_coin_account(self, **params): + return await self._request_futures_coin_api( + "get", "account", signed=True, data=params + ) + + async def futures_coin_change_leverage(self, **params): + return await self._request_futures_coin_api( + "post", "leverage", signed=True, data=params + ) + + async def futures_coin_change_margin_type(self, **params): + return await self._request_futures_coin_api( + "post", "marginType", signed=True, data=params + ) + + async def futures_coin_change_position_margin(self, **params): + return await self._request_futures_coin_api( + "post", "positionMargin", True, data=params + ) + + async def futures_coin_position_margin_history(self, **params): + return await self._request_futures_coin_api( + "get", "positionMargin/history", True, data=params + ) + + async def futures_coin_position_information(self, **params): + return await self._request_futures_coin_api( + "get", "positionRisk", True, data=params + ) + + async def futures_coin_account_trades(self, **params): + return await self._request_futures_coin_api( + "get", "userTrades", True, data=params + ) + + async def futures_coin_income_history(self, **params): + return await self._request_futures_coin_api("get", "income", True, data=params) + + async def futures_coin_change_position_mode(self, **params): + return await self._request_futures_coin_api( + "post", "positionSide/dual", True, data=params + ) + + async def futures_coin_get_position_mode(self, **params): + return await self._request_futures_coin_api( + "get", "positionSide/dual", True, data=params + ) + + async def futures_coin_stream_get_listen_key(self): + res = await self._request_futures_coin_api( + "post", "listenKey", signed=False, data={} + ) + return res["listenKey"] + + async def futures_coin_stream_keepalive(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_futures_coin_api( + "put", "listenKey", signed=False, data=params + ) + + async def futures_coin_stream_close(self, listenKey): + params = {"listenKey": listenKey} + return await self._request_futures_coin_api( + "delete", "listenKey", signed=False, data=params + ) + + async def get_all_coins_info(self, **params): + return await self._request_margin_api( + "get", "capital/config/getall", True, data=params + ) + + async def get_account_snapshot(self, **params): + return await self._request_margin_api( + "get", "accountSnapshot", True, data=params + ) + + async def disable_fast_withdraw_switch(self, **params): + return await self._request_margin_api( + "post", "disableFastWithdrawSwitch", True, data=params + ) + + async def enable_fast_withdraw_switch(self, **params): + return await self._request_margin_api( + "post", "enableFastWithdrawSwitch", True, data=params + ) + + """ + ==================================================================================================================== + Options API + ==================================================================================================================== + """ + + # Quoting interface endpoints + + async def options_ping(self): + return await self._request_options_api("get", "ping") + + async def options_time(self): + return await self._request_options_api("get", "time") + + async def options_info(self): + return await self._request_options_api("get", "optionInfo") + + async def options_exchange_info(self): + return await self._request_options_api("get", "exchangeInfo") + + async def options_index_price(self, **params): + return await self._request_options_api("get", "index", data=params) + + async def options_price(self, **params): + return await self._request_options_api("get", "ticker", data=params) + + async def options_mark_price(self, **params): + return await self._request_options_api("get", "mark", data=params) + + async def options_order_book(self, **params): + return await self._request_options_api("get", "depth", data=params) + + async def options_klines(self, **params): + return await self._request_options_api("get", "klines", data=params) + + async def options_recent_trades(self, **params): + return await self._request_options_api("get", "trades", data=params) + + async def options_historical_trades(self, **params): + return await self._request_options_api("get", "historicalTrades", data=params) + + # Account and trading interface endpoints + + async def options_account_info(self, **params): + return await self._request_options_api( + "get", "account", signed=True, data=params + ) + + async def options_funds_transfer(self, **params): + return await self._request_options_api( + "post", "transfer", signed=True, data=params + ) + + async def options_positions(self, **params): + return await self._request_options_api( + "get", "position", signed=True, data=params + ) + + async def options_bill(self, **params): + return await self._request_options_api("post", "bill", signed=True, data=params) + + async def options_place_order(self, **params): + if "clientOrderId" not in params: + params["clientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_options_api( + "post", "order", signed=True, data=params + ) + + async def options_place_batch_order(self, **params): + for order in params["batchOrders"]: + if "newClientOrderId" not in order: + order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_options_api( + "post", "batchOrders", signed=True, data=params + ) + + async def options_cancel_order(self, **params): + return await self._request_options_api( + "delete", "order", signed=True, data=params + ) + + async def options_cancel_batch_order(self, **params): + return await self._request_options_api( + "delete", "batchOrders", signed=True, data=params + ) + + async def options_cancel_all_orders(self, **params): + return await self._request_options_api( + "delete", "allOpenOrders", signed=True, data=params + ) + + async def options_query_order(self, **params): + return await self._request_options_api("get", "order", signed=True, data=params) + + async def options_query_pending_orders(self, **params): + return await self._request_options_api( + "get", "openOrders", signed=True, data=params + ) + + async def options_query_order_history(self, **params): + return await self._request_options_api( + "get", "historyOrders", signed=True, data=params + ) + + async def options_user_trades(self, **params): + return await self._request_options_api( + "get", "userTrades", signed=True, data=params + ) + + # Fiat Endpoints + + async def get_fiat_deposit_withdraw_history(self, **params): + return await self._request_margin_api( + "get", "fiat/orders", signed=True, data=params + ) + + async def get_fiat_payments_history(self, **params): + return await self._request_margin_api( + "get", "fiat/payments", signed=True, data=params + ) + + # C2C Endpoints + + async def get_c2c_trade_history(self, **params): + return await self._request_margin_api( + "get", "c2c/orderMatch/listUserOrderHistory", signed=True, data=params + ) + + # Pay Endpoints + + async def get_pay_trade_history(self, **params): + return await self._request_margin_api( + "get", "pay/transactions", signed=True, data=params + ) + + get_pay_trade_history.__doc__ = Client.get_pay_trade_history.__doc__ + + # Convert Endpoints + + async def get_convert_trade_history(self, **params): + return await self._request_margin_api( + "get", "convert/tradeFlow", signed=True, data=params + ) + + get_convert_trade_history.__doc__ = Client.get_convert_trade_history.__doc__ + + async def convert_request_quote(self, **params): + return await self._request_margin_api( + "post", "convert/getQuote", signed=True, data=params + ) + + convert_request_quote.__doc__ = Client.convert_request_quote.__doc__ + + async def convert_accept_quote(self, **params): + return await self._request_margin_api( + "post", "convert/acceptQuote", signed=True, data=params + ) + + convert_accept_quote.__doc__ = Client.convert_accept_quote.__doc__ + + """ + ==================================================================================================================== + PortfolioMargin API + ==================================================================================================================== + """ + + async def papi_get_balance(self, **params): + return await self._request_papi_api("get", "balance", signed=True, data=params) + + async def papi_get_account(self, **params): + return await self._request_papi_api("get", "account", signed=True, data=params) + + async def papi_get_margin_max_borrowable(self, **params): + return await self._request_papi_api( + "get", "margin/maxBorrowable", signed=True, data=params + ) + + async def papi_get_margin_max_withdraw(self, **params): + return await self._request_papi_api( + "get", "margin/maxWithdraw", signed=True, data=params + ) + + async def papi_get_um_position_risk(self, **params): + return await self._request_papi_api( + "get", "um/positionRisk", signed=True, data=params + ) + + async def papi_get_cm_position_risk(self, **params): + return await self._request_papi_api( + "get", "cm/positionRisk", signed=True, data=params + ) + + async def papi_set_um_leverage(self, **params): + return await self._request_papi_api( + "post", "um/leverage", signed=True, data=params + ) + + async def papi_set_cm_leverage(self, **params): + return await self._request_papi_api( + "post", "cm/leverage", signed=True, data=params + ) + + async def papi_change_um_position_side_dual(self, **params): + return await self._request_papi_api( + "post", "um/positionSide/dual", signed=True, data=params + ) + + async def papi_get_um_position_side_dual(self, **params): + return await self._request_papi_api( + "get", "um/positionSide/dual", signed=True, data=params + ) + + async def papi_get_cm_position_side_dual(self, **params): + return await self._request_papi_api( + "get", "cm/positionSide/dual", signed=True, data=params + ) + + async def papi_get_um_leverage_bracket(self, **params): + return await self._request_papi_api( + "get", "um/leverageBracket", signed=True, data=params + ) + + async def papi_get_cm_leverage_bracket(self, **params): + return await self._request_papi_api( + "get", "cm/leverageBracket", signed=True, data=params + ) + + async def papi_get_um_api_trading_status(self, **params): + return await self._request_papi_api( + "get", "um/apiTradingStatus", signed=True, data=params + ) + + async def papi_get_um_comission_rate(self, **params): + return await self._request_papi_api( + "get", "um/commissionRate", signed=True, data=params + ) + + async def papi_get_cm_comission_rate(self, **params): + return await self._request_papi_api( + "get", "cm/commissionRate", signed=True, data=params + ) + + async def papi_get_margin_margin_loan(self, **params): + return await self._request_papi_api( + "get", "margin/marginLoan", signed=True, data=params + ) + + async def papi_get_margin_repay_loan(self, **params): + return await self._request_papi_api( + "get", "margin/repayLoan", signed=True, data=params + ) + + async def papi_get_repay_futures_switch(self, **params): + return await self._request_papi_api( + "get", "repay-futures-switch", signed=True, data=params + ) + + async def papi_repay_futures_switch(self, **params): + return await self._request_papi_api( + "post", "repay-futures-switch", signed=True, data=params + ) + + async def papi_get_margin_interest_history(self, **params): + return await self._request_papi_api( + "get", "margin/marginInterestHistory", signed=True, data=params + ) + + async def papi_repay_futures_negative_balance(self, **params): + return await self._request_papi_api( + "post", "repay-futures-negative-balance", signed=True, data=params + ) + + async def papi_get_portfolio_interest_history(self, **params): + return await self._request_papi_api( + "get", "portfolio/interest-history", signed=True, data=params + ) + + async def papi_fund_auto_collection(self, **params): + return await self._request_papi_api( + "post", "auto-collection", signed=True, data=params + ) + + async def papi_fund_asset_collection(self, **params): + return await self._request_papi_api( + "post", "asset-collection", signed=True, data=params + ) + + async def papi_bnb_transfer(self, **params): + return await self._request_papi_api( + "post", "bnb-transfer", signed=True, data=params + ) + + async def papi_get_um_income_history(self, **params): + return await self._request_papi_api( + "get", "um/income", signed=True, data=params + ) + + async def papi_get_cm_income_history(self, **params): + return await self._request_papi_api( + "get", "cm/income", signed=True, data=params + ) + + async def papi_get_um_account(self, **params): + return await self._request_papi_api( + "get", "um/account", signed=True, data=params + ) + + async def papi_get_um_account_v2(self, **params): + return await self._request_papi_api( + "get", "um/account", version=2, signed=True, data=params + ) + + async def papi_get_cm_account(self, **params): + return await self._request_papi_api( + "get", "cm/account", signed=True, data=params + ) + + async def papi_get_um_account_config(self, **params): + return await self._request_papi_api( + "get", "um/accountConfig", signed=True, data=params + ) + + async def papi_get_um_symbol_config(self, **params): + return await self._request_papi_api( + "get", "um/symbolConfig", signed=True, data=params + ) + + async def papi_get_um_trade_asyn(self, **params): + return await self._request_papi_api( + "get", "um/trade/asyn", signed=True, data=params + ) + + async def papi_get_um_trade_asyn_id(self, **params): + return await self._request_papi_api( + "get", "um/trade/asyn/id", signed=True, data=params + ) + + async def papi_get_um_order_asyn(self, **params): + return await self._request_papi_api( + "get", "um/order/asyn", signed=True, data=params + ) + + async def papi_get_um_order_asyn_id(self, **params): + return await self._request_papi_api( + "get", "um/order/asyn/id", signed=True, data=params + ) + + async def papi_get_um_income_asyn(self, **params): + return await self._request_papi_api( + "get", "um/income/asyn", signed=True, data=params + ) + + async def papi_get_um_income_asyn_id(self, **params): + return await self._request_papi_api( + "get", "um/income/asyn/id", signed=True, data=params + ) + + async def papi_ping(self, **params): + return await self._request_papi_api("get", "ping", signed=False, data=params) + + # papi trading endpoints + + async def papi_create_um_order(self, **params): + """Place new UM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade + + :returns: API response + + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api( + "post", "um/order", signed=True, data=params + ) + + async def papi_create_um_conditional_order(self, **params): + """Place new UM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Conditional-Order + + :returns: API response + + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api( + "post", "um/conditional/order", signed=True, data=params + ) + + async def papi_create_cm_order(self, **params): + """Place new CM order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Order + + :returns: API response + + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api( + "post", "cm/order", signed=True, data=params + ) + + async def papi_create_cm_conditional_order(self, **params): + """Place new CM Conditional order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Conditional-Order + + :returns: API response + + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api( + "post", "cm/conditional/order", signed=True, data=params + ) + + async def papi_create_margin_order(self, **params): + """New Margin Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-Margin-Order + + :returns: API response + + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._request_papi_api( + "post", "margin/order", signed=True, data=params + ) + + async def papi_margin_loan(self, **params): + """Apply for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Borrow + + :returns: API response + + """ + return await self._request_papi_api( + "post", "marginLoan", signed=True, data=params + ) + + async def papi_repay_loan(self, **params): + """Repay for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay + + :returns: API response + + """ + return await self._request_papi_api( + "post", "repayLoan", signed=True, data=params + ) + + async def papi_margin_order_oco(self, **params): + """Send in a new OCO for a margin account. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-New-OCO + + :returns: API response + + """ + return await self._request_papi_api( + "post", "margin/order/oco", signed=True, data=params + ) + + async def papi_cancel_um_order(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Order + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "um/order", signed=True, data=params + ) + + async def papi_cancel_um_all_open_orders(self, **params): + """Cancel an active UM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "um/allOpenOrders", signed=True, data=params + ) + + async def papi_cancel_um_conditional_order(self, **params): + """Cancel UM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "um/conditional/order", signed=True, data=params + ) + + async def papi_cancel_um_conditional_all_open_orders(self, **params): + """Cancel All UM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "um/conditional/allOpenOrders", signed=True, data=params + ) + + async def papi_cancel_cm_order(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Order + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "cm/order", signed=True, data=params + ) + + async def papi_cancel_cm_all_open_orders(self, **params): + """Cancel an active CM LIMIT order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "cm/allOpenOrders", signed=True, data=params + ) + + async def papi_cancel_cm_conditional_order(self, **params): + """Cancel CM Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "cm/conditional/order", signed=True, data=params + ) + + async def papi_cancel_cm_conditional_all_open_orders(self, **params): + """Cancel All CM Open Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "cm/conditional/allOpenOrders", signed=True, data=params + ) + + async def papi_cancel_margin_order(self, **params): + """Cancel Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "margin/order", signed=True, data=params + ) + + async def papi_cancel_margin_order_list(self, **params): + """Cancel Margin Account OCO Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-OCO-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "margin/orderList", signed=True, data=params + ) + + async def papi_cancel_margin_all_open_orders(self, **params): + """Cancel Margin Account All Open Orders on a Symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-All-Open-Orders-on-a-Symbol + + :returns: API response + + """ + return await self._request_papi_api( + "delete", "margin/allOpenOrders", signed=True, data=params + ) + + async def papi_modify_um_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-UM-Order + + :returns: API response + + """ + return await self._request_papi_api("put", "um/order", signed=True, data=params) + + async def papi_modify_cm_order(self, **params): + """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-CM-Order + + :returns: API response + + """ + return await self._request_papi_api("put", "cm/order", signed=True, data=params) + + async def papi_get_um_order(self, **params): + """Check an UM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return await self._request_papi_api("get", "um/order", signed=True, data=params) + + async def papi_get_um_all_orders(self, **params): + """Get all account UM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/allOrders", signed=True, data=params + ) + + async def papi_get_um_open_order(self, **params): + """Query current UM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/openOrder", signed=True, data=params + ) + + async def papi_get_um_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/openOrders", signed=True, data=params + ) + + async def papi_get_um_conditional_all_orders(self, **params): + """Query All UM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/conditional/allOrders", signed=True, data=params + ) + + async def papi_get_um_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/conditional/openOrders", signed=True, data=params + ) + + async def papi_get_um_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/conditional/openOrder", signed=True, data=params + ) + + async def papi_get_um_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Conditional-Order-History + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/conditional/orderHistory", signed=True, data=params + ) + + async def papi_get_cm_order(self, **params): + """Check an CM order's status. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return await self._request_papi_api("get", "cm/order", signed=True, data=params) + + async def papi_get_cm_all_orders(self, **params): + """Get all account CM orders; active, canceled, or filled. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/allOrders", signed=True, data=params + ) + + async def papi_get_cm_open_order(self, **params): + """Query current CM open order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/openOrder", signed=True, data=params + ) + + async def papi_get_cm_open_orders(self, **params): + """Get all open orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/openOrders", signed=True, data=params + ) + + async def papi_get_cm_conditional_all_orders(self, **params): + """Query All CM Conditional Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/conditional/allOrders", signed=True, data=params + ) + + async def papi_get_cm_conditional_open_orders(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Conditional-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/conditional/openOrders", signed=True, data=params + ) + + async def papi_get_cm_conditional_open_order(self, **params): + """Query Current UM Open Conditional Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Conditional-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/conditional/openOrder", signed=True, data=params + ) + + async def papi_get_cm_conditional_order_history(self, **params): + """Get all open conditional orders on a symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Conditional-Order-History + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/conditional/orderHistory", signed=True, data=params + ) + + async def papi_get_um_force_orders(self, **params): + """Query User's UM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-UM-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/forceOrders", signed=True, data=params + ) + + async def papi_get_cm_force_orders(self, **params): + """Query User's CM Force Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-CM-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/forceOrders", signed=True, data=params + ) + + async def papi_get_um_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Modify-Order-History + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/orderAmendment", signed=True, data=params + ) + + async def papi_get_cm_order_amendment(self, **params): + """Get order modification history. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Modify-Order-History + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/orderAmendment", signed=True, data=params + ) + + async def papi_get_margin_force_orders(self, **params): + """Query user's margin force orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-Margin-Force-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/forceOrders", signed=True, data=params + ) + + async def papi_get_um_user_trades(self, **params): + """Get trades for a specific account and UM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/userTrades", signed=True, data=params + ) + + async def papi_get_cm_user_trades(self, **params): + """Get trades for a specific account and CM symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/userTrades", signed=True, data=params + ) + + async def papi_get_um_adl_quantile(self, **params): + """Query UM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/adlQuantile", signed=True, data=params + ) + + async def papi_get_cm_adl_quantile(self, **params): + """Query CM Position ADL Quantile Estimation. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Position-ADL-Quantile-Estimation + + :returns: API response + + """ + return await self._request_papi_api( + "get", "cm/adlQuantile", signed=True, data=params + ) + + async def papi_set_um_fee_burn(self, **params): + """Change user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off ) on EVERY symbol. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Toggle-BNB-Burn-On-UM-Futures-Trade + + :returns: API response + + """ + return await self._request_papi_api( + "post", "um/feeBurn", signed=True, data=params + ) + + async def papi_get_um_fee_burn(self, **params): + """Get user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off). + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Get-UM-Futures-BNB-Burn-Status + + :returns: API response + + """ + return await self._request_papi_api( + "get", "um/feeBurn", signed=True, data=params + ) + + async def papi_get_margin_order(self, **params): + """Query Margin Account Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/order", signed=True, data=params + ) + + async def papi_get_margin_open_orders(self, **params): + """Query Current Margin Open Order. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/openOrders", signed=True, data=params + ) + + async def papi_get_margin_all_orders(self, **params): + """Query All Margin Account Orders. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Margin-Account-Orders + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/allOrders", signed=True, data=params + ) + + async def papi_get_margin_order_list(self, **params): + """Retrieves a specific OCO based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-OCO + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/orderList", signed=True, data=params + ) + + async def papi_get_margin_all_order_list(self, **params): + """Query all OCO for a specific margin account based on provided optional parameters. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-all-OCO + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/allOrderList", signed=True, data=params + ) + + async def papi_get_margin_open_order_list(self, **params): + """Query Margin Account's Open OCO. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Open-OCO + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/openOrderList", signed=True, data=params + ) + + async def papi_get_margin_my_trades(self, **params): + """Margin Account Trade List. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api( + "get", "margin/myTrades", signed=True, data=params + ) + + async def papi_get_margin_repay_debt(self, **params): + """Repay debt for a margin loan. + + https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List + + :returns: API response + + """ + return await self._request_papi_api( + "post", "margin/repay-debt", signed=True, data=params + ) + + async def create_oco_order(self, **params): + if "listClientOrderId" not in params: + params["listClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._post("orderList/oco", True, data=params) + + ############################################################ + # WebSocket API methods + ############################################################ + + async def ws_create_test_order(self, **params): + """Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. + https://binance-docs.github.io/apidocs/websocket_api/en/#test-new-order-trade + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param type: required + :type type: str + :param timeInForce: required if limit order + :type timeInForce: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: The number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + .. code-block:: python + {} + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + + return await self._ws_api_request("order.test", True, params) + + async def ws_create_order(self, **params): + """Create an order via WebSocket. + https://binance-docs.github.io/apidocs/websocket_api/en/#place-new-order-trade + :param id: The request ID to be used. By default uuid22() is used. + :param symbol: The symbol to create an order for + :param side: BUY or SELL + :param type: Order type (e.g., LIMIT, MARKET) + :param quantity: The amount to buy or sell + :param kwargs: Additional order parameters + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + + return await self._ws_api_request("order.place", True, params) + + async def ws_order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + """Send in a new limit order + Any order with an icebergQty MUST have timeInForce set to GTC. + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({ + "type": self.ORDER_TYPE_LIMIT, + "timeInForce": timeInForce, + }) + return await self.ws_create_order(**params) + + async def ws_order_limit_buy( + self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params + ): + """Send in a new limit buy order + Any order with an icebergQty MUST have timeInForce set to GTC. + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param stopPrice: Used with stop orders + :type stopPrice: decimal + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({ + "side": self.SIDE_BUY, + }) + return await self.ws_order_limit(timeInForce=timeInForce, **params) + + async def ws_order_limit_sell( + self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params + ): + """Send in a new limit sell order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param stopPrice: Used with stop orders + :type stopPrice: decimal + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_SELL}) + return await self.ws_order_limit(timeInForce=timeInForce, **params) + + async def ws_order_market(self, **params): + """Send in a new market order + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) + of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"type": self.ORDER_TYPE_MARKET}) + return await self.ws_create_order(**params) + + async def ws_order_market_buy(self, **params): + """Send in a new market buy order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: the amount the user wants to spend of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_BUY}) + return await self.ws_order_market(**params) + + async def ws_order_market_sell(self, **params): + """Send in a new market sell order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: the amount the user wants to receive of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_SELL}) + return await self.ws_order_market(**params) + + async def ws_get_order(self, **params): + """Check an order's status. Either orderId or origClientOrderId must be sent. + https://binance-docs.github.io/apidocs/websocket_api/en/#query-order-user_data + :param symbol: required + :type symbol: str + :param orderId: The unique order id + :type orderId: int + :param origClientOrderId: optional + :type origClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + """ + + return await self._ws_api_request("order.status", True, params) + + async def ws_cancel_order(self, **params): + return await self._ws_api_request("order.cancel", True, params) + + cancel_order.__doc__ = cancel_order.__doc__ + + async def cancel_all_open_orders(self, **params): + return await self._delete("openOrders", True, data=params) + + async def cancel_replace_order(self, **params): + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() + return await self._post("order/cancelReplace", signed=True, data=params) + + async def ws_cancel_and_replace_order(self, **params): + return await self._ws_api_request("order.cancelReplace", True, params) + + async def ws_get_open_orders(self, **params): + return await self._ws_api_request("openOrders.status", True, params) + + async def ws_cancel_all_open_orders(self, **params): + return await self._ws_api_request("openOrders.cancelAll", True, params) + + async def ws_create_oco_order(self, **params): + return await self._ws_api_request("orderList.place.oco", True, params) + + async def ws_create_oto_order(self, **params): + return await self._ws_api_request("orderList.place.oto", True, params) + + async def ws_create_otoco_order(self, **params): + return await self._ws_api_request("orderList.place.otoco", True, params) + + async def ws_get_oco_order(self, **params): + return await self._ws_api_request("orderList.status", True, params) + + async def ws_cancel_oco_order(self, **params): + return await self._ws_api_request("orderList.cancel", True, params) + + async def ws_get_oco_open_orders(self, **params): + return await self._ws_api_request("openOrderLists.status", True, params) + + async def ws_create_sor_order(self, **params): + return await self._ws_api_request("sor.order.place", True, params) + + async def ws_create_test_sor_order(self, **params): + return await self._ws_api_request("sor.order.test", True, params) + + async def ws_get_account(self, **params): + return await self._ws_api_request("account.status", True, params) + + async def ws_get_account_rate_limits_orders(self, **params): + return await self._ws_api_request("account.rateLimits.orders", True, params) + + async def ws_get_all_orders(self, **params): + return await self._ws_api_request("allOrders", True, params) + + async def ws_get_my_trades(self, **params): + return await self._ws_api_request("myTrades", True, params) + + async def ws_get_prevented_matches(self, **params): + return await self._ws_api_request("myPreventedMatches", True, params) + + async def ws_get_allocations(self, **params): + return await self._ws_api_request("myAllocations", True, params) + + async def ws_get_commission_rates(self, **params): + return await self._ws_api_request("account.commission", True, params) + + async def ws_get_order_book(self, **params): + return await self._ws_api_request("depth", False, params) + + async def ws_get_recent_trades(self, **params): + return await self._ws_api_request("trades.recent", False, params) + + async def ws_get_historical_trades(self, **params): + return await self._ws_api_request("trades.historical", False, params) + + async def ws_get_aggregate_trades(self, **params): + return await self._ws_api_request("trades.aggregate", False, params) + + async def ws_get_klines(self, **params): + return await self._ws_api_request("klines", False, params) + + async def ws_get_uiKlines(self, **params): + return await self._ws_api_request("uiKlines", False, params) + + async def ws_get_avg_price(self, **params): + return await self._ws_api_request("avgPrice", False, params) + + async def ws_get_ticker(self, **params): + return await self._ws_api_request("ticker.24hr", False, params) + + async def ws_get_trading_day_ticker(self, **params): + return await self._ws_api_request("ticker.tradingDay", False, params) + + async def ws_get_symbol_ticker_window(self, **params): + return await self._ws_api_request("ticker", False, params) + + async def ws_get_symbol_ticker(self, **params): + return await self._ws_api_request("ticker.price", False, params) + + async def ws_get_orderbook_ticker(self, **params): + return await self._ws_api_request("ticker.book", False, params) + + async def ws_ping(self, **params): + return await self._ws_api_request("ping", False, params) + + async def ws_get_time(self, **params): + return await self._ws_api_request("time", False, params) + + async def ws_get_exchange_info(self, **params): + return await self._ws_api_request("exchangeInfo", False, params) + + #################################################### + # FUTURES WS API Endpoints + #################################################### + async def ws_futures_get_order_book(self, **params): + """ + Get the order book for a symbol + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api + """ + return await self._ws_futures_api_request("depth", False, params) + + async def ws_futures_get_all_tickers(self, **params): + """ + Latest price for a symbol or symbols + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Symbol-Price-Ticker + """ + return await self._ws_futures_api_request("ticker.price", False, params) + + async def ws_futures_get_order_book_ticker(self, **params): + """ + Best price/qty on the order book for a symbol or symbols. + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Symbol-Order-Book-Ticker + """ + return await self._ws_futures_api_request("ticker.book", False, params) + + async def ws_futures_create_order(self, **params): + """ + Send in a new order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return await self._ws_futures_api_request("order.place", True, params) + + async def ws_futures_edit_order(self, **params): + """ + Edit an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Modify-Order + """ + return await self._ws_futures_api_request("order.modify", True, params) + + async def ws_futures_cancel_order(self, **params): + """ + cancel an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Order + """ + return await self._ws_futures_api_request("order.cancel", True, params) + + async def ws_futures_get_order(self, **params): + """ + Get an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Query-Order + """ + return await self._ws_futures_api_request("order.status", True, params) + + async def ws_futures_v2_account_position(self, **params): + """ + Get current position information(only symbol that has position or open orders will be return awaited). + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Info-V2 + """ + return await self._ws_futures_api_request("v2/account.position", True, params) + + async def ws_futures_account_position(self, **params): + """ + Get current position information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Information + """ + return await self._ws_futures_api_request("account.position", True, params) + + async def ws_futures_v2_account_balance(self, **params): + """ + Get current account information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api#api-description + """ + return await self._ws_futures_api_request("v2/account.balance", True, params) + + async def ws_futures_account_balance(self, **params): + """ + Get current account information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Futures-Account-Balance + """ + return await self._ws_futures_api_request("account.balance", True, params) + + async def ws_futures_v2_account_status(self, **params): + """ + Get current account information. User in single-asset/ multi-assets mode will see different value, see comments in response section for detail. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Account-Information-V2 + """ + return await self._ws_futures_api_request("v2/account.status", True, params) + + async def ws_futures_account_status(self, **params): + """ + Get current account information. User in single-asset/ multi-assets mode will see different value, see comments in response section for detail. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Account-Information + """ + return await self._ws_futures_api_request("account.status", True, params) diff --git a/binance/base_client.py b/binance/base_client.py new file mode 100644 index 000000000..9c6bd5c35 --- /dev/null +++ b/binance/base_client.py @@ -0,0 +1,473 @@ +from base64 import b64encode +from pathlib import Path +import random +from typing import Dict, Optional, List, Tuple, Union, Any + +import asyncio +import hashlib +import hmac +import time +from Crypto.PublicKey import RSA, ECC +from Crypto.Hash import SHA256 +from Crypto.Signature import pkcs1_15, eddsa +from operator import itemgetter +from urllib.parse import urlencode + +from binance.ws.websocket_api import WebsocketAPI + +from .helpers import get_loop + + +class BaseClient: + API_URL = "https://api{}.binance.{}/api" + API_TESTNET_URL = "https://testnet.binance.vision/api" + MARGIN_API_URL = "https://api{}.binance.{}/sapi" + WEBSITE_URL = "https://www.binance.{}" + FUTURES_URL = "https://fapi.binance.{}/fapi" + FUTURES_TESTNET_URL = "https://testnet.binancefuture.com/fapi" + FUTURES_DATA_URL = "https://fapi.binance.{}/futures/data" + FUTURES_DATA_TESTNET_URL = "https://testnet.binancefuture.com/futures/data" + FUTURES_COIN_URL = "https://dapi.binance.{}/dapi" + FUTURES_COIN_TESTNET_URL = "https://testnet.binancefuture.com/dapi" + FUTURES_COIN_DATA_URL = "https://dapi.binance.{}/futures/data" + FUTURES_COIN_DATA_TESTNET_URL = "https://testnet.binancefuture.com/futures/data" + OPTIONS_URL = "https://eapi.binance.{}/eapi" + OPTIONS_TESTNET_URL = "https://testnet.binanceops.{}/eapi" + PAPI_URL = "https://papi.binance.{}/papi" + WS_API_URL = "wss://ws-api.binance.{}/ws-api/v3" + WS_API_TESTNET_URL = "wss://testnet.binance.vision/ws-api/v3" + WS_FUTURES_URL = "wss://ws-fapi.binance.{}/ws-fapi/v1" + WS_FUTURES_TESTNET_URL = "wss://testnet.binancefuture.com/ws-fapi/v1" + PUBLIC_API_VERSION = "v1" + PRIVATE_API_VERSION = "v3" + MARGIN_API_VERSION = "v1" + MARGIN_API_VERSION2 = "v2" + MARGIN_API_VERSION3 = "v3" + MARGIN_API_VERSION4 = "v4" + FUTURES_API_VERSION = "v1" + FUTURES_API_VERSION2 = "v2" + FUTURES_API_VERSION3 = "v3" + OPTIONS_API_VERSION = "v1" + PORTFOLIO_API_VERSION = "v1" + PORTFOLIO_API_VERSION2 = "v2" + + BASE_ENDPOINT_DEFAULT = "" + BASE_ENDPOINT_1 = "1" + BASE_ENDPOINT_2 = "2" + BASE_ENDPOINT_3 = "3" + BASE_ENDPOINT_4 = "4" + + REQUEST_TIMEOUT: float = 10 + + SYMBOL_TYPE_SPOT = "SPOT" + + ORDER_STATUS_NEW = "NEW" + ORDER_STATUS_PARTIALLY_FILLED = "PARTIALLY_FILLED" + ORDER_STATUS_FILLED = "FILLED" + ORDER_STATUS_CANCELED = "CANCELED" + ORDER_STATUS_PENDING_CANCEL = "PENDING_CANCEL" + ORDER_STATUS_REJECTED = "REJECTED" + ORDER_STATUS_EXPIRED = "EXPIRED" + + KLINE_INTERVAL_1SECOND = "1s" + KLINE_INTERVAL_1MINUTE = "1m" + KLINE_INTERVAL_3MINUTE = "3m" + KLINE_INTERVAL_5MINUTE = "5m" + KLINE_INTERVAL_15MINUTE = "15m" + KLINE_INTERVAL_30MINUTE = "30m" + KLINE_INTERVAL_1HOUR = "1h" + KLINE_INTERVAL_2HOUR = "2h" + KLINE_INTERVAL_4HOUR = "4h" + KLINE_INTERVAL_6HOUR = "6h" + KLINE_INTERVAL_8HOUR = "8h" + KLINE_INTERVAL_12HOUR = "12h" + KLINE_INTERVAL_1DAY = "1d" + KLINE_INTERVAL_3DAY = "3d" + KLINE_INTERVAL_1WEEK = "1w" + KLINE_INTERVAL_1MONTH = "1M" + + SIDE_BUY = "BUY" + SIDE_SELL = "SELL" + + ORDER_TYPE_LIMIT = "LIMIT" + ORDER_TYPE_MARKET = "MARKET" + ORDER_TYPE_STOP_LOSS = "STOP_LOSS" + ORDER_TYPE_STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT" + ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT" + ORDER_TYPE_TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT" + ORDER_TYPE_LIMIT_MAKER = "LIMIT_MAKER" + + FUTURE_ORDER_TYPE_LIMIT = "LIMIT" + FUTURE_ORDER_TYPE_MARKET = "MARKET" + FUTURE_ORDER_TYPE_STOP = "STOP" + FUTURE_ORDER_TYPE_STOP_MARKET = "STOP_MARKET" + FUTURE_ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT" + FUTURE_ORDER_TYPE_TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET" + FUTURE_ORDER_TYPE_LIMIT_MAKER = "LIMIT_MAKER" + + TIME_IN_FORCE_GTC = "GTC" # Good till cancelled + TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel + TIME_IN_FORCE_FOK = "FOK" # Fill or kill + + ORDER_RESP_TYPE_ACK = "ACK" + ORDER_RESP_TYPE_RESULT = "RESULT" + ORDER_RESP_TYPE_FULL = "FULL" + + # For accessing the data returned by Client.aggregate_trades(). + AGG_ID = "a" + AGG_PRICE = "p" + AGG_QUANTITY = "q" + AGG_FIRST_TRADE_ID = "f" + AGG_LAST_TRADE_ID = "l" + AGG_TIME = "T" + AGG_BUYER_MAKES = "m" + AGG_BEST_MATCH = "M" + + # new asset transfer api enum + SPOT_TO_FIAT = "MAIN_C2C" + SPOT_TO_USDT_FUTURE = "MAIN_UMFUTURE" + SPOT_TO_COIN_FUTURE = "MAIN_CMFUTURE" + SPOT_TO_MARGIN_CROSS = "MAIN_MARGIN" + SPOT_TO_MINING = "MAIN_MINING" + FIAT_TO_SPOT = "C2C_MAIN" + FIAT_TO_USDT_FUTURE = "C2C_UMFUTURE" + FIAT_TO_MINING = "C2C_MINING" + USDT_FUTURE_TO_SPOT = "UMFUTURE_MAIN" + USDT_FUTURE_TO_FIAT = "UMFUTURE_C2C" + USDT_FUTURE_TO_MARGIN_CROSS = "UMFUTURE_MARGIN" + COIN_FUTURE_TO_SPOT = "CMFUTURE_MAIN" + MARGIN_CROSS_TO_SPOT = "MARGIN_MAIN" + MARGIN_CROSS_TO_USDT_FUTURE = "MARGIN_UMFUTURE" + MINING_TO_SPOT = "MINING_MAIN" + MINING_TO_USDT_FUTURE = "MINING_UMFUTURE" + MINING_TO_FIAT = "MINING_C2C" + + ## order ids + SPOT_ORDER_PREFIX = "x-HNA2TXFJ" + CONTRACT_ORDER_PREFIX = "x-Cb7ytekJ" + + def __init__( + self, + api_key: Optional[str] = None, + api_secret: Optional[str] = None, + requests_params: Optional[Dict[str, Any]] = None, + tld: str = "com", + base_endpoint: str = BASE_ENDPOINT_DEFAULT, + testnet: bool = False, + private_key: Optional[Union[str, Path]] = None, + private_key_pass: Optional[str] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ): + """Binance API Client constructor + + :param api_key: Api Key + :type api_key: str. + :param api_secret: Api Secret + :type api_secret: str. + :param requests_params: optional - Dictionary of requests params to use for all calls + :type requests_params: dict. + :param testnet: Use testnet environment - only available for vanilla options at the moment + :type testnet: bool + :param private_key: Path to private key, or string of file contents + :type private_key: optional - str or Path + :param private_key_pass: Password of private key + :type private_key_pass: optional - str + + """ + + self.tld = tld + self.API_URL = self.API_URL.format(base_endpoint, tld) + self.MARGIN_API_URL = self.MARGIN_API_URL.format(base_endpoint, tld) + self.WEBSITE_URL = self.WEBSITE_URL.format(tld) + self.FUTURES_URL = self.FUTURES_URL.format(tld) + self.FUTURES_DATA_URL = self.FUTURES_DATA_URL.format(tld) + self.FUTURES_COIN_URL = self.FUTURES_COIN_URL.format(tld) + self.FUTURES_COIN_DATA_URL = self.FUTURES_COIN_DATA_URL.format(tld) + self.OPTIONS_URL = self.OPTIONS_URL.format(tld) + self.OPTIONS_TESTNET_URL = self.OPTIONS_TESTNET_URL.format(tld) + + self.API_KEY = api_key + self.API_SECRET = api_secret + self._is_rsa = False + self.PRIVATE_KEY: Any = self._init_private_key(private_key, private_key_pass) + self.session = self._init_session() + self._requests_params = requests_params + self.response = None + self.testnet = testnet + self.timestamp_offset = 0 + ws_api_url = self.WS_API_TESTNET_URL if testnet else self.WS_API_URL.format(tld) + self.ws_api = WebsocketAPI(url=ws_api_url, tld=tld) + ws_future_url = ( + self.WS_FUTURES_TESTNET_URL if testnet else self.WS_FUTURES_URL.format(tld) + ) + self.ws_future = WebsocketAPI(url=ws_future_url, tld=tld) + self.loop = loop or get_loop() + + def _get_headers(self) -> Dict: + headers = { + "Accept": "application/json", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", # noqa + } + if self.API_KEY: + assert self.API_KEY + headers["X-MBX-APIKEY"] = self.API_KEY + return headers + + def _init_session(self): + raise NotImplementedError + + def _init_private_key( + self, + private_key: Optional[Union[str, Path]], + private_key_pass: Optional[str] = None, + ): + if not private_key: + return + if isinstance(private_key, Path): + with open(private_key, "r") as f: + private_key = f.read() + if len(private_key) > 120: + self._is_rsa = True + return RSA.import_key(private_key, passphrase=private_key_pass) + return ECC.import_key(private_key) + + def _create_api_uri( + self, path: str, signed: bool = True, version: str = PUBLIC_API_VERSION + ) -> str: + url = self.API_URL + if self.testnet: + url = self.API_TESTNET_URL + v = self.PRIVATE_API_VERSION if signed else version + return url + "/" + v + "/" + path + + def _create_margin_api_uri(self, path: str, version: int = 1) -> str: + options = { + 1: self.MARGIN_API_VERSION, + 2: self.MARGIN_API_VERSION2, + 3: self.MARGIN_API_VERSION3, + 4: self.MARGIN_API_VERSION4, + } + return self.MARGIN_API_URL + "/" + options[version] + "/" + path + + def _create_papi_api_uri(self, path: str, version: int = 1) -> str: + options = {1: self.PORTFOLIO_API_VERSION, 2: self.PORTFOLIO_API_VERSION2} + return self.PAPI_URL.format(self.tld) + "/" + options[version] + "/" + path + + def _create_website_uri(self, path: str) -> str: + return self.WEBSITE_URL + "/" + path + + def _create_futures_api_uri(self, path: str, version: int = 1) -> str: + url = self.FUTURES_URL + if self.testnet: + url = self.FUTURES_TESTNET_URL + options = { + 1: self.FUTURES_API_VERSION, + 2: self.FUTURES_API_VERSION2, + 3: self.FUTURES_API_VERSION3, + } + return url + "/" + options[version] + "/" + path + + def _create_futures_data_api_uri(self, path: str) -> str: + url = self.FUTURES_DATA_URL + if self.testnet: + url = self.FUTURES_DATA_TESTNET_URL + return url + "/" + path + + def _create_futures_coin_api_url(self, path: str, version: int = 1) -> str: + url = self.FUTURES_COIN_URL + if self.testnet: + url = self.FUTURES_COIN_TESTNET_URL + options = { + 1: self.FUTURES_API_VERSION, + 2: self.FUTURES_API_VERSION2, + 3: self.FUTURES_API_VERSION3, + } + return url + "/" + options[version] + "/" + path + + def _create_futures_coin_data_api_url(self, path: str, version: int = 1) -> str: + url = self.FUTURES_COIN_DATA_URL + if self.testnet: + url = self.FUTURES_COIN_DATA_TESTNET_URL + return url + "/" + path + + def _create_options_api_uri(self, path: str) -> str: + url = self.OPTIONS_URL + if self.testnet: + url = self.OPTIONS_TESTNET_URL + return url + "/" + self.OPTIONS_API_VERSION + "/" + path + + def _rsa_signature(self, query_string: str): + assert self.PRIVATE_KEY + h = SHA256.new(query_string.encode("utf-8")) + signature = pkcs1_15.new(self.PRIVATE_KEY).sign(h) # type: ignore + return b64encode(signature).decode() + + def _ed25519_signature(self, query_string: str): + assert self.PRIVATE_KEY + return b64encode( + eddsa.new(self.PRIVATE_KEY, "rfc8032").sign(query_string.encode()) + ).decode() # type: ignore + + def _hmac_signature(self, query_string: str) -> str: + assert self.API_SECRET, "API Secret required for private endpoints" + m = hmac.new( + self.API_SECRET.encode("utf-8"), + query_string.encode("utf-8"), + hashlib.sha256, + ) + return m.hexdigest() + + def _generate_signature(self, data: Dict) -> str: + sig_func = self._hmac_signature + if self.PRIVATE_KEY: + if self._is_rsa: + sig_func = self._rsa_signature + else: + sig_func = self._ed25519_signature + query_string = "&".join([f"{d[0]}={d[1]}" for d in self._order_params(data)]) + return sig_func(query_string) + + def _sign_ws_params(self, params, signature_func): + if "signature" in params: + return params + params.setdefault("apiKey", self.API_KEY) + params.setdefault("timestamp", int(time.time() * 1000 + self.timestamp_offset)) + params = dict(sorted(params.items())) + return {**params, "signature": signature_func(params)} + + def _generate_ws_api_signature(self, data: Dict) -> str: + sig_func = self._hmac_signature + if self.PRIVATE_KEY: + if self._is_rsa: + sig_func = self._rsa_signature + else: + sig_func = self._ed25519_signature + query_string = urlencode(data) + return sig_func(query_string) + + async def _ws_futures_api_request(self, method: str, signed: bool, params: dict): + """Send request and wait for response""" + id = params.pop("id", self.uuid22()) + payload = { + "id": id, + "method": method, + "params": params, + } + if signed: + payload["params"] = self._sign_ws_params(params, self._generate_signature) + return await self.ws_future.request(id, payload) + + def _ws_futures_api_request_sync(self, method: str, signed: bool, params: dict): + self.loop = get_loop() + return self.loop.run_until_complete( + self._ws_futures_api_request(method, signed, params) + ) + + async def _make_sync(self, method): + return asyncio.run(method) + + async def _ws_api_request(self, method: str, signed: bool, params: dict): + """Send request and wait for response""" + id = params.pop("id", self.uuid22()) + payload = { + "id": id, + "method": method, + "params": params, + } + if signed: + payload["params"] = self._sign_ws_params( + params, self._generate_ws_api_signature + ) + return await self.ws_api.request(id, payload) + + def _ws_api_request_sync(self, method: str, signed: bool, params: dict): + """Send request to WS API and wait for response""" + self.loop = get_loop() + return self.loop.run_until_complete( + self._ws_api_request(method, signed, params) + ) + + @staticmethod + def _get_version(version: int, **kwargs) -> int: + if "data" in kwargs and "version" in kwargs["data"]: + version_override = kwargs["data"].get("version") + del kwargs["data"]["version"] + return version_override + return version + + @staticmethod + def uuid22(length=22): + return format(random.getrandbits(length * 4), "x") + + @staticmethod + def _order_params(data: Dict) -> List[Tuple[str, str]]: + """Convert params to list with signature as last element + + :param data: + :return: + + """ + data = dict(filter(lambda el: el[1] is not None, data.items())) + has_signature = False + params = [] + for key, value in data.items(): + if key == "signature": + has_signature = True + else: + params.append((key, str(value))) + # sort parameters by key + params.sort(key=itemgetter(0)) + if has_signature: + params.append(("signature", data["signature"])) + return params + + def _get_request_kwargs( + self, method, signed: bool, force_params: bool = False, **kwargs + ) -> Dict: + # set default requests timeout + kwargs["timeout"] = self.REQUEST_TIMEOUT + + # add our global requests params + if self._requests_params: + kwargs.update(self._requests_params) + + data = kwargs.get("data", None) + if data and isinstance(data, dict): + kwargs["data"] = data + # find any requests params passed and apply them + if "requests_params" in kwargs["data"]: + # merge requests params into kwargs + kwargs.update(kwargs["data"]["requests_params"]) + del kwargs["data"]["requests_params"] + + if signed: + # generate signature + kwargs["data"]["timestamp"] = int( + time.time() * 1000 + self.timestamp_offset + ) + kwargs["data"]["signature"] = self._generate_signature(kwargs["data"]) + + # sort get and post params to match signature order + if data: + # sort post params and remove any arguments with values of None + kwargs["data"] = self._order_params(kwargs["data"]) + # Remove any arguments with values of None. + null_args = [ + i for i, (key, value) in enumerate(kwargs["data"]) if value is None + ] + for i in reversed(null_args): + del kwargs["data"][i] + + # if get request assign data array to params value for requests lib + if data and (method == "get" or force_params): + kwargs["params"] = "&".join( + "%s=%s" % (data[0], data[1]) for data in kwargs["data"] + ) + del kwargs["data"] + + # Temporary fix for Signature issue while using batchOrders in AsyncClient + if "params" in kwargs.keys() and "batchOrders" in kwargs["params"]: + kwargs["data"] = kwargs["params"] + del kwargs["params"] + + return kwargs diff --git a/binance/client.py b/binance/client.py index 348e11a0b..3d6470bae 100755 --- a/binance/client.py +++ b/binance/client.py @@ -1,21 +1,12 @@ -from base64 import b64encode from pathlib import Path -import random -from typing import Dict, Optional, List, Tuple, Union, Any +from typing import Dict, Optional, List, Union, Any -import aiohttp -import asyncio -import hashlib -import hmac import requests import time -from Crypto.PublicKey import RSA, ECC -from Crypto.Hash import SHA256 -from Crypto.Signature import pkcs1_15, eddsa -from operator import itemgetter from urllib.parse import urlencode +from .base_client import BaseClient -from .helpers import interval_to_milliseconds, convert_ts_str, get_loop +from .helpers import interval_to_milliseconds, convert_ts_str from .exceptions import ( BinanceAPIException, BinanceRequestException, @@ -24,389 +15,6 @@ from .enums import HistoricalKlinesType -class BaseClient: - API_URL = "https://api{}.binance.{}/api" - API_TESTNET_URL = "https://testnet.binance.vision/api" - MARGIN_API_URL = "https://api{}.binance.{}/sapi" - WEBSITE_URL = "https://www.binance.{}" - FUTURES_URL = "https://fapi.binance.{}/fapi" - FUTURES_TESTNET_URL = "https://testnet.binancefuture.com/fapi" - FUTURES_DATA_URL = "https://fapi.binance.{}/futures/data" - FUTURES_DATA_TESTNET_URL = "https://testnet.binancefuture.com/futures/data" - FUTURES_COIN_URL = "https://dapi.binance.{}/dapi" - FUTURES_COIN_TESTNET_URL = "https://testnet.binancefuture.com/dapi" - FUTURES_COIN_DATA_URL = "https://dapi.binance.{}/futures/data" - FUTURES_COIN_DATA_TESTNET_URL = "https://testnet.binancefuture.com/futures/data" - OPTIONS_URL = "https://eapi.binance.{}/eapi" - OPTIONS_TESTNET_URL = "https://testnet.binanceops.{}/eapi" - PAPI_URL = "https://papi.binance.{}/papi" - PUBLIC_API_VERSION = "v1" - PRIVATE_API_VERSION = "v3" - MARGIN_API_VERSION = "v1" - MARGIN_API_VERSION2 = "v2" - MARGIN_API_VERSION3 = "v3" - MARGIN_API_VERSION4 = "v4" - FUTURES_API_VERSION = "v1" - FUTURES_API_VERSION2 = "v2" - FUTURES_API_VERSION3 = "v3" - OPTIONS_API_VERSION = "v1" - PORTFOLIO_API_VERSION = "v1" - PORTFOLIO_API_VERSION2 = "v2" - - BASE_ENDPOINT_DEFAULT = "" - BASE_ENDPOINT_1 = "1" - BASE_ENDPOINT_2 = "2" - BASE_ENDPOINT_3 = "3" - BASE_ENDPOINT_4 = "4" - - REQUEST_TIMEOUT: float = 10 - - SYMBOL_TYPE_SPOT = "SPOT" - - ORDER_STATUS_NEW = "NEW" - ORDER_STATUS_PARTIALLY_FILLED = "PARTIALLY_FILLED" - ORDER_STATUS_FILLED = "FILLED" - ORDER_STATUS_CANCELED = "CANCELED" - ORDER_STATUS_PENDING_CANCEL = "PENDING_CANCEL" - ORDER_STATUS_REJECTED = "REJECTED" - ORDER_STATUS_EXPIRED = "EXPIRED" - - KLINE_INTERVAL_1SECOND = "1s" - KLINE_INTERVAL_1MINUTE = "1m" - KLINE_INTERVAL_3MINUTE = "3m" - KLINE_INTERVAL_5MINUTE = "5m" - KLINE_INTERVAL_15MINUTE = "15m" - KLINE_INTERVAL_30MINUTE = "30m" - KLINE_INTERVAL_1HOUR = "1h" - KLINE_INTERVAL_2HOUR = "2h" - KLINE_INTERVAL_4HOUR = "4h" - KLINE_INTERVAL_6HOUR = "6h" - KLINE_INTERVAL_8HOUR = "8h" - KLINE_INTERVAL_12HOUR = "12h" - KLINE_INTERVAL_1DAY = "1d" - KLINE_INTERVAL_3DAY = "3d" - KLINE_INTERVAL_1WEEK = "1w" - KLINE_INTERVAL_1MONTH = "1M" - - SIDE_BUY = "BUY" - SIDE_SELL = "SELL" - - ORDER_TYPE_LIMIT = "LIMIT" - ORDER_TYPE_MARKET = "MARKET" - ORDER_TYPE_STOP_LOSS = "STOP_LOSS" - ORDER_TYPE_STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT" - ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT" - ORDER_TYPE_TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT" - ORDER_TYPE_LIMIT_MAKER = "LIMIT_MAKER" - - FUTURE_ORDER_TYPE_LIMIT = "LIMIT" - FUTURE_ORDER_TYPE_MARKET = "MARKET" - FUTURE_ORDER_TYPE_STOP = "STOP" - FUTURE_ORDER_TYPE_STOP_MARKET = "STOP_MARKET" - FUTURE_ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT" - FUTURE_ORDER_TYPE_TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET" - FUTURE_ORDER_TYPE_LIMIT_MAKER = "LIMIT_MAKER" - - TIME_IN_FORCE_GTC = "GTC" # Good till cancelled - TIME_IN_FORCE_IOC = "IOC" # Immediate or cancel - TIME_IN_FORCE_FOK = "FOK" # Fill or kill - - ORDER_RESP_TYPE_ACK = "ACK" - ORDER_RESP_TYPE_RESULT = "RESULT" - ORDER_RESP_TYPE_FULL = "FULL" - - # For accessing the data returned by Client.aggregate_trades(). - AGG_ID = "a" - AGG_PRICE = "p" - AGG_QUANTITY = "q" - AGG_FIRST_TRADE_ID = "f" - AGG_LAST_TRADE_ID = "l" - AGG_TIME = "T" - AGG_BUYER_MAKES = "m" - AGG_BEST_MATCH = "M" - - # new asset transfer api enum - SPOT_TO_FIAT = "MAIN_C2C" - SPOT_TO_USDT_FUTURE = "MAIN_UMFUTURE" - SPOT_TO_COIN_FUTURE = "MAIN_CMFUTURE" - SPOT_TO_MARGIN_CROSS = "MAIN_MARGIN" - SPOT_TO_MINING = "MAIN_MINING" - FIAT_TO_SPOT = "C2C_MAIN" - FIAT_TO_USDT_FUTURE = "C2C_UMFUTURE" - FIAT_TO_MINING = "C2C_MINING" - USDT_FUTURE_TO_SPOT = "UMFUTURE_MAIN" - USDT_FUTURE_TO_FIAT = "UMFUTURE_C2C" - USDT_FUTURE_TO_MARGIN_CROSS = "UMFUTURE_MARGIN" - COIN_FUTURE_TO_SPOT = "CMFUTURE_MAIN" - MARGIN_CROSS_TO_SPOT = "MARGIN_MAIN" - MARGIN_CROSS_TO_USDT_FUTURE = "MARGIN_UMFUTURE" - MINING_TO_SPOT = "MINING_MAIN" - MINING_TO_USDT_FUTURE = "MINING_UMFUTURE" - MINING_TO_FIAT = "MINING_C2C" - - ## order ids - SPOT_ORDER_PREFIX = "x-HNA2TXFJ" - CONTRACT_ORDER_PREFIX = "x-Cb7ytekJ" - - def __init__( - self, - api_key: Optional[str] = None, - api_secret: Optional[str] = None, - requests_params: Optional[Dict[str, Any]] = None, - tld: str = "com", - base_endpoint: str = BASE_ENDPOINT_DEFAULT, - testnet: bool = False, - private_key: Optional[Union[str, Path]] = None, - private_key_pass: Optional[str] = None, - ): - """Binance API Client constructor - - :param api_key: Api Key - :type api_key: str. - :param api_secret: Api Secret - :type api_secret: str. - :param requests_params: optional - Dictionary of requests params to use for all calls - :type requests_params: dict. - :param testnet: Use testnet environment - only available for vanilla options at the moment - :type testnet: bool - :param private_key: Path to private key, or string of file contents - :type private_key: optional - str or Path - :param private_key_pass: Password of private key - :type private_key_pass: optional - str - - """ - - self.tld = tld - self.API_URL = self.API_URL.format(base_endpoint, tld) - self.MARGIN_API_URL = self.MARGIN_API_URL.format(base_endpoint, tld) - self.WEBSITE_URL = self.WEBSITE_URL.format(tld) - self.FUTURES_URL = self.FUTURES_URL.format(tld) - self.FUTURES_DATA_URL = self.FUTURES_DATA_URL.format(tld) - self.FUTURES_COIN_URL = self.FUTURES_COIN_URL.format(tld) - self.FUTURES_COIN_DATA_URL = self.FUTURES_COIN_DATA_URL.format(tld) - self.OPTIONS_URL = self.OPTIONS_URL.format(tld) - self.OPTIONS_TESTNET_URL = self.OPTIONS_TESTNET_URL.format(tld) - - self.API_KEY = api_key - self.API_SECRET = api_secret - self._is_rsa = False - self.PRIVATE_KEY: Any = self._init_private_key(private_key, private_key_pass) - self.session = self._init_session() - self._requests_params = requests_params - self.response = None - self.testnet = testnet - self.timestamp_offset = 0 - - def _get_headers(self) -> Dict: - headers = { - "Accept": "application/json", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", # noqa - } - if self.API_KEY: - assert self.API_KEY - headers["X-MBX-APIKEY"] = self.API_KEY - return headers - - def _init_session(self): - raise NotImplementedError - - def _init_private_key( - self, - private_key: Optional[Union[str, Path]], - private_key_pass: Optional[str] = None, - ): - if not private_key: - return - if isinstance(private_key, Path): - with open(private_key, "r") as f: - private_key = f.read() - if len(private_key) > 120: - self._is_rsa = True - return RSA.import_key(private_key, passphrase=private_key_pass) - return ECC.import_key(private_key) - - def _create_api_uri( - self, path: str, signed: bool = True, version: str = PUBLIC_API_VERSION - ) -> str: - url = self.API_URL - if self.testnet: - url = self.API_TESTNET_URL - v = self.PRIVATE_API_VERSION if signed else version - return url + "/" + v + "/" + path - - def _create_margin_api_uri(self, path: str, version: int = 1) -> str: - options = { - 1: self.MARGIN_API_VERSION, - 2: self.MARGIN_API_VERSION2, - 3: self.MARGIN_API_VERSION3, - 4: self.MARGIN_API_VERSION4, - } - return self.MARGIN_API_URL + "/" + options[version] + "/" + path - - def _create_papi_api_uri(self, path: str, version: int = 1) -> str: - options = {1: self.PORTFOLIO_API_VERSION, 2: self.PORTFOLIO_API_VERSION2} - return self.PAPI_URL.format(self.tld) + "/" + options[version] + "/" + path - - def _create_website_uri(self, path: str) -> str: - return self.WEBSITE_URL + "/" + path - - def _create_futures_api_uri(self, path: str, version: int = 1) -> str: - url = self.FUTURES_URL - if self.testnet: - url = self.FUTURES_TESTNET_URL - options = { - 1: self.FUTURES_API_VERSION, - 2: self.FUTURES_API_VERSION2, - 3: self.FUTURES_API_VERSION3, - } - return url + "/" + options[version] + "/" + path - - def _create_futures_data_api_uri(self, path: str) -> str: - url = self.FUTURES_DATA_URL - if self.testnet: - url = self.FUTURES_DATA_TESTNET_URL - return url + "/" + path - - def _create_futures_coin_api_url(self, path: str, version: int = 1) -> str: - url = self.FUTURES_COIN_URL - if self.testnet: - url = self.FUTURES_COIN_TESTNET_URL - options = { - 1: self.FUTURES_API_VERSION, - 2: self.FUTURES_API_VERSION2, - 3: self.FUTURES_API_VERSION3, - } - return url + "/" + options[version] + "/" + path - - def _create_futures_coin_data_api_url(self, path: str, version: int = 1) -> str: - url = self.FUTURES_COIN_DATA_URL - if self.testnet: - url = self.FUTURES_COIN_DATA_TESTNET_URL - return url + "/" + path - - def _create_options_api_uri(self, path: str) -> str: - url = self.OPTIONS_URL - if self.testnet: - url = self.OPTIONS_TESTNET_URL - return url + "/" + self.OPTIONS_API_VERSION + "/" + path - - def _rsa_signature(self, query_string: str): - assert self.PRIVATE_KEY - h = SHA256.new(query_string.encode("utf-8")) - signature = pkcs1_15.new(self.PRIVATE_KEY).sign(h) # type: ignore - return b64encode(signature).decode() - - def _ed25519_signature(self, query_string: str): - assert self.PRIVATE_KEY - return b64encode( - eddsa.new(self.PRIVATE_KEY, "rfc8032").sign(query_string.encode()) - ).decode() # type: ignore - - def _hmac_signature(self, query_string: str) -> str: - assert self.API_SECRET, "API Secret required for private endpoints" - m = hmac.new( - self.API_SECRET.encode("utf-8"), - query_string.encode("utf-8"), - hashlib.sha256, - ) - return m.hexdigest() - - def _generate_signature(self, data: Dict) -> str: - sig_func = self._hmac_signature - if self.PRIVATE_KEY: - if self._is_rsa: - sig_func = self._rsa_signature - else: - sig_func = self._ed25519_signature - query_string = "&".join([f"{d[0]}={d[1]}" for d in self._order_params(data)]) - return sig_func(query_string) - - @staticmethod - def _get_version(version: int, **kwargs) -> int: - if "data" in kwargs and "version" in kwargs["data"]: - version_override = kwargs["data"].get("version") - del kwargs["data"]["version"] - return version_override - return version - - @staticmethod - def uuid22(length=22): - return format(random.getrandbits(length * 4), "x") - - @staticmethod - def _order_params(data: Dict) -> List[Tuple[str, str]]: - """Convert params to list with signature as last element - - :param data: - :return: - - """ - data = dict(filter(lambda el: el[1] is not None, data.items())) - has_signature = False - params = [] - for key, value in data.items(): - if key == "signature": - has_signature = True - else: - params.append((key, str(value))) - # sort parameters by key - params.sort(key=itemgetter(0)) - if has_signature: - params.append(("signature", data["signature"])) - return params - - def _get_request_kwargs( - self, method, signed: bool, force_params: bool = False, **kwargs - ) -> Dict: - # set default requests timeout - kwargs["timeout"] = self.REQUEST_TIMEOUT - - # add our global requests params - if self._requests_params: - kwargs.update(self._requests_params) - - data = kwargs.get("data", None) - if data and isinstance(data, dict): - kwargs["data"] = data - # find any requests params passed and apply them - if "requests_params" in kwargs["data"]: - # merge requests params into kwargs - kwargs.update(kwargs["data"]["requests_params"]) - del kwargs["data"]["requests_params"] - - if signed: - # generate signature - kwargs["data"]["timestamp"] = int( - time.time() * 1000 + self.timestamp_offset - ) - kwargs["data"]["signature"] = self._generate_signature(kwargs["data"]) - - # sort get and post params to match signature order - if data: - # sort post params and remove any arguments with values of None - kwargs["data"] = self._order_params(kwargs["data"]) - # Remove any arguments with values of None. - null_args = [ - i for i, (key, value) in enumerate(kwargs["data"]) if value is None - ] - for i in reversed(null_args): - del kwargs["data"][i] - - # if get request assign data array to params value for requests lib - if data and (method == "get" or force_params): - kwargs["params"] = "&".join( - "%s=%s" % (data[0], data[1]) for data in kwargs["data"] - ) - del kwargs["data"] - - # Temporary fix for Signature issue while using batchOrders in AsyncClient - if "params" in kwargs.keys() and "batchOrders" in kwargs["params"]: - kwargs["data"] = kwargs["params"] - del kwargs["params"] - - return kwargs - - class Client(BaseClient): def __init__( self, @@ -1744,11 +1352,9 @@ def order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): :raises: BinanceRequestException, BinanceAPIException, BinanceOrderException, BinanceOrderMinAmountException, BinanceOrderMinPriceException, BinanceOrderMinTotalException, BinanceOrderUnknownSymbolException, BinanceOrderInactiveSymbolException """ - params.update( - { - "side": self.SIDE_BUY, - } - ) + params.update({ + "side": self.SIDE_BUY, + }) return self.order_limit(timeInForce=timeInForce, **params) def order_limit_sell(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): @@ -4831,8 +4437,10 @@ def cancel_margin_order(self, **params): ) def cancel_all_open_margin_orders(self, **params): - return self._request_margin_api("delete", "margin/openOrders", signed=True, data=params) - + return self._request_margin_api( + "delete", "margin/openOrders", signed=True, data=params + ) + def set_margin_max_leverage(self, **params): """Adjust cross margin max leverage @@ -10503,3163 +10111,416 @@ def close_connection(self): def __del__(self): self.close_connection() + ############################################################ + # WebSocket API methods + ############################################################ -class AsyncClient(BaseClient): - def __init__( - self, - api_key: Optional[str] = None, - api_secret: Optional[str] = None, - requests_params: Optional[Dict[str, Any]] = None, - tld: str = "com", - base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, - testnet: bool = False, - loop=None, - session_params: Optional[Dict[str, Any]] = None, - private_key: Optional[Union[str, Path]] = None, - private_key_pass: Optional[str] = None, - https_proxy: Optional[str] = None, - ): - self.https_proxy = https_proxy - self.loop = loop or get_loop() - self._session_params: Dict[str, Any] = session_params or {} - super().__init__( - api_key, - api_secret, - requests_params, - tld, - base_endpoint, - testnet, - private_key, - private_key_pass, - ) - - @classmethod - async def create( - cls, - api_key: Optional[str] = None, - api_secret: Optional[str] = None, - requests_params: Optional[Dict[str, Any]] = None, - tld: str = "com", - base_endpoint: str = BaseClient.BASE_ENDPOINT_DEFAULT, - testnet: bool = False, - loop=None, - session_params: Optional[Dict[str, Any]] = None, - https_proxy: Optional[str] = None, - ): - self = cls( - api_key, - api_secret, - requests_params, - tld, - base_endpoint, - testnet, - loop, - session_params, - ) - self.https_proxy = https_proxy # move this to the constructor - - try: - await self.ping() - - # calculate timestamp offset between local and binance server - res = await self.get_server_time() - self.timestamp_offset = res["serverTime"] - int(time.time() * 1000) - - return self - except Exception: - # If ping throw an exception, the current self must be cleaned - # else, we can receive a "asyncio:Unclosed client session" - await self.close_connection() - raise + def ws_create_test_order(self, **params): + """Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine. + https://binance-docs.github.io/apidocs/websocket_api/en/#test-new-order-trade + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param type: required + :type type: str + :param timeInForce: required if limit order + :type timeInForce: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: The number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + .. code-block:: python + {} + """ - def _init_session(self) -> aiohttp.ClientSession: - session = aiohttp.ClientSession( - loop=self.loop, headers=self._get_headers(), **self._session_params - ) - return session + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - async def close_connection(self): - if self.session: - assert self.session - await self.session.close() + return self._ws_api_request_sync("order.test", True, params) - async def _request( - self, method, uri: str, signed: bool, force_params: bool = False, **kwargs - ): - kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) + def ws_create_order(self, **params): + """Create an order via WebSocket. + https://binance-docs.github.io/apidocs/websocket_api/en/#place-new-order-trade + :param id: The request ID to be used. By default uuid22() is used. + :param symbol: The symbol to create an order for + :param side: BUY or SELL + :param type: Order type (e.g., LIMIT, MARKET) + :param quantity: The amount to buy or sell + :param kwargs: Additional order parameters + """ + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - async with getattr(self.session, method)( - uri, proxy=self.https_proxy, **kwargs - ) as response: - self.response = response - return await self._handle_response(response) + return self._ws_api_request_sync("order.place", True, params) - async def _handle_response(self, response: aiohttp.ClientResponse): - """Internal helper for handling API responses from the Binance server. - Raises the appropriate exceptions when necessary; otherwise, returns the - response. + def ws_order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + """Send in a new limit order + Any order with an icebergQty MUST have timeInForce set to GTC. + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param icebergQty: Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options """ - if not str(response.status).startswith("2"): - raise BinanceAPIException(response, response.status, await response.text()) - try: - return await response.json() - except ValueError: - txt = await response.text() - raise BinanceRequestException(f"Invalid Response: {txt}") + params.update({ + "type": self.ORDER_TYPE_LIMIT, + "timeInForce": timeInForce, + }) + return self.ws_create_order(**params) - async def _request_api( - self, - method, - path, - signed=False, - version=BaseClient.PUBLIC_API_VERSION, - **kwargs, - ): - uri = self._create_api_uri(path, signed, version) - return await self._request(method, uri, signed, **kwargs) + def ws_order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + """Send in a new limit buy order + Any order with an icebergQty MUST have timeInForce set to GTC. + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param stopPrice: Used with stop orders + :type stopPrice: decimal + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({ + "side": self.SIDE_BUY, + }) + return self.ws_order_limit(timeInForce=timeInForce, **params) - async def _request_futures_api( - self, method, path, signed=False, version=1, **kwargs - ) -> Dict: - version = self._get_version(version, **kwargs) - uri = self._create_futures_api_uri(path, version=version) + def ws_order_limit_sell(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): + """Send in a new limit sell order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param price: required + :type price: str + :param timeInForce: default Good till cancelled + :type timeInForce: str + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param stopPrice: Used with stop orders + :type stopPrice: decimal + :param icebergQty: Used with iceberg orders + :type icebergQty: decimal + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_SELL}) + return self.ws_order_limit(timeInForce=timeInForce, **params) - return await self._request(method, uri, signed, False, **kwargs) + def ws_order_market(self, **params): + """Send in a new market order + :param symbol: required + :type symbol: str + :param side: required + :type side: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: amount the user wants to spend (when buying) or receive (when selling) + of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"type": self.ORDER_TYPE_MARKET}) + return self.ws_create_order(**params) - async def _request_futures_data_api( - self, method, path, signed=False, **kwargs - ) -> Dict: - uri = self._create_futures_data_api_uri(path) + def ws_order_market_buy(self, **params): + """Send in a new market buy order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: the amount the user wants to spend of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_BUY}) + return self.ws_order_market(**params) - return await self._request(method, uri, signed, True, **kwargs) - - async def _request_futures_coin_api( - self, method, path, signed=False, version=1, **kwargs - ) -> Dict: - version = self._get_version(version, **kwargs) - uri = self._create_futures_coin_api_url(path, version=version) - - return await self._request(method, uri, signed, False, **kwargs) - - async def _request_futures_coin_data_api( - self, method, path, signed=False, version=1, **kwargs - ) -> Dict: - version = self._get_version(version, **kwargs) - uri = self._create_futures_coin_data_api_url(path, version=version) - - return await self._request(method, uri, signed, True, **kwargs) - - async def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: - uri = self._create_options_api_uri(path) - - return await self._request(method, uri, signed, True, **kwargs) - - async def _request_margin_api( - self, method, path, signed=False, version=1, **kwargs - ) -> Dict: - version = self._get_version(version, **kwargs) - uri = self._create_margin_api_uri(path, version) - - return await self._request(method, uri, signed, **kwargs) - - async def _request_papi_api( - self, method, path, signed=False, version=1, **kwargs - ) -> Dict: - version = self._get_version(version, **kwargs) - uri = self._create_papi_api_uri(path, version) - - return await self._request(method, uri, signed, **kwargs) - - async def _request_website(self, method, path, signed=False, **kwargs) -> Dict: - uri = self._create_website_uri(path) - return await self._request(method, uri, signed, **kwargs) - - async def _get( - self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs - ): - return await self._request_api("get", path, signed, version, **kwargs) - - async def _post( - self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs - ) -> Dict: - return await self._request_api("post", path, signed, version, **kwargs) - - async def _put( - self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs - ) -> Dict: - return await self._request_api("put", path, signed, version, **kwargs) - - async def _delete( - self, path, signed=False, version=BaseClient.PUBLIC_API_VERSION, **kwargs - ) -> Dict: - return await self._request_api("delete", path, signed, version, **kwargs) - - # Exchange Endpoints - - async def get_products(self) -> Dict: - products = await self._request_website( - "get", - "bapi/asset/v2/public/asset-service/product/get-products?includeEtf=true", - ) - return products - - get_products.__doc__ = Client.get_products.__doc__ - - async def get_exchange_info(self) -> Dict: - return await self._get("exchangeInfo", version=self.PRIVATE_API_VERSION) - - get_exchange_info.__doc__ = Client.get_exchange_info.__doc__ - - async def get_symbol_info(self, symbol) -> Optional[Dict]: - res = await self.get_exchange_info() - - for item in res["symbols"]: - if item["symbol"] == symbol.upper(): - return item - - return None - - get_symbol_info.__doc__ = Client.get_symbol_info.__doc__ - - # General Endpoints - - async def ping(self) -> Dict: - return await self._get("ping", version=self.PRIVATE_API_VERSION) - - ping.__doc__ = Client.ping.__doc__ - - async def get_server_time(self) -> Dict: - return await self._get("time", version=self.PRIVATE_API_VERSION) - - get_server_time.__doc__ = Client.get_server_time.__doc__ - - # Market Data Endpoints - - async def get_all_tickers( - self, symbol: Optional[str] = None - ) -> List[Dict[str, str]]: - params = {} - if symbol: - params["symbol"] = symbol - return await self._get( - "ticker/price", version=self.PRIVATE_API_VERSION, data=params - ) - - get_all_tickers.__doc__ = Client.get_all_tickers.__doc__ - - async def get_orderbook_tickers(self, **params) -> Dict: - data = {} - if "symbol" in params: - data["symbol"] = params["symbol"] - elif "symbols" in params: - data["symbols"] = params["symbols"] - return await self._get( - "ticker/bookTicker", data=data, version=self.PRIVATE_API_VERSION - ) - - get_orderbook_tickers.__doc__ = Client.get_orderbook_tickers.__doc__ - - async def get_order_book(self, **params) -> Dict: - return await self._get("depth", data=params, version=self.PRIVATE_API_VERSION) - - get_order_book.__doc__ = Client.get_order_book.__doc__ - - async def get_recent_trades(self, **params) -> Dict: - return await self._get("trades", data=params) - - get_recent_trades.__doc__ = Client.get_recent_trades.__doc__ - - async def get_historical_trades(self, **params) -> Dict: - return await self._get( - "historicalTrades", data=params, version=self.PRIVATE_API_VERSION - ) - - get_historical_trades.__doc__ = Client.get_historical_trades.__doc__ - - async def get_aggregate_trades(self, **params) -> Dict: - return await self._get( - "aggTrades", data=params, version=self.PRIVATE_API_VERSION - ) - - get_aggregate_trades.__doc__ = Client.get_aggregate_trades.__doc__ - - async def aggregate_trade_iter(self, symbol, start_str=None, last_id=None): - if start_str is not None and last_id is not None: - raise ValueError( - "start_time and last_id may not be simultaneously specified." - ) - - # If there's no last_id, get one. - if last_id is None: - # Without a last_id, we actually need the first trade. Normally, - # we'd get rid of it. See the next loop. - if start_str is None: - trades = await self.get_aggregate_trades(symbol=symbol, fromId=0) - else: - # The difference between startTime and endTime should be less - # or equal than an hour and the result set should contain at - # least one trade. - start_ts = convert_ts_str(start_str) - # If the resulting set is empty (i.e. no trades in that interval) - # then we just move forward hour by hour until we find at least one - # trade or reach present moment - while True: - end_ts = start_ts + (60 * 60 * 1000) - trades = await self.get_aggregate_trades( - symbol=symbol, startTime=start_ts, endTime=end_ts - ) - if len(trades) > 0: - break - # If we reach present moment and find no trades then there is - # nothing to iterate, so we're done - if end_ts > int(time.time() * 1000): - return - start_ts = end_ts - for t in trades: - yield t - last_id = trades[-1][self.AGG_ID] - - while True: - # There is no need to wait between queries, to avoid hitting the - # rate limit. We're using blocking IO, and as long as we're the - # only thread running calls like this, Binance will automatically - # add the right delay time on their end, forcing us to wait for - # data. That really simplifies this function's job. Binance is - # fucking awesome. - trades = await self.get_aggregate_trades(symbol=symbol, fromId=last_id) - # fromId=n returns a set starting with id n, but we already have - # that one. So get rid of the first item in the result set. - trades = trades[1:] - if len(trades) == 0: - return - for t in trades: - yield t - last_id = trades[-1][self.AGG_ID] - - aggregate_trade_iter.__doc__ = Client.aggregate_trade_iter.__doc__ - - async def get_klines(self, **params) -> Dict: - return await self._get("klines", data=params, version=self.PRIVATE_API_VERSION) - - get_klines.__doc__ = Client.get_klines.__doc__ - - async def _klines( - self, klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, **params - ) -> Dict: - if "endTime" in params and not params["endTime"]: - del params["endTime"] - if HistoricalKlinesType.SPOT == klines_type: - return await self.get_klines(**params) - elif HistoricalKlinesType.FUTURES == klines_type: - return await self.futures_klines(**params) - elif HistoricalKlinesType.FUTURES_COIN == klines_type: - return await self.futures_coin_klines(**params) - else: - raise NotImplementedException(klines_type) - - _klines.__doc__ = Client._klines.__doc__ - - async def _get_earliest_valid_timestamp( - self, - symbol, - interval, - klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, - ): - kline = await self._klines( - klines_type=klines_type, - symbol=symbol, - interval=interval, - limit=1, - startTime=0, - endTime=int(time.time() * 1000), - ) - return kline[0][0] - - _get_earliest_valid_timestamp.__doc__ = Client._get_earliest_valid_timestamp.__doc__ - - async def get_historical_klines( - self, - symbol, - interval, - start_str=None, - end_str=None, - limit=None, - klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, - ): - return await self._historical_klines( - symbol, - interval, - start_str, - end_str=end_str, - limit=limit, - klines_type=klines_type, - ) - - get_historical_klines.__doc__ = Client.get_historical_klines.__doc__ - - async def _historical_klines( - self, - symbol, - interval, - start_str=None, - end_str=None, - limit=None, - klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, - ): - initial_limit_set = True - if limit is None: - limit = 1000 - initial_limit_set = False - - # init our list - output_data = [] - - # convert interval to useful value in seconds - timeframe = interval_to_milliseconds(interval) - - # establish first available start timestamp - start_ts = convert_ts_str(start_str) - if start_ts is not None: - first_valid_ts = await self._get_earliest_valid_timestamp( - symbol, interval, klines_type - ) - start_ts = max(start_ts, first_valid_ts) - - # if an end time was passed convert it - end_ts = convert_ts_str(end_str) - if end_ts and start_ts and end_ts <= start_ts: - return output_data - - idx = 0 - while True: - # fetch the klines from start_ts up to max 500 entries or the end_ts if set - temp_data = await self._klines( - klines_type=klines_type, - symbol=symbol, - interval=interval, - limit=limit, - startTime=start_ts, - endTime=end_ts, - ) - - # append this loops data to our output data - if temp_data: - output_data += temp_data - - # check if output_data is greater than limit and truncate if needed and break loop - if initial_limit_set and len(output_data) > limit: - output_data = output_data[:limit] - break - - # handle the case where exactly the limit amount of data was returned last loop - # or check if we received less than the required limit and exit the loop - if not len(temp_data) or len(temp_data) < limit: - # exit the while loop - break - - # set our start timestamp using the last value in the array - # and increment next call by our timeframe - start_ts = temp_data[-1][0] + timeframe - - # exit loop if we reached end_ts before reaching klines - if end_ts and start_ts >= end_ts: - break - - # sleep after every 3rd call to be kind to the API - idx += 1 - if idx % 3 == 0: - await asyncio.sleep(1) - - return output_data - - _historical_klines.__doc__ = Client._historical_klines.__doc__ - - async def get_historical_klines_generator( - self, - symbol, - interval, - start_str=None, - end_str=None, - limit=1000, - klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, - ): - return self._historical_klines_generator( - symbol, - interval, - start_str, - end_str=end_str, - limit=limit, - klines_type=klines_type, - ) - - get_historical_klines_generator.__doc__ = ( - Client.get_historical_klines_generator.__doc__ - ) - - async def _historical_klines_generator( - self, - symbol, - interval, - start_str=None, - end_str=None, - limit=1000, - klines_type: HistoricalKlinesType = HistoricalKlinesType.SPOT, - ): - # convert interval to useful value in seconds - timeframe = interval_to_milliseconds(interval) - - # if a start time was passed convert it - start_ts = convert_ts_str(start_str) - - # establish first available start timestamp - if start_ts is not None: - first_valid_ts = await self._get_earliest_valid_timestamp( - symbol, interval, klines_type - ) - start_ts = max(start_ts, first_valid_ts) - - # if an end time was passed convert it - end_ts = convert_ts_str(end_str) - if end_ts and start_ts and end_ts <= start_ts: - return - - idx = 0 - while True: - # fetch the klines from start_ts up to max 500 entries or the end_ts if set - output_data = await self._klines( - klines_type=klines_type, - symbol=symbol, - interval=interval, - limit=limit, - startTime=start_ts, - endTime=end_ts, - ) - - # yield data - if output_data: - for o in output_data: - yield o - - # handle the case where exactly the limit amount of data was returned last loop - # check if we received less than the required limit and exit the loop - if not len(output_data) or len(output_data) < limit: - # exit the while loop - break - - # increment next call by our timeframe - start_ts = output_data[-1][0] + timeframe - - # exit loop if we reached end_ts before reaching klines - if end_ts and start_ts >= end_ts: - break - - # sleep after every 3rd call to be kind to the API - idx += 1 - if idx % 3 == 0: - await asyncio.sleep(1) - - _historical_klines_generator.__doc__ = Client._historical_klines_generator.__doc__ - - async def get_avg_price(self, **params): - return await self._get( - "avgPrice", data=params, version=self.PRIVATE_API_VERSION - ) - - get_avg_price.__doc__ = Client.get_avg_price.__doc__ - - async def get_ticker(self, **params): - return await self._get( - "ticker/24hr", data=params, version=self.PRIVATE_API_VERSION - ) - - get_ticker.__doc__ = Client.get_ticker.__doc__ - - async def get_symbol_ticker(self, **params): - return await self._get( - "ticker/price", data=params, version=self.PRIVATE_API_VERSION - ) - - get_symbol_ticker.__doc__ = Client.get_symbol_ticker.__doc__ - - async def get_symbol_ticker_window(self, **params): - return await self._get("ticker", data=params, version=self.PRIVATE_API_VERSION) - - get_symbol_ticker_window.__doc__ = Client.get_symbol_ticker_window.__doc__ - - async def get_orderbook_ticker(self, **params): - return await self._get( - "ticker/bookTicker", data=params, version=self.PRIVATE_API_VERSION - ) - - get_orderbook_ticker.__doc__ = Client.get_orderbook_ticker.__doc__ - - # Account Endpoints - - async def create_order(self, **params): - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - return await self._post("order", True, data=params) - - create_order.__doc__ = Client.create_order.__doc__ - - async def order_limit(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): - params.update({"type": self.ORDER_TYPE_LIMIT, "timeInForce": timeInForce}) - return await self.create_order(**params) - - order_limit.__doc__ = Client.order_limit.__doc__ - - async def order_limit_buy(self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params): - params.update( - { - "side": self.SIDE_BUY, - } - ) - return await self.order_limit(timeInForce=timeInForce, **params) - - order_limit_buy.__doc__ = Client.order_limit_buy.__doc__ - - async def order_limit_sell( - self, timeInForce=BaseClient.TIME_IN_FORCE_GTC, **params - ): - params.update({"side": self.SIDE_SELL}) - return await self.order_limit(timeInForce=timeInForce, **params) - - order_limit_sell.__doc__ = Client.order_limit_sell.__doc__ - - async def order_market(self, **params): - params.update({"type": self.ORDER_TYPE_MARKET}) - return await self.create_order(**params) - - order_market.__doc__ = Client.order_market.__doc__ - - async def order_market_buy(self, **params): - params.update({"side": self.SIDE_BUY}) - return await self.order_market(**params) - - order_market_buy.__doc__ = Client.order_market_buy.__doc__ - - async def order_market_sell(self, **params): - params.update({"side": self.SIDE_SELL}) - return await self.order_market(**params) - - order_market_sell.__doc__ = Client.order_market_sell.__doc__ - - async def create_oco_order(self, **params): - if "listClientOrderId" not in params: - params["listClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - return await self._post("orderList/oco", True, data=params) - - create_oco_order.__doc__ = Client.create_oco_order.__doc__ - - async def order_oco_buy(self, **params): - params.update({"side": self.SIDE_BUY}) - return await self.create_oco_order(**params) - - order_oco_buy.__doc__ = Client.order_oco_buy.__doc__ - - async def order_oco_sell(self, **params): - params.update({"side": self.SIDE_SELL}) - return await self.create_oco_order(**params) - - order_oco_sell.__doc__ = Client.order_oco_sell.__doc__ - - async def create_test_order(self, **params): - return await self._post("order/test", True, data=params) - - create_test_order.__doc__ = Client.create_test_order.__doc__ - - async def get_order(self, **params): - return await self._get("order", True, data=params) - - get_order.__doc__ = Client.get_order.__doc__ - - async def get_all_orders(self, **params): - return await self._get("allOrders", True, data=params) - - get_all_orders.__doc__ = Client.get_all_orders.__doc__ - - async def cancel_order(self, **params): - return await self._delete("order", True, data=params) - - cancel_order.__doc__ = Client.cancel_order.__doc__ - - async def cancel_all_open_orders(self, **params): - return await self._delete("openOrders", True, data=params) - - async def cancel_replace_order(self, **params): - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - return await self._post("order/cancelReplace", signed=True, data=params) - - async def get_open_orders(self, **params): - return await self._get("openOrders", True, data=params) - - get_open_orders.__doc__ = Client.get_open_orders.__doc__ - - async def get_open_oco_orders(self, **params): - return await self._get("openOrderList", True, data=params) - - get_open_oco_orders.__doc__ = Client.get_open_oco_orders.__doc__ - - # User Stream Endpoints - async def get_account(self, **params): - return await self._get("account", True, data=params) - - get_account.__doc__ = Client.get_account.__doc__ - - async def get_asset_balance(self, asset, **params): - res = await self.get_account(**params) - # find asset balance in list of balances - if "balances" in res: - for bal in res["balances"]: - if bal["asset"].lower() == asset.lower(): - return bal - return None - - get_asset_balance.__doc__ = Client.get_asset_balance.__doc__ - - async def get_my_trades(self, **params): - return await self._get("myTrades", True, data=params) - - get_my_trades.__doc__ = Client.get_my_trades.__doc__ - - async def get_current_order_count(self, **params): - return await self._get("rateLimit/order", True, data=params) - - get_current_order_count.__doc__ = Client.get_current_order_count.__doc__ - - async def get_prevented_matches(self, **params): - return await self._get("myPreventedMatches", True, data=params) - - get_prevented_matches.__doc__ = Client.get_prevented_matches.__doc__ - - async def get_allocations(self, **params): - return await self._get("myAllocations", True, data=params) - - get_allocations.__doc__ = Client.get_allocations.__doc__ - - async def get_system_status(self): - return await self._request_margin_api("get", "system/status") - - get_system_status.__doc__ = Client.get_system_status.__doc__ - - async def get_account_status(self, **params): - return await self._request_margin_api( - "get", "account/status", True, data=params - ) - - get_account_status.__doc__ = Client.get_account_status.__doc__ - - async def get_account_api_trading_status(self, **params): - return await self._request_margin_api( - "get", "account/apiTradingStatus", True, data=params - ) - - get_account_api_trading_status.__doc__ = ( - Client.get_account_api_trading_status.__doc__ - ) - - async def get_account_api_permissions(self, **params): - return await self._request_margin_api( - "get", "account/apiRestrictions", True, data=params - ) - - get_account_api_permissions.__doc__ = Client.get_account_api_permissions.__doc__ - - async def get_dust_assets(self, **params): - return await self._request_margin_api( - "post", "asset/dust-btc", True, data=params - ) - - get_dust_assets.__doc__ = Client.get_dust_assets.__doc__ - - async def get_dust_log(self, **params): - return await self._request_margin_api( - "get", "asset/dribblet", True, data=params - ) - - get_dust_log.__doc__ = Client.get_dust_log.__doc__ - - async def transfer_dust(self, **params): - return await self._request_margin_api("post", "asset/dust", True, data=params) - - transfer_dust.__doc__ = Client.transfer_dust.__doc__ - - async def get_asset_dividend_history(self, **params): - return await self._request_margin_api( - "get", "asset/assetDividend", True, data=params - ) - - get_asset_dividend_history.__doc__ = Client.get_asset_dividend_history.__doc__ - - async def make_universal_transfer(self, **params): - return await self._request_margin_api( - "post", "asset/transfer", signed=True, data=params - ) - - make_universal_transfer.__doc__ = Client.make_universal_transfer.__doc__ - - async def query_universal_transfer_history(self, **params): - return await self._request_margin_api( - "get", "asset/transfer", signed=True, data=params - ) - - query_universal_transfer_history.__doc__ = ( - Client.query_universal_transfer_history.__doc__ - ) - - async def get_trade_fee(self, **params): - if self.tld == "us": - endpoint = "asset/query/trading-fee" - else: - endpoint = "asset/tradeFee" - return await self._request_margin_api("get", endpoint, True, data=params) - - get_trade_fee.__doc__ = Client.get_trade_fee.__doc__ - - async def get_asset_details(self, **params): - return await self._request_margin_api( - "get", "asset/assetDetail", True, data=params - ) - - get_asset_details.__doc__ = Client.get_asset_details.__doc__ - - async def get_spot_delist_schedule(self, **params): - return await self._request_margin_api( - "get", "/spot/delist-schedule", signed=True, data=params - ) - - # Withdraw Endpoints - - async def withdraw(self, **params): - # force a name for the withdrawal if one not set - if "coin" in params and "name" not in params: - params["name"] = params["coin"] - return await self._request_margin_api( - "post", "capital/withdraw/apply", True, data=params - ) - - withdraw.__doc__ = Client.withdraw.__doc__ - - async def get_deposit_history(self, **params): - return await self._request_margin_api( - "get", "capital/deposit/hisrec", True, data=params - ) - - get_deposit_history.__doc__ = Client.get_deposit_history.__doc__ - - async def get_withdraw_history(self, **params): - return await self._request_margin_api( - "get", "capital/withdraw/history", True, data=params - ) - - get_withdraw_history.__doc__ = Client.get_withdraw_history.__doc__ - - async def get_withdraw_history_id(self, withdraw_id, **params): - result = await self.get_withdraw_history(**params) - - for entry in result: - if "id" in entry and entry["id"] == withdraw_id: - return entry - - raise Exception("There is no entry with withdraw id", result) - - get_withdraw_history_id.__doc__ = Client.get_withdraw_history_id.__doc__ - - async def get_deposit_address( - self, coin: str, network: Optional[str] = None, **params - ): - params["coin"] = coin - if network: - params["network"] = network - return await self._request_margin_api( - "get", "capital/deposit/address", True, data=params - ) - - get_deposit_address.__doc__ = Client.get_deposit_address.__doc__ - - # User Stream Endpoints - - async def stream_get_listen_key(self): - res = await self._post("userDataStream", False, data={}) - return res["listenKey"] - - stream_get_listen_key.__doc__ = Client.stream_get_listen_key.__doc__ - - async def stream_keepalive(self, listenKey): - params = {"listenKey": listenKey} - return await self._put("userDataStream", False, data=params) - - stream_keepalive.__doc__ = Client.stream_keepalive.__doc__ - - async def stream_close(self, listenKey): - params = {"listenKey": listenKey} - return await self._delete("userDataStream", False, data=params) - - stream_close.__doc__ = Client.stream_close.__doc__ - - # Margin Trading Endpoints - async def get_margin_account(self, **params): - return await self._request_margin_api( - "get", "margin/account", True, data=params - ) - - get_margin_account.__doc__ = Client.get_margin_account.__doc__ - - async def get_isolated_margin_account(self, **params): - return await self._request_margin_api( - "get", "margin/isolated/account", True, data=params - ) - - get_isolated_margin_account.__doc__ = Client.get_isolated_margin_account.__doc__ - - async def enable_isolated_margin_account(self, **params): - return await self._request_margin_api( - "post", "margin/isolated/account", True, data=params - ) - - enable_isolated_margin_account.__doc__ = ( - Client.enable_isolated_margin_account.__doc__ - ) - - async def disable_isolated_margin_account(self, **params): - return await self._request_margin_api( - "delete", "margin/isolated/account", True, data=params - ) - - disable_isolated_margin_account.__doc__ = ( - Client.disable_isolated_margin_account.__doc__ - ) - - async def get_enabled_isolated_margin_account_limit(self, **params): - return await self._request_margin_api( - "get", "margin/isolated/accountLimit", True, data=params - ) - - get_enabled_isolated_margin_account_limit.__doc__ = ( - Client.get_enabled_isolated_margin_account_limit.__doc__ - ) - - async def get_margin_dustlog(self, **params): - return await self._request_margin_api( - "get", "margin/dribblet", True, data=params - ) - - get_margin_dustlog.__doc__ = Client.get_margin_dustlog.__doc__ - - async def get_margin_dust_assets(self, **params): - return await self._request_margin_api("get", "margin/dust", True, data=params) - - get_margin_dust_assets.__doc__ = Client.get_margin_dust_assets.__doc__ - - async def transfer_margin_dust(self, **params): - return await self._request_margin_api("post", "margin/dust", True, data=params) - - transfer_margin_dust.__doc__ = Client.transfer_margin_dust.__doc__ - - async def get_cross_margin_collateral_ratio(self, **params): - return await self._request_margin_api( - "get", "margin/crossMarginCollateralRatio", True, data=params - ) - - get_cross_margin_collateral_ratio.__doc__ = ( - Client.get_cross_margin_collateral_ratio.__doc__ - ) - - async def get_small_liability_exchange_assets(self, **params): - return await self._request_margin_api( - "get", "margin/exchange-small-liability", True, data=params - ) - - get_small_liability_exchange_assets.__doc__ = ( - Client.get_small_liability_exchange_assets.__doc__ - ) - - async def exchange_small_liability_assets(self, **params): - return await self._request_margin_api( - "post", "margin/exchange-small-liability", True, data=params - ) - - exchange_small_liability_assets.__doc__ = ( - Client.exchange_small_liability_assets.__doc__ - ) - - async def get_small_liability_exchange_history(self, **params): - return await self._request_margin_api( - "get", "margin/exchange-small-liability-history", True, data=params - ) - - get_small_liability_exchange_history.__doc__ = ( - Client.get_small_liability_exchange_history.__doc__ - ) - - async def get_future_hourly_interest_rate(self, **params): - return await self._request_margin_api( - "get", "margin/next-hourly-interest-rate", True, data=params - ) - - get_future_hourly_interest_rate.__doc__ = ( - Client.get_future_hourly_interest_rate.__doc__ - ) - - async def get_margin_capital_flow(self, **params): - return await self._request_margin_api( - "get", "margin/capital-flow", True, data=params - ) - - get_margin_capital_flow.__doc__ = Client.get_margin_capital_flow.__doc__ - - async def get_margin_delist_schedule(self, **params): - return await self._request_margin_api( - "get", "margin/delist-schedule", True, data=params - ) - - get_margin_delist_schedule.__doc__ = Client.get_margin_delist_schedule.__doc__ - - async def get_margin_asset(self, **params): - return await self._request_margin_api("get", "margin/asset", data=params) - - get_margin_asset.__doc__ = Client.get_margin_asset.__doc__ - - async def get_margin_symbol(self, **params): - return await self._request_margin_api("get", "margin/pair", data=params) - - get_margin_symbol.__doc__ = Client.get_margin_symbol.__doc__ - - async def get_margin_all_assets(self, **params): - return await self._request_margin_api("get", "margin/allAssets", data=params) - - get_margin_all_assets.__doc__ = Client.get_margin_all_assets.__doc__ - - async def get_margin_all_pairs(self, **params): - return await self._request_margin_api("get", "margin/allPairs", data=params) - - get_margin_all_pairs.__doc__ = Client.get_margin_all_pairs.__doc__ - - async def create_isolated_margin_account(self, **params): - return await self._request_margin_api( - "post", "margin/isolated/create", signed=True, data=params - ) - - create_isolated_margin_account.__doc__ = ( - Client.create_isolated_margin_account.__doc__ - ) - - async def get_isolated_margin_symbol(self, **params): - return await self._request_margin_api( - "get", "margin/isolated/pair", signed=True, data=params - ) - - get_isolated_margin_symbol.__doc__ = Client.get_isolated_margin_symbol.__doc__ - - async def get_all_isolated_margin_symbols(self, **params): - return await self._request_margin_api( - "get", "margin/isolated/allPairs", signed=True, data=params - ) - - get_all_isolated_margin_symbols.__doc__ = ( - Client.get_all_isolated_margin_symbols.__doc__ - ) - - async def get_isolated_margin_fee_data(self, **params): - return await self._request_margin_api( - "get", "margin/isolatedMarginData", True, data=params - ) - - get_isolated_margin_fee_data.__doc__ = Client.get_isolated_margin_fee_data.__doc__ - - async def get_isolated_margin_tier_data(self, **params): - return await self._request_margin_api( - "get", "margin/isolatedMarginTier", True, data=params - ) - - get_isolated_margin_tier_data.__doc__ = Client.get_isolated_margin_tier_data.__doc__ - - async def margin_manual_liquidation(self, **params): - return await self._request_margin_api( - "get", "margin/manual-liquidation", True, data=params - ) - - margin_manual_liquidation.__doc__ = Client.margin_manual_liquidation.__doc__ - - async def toggle_bnb_burn_spot_margin(self, **params): - return await self._request_margin_api( - "post", "bnbBurn", signed=True, data=params - ) - - toggle_bnb_burn_spot_margin.__doc__ = Client.toggle_bnb_burn_spot_margin.__doc__ - - async def get_bnb_burn_spot_margin(self, **params): - return await self._request_margin_api( - "get", "bnbBurn", signed=True, data=params - ) - - get_bnb_burn_spot_margin.__doc__ = Client.get_bnb_burn_spot_margin.__doc__ - - async def get_margin_price_index(self, **params): - return await self._request_margin_api("get", "margin/priceIndex", data=params) - - get_margin_price_index.__doc__ = Client.get_margin_price_index.__doc__ - - async def transfer_margin_to_spot(self, **params): - params["type"] = 2 - return await self._request_margin_api( - "post", "margin/transfer", signed=True, data=params - ) - - transfer_margin_to_spot.__doc__ = Client.transfer_margin_to_spot.__doc__ - - async def transfer_spot_to_margin(self, **params): - params["type"] = 1 - return await self._request_margin_api( - "post", "margin/transfer", signed=True, data=params - ) - - transfer_spot_to_margin.__doc__ = Client.transfer_spot_to_margin.__doc__ - - async def transfer_isolated_margin_to_spot(self, **params): - params["transFrom"] = "ISOLATED_MARGIN" - params["transTo"] = "SPOT" - return await self._request_margin_api( - "post", "margin/isolated/transfer", signed=True, data=params - ) - - transfer_isolated_margin_to_spot.__doc__ = ( - Client.transfer_isolated_margin_to_spot.__doc__ - ) - - async def transfer_spot_to_isolated_margin(self, **params): - params["transFrom"] = "SPOT" - params["transTo"] = "ISOLATED_MARGIN" - return await self._request_margin_api( - "post", "margin/isolated/transfer", signed=True, data=params - ) - - transfer_spot_to_isolated_margin.__doc__ = ( - Client.transfer_spot_to_isolated_margin.__doc__ - ) - - async def create_margin_loan(self, **params): - return await self._request_margin_api( - "post", "margin/loan", signed=True, data=params - ) - - create_margin_loan.__doc__ = Client.create_margin_loan.__doc__ - - async def repay_margin_loan(self, **params): - return await self._request_margin_api( - "post", "margin/repay", signed=True, data=params - ) - - repay_margin_loan.__doc__ = Client.repay_margin_loan.__doc__ - - async def create_margin_order(self, **params): - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.SPOT_ORDER_PREFIX + self.uuid22() - return await self._request_margin_api( - "post", "margin/order", signed=True, data=params - ) - - create_margin_order.__doc__ = Client.create_margin_order.__doc__ - - async def cancel_margin_order(self, **params): - return await self._request_margin_api( - "delete", "margin/order", signed=True, data=params - ) - - cancel_margin_order.__doc__ = Client.cancel_margin_order.__doc__ - - async def cancel_all_open_margin_orders(self, **params): - return await self._request_margin_api("delete", "margin/openOrders", signed=True, data=params) - - cancel_all_open_margin_orders.__doc__ = Client.cancel_all_open_margin_orders.__doc__ - - async def set_margin_max_leverage(self, **params): - return await self._request_margin_api( - "post", "margin/max-leverage", signed=True, data=params - ) - - set_margin_max_leverage.__doc__ = Client.set_margin_max_leverage.__doc__ - - async def get_margin_transfer_history(self, **params): - return await self._request_margin_api( - "get", "margin/transfer", signed=True, data=params - ) - - get_margin_transfer_history.__doc__ = Client.get_margin_transfer_history.__doc__ - - async def get_margin_loan_details(self, **params): - return await self._request_margin_api( - "get", "margin/loan", signed=True, data=params - ) - - get_margin_loan_details.__doc__ = Client.get_margin_loan_details.__doc__ - - async def get_margin_repay_details(self, **params): - return await self._request_margin_api( - "get", "margin/repay", signed=True, data=params - ) - - async def get_cross_margin_data(self, **params): - return await self._request_margin_api( - "get", "margin/crossMarginData", signed=True, data=params - ) - - async def get_margin_interest_history(self, **params): - return await self._request_margin_api( - "get", "margin/interestHistory", signed=True, data=params - ) - - async def get_margin_force_liquidation_rec(self, **params): - return await self._request_margin_api( - "get", "margin/forceLiquidationRec", signed=True, data=params - ) - - async def get_margin_order(self, **params): - return await self._request_margin_api( - "get", "margin/order", signed=True, data=params - ) - - async def get_open_margin_orders(self, **params): - return await self._request_margin_api( - "get", "margin/openOrders", signed=True, data=params - ) - - async def get_all_margin_orders(self, **params): - return await self._request_margin_api( - "get", "margin/allOrders", signed=True, data=params - ) - - async def get_margin_trades(self, **params): - return await self._request_margin_api( - "get", "margin/myTrades", signed=True, data=params - ) - - async def get_max_margin_loan(self, **params): - return await self._request_margin_api( - "get", "margin/maxBorrowable", signed=True, data=params - ) - - async def get_max_margin_transfer(self, **params): - return await self._request_margin_api( - "get", "margin/maxTransferable", signed=True, data=params - ) - - # Margin OCO - - async def create_margin_oco_order(self, **params): - return await self._request_margin_api( - "post", "margin/order/oco", signed=True, data=params - ) - - async def cancel_margin_oco_order(self, **params): - return await self._request_margin_api( - "delete", "margin/orderList", signed=True, data=params - ) - - async def get_margin_oco_order(self, **params): - return await self._request_margin_api( - "get", "margin/orderList", signed=True, data=params - ) - - async def get_open_margin_oco_orders(self, **params): - return await self._request_margin_api( - "get", "margin/openOrderList", signed=True, data=params - ) - - # Cross-margin - - async def margin_stream_get_listen_key(self): - res = await self._request_margin_api( - "post", "userDataStream", signed=False, data={} - ) - return res["listenKey"] - - async def margin_stream_keepalive(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_margin_api( - "put", "userDataStream", signed=False, data=params - ) - - async def margin_stream_close(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_margin_api( - "delete", "userDataStream", signed=False, data=params - ) - - # Isolated margin - - async def isolated_margin_stream_get_listen_key(self, symbol): - params = {"symbol": symbol} - res = await self._request_margin_api( - "post", "userDataStream/isolated", signed=False, data=params - ) - return res["listenKey"] - - async def isolated_margin_stream_keepalive(self, symbol, listenKey): - params = {"symbol": symbol, "listenKey": listenKey} - return await self._request_margin_api( - "put", "userDataStream/isolated", signed=False, data=params - ) - - async def isolated_margin_stream_close(self, symbol, listenKey): - params = {"symbol": symbol, "listenKey": listenKey} - return await self._request_margin_api( - "delete", "userDataStream/isolated", signed=False, data=params - ) - - # Simple Earn Endpoints - - async def get_simple_earn_flexible_product_list(self, **params): - return await self._request_margin_api( - "get", "simple-earn/flexible/list", signed=True, data=params - ) - - get_simple_earn_flexible_product_list.__doc__ = ( - Client.get_simple_earn_flexible_product_list.__doc__ - ) - - async def get_simple_earn_locked_product_list(self, **params): - return await self._request_margin_api( - "get", "simple-earn/locked/list", signed=True, data=params - ) - - get_simple_earn_locked_product_list.__doc__ = ( - Client.get_simple_earn_locked_product_list.__doc__ - ) - - async def subscribe_simple_earn_flexible_product(self, **params): - return await self._request_margin_api( - "post", "simple-earn/flexible/subscribe", signed=True, data=params - ) - - subscribe_simple_earn_flexible_product.__doc__ = ( - Client.subscribe_simple_earn_flexible_product.__doc__ - ) - - async def subscribe_simple_earn_locked_product(self, **params): - return await self._request_margin_api( - "post", "simple-earn/locked/subscribe", signed=True, data=params - ) - - subscribe_simple_earn_locked_product.__doc__ = ( - Client.subscribe_simple_earn_locked_product.__doc__ - ) - - async def redeem_simple_earn_flexible_product(self, **params): - return await self._request_margin_api( - "post", "simple-earn/flexible/redeem", signed=True, data=params - ) - - redeem_simple_earn_flexible_product.__doc__ = ( - Client.redeem_simple_earn_flexible_product.__doc__ - ) - - async def redeem_simple_earn_locked_product(self, **params): - return await self._request_margin_api( - "post", "simple-earn/locked/redeem", signed=True, data=params - ) - - redeem_simple_earn_locked_product.__doc__ = ( - Client.redeem_simple_earn_locked_product.__doc__ - ) - - async def get_simple_earn_flexible_product_position(self, **params): - return await self._request_margin_api( - "get", "simple-earn/flexible/position", signed=True, data=params - ) - - get_simple_earn_flexible_product_position.__doc__ = ( - Client.get_simple_earn_flexible_product_position.__doc__ - ) - - async def get_simple_earn_locked_product_position(self, **params): - return await self._request_margin_api( - "get", "simple-earn/locked/position", signed=True, data=params - ) - - get_simple_earn_locked_product_position.__doc__ = ( - Client.get_simple_earn_locked_product_position.__doc__ - ) - - async def get_simple_earn_account(self, **params): - return await self._request_margin_api( - "get", "simple-earn/account", signed=True, data=params - ) - - get_simple_earn_account.__doc__ = Client.get_simple_earn_account.__doc__ - - # Lending Endpoints - - async def get_fixed_activity_project_list(self, **params): - return await self._request_margin_api( - "get", "lending/project/list", signed=True, data=params - ) - - async def change_fixed_activity_to_daily_position(self, **params): - return await self._request_margin_api( - "post", "lending/positionChanged", signed=True, data=params - ) - - # Staking Endpoints - - async def get_staking_product_list(self, **params): - return await self._request_margin_api( - "get", "staking/productList", signed=True, data=params - ) - - async def purchase_staking_product(self, **params): - return await self._request_margin_api( - "post", "staking/purchase", signed=True, data=params - ) - - async def redeem_staking_product(self, **params): - return await self._request_margin_api( - "post", "staking/redeem", signed=True, data=params - ) - - async def get_staking_position(self, **params): - return await self._request_margin_api( - "get", "staking/position", signed=True, data=params - ) - - async def get_staking_purchase_history(self, **params): - return await self._request_margin_api( - "get", "staking/purchaseRecord", signed=True, data=params - ) - - async def set_auto_staking(self, **params): - return await self._request_margin_api( - "post", "staking/setAutoStaking", signed=True, data=params - ) - - async def get_personal_left_quota(self, **params): - return await self._request_margin_api( - "get", "staking/personalLeftQuota", signed=True, data=params - ) - - # US Staking Endpoints - - async def get_staking_asset_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api("get", "staking/asset", True, data=params) - - get_staking_asset_us.__doc__ = Client.get_staking_asset_us.__doc__ - - async def stake_asset_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api( - "post", "staking/stake", True, data=params - ) - - stake_asset_us.__doc__ = Client.stake_asset_us.__doc__ - - async def unstake_asset_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api( - "post", "staking/unstake", True, data=params - ) - - unstake_asset_us.__doc__ = Client.unstake_asset_us.__doc__ - - async def get_staking_balance_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api( - "get", "staking/stakingBalance", True, data=params - ) - - get_staking_balance_us.__doc__ = Client.get_staking_balance_us.__doc__ - - async def get_staking_history_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api( - "get", "staking/history", True, data=params - ) - - get_staking_history_us.__doc__ = Client.get_staking_history_us.__doc__ - - async def get_staking_rewards_history_us(self, **params): - assert self.tld == "us", "Endpoint only available on binance.us" - return await self._request_margin_api( - "get", "staking/stakingRewardsHistory", True, data=params - ) - - get_staking_rewards_history_us.__doc__ = ( - Client.get_staking_rewards_history_us.__doc__ - ) - - # Sub Accounts - - async def get_sub_account_list(self, **params): - return await self._request_margin_api( - "get", "sub-account/list", True, data=params - ) - - async def get_sub_account_transfer_history(self, **params): - return await self._request_margin_api( - "get", "sub-account/sub/transfer/history", True, data=params - ) - - async def get_sub_account_futures_transfer_history(self, **params): - return await self._request_margin_api( - "get", "sub-account/futures/internalTransfer", True, data=params - ) - - async def create_sub_account_futures_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/futures/internalTransfer", True, data=params - ) - - async def get_sub_account_assets(self, **params): - return await self._request_margin_api( - "get", "sub-account/assets", True, data=params, version=4 - ) - - async def query_subaccount_spot_summary(self, **params): - return await self._request_margin_api( - "get", "sub-account/spotSummary", True, data=params - ) - - async def get_subaccount_deposit_address(self, **params): - return await self._request_margin_api( - "get", "capital/deposit/subAddress", True, data=params - ) - - async def get_subaccount_deposit_history(self, **params): - return await self._request_margin_api( - "get", "capital/deposit/subHisrec", True, data=params - ) - - async def get_subaccount_futures_margin_status(self, **params): - return await self._request_margin_api( - "get", "sub-account/status", True, data=params - ) - - async def enable_subaccount_margin(self, **params): - return await self._request_margin_api( - "post", "sub-account/margin/enable", True, data=params - ) - - async def get_subaccount_margin_details(self, **params): - return await self._request_margin_api( - "get", "sub-account/margin/account", True, data=params - ) - - async def get_subaccount_margin_summary(self, **params): - return await self._request_margin_api( - "get", "sub-account/margin/accountSummary", True, data=params - ) - - async def enable_subaccount_futures(self, **params): - return await self._request_margin_api( - "post", "sub-account/futures/enable", True, data=params - ) - - async def get_subaccount_futures_details(self, **params): - return await self._request_margin_api( - "get", "sub-account/futures/account", True, data=params, version=2 - ) - - async def get_subaccount_futures_summary(self, **params): - return await self._request_margin_api( - "get", "sub-account/futures/accountSummary", True, data=params, version=2 - ) - - async def get_subaccount_futures_positionrisk(self, **params): - return await self._request_margin_api( - "get", "sub-account/futures/positionRisk", True, data=params, version=2 - ) - - async def make_subaccount_futures_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/futures/transfer", True, data=params - ) - - async def make_subaccount_margin_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/margin/transfer", True, data=params - ) - - async def make_subaccount_to_subaccount_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/transfer/subToSub", True, data=params - ) - - async def make_subaccount_to_master_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/transfer/subToMaster", True, data=params - ) - - async def get_subaccount_transfer_history(self, **params): - return await self._request_margin_api( - "get", "sub-account/transfer/subUserHistory", True, data=params - ) - - async def make_subaccount_universal_transfer(self, **params): - return await self._request_margin_api( - "post", "sub-account/universalTransfer", True, data=params - ) - - async def get_universal_transfer_history(self, **params): - return await self._request_margin_api( - "get", "sub-account/universalTransfer", True, data=params - ) - - # Futures API - - async def futures_ping(self): - return await self._request_futures_api("get", "ping") - - async def futures_time(self): - return await self._request_futures_api("get", "time") - - async def futures_exchange_info(self): - return await self._request_futures_api("get", "exchangeInfo") - - async def futures_order_book(self, **params): - return await self._request_futures_api("get", "depth", data=params) - - async def futures_recent_trades(self, **params): - return await self._request_futures_api("get", "trades", data=params) - - async def futures_historical_trades(self, **params): - return await self._request_futures_api("get", "historicalTrades", data=params) - - async def futures_aggregate_trades(self, **params): - return await self._request_futures_api("get", "aggTrades", data=params) - - async def futures_klines(self, **params): - return await self._request_futures_api("get", "klines", data=params) - - async def futures_continous_klines(self, **params): - return await self._request_futures_api("get", "continuousKlines", data=params) - - async def futures_historical_klines( - self, symbol, interval, start_str, end_str=None, limit=500 - ): - return await self._historical_klines( - symbol, - interval, - start_str, - end_str=end_str, - limit=limit, - klines_type=HistoricalKlinesType.FUTURES, - ) - - async def futures_historical_klines_generator( - self, symbol, interval, start_str, end_str=None - ): - return self._historical_klines_generator( - symbol, - interval, - start_str, - end_str=end_str, - klines_type=HistoricalKlinesType.FUTURES, - ) - - async def futures_mark_price(self, **params): - return await self._request_futures_api("get", "premiumIndex", data=params) - - async def futures_funding_rate(self, **params): - return await self._request_futures_api("get", "fundingRate", data=params) - - async def futures_top_longshort_account_ratio(self, **params): - return await self._request_futures_data_api( - "get", "topLongShortAccountRatio", data=params - ) - - async def futures_top_longshort_position_ratio(self, **params): - return await self._request_futures_data_api( - "get", "topLongShortPositionRatio", data=params - ) - - async def futures_global_longshort_ratio(self, **params): - return await self._request_futures_data_api( - "get", "globalLongShortAccountRatio", data=params - ) - - async def futures_ticker(self, **params): - return await self._request_futures_api("get", "ticker/24hr", data=params) - - async def futures_symbol_ticker(self, **params): - return await self._request_futures_api("get", "ticker/price", data=params) - - async def futures_orderbook_ticker(self, **params): - return await self._request_futures_api("get", "ticker/bookTicker", data=params) - - async def futures_liquidation_orders(self, **params): - return await self._request_futures_api( - "get", "forceOrders", signed=True, data=params - ) - - async def futures_api_trading_status(self, **params): - return await self._request_futures_api( - "get", "apiTradingStatus", signed=True, data=params - ) - - async def futures_commission_rate(self, **params): - return await self._request_futures_api( - "get", "commissionRate", signed=True, data=params - ) - - async def futures_adl_quantile_estimate(self, **params): - return await self._request_futures_api( - "get", "adlQuantile", signed=True, data=params - ) - - async def futures_open_interest(self, **params): - return await self._request_futures_api("get", "openInterest", data=params) - - async def futures_index_info(self, **params): - return await self._request_futures_api("get", "indexInfo", data=params) - - async def futures_open_interest_hist(self, **params): - return await self._request_futures_data_api( - "get", "openInterestHist", data=params - ) - - async def futures_leverage_bracket(self, **params): - return await self._request_futures_api( - "get", "leverageBracket", True, data=params - ) - - async def futures_account_transfer(self, **params): - return await self._request_margin_api( - "post", "futures/transfer", True, data=params - ) - - async def transfer_history(self, **params): - return await self._request_margin_api( - "get", "futures/transfer", True, data=params - ) - - async def futures_loan_borrow_history(self, **params): - return await self._request_margin_api( - "get", "futures/loan/borrow/history", True, data=params - ) - - async def futures_loan_repay_history(self, **params): - return await self._request_margin_api( - "get", "futures/loan/repay/history", True, data=params - ) - - async def futures_loan_wallet(self, **params): - return await self._request_margin_api( - "get", "futures/loan/wallet", True, data=params, version=2 - ) - - async def futures_cross_collateral_adjust_history(self, **params): - return await self._request_margin_api( - "get", "futures/loan/adjustCollateral/history", True, data=params - ) - - async def futures_cross_collateral_liquidation_history(self, **params): - return await self._request_margin_api( - "get", "futures/loan/liquidationHistory", True, data=params - ) - - async def futures_loan_interest_history(self, **params): - return await self._request_margin_api( - "get", "futures/loan/interestHistory", True, data=params - ) - - async def futures_create_order(self, **params): - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_futures_api("post", "order", True, data=params) - - async def futures_create_test_order(self, **params): - return await self._request_futures_api("post", "order/test", True, data=params) - - async def futures_place_batch_order(self, **params): - for order in params["batchOrders"]: - if "newClientOrderId" not in order: - order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - query_string = urlencode(params) - query_string = query_string.replace("%27", "%22") - params["batchOrders"] = query_string[12:] - return await self._request_futures_api("post", "batchOrders", True, data=params) - - async def futures_get_order(self, **params): - return await self._request_futures_api("get", "order", True, data=params) - - async def futures_get_open_orders(self, **params): - return await self._request_futures_api("get", "openOrders", True, data=params) - - async def futures_get_all_orders(self, **params): - return await self._request_futures_api("get", "allOrders", True, data=params) - - async def futures_cancel_order(self, **params): - return await self._request_futures_api("delete", "order", True, data=params) - - async def futures_cancel_all_open_orders(self, **params): - return await self._request_futures_api( - "delete", "allOpenOrders", True, data=params - ) - - async def futures_cancel_orders(self, **params): - return await self._request_futures_api( - "delete", "batchOrders", True, data=params - ) - - async def futures_countdown_cancel_all(self, **params): - return await self._request_futures_api( - "post", "countdownCancelAll", True, data=params - ) - - async def futures_account_balance(self, **params): - return await self._request_futures_api( - "get", "balance", True, version=3, data=params - ) - - async def futures_account(self, **params): - return await self._request_futures_api( - "get", "account", True, version=2, data=params - ) - - async def futures_change_leverage(self, **params): - return await self._request_futures_api("post", "leverage", True, data=params) - - async def futures_change_margin_type(self, **params): - return await self._request_futures_api("post", "marginType", True, data=params) - - async def futures_change_position_margin(self, **params): - return await self._request_futures_api( - "post", "positionMargin", True, data=params - ) - - async def futures_position_margin_history(self, **params): - return await self._request_futures_api( - "get", "positionMargin/history", True, data=params - ) - - async def futures_position_information(self, **params): - return await self._request_futures_api( - "get", "positionRisk", True, version=3, data=params - ) - - async def futures_account_trades(self, **params): - return await self._request_futures_api("get", "userTrades", True, data=params) - - async def futures_income_history(self, **params): - return await self._request_futures_api("get", "income", True, data=params) - - async def futures_change_position_mode(self, **params): - return await self._request_futures_api( - "post", "positionSide/dual", True, data=params - ) - - async def futures_get_position_mode(self, **params): - return await self._request_futures_api( - "get", "positionSide/dual", True, data=params - ) - - async def futures_change_multi_assets_mode(self, multiAssetsMargin: bool): - params = {"multiAssetsMargin": "true" if multiAssetsMargin else "false"} - return await self._request_futures_api( - "post", "multiAssetsMargin", True, data=params - ) - - async def futures_get_multi_assets_mode(self): - return await self._request_futures_api( - "get", "multiAssetsMargin", True, data={} - ) - - async def futures_stream_get_listen_key(self): - res = await self._request_futures_api( - "post", "listenKey", signed=False, data={} - ) - return res["listenKey"] - - async def futures_stream_keepalive(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_futures_api( - "put", "listenKey", signed=False, data=params - ) - - async def futures_stream_close(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_futures_api( - "delete", "listenKey", signed=False, data=params - ) - - # new methods - async def futures_account_config(self, **params): - return await self._request_futures_api( - "get", "accountConfig", signed=True, version=1, data=params - ) - - async def futures_symbol_config(self, **params): - return await self._request_futures_api( - "get", "symbolConfig", signed=True, version=1, data=params - ) - - # COIN Futures API - - async def futures_coin_ping(self): - return await self._request_futures_coin_api("get", "ping") - - async def futures_coin_time(self): - return await self._request_futures_coin_api("get", "time") - - async def futures_coin_exchange_info(self): - return await self._request_futures_coin_api("get", "exchangeInfo") - - async def futures_coin_order_book(self, **params): - return await self._request_futures_coin_api("get", "depth", data=params) - - async def futures_coin_recent_trades(self, **params): - return await self._request_futures_coin_api("get", "trades", data=params) - - async def futures_coin_historical_trades(self, **params): - return await self._request_futures_coin_api( - "get", "historicalTrades", data=params - ) - - async def futures_coin_aggregate_trades(self, **params): - return await self._request_futures_coin_api("get", "aggTrades", data=params) - - async def futures_coin_klines(self, **params): - return await self._request_futures_coin_api("get", "klines", data=params) - - async def futures_coin_continous_klines(self, **params): - return await self._request_futures_coin_api( - "get", "continuousKlines", data=params - ) - - async def futures_coin_index_price_klines(self, **params): - return await self._request_futures_coin_api( - "get", "indexPriceKlines", data=params - ) - - async def futures_coin_mark_price_klines(self, **params): - return await self._request_futures_coin_api( - "get", "markPriceKlines", data=params - ) - - async def futures_coin_mark_price(self, **params): - return await self._request_futures_coin_api("get", "premiumIndex", data=params) - - async def futures_coin_funding_rate(self, **params): - return await self._request_futures_coin_api("get", "fundingRate", data=params) - - async def futures_coin_ticker(self, **params): - return await self._request_futures_coin_api("get", "ticker/24hr", data=params) - - async def futures_coin_symbol_ticker(self, **params): - return await self._request_futures_coin_api("get", "ticker/price", data=params) - - async def futures_coin_orderbook_ticker(self, **params): - return await self._request_futures_coin_api( - "get", "ticker/bookTicker", data=params - ) - - async def futures_coin_liquidation_orders(self, **params): - return await self._request_futures_coin_api( - "get", "forceOrders", signed=True, data=params - ) - - async def futures_coin_open_interest(self, **params): - return await self._request_futures_coin_api("get", "openInterest", data=params) - - async def futures_coin_open_interest_hist(self, **params): - return await self._request_futures_coin_data_api( - "get", "openInterestHist", data=params - ) - - async def futures_coin_leverage_bracket(self, **params): - return await self._request_futures_coin_api( - "get", "leverageBracket", version=2, signed=True, data=params - ) - - async def new_transfer_history(self, **params): - return await self._request_margin_api( - "get", "asset/transfer", True, data=params - ) - - async def funding_wallet(self, **params): - return await self._request_margin_api( - "post", "asset/get-funding-asset", True, data=params - ) - - async def get_user_asset(self, **params): - return await self._request_margin_api( - "post", "asset/getUserAsset", True, data=params, version=3 - ) - - async def universal_transfer(self, **params): - return await self._request_margin_api( - "post", "asset/transfer", signed=True, data=params - ) - - async def futures_coin_create_order(self, **params): - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_futures_coin_api("post", "order", True, data=params) - - async def futures_coin_place_batch_order(self, **params): - for order in params["batchOrders"]: - if "newClientOrderId" not in order: - order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - query_string = urlencode(params) - query_string = query_string.replace("%27", "%22") - params["batchOrders"] = query_string[12:] - - return await self._request_futures_coin_api( - "post", "batchOrders", True, data=params - ) - - async def futures_coin_get_order(self, **params): - return await self._request_futures_coin_api("get", "order", True, data=params) - - async def futures_coin_get_open_orders(self, **params): - return await self._request_futures_coin_api( - "get", "openOrders", True, data=params - ) - - async def futures_coin_get_all_orders(self, **params): - return await self._request_futures_coin_api( - "get", "allOrders", signed=True, data=params - ) - - async def futures_coin_cancel_order(self, **params): - return await self._request_futures_coin_api( - "delete", "order", signed=True, data=params - ) - - async def futures_coin_cancel_all_open_orders(self, **params): - return await self._request_futures_coin_api( - "delete", "allOpenOrders", signed=True, data=params - ) - - async def futures_coin_cancel_orders(self, **params): - return await self._request_futures_coin_api( - "delete", "batchOrders", True, data=params - ) - - async def futures_coin_account_balance(self, **params): - return await self._request_futures_coin_api( - "get", "balance", signed=True, data=params - ) - - async def futures_coin_account(self, **params): - return await self._request_futures_coin_api( - "get", "account", signed=True, data=params - ) - - async def futures_coin_change_leverage(self, **params): - return await self._request_futures_coin_api( - "post", "leverage", signed=True, data=params - ) - - async def futures_coin_change_margin_type(self, **params): - return await self._request_futures_coin_api( - "post", "marginType", signed=True, data=params - ) - - async def futures_coin_change_position_margin(self, **params): - return await self._request_futures_coin_api( - "post", "positionMargin", True, data=params - ) - - async def futures_coin_position_margin_history(self, **params): - return await self._request_futures_coin_api( - "get", "positionMargin/history", True, data=params - ) - - async def futures_coin_position_information(self, **params): - return await self._request_futures_coin_api( - "get", "positionRisk", True, data=params - ) - - async def futures_coin_account_trades(self, **params): - return await self._request_futures_coin_api( - "get", "userTrades", True, data=params - ) - - async def futures_coin_income_history(self, **params): - return await self._request_futures_coin_api("get", "income", True, data=params) - - async def futures_coin_change_position_mode(self, **params): - return await self._request_futures_coin_api( - "post", "positionSide/dual", True, data=params - ) - - async def futures_coin_get_position_mode(self, **params): - return await self._request_futures_coin_api( - "get", "positionSide/dual", True, data=params - ) - - async def futures_coin_stream_get_listen_key(self): - res = await self._request_futures_coin_api( - "post", "listenKey", signed=False, data={} - ) - return res["listenKey"] - - async def futures_coin_stream_keepalive(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_futures_coin_api( - "put", "listenKey", signed=False, data=params - ) - - async def futures_coin_stream_close(self, listenKey): - params = {"listenKey": listenKey} - return await self._request_futures_coin_api( - "delete", "listenKey", signed=False, data=params - ) - - async def get_all_coins_info(self, **params): - return await self._request_margin_api( - "get", "capital/config/getall", True, data=params - ) - - async def get_account_snapshot(self, **params): - return await self._request_margin_api( - "get", "accountSnapshot", True, data=params - ) - - async def disable_fast_withdraw_switch(self, **params): - return await self._request_margin_api( - "post", "disableFastWithdrawSwitch", True, data=params - ) - - async def enable_fast_withdraw_switch(self, **params): - return await self._request_margin_api( - "post", "enableFastWithdrawSwitch", True, data=params - ) - - """ - ==================================================================================================================== - Options API - ==================================================================================================================== - """ - - # Quoting interface endpoints - - async def options_ping(self): - return await self._request_options_api("get", "ping") - - async def options_time(self): - return await self._request_options_api("get", "time") - - async def options_info(self): - return await self._request_options_api("get", "optionInfo") - - async def options_exchange_info(self): - return await self._request_options_api("get", "exchangeInfo") - - async def options_index_price(self, **params): - return await self._request_options_api("get", "index", data=params) - - async def options_price(self, **params): - return await self._request_options_api("get", "ticker", data=params) - - async def options_mark_price(self, **params): - return await self._request_options_api("get", "mark", data=params) - - async def options_order_book(self, **params): - return await self._request_options_api("get", "depth", data=params) - - async def options_klines(self, **params): - return await self._request_options_api("get", "klines", data=params) - - async def options_recent_trades(self, **params): - return await self._request_options_api("get", "trades", data=params) - - async def options_historical_trades(self, **params): - return await self._request_options_api("get", "historicalTrades", data=params) - - # Account and trading interface endpoints - - async def options_account_info(self, **params): - return await self._request_options_api( - "get", "account", signed=True, data=params - ) - - async def options_funds_transfer(self, **params): - return await self._request_options_api( - "post", "transfer", signed=True, data=params - ) - - async def options_positions(self, **params): - return await self._request_options_api( - "get", "position", signed=True, data=params - ) - - async def options_bill(self, **params): - return await self._request_options_api("post", "bill", signed=True, data=params) - - async def options_place_order(self, **params): - if "clientOrderId" not in params: - params["clientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_options_api( - "post", "order", signed=True, data=params - ) - - async def options_place_batch_order(self, **params): - for order in params["batchOrders"]: - if "newClientOrderId" not in order: - order["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_options_api( - "post", "batchOrders", signed=True, data=params - ) - - async def options_cancel_order(self, **params): - return await self._request_options_api( - "delete", "order", signed=True, data=params - ) - - async def options_cancel_batch_order(self, **params): - return await self._request_options_api( - "delete", "batchOrders", signed=True, data=params - ) - - async def options_cancel_all_orders(self, **params): - return await self._request_options_api( - "delete", "allOpenOrders", signed=True, data=params - ) - - async def options_query_order(self, **params): - return await self._request_options_api("get", "order", signed=True, data=params) - - async def options_query_pending_orders(self, **params): - return await self._request_options_api( - "get", "openOrders", signed=True, data=params - ) - - async def options_query_order_history(self, **params): - return await self._request_options_api( - "get", "historyOrders", signed=True, data=params - ) - - async def options_user_trades(self, **params): - return await self._request_options_api( - "get", "userTrades", signed=True, data=params - ) - - # Fiat Endpoints - - async def get_fiat_deposit_withdraw_history(self, **params): - return await self._request_margin_api( - "get", "fiat/orders", signed=True, data=params - ) - - async def get_fiat_payments_history(self, **params): - return await self._request_margin_api( - "get", "fiat/payments", signed=True, data=params - ) - - # C2C Endpoints - - async def get_c2c_trade_history(self, **params): - return await self._request_margin_api( - "get", "c2c/orderMatch/listUserOrderHistory", signed=True, data=params - ) - - # Pay Endpoints - - async def get_pay_trade_history(self, **params): - return await self._request_margin_api( - "get", "pay/transactions", signed=True, data=params - ) - - get_pay_trade_history.__doc__ = Client.get_pay_trade_history.__doc__ - - # Convert Endpoints - - async def get_convert_trade_history(self, **params): - return await self._request_margin_api( - "get", "convert/tradeFlow", signed=True, data=params - ) - - get_convert_trade_history.__doc__ = Client.get_convert_trade_history.__doc__ - - async def convert_request_quote(self, **params): - return await self._request_margin_api( - "post", "convert/getQuote", signed=True, data=params - ) - - convert_request_quote.__doc__ = Client.convert_request_quote.__doc__ - - async def convert_accept_quote(self, **params): - return await self._request_margin_api( - "post", "convert/acceptQuote", signed=True, data=params - ) - - convert_accept_quote.__doc__ = Client.convert_accept_quote.__doc__ - - """ - ==================================================================================================================== - PortfolioMargin API - ==================================================================================================================== - """ - - async def papi_get_balance(self, **params): - return await self._request_papi_api("get", "balance", signed=True, data=params) - - async def papi_get_account(self, **params): - return await self._request_papi_api("get", "account", signed=True, data=params) - - async def papi_get_margin_max_borrowable(self, **params): - return await self._request_papi_api( - "get", "margin/maxBorrowable", signed=True, data=params - ) - - async def papi_get_margin_max_withdraw(self, **params): - return await self._request_papi_api( - "get", "margin/maxWithdraw", signed=True, data=params - ) - - async def papi_get_um_position_risk(self, **params): - return await self._request_papi_api( - "get", "um/positionRisk", signed=True, data=params - ) - - async def papi_get_cm_position_risk(self, **params): - return await self._request_papi_api( - "get", "cm/positionRisk", signed=True, data=params - ) - - async def papi_set_um_leverage(self, **params): - return await self._request_papi_api( - "post", "um/leverage", signed=True, data=params - ) - - async def papi_set_cm_leverage(self, **params): - return await self._request_papi_api( - "post", "cm/leverage", signed=True, data=params - ) - - async def papi_change_um_position_side_dual(self, **params): - return await self._request_papi_api( - "post", "um/positionSide/dual", signed=True, data=params - ) - - async def papi_get_um_position_side_dual(self, **params): - return await self._request_papi_api( - "get", "um/positionSide/dual", signed=True, data=params - ) - - async def papi_get_cm_position_side_dual(self, **params): - return await self._request_papi_api( - "get", "cm/positionSide/dual", signed=True, data=params - ) - - async def papi_get_um_leverage_bracket(self, **params): - return await self._request_papi_api( - "get", "um/leverageBracket", signed=True, data=params - ) - - async def papi_get_cm_leverage_bracket(self, **params): - return await self._request_papi_api( - "get", "cm/leverageBracket", signed=True, data=params - ) - - async def papi_get_um_api_trading_status(self, **params): - return await self._request_papi_api( - "get", "um/apiTradingStatus", signed=True, data=params - ) - - async def papi_get_um_comission_rate(self, **params): - return await self._request_papi_api( - "get", "um/commissionRate", signed=True, data=params - ) - - async def papi_get_cm_comission_rate(self, **params): - return await self._request_papi_api( - "get", "cm/commissionRate", signed=True, data=params - ) - - async def papi_get_margin_margin_loan(self, **params): - return await self._request_papi_api( - "get", "margin/marginLoan", signed=True, data=params - ) - - async def papi_get_margin_repay_loan(self, **params): - return await self._request_papi_api( - "get", "margin/repayLoan", signed=True, data=params - ) - - async def papi_get_repay_futures_switch(self, **params): - return await self._request_papi_api( - "get", "repay-futures-switch", signed=True, data=params - ) - - async def papi_repay_futures_switch(self, **params): - return await self._request_papi_api( - "post", "repay-futures-switch", signed=True, data=params - ) - - async def papi_get_margin_interest_history(self, **params): - return await self._request_papi_api( - "get", "margin/marginInterestHistory", signed=True, data=params - ) - - async def papi_repay_futures_negative_balance(self, **params): - return await self._request_papi_api( - "post", "repay-futures-negative-balance", signed=True, data=params - ) - - async def papi_get_portfolio_interest_history(self, **params): - return await self._request_papi_api( - "get", "portfolio/interest-history", signed=True, data=params - ) - - async def papi_fund_auto_collection(self, **params): - return await self._request_papi_api( - "post", "auto-collection", signed=True, data=params - ) - - async def papi_fund_asset_collection(self, **params): - return await self._request_papi_api( - "post", "asset-collection", signed=True, data=params - ) - - async def papi_bnb_transfer(self, **params): - return await self._request_papi_api( - "post", "bnb-transfer", signed=True, data=params - ) - - async def papi_get_um_income_history(self, **params): - return await self._request_papi_api( - "get", "um/income", signed=True, data=params - ) - - async def papi_get_cm_income_history(self, **params): - return await self._request_papi_api( - "get", "cm/income", signed=True, data=params - ) - - async def papi_get_um_account(self, **params): - return await self._request_papi_api( - "get", "um/account", signed=True, data=params - ) - - async def papi_get_um_account_v2(self, **params): - return await self._request_papi_api( - "get", "um/account", version=2, signed=True, data=params - ) - - async def papi_get_cm_account(self, **params): - return await self._request_papi_api( - "get", "cm/account", signed=True, data=params - ) - - async def papi_get_um_account_config(self, **params): - return await self._request_papi_api( - "get", "um/accountConfig", signed=True, data=params - ) - - async def papi_get_um_symbol_config(self, **params): - return await self._request_papi_api( - "get", "um/symbolConfig", signed=True, data=params - ) - - async def papi_get_um_trade_asyn(self, **params): - return await self._request_papi_api( - "get", "um/trade/asyn", signed=True, data=params - ) - - async def papi_get_um_trade_asyn_id(self, **params): - return await self._request_papi_api( - "get", "um/trade/asyn/id", signed=True, data=params - ) - - async def papi_get_um_order_asyn(self, **params): - return await self._request_papi_api( - "get", "um/order/asyn", signed=True, data=params - ) - - async def papi_get_um_order_asyn_id(self, **params): - return await self._request_papi_api( - "get", "um/order/asyn/id", signed=True, data=params - ) - - async def papi_get_um_income_asyn(self, **params): - return await self._request_papi_api( - "get", "um/income/asyn", signed=True, data=params - ) - - async def papi_get_um_income_asyn_id(self, **params): - return await self._request_papi_api( - "get", "um/income/asyn/id", signed=True, data=params - ) - - async def papi_ping(self, **params): - return await self._request_papi_api("get", "ping", signed=False, data=params) - - # papi trading endpoints - - async def papi_create_um_order(self, **params): - """Place new UM order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade - - :returns: API response - - """ - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_papi_api( - "post", "um/order", signed=True, data=params - ) - - async def papi_create_um_conditional_order(self, **params): - """Place new UM Conditional order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-UM-Conditional-Order - - :returns: API response - - """ - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_papi_api( - "post", "um/conditional/order", signed=True, data=params - ) - - async def papi_create_cm_order(self, **params): - """Place new CM order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Order - - :returns: API response - - """ - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_papi_api( - "post", "cm/order", signed=True, data=params - ) - - async def papi_create_cm_conditional_order(self, **params): - """Place new CM Conditional order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-CM-Conditional-Order - - :returns: API response - - """ - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_papi_api( - "post", "cm/conditional/order", signed=True, data=params - ) - - async def papi_create_margin_order(self, **params): - """New Margin Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/New-Margin-Order - - :returns: API response - - """ - if "newClientOrderId" not in params: - params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() - return await self._request_papi_api( - "post", "margin/order", signed=True, data=params - ) - - async def papi_margin_loan(self, **params): - """Apply for a margin loan. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Borrow - - :returns: API response - - """ - return await self._request_papi_api( - "post", "marginLoan", signed=True, data=params - ) - - async def papi_repay_loan(self, **params): - """Repay for a margin loan. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Repay - - :returns: API response - - """ - return await self._request_papi_api( - "post", "repayLoan", signed=True, data=params - ) - - async def papi_margin_order_oco(self, **params): - """Send in a new OCO for a margin account. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-New-OCO - - :returns: API response - - """ - return await self._request_papi_api( - "post", "margin/order/oco", signed=True, data=params - ) - - async def papi_cancel_um_order(self, **params): - """Cancel an active UM LIMIT order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Order - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "um/order", signed=True, data=params - ) - - async def papi_cancel_um_all_open_orders(self, **params): - """Cancel an active UM LIMIT order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Orders - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "um/allOpenOrders", signed=True, data=params - ) - - async def papi_cancel_um_conditional_order(self, **params): - """Cancel UM Conditional Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-UM-Conditional-Order - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "um/conditional/order", signed=True, data=params - ) - - async def papi_cancel_um_conditional_all_open_orders(self, **params): - """Cancel All UM Open Conditional Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-UM-Open-Conditional-Orders - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "um/conditional/allOpenOrders", signed=True, data=params - ) - - async def papi_cancel_cm_order(self, **params): - """Cancel an active CM LIMIT order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Order - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "cm/order", signed=True, data=params - ) - - async def papi_cancel_cm_all_open_orders(self, **params): - """Cancel an active CM LIMIT order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Orders - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "cm/allOpenOrders", signed=True, data=params - ) - - async def papi_cancel_cm_conditional_order(self, **params): - """Cancel CM Conditional Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-CM-Conditional-Order - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "cm/conditional/order", signed=True, data=params - ) - - async def papi_cancel_cm_conditional_all_open_orders(self, **params): - """Cancel All CM Open Conditional Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-All-CM-Open-Conditional-Orders - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "cm/conditional/allOpenOrders", signed=True, data=params - ) - - async def papi_cancel_margin_order(self, **params): - """Cancel Margin Account Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-Order - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "margin/order", signed=True, data=params - ) - - async def papi_cancel_margin_order_list(self, **params): - """Cancel Margin Account OCO Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-OCO-Orders - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "margin/orderList", signed=True, data=params - ) - - async def papi_cancel_margin_all_open_orders(self, **params): - """Cancel Margin Account All Open Orders on a Symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Cancel-Margin-Account-All-Open-Orders-on-a-Symbol - - :returns: API response - - """ - return await self._request_papi_api( - "delete", "margin/allOpenOrders", signed=True, data=params - ) - - async def papi_modify_um_order(self, **params): - """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-UM-Order - - :returns: API response - - """ - return await self._request_papi_api("put", "um/order", signed=True, data=params) - - async def papi_modify_cm_order(self, **params): - """Order modify function, currently only LIMIT order modification is supported, modified orders will be reordered in the match queue. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Modify-CM-Order - - :returns: API response - - """ - return await self._request_papi_api("put", "cm/order", signed=True, data=params) - - async def papi_get_um_order(self, **params): - """Check an UM order's status. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order - - :returns: API response + def ws_order_market_sell(self, **params): + """Send in a new market sell order + :param symbol: required + :type symbol: str + :param quantity: required + :type quantity: decimal + :param quoteOrderQty: the amount the user wants to receive of the quote asset + :type quoteOrderQty: decimal + :param newClientOrderId: A unique id for the order. Automatically generated if not sent. + :type newClientOrderId: str + :param newOrderRespType: Set the response JSON. ACK, RESULT, or FULL; default: RESULT. + :type newOrderRespType: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int + :returns: WS response + See order endpoint for full response options + """ + params.update({"side": self.SIDE_SELL}) + return self.ws_order_market(**params) + def ws_get_order(self, **params): + """Check an order's status. Either orderId or origClientOrderId must be sent. + https://binance-docs.github.io/apidocs/websocket_api/en/#query-order-user_data + :param symbol: required + :type symbol: str + :param orderId: The unique order id + :type orderId: int + :param origClientOrderId: optional + :type origClientOrderId: str + :param recvWindow: the number of milliseconds the request is valid for + :type recvWindow: int """ - return await self._request_papi_api("get", "um/order", signed=True, data=params) + return self._ws_api_request_sync("order.status", True, params) - async def papi_get_um_all_orders(self, **params): - """Get all account UM orders; active, canceled, or filled. + def ws_cancel_order(self, **params): + return self._ws_api_request_sync("order.cancel", True, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Order + cancel_order.__doc__ = cancel_order.__doc__ - :returns: API response + def ws_cancel_and_replace_order(self, **params): + return self._ws_api_request_sync("order.cancelReplace", True, params) - """ - return await self._request_papi_api( - "get", "um/allOrders", signed=True, data=params - ) + def ws_get_open_orders(self, **params): + return self._ws_api_request_sync("openOrders.status", True, params) - async def papi_get_um_open_order(self, **params): - """Query current UM open order. + def ws_cancel_all_open_orders(self, **params): + return self._ws_api_request_sync("openOrders.cancelAll", True, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Order + def ws_create_oco_order(self, **params): + return self._ws_api_request_sync("orderList.place.oco", True, params) - :returns: API response + def ws_create_oto_order(self, **params): + return self._ws_api_request_sync("orderList.place.oto", True, params) - """ - return await self._request_papi_api( - "get", "um/openOrder", signed=True, data=params - ) + def ws_create_otoco_order(self, **params): + return self._ws_api_request_sync("orderList.place.otoco", True, params) - async def papi_get_um_open_orders(self, **params): - """Get all open orders on a symbol. + def ws_get_oco_order(self, **params): + return self._ws_api_request_sync("orderList.status", True, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Orders + def ws_cancel_oco_order(self, **params): + return self._ws_api_request_sync("orderList.cancel", True, params) - :returns: API response + def ws_get_oco_open_orders(self, **params): + return self._ws_api_request_sync("openOrderLists.status", True, params) - """ - return await self._request_papi_api( - "get", "um/openOrders", signed=True, data=params - ) + def ws_create_sor_order(self, **params): + return self._ws_api_request_sync("sor.order.place", True, params) - async def papi_get_um_conditional_all_orders(self, **params): - """Query All UM Conditional Orders. + def ws_create_test_sor_order(self, **params): + return self._ws_api_request_sync("sor.order.test", True, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-UM-Conditional-Orders + def ws_get_account(self, **params): + return self._ws_api_request_sync("account.status", True, params) - :returns: API response + def ws_get_account_rate_limits_orders(self, **params): + return self._ws_api_request_sync("account.rateLimits.orders", True, params) - """ - return await self._request_papi_api( - "get", "um/conditional/allOrders", signed=True, data=params - ) + def ws_get_all_orders(self, **params): + return self._ws_api_request_sync("allOrders", True, params) - async def papi_get_um_conditional_open_orders(self, **params): - """Get all open conditional orders on a symbol. + def ws_get_my_trades(self, **params): + return self._ws_api_request_sync("myTrades", True, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-UM-Open-Conditional-Orders + def ws_get_prevented_matches(self, **params): + return self._ws_api_request_sync("myPreventedMatches", True, params) - :returns: API response + def ws_get_allocations(self, **params): + return self._ws_api_request_sync("myAllocations", True, params) - """ - return await self._request_papi_api( - "get", "um/conditional/openOrders", signed=True, data=params - ) + def ws_get_commission_rates(self, **params): + return self._ws_api_request_sync("account.commission", True, params) - async def papi_get_um_conditional_open_order(self, **params): - """Query Current UM Open Conditional Order. + def ws_get_order_book(self, **params): + return self._ws_api_request_sync("depth", False, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-UM-Open-Conditional-Order + def ws_get_recent_trades(self, **params): + return self._ws_api_request_sync("trades.recent", False, params) - :returns: API response + def ws_get_historical_trades(self, **params): + return self._ws_api_request_sync("trades.historical", False, params) - """ - return await self._request_papi_api( - "get", "um/conditional/openOrder", signed=True, data=params - ) + def ws_get_aggregate_trades(self, **params): + return self._ws_api_request_sync("trades.aggregate", False, params) - async def papi_get_um_conditional_order_history(self, **params): - """Get all open conditional orders on a symbol. + def ws_get_klines(self, **params): + return self._ws_api_request_sync("klines", False, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Conditional-Order-History + def ws_get_uiKlines(self, **params): + return self._ws_api_request_sync("uiKlines", False, params) - :returns: API response + def ws_get_avg_price(self, **params): + return self._ws_api_request_sync("avgPrice", False, params) - """ - return await self._request_papi_api( - "get", "um/conditional/orderHistory", signed=True, data=params - ) + def ws_get_ticker(self, **params): + return self._ws_api_request_sync("ticker.24hr", False, params) - async def papi_get_cm_order(self, **params): - """Check an CM order's status. + def ws_get_trading_day_ticker(self, **params): + return self._ws_api_request_sync("ticker.tradingDay", False, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + def ws_get_symbol_ticker_window(self, **params): + return self._ws_api_request_sync("ticker", False, params) - :returns: API response + def ws_get_symbol_ticker(self, **params): + return self._ws_api_request_sync("ticker.price", False, params) - """ - return await self._request_papi_api("get", "cm/order", signed=True, data=params) + def ws_get_orderbook_ticker(self, **params): + return self._ws_api_request_sync("ticker.book", False, params) - async def papi_get_cm_all_orders(self, **params): - """Get all account CM orders; active, canceled, or filled. + def ws_ping(self, **params): + return self._ws_api_request_sync("ping", False, params) - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Order + def ws_get_time(self, **params): + return self._ws_api_request_sync("time", False, params) - :returns: API response + def ws_get_exchange_info(self, **params): + return self._ws_api_request_sync("exchangeInfo", False, params) + #################################################### + # WS Futures Endpoints + #################################################### + def ws_futures_get_order_book(self, **params): """ - return await self._request_papi_api( - "get", "cm/allOrders", signed=True, data=params - ) - - async def papi_get_cm_open_order(self, **params): - """Query current CM open order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Order - - :returns: API response - + Get the order book for a symbol + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api """ - return await self._request_papi_api( - "get", "cm/openOrder", signed=True, data=params - ) - - async def papi_get_cm_open_orders(self, **params): - """Get all open orders on a symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Orders - - :returns: API response + return self._ws_futures_api_request_sync("depth", False, params) + def ws_futures_get_all_tickers(self, **params): """ - return await self._request_papi_api( - "get", "cm/openOrders", signed=True, data=params - ) - - async def papi_get_cm_conditional_all_orders(self, **params): - """Query All CM Conditional Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-CM-Conditional-Orders - - :returns: API response - + Latest price for a symbol or symbols + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Symbol-Price-Ticker """ - return await self._request_papi_api( - "get", "cm/conditional/allOrders", signed=True, data=params - ) - - async def papi_get_cm_conditional_open_orders(self, **params): - """Get all open conditional orders on a symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Current-CM-Open-Conditional-Orders - - :returns: API response + return self._ws_futures_api_request_sync("ticker.price", False, params) + def ws_futures_get_order_book_ticker(self, **params): """ - return await self._request_papi_api( - "get", "cm/conditional/openOrders", signed=True, data=params - ) - - async def papi_get_cm_conditional_open_order(self, **params): - """Query Current UM Open Conditional Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Current-CM-Open-Conditional-Order - - :returns: API response - + Best price/qty on the order book for a symbol or symbols. + https://developers.binance.com/docs/derivatives/usds-margined-futures/market-data/websocket-api/Symbol-Order-Book-Ticker """ - return await self._request_papi_api( - "get", "cm/conditional/openOrder", signed=True, data=params - ) - - async def papi_get_cm_conditional_order_history(self, **params): - """Get all open conditional orders on a symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Conditional-Order-History - - :returns: API response + return self._ws_futures_api_request_sync("ticker.book", False, params) + def ws_futures_create_order(self, **params): """ - return await self._request_papi_api( - "get", "cm/conditional/orderHistory", signed=True, data=params - ) - - async def papi_get_um_force_orders(self, **params): - """Query User's UM Force Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-UM-Force-Orders - - :returns: API response - + Send in a new order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api """ - return await self._request_papi_api( - "get", "um/forceOrders", signed=True, data=params - ) - - async def papi_get_cm_force_orders(self, **params): - """Query User's CM Force Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-CM-Force-Orders - - :returns: API response + if "newClientOrderId" not in params: + params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() + return self._ws_futures_api_request_sync("order.place", True, params) + def ws_futures_edit_order(self, **params): """ - return await self._request_papi_api( - "get", "cm/forceOrders", signed=True, data=params - ) - - async def papi_get_um_order_amendment(self, **params): - """Get order modification history. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-UM-Modify-Order-History - - :returns: API response - + Edit an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Modify-Order """ - return await self._request_papi_api( - "get", "um/orderAmendment", signed=True, data=params - ) - - async def papi_get_cm_order_amendment(self, **params): - """Get order modification history. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-CM-Modify-Order-History - - :returns: API response + return self._ws_futures_api_request_sync("order.modify", True, params) + def ws_futures_cancel_order(self, **params): """ - return await self._request_papi_api( - "get", "cm/orderAmendment", signed=True, data=params - ) - - async def papi_get_margin_force_orders(self, **params): - """Query user's margin force orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Users-Margin-Force-Orders - - :returns: API response - + cancel an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Cancel-Order """ - return await self._request_papi_api( - "get", "margin/forceOrders", signed=True, data=params - ) - - async def papi_get_um_user_trades(self, **params): - """Get trades for a specific account and UM symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Account-Trade-List - - :returns: API response + return self._ws_futures_api_request_sync("order.cancel", True, params) + def ws_futures_get_order(self, **params): """ - return await self._request_papi_api( - "get", "um/userTrades", signed=True, data=params - ) - - async def papi_get_cm_user_trades(self, **params): - """Get trades for a specific account and CM symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Account-Trade-List - - :returns: API response - + Get an order + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Query-Order """ - return await self._request_papi_api( - "get", "cm/userTrades", signed=True, data=params - ) - - async def papi_get_um_adl_quantile(self, **params): - """Query UM Position ADL Quantile Estimation. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/UM-Position-ADL-Quantile-Estimation - - :returns: API response + return self._ws_futures_api_request_sync("order.status", True, params) + def ws_futures_v2_account_position(self, **params): """ - return await self._request_papi_api( - "get", "um/adlQuantile", signed=True, data=params - ) - - async def papi_get_cm_adl_quantile(self, **params): - """Query CM Position ADL Quantile Estimation. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/CM-Position-ADL-Quantile-Estimation - - :returns: API response - + Get current position information(only symbol that has position or open orders will be returned). + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Info-V2 """ - return await self._request_papi_api( - "get", "cm/adlQuantile", signed=True, data=params - ) - - async def papi_set_um_fee_burn(self, **params): - """Change user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off ) on EVERY symbol. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Toggle-BNB-Burn-On-UM-Futures-Trade - - :returns: API response + return self._ws_futures_api_request_sync("v2/account.position", True, params) + def ws_futures_account_position(self, **params): """ - return await self._request_papi_api( - "post", "um/feeBurn", signed=True, data=params - ) - - async def papi_get_um_fee_burn(self, **params): - """Get user's BNB Fee Discount for UM Futures (Fee Discount On or Fee Discount Off). - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Get-UM-Futures-BNB-Burn-Status - - :returns: API response - + Get current position information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/trade/websocket-api/Position-Information """ - return await self._request_papi_api( - "get", "um/feeBurn", signed=True, data=params - ) - - async def papi_get_margin_order(self, **params): - """Query Margin Account Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order - - :returns: API response + return self._ws_futures_api_request_sync("account.position", True, params) + def ws_futures_v2_account_balance(self, **params): """ - return await self._request_papi_api( - "get", "margin/order", signed=True, data=params - ) - - async def papi_get_margin_open_orders(self, **params): - """Query Current Margin Open Order. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Order - - :returns: API response - + Get current account information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api#api-description """ - return await self._request_papi_api( - "get", "margin/openOrders", signed=True, data=params - ) - - async def papi_get_margin_all_orders(self, **params): - """Query All Margin Account Orders. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-All-Margin-Account-Orders - - :returns: API response + return self._ws_futures_api_request_sync("v2/account.balance", True, params) + def ws_futures_account_balance(self, **params): """ - return await self._request_papi_api( - "get", "margin/allOrders", signed=True, data=params - ) - - async def papi_get_margin_order_list(self, **params): - """Retrieves a specific OCO based on provided optional parameters. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-OCO - - :returns: API response - + Get current account information. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Futures-Account-Balance """ - return await self._request_papi_api( - "get", "margin/orderList", signed=True, data=params - ) - - async def papi_get_margin_all_order_list(self, **params): - """Query all OCO for a specific margin account based on provided optional parameters. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-all-OCO - - :returns: API response + return self._ws_futures_api_request_sync("account.balance", True, params) + def ws_futures_v2_account_status(self, **params): """ - return await self._request_papi_api( - "get", "margin/allOrderList", signed=True, data=params - ) - - async def papi_get_margin_open_order_list(self, **params): - """Query Margin Account's Open OCO. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Query-Margin-Account-Open-OCO - - :returns: API response - + Get current account information. User in single-asset/ multi-assets mode will see different value, see comments in response section for detail. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Account-Information-V2 """ - return await self._request_papi_api( - "get", "margin/openOrderList", signed=True, data=params - ) - - async def papi_get_margin_my_trades(self, **params): - """Margin Account Trade List. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List - - :returns: API response + return self._ws_futures_api_request_sync("v2/account.status", True, params) + def ws_futures_account_status(self, **params): """ - return await self._request_papi_api( - "get", "margin/myTrades", signed=True, data=params - ) - - async def papi_get_margin_repay_debt(self, **params): - """Repay debt for a margin loan. - - https://developers.binance.com/docs/derivatives/portfolio-margin/trade/Margin-Account-Trade-List - - :returns: API response - + Get current account information. User in single-asset/ multi-assets mode will see different value, see comments in response section for detail. + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Account-Information """ - return await self._request_papi_api( - "post", "margin/repay-debt", signed=True, data=params - ) + return self._ws_futures_api_request_sync("account.status", True, params) diff --git a/binance/exceptions.py b/binance/exceptions.py index 62fcefb97..44753f567 100644 --- a/binance/exceptions.py +++ b/binance/exceptions.py @@ -79,5 +79,4 @@ def __init__(self, value): super().__init__(message) -class UnknownDateFormat(Exception): - ... +class UnknownDateFormat(Exception): ... diff --git a/binance/ws/constants.py b/binance/ws/constants.py new file mode 100644 index 000000000..d526ff674 --- /dev/null +++ b/binance/ws/constants.py @@ -0,0 +1,10 @@ +from enum import Enum + +KEEPALIVE_TIMEOUT = 5 * 60 # 5 minutes + + +class WSListenerState(Enum): + INITIALISING = "Initialising" + STREAMING = "Streaming" + RECONNECTING = "Reconnecting" + EXITING = "Exiting" diff --git a/binance/depthcache.py b/binance/ws/depthcache.py similarity index 99% rename from binance/depthcache.py rename to binance/ws/depthcache.py index bb9b0b3d4..5b5484a3f 100644 --- a/binance/depthcache.py +++ b/binance/ws/depthcache.py @@ -4,7 +4,7 @@ import time from typing import Optional, Dict, Callable -from .helpers import get_loop +from ..helpers import get_loop from .streams import BinanceSocketManager from .threaded_stream import ThreadedApiManager diff --git a/binance/ws/keepalive_websocket.py b/binance/ws/keepalive_websocket.py new file mode 100644 index 000000000..a2e377e17 --- /dev/null +++ b/binance/ws/keepalive_websocket.py @@ -0,0 +1,93 @@ +import asyncio +from binance.async_client import AsyncClient +from binance.ws.reconnecting_websocket import ReconnectingWebsocket +from binance.ws.constants import KEEPALIVE_TIMEOUT + + +class KeepAliveWebsocket(ReconnectingWebsocket): + def __init__( + self, + client: AsyncClient, + url, + keepalive_type, + prefix="ws/", + is_binary=False, + exit_coro=None, + user_timeout=None, + **kwargs, + ): + super().__init__( + path=None, + url=url, + prefix=prefix, + is_binary=is_binary, + exit_coro=exit_coro, + **kwargs, + ) + self._keepalive_type = keepalive_type + self._client = client + self._user_timeout = user_timeout or KEEPALIVE_TIMEOUT + self._timer = None + + async def __aexit__(self, *args, **kwargs): + if not self._path: + return + if self._timer: + self._timer.cancel() + self._timer = None + await super().__aexit__(*args, **kwargs) + + async def _before_connect(self): + if not self._path: + self._path = await self._get_listen_key() + + async def _after_connect(self): + self._start_socket_timer() + + def _start_socket_timer(self): + self._timer = self._loop.call_later( + self._user_timeout, lambda: asyncio.create_task(self._keepalive_socket()) + ) + + async def _get_listen_key(self): + if self._keepalive_type == "user": + listen_key = await self._client.stream_get_listen_key() + elif self._keepalive_type == "margin": # cross-margin + listen_key = await self._client.margin_stream_get_listen_key() + elif self._keepalive_type == "futures": + listen_key = await self._client.futures_stream_get_listen_key() + elif self._keepalive_type == "coin_futures": + listen_key = await self._client.futures_coin_stream_get_listen_key() + else: # isolated margin + # Passing symbol for isolated margin + listen_key = await self._client.isolated_margin_stream_get_listen_key( + self._keepalive_type + ) + return listen_key + + async def _keepalive_socket(self): + try: + listen_key = await self._get_listen_key() + if listen_key != self._path: + self._log.debug("listen key changed: reconnect") + self._path = listen_key + await self._reconnect() + else: + self._log.debug("listen key same: keepalive") + if self._keepalive_type == "user": + await self._client.stream_keepalive(self._path) + elif self._keepalive_type == "margin": # cross-margin + await self._client.margin_stream_keepalive(self._path) + elif self._keepalive_type == "futures": + await self._client.futures_stream_keepalive(self._path) + elif self._keepalive_type == "coin_futures": + await self._client.futures_coin_stream_keepalive(self._path) + else: # isolated margin + # Passing symbol for isolated margin + await self._client.isolated_margin_stream_keepalive( + self._keepalive_type, self._path + ) + except Exception as e: + self._log.error(f"error in keepalive_socket: {e}") + finally: + self._start_socket_timer() diff --git a/binance/ws/reconnecting_websocket.py b/binance/ws/reconnecting_websocket.py new file mode 100644 index 000000000..f53cd717c --- /dev/null +++ b/binance/ws/reconnecting_websocket.py @@ -0,0 +1,229 @@ +import asyncio +import gzip +import json +import logging +from socket import gaierror +from typing import Optional +from asyncio import sleep +from random import random + +try: + from websockets.exceptions import ConnectionClosedError # type: ignore +except ImportError: + from websockets import ConnectionClosedError # type: ignore + +import websockets as ws + +from binance.exceptions import BinanceWebsocketUnableToConnect +from binance.helpers import get_loop +from binance.ws.constants import WSListenerState + + +class ReconnectingWebsocket: + MAX_RECONNECTS = 5 + MAX_RECONNECT_SECONDS = 60 + MIN_RECONNECT_WAIT = 0.1 + TIMEOUT = 10 + NO_MESSAGE_RECONNECT_TIMEOUT = 60 + MAX_QUEUE_SIZE = 100 + + def __init__( + self, + url: str, + path: Optional[str] = None, + prefix: str = "ws/", + is_binary: bool = False, + exit_coro=None, + **kwargs, + ): + self._loop = get_loop() + self._log = logging.getLogger(__name__) + self._path = path + self._url = url + self._exit_coro = exit_coro + self._prefix = prefix + self._reconnects = 0 + self._is_binary = is_binary + self._conn = None + self._socket = None + self.ws: Optional[ws.WebSocketClientProtocol] = None # type: ignore + self.ws_state = WSListenerState.INITIALISING + self._queue = asyncio.Queue() + self._handle_read_loop = None + self._ws_kwargs = kwargs + + async def __aenter__(self): + await self.connect() + return self + + async def close(self): + await self.__aexit__(None, None, None) + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if self._exit_coro: + await self._exit_coro(self._path) + self.ws_state = WSListenerState.EXITING + if self.ws: + await self.ws.close() + if self._conn and hasattr(self._conn, "protocol"): + await self._conn.__aexit__(exc_type, exc_val, exc_tb) + self.ws = None + if self._handle_read_loop: + self._log.error("CANCEL read_loop") + await self._kill_read_loop() + + async def connect(self): + self._log.debug("Establishing new WebSocket connection") + self.ws_state = WSListenerState.RECONNECTING + await self._before_connect() + ws_url = ( + f"{self._url}{getattr(self, '_prefix', '')}{getattr(self, '_path', '')}" + ) + self._conn = ws.connect(ws_url, close_timeout=0.1, **self._ws_kwargs) # type: ignore + try: + self.ws = await self._conn.__aenter__() + except Exception as e: # noqa + self._log.error(f"Failed to connect to websocket: {e}") + self.ws_state = WSListenerState.INITIALISING + return + self.ws_state = WSListenerState.STREAMING + self._reconnects = 0 + await self._after_connect() + # To manage the "cannot call recv while another coroutine is already waiting for the next message" + if not self._handle_read_loop: + self._handle_read_loop = self._loop.call_soon_threadsafe( + asyncio.create_task, self._read_loop() + ) + + async def _kill_read_loop(self): + self.ws_state = WSListenerState.EXITING + while self._handle_read_loop: + await sleep(0.1) + self._log.debug("Finished killing read_loop") + + async def _before_connect(self): + pass + + async def _after_connect(self): + pass + + def _handle_message(self, evt): + if self._is_binary: + try: + evt = gzip.decompress(evt) + except (ValueError, OSError): + return None + try: + return json.loads(evt) + except ValueError: + self._log.debug(f"error parsing evt json:{evt}") + return None + + async def _read_loop(self): + try: + while True: + try: + while self.ws_state == WSListenerState.RECONNECTING: + await self._run_reconnect() + + if self.ws_state == WSListenerState.EXITING: + self._log.debug( + f"_read_loop {self._path} break for {self.ws_state}" + ) + break + elif self.ws.state == ws.protocol.State.CLOSING: # type: ignore + await asyncio.sleep(0.1) + continue + elif self.ws.state == ws.protocol.State.CLOSED: # type: ignore + await self._reconnect() + elif self.ws_state == WSListenerState.STREAMING: + assert self.ws + res = await asyncio.wait_for( + self.ws.recv(), timeout=self.TIMEOUT + ) + res = self._handle_message(res) + if res: + if self._queue.qsize() < self.MAX_QUEUE_SIZE: + await self._queue.put(res) + else: + self._log.debug( + f"Queue overflow {self.MAX_QUEUE_SIZE}. Message not filled" + ) + await self._queue.put({ + "e": "error", + "m": "Queue overflow. Message not filled", + }) + raise BinanceWebsocketUnableToConnect + except asyncio.TimeoutError: + self._log.debug(f"no message in {self.TIMEOUT} seconds") + # _no_message_received_reconnect + except asyncio.CancelledError as e: + self._log.debug(f"cancelled error {e}") + break + except asyncio.IncompleteReadError as e: + self._log.debug(f"incomplete read error ({e})") + except ConnectionClosedError as e: + self._log.debug(f"connection close error ({e})") + except gaierror as e: + self._log.debug(f"DNS Error ({e})") + except BinanceWebsocketUnableToConnect as e: + self._log.debug(f"BinanceWebsocketUnableToConnect ({e})") + break + except Exception as e: + self._log.debug(f"Unknown exception ({e})") + continue + finally: + self._handle_read_loop = None # Signal the coro is stopped + self._reconnects = 0 + + async def _run_reconnect(self): + await self.before_reconnect() + if self._reconnects < self.MAX_RECONNECTS: + reconnect_wait = self._get_reconnect_wait(self._reconnects) + self._log.debug( + f"websocket reconnecting. {self.MAX_RECONNECTS - self._reconnects} reconnects left - " + f"waiting {reconnect_wait}" + ) + await asyncio.sleep(reconnect_wait) + await self.connect() + else: + self._log.error(f"Max reconnections {self.MAX_RECONNECTS} reached:") + # Signal the error + await self._queue.put({"e": "error", "m": "Max reconnect retries reached"}) + raise BinanceWebsocketUnableToConnect + + async def recv(self): + res = None + while not res: + try: + res = await asyncio.wait_for(self._queue.get(), timeout=self.TIMEOUT) + except asyncio.TimeoutError: + self._log.debug(f"no message in {self.TIMEOUT} seconds") + return res + + async def _wait_for_reconnect(self): + while ( + self.ws_state != WSListenerState.STREAMING + and self.ws_state != WSListenerState.EXITING + ): + await sleep(0.1) + + def _get_reconnect_wait(self, attempts: int) -> int: + expo = 2**attempts + return round(random() * min(self.MAX_RECONNECT_SECONDS, expo - 1) + 1) + + async def before_reconnect(self): + if self.ws: + self.ws = None + + if self._conn and hasattr(self._conn, "protocol"): + await self._conn.__aexit__(None, None, None) + + self._reconnects += 1 + + def _no_message_received_reconnect(self): + self._log.debug("No message received, reconnecting") + self.ws_state = WSListenerState.RECONNECTING + + async def _reconnect(self): + self.ws_state = WSListenerState.RECONNECTING diff --git a/binance/streams.py b/binance/ws/streams.py similarity index 81% rename from binance/streams.py rename to binance/ws/streams.py index f71d5236c..4c9e4a26e 100755 --- a/binance/streams.py +++ b/binance/ws/streams.py @@ -1,32 +1,18 @@ import asyncio -import gzip -import json -import logging import time -from asyncio import sleep from enum import Enum -from random import random -from socket import gaierror from typing import Optional, List, Dict, Callable, Any -import websockets as ws -from websockets.exceptions import ConnectionClosedError +from binance.ws.constants import KEEPALIVE_TIMEOUT +from binance.ws.keepalive_websocket import KeepAliveWebsocket +from binance.ws.reconnecting_websocket import ReconnectingWebsocket +from binance.ws.threaded_stream import ThreadedApiManager -from .client import AsyncClient -from .enums import FuturesType -from .exceptions import BinanceWebsocketUnableToConnect -from .enums import ContractType -from .helpers import get_loop -from .threaded_stream import ThreadedApiManager -KEEPALIVE_TIMEOUT = 5 * 60 # 5 minutes - - -class WSListenerState(Enum): - INITIALISING = "Initialising" - STREAMING = "Streaming" - RECONNECTING = "Reconnecting" - EXITING = "Exiting" +from binance.async_client import AsyncClient +from binance.enums import FuturesType +from binance.enums import ContractType +from binance.helpers import get_loop class BinanceSocketType(str, Enum): @@ -37,300 +23,6 @@ class BinanceSocketType(str, Enum): ACCOUNT = "Account" -class ReconnectingWebsocket: - MAX_RECONNECTS = 5 - MAX_RECONNECT_SECONDS = 60 - MIN_RECONNECT_WAIT = 0.1 - TIMEOUT = 10 - NO_MESSAGE_RECONNECT_TIMEOUT = 60 - MAX_QUEUE_SIZE = 100 - - def __init__( - self, - url: str, - path: Optional[str] = None, - prefix: str = "ws/", - is_binary: bool = False, - exit_coro=None, - **kwargs, - ): - self._loop = get_loop() - self._log = logging.getLogger(__name__) - self._path = path - self._url = url - self._exit_coro = exit_coro - self._prefix = prefix - self._reconnects = 0 - self._is_binary = is_binary - self._conn = None - self._socket = None - self.ws: Optional[ws.WebSocketClientProtocol] = None # type: ignore - self.ws_state = WSListenerState.INITIALISING - self._queue = asyncio.Queue() - self._handle_read_loop = None - self._ws_kwargs = kwargs - - async def __aenter__(self): - await self.connect() - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - if self._exit_coro: - await self._exit_coro(self._path) - self.ws_state = WSListenerState.EXITING - if self.ws: - # self.ws.fail_connection() - await self.ws.close() - if self._conn and hasattr(self._conn, "protocol"): - await self._conn.__aexit__(exc_type, exc_val, exc_tb) - self.ws = None - if not self._handle_read_loop: - self._log.error("CANCEL read_loop") - await self._kill_read_loop() - - async def connect(self): - await self._before_connect() - assert self._path - ws_url = self._url + self._prefix + self._path - self._conn = ws.connect(ws_url, close_timeout=0.1, **self._ws_kwargs) # type: ignore - try: - self.ws = await self._conn.__aenter__() - except: # noqa - await self._reconnect() - return - self.ws_state = WSListenerState.STREAMING - self._reconnects = 0 - await self._after_connect() - # To manage the "cannot call recv while another coroutine is already waiting for the next message" - if not self._handle_read_loop: - self._handle_read_loop = self._loop.call_soon_threadsafe( - asyncio.create_task, self._read_loop() - ) - - async def _kill_read_loop(self): - self.ws_state = WSListenerState.EXITING - while self._handle_read_loop: - await sleep(0.1) - - async def _before_connect(self): - pass - - async def _after_connect(self): - pass - - def _handle_message(self, evt): - if self._is_binary: - try: - evt = gzip.decompress(evt) - except (ValueError, OSError): - return None - try: - return json.loads(evt) - except ValueError: - self._log.debug(f"error parsing evt json:{evt}") - return None - - async def _read_loop(self): - try: - while True: - try: - while self.ws_state == WSListenerState.RECONNECTING: - await self._run_reconnect() - - if self.ws_state == WSListenerState.EXITING: - self._log.debug( - f"_read_loop {self._path} break for {self.ws_state}" - ) - break - elif self.ws.state == ws.protocol.State.CLOSING: # type: ignore - await asyncio.sleep(0.1) - continue - elif self.ws.state == ws.protocol.State.CLOSED: # type: ignore - await self._reconnect() - elif self.ws_state == WSListenerState.STREAMING: - assert self.ws - res = await asyncio.wait_for( - self.ws.recv(), timeout=self.TIMEOUT - ) - res = self._handle_message(res) - if res: - if self._queue.qsize() < self.MAX_QUEUE_SIZE: - await self._queue.put(res) - else: - self._log.debug( - f"Queue overflow {self.MAX_QUEUE_SIZE}. Message not filled" - ) - await self._queue.put( - { - "e": "error", - "m": "Queue overflow. Message not filled", - } - ) - raise BinanceWebsocketUnableToConnect - except asyncio.TimeoutError: - self._log.debug(f"no message in {self.TIMEOUT} seconds") - # _no_message_received_reconnect - except asyncio.CancelledError as e: - self._log.debug(f"cancelled error {e}") - break - except asyncio.IncompleteReadError as e: - self._log.debug(f"incomplete read error ({e})") - except ConnectionClosedError as e: - self._log.debug(f"connection close error ({e})") - except gaierror as e: - self._log.debug(f"DNS Error ({e})") - except BinanceWebsocketUnableToConnect as e: - self._log.debug(f"BinanceWebsocketUnableToConnect ({e})") - break - except Exception as e: - self._log.debug(f"Unknown exception ({e})") - continue - finally: - self._handle_read_loop = None # Signal the coro is stopped - self._reconnects = 0 - - async def _run_reconnect(self): - await self.before_reconnect() - if self._reconnects < self.MAX_RECONNECTS: - reconnect_wait = self._get_reconnect_wait(self._reconnects) - self._log.debug( - f"websocket reconnecting. {self.MAX_RECONNECTS - self._reconnects} reconnects left - " - f"waiting {reconnect_wait}" - ) - await asyncio.sleep(reconnect_wait) - await self.connect() - else: - self._log.error(f"Max reconnections {self.MAX_RECONNECTS} reached:") - # Signal the error - await self._queue.put({"e": "error", "m": "Max reconnect retries reached"}) - raise BinanceWebsocketUnableToConnect - - async def recv(self): - res = None - while not res: - try: - res = await asyncio.wait_for(self._queue.get(), timeout=self.TIMEOUT) - except asyncio.TimeoutError: - self._log.debug(f"no message in {self.TIMEOUT} seconds") - return res - - async def _wait_for_reconnect(self): - while ( - self.ws_state != WSListenerState.STREAMING - and self.ws_state != WSListenerState.EXITING - ): - await sleep(0.1) - - def _get_reconnect_wait(self, attempts: int) -> int: - expo = 2**attempts - return round(random() * min(self.MAX_RECONNECT_SECONDS, expo - 1) + 1) - - async def before_reconnect(self): - if self.ws: - self.ws = None - - if self._conn and hasattr(self._conn, "protocol"): - await self._conn.__aexit__(None, None, None) - - self._reconnects += 1 - - def _no_message_received_reconnect(self): - self._log.debug("No message received, reconnecting") - self.ws_state = WSListenerState.RECONNECTING - - async def _reconnect(self): - self.ws_state = WSListenerState.RECONNECTING - - -class KeepAliveWebsocket(ReconnectingWebsocket): - def __init__( - self, - client: AsyncClient, - url, - keepalive_type, - prefix="ws/", - is_binary=False, - exit_coro=None, - user_timeout=None, - **kwargs, - ): - super().__init__( - path=None, - url=url, - prefix=prefix, - is_binary=is_binary, - exit_coro=exit_coro, - **kwargs, - ) - self._keepalive_type = keepalive_type - self._client = client - self._user_timeout = user_timeout or KEEPALIVE_TIMEOUT - self._timer = None - - async def __aexit__(self, *args, **kwargs): - if not self._path: - return - if self._timer: - self._timer.cancel() - self._timer = None - await super().__aexit__(*args, **kwargs) - - async def _before_connect(self): - if not self._path: - self._path = await self._get_listen_key() - - async def _after_connect(self): - self._start_socket_timer() - - def _start_socket_timer(self): - self._timer = self._loop.call_later( - self._user_timeout, lambda: asyncio.create_task(self._keepalive_socket()) - ) - - async def _get_listen_key(self): - if self._keepalive_type == "user": - listen_key = await self._client.stream_get_listen_key() - elif self._keepalive_type == "margin": # cross-margin - listen_key = await self._client.margin_stream_get_listen_key() - elif self._keepalive_type == "futures": - listen_key = await self._client.futures_stream_get_listen_key() - elif self._keepalive_type == "coin_futures": - listen_key = await self._client.futures_coin_stream_get_listen_key() - else: # isolated margin - # Passing symbol for isolated margin - listen_key = await self._client.isolated_margin_stream_get_listen_key( - self._keepalive_type - ) - return listen_key - - async def _keepalive_socket(self): - try: - listen_key = await self._get_listen_key() - if listen_key != self._path: - self._log.debug("listen key changed: reconnect") - self._path = listen_key - await self._reconnect() - else: - self._log.debug("listen key same: keepalive") - if self._keepalive_type == "user": - await self._client.stream_keepalive(self._path) - elif self._keepalive_type == "margin": # cross-margin - await self._client.margin_stream_keepalive(self._path) - elif self._keepalive_type == "futures": - await self._client.futures_stream_keepalive(self._path) - elif self._keepalive_type == "coin_futures": - await self._client.futures_coin_stream_keepalive(self._path) - else: # isolated margin - # Passing symbol for isolated margin - await self._client.isolated_margin_stream_keepalive( - self._keepalive_type, self._path - ) - except Exception: - pass # Ignore - finally: - self._start_socket_timer() - - class BinanceSocketManager: STREAM_URL = "wss://stream.binance.{}:9443/" STREAM_TESTNET_URL = "wss://testnet.binance.vision/" @@ -1172,7 +864,7 @@ def multiplex_socket(self, streams: List[str]): Message Format - see Binance API docs for all types """ - path = f'streams={"/".join(streams)}' + path = f"streams={'/'.join(streams)}" return self._get_socket(path, prefix="stream?") def options_multiplex_socket(self, streams: List[str]): @@ -1217,7 +909,7 @@ def futures_multiplex_socket( Message Format - see Binance API docs for all types """ - path = f'streams={"/".join(streams)}' + path = f"streams={'/'.join(streams)}" return self._get_futures_socket( path, prefix="stream?", futures_type=futures_type ) diff --git a/binance/threaded_stream.py b/binance/ws/threaded_stream.py similarity index 94% rename from binance/threaded_stream.py rename to binance/ws/threaded_stream.py index e53d7dcc4..e2fc575f9 100755 --- a/binance/threaded_stream.py +++ b/binance/ws/threaded_stream.py @@ -2,8 +2,8 @@ import threading from typing import Optional, Dict, Any -from .client import AsyncClient -from .helpers import get_loop +from binance.async_client import AsyncClient +from binance.helpers import get_loop class ThreadedApiManager(threading.Thread): @@ -32,8 +32,7 @@ def __init__( "session_params": session_params, } - async def _before_socket_listener_start(self): - ... + async def _before_socket_listener_start(self): ... async def socket_listener(self): self._client = await AsyncClient.create(loop=self._loop, **self._client_params) diff --git a/binance/ws/websocket_api.py b/binance/ws/websocket_api.py new file mode 100644 index 000000000..e155e914b --- /dev/null +++ b/binance/ws/websocket_api.py @@ -0,0 +1,132 @@ +from typing import Dict +import asyncio +import json + +from websockets import WebSocketClientProtocol # type: ignore + +from .constants import WSListenerState +from .reconnecting_websocket import ReconnectingWebsocket +from binance.exceptions import BinanceAPIException, BinanceWebsocketUnableToConnect + + +class WebsocketAPI(ReconnectingWebsocket): + def __init__(self, url: str, tld: str = "com", testnet: bool = False): + self._tld = tld + self._testnet = testnet + self._responses: Dict[str, asyncio.Future] = {} + self._connection_lock = ( + asyncio.Lock() + ) # used to ensure only one connection is established at a time + super().__init__(url=url, prefix="", path="", is_binary=False) + + def _handle_message(self, msg): + """Override message handling to support request-response""" + parsed_msg = super()._handle_message(msg) + if parsed_msg is None: + return None + req_id, exception, throwError = None, None, False + if "id" in parsed_msg: + req_id = parsed_msg["id"] + if "status" in parsed_msg: + if parsed_msg["status"] != 200: + throwError = True + exception = BinanceAPIException( + parsed_msg, parsed_msg["status"], json.dumps(parsed_msg["error"]) + ) + if req_id is not None and req_id in self._responses: + if throwError and exception is not None: + self._responses[req_id].set_exception(exception) + else: + self._responses[req_id].set_result(parsed_msg) + elif throwError and exception is not None: + raise exception + return parsed_msg + + async def _ensure_ws_connection(self) -> None: + """Ensure WebSocket connection is established and ready + + This function will: + 1. Check if connection exists and is streaming + 2. Attempt to connect if not + 3. Wait for connection to be ready + 4. Handle reconnection if needed + """ + async with self._connection_lock: + try: + if ( + self.ws is None + or (isinstance(self.ws, WebSocketClientProtocol) and self.ws.closed) + or self.ws_state != WSListenerState.STREAMING + ): + await self.connect() + + # Wait for connection to be ready + retries = 0 + while ( + self.ws_state != WSListenerState.STREAMING + and retries < self.MAX_RECONNECTS + ): + if self.ws_state == WSListenerState.RECONNECTING: + self._log.info("Connection is reconnecting, waiting...") + await self._wait_for_reconnect() + + elif self.ws is None or self.ws.closed: + self._log.info("Connection lost, reconnecting...") + await self.connect() + + retries += 1 + await asyncio.sleep(self.MIN_RECONNECT_WAIT) + + if self.ws_state != WSListenerState.STREAMING: + raise BinanceWebsocketUnableToConnect( + f"Failed to establish connection after {retries} attempts" + ) + + self._log.debug("WebSocket connection established") + + except Exception as e: + self._log.error(f"Error ensuring WebSocket connection: {e}") + raise BinanceWebsocketUnableToConnect(f"Connection failed: {str(e)}") + + async def request(self, id: str, payload: dict) -> dict: + """Send request and wait for response""" + await self._ensure_ws_connection() + + # Create future for response + future = asyncio.Future() + self._responses[id] = future + + try: + # Send request + if self.ws is None: + raise BinanceWebsocketUnableToConnect( + "Trying to send request while WebSocket is not connected" + ) + await self.ws.send(json.dumps(payload)) + + # Wait for response + response = await asyncio.wait_for(future, timeout=self.TIMEOUT) + + # Check for errors + if "error" in response: + raise BinanceWebsocketUnableToConnect(response["error"]) + + return response.get("result", response) + + except asyncio.TimeoutError: + raise BinanceWebsocketUnableToConnect("Request timed out") + except Exception as e: + raise e + finally: + self._responses.pop(id, None) + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Clean up responses before closing""" + response_ids = list(self._responses.keys()) # Create a copy of keys + for req_id in response_ids: + future = self._responses.pop(req_id) # Remove and get the future + if not future.done(): + future.set_exception( + BinanceWebsocketUnableToConnect("WebSocket closing") + ) + await super().__aexit__(exc_type, exc_val, exc_tb) diff --git a/docs/conf.py b/docs/conf.py index b6aaed97f..9cb105910 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,8 @@ # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -32,37 +33,37 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.imgmath', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', + "sphinx.ext.autodoc", + "sphinx.ext.imgmath", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'python-binance' -copyright = '2017, Sam McHardy' -author = 'Sam McHardy' +project = "python-binance" +copyright = "2017, Sam McHardy" +author = "Sam McHardy" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.2.0' +version = "0.2.0" # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = "0.2.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -74,10 +75,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -89,7 +90,7 @@ # a list of builtin themes. # # html_theme = 'alabaster' -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -100,7 +101,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -108,12 +109,12 @@ # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", ] } @@ -121,7 +122,7 @@ # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'python-binancedoc' +htmlhelp_basename = "python-binancedoc" # -- Options for LaTeX output --------------------------------------------- @@ -130,15 +131,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -148,8 +146,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'python-binance.tex', 'python-binance Documentation', - 'Sam McHardy', 'manual'), + ( + master_doc, + "python-binance.tex", + "python-binance Documentation", + "Sam McHardy", + "manual", + ), ] @@ -158,8 +161,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'python-binance', 'python-binance Documentation', - [author], 1) + (master_doc, "python-binance", "python-binance Documentation", [author], 1) ] @@ -169,9 +171,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'python-binance', 'python-binance Documentation', - author, 'python-binance', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "python-binance", + "python-binance Documentation", + author, + "python-binance", + "One line description of project.", + "Miscellaneous", + ), ] diff --git a/docs/websockets.rst b/docs/websockets.rst index 19f9676bf..28e17cd41 100755 --- a/docs/websockets.rst +++ b/docs/websockets.rst @@ -1,7 +1,28 @@ Websockets ========== -There are 2 ways to interact with websockets. +API Requests via Websockets +-------------------------- + +Some API endpoints can be accessed via websockets. For supported endpoints, you can make requests using either the synchronous or asynchronous client: + +* Synchronous client: ``client.ws_`` +* Asynchronous client: ``async_client.ws_`` + +Example usage: + +.. code:: python + + # Synchronous + client.ws_get_order_book(symbol="BTCUSDT") + + # Asynchronous + await async_client.ws_get_order_book(symbol="BTCUSDT") + +Websocket Managers for Streaming Data +----------------- + +There are 2 ways to interact with websockets for streaming data: with `ThreadedWebsocketManager `_ or `BinanceSocketManager `_. diff --git a/examples/create_order_async.py b/examples/create_order_async.py index 1c3d1d247..3aff1b6d4 100644 --- a/examples/create_order_async.py +++ b/examples/create_order_async.py @@ -5,7 +5,7 @@ root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(root) -from binance.client import AsyncClient +from binance import AsyncClient # create futures order diff --git a/examples/save_historical_data.py b/examples/save_historical_data.py index c5aebecd8..1865d0981 100644 --- a/examples/save_historical_data.py +++ b/examples/save_historical_data.py @@ -41,12 +41,7 @@ def interval_to_milliseconds(interval): int value of interval in milliseconds """ ms = None - seconds_per_unit = { - "m": 60, - "h": 60 * 60, - "d": 24 * 60 * 60, - "w": 7 * 24 * 60 * 60 - } + seconds_per_unit = {"m": 60, "h": 60 * 60, "d": 24 * 60 * 60, "w": 7 * 24 * 60 * 60} unit = interval[-1] if unit in seconds_per_unit: @@ -106,7 +101,7 @@ def get_historical_klines(symbol, interval, start_str, end_str=None): interval=interval, limit=limit, startTime=start_ts, - endTime=end_ts + endTime=end_ts, ) # handle the case where our start date is before the symbol pair listed on Binance @@ -146,11 +141,8 @@ def get_historical_klines(symbol, interval, start_str, end_str=None): # open a file with filename including symbol, interval and start and end converted to milliseconds with open( "Binance_{}_{}_{}-{}.json".format( - symbol, - interval, - date_to_milliseconds(start), - date_to_milliseconds(end) + symbol, interval, date_to_milliseconds(start), date_to_milliseconds(end) ), - 'w' # set file write mode + "w", # set file write mode ) as f: f.write(json.dumps(klines)) diff --git a/examples/websocket.py b/examples/websocket.py index 01422eb5d..8b58db5e4 100644 --- a/examples/websocket.py +++ b/examples/websocket.py @@ -6,19 +6,19 @@ from binance import ThreadedWebsocketManager -api_key = '' -api_secret = '' +api_key = "" +api_secret = "" -def main(): - symbol = 'BNBBTC' +def main(): + symbol = "BNBBTC" twm = ThreadedWebsocketManager(api_key=api_key, api_secret=api_secret) # start is required to initialise its internal loop twm.start() def handle_socket_message(msg): - if msg.get('e') == 'error': + if msg.get("e") == "error": print(f"WebSocket error: {msg.get('m', 'Unknown error')}") return @@ -31,14 +31,14 @@ def handle_socket_message(msg): # Start kline socket kline_socket = twm.start_kline_socket(callback=handle_socket_message, symbol=symbol) - sockets.append(('kline', kline_socket, symbol)) + sockets.append(("kline", kline_socket, symbol)) # Start depth socket depth_socket = twm.start_depth_socket(callback=handle_socket_message, symbol=symbol) - sockets.append(('depth', depth_socket, symbol)) + sockets.append(("depth", depth_socket, symbol)) # Start multiplex socket - streams = ['bnbbtc@miniTicker', 'bnbbtc@bookTicker'] + streams = ["bnbbtc@miniTicker", "bnbbtc@bookTicker"] twm.start_multiplex_socket(callback=handle_socket_message, streams=streams) twm.join() diff --git a/examples/ws_create_order.py b/examples/ws_create_order.py new file mode 100644 index 000000000..b0325992e --- /dev/null +++ b/examples/ws_create_order.py @@ -0,0 +1,27 @@ +import os +import sys + + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(root) + +from binance.client import Client + +## create order using websockets sync +## the API is very similar to the REST API + + +def main(): + api_key = "" # your api_key here + secret = "" # your secret here + client = Client(api_key, secret, testnet=True) + order = client.ws_create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + ) + print(order["orderId"]) + + +main() diff --git a/examples/ws_create_order_async.py b/examples/ws_create_order_async.py new file mode 100644 index 000000000..8337f03fc --- /dev/null +++ b/examples/ws_create_order_async.py @@ -0,0 +1,29 @@ +import os +import sys +import asyncio + + +root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(root) + +from binance import AsyncClient + +## create order using websockets async +## the API is very similar to the REST API + + +async def main(): + api_key = "" # your api_key here + secret = "" # your secret here + client = AsyncClient(api_key, secret, testnet=True) + order = await client.ws_create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + ) + print(order["orderId"]) + await client.close_connection() + + +asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml index d9df823af..c9f1e92f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,7 @@ [tool.ruff] preview = true -ignore = ["F722","F841","F821","E402","E501","E902","E713","E741","E714", "E275","E721","E266", "E261"] \ No newline at end of file +lint.ignore = ["F722","F841","F821","E402","E501","E902","E713","E741","E714", "E275","E721","E266", "E261"] + +[tool.pytest.ini_options] +timeout = 10 +timeout_method = "thread" diff --git a/setup.py b/setup.py index f5facdeeb..fd1ded1f2 100644 --- a/setup.py +++ b/setup.py @@ -6,48 +6,53 @@ with codecs.open( - os.path.join( - os.path.abspath(os.path.dirname(__file__)), - 'binance', - '__init__.py' - ), 'r', 'latin1') as fp: + os.path.join(os.path.abspath(os.path.dirname(__file__)), "binance", "__init__.py"), + "r", + "latin1", +) as fp: try: version = re.findall(r'^__version__ = "([^"]+)"\r?$', fp.read(), re.M)[0] except IndexError: - raise RuntimeError('Unable to determine version.') + raise RuntimeError("Unable to determine version.") with open("README.rst", "r") as fh: long_description = fh.read() setup( - name='python-binance', + name="python-binance", version=version, - packages=['binance'], - description='Binance REST API python implementation', + packages=["binance"], + description="Binance REST API python implementation", long_description=long_description, long_description_content_type="text/x-rst", - url='https://github.com/sammchardy/python-binance', - author='Sam McHardy', - license='MIT', - author_email='', + url="https://github.com/sammchardy/python-binance", + author="Sam McHardy", + license="MIT", + author_email="", install_requires=[ - 'requests', 'six', 'dateparser', 'aiohttp', 'ujson', 'websockets', 'pycryptodome' + "requests", + "six", + "dateparser", + "aiohttp", + "ujson", + "websockets", + "pycryptodome", ], - keywords='binance exchange rest api bitcoin ethereum btc eth neo', + keywords="binance exchange rest api bitcoin ethereum btc eth neo", classifiers=[ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", ], ) diff --git a/test-requirements.txt b/test-requirements.txt index 9078d8f9b..24c7d0a70 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,7 @@ coverage -flake8 pytest pytest-asyncio pytest-cov -pytest-flake8 requests-mock tox setuptools diff --git a/tests/conftest.py b/tests/conftest.py index e80b6375b..b4e57a7f7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,9 @@ import pytest -from binance.client import Client, AsyncClient +from binance.client import Client +from binance.async_client import AsyncClient import os import asyncio +import logging proxies = {} proxy = os.getenv("PROXY") @@ -11,19 +13,61 @@ else: print("No proxy set") +api_key = os.getenv("TEST_API_KEY") +api_secret = os.getenv("TEST_API_SECRET") +futures_api_key = os.getenv("TEST_FUTURES_API_KEY") +futures_api_secret = os.getenv("TEST_FUTURES_API_SECRET") +testnet = os.getenv("TEST_TESTNET", "true").lower() == "true" -@pytest.fixture(scope="module") + +# Configure logging for all tests +@pytest.fixture(autouse=True) +def setup_logging(): + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + force=True, # This ensures the config is applied even if logging was initialized elsewhere + ) + console_handler = logging.StreamHandler() + console_handler.setFormatter( + logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + ) + logging.getLogger().addHandler(console_handler) + + +@pytest.fixture(scope="function") def client(): - return Client("test_api_key", "test_api_secret", {"proxies": proxies}) + return Client(api_key, api_secret, {"proxies": proxies}, testnet=testnet) + +@pytest.fixture(scope="function") +def futuresClient(): + return Client( + futures_api_key, futures_api_secret, {"proxies": proxies}, testnet=testnet + ) + + +@pytest.fixture(scope="function") +def clientAsync(): + return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=testnet) -@pytest.fixture(scope="module") -async def clientAsync(): - # for now this is not working inside the tests - res = await AsyncClient().create( - api_key="api_key", - api_secret="api_secret", - https_proxy=proxy, - loop=asyncio.new_event_loop(), + +@pytest.fixture(scope="function") +def futuresClientAsync(): + return AsyncClient( + futures_api_key, futures_api_secret, https_proxy=proxy, testnet=testnet ) - return res + + +@pytest.fixture(autouse=True, scope="function") +def event_loop(): + """Create new event loop for each test""" + loop = asyncio.new_event_loop() + yield loop + # Clean up pending tasks + pending = asyncio.all_tasks(loop) + for task in pending: + task.cancel() + loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + loop.close() diff --git a/tests/test_async_client_ws_api.py b/tests/test_async_client_ws_api.py new file mode 100644 index 000000000..c72a8e7da --- /dev/null +++ b/tests/test_async_client_ws_api.py @@ -0,0 +1,76 @@ +import pytest + + +@pytest.mark.asyncio() +async def test_ws_get_order_book(clientAsync): + await clientAsync.ws_get_order_book(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_recent_trades(clientAsync): + await clientAsync.ws_get_recent_trades(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_historical_trades(clientAsync): + await clientAsync.ws_get_historical_trades(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_aggregate_trades(clientAsync): + await clientAsync.ws_get_aggregate_trades(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_klines(clientAsync): + await clientAsync.ws_get_klines(symbol="BTCUSDT", interval="1m") + + +@pytest.mark.asyncio() +async def test_ws_get_uiKlines(clientAsync): + await clientAsync.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") + + +@pytest.mark.asyncio() +async def test_ws_get_avg_price(clientAsync): + await clientAsync.ws_get_avg_price(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_ticker(clientAsync): + await clientAsync.ws_get_ticker(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_trading_day_ticker(clientAsync): + await clientAsync.ws_get_trading_day_ticker(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_symbol_ticker_window(clientAsync): + await clientAsync.ws_get_symbol_ticker_window(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_symbol_ticker(clientAsync): + await clientAsync.ws_get_symbol_ticker(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_get_orderbook_ticker(clientAsync): + await clientAsync.ws_get_orderbook_ticker(symbol="BTCUSDT") + + +@pytest.mark.asyncio() +async def test_ws_ping(clientAsync): + await clientAsync.ws_ping() + + +@pytest.mark.asyncio() +async def test_ws_get_time(clientAsync): + await clientAsync.ws_get_time() + + +@pytest.mark.asyncio() +async def test_ws_get_exchange_info(clientAsync): + await clientAsync.ws_get_exchange_info(symbol="BTCUSDT") diff --git a/tests/test_async_client_ws_futures_requests.py b/tests/test_async_client_ws_futures_requests.py new file mode 100644 index 000000000..1df0d4beb --- /dev/null +++ b/tests/test_async_client_ws_futures_requests.py @@ -0,0 +1,122 @@ +import asyncio +import pytest + +from binance.exceptions import BinanceAPIException, BinanceWebsocketUnableToConnect +from .test_get_order_book import assert_ob +from .test_order import assert_contract_order + +try: + from unittest.mock import AsyncMock, patch # Python 3.8+ +except ImportError: + from asynctest import CoroutineMock as AsyncMock, patch # Python 3.7 + + +@pytest.mark.asyncio() +async def test_ws_futures_get_order_book(futuresClientAsync): + orderbook = await futuresClientAsync.ws_futures_get_order_book(symbol="BTCUSDT") + assert_ob(orderbook) + + +@pytest.mark.asyncio +async def test_concurrent_ws_futures_get_order_book(futuresClientAsync): + symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT"] + + async def get_orderbook(symbol): + orderbook = await futuresClientAsync.ws_futures_get_order_book(symbol=symbol) + assert_ob(orderbook) + return orderbook + + tasks = [get_orderbook(symbol) for symbol in symbols] + results = await asyncio.gather(*tasks) + + # Verify results + assert len(results) == len(symbols) + for orderbook in results: + assert_ob(orderbook) + + +@pytest.mark.asyncio() +async def test_bad_request(futuresClientAsync): + with pytest.raises(BinanceAPIException): + await futuresClientAsync.ws_futures_get_order_book() + + +@pytest.mark.asyncio() +async def test_ws_futures_get_all_tickers(futuresClientAsync): + await futuresClientAsync.ws_futures_get_all_tickers() + + +@pytest.mark.asyncio() +async def test_ws_futures_get_order_book_ticker(futuresClientAsync): + await futuresClientAsync.ws_futures_get_order_book_ticker() + + +@pytest.mark.asyncio() +async def test_ws_futures_create_get_edit_cancel_order(futuresClientAsync): + ticker = await futuresClientAsync.ws_futures_get_order_book_ticker(symbol="LTCUSDT") + positions = await futuresClientAsync.ws_futures_v2_account_position( + symbol="LTCUSDT" + ) + order = await futuresClientAsync.ws_futures_create_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(float(ticker["bidPrice"]) - 2), + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.ws_futures_edit_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.ws_futures_get_order( + symbol="LTCUSDT", orderid=order["orderId"] + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.ws_futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +@pytest.mark.asyncio() +async def test_ws_futures_v2_account_position(futuresClientAsync): + await futuresClientAsync.ws_futures_v2_account_position() + + +@pytest.mark.asyncio() +async def test_ws_futures_account_position(futuresClientAsync): + await futuresClientAsync.ws_futures_account_position() + + +@pytest.mark.asyncio() +async def test_ws_futures_v2_account_balance(futuresClientAsync): + await futuresClientAsync.ws_futures_v2_account_balance() + + +@pytest.mark.asyncio() +async def test_ws_futures_account_balance(futuresClientAsync): + await futuresClientAsync.ws_futures_account_balance() + + +@pytest.mark.asyncio() +async def test_ws_futures_v2_account_status(futuresClientAsync): + await futuresClientAsync.ws_futures_v2_account_status() + + +@pytest.mark.asyncio() +async def test_ws_futures_account_status(futuresClientAsync): + await futuresClientAsync.ws_futures_account_status() + + +@pytest.mark.asyncio +async def test_ws_futures_fail_to_connect(futuresClientAsync): + # Simulate WebSocket connection being closed during the request + with patch("websockets.connect", new_callable=AsyncMock): + with pytest.raises(BinanceWebsocketUnableToConnect): + await futuresClientAsync.ws_futures_get_order_book(symbol="BTCUSDT") diff --git a/tests/test_client.py b/tests/test_client.py index 0c46e7f6c..e329e9d63 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,166 @@ def test_client_initialization(client): - assert client.API_KEY == "test_api_key" - assert client.API_SECRET == "test_api_secret" - assert client.testnet is False + assert client.API_KEY is not None + assert client.API_SECRET is not None + + +# TODO: whitelist in proxy to pass test +# def test_get_products(client): +# client.get_products() + + +def test_get_exchange_info(client): + client.get_exchange_info() + + +def test_get_symbol_info(client): + client.get_symbol_info("BTCUSDT") + + +def test_ping(client): + client.ping() + + +def test_get_server_time(client): + client.get_server_time() + + +def test_get_all_tickers(client): + client.get_all_tickers() + + +def test_get_orderbook_tickers(client): + client.get_orderbook_tickers() + + +def test_get_order_book(client): + client.get_order_book(symbol="BTCUSDT") + + +def test_get_recent_trades(client): + client.get_recent_trades(symbol="BTCUSDT") + + +def test_get_historical_trades(client): + client.get_historical_trades(symbol="BTCUSDT") + + +def test_get_aggregate_trades(client): + client.get_aggregate_trades(symbol="BTCUSDT") + + +def test_get_klines(client): + client.get_klines(symbol="BTCUSDT", interval="1d") + + +def test_get_avg_price(client): + client.get_avg_price(symbol="BTCUSDT") + + +def test_get_ticker(client): + client.get_ticker(symbol="BTCUSDT") + + +def test_get_symbol_ticker(client): + client.get_symbol_ticker(symbol="BTCUSDT") + + +def test_get_orderbook_ticker(client): + client.get_orderbook_ticker(symbol="BTCUSDT") + + +def test_get_account(client): + client.get_account() + + +def test_get_asset_balance(client): + client.get_asset_balance(asset="BTC") + + +def test_get_my_trades(client): + client.get_my_trades(symbol="BTCUSDT") + + +def test_get_system_status(client): + client.get_system_status() + + +# TODO: Tests not working on testnet +# def test_get_account_status(client): +# client.get_account_status() + + +# def test_get_account_api_trading_status(client): +# client.get_account_api_trading_status() + + +# def test_get_account_api_permissions(client): +# client.get_account_api_permissions() + + +# def test_get_dust_assets(client): +# client.get_dust_assets() + + +######################### +# Websocket API Requests # +######################### + + +def test_ws_get_order_book(client): + client.ws_get_order_book(symbol="BTCUSDT") + + +def test_ws_get_recent_trades(client): + client.ws_get_recent_trades(symbol="BTCUSDT") + + +def test_ws_get_historical_trades(client): + client.ws_get_historical_trades(symbol="BTCUSDT") + + +def test_ws_get_aggregate_trades(client): + client.ws_get_aggregate_trades(symbol="BTCUSDT") + + +def test_ws_get_klines(client): + client.ws_get_klines(symbol="BTCUSDT", interval="1m") + + +def test_ws_get_uiKlines(client): + client.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") + + +def test_ws_get_avg_price(client): + client.ws_get_avg_price(symbol="BTCUSDT") + + +def test_ws_get_ticker(client): + ticker = client.ws_get_ticker(symbol="BTCUSDT") + + +def test_ws_get_trading_day_ticker(client): + client.ws_get_trading_day_ticker(symbol="BTCUSDT") + + +def test_ws_get_symbol_ticker_window(client): + client.ws_get_symbol_ticker_window(symbol="BTCUSDT") + + +def test_ws_get_symbol_ticker(client): + client.ws_get_symbol_ticker(symbol="BTCUSDT") + + +def test_ws_get_orderbook_ticker(client): + client.ws_get_orderbook_ticker(symbol="BTCUSDT") + + +def test_ws_ping(client): + client.ws_ping() + + +def test_ws_get_time(client): + client.ws_get_time() + + +def test_ws_get_exchange_info(client): + client.ws_get_exchange_info(symbol="BTCUSDT") diff --git a/tests/test_client_ws_api.py b/tests/test_client_ws_api.py new file mode 100644 index 000000000..c23e6d6cb --- /dev/null +++ b/tests/test_client_ws_api.py @@ -0,0 +1,62 @@ +from .test_get_order_book import assert_ob + + +def test_ws_get_order_book(client): + orderbook = client.ws_get_order_book(symbol="BTCUSDT") + assert_ob(orderbook) + + +def test_ws_get_recent_trades(client): + client.ws_get_recent_trades(symbol="BTCUSDT") + + +def test_ws_get_historical_trades(client): + client.ws_get_historical_trades(symbol="BTCUSDT") + + +def test_ws_get_aggregate_trades(client): + client.ws_get_aggregate_trades(symbol="BTCUSDT") + + +def test_ws_get_klines(client): + client.ws_get_klines(symbol="BTCUSDT", interval="1m") + + +def test_ws_get_uiKlines(client): + client.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") + + +def test_ws_get_avg_price(client): + client.ws_get_avg_price(symbol="BTCUSDT") + + +def test_ws_get_ticker(client): + client.ws_get_ticker(symbol="BTCUSDT") + + +def test_ws_get_trading_day_ticker(client): + client.ws_get_trading_day_ticker(symbol="BTCUSDT") + + +def test_ws_get_symbol_ticker_window(client): + client.ws_get_symbol_ticker_window(symbol="BTCUSDT") + + +def test_ws_get_symbol_ticker(client): + client.ws_get_symbol_ticker(symbol="BTCUSDT") + + +def test_ws_get_orderbook_ticker(client): + client.ws_get_orderbook_ticker(symbol="BTCUSDT") + + +def test_ws_ping(client): + client.ws_ping() + + +def test_ws_get_time(client): + client.ws_get_time() + + +def test_ws_get_exchange_info(client): + client.ws_get_exchange_info(symbol="BTCUSDT") diff --git a/tests/test_client_ws_futures_requests.py b/tests/test_client_ws_futures_requests.py new file mode 100644 index 000000000..488b55ddf --- /dev/null +++ b/tests/test_client_ws_futures_requests.py @@ -0,0 +1,76 @@ +import pytest +from binance.exceptions import BinanceAPIException +from .test_get_order_book import assert_ob +from .test_order import assert_contract_order + + +def test_ws_futures_get_order_book(futuresClient): + orderbook = futuresClient.ws_futures_get_order_book(symbol="BTCUSDT") + assert_ob(orderbook) + + +def test_bad_request(futuresClient): + with pytest.raises(BinanceAPIException): + futuresClient.ws_futures_get_order_book() + + +def test_ws_futures_get_all_tickers(futuresClient): + futuresClient.ws_futures_get_all_tickers() + + +def test_ws_futures_get_order_book_ticker(futuresClient): + futuresClient.ws_futures_get_order_book_ticker() + + +def test_ws_futures_create_get_edit_cancel_order(futuresClient): + ticker = futuresClient.ws_futures_get_order_book_ticker(symbol="LTCUSDT") + positions = futuresClient.ws_futures_v2_account_position(symbol="LTCUSDT") + order = futuresClient.ws_futures_create_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(round(float(ticker["bidPrice"]) - 2)), + ) + assert_contract_order(futuresClient, order) + order = futuresClient.ws_futures_edit_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClient, order) + order = futuresClient.ws_futures_get_order( + symbol="LTCUSDT", orderid=order["orderId"] + ) + assert_contract_order(futuresClient, order) + order = futuresClient.ws_futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +def test_ws_futures_v2_account_position(futuresClient): + futuresClient.ws_futures_v2_account_position() + + +def test_ws_futures_account_position(futuresClient): + futuresClient.ws_futures_account_position() + + +def test_ws_futures_v2_account_balance(futuresClient): + futuresClient.ws_futures_v2_account_balance() + + +def test_ws_futures_account_balance(futuresClient): + futuresClient.ws_futures_account_balance() + + +def test_ws_futures_v2_account_status(futuresClient): + futuresClient.ws_futures_v2_account_status() + + +def test_ws_futures_account_status(futuresClient): + futuresClient.ws_futures_account_status() diff --git a/tests/test_cryptography.py b/tests/test_cryptography.py index 04240262d..8048fda36 100644 --- a/tests/test_cryptography.py +++ b/tests/test_cryptography.py @@ -47,6 +47,6 @@ def test_encryption(): ping=False, ) signature = client._generate_signature(data) - assert ( - signature == case["expected_signature"] - ), f"Test failed: {case['description']}" + assert signature == case["expected_signature"], ( + f"Test failed: {case['description']}" + ) diff --git a/tests/test_depth_cache.py b/tests/test_depth_cache.py index df4cb2b3b..f1f469ba7 100644 --- a/tests/test_depth_cache.py +++ b/tests/test_depth_cache.py @@ -1,4 +1,4 @@ -from binance.depthcache import DepthCache +from binance.ws.depthcache import DepthCache from decimal import Decimal import pytest diff --git a/tests/test_get_order_book.py b/tests/test_get_order_book.py index 95f6b526c..71b6068cd 100644 --- a/tests/test_get_order_book.py +++ b/tests/test_get_order_book.py @@ -1,10 +1,5 @@ import pytest from binance.exceptions import BinanceAPIException -from binance import AsyncClient -import os - -proxies = {} -proxy = os.getenv("PROXY") def assert_ob(order_book): @@ -56,27 +51,23 @@ def test_get_order_book_with_limit(client): except BinanceAPIException as e: pytest.fail(f"API request failed: {str(e)}") -@pytest.mark.asyncio() -async def test_get_order_book_async(): - try: - client = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) - order_book = await client.get_order_book(symbol="BTCUSDT") - assert_ob(order_book) - except BinanceAPIException as e: - pytest.fail(f"API request failed: {str(e)}") +@pytest.mark.asyncio(scope="function") +async def test_get_order_book_async(clientAsync): + order_book = await clientAsync.get_order_book(symbol="BTCUSDT") + assert_ob(order_book) -@pytest.mark.asyncio() -async def test_futures_get_order_book_async(): +@pytest.mark.asyncio(scope="function") +async def test_futures_get_order_book_async(clientAsync): try: - client = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) - order_book = await client.futures_order_book(symbol="BTCUSDT") - + order_book = await clientAsync.futures_order_book(symbol="BTCUSDT") assert_ob(order_book) except BinanceAPIException as e: pytest.fail(f"API request failed: {str(e)}") + + +@pytest.mark.asyncio() +async def test_ws_get_order_book(clientAsync): + order_book = await clientAsync.ws_get_order_book(symbol="BTCUSDT") + assert_ob(order_book) diff --git a/tests/test_ids.py b/tests/test_ids.py index 6a32ff422..857615833 100644 --- a/tests/test_ids.py +++ b/tests/test_ids.py @@ -2,7 +2,7 @@ import pytest from aioresponses import aioresponses -from binance.client import Client, AsyncClient +from binance import Client, AsyncClient client = Client(api_key="api_key", api_secret="api_secret", ping=False) @@ -41,13 +41,22 @@ def test_spot_market_id(): def test_spot_cancel_replace_id(): with requests_mock.mock() as m: - m.post("https://api.binance.com/api/v3/order/cancelReplace", json={}, status_code=200) + m.post( + "https://api.binance.com/api/v3/order/cancelReplace", + json={}, + status_code=200, + ) client.cancel_replace_order( - cancelOrderId="orderId", symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1 + cancelOrderId="orderId", + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, ) url_dict = dict(pair.split("=") for pair in m.last_request.text.split("&")) assert url_dict["newClientOrderId"].startswith("x-HNA2TXFJ") + def test_spot_oco_order_id(): with requests_mock.mock() as m: m.post("https://api.binance.com/api/v3/orderList/oco", json={}, status_code=200) @@ -57,6 +66,7 @@ def test_spot_oco_order_id(): url_dict = dict(pair.split("=") for pair in m.last_request.text.split("&")) assert url_dict["listClientOrderId"].startswith("x-HNA2TXFJ") + def test_swap_id(): with requests_mock.mock() as m: m.post("https://fapi.binance.com/fapi/v1/order", json={}, status_code=200) @@ -187,6 +197,7 @@ def handler(url, **kwargs): ) await clientAsync.close_connection() + @pytest.mark.asyncio() async def test_swap_id_async(): clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") @@ -291,6 +302,7 @@ def handler(url, **kwargs): ) await clientAsync.close_connection() + @pytest.mark.asyncio() async def test_swap_batch_id_async(): with aioresponses() as m: diff --git a/tests/test_order.py b/tests/test_order.py new file mode 100644 index 000000000..09f9d1c51 --- /dev/null +++ b/tests/test_order.py @@ -0,0 +1,5 @@ +def assert_contract_order(client, order): + assert isinstance(order, dict) + + assert order["clientOrderId"].startswith(client.CONTRACT_ORDER_PREFIX) + assert order["symbol"] diff --git a/tests/test_ping.py b/tests/test_ping.py index 495e60042..e2b599dd0 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -1,4 +1,3 @@ -from binance.client import AsyncClient import os import pytest @@ -28,36 +27,24 @@ def test_coin_ping(client): @pytest.mark.asyncio() -async def test_papi_ping_async(): - clientAsync = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) +async def test_papi_ping_async(clientAsync): ping_response = await clientAsync.papi_ping() assert ping_response is not None @pytest.mark.asyncio() -async def test_ping_async(): - clientAsync = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) +async def test_ping_async(clientAsync): ping_response = await clientAsync.ping() assert ping_response is not None @pytest.mark.asyncio() -async def test_futures_ping_async(): - clientAsync = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) +async def test_futures_ping_async(clientAsync): ping_response = await clientAsync.futures_ping() assert ping_response is not None @pytest.mark.asyncio() -async def test_coin_ping_async(): - clientAsync = AsyncClient( - api_key="api_key", api_secret="api_secret", https_proxy=proxy - ) +async def test_coin_ping_async(clientAsync): ping_response = await clientAsync.futures_coin_ping() assert ping_response is not None diff --git a/tests/test_streams.py b/tests/test_streams.py index b307305e1..e1aa450e7 100644 --- a/tests/test_streams.py +++ b/tests/test_streams.py @@ -1,5 +1,5 @@ -from binance.streams import BinanceSocketManager -from binance.client import AsyncClient +from binance import BinanceSocketManager +from binance import AsyncClient import pytest diff --git a/tests/test_ws_api.py b/tests/test_ws_api.py new file mode 100644 index 000000000..4f09d1c99 --- /dev/null +++ b/tests/test_ws_api.py @@ -0,0 +1,153 @@ +import json +import re +import pytest +import asyncio +from binance import AsyncClient + +from binance.exceptions import BinanceAPIException, BinanceWebsocketUnableToConnect +from binance.ws.constants import WSListenerState +from .test_get_order_book import assert_ob + + +@pytest.mark.asyncio +async def test_ws_api_public_endpoint(clientAsync): + """Test normal order book request""" + order_book = await clientAsync.ws_get_order_book(symbol="BTCUSDT") + assert_ob(order_book) + + +@pytest.mark.asyncio +async def test_ws_api_private_endpoint(clientAsync): + """Test normal order book request""" + orders = await clientAsync.ws_get_all_orders(symbol="BTCUSDT") + + +@pytest.mark.asyncio +async def test_ws_futures_public_endpoint(futuresClientAsync): + """Test normal order book request""" + order_book = await futuresClientAsync.ws_futures_get_order_book(symbol="BTCUSDT") + assert_ob(order_book) + + +@pytest.mark.asyncio +async def test_ws_futures_private_endpoint(futuresClientAsync): + """Test normal order book request""" + await futuresClientAsync.ws_futures_v2_account_position(symbol="BTCUSDT") + + +@pytest.mark.asyncio +async def test_ws_get_symbol_ticker(clientAsync): + """Test symbol ticker request""" + ticker = await clientAsync.ws_get_symbol_ticker(symbol="BTCUSDT") + assert "symbol" in ticker + assert ticker["symbol"] == "BTCUSDT" + + +@pytest.mark.asyncio +async def test_invalid_request(clientAsync): + """Test error handling for invalid symbol""" + with pytest.raises( + BinanceAPIException, + match=re.escape( + "APIError(code=-1100): Illegal characters found in parameter 'symbol'; legal range is '^[A-Z0-9-_.]{1,20}$'." + ), + ): + await clientAsync.ws_get_order_book(symbol="send error") + + +@pytest.mark.asyncio +async def test_connection_handling(clientAsync): + """Test connection handling and reconnection""" + # First request should establish connection + await clientAsync.ws_get_order_book(symbol="BTCUSDT") + assert clientAsync.ws_api.ws_state == WSListenerState.STREAMING + + # Force connection close + await clientAsync.close_connection() + assert clientAsync.ws_api.ws_state == WSListenerState.EXITING + assert clientAsync.ws_api.ws is None + + # Next request should reconnect + order_book = await clientAsync.ws_get_order_book(symbol="LTCUSDT") + assert_ob(order_book) + assert clientAsync.ws_api.ws_state == WSListenerState.STREAMING + + +@pytest.mark.asyncio +async def test_timeout_handling(clientAsync): + """Test request timeout handling""" + # Set very short timeout to force timeout error + original_timeout = clientAsync.ws_api.TIMEOUT + clientAsync.ws_api.TIMEOUT = 0.0001 + + try: + with pytest.raises(BinanceWebsocketUnableToConnect, match="Request timed out"): + await clientAsync.ws_get_order_book(symbol="BTCUSDT") + finally: + clientAsync.ws_api.TIMEOUT = original_timeout + + +@pytest.mark.asyncio +async def test_multiple_requests(clientAsync): + """Test multiple concurrent requests""" + symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT"] + tasks = [clientAsync.ws_get_order_book(symbol=symbol) for symbol in symbols] + results = await asyncio.gather(*tasks) + assert len(results) == len(symbols) + for result in results: + assert_ob(result) + + +@pytest.mark.asyncio +async def test_testnet_url(): + """Test testnet URL configuration""" + testnet_client = AsyncClient(testnet=True) + try: + assert testnet_client.ws_api._url == testnet_client.WS_API_TESTNET_URL + order_book = await testnet_client.ws_get_order_book(symbol="BTCUSDT") + assert_ob(order_book) + finally: + await testnet_client.close_connection() + + +@pytest.mark.asyncio +async def test_message_handling(clientAsync): + """Test message handling with various message types""" + # Test valid message + valid_msg = {"id": "123", "result": {"test": "data"}} + result = clientAsync.ws_api._handle_message(json.dumps(valid_msg)) + assert result == valid_msg + + # Test message without ID + no_id_msg = {"data": "test"} + result = clientAsync.ws_api._handle_message(json.dumps(no_id_msg)) + assert result == no_id_msg + + # Test invalid JSON + result = clientAsync.ws_api._handle_message("invalid json") + assert result is None + + +@pytest.mark.asyncio(scope="function") +async def test_connection_failure(clientAsync): + """Test handling of connection failures""" + # Set invalid URL + clientAsync.ws_api._url = "wss://invalid.url" + + with pytest.raises(BinanceWebsocketUnableToConnect, match="Connection failed"): + await clientAsync.ws_get_order_book(symbol="BTCUSDT") + + +@pytest.mark.asyncio(scope="function") +async def test_cleanup_on_exit(clientAsync): + """Test cleanup of resources on exit""" + # Create some pending requests + future = asyncio.Future() + clientAsync.ws_api._responses["test"] = future + + # Close connection + await clientAsync.close_connection() + + # Check cleanup + assert "test" not in clientAsync.ws_api._responses + assert future.exception() is not None diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 000000000..71fdefad2 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,16 @@ +def test_multiple_objects(obj_list, assertion_func): + """ + Generic test function for validating multiple objects + + Args: + client_response: List or iterator of objects to validate + assertion_func: Function to use for asserting each object's structure + """ + assert obj_list is not None, "Response should not be None" + + # Handle both lists and iterators + objects = list(obj_list) + + # Validate each object + for obj in objects: + assertion_func(obj) diff --git a/tox.ini b/tox.ini index 52038b8d3..e860ef7ca 100644 --- a/tox.ini +++ b/tox.ini @@ -5,21 +5,14 @@ envlist = py36, py37, py38, py39, py310, py311, py312 deps = -rtest-requirements.txt -rrequirements.txt -commands = pytest -v tests/ --doctest-modules --cov binance --cov-report term-missing passenv = PROXY - -[testenv:flake8] -commands = flake8 binance/ setup.py examples/ -deps = flake8 - -[flake8] -exclude = - .git, - .tox, - build, - dist -ignore = E501 + TEST_TESTNET + TEST_API_KEY + TEST_API_SECRET + TEST_FUTURES_API_KEY + TEST_FUTURES_API_SECRET +commands = pytest -v tests/ --doctest-modules --cov binance --cov-report term-missing [pep8] ignore = E501