diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0c49f2342..28655450b 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -26,10 +26,9 @@ jobs: run: pip install ruff - name: Lint code with Ruff run: ruff check --output-format=github --target-version=py39 . - continue-on-error: false # TODO: delete once ruff errors are fixed - # - name: Check code formatting with Ruff - # run: ruff format --check . - # continue-on-error: false # TODO: delete once ruff errors are fixed + - name: Check code formatting with Ruff + run: ruff format --check . + continue-on-error: true build: needs: lint @@ -42,6 +41,7 @@ jobs: TEST_FUTURES_API_KEY: "227719da8d8499e8d3461587d19f259c0b39c2b462a77c9b748a6119abd74401" TEST_FUTURES_API_SECRET: "b14b935f9cfacc5dec829008733c40da0588051f29a44625c34967b45c11d73c" strategy: + max-parallel: 1 matrix: python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: diff --git a/README.rst b/README.rst index 7e154db85..e9426fe0d 100755 --- a/README.rst +++ b/README.rst @@ -66,7 +66,9 @@ Features - Testnet support for Spot, Futures and Vanilla Options - Simple handling of authentication include RSA and EDDSA keys - No need to generate timestamps yourself, the wrapper does it for you +- RecvWindow sent by default - Response exception handling +- Customizable HTTP headers - Websocket handling with reconnection and multiplexed connections - CRUD over websockets, create/fetch/edit through websockets for minimum latency. - Symbol Depth Cache @@ -80,6 +82,7 @@ Features - Proxy support (REST and WS) - Orjson support for faster JSON parsing - Support other domains (.us, .jp, etc) +- Support for the Gift Card API Upgrading to v1.0.0+ -------------------- @@ -165,6 +168,9 @@ pass `testnet=True` when creating the client. # create order through websockets order_ws = client.ws_create_order( symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + # get account using custom headers + account = client.get_account(headers={'MyCustomKey': 'MyCustomValue'}) + # socket manager using threads twm = ThreadedWebsocketManager() twm.start() diff --git a/binance/async_client.py b/binance/async_client.py index 94939ff2b..f0a01b8ef 100644 --- a/binance/async_client.py +++ b/binance/async_client.py @@ -1,7 +1,7 @@ import asyncio from pathlib import Path from typing import Any, Dict, List, Optional, Union -from urllib.parse import urlencode +from urllib.parse import urlencode, quote import time import aiohttp import yarl @@ -12,7 +12,12 @@ BinanceRequestException, NotImplementedException, ) -from binance.helpers import convert_ts_str, get_loop, interval_to_milliseconds +from binance.helpers import ( + convert_list_to_json_array, + convert_ts_str, + get_loop, + interval_to_milliseconds, +) from .base_client import BaseClient from .client import Client @@ -102,16 +107,32 @@ async def close_connection(self): async def _request( self, method, uri: str, signed: bool, force_params: bool = False, **kwargs ): + # this check needs to be done before __get_request_kwargs to avoid + # polluting the signature + headers = {} + if method.upper() in ["POST", "PUT", "DELETE"]: + headers.update({"Content-Type": "application/x-www-form-urlencoded"}) + + if "data" in kwargs: + for key in kwargs["data"]: + if key == "headers": + headers.update(kwargs["data"][key]) + del kwargs["data"][key] + break + kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) - if method == 'get': + if method == "get": # url encode the query string - if 'params' in kwargs: + if "params" in kwargs: uri = f"{uri}?{kwargs['params']}" - kwargs.pop('params') + kwargs.pop("params") async with getattr(self.session, method)( - yarl.URL(uri, encoded=True), proxy=self.https_proxy, **kwargs + yarl.URL(uri, encoded=True), + proxy=self.https_proxy, + headers=headers, + **kwargs, ) as response: self.response = response return await self._handle_response(response) @@ -138,30 +159,31 @@ async def _request_api( **kwargs, ): uri = self._create_api_uri(path, signed, version) - return await self._request(method, uri, signed, **kwargs) + force_params = kwargs.pop("force_params", False) + return await self._request(method, uri, signed, force_params, **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) + force_params = kwargs.pop("force_params", False) + return await self._request(method, uri, signed, force_params, **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) + force_params = kwargs.pop("force_params", True) + return await self._request(method, uri, signed, force_params, **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) + force_params = kwargs.pop("force_params", False) + return await self._request(method, uri, signed, force_params, **kwargs) async def _request_futures_coin_data_api( self, method, path, signed=False, version=1, **kwargs @@ -169,12 +191,14 @@ async def _request_futures_coin_data_api( 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) + force_params = kwargs.pop("force_params", True) + return await self._request(method, uri, signed, force_params, **kwargs) async def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: uri = self._create_options_api_uri(path) + force_params = kwargs.pop("force_params", True) - return await self._request(method, uri, signed, True, **kwargs) + return await self._request(method, uri, signed, force_params, **kwargs) async def _request_margin_api( self, method, path, signed=False, version=1, **kwargs @@ -182,7 +206,8 @@ async def _request_margin_api( version = self._get_version(version, **kwargs) uri = self._create_margin_api_uri(path, version) - return await self._request(method, uri, signed, **kwargs) + force_params = kwargs.pop("force_params", False) + return await self._request(method, uri, signed, force_params, **kwargs) async def _request_papi_api( self, method, path, signed=False, version=1, **kwargs @@ -190,7 +215,8 @@ async def _request_papi_api( version = self._get_version(version, **kwargs) uri = self._create_papi_api_uri(path, version) - return await self._request(method, uri, signed, **kwargs) + force_params = kwargs.pop("force_params", False) + return await self._request(method, uri, signed, force_params, **kwargs) async def _request_website(self, method, path, signed=False, **kwargs) -> Dict: uri = self._create_website_uri(path) @@ -712,13 +738,16 @@ async def get_account(self, **params): get_account.__doc__ = Client.get_account.__doc__ - async def get_asset_balance(self, asset, **params): + async def get_asset_balance(self, asset=None, **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 + if asset: + for bal in res["balances"]: + if bal["asset"].lower() == asset.lower(): + return bal + else: + return res["balances"] return None get_asset_balance.__doc__ = Client.get_asset_balance.__doc__ @@ -1775,6 +1804,14 @@ async def futures_create_order(self, **params): params["newClientOrderId"] = self.CONTRACT_ORDER_PREFIX + self.uuid22() return await self._request_futures_api("post", "order", True, data=params) + async def futures_modify_order(self, **params): + """Modify an existing order. Currently only LIMIT order modification is supported. + + https://binance-docs.github.io/apidocs/futures/en/#modify-order-trade + + """ + return await self._request_futures_api("put", "order", True, data=params) + async def futures_create_test_order(self, **params): return await self._request_futures_api("post", "order/test", True, data=params) @@ -1782,10 +1819,13 @@ 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") + order = self._order_params(order) + query_string = urlencode(params).replace("%40", "@").replace("%27", "%22") params["batchOrders"] = query_string[12:] - return await self._request_futures_api("post", "batchOrders", True, data=params) + + return await self._request_futures_api( + "post", "batchOrders", True, data=params, force_params=True + ) async def futures_get_order(self, **params): return await self._request_futures_api("get", "order", True, data=params) @@ -1805,8 +1845,16 @@ async def futures_cancel_all_open_orders(self, **params): ) async def futures_cancel_orders(self, **params): + if params.get("orderidlist"): + params["orderidlist"] = quote( + convert_list_to_json_array(params["orderidlist"]) + ) + if params.get("origclientorderidlist"): + params["origclientorderidlist"] = quote( + convert_list_to_json_array(params["origclientorderidlist"]) + ) return await self._request_futures_api( - "delete", "batchOrders", True, data=params + "delete", "batchOrders", True, data=params, force_params=True ) async def futures_countdown_cancel_all(self, **params): @@ -2036,10 +2084,18 @@ async def futures_coin_cancel_order(self, **params): async def futures_coin_cancel_all_open_orders(self, **params): return await self._request_futures_coin_api( - "delete", "allOpenOrders", signed=True, data=params + "delete", "allOpenOrders", signed=True, data=params, force_params=True ) async def futures_coin_cancel_orders(self, **params): + if params.get("orderidlist"): + params["orderidlist"] = quote( + convert_list_to_json_array(params["orderidlist"]) + ) + if params.get("origclientorderidlist"): + params["origclientorderidlist"] = quote( + convert_list_to_json_array(params["origclientorderidlist"]) + ) return await self._request_futures_coin_api( "delete", "batchOrders", True, data=params ) @@ -3601,3 +3657,51 @@ async def ws_futures_account_status(self, **params): 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) + + #################################################### + # Gift Card API Endpoints + #################################################### + + async def gift_card_fetch_token_limit(self, **params): + return await self._request_margin_api( + "get", "giftcard/buyCode/token-limit", signed=True, data=params + ) + + gift_card_fetch_token_limit.__doc__ = Client.gift_card_fetch_token_limit.__doc__ + + async def gift_card_fetch_rsa_public_key(self, **params): + return await self._request_margin_api( + "get", "giftcard/cryptography/rsa-public-key", signed=True, data=params + ) + + gift_card_fetch_rsa_public_key.__doc__ = ( + Client.gift_card_fetch_rsa_public_key.__doc__ + ) + + async def gift_card_verify(self, **params): + return await self._request_margin_api( + "get", "giftcard/verify", signed=True, data=params + ) + + gift_card_verify.__doc__ = Client.gift_card_verify.__doc__ + + async def gift_card_redeem(self, **params): + return await self._request_margin_api( + "post", "giftcard/redeemCode", signed=True, data=params + ) + + gift_card_redeem.__doc__ = Client.gift_card_redeem.__doc__ + + async def gift_card_create(self, **params): + return await self._request_margin_api( + "post", "giftcard/createCode", signed=True, data=params + ) + + gift_card_create.__doc__ = Client.gift_card_create.__doc__ + + async def gift_card_create_dual_token(self, **params): + return await self._request_margin_api( + "post", "giftcard/buyCode", signed=True, data=params + ) + + gift_card_create_dual_token.__doc__ = Client.gift_card_create_dual_token.__doc__ diff --git a/binance/base_client.py b/binance/base_client.py index 34578bd3d..e36eff8a3 100644 --- a/binance/base_client.py +++ b/binance/base_client.py @@ -60,7 +60,7 @@ class BaseClient: REQUEST_TIMEOUT: float = 10 - REQUEST_RECVWINDOW: int = 10000 # 10 seconds + REQUEST_RECVWINDOW: int = 10000 # 10 seconds SYMBOL_TYPE_SPOT = "SPOT" @@ -209,6 +209,7 @@ def __init__( def _get_headers(self) -> Dict: headers = { "Accept": "application/json", + "Content-Type": "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: @@ -480,8 +481,13 @@ def _get_request_kwargs( 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"] + if "params" in kwargs.keys(): + if ( + "batchOrders" in kwargs["params"] + or "orderidlist" in kwargs["params"] + or "origclientorderidlist" in kwargs["params"] + ): + kwargs["data"] = kwargs["params"] + del kwargs["params"] return kwargs diff --git a/binance/client.py b/binance/client.py index 3d6470bae..b9c0b5571 100755 --- a/binance/client.py +++ b/binance/client.py @@ -3,10 +3,15 @@ import requests import time -from urllib.parse import urlencode +from urllib.parse import urlencode, quote + from .base_client import BaseClient -from .helpers import interval_to_milliseconds, convert_ts_str +from .helpers import ( + convert_list_to_json_array, + interval_to_milliseconds, + convert_ts_str, +) from .exceptions import ( BinanceAPIException, BinanceRequestException, @@ -53,9 +58,20 @@ def _init_session(self) -> requests.Session: def _request( self, method, uri: str, signed: bool, force_params: bool = False, **kwargs ): + headers = {} + if method.upper() in ["POST", "PUT", "DELETE"]: + headers.update({"Content-Type": "application/x-www-form-urlencoded"}) + + if "data" in kwargs: + for key in kwargs["data"]: + if key == "headers": + headers.update(kwargs["data"][key]) + del kwargs["data"][key] + break + kwargs = self._get_request_kwargs(method, signed, force_params, **kwargs) - self.response = getattr(self.session, method)(uri, **kwargs) + self.response = getattr(self.session, method)(uri, headers=headers, **kwargs) return self._handle_response(self.response) @staticmethod @@ -87,13 +103,15 @@ def _request_futures_api( ) -> Dict: version = self._get_version(version, **kwargs) uri = self._create_futures_api_uri(path, version) + force_params = kwargs.pop("force_params", False) - return self._request(method, uri, signed, True, **kwargs) + return self._request(method, uri, signed, force_params, **kwargs) def _request_futures_data_api(self, method, path, signed=False, **kwargs) -> Dict: uri = self._create_futures_data_api_uri(path) - return self._request(method, uri, signed, True, **kwargs) + force_params = kwargs.pop("force_params", True) + return self._request(method, uri, signed, force_params, **kwargs) def _request_futures_coin_api( self, method, path, signed=False, version=1, **kwargs @@ -101,7 +119,8 @@ def _request_futures_coin_api( version = self._get_version(version, **kwargs) uri = self._create_futures_coin_api_url(path, version=version) - return self._request(method, uri, signed, False, **kwargs) + force_params = kwargs.pop("force_params", False) + return self._request(method, uri, signed, force_params, **kwargs) def _request_futures_coin_data_api( self, method, path, signed=False, version=1, **kwargs @@ -109,12 +128,14 @@ def _request_futures_coin_data_api( version = self._get_version(version, **kwargs) uri = self._create_futures_coin_data_api_url(path, version=version) - return self._request(method, uri, signed, True, **kwargs) + force_params = kwargs.pop("force_params", True) + return self._request(method, uri, signed, force_params, **kwargs) def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: uri = self._create_options_api_uri(path) - return self._request(method, uri, signed, True, **kwargs) + force_params = kwargs.pop("force_params", True) + return self._request(method, uri, signed, force_params, **kwargs) def _request_margin_api( self, method, path, signed=False, version=1, **kwargs @@ -122,14 +143,16 @@ def _request_margin_api( version = self._get_version(version, **kwargs) uri = self._create_margin_api_uri(path, version) - return self._request(method, uri, signed, **kwargs) + force_params = kwargs.pop("force_params", False) + return self._request(method, uri, signed, force_params, **kwargs) 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 self._request(method, uri, signed, **kwargs) + force_params = kwargs.pop("force_params", False) + return self._request(method, uri, signed, force_params, **kwargs) def _request_website(self, method, path, signed=False, **kwargs) -> Dict: uri = self._create_website_uri(path) @@ -1990,10 +2013,10 @@ def get_account(self, **params): """ return self._get("account", True, data=params) - def get_asset_balance(self, asset, **params): + def get_asset_balance(self, asset=None, **params): """Get current asset balance. - :param asset: required + :param asset: optional - the asset to get the balance of :type asset: str :param recvWindow: the number of milliseconds the request is valid for :type recvWindow: int @@ -2014,9 +2037,12 @@ def get_asset_balance(self, asset, **params): res = 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 + if asset: + for bal in res["balances"]: + if bal["asset"].lower() == asset.lower(): + return bal + else: + return res["balances"] return None def get_my_trades(self, **params): @@ -7442,7 +7468,9 @@ def futures_place_batch_order(self, **params): query_string = urlencode(params) query_string = query_string.replace("%27", "%22") params["batchOrders"] = query_string[12:] - return self._request_futures_api("post", "batchOrders", True, data=params) + return self._request_futures_api( + "post", "batchOrders", True, data=params, force_params=True + ) def futures_get_order(self, **params): """Check an order's status. @@ -7490,7 +7518,17 @@ def futures_cancel_orders(self, **params): https://binance-docs.github.io/apidocs/futures/en/#cancel-multiple-orders-trade """ - return self._request_futures_api("delete", "batchOrders", True, data=params) + if params.get("orderidlist"): + params["orderidlist"] = quote( + convert_list_to_json_array(params["orderidlist"]) + ) + if params.get("origclientorderidlist"): + params["origclientorderidlist"] = quote( + convert_list_to_json_array(params["origclientorderidlist"]) + ) + return self._request_futures_api( + "delete", "batchOrders", True, force_params=True, data=params + ) def futures_countdown_cancel_all(self, **params): """Cancel all open orders of the specified symbol at the end of the specified countdown. @@ -7915,7 +7953,7 @@ def futures_coin_cancel_all_open_orders(self, **params): """ return self._request_futures_coin_api( - "delete", "allOpenOrders", signed=True, data=params + "delete", "allOpenOrders", signed=True, force_params=True, data=params ) def futures_coin_cancel_orders(self, **params): @@ -7924,6 +7962,14 @@ def futures_coin_cancel_orders(self, **params): https://binance-docs.github.io/apidocs/delivery/en/#cancel-multiple-orders-trade """ + if params.get("orderidlist"): + params["orderidlist"] = quote( + convert_list_to_json_array(params["orderidlist"]) + ) + if params.get("origclientOrderidlist"): + params["origclientorderidlist"] = quote( + convert_list_to_json_array(params["origclientorderidlist"]) + ) return self._request_futures_coin_api( "delete", "batchOrders", True, data=params ) @@ -10524,3 +10570,192 @@ def ws_futures_account_status(self, **params): https://developers.binance.com/docs/derivatives/usds-margined-futures/account/websocket-api/Account-Information """ return self._ws_futures_api_request_sync("account.status", True, params) + + ############################################### + ### Gift card api + ############################################### + def gift_card_fetch_token_limit(self, **params): + """Verify which tokens are available for you to create Stablecoin-Denominated gift cards + https://developers.binance.com/docs/gift_card/market-data/Fetch-Token-Limit + + :param baseToken: The token you want to pay, example: BUSD + :type baseToken: str + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": [ + { + "coin": "BNB", + "fromMin": "0.01", + "fromMax": "1" + } + ], + "success": true + } + """ + return self._request_margin_api( + "get", "giftcard/buyCode/token-limit", signed=True, data=params + ) + + def gift_card_fetch_rsa_public_key(self, **params): + """This API is for fetching the RSA Public Key. This RSA Public key will be used to encrypt the card code. + + Important Note: + The RSA Public key fetched is valid only for the current day. + + https://developers.binance.com/docs/gift_card/market-data/Fetch-RSA-Public-Key + :param recvWindow: The receive window for the request in milliseconds (optional) + :type recvWindow: int + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXBBVKLAc1GQ5FsIFFqOHrPTox5noBONIKr+IAedTR9FkVxq6e65updEbfdhRNkMOeYIO2i0UylrjGC0X8YSoIszmrVHeV0l06Zh1oJuZos1+7N+WLuz9JvlPaawof3GUakTxYWWCa9+8KIbLKsoKMdfS96VT+8iOXO3quMGKUmQIDAQAB", + "success": true + } + """ + return self._request_margin_api( + "get", "giftcard/cryptography/rsa-public-key", signed=True, data=params + ) + + def gift_card_verify(self, **params): + """This API is for verifying whether the Binance Gift Card is valid or not by entering Gift Card Number. + + Important Note: + If you enter the wrong Gift Card Number 5 times within an hour, you will no longer be able + to verify any Gift Card Number for that hour. + + https://developers.binance.com/docs/gift_card/market-data/Verify-Binance-Gift-Card-by-Gift-Card-Number + + :param referenceNo: Enter the Gift Card Number + :type referenceNo: str + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": { + "valid": true, + "token": "BNB", # coin + "amount": "0.00000001" # amount + }, + "success": true + } + """ + return self._request_margin_api( + "get", "giftcard/verify", signed=True, data=params + ) + + def gift_card_redeem(self, **params): + """This API is for redeeming a Binance Gift Card. Once redeemed, the coins will be deposited in your funding wallet. + + Important Note: + If you enter the wrong redemption code 5 times within 24 hours, you will no longer be able to + redeem any Binance Gift Cards that day. + + Code Format Options: + - Plaintext + - Encrypted (Recommended for better security) + + For encrypted format: + 1. Fetch RSA public key from the RSA public key endpoint + 2. Encrypt the code using algorithm: RSA/ECB/OAEPWithSHA-256AndMGF1Padding + + https://developers.binance.com/docs/gift_card/market-data/Redeem-a-Binance-Gift-Card + :param code: Redemption code of Binance Gift Card to be redeemed, supports both Plaintext & Encrypted code + :type code: str + :param externalUid: External unique ID representing a user on the partner platform. + Helps identify redemption behavior and control risks/limits. + Max 400 characters. (optional) + :type externalUid: str + :param recvWindow: The receive window for the request in milliseconds (optional) + :type recvWindow: int + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": { + "referenceNo": "0033002328060227", + "identityNo": "10317392647411060736", + "token": "BNB", + "amount": "0.00000001" + }, + "success": true + } + """ + return self._request_margin_api( + "post", "giftcard/redeemCode", signed=True, data=params + ) + + def gift_card_create(self, **params): + """ + This API is for creating a Binance Gift Card. + + To get started with, please make sure: + + - You have a Binance account + - You have passed KYB + - You have a sufficient balance(Gift Card amount and fee amount) in your Binance funding wallet + - You need Enable Withdrawals for the API Key which requests this endpoint. + + https://developers.binance.com/docs/gift_card/market-data + + :param token: The token type contained in the Binance Gift Card + :type token: str + :param amount: The amount of the token contained in the Binance Gift Card + :type amount: float + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": { + "referenceNo": "0033002144060553", + "code": "6H9EKF5ECCWFBHGE", + "expiredTime": 1727417154000 + }, + "success": true + } + """ + return self._request_margin_api( + "post", "giftcard/createCode", signed=True, data=params + ) + + def gift_card_create_dual_token(self, **params): + """This API is for creating a dual-token ( stablecoin-denominated) Binance Gift Card. You may create a gift card using USDT as baseToken, that is redeemable to another designated token (faceToken). For example, you can create a fixed-value BTC gift card and pay with 100 USDT plus 1 USDT fee. This gift card can keep the value fixed at 100 USDT before redemption, and will be redeemable to BTC equivalent to 100 USDT upon redemption. + + Once successfully created, the amount of baseToken (e.g. USDT) in the fixed-value gift card along with the fee would be deducted from your funding wallet. + + To get started with, please make sure: + - You have a Binance account + - You have passed KYB + - You have a sufficient balance(Gift Card amount and fee amount) in your Binance funding wallet + - You need Enable Withdrawals for the API Key which requests this endpoint. + + https://developers.binance.com/docs/gift_card/market-data/Create-a-dual-token-gift-card + :param baseToken: The token you want to pay, example: BUSD + :type baseToken: str + :param faceToken: The token you want to buy, example: BNB. If faceToken = baseToken, it's the same as createCode endpoint. + :type faceToken: str + :param discount: Stablecoin-denominated card discount percentage, Example: 1 for 1% discount. Scale should be less than 6. + :type discount: float + :return: api response + .. code-block:: python + { + "code": "000000", + "message": "success", + "data": { + "referenceNo": "0033002144060553", + "code": "6H9EKF5ECCWFBHGE", + "expiredTime": 1727417154000 + }, + "success": true + } + """ + return self._request_margin_api( + "post", "giftcard/buyCode", signed=True, data=params + ) diff --git a/binance/exceptions.py b/binance/exceptions.py index 44753f567..62fcefb97 100644 --- a/binance/exceptions.py +++ b/binance/exceptions.py @@ -79,4 +79,5 @@ def __init__(self, value): super().__init__(message) -class UnknownDateFormat(Exception): ... +class UnknownDateFormat(Exception): + ... diff --git a/binance/helpers.py b/binance/helpers.py index 4fc7676ec..8e02d0ea3 100644 --- a/binance/helpers.py +++ b/binance/helpers.py @@ -1,5 +1,6 @@ import asyncio from decimal import Decimal +import json from typing import Union, Optional, Dict import dateparser @@ -80,6 +81,13 @@ def convert_ts_str(ts_str): return date_to_milliseconds(ts_str) +def convert_list_to_json_array(l): + if l is None: + return l + res = json.dumps(l) + return res.replace(" ", "") + + def get_loop(): """check if there is an event loop in the current thread, if not create one inspired by https://stackoverflow.com/questions/46727787/runtimeerror-there-is-no-current-event-loop-in-thread-in-async-apscheduler diff --git a/binance/ws/reconnecting_websocket.py b/binance/ws/reconnecting_websocket.py index 424a23835..3a8105666 100644 --- a/binance/ws/reconnecting_websocket.py +++ b/binance/ws/reconnecting_websocket.py @@ -188,10 +188,12 @@ async def _read_loop(self): 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", - }) + 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") diff --git a/binance/ws/threaded_stream.py b/binance/ws/threaded_stream.py index 4ee3254a4..596bd75ed 100755 --- a/binance/ws/threaded_stream.py +++ b/binance/ws/threaded_stream.py @@ -34,7 +34,8 @@ def __init__( "https_proxy": https_proxy, } - 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/pyproject.toml b/pyproject.toml index c9f1e92f6..740a8bbc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,4 @@ lint.ignore = ["F722","F841","F821","E402","E501","E902","E713","E741","E714", " [tool.pytest.ini_options] timeout = 10 timeout_method = "thread" +addopts = "-n 10" diff --git a/test-requirements.txt b/test-requirements.txt index 24c7d0a70..a787f1d0e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,6 +2,8 @@ coverage pytest pytest-asyncio pytest-cov +pytest-xdist +pytest-rerunfailures requests-mock tox setuptools diff --git a/tests/conftest.py b/tests/conftest.py index b4e57a7f7..3ea949ad9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ proxies = {} proxy = os.getenv("PROXY") +proxy = "http://51.83.140.52:16301" if proxy: proxies = {"http": proxy, "https": proxy} # tmp: improve this in the future else: @@ -18,6 +19,11 @@ 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" +api_key = "u4L8MG2DbshTfTzkx2Xm7NfsHHigvafxeC29HrExEmah1P8JhxXkoOu6KntLICUc" +api_secret = "hBZEqhZUUS6YZkk7AIckjJ3iLjrgEFr5CRtFPp5gjzkrHKKC9DAv4OH25PlT6yq5" +testnet = True +futures_api_key = "227719da8d8499e8d3461587d19f259c0b39c2b462a77c9b748a6119abd74401" +futures_api_secret = "b14b935f9cfacc5dec829008733c40da0588051f29a44625c34967b45c11d73c" # Configure logging for all tests @@ -41,6 +47,11 @@ def client(): return Client(api_key, api_secret, {"proxies": proxies}, testnet=testnet) +@pytest.fixture(scope="function") +def liveClient(): + return Client(api_key, api_secret, {"proxies": proxies}, testnet=False) + + @pytest.fixture(scope="function") def futuresClient(): return Client( @@ -60,6 +71,11 @@ def futuresClientAsync(): ) +@pytest.fixture(scope="function") +def liveClientAsync(): + return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=False) + + @pytest.fixture(autouse=True, scope="function") def event_loop(): """Create new event loop for each test""" @@ -71,3 +87,62 @@ def event_loop(): task.cancel() loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) loop.close() + + +def pytest_addoption(parser): + parser.addoption( + "--run-spot", action="store_true", default=True, help="Run margin tests" + ) + parser.addoption( + "--run-futures", action="store_true", default=True, help="Run margin tests" + ) + parser.addoption( + "--run-margin", action="store_true", default=False, help="Run margin tests" + ) + parser.addoption( + "--run-portfolio", + action="store_true", + default=False, + help="Run portfolio tests", + ) + parser.addoption( + "--run-gift-card", + action="store_true", + default=False, + help="Run gift card tests", + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "spot: mark a test as part of the spot tests") + config.addinivalue_line( + "markers", "futures: mark a test as part of the futures tests" + ) + config.addinivalue_line( + "markers", "margin: mark a test as part of the margin tests" + ) + config.addinivalue_line( + "markers", "portfolio: mark a test as part of the portfolio tests" + ) + config.addinivalue_line( + "markers", "gift_card: mark a test as part of the gift card tests" + ) + + +def pytest_collection_modifyitems(config, items): + skip_spot = pytest.mark.skip(reason="need --run-spot option to run") + skip_futures = pytest.mark.skip(reason="need --run-futures option to run") + skip_margin = pytest.mark.skip(reason="need --run-margin option to run") + skip_portfolio = pytest.mark.skip(reason="need --run-portfolio option to run") + skip_gift_card = pytest.mark.skip(reason="need --run-gift-card option to run") + for item in items: + if "spot" in item.keywords and not config.getoption("--run-spot"): + item.add_marker(skip_spot) + if "futures" in item.keywords and not config.getoption("--run-futures"): + item.add_marker(skip_futures) + if "margin" in item.keywords and not config.getoption("--run-margin"): + item.add_marker(skip_margin) + if "portfolio" in item.keywords and not config.getoption("--run-portfolio"): + item.add_marker(skip_portfolio) + if "gift_card" in item.keywords and not config.getoption("--run-gift-card"): + item.add_marker(skip_gift_card) diff --git a/tests/test_async_client.py b/tests/test_async_client.py new file mode 100644 index 000000000..d7c32fb33 --- /dev/null +++ b/tests/test_async_client.py @@ -0,0 +1,169 @@ +import pytest + +pytestmark = [pytest.mark.asyncio] + + +async def test_clientAsync_initialization(clientAsync): + assert clientAsync.API_KEY is not None + assert clientAsync.API_SECRET is not None + + +@pytest.mark.skip(reason="Endpoint not documented") +async def test_get_products(clientAsync): + await clientAsync.get_products() + + +async def test_get_exchange_info(clientAsync): + await clientAsync.get_exchange_info() + + +async def test_get_symbol_info(clientAsync): + await clientAsync.get_symbol_info("BTCUSDT") + + +async def test_ping(clientAsync): + await clientAsync.ping() + + +async def test_get_server_time(clientAsync): + await clientAsync.get_server_time() + + +async def test_get_all_tickers(clientAsync): + await clientAsync.get_all_tickers() + + +async def test_get_orderbook_tickers(clientAsync): + await clientAsync.get_orderbook_tickers() + + +async def test_get_order_book(clientAsync): + await clientAsync.get_order_book(symbol="BTCUSDT") + + +async def test_get_recent_trades(clientAsync): + await clientAsync.get_recent_trades(symbol="BTCUSDT") + + +async def test_get_historical_trades(clientAsync): + await clientAsync.get_historical_trades(symbol="BTCUSDT") + + +async def test_get_aggregate_trades(clientAsync): + await clientAsync.get_aggregate_trades(symbol="BTCUSDT") + + +async def test_get_klines(clientAsync): + await clientAsync.get_klines(symbol="BTCUSDT", interval="1d") + + +async def test_get_avg_price(clientAsync): + await clientAsync.get_avg_price(symbol="BTCUSDT") + + +async def test_get_ticker(clientAsync): + await clientAsync.get_ticker(symbol="BTCUSDT") + + +async def test_get_symbol_ticker(clientAsync): + await clientAsync.get_symbol_ticker(symbol="BTCUSDT") + + +async def test_get_orderbook_ticker(clientAsync): + await clientAsync.get_orderbook_ticker(symbol="BTCUSDT") + + +async def test_get_account(clientAsync): + await clientAsync.get_account() + + +async def test_get_asset_balance(clientAsync): + await clientAsync.get_asset_balance(asset="BTC") + + +async def test_get_asset_balance_no_asset_provided(clientAsync): + await clientAsync.get_asset_balance() + + +async def test_get_my_trades(clientAsync): + await clientAsync.get_my_trades(symbol="BTCUSDT") + + +async def test_get_system_status(clientAsync): + await clientAsync.get_system_status() + + +# User Stream Endpoints + + +async def test_stream_get_listen_key_and_close(clientAsync): + listen_key = await clientAsync.stream_get_listen_key() + await clientAsync.stream_close(listen_key) + + +# Quoting interface endpoints + + +######################### +# Websocket API Requests # +######################### + + +async def test_ws_get_order_book(clientAsync): + await clientAsync.ws_get_order_book(symbol="BTCUSDT") + + +async def test_ws_get_recent_trades(clientAsync): + await clientAsync.ws_get_recent_trades(symbol="BTCUSDT") + + +async def test_ws_get_historical_trades(clientAsync): + await clientAsync.ws_get_historical_trades(symbol="BTCUSDT") + + +async def test_ws_get_aggregate_trades(clientAsync): + await clientAsync.ws_get_aggregate_trades(symbol="BTCUSDT") + + +async def test_ws_get_klines(clientAsync): + await clientAsync.ws_get_klines(symbol="BTCUSDT", interval="1m") + + +async def test_ws_get_uiKlines(clientAsync): + await clientAsync.ws_get_uiKlines(symbol="BTCUSDT", interval="1m") + + +async def test_ws_get_avg_price(clientAsync): + await clientAsync.ws_get_avg_price(symbol="BTCUSDT") + + +async def test_ws_get_ticker(clientAsync): + ticker = await clientAsync.ws_get_ticker(symbol="BTCUSDT") + + +async def test_ws_get_trading_day_ticker(clientAsync): + await clientAsync.ws_get_trading_day_ticker(symbol="BTCUSDT") + + +async def test_ws_get_symbol_ticker_window(clientAsync): + await clientAsync.ws_get_symbol_ticker_window(symbol="BTCUSDT") + + +async def test_ws_get_symbol_ticker(clientAsync): + await clientAsync.ws_get_symbol_ticker(symbol="BTCUSDT") + + +async def test_ws_get_orderbook_ticker(clientAsync): + await clientAsync.ws_get_orderbook_ticker(symbol="BTCUSDT") + + +async def test_ws_ping(clientAsync): + await clientAsync.ws_ping() + + +async def test_ws_get_time(clientAsync): + await clientAsync.ws_get_time() + + +async def test_ws_get_exchange_info(clientAsync): + await clientAsync.ws_get_exchange_info(symbol="BTCUSDT") diff --git a/tests/test_async_client_futures.py b/tests/test_async_client_futures.py new file mode 100644 index 000000000..d636f1929 --- /dev/null +++ b/tests/test_async_client_futures.py @@ -0,0 +1,581 @@ +from datetime import datetime + +import pytest +from .test_order import assert_contract_order +from .test_get_order_book import assert_ob + +pytestmark = [pytest.mark.futures, pytest.mark.asyncio] + + +async def test_futures_ping(futuresClientAsync): + await futuresClientAsync.futures_ping() + + +async def test_futures_time(futuresClientAsync): + await futuresClientAsync.futures_time() + + +async def test_futures_exchange_info(futuresClientAsync): + await futuresClientAsync.futures_exchange_info() + + +async def test_futures_order_book(futuresClientAsync): + order_book = await futuresClientAsync.futures_order_book(symbol="BTCUSDT") + assert_ob(order_book) + + +async def test_futures_recent_trades(futuresClientAsync): + await futuresClientAsync.futures_recent_trades(symbol="BTCUSDT") + + +async def test_futures_historical_trades(futuresClientAsync): + await futuresClientAsync.futures_historical_trades(symbol="BTCUSDT") + + +async def test_futures_aggregate_trades(futuresClientAsync): + await futuresClientAsync.futures_aggregate_trades(symbol="BTCUSDT") + + +async def test_futures_klines(futuresClientAsync): + await futuresClientAsync.futures_klines(symbol="BTCUSDT", interval="1h") + + +async def test_futures_continous_klines(futuresClientAsync): + await futuresClientAsync.futures_continous_klines( + pair="BTCUSDT", contractType="PERPETUAL", interval="1h" + ) + + +async def test_futures_historical_klines(futuresClientAsync): + await futuresClientAsync.futures_historical_klines( + symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") + ) + + +async def test_futures_historical_klines_generator(futuresClientAsync): + await futuresClientAsync.futures_historical_klines_generator( + symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") + ) + + +async def test_futures_mark_price(futuresClientAsync): + await futuresClientAsync.futures_mark_price() + + +async def test_futures_funding_rate(futuresClientAsync): + await futuresClientAsync.futures_funding_rate() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_top_longshort_account_ratio(futuresClientAsync): + await futuresClientAsync.futures_top_longshort_account_ratio() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_top_longshort_position_ratio(futuresClientAsync): + await futuresClientAsync.futures_top_longshort_position_ratio() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_global_longshort_ratio(futuresClientAsync): + await futuresClientAsync.futures_global_longshort_ratio() + + +async def test_futures_ticker(futuresClientAsync): + await futuresClientAsync.futures_ticker() + + +async def test_futures_symbol_ticker(futuresClientAsync): + await futuresClientAsync.futures_symbol_ticker() + + +async def test_futures_orderbook_ticker(futuresClientAsync): + await futuresClientAsync.futures_orderbook_ticker() + + +async def test_futures_liquidation_orders(futuresClientAsync): + await futuresClientAsync.futures_liquidation_orders() + + +async def test_futures_api_trading_status(futuresClientAsync): + await futuresClientAsync.futures_api_trading_status() + + +async def test_futures_commission_rate(futuresClientAsync): + await futuresClientAsync.futures_commission_rate(symbol="BTCUSDT") + + +async def test_futures_adl_quantile_estimate(futuresClientAsync): + await futuresClientAsync.futures_adl_quantile_estimate() + + +async def test_futures_open_interest(futuresClientAsync): + await futuresClientAsync.futures_open_interest(symbol="BTCUSDT") + + +async def test_futures_index_info(futuresClientAsync): + await futuresClientAsync.futures_index_info() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_open_interest_hist(futuresClientAsync): + await futuresClientAsync.futures_open_interest_hist(symbol="BTCUSDT") + + +async def test_futures_leverage_bracket(futuresClientAsync): + await futuresClientAsync.futures_leverage_bracket() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_account_transfer(futuresClientAsync): + await futuresClientAsync.futures_account_transfer() + + +@pytest.mark.skip(reason="Not implemented") +async def test_transfer_history(client): + client.transfer_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_loan_borrow_history(futuresClientAsync): + await futuresClientAsync.futures_loan_borrow_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_loan_repay_history(futuresClientAsync): + await futuresClientAsync.futures_loan_repay_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_loan_wallet(futuresClientAsync): + await futuresClientAsync.futures_loan_wallet() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_cross_collateral_adjust_history(futuresClientAsync): + await futuresClientAsync.futures_cross_collateral_adjust_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_cross_collateral_liquidation_history(futuresClientAsync): + await futuresClientAsync.futures_cross_collateral_liquidation_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_loan_interest_history(futuresClientAsync): + await futuresClientAsync.futures_loan_interest_history() + + +async def test_futures_create_get_edit_cancel_order(futuresClientAsync): + ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") + positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") + order = await futuresClientAsync.futures_create_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(round(float(ticker["lastPrice"]) - 1)), + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_modify_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_get_order( + symbol=order["symbol"], orderid=order["orderId"] + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +async def test_futures_create_test_order(futuresClientAsync): + ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") + positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") + await futuresClientAsync.futures_create_test_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(round(float(ticker["lastPrice"]) - 1, 0)), + ) + + +async def test_futures_place_batch_order_and_cancel(futuresClientAsync): + ticker = await futuresClientAsync.futures_ticker(symbol="LTCUSDT") + positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") + orders = await futuresClientAsync.futures_place_batch_order( + batchOrders=[ + { + "positionSide": positions[0]["positionSide"], + "price": str(round(float(ticker["lastPrice"]) - 1, 0)), + "quantity": "0.1", + "side": "BUY", + "symbol": ticker["symbol"], + "timeInForce": "GTC", + "type": "LIMIT", + }, + { + "side": "BUY", + "type": "LIMIT", + "positionSide": positions[0]["positionSide"], + "price": str(round(float(ticker["lastPrice"]) - 1, 0)), + "quantity": "0.1", + "symbol": ticker["symbol"], + "timeInForce": "GTC", + }, + ] + ) + for order in orders: + assert_contract_order(futuresClientAsync, order) + + # Cancel using orderidlist + order_ids = [order["orderId"] for order in orders][:1] + cancelled_orders = await futuresClientAsync.futures_cancel_orders( + symbol=orders[0]["symbol"], orderidlist=order_ids + ) + for order in cancelled_orders: + assert_contract_order(futuresClientAsync, order) + # Cancel using origClientOrderIdList + client_order_ids = [order["clientOrderId"] for order in orders][1:] + cancelled_orders = await futuresClientAsync.futures_cancel_orders( + symbol=orders[0]["symbol"], origclientorderidlist=client_order_ids + ) + for order in cancelled_orders: + assert_contract_order(futuresClientAsync, order) + + +async def test_futures_get_open_orders(futuresClientAsync): + await futuresClientAsync.futures_get_open_orders() + + +async def test_futures_get_all_orders(futuresClientAsync): + orders = futuresClientAsync.futures_get_all_orders() + print(orders) + + +async def test_futures_cancel_all_open_orders(futuresClientAsync): + await futuresClientAsync.futures_cancel_all_open_orders(symbol="LTCUSDT") + + +async def test_futures_countdown_cancel_all(futuresClientAsync): + await futuresClientAsync.futures_countdown_cancel_all( + symbol="LTCUSDT", countdownTime=10 + ) + + +async def test_futures_account_balance(futuresClientAsync): + await futuresClientAsync.futures_account_balance() + + +async def test_futures_account(futuresClientAsync): + await futuresClientAsync.futures_account() + + +async def test_futures_change_leverage(futuresClientAsync): + await futuresClientAsync.futures_change_leverage(symbol="LTCUSDT", leverage=10) + + +async def test_futures_change_margin_type(futuresClientAsync): + try: + await futuresClientAsync.futures_change_margin_type( + symbol="XRPUSDT", marginType="CROSSED" + ) + except Exception as e: + await futuresClientAsync.futures_change_margin_type( + symbol="XRPUSDT", marginType="ISOLATED" + ) + + +async def test_futures_position_margin_history(futuresClientAsync): + position = await futuresClientAsync.futures_position_margin_history( + symbol="LTCUSDT" + ) + + +async def test_futures_position_information(futuresClientAsync): + await futuresClientAsync.futures_position_information() + + +async def test_futures_account_trades(futuresClientAsync): + await futuresClientAsync.futures_account_trades() + + +async def test_futures_income_history(futuresClientAsync): + await futuresClientAsync.futures_income_history() + + +async def close_all_futures_positions(futuresClientAsync): + # Get all open positions + positions = await futuresClientAsync.futures_position_information(symbol="LTCUSDT") + + for position in positions: + # Check if there is an open position + if float(position["positionAmt"]) != 0: + symbol = position["symbol"] + position_amt = float(position["positionAmt"]) + side = "SELL" if position_amt > 0 else "BUY" + + # Place a market order to close the position + try: + print(f"Closing position for {symbol}: {position_amt} units") + await futuresClientAsync.futures_create_order( + symbol=symbol, side=side, type="market", quantity=abs(position_amt) + ) + print(f"Position for {symbol} closed successfully.") + except Exception as e: + print(f"Failed to close position for {symbol}: {e}") + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_get_and_change_position_mode(futuresClientAsync): + mode = await futuresClientAsync.futures_get_position_mode() + await futuresClientAsync.futures_change_position_mode( + dualSidePosition=not mode["dualSidePosition"] + ) + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_change_multi_assets_mode(futuresClientAsync): + await futuresClientAsync.futures_change_multi_assets_mode() + + +async def test_futures_get_multi_assets_mode(futuresClientAsync): + await futuresClientAsync.futures_get_multi_assets_mode() + + +async def test_futures_stream_get_listen_key(futuresClientAsync): + await futuresClientAsync.futures_stream_get_listen_key() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_stream_close(futuresClientAsync): + await futuresClientAsync.futures_stream_close() + + +# new methods +async def test_futures_account_config(futuresClientAsync): + await futuresClientAsync.futures_account_config() + + +async def test_futures_symbol_config(futuresClientAsync): + await futuresClientAsync.futures_symbol_config() + + +# COIN Futures API +async def test_futures_coin_ping(futuresClientAsync): + await futuresClientAsync.futures_coin_ping() + + +async def test_futures_coin_time(futuresClientAsync): + await futuresClientAsync.futures_coin_time() + + +async def test_futures_coin_exchange_info(futuresClientAsync): + await futuresClientAsync.futures_coin_exchange_info() + + +async def test_futures_coin_order_book(futuresClientAsync): + order_book = await futuresClientAsync.futures_coin_order_book(symbol="BTCUSD_PERP") + assert_ob(order_book) + + +async def test_futures_coin_recent_trades(futuresClientAsync): + await futuresClientAsync.futures_coin_recent_trades(symbol="BTCUSD_PERP") + + +async def test_futures_coin_historical_trades(futuresClientAsync): + await futuresClientAsync.futures_coin_historical_trades(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_aggregate_trades(futuresClientAsync): + await futuresClientAsync.futures_coin_aggregate_trades(symbol="BTCUSD_PERP") + + +async def test_futures_coin_klines(futuresClientAsync): + await futuresClientAsync.futures_coin_klines(symbol="BTCUSD_PERP", interval="1h") + + +async def test_futures_coin_continous_klines(futuresClientAsync): + await futuresClientAsync.futures_coin_continous_klines( + pair="BTCUSD", contractType="PERPETUAL", interval="1h" + ) + + +async def test_futures_coin_index_price_klines(futuresClientAsync): + await futuresClientAsync.futures_coin_index_price_klines( + pair="BTCUSD", interval="1h" + ) + + +async def test_futures_coin_mark_price_klines(futuresClientAsync): + await futuresClientAsync.futures_coin_mark_price_klines( + symbol="BTCUSD_PERP", interval="1h" + ) + + +async def test_futures_coin_mark_price(futuresClientAsync): + await futuresClientAsync.futures_coin_mark_price() + + +async def test_futures_coin_funding_rate(futuresClientAsync): + await futuresClientAsync.futures_coin_funding_rate(symbol="BTCUSD_PERP") + + +async def test_futures_coin_ticker(futuresClientAsync): + await futuresClientAsync.futures_coin_ticker() + + +async def test_futures_coin_symbol_ticker(futuresClientAsync): + await futuresClientAsync.futures_coin_symbol_ticker() + + +async def test_futures_coin_orderbook_ticker(futuresClientAsync): + await futuresClientAsync.futures_coin_orderbook_ticker() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_liquidation_orders(futuresClientAsync): + await futuresClientAsync.futures_coin_liquidation_orders() + + +async def test_futures_coin_open_interest(futuresClientAsync): + await futuresClientAsync.futures_coin_open_interest(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_open_interest_hist(futuresClientAsync): + await futuresClientAsync.futures_coin_open_interest_hist(symbol="BTCUSD_PERP") + + +async def test_futures_coin_leverage_bracket(futuresClientAsync): + await futuresClientAsync.futures_coin_leverage_bracket() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_create_order(futuresClientAsync): + positions = await futuresClientAsync.futures_coin_position_information() + ticker = await futuresClientAsync.futures_coin_ticker(symbol=positions[0]["symbol"]) + order = await futuresClientAsync.futures_coin_create_order( + symbol=positions[0]["symbol"], + side="BUY", + type="LIMIT", + timeInForce="GTC", + quantity=1, + price=str(round(float(ticker[0]["lastPrice"]) - 1, 0)), + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_modify_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_get_order( + symbol=order["symbol"], orderid=order["orderId"] + ) + assert_contract_order(futuresClientAsync, order) + order = await futuresClientAsync.futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_place_batch_order(futuresClientAsync): + await futuresClientAsync.futures_coin_place_batch_order() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_get_order(futuresClientAsync): + await futuresClientAsync.futures_coin_get_order() + + +async def test_futures_coin_get_open_orders(futuresClientAsync): + await futuresClientAsync.futures_coin_get_open_orders() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_get_all_orders(futuresClientAsync): + await futuresClientAsync.futures_coin_get_all_orders() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_cancel_order(futuresClientAsync): + await futuresClientAsync.futures_coin_cancel_order() + + +async def test_futures_coin_cancel_all_open_orders(futuresClientAsync): + await futuresClientAsync.futures_coin_cancel_all_open_orders(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_cancel_orders(futuresClientAsync): + await futuresClientAsync.futures_coin_cancel_orders() + + +async def test_futures_coin_account_balance(futuresClientAsync): + await futuresClientAsync.futures_coin_account_balance() + + +async def test_futures_coin_account(futuresClientAsync): + await futuresClientAsync.futures_coin_account() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_change_leverage(futuresClientAsync): + await futuresClientAsync.futures_coin_change_leverage(symbol="XRPUSDT", leverage=10) + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_change_margin_type(futuresClientAsync): + await futuresClientAsync.futures_coin_change_margin_type() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_change_position_margin(futuresClientAsync): + await futuresClientAsync.futures_coin_change_position_margin() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_position_margin_history(futuresClientAsync): + await futuresClientAsync.futures_coin_position_margin_history() + + +async def test_futures_coin_position_information(futuresClientAsync): + await futuresClientAsync.futures_coin_position_information() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_account_trades(futuresClientAsync): + await futuresClientAsync.futures_coin_account_trades() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_income_history(futuresClientAsync): + await futuresClientAsync.futures_coin_income_history() + + +@pytest.mark.skip(reason="Not implemented") +async def test_futures_coin_change_position_mode(futuresClientAsync): + await futuresClientAsync.futures_coin_change_position_mode() + + +async def test_futures_coin_get_position_mode(futuresClientAsync): + await futuresClientAsync.futures_coin_get_position_mode() + + +async def test_futures_coin_stream_close(futuresClientAsync): + listen_key = await futuresClientAsync.futures_coin_stream_get_listen_key() + await futuresClientAsync.futures_coin_stream_close(listenKey=listen_key) diff --git a/tests/test_async_client_gift_card copy.py b/tests/test_async_client_gift_card copy.py new file mode 100644 index 000000000..4b76b929f --- /dev/null +++ b/tests/test_async_client_gift_card copy.py @@ -0,0 +1,46 @@ +import pytest + +pytestmark = [pytest.mark.gift_card, pytest.mark.asyncio] + + +async def test_gift_card_fetch_token_limit(liveClientAsync): + await liveClientAsync.gift_card_fetch_token_limit(baseToken="BUSD") + + +async def test_gift_card_fetch_rsa_public_key(liveClientAsync): + await liveClientAsync.gift_card_fetch_rsa_public_key() + + +async def test_gift_card_create_verify_and_redeem(liveClientAsync): + # create a gift card + response = await liveClientAsync.gift_card_create(token="USDT", amount=1.0) + assert response["data"]["referenceNo"] is not None + assert response["data"]["code"] is not None + # verify the gift card + response = await liveClientAsync.gift_card_verify( + referenceNo=response["data"]["referenceNo"] + ) + assert response["data"]["valid"] == "SUCCESS" + # redeem the gift card + redeem_response = await liveClientAsync.gift_card_redeem( + code=response["data"]["code"], + ) + assert response["data"]["referenceNo"] == redeem_response["data"]["referenceNo"] + + +async def test_gift_card_create_dual_token_and_redeem(liveClientAsync): + response = await liveClientAsync.gift_card_create_dual_token( + baseToken="USDT", faceToken="BNB", baseTokenAmount=1.0 + ) + assert response["data"]["referenceNo"] is not None + assert response["data"]["code"] is not None + # verify the gift card + response = await liveClientAsync.gift_card_verify( + referenceNo=response["data"]["referenceNo"] + ) + assert response["data"]["valid"] == "SUCCESS" + # redeem the gift card + redeem_response = await liveClientAsync.gift_card_redeem( + code=response["data"]["code"], + ) + assert response["data"]["referenceNo"] == redeem_response["data"]["referenceNo"] diff --git a/tests/test_async_client_margin.py b/tests/test_async_client_margin.py new file mode 100644 index 000000000..38019bc41 --- /dev/null +++ b/tests/test_async_client_margin.py @@ -0,0 +1,562 @@ +import pytest + + +pytestmark = [pytest.mark.margin, pytest.mark.asyncio] + + +async def test_margin__get_account_status(asyncClient): + await asyncClient.get_account_status() + + +async def test_margin_get_account_api_trading_status(asyncClient): + await asyncClient.get_account_api_trading_status() + + +async def test_margin_get_account_api_permissions(asyncClient): + await asyncClient.get_account_api_permissions() + + +async def test_margin_get_dust_assets(asyncClient): + await asyncClient.get_dust_assets() + + +async def test_margin_get_dust_log(asyncClient): + await asyncClient.test_get_dust_log() + + +async def test_margin_transfer_dust(asyncClient): + await asyncClient.transfer_dust() + + +async def test_margin_get_asset_dividend_history(asyncClient): + await asyncClient.get_asset_dividend_history() + + +async def test_margin_make_universal_transfer(asyncClient): + await asyncClient.make_universal_transfer() + + +async def test_margin_query_universal_transfer_history(asyncClient): + await asyncClient.query_universal_transfer_history() + + +async def test_margin_get_trade_fee(asyncClient): + await asyncClient.get_trade_fee() + + +async def test_margin_get_asset_details(asyncClient): + await asyncClient.get_asset_details() + + +async def test_margin_get_spot_delist_schedule(asyncClient): + await asyncClient.get_spot_delist_schedule() + + +# Withdraw Endpoints + + +async def test_margin_withdraw(asyncClient): + await asyncClient.withdraw() + + +async def test_margin_get_deposit_history(asyncClient): + await asyncClient.get_deposit_history() + + +async def test_margin_get_withdraw_history(asyncClient): + await asyncClient.get_withdraw_history() + + +async def test_margin_get_withdraw_history_id(asyncClient): + await asyncClient.get_withdraw_history_id() + + +async def test_margin_get_deposit_address(asyncClient): + await asyncClient.get_deposit_address() + + +# Margin Trading Endpoints + + +async def test_margin_get_margin_account(asyncClient): + await asyncClient.get_margin_account() + + +async def test_margin_get_isolated_margin_account(asyncClient): + await asyncClient.get_isolated_margin_account() + + +async def test_margin_enable_isolated_margin_account(asyncClient): + await asyncClient.enable_isolated_margin_account() + + +async def test_margin_disable_isolated_margin_account(asyncClient): + await asyncClient.disable_isolated_margin_account() + + +async def test_margin_get_enabled_isolated_margin_account_limit(asyncClient): + await asyncClient.get_enabled_isolated_margin_account_limit() + + +async def test_margin_get_margin_dustlog(asyncClient): + await asyncClient.get_margin_dustlog() + + +async def test_margin_get_margin_dust_assets(asyncClient): + await asyncClient.get_margin_dust_assets() + + +async def test_margin_transfer_margin_dust(asyncClient): + await asyncClient.transfer_margin_dust() + + +async def test_margin_get_cross_margin_collateral_ratio(asyncClient): + await asyncClient.get_cross_margin_collateral_ratio() + + +async def test_margin_get_small_liability_exchange_assets(asyncClient): + await asyncClient.get_small_liability_exchange_assets() + + +async def test_margin_exchange_small_liability_assets(asyncClient): + await asyncClient.exchange_small_liability_assets() + + +async def test_margin_get_small_liability_exchange_history(asyncClient): + await asyncClient.get_small_liability_exchange_history() + + +async def test_margin_get_future_hourly_interest_rate(asyncClient): + await asyncClient.get_future_hourly_interest_rate() + + +async def test_margin_get_margin_capital_flow(asyncClient): + await asyncClient.get_margin_capital_flow() + + +async def test_margin_get_margin_asset(asyncClient): + await asyncClient.get_margin_asset() + + +async def test_margin_get_margin_symbol(asyncClient): + await asyncClient.get_margin_symbol() + + +async def test_margin_get_margin_all_assets(asyncClient): + await asyncClient.get_margin_all_assets() + + +async def test_margin_get_margin_all_pairs(asyncClient): + await asyncClient.get_margin_all_pairs() + + +async def test_margin_create_isolated_margin_account(asyncClient): + await asyncClient.create_isolated_margin_account() + + +async def test_margin_get_isolated_margin_symbol(asyncClient): + await asyncClient.get_isolated_margin_symbol() + + +async def test_margin_get_all_isolated_margin_symbols(asyncClient): + await asyncClient.get_all_isolated_margin_symbols() + + +async def test_margin_get_isolated_margin_fee_data(asyncClient): + await asyncClient.get_isolated_margin_fee_data() + + +async def test_margin_get_isolated_margin_tier_data(asyncClient): + await asyncClient.get_isolated_margin_tier_data() + + +async def test_margin_margin_manual_liquidation(asyncClient): + await asyncClient.margin_manual_liquidation() + + +async def test_margin_toggle_bnb_burn_spot_margin(asyncClient): + await asyncClient.toggle_bnb_burn_spot_margin() + + +async def test_margin_get_bnb_burn_spot_margin(asyncClient): + await asyncClient.get_bnb_burn_spot_margin() + + +async def test_margin_get_margin_price_index(asyncClient): + await asyncClient.get_margin_price_index() + + +async def test_margin_transfer_margin_to_spot(asyncClient): + await asyncClient.transfer_margin_to_spot() + + +async def test_margin_transfer_spot_to_margin(asyncClient): + await asyncClient.transfer_spot_to_margin() + + +async def test_margin_transfer_isolated_margin_to_spot(asyncClient): + await asyncClient.transfer_isolated_margin_to_spot() + + +async def test_margin_transfer_spot_to_isolated_margin(asyncClient): + await asyncClient.transfer_spot_to_isolated_margin() + + +async def test_margin_get_isolated_margin_tranfer_history(asyncClient): + await asyncClient.get_isolated_margin_tranfer_history() + + +async def test_margin_create_margin_loan(asyncClient): + await asyncClient.create_margin_loan() + + +async def test_margin_repay_margin_loan(asyncClient): + await asyncClient.repay_margin_loan() + + +async def create_margin_ordertest_(asyncClient): + await asyncClient.create_margin_order() + + +async def test_margin_cancel_margin_order(asyncClient): + await asyncClient.cancel_margin_order() + + +async def test_margin_set_margin_max_leverage(asyncClient): + await asyncClient.set_margin_max_leverage() + + +async def test_margin_get_margin_transfer_history(asyncClient): + await asyncClient.get_margin_transfer_history() + + +async def test_margin_get_margin_loan_details(asyncClient): + await asyncClient.get_margin_loan_details() + + +async def test_margin_get_margin_repay_details(asyncClient): + await asyncClient.get_margin_repay_details() + + +async def test_margin_get_cross_margin_data(asyncClient): + await asyncClient.get_cross_margin_data() + + +async def test_margin_get_margin_interest_history(asyncClient): + await asyncClient.get_margin_interest_history() + + +async def test_margin_get_margin_force_liquidation_rec(asyncClient): + await asyncClient.get_margin_force_liquidation_rec() + + +async def test_margin_get_margin_order(asyncClient): + await asyncClient.get_margin_order() + + +async def test_margin_get_open_margin_orders(asyncClient): + await asyncClient.get_open_margin_orders() + + +async def test_margin_get_all_margin_orders(asyncClient): + await asyncClient.get_all_margin_orders() + + +async def test_margin_get_margin_trades(asyncClient): + await asyncClient.get_margin_trades() + + +async def test_margin_get_max_margin_loan(asyncClient): + await asyncClient.get_max_margin_loan() + + +async def test_margin_get_max_margin_transfer(asyncClient): + await asyncClient.get_max_margin_transfer() + + +async def test_margin_get_margin_delist_schedule(asyncClient): + await asyncClient.get_margin_delist_schedule() + + +# Margin OCO + + +async def test_margin_create_margin_oco_order(asyncClient): + await asyncClient.create_margin_oco_order() + + +async def test_margin_cancel_margin_oco_order(asyncClient): + await asyncClient.cancel_margin_oco_order() + + +async def test_margin_get_margin_oco_order(asyncClient): + await asyncClient.get_margin_oco_order() + + +async def test_margin_get_open_margin_oco_orders(asyncClient): + await asyncClient.get_open_margin_oco_orders() + + +# Cross-margin + + +async def test_margin_margin_stream_get_listen_key(asyncClient): + await asyncClient.margin_stream_get_listen_key() + + +async def test_margin_margin_stream_close(asyncClient): + await asyncClient.margin_stream_close() + + +# Isolated margin + + +async def test_margin_isolated_margin_stream_get_listen_key(asyncClient): + await asyncClient.isolated_margin_stream_get_listen_key() + + +async def test_margin_isolated_margin_stream_close(asyncClient): + await asyncClient.isolated_margin_stream_close() + + +# Simple Earn Endpoints + + +async def test_margin_get_simple_earn_flexible_product_list(asyncClient): + await asyncClient.get_simple_earn_flexible_product_list() + + +async def test_margin_get_simple_earn_locked_product_list(asyncClient): + await asyncClient.get_simple_earn_locked_product_list() + + +async def test_margin_subscribe_simple_earn_flexible_product(asyncClient): + await asyncClient.subscribe_simple_earn_flexible_product() + + +async def test_margin_subscribe_simple_earn_locked_product(asyncClient): + await asyncClient.subscribe_simple_earn_locked_product() + + +async def test_margin_redeem_simple_earn_flexible_product(asyncClient): + await asyncClient.redeem_simple_earn_flexible_product() + + +async def test_margin_redeem_simple_earn_locked_product(asyncClient): + await asyncClient.redeem_simple_earn_locked_product() + + +async def test_margin_get_simple_earn_flexible_product_position(asyncClient): + await asyncClient.get_simple_earn_flexible_product_position() + + +async def test_margin_get_simple_earn_locked_product_position(asyncClient): + await asyncClient.get_simple_earn_locked_product_position() + + +async def test_margin_get_simple_earn_account(asyncClient): + await asyncClient.get_simple_earn_account() + + +# Lending Endpoints + + +async def test_margin_get_fixed_activity_project_list(asyncClient): + await asyncClient.get_fixed_activity_project_list() + + +async def test_margin_change_fixed_activity_to_daily_position(asyncClient): + await asyncClient.change_fixed_activity_to_daily_position() + + +# Staking Endpoints + + +async def test_margin_get_staking_product_list(asyncClient): + await asyncClient.get_staking_product_list() + + +async def test_margin_purchase_staking_product(asyncClient): + await asyncClient.purchase_staking_product() + + +async def test_margin_redeem_staking_product(asyncClient): + await asyncClient.redeem_staking_product() + + +async def test_margin_get_staking_position(asyncClient): + await asyncClient.get_staking_position() + + +async def test_margin_get_staking_purchase_history(asyncClient): + await asyncClient.get_staking_purchase_history() + + +async def test_margin_set_auto_staking(asyncClient): + await asyncClient.set_auto_staking() + + +async def test_margin_get_personal_left_quota(asyncClient): + await asyncClient.get_personal_left_quota() + + +# US Staking Endpoints + + +async def test_margin_get_staking_asset_us(asyncClient): + await asyncClient.get_staking_asset_us() + + +async def test_margin_stake_asset_us(asyncClient): + await asyncClient.stake_asset_us() + + +async def test_margin_unstake_asset_us(asyncClient): + await asyncClient.unstake_asset_us() + + +async def test_margin_get_staking_balance_us(asyncClient): + await asyncClient.get_staking_balance_us() + + +async def test_margin_get_staking_history_us(asyncClient): + await asyncClient.get_staking_history_us() + + +async def test_margin_get_staking_rewards_history_us(asyncClient): + await asyncClient.get_staking_rewards_history_us() + + +# Sub Accounts + + +async def test_margin_get_sub_account_list(asyncClient): + await asyncClient.get_sub_account_list() + + +async def test_margin_get_sub_account_transfer_history(asyncClient): + await asyncClient.get_sub_account_transfer_history() + + +async def test_margin_get_sub_account_futures_transfer_history(asyncClient): + await asyncClient.get_sub_account_futures_transfer_history() + + +async def test_margin_create_sub_account_futures_transfer(asyncClient): + await asyncClient.create_sub_account_futures_transfer() + + +async def test_margin_get_sub_account_assets(asyncClient): + await asyncClient.get_sub_account_assets() + + +async def test_margin_query_subaccount_spot_summary(asyncClient): + await asyncClient.query_subaccount_spot_summary() + + +async def test_margin_get_subaccount_deposit_address(asyncClient): + await asyncClient.get_subaccount_deposit_address() + + +async def test_margin_get_subaccount_deposit_history(asyncClient): + await asyncClient.get_subaccount_deposit_history() + + +async def test_margin_get_subaccount_futures_margin_status(asyncClient): + await asyncClient.get_subaccount_futures_margin_status() + + +async def test_margin_enable_subaccount_margin(asyncClient): + await asyncClient.enable_subaccount_margin() + + +async def test_margin_get_subaccount_margin_details(asyncClient): + await asyncClient.get_subaccount_margin_details() + + +async def test_margin_get_subaccount_margin_summary(asyncClient): + await asyncClient.get_subaccount_margin_summary() + + +async def test_margin_enable_subaccount_futures(asyncClient): + await asyncClient.enable_subaccount_futures() + + +async def test_margin_get_subaccount_futures_details(asyncClient): + await asyncClient.get_subaccount_futures_details() + + +async def test_margin_get_subaccount_futures_summary(asyncClient): + await asyncClient.get_subaccount_futures_summary() + + +async def test_margin_get_subaccount_futures_positionrisk(asyncClient): + await asyncClient.get_subaccount_futures_positionrisk() + + +async def test_margin_make_subaccount_futures_transfer(asyncClient): + await asyncClient.make_subaccount_futures_transfer() + + +async def test_margin_make_subaccount_margin_transfer(asyncClient): + await asyncClient.make_subaccount_margin_transfer() + + +async def test_margin_make_subaccount_to_subaccount_transfer(asyncClient): + await asyncClient.make_subaccount_to_subaccount_transfer() + + +async def test_margin_make_subaccount_to_master_transfer(asyncClient): + await asyncClient.make_subaccount_to_master_transfer() + + +async def test_margin_get_subaccount_transfer_history(asyncClient): + await asyncClient.get_subaccount_transfer_history() + + +async def test_margin_make_subaccount_universal_transfer(asyncClient): + await asyncClient.make_subaccount_universal_transfer() + + +async def test_margin_get_universal_transfer_history(asyncClient): + await asyncClient.get_universal_transfer_history() + + +# Fiat Endpoints + + +async def test_margin_get_fiat_deposit_withdraw_history(asyncClient): + await asyncClient.get_fiat_deposit_withdraw_history() + + +async def test_margin_get_fiat_payments_history(asyncClient): + await asyncClient.get_fiat_payments_history() + + +# C2C Endpoints + + +async def test_margin_get_c2c_trade_history(asyncClient): + await asyncClient.get_c2c_trade_history() + + +# Pay Endpoints + + +async def test_margin_get_pay_trade_history(asyncClient): + await asyncClient.get_pay_trade_history() + + +# Convert Endpoints + + +async def test_margin_get_convert_trade_history(asyncClient): + await asyncClient.get_convert_trade_history() + + +async def test_margin_convert_request_quote(asyncClient): + await asyncClient.convert_request_quote() + + +async def test_margin_convert_accept_quote(asyncClient): + await asyncClient.convert_accept_quote() diff --git a/tests/test_async_client_options.py b/tests/test_async_client_options.py new file mode 100644 index 000000000..d78287ebb --- /dev/null +++ b/tests/test_async_client_options.py @@ -0,0 +1,123 @@ +import pytest + + +pytestmark = [pytest.mark.options, pytest.mark.asyncio] + + +@pytest.fixture +def options_symbol(liveClient): + prices = liveClient.options_price() + return prices[0]["symbol"] + + +async def test_options_ping(liveClientAsync): + await liveClientAsync.options_ping() + + +async def test_options_time(liveClientAsync): + await liveClientAsync.options_time() + + +@pytest.mark.skip(reason="Not implemented") +async def test_options_info(liveClientAsync): + await liveClientAsync.options_info() + + +async def test_options_exchange_info(liveClientAsync): + await liveClientAsync.options_exchange_info() + + +async def test_options_index_price(liveClientAsync): + await liveClientAsync.options_index_price(underlying="BTCUSDT") + + +async def test_options_price(liveClientAsync): + prices = await liveClientAsync.options_price() + + +async def test_options_mark_price(liveClientAsync): + await liveClientAsync.options_mark_price() + + +async def test_options_order_book(liveClientAsync, options_symbol): + await liveClientAsync.options_order_book(symbol=options_symbol) + + +async def test_options_klines(liveClientAsync, options_symbol): + await liveClientAsync.options_klines(symbol=options_symbol, interval="1m") + + +async def test_options_recent_trades(liveClientAsync, options_symbol): + await liveClientAsync.options_recent_trades(symbol=options_symbol) + + +async def test_options_historical_trades(liveClientAsync, options_symbol): + await liveClientAsync.options_historical_trades(symbol=options_symbol) + + +# Account and trading interface endpoints + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_account_info(liveClientAsync): + await liveClientAsync.options_account_info() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_funds_transfer(liveClientAsync): + await liveClientAsync.options_funds_transfer() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_positions(liveClientAsync): + await liveClientAsync.options_positions() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_bill(liveClientAsync): + await liveClientAsync.options_bill() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_place_order(liveClientAsync): + await liveClientAsync.options_place_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_test_options_place_batch_order(liveClientAsync): + await liveClientAsync.test_options_place_batch_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_cancel_order(liveClientAsync): + await liveClientAsync.options_cancel_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_cancel_batch_order(liveClientAsync): + await liveClientAsync.options_cancel_batch_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_cancel_all_orders(liveClientAsync): + await liveClientAsync.options_cancel_all_orders() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_query_order(liveClientAsync): + await liveClientAsync.options_query_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_query_pending_orders(liveClientAsync): + await liveClientAsync.options_query_pending_orders() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_query_order_history(liveClientAsync): + await liveClientAsync.options_query_order_history() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +async def test_options_user_trades(liveClientAsync): + await liveClientAsync.options_user_trades() diff --git a/tests/test_async_client_portfolio.py b/tests/test_async_client_portfolio.py new file mode 100644 index 000000000..824db17a0 --- /dev/null +++ b/tests/test_async_client_portfolio.py @@ -0,0 +1,398 @@ +import pytest + +# Apply the 'portfolio' mark to all tests in this file +pytestmark = [pytest.mark.portfolio, pytest.mark.asyncio] + + +async def test_papi_get_balance(client): + await client.papi_get_balance() + + +async def test_papi_get_account(client): + await client.papi_get_account() + + +async def test_papi_get_margin_max_borrowable(client): + await client.papi_get_margin_max_borrowable() + + +async def test_papi_get_margin_max_withdraw(client): + await client.papi_get_margin_max_withdraw() + + +async def test_papi_get_um_position_risk(client): + await client.papi_get_um_position_risk() + + +async def test_papi_get_cm_position_risk(client): + await client.papi_get_cm_position_risk() + + +async def test_papi_set_um_leverage(client): + await client.papi_set_um_leverage() + + +async def test_papi_set_cm_leverage(client): + await client.papi_set_cm_leverage() + + +async def test_papi_change_um_position_side_dual(client): + await client.papi_change_um_position_side_dual() + + +async def test_papi_get_um_position_side_dual(client): + await client.papi_get_um_position_side_dual() + + +async def test_papi_get_cm_position_side_dual(client): + await client.papi_get_cm_position_side_dual() + + +async def test_papi_get_um_leverage_bracket(client): + await client.papi_get_um_leverage_bracket() + + +async def test_papi_get_cm_leverage_bracket(client): + await client.papi_get_cm_leverage_bracket() + + +async def test_papi_get_um_api_trading_status(client): + await client.papi_get_um_api_trading_status() + + +async def test_papi_get_um_comission_rate(client): + await client.papi_get_um_comission_rate() + + +async def test_papi_get_cm_comission_rate(client): + await client.papi_get_cm_comission_rate() + + +async def test_papi_get_margin_margin_loan(client): + await client.papi_get_margin_margin_loan() + + +async def test_papi_get_margin_repay_loan(client): + await client.papi_get_margin_repay_loan() + + +async def test_papi_get_repay_futures_switch(client): + await client.papi_get_repay_futures_switch() + + +async def test_papi_repay_futures_switch(client): + await client.papi_repay_futures_switch() + + +async def test_papi_get_margin_interest_history(client): + await client.papi_get_margin_interest_history() + + +async def test_papi_repay_futures_negative_balance(client): + await client.papi_repay_futures_negative_balance() + + +async def test_papi_get_portfolio_interest_history(client): + await client.papi_get_portfolio_interest_history() + + +async def test_papi_fund_auto_collection(client): + await client.papi_fund_auto_collection() + + +async def test_papi_fund_asset_collection(client): + await client.papi_fund_asset_collection() + + +async def test_papi_bnb_transfer(client): + await client.papi_bnb_transfer() + + +async def test_papi_get_um_income_history(client): + await client.papi_get_um_income_history() + + +async def test_papi_get_cm_income_history(client): + await client.papi_get_cm_income_history() + + +async def test_papi_get_um_account(client): + await client.papi_get_um_account() + + +async def test_papi_get_um_account_v2(client): + await client.papi_get_um_account_v2() + + +async def test_papi_get_cm_account(client): + await client.papi_get_cm_account() + + +async def test_papi_get_um_account_config(client): + await client.papi_get_um_account_config() + + +async def test_papi_get_um_symbol_config(client): + await client.papi_get_um_symbol_config() + + +async def test_papi_get_um_trade_asyn(client): + await client.papi_get_um_trade_asyn() + + +async def test_papi_get_um_trade_asyn_id(client): + await client.papi_get_um_trade_asyn_id() + + +async def test_papi_get_um_order_asyn(client): + await client.papi_get_um_order_asyn() + + +async def test_papi_get_um_order_asyn_id(client): + await client.papi_get_um_order_asyn_id() + + +async def test_papi_get_um_income_asyn(client): + await client.papi_get_um_income_asyn() + + +async def test_papi_get_um_income_asyn_id(client): + await client.papi_get_um_income_asyn_id() + + +# Public papi endpoints + + +async def test_papi_ping(client): + await client.papi_ping() + + +# Trade papi endpoints + + +async def test_papi_create_um_order(client): + await client.papi_create_um_order() + + +async def test_papi_create_um_conditional_order(client): + await client.papi_create_um_conditional_order() + + +async def test_papi_create_cm_order(client): + await client.papi_create_cm_order() + + +async def test_papi_create_cm_conditional_order(client): + await client.papi_create_cm_conditional_order() + + +async def test_papi_create_margin_order(client): + await client.papi_create_margin_order() + + +async def test_papi_margin_loan(client): + await client.papi_margin_loan() + + +async def test_papi_repay_loan(client): + await client.papi_repay_loan() + + +async def test_papi_margin_order_oco(client): + await client.papi_margin_order_oco() + + +async def test_papi_cancel_um_order(client): + await client.papi_cancel_um_order() + + +async def test_papi_cancel_um_all_open_orders(client): + await client.papi_cancel_um_all_open_orders() + + +async def test_papi_cancel_um_conditional_order(client): + await client.papi_cancel_um_conditional_order() + + +async def test_papi_cancel_um_conditional_all_open_orders(client): + await client.papi_cancel_um_conditional_all_open_orders() + + +async def test_papi_cancel_cm_order(client): + await client.papi_cancel_cm_order() + + +async def test_papi_cancel_cm_all_open_orders(client): + await client.papi_cancel_cm_all_open_orders() + + +async def test_papi_cancel_cm_conditional_order(client): + await client.papi_cancel_cm_conditional_order() + + +async def test_papi_cancel_cm_conditional_all_open_orders(client): + await client.papi_cancel_cm_conditional_all_open_orders() + + +async def test_papi_cancel_margin_order(client): + await client.papi_cancel_margin_order() + + +async def test_papi_cancel_margin_order_list(client): + await client.papi_cancel_margin_order_list() + + +async def test_papi_cancel_margin_all_open_orders(client): + await client.papi_cancel_margin_all_open_orders() + + +async def test_papi_modify_um_order(client): + await client.papi_modify_um_order() + + +async def test_papi_modify_cm_order(client): + await client.papi_modify_cm_order() + + +async def test_papi_get_um_order(client): + await client.papi_get_um_order() + + +async def test_papi_get_um_all_orders(client): + await client.papi_get_um_all_orders() + + +async def test_papi_get_um_open_order(client): + await client.papi_get_um_open_order() + + +async def test_papi_get_um_open_orders(client): + await client.papi_get_um_open_orders() + + +async def test_papi_get_um_conditional_all_orders(client): + await client.papi_get_um_conditional_all_orders() + + +async def test_papi_get_um_conditional_open_orders(client): + await client.papi_get_um_conditional_open_orders() + + +async def test_papi_get_um_conditional_open_order(client): + await client.papi_get_um_conditional_open_order() + + +async def test_papi_get_um_conditional_order_history(client): + await client.papi_get_um_conditional_order_history() + + +async def test_papi_get_cm_order(client): + await client.papi_get_cm_order() + + +async def test_papi_get_cm_all_orders(client): + await client.papi_get_cm_all_orders() + + +async def test_papi_get_cm_open_order(client): + await client.papi_get_cm_open_order() + + +async def test_papi_get_cm_open_orders(client): + await client.papi_get_cm_open_orders() + + +async def test_papi_get_cm_conditional_all_orders(client): + await client.papi_get_cm_conditional_all_orders() + + +async def test_papi_get_cm_conditional_open_orders(client): + await client.papi_get_cm_conditional_open_orders() + + +async def test_papi_get_cm_conditional_open_order(client): + await client.papi_get_cm_conditional_open_order() + + +async def test_papi_get_cm_conditional_order_history(client): + await client.papi_get_cm_conditional_order_history() + + +async def test_papi_get_um_force_orders(client): + await client.papi_get_um_force_orders() + + +async def test_papi_get_cm_force_orders(client): + await client.papi_get_cm_force_orders() + + +async def test_papi_get_um_order_amendment(client): + await client.papi_get_um_order_amendment() + + +async def test_papi_get_cm_order_amendment(client): + await client.papi_get_cm_order_amendment() + + +async def test_papi_get_margin_force_orders(client): + await client.papi_get_margin_force_orders() + + +async def test_papi_get_um_user_trades(client): + await client.papi_get_um_user_trades() + + +async def test_papi_get_cm_user_trades(client): + await client.papi_get_cm_user_trades() + + +async def test_papi_get_um_adl_quantile(client): + await client.papi_get_um_adl_quantile() + + +async def test_papi_get_cm_adl_quantile(client): + await client.papi_get_cm_adl_quantile() + + +async def test_papi_set_um_fee_burn(client): + await client.papi_set_um_fee_burn() + + +async def test_papi_get_um_fee_burn(client): + await client.papi_get_um_fee_burn() + + +async def test_papi_get_margin_order(client): + await client.papi_get_margin_order() + + +async def test_papi_get_margin_open_orders(client): + await client.papi_get_margin_open_orders() + + +async def test_papi_get_margin_all_orders(client): + await client.papi_get_margin_all_orders() + + +async def test_papi_get_margin_order_list(client): + await client.papi_get_margin_order_list() + + +async def test_papi_get_margin_all_order_list(client): + await client.papi_get_margin_all_order_list() + + +async def test_papi_get_margin_open_order_list(client): + await client.papi_get_margin_open_order_list() + + +async def test_papi_get_margin_my_trades(client): + await client.papi_get_margin_my_trades() + + +async def test_papi_get_margin_repay_debt(client): + await client.papi_get_margin_repay_debt() + + +async def test_close_connection(client): + await client.close_connection() diff --git a/tests/test_client.py b/tests/test_client.py index e329e9d63..281060ec4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,11 +1,14 @@ +import pytest + + def test_client_initialization(client): 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() +@pytest.mark.skip(reason="Endpoint not documented") +def test_get_products(client): + client.get_products() def test_get_exchange_info(client): @@ -76,6 +79,10 @@ def test_get_asset_balance(client): client.get_asset_balance(asset="BTC") +def test_get_asset_balance_no_asset_provided(client): + client.get_asset_balance() + + def test_get_my_trades(client): client.get_my_trades(symbol="BTCUSDT") @@ -84,21 +91,33 @@ 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() +# User Stream Endpoints + + +def test_stream_get_listen_key_and_close(client): + listen_key = client.stream_get_listen_key() + client.stream_close(listen_key) + + +# Quoting interface endpoints +@pytest.mark.skip(reason="Endpoint 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() +@pytest.mark.skip(reason="Endpoint not working on testnet") +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() +@pytest.mark.skip(reason="Endpoint not working on testnet") +def test_get_account_api_permissions(client): + client.get_account_api_permissions() -# def test_get_dust_assets(client): -# client.get_dust_assets() +@pytest.mark.skip(reason="Endpoint not working on testnet") +def test_get_dust_assets(client): + client.get_dust_assets() ######################### diff --git a/tests/test_client_futures.py b/tests/test_client_futures.py new file mode 100644 index 000000000..b4a8b8e1f --- /dev/null +++ b/tests/test_client_futures.py @@ -0,0 +1,569 @@ +from datetime import datetime + +import pytest +from .test_order import assert_contract_order +from .test_get_order_book import assert_ob + + +def test_futures_ping(futuresClient): + futuresClient.futures_ping() + + +def test_futures_time(futuresClient): + futuresClient.futures_time() + + +def test_futures_exchange_info(futuresClient): + futuresClient.futures_exchange_info() + + +def test_futures_order_book(futuresClient): + order_book = futuresClient.futures_order_book(symbol="BTCUSDT") + assert_ob(order_book) + + +def test_futures_recent_trades(futuresClient): + futuresClient.futures_recent_trades(symbol="BTCUSDT") + + +def test_futures_historical_trades(futuresClient): + futuresClient.futures_historical_trades(symbol="BTCUSDT") + + +def test_futures_aggregate_trades(futuresClient): + futuresClient.futures_aggregate_trades(symbol="BTCUSDT") + + +def test_futures_klines(futuresClient): + futuresClient.futures_klines(symbol="BTCUSDT", interval="1h") + + +def test_futures_continous_klines(futuresClient): + futuresClient.futures_continous_klines( + pair="BTCUSDT", contractType="PERPETUAL", interval="1h" + ) + + +def test_futures_historical_klines(futuresClient): + futuresClient.futures_historical_klines( + symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") + ) + + +def test_futures_historical_klines_generator(futuresClient): + futuresClient.futures_historical_klines_generator( + symbol="BTCUSDT", interval="1h", start_str=datetime.now().strftime("%Y-%m-%d") + ) + + +def test_futures_mark_price(futuresClient): + futuresClient.futures_mark_price() + + +def test_futures_funding_rate(futuresClient): + futuresClient.futures_funding_rate() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_top_longshort_account_ratio(futuresClient): + futuresClient.futures_top_longshort_account_ratio() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_top_longshort_position_ratio(futuresClient): + futuresClient.futures_top_longshort_position_ratio() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_global_longshort_ratio(futuresClient): + futuresClient.futures_global_longshort_ratio() + + +def test_futures_ticker(futuresClient): + futuresClient.futures_ticker() + + +def test_futures_symbol_ticker(futuresClient): + futuresClient.futures_symbol_ticker() + + +def test_futures_orderbook_ticker(futuresClient): + futuresClient.futures_orderbook_ticker() + + +def test_futures_liquidation_orders(futuresClient): + futuresClient.futures_liquidation_orders() + + +def test_futures_api_trading_status(futuresClient): + futuresClient.futures_api_trading_status() + + +def test_futures_commission_rate(futuresClient): + futuresClient.futures_commission_rate(symbol="BTCUSDT") + + +def test_futures_adl_quantile_estimate(futuresClient): + futuresClient.futures_adl_quantile_estimate() + + +def test_futures_open_interest(futuresClient): + futuresClient.futures_open_interest(symbol="BTCUSDT") + + +def test_futures_index_info(futuresClient): + futuresClient.futures_index_info() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_open_interest_hist(futuresClient): + futuresClient.futures_open_interest_hist(symbol="BTCUSDT") + + +def test_futures_leverage_bracket(futuresClient): + futuresClient.futures_leverage_bracket() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_account_transfer(futuresClient): + futuresClient.futures_account_transfer() + + +@pytest.mark.skip(reason="Not implemented") +def test_transfer_history(client): + client.transfer_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_loan_borrow_history(futuresClient): + futuresClient.futures_loan_borrow_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_loan_repay_history(futuresClient): + futuresClient.futures_loan_repay_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_loan_wallet(futuresClient): + futuresClient.futures_loan_wallet() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_cross_collateral_adjust_history(futuresClient): + futuresClient.futures_cross_collateral_adjust_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_cross_collateral_liquidation_history(futuresClient): + futuresClient.futures_cross_collateral_liquidation_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_loan_interest_history(futuresClient): + futuresClient.futures_loan_interest_history() + + +def test_futures_create_get_edit_cancel_order(futuresClient): + ticker = futuresClient.futures_ticker(symbol="LTCUSDT") + positions = futuresClient.futures_position_information(symbol="LTCUSDT") + order = futuresClient.futures_create_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(round(float(ticker["lastPrice"]) - 1)), + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_modify_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_get_order( + symbol=order["symbol"], orderid=order["orderId"] + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +def test_futures_create_test_order(futuresClient): + ticker = futuresClient.futures_ticker(symbol="LTCUSDT") + positions = futuresClient.futures_position_information(symbol="LTCUSDT") + futuresClient.futures_create_test_order( + symbol=ticker["symbol"], + side="BUY", + positionSide=positions[0]["positionSide"], + type="LIMIT", + timeInForce="GTC", + quantity=0.1, + price=str(round(float(ticker["lastPrice"]) - 1, 0)), + ) + + +def test_futures_place_batch_order_and_cancel(futuresClient): + ticker = futuresClient.futures_ticker(symbol="LTCUSDT") + positions = futuresClient.futures_position_information(symbol="LTCUSDT") + orders = futuresClient.futures_place_batch_order( + batchOrders=[ + { + "symbol": ticker["symbol"], + "side": "BUY", + "positionSide": positions[0]["positionSide"], + "type": "LIMIT", + "timeInForce": "GTC", + "quantity": "0.1", + "price": str(round(float(ticker["lastPrice"]) - 1, 0)), + }, + { + "symbol": ticker["symbol"], + "type": "LIMIT", + "side": "BUY", + "price": str(round(float(ticker["lastPrice"]) - 1, 0)), + "positionSide": positions[0]["positionSide"], + "timeInForce": "GTC", + "quantity": "0.1", + }, + ] + ) + for order in orders: + assert_contract_order(futuresClient, order) + # Cancel using orderidlist + order_ids = [order["orderId"] for order in orders][:1] + cancelled_orders = futuresClient.futures_cancel_orders( + symbol=orders[0]["symbol"], orderidlist=order_ids + ) + for order in cancelled_orders: + assert_contract_order(futuresClient, order) + # Cancel using origClientOrderIdList + client_order_ids = [order["clientOrderId"] for order in orders][1:] + cancelled_orders = futuresClient.futures_cancel_orders( + symbol=orders[0]["symbol"], origclientorderidlist=client_order_ids + ) + for order in cancelled_orders: + assert_contract_order(futuresClient, order) + + +def test_futures_get_open_orders(futuresClient): + futuresClient.futures_get_open_orders() + + +def test_futures_get_all_orders(futuresClient): + orders = futuresClient.futures_get_all_orders() + print(orders) + + +def test_futures_cancel_all_open_orders(futuresClient): + futuresClient.futures_cancel_all_open_orders(symbol="LTCUSDT") + + +def test_futures_countdown_cancel_all(futuresClient): + futuresClient.futures_countdown_cancel_all(symbol="LTCUSDT", countdownTime=10) + + +def test_futures_account_balance(futuresClient): + futuresClient.futures_account_balance() + + +def test_futures_account(futuresClient): + futuresClient.futures_account() + + +def test_futures_change_leverage(futuresClient): + futuresClient.futures_change_leverage(symbol="LTCUSDT", leverage=10) + + +def test_futures_change_margin_type(futuresClient): + try: + futuresClient.futures_change_margin_type(symbol="XRPUSDT", marginType="CROSSED") + except Exception as e: + futuresClient.futures_change_margin_type( + symbol="XRPUSDT", marginType="ISOLATED" + ) + + +def test_futures_position_margin_history(futuresClient): + position = futuresClient.futures_position_margin_history(symbol="LTCUSDT") + print(position) + + +def test_futures_position_information(futuresClient): + futuresClient.futures_position_information() + + +def test_futures_account_trades(futuresClient): + futuresClient.futures_account_trades() + + +def test_futures_income_history(futuresClient): + futuresClient.futures_income_history() + + +def close_all_futures_positions(futuresClient): + # Get all open positions + positions = futuresClient.futures_position_information(symbol="LTCUSDT") + + for position in positions: + # Check if there is an open position + if float(position["positionAmt"]) != 0: + symbol = position["symbol"] + position_amt = float(position["positionAmt"]) + side = "SELL" if position_amt > 0 else "BUY" + + # Place a market order to close the position + try: + print(f"Closing position for {symbol}: {position_amt} units") + futuresClient.futures_create_order( + symbol=symbol, side=side, type="market", quantity=abs(position_amt) + ) + print(f"Position for {symbol} closed successfully.") + except Exception as e: + print(f"Failed to close position for {symbol}: {e}") + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_get_and_change_position_mode(futuresClient): + mode = futuresClient.futures_get_position_mode() + futuresClient.futures_change_position_mode( + dualSidePosition=not mode["dualSidePosition"] + ) + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_change_multi_assets_mode(futuresClient): + futuresClient.futures_change_multi_assets_mode() + + +def test_futures_get_multi_assets_mode(futuresClient): + futuresClient.futures_get_multi_assets_mode() + + +def test_futures_stream_get_listen_key(futuresClient): + futuresClient.futures_stream_get_listen_key() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_stream_close(futuresClient): + futuresClient.futures_stream_close() + + +# new methods +def test_futures_account_config(futuresClient): + futuresClient.futures_account_config() + + +def test_futures_symbol_config(futuresClient): + futuresClient.futures_symbol_config() + + +# COIN Futures API +def test_futures_coin_ping(futuresClient): + futuresClient.futures_coin_ping() + + +def test_futures_coin_time(futuresClient): + futuresClient.futures_coin_time() + + +def test_futures_coin_exchange_info(futuresClient): + futuresClient.futures_coin_exchange_info() + + +def test_futures_coin_order_book(futuresClient): + order_book = futuresClient.futures_coin_order_book(symbol="BTCUSD_PERP") + assert_ob(order_book) + + +def test_futures_coin_recent_trades(futuresClient): + futuresClient.futures_coin_recent_trades(symbol="BTCUSD_PERP") + + +def test_futures_coin_historical_trades(futuresClient): + futuresClient.futures_coin_historical_trades(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_aggregate_trades(futuresClient): + futuresClient.futures_coin_aggregate_trades(symbol="BTCUSD_PERP") + + +def test_futures_coin_klines(futuresClient): + futuresClient.futures_coin_klines(symbol="BTCUSD_PERP", interval="1h") + + +def test_futures_coin_continous_klines(futuresClient): + futuresClient.futures_coin_continous_klines( + pair="BTCUSD", contractType="PERPETUAL", interval="1h" + ) + + +def test_futures_coin_index_price_klines(futuresClient): + futuresClient.futures_coin_index_price_klines(pair="BTCUSD", interval="1h") + + +def test_futures_coin_mark_price_klines(futuresClient): + futuresClient.futures_coin_mark_price_klines(symbol="BTCUSD_PERP", interval="1h") + + +def test_futures_coin_mark_price(futuresClient): + futuresClient.futures_coin_mark_price() + + +def test_futures_coin_funding_rate(futuresClient): + futuresClient.futures_coin_funding_rate(symbol="BTCUSD_PERP") + + +def test_futures_coin_ticker(futuresClient): + futuresClient.futures_coin_ticker() + + +def test_futures_coin_symbol_ticker(futuresClient): + futuresClient.futures_coin_symbol_ticker() + + +def test_futures_coin_orderbook_ticker(futuresClient): + futuresClient.futures_coin_orderbook_ticker() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_liquidation_orders(futuresClient): + futuresClient.futures_coin_liquidation_orders(limit=5) + + +def test_futures_coin_open_interest(futuresClient): + futuresClient.futures_coin_open_interest(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_open_interest_hist(futuresClient): + futuresClient.futures_coin_open_interest_hist(symbol="BTCUSD_PERP") + + +def test_futures_coin_leverage_bracket(futuresClient): + futuresClient.futures_coin_leverage_bracket() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_create_order(futuresClient): + positions = futuresClient.futures_coin_position_information() + ticker = futuresClient.futures_coin_ticker(symbol=positions[0]["symbol"]) + order = futuresClient.futures_coin_create_order( + symbol=positions[0]["symbol"], + side="BUY", + type="LIMIT", + timeInForce="GTC", + quantity=1, + price=str(round(float(ticker[0]["lastPrice"]) - 1, 0)), + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_modify_order( + orderid=order["orderId"], + symbol=order["symbol"], + quantity=0.11, + side=order["side"], + price=order["price"], + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_get_order( + symbol=order["symbol"], orderid=order["orderId"] + ) + assert_contract_order(futuresClient, order) + order = futuresClient.futures_cancel_order( + orderid=order["orderId"], symbol=order["symbol"] + ) + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_place_batch_order(futuresClient): + futuresClient.futures_coin_place_batch_order() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_get_order(futuresClient): + futuresClient.futures_coin_get_order() + + +def test_futures_coin_get_open_orders(futuresClient): + futuresClient.futures_coin_get_open_orders() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_get_all_orders(futuresClient): + futuresClient.futures_coin_get_all_orders() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_cancel_order(futuresClient): + futuresClient.futures_coin_cancel_order() + + +def test_futures_coin_cancel_all_open_orders(futuresClient): + futuresClient.futures_coin_cancel_all_open_orders(symbol="BTCUSD_PERP") + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_cancel_orders(futuresClient): + futuresClient.futures_coin_cancel_orders() + + +def test_futures_coin_account_balance(futuresClient): + futuresClient.futures_coin_account_balance() + + +def test_futures_coin_account(futuresClient): + futuresClient.futures_coin_account() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_change_leverage(futuresClient): + futuresClient.futures_coin_change_leverage(symbol="XRPUSDT", leverage=10) + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_change_margin_type(futuresClient): + futuresClient.futures_coin_change_margin_type() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_change_position_margin(futuresClient): + futuresClient.futures_coin_change_position_margin() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_position_margin_history(futuresClient): + futuresClient.futures_coin_position_margin_history() + + +def test_futures_coin_position_information(futuresClient): + futuresClient.futures_coin_position_information() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_account_trades(futuresClient): + futuresClient.futures_coin_account_trades() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_income_history(futuresClient): + futuresClient.futures_coin_income_history() + + +@pytest.mark.skip(reason="Not implemented") +def test_futures_coin_change_position_mode(futuresClient): + futuresClient.futures_coin_change_position_mode() + + +def test_futures_coin_get_position_mode(futuresClient): + futuresClient.futures_coin_get_position_mode() + + +def test_futures_coin_stream_close(futuresClient): + listen_key = futuresClient.futures_coin_stream_get_listen_key() + futuresClient.futures_coin_stream_close(listenKey=listen_key) diff --git a/tests/test_client_gift_card.py b/tests/test_client_gift_card.py new file mode 100644 index 000000000..0525783b5 --- /dev/null +++ b/tests/test_client_gift_card.py @@ -0,0 +1,62 @@ +import pytest +import requests_mock + +pytestmark = pytest.mark.gift_card + + +def test_mock_gift_card_fetch_token_limit(liveClient): + """Test gift card token limit endpoint with mocked response""" + expected_response = { + "code": "000000", + "message": "success", + "data": [{"coin": "BNB", "fromMin": "0.01", "fromMax": "1"}], + "success": True, + } + + with requests_mock.mock() as m: + m.get( + "https://api.binance.com/sapi/v1/giftcard/buyCode/token-limit", + json=expected_response, + ) + + response = liveClient.gift_card_fetch_token_limit(baseToken="BUSD") + assert response == expected_response + + +def test_gift_card_fetch_token_limit(liveClient): + liveClient.gift_card_fetch_token_limit(baseToken="BUSD") + + +def test_gift_card_fetch_rsa_public_key(liveClient): + liveClient.gift_card_fetch_rsa_public_key() + + +def test_gift_card_create_verify_and_redeem(liveClient): + # create a gift card + response = liveClient.gift_card_create(token="USDT", amount=1.0) + assert response["data"]["referenceNo"] is not None + assert response["data"]["code"] is not None + # verify the gift card + response = liveClient.gift_card_verify(referenceNo=response["data"]["referenceNo"]) + assert response["data"]["valid"] == "SUCCESS" + # redeem the gift card + redeem_response = liveClient.gift_card_redeem( + code=response["data"]["code"], + ) + assert response["data"]["referenceNo"] == redeem_response["data"]["referenceNo"] + + +def test_gift_card_create_dual_token_and_redeem(liveClient): + response = liveClient.gift_card_create_dual_token( + baseToken="USDT", faceToken="BNB", baseTokenAmount=1.0 + ) + assert response["data"]["referenceNo"] is not None + assert response["data"]["code"] is not None + # verify the gift card + response = liveClient.gift_card_verify(referenceNo=response["data"]["referenceNo"]) + assert response["data"]["valid"] == "SUCCESS" + # redeem the gift card + redeem_response = liveClient.gift_card_redeem( + code=response["data"]["code"], + ) + assert response["data"]["referenceNo"] == redeem_response["data"]["referenceNo"] diff --git a/tests/test_client_margin.py b/tests/test_client_margin.py new file mode 100644 index 000000000..5e6add03b --- /dev/null +++ b/tests/test_client_margin.py @@ -0,0 +1,594 @@ +import pytest + + +pytestmark = pytest.mark.margin + + +def test_margin__get_account_status(client): + client.get_account_status() + + +def test_margin_get_account_api_trading_status(client): + client.get_account_api_trading_status() + + +def test_margin_get_account_api_permissions(client): + client.get_account_api_permissions() + + +def test_margin_get_dust_assets(client): + client.get_dust_assets() + + +def test_margin_get_dust_log(client): + client.test_get_dust_log() + + +def test_margin_transfer_dust(client): + client.transfer_dust() + + +def test_margin_get_asset_dividend_history(client): + client.get_asset_dividend_history() + + +def test_margin_make_universal_transfer(client): + client.make_universal_transfer() + + +def test_margin_query_universal_transfer_history(client): + client.query_universal_transfer_history() + + +def test_margin_get_trade_fee(client): + client.get_trade_fee() + + +def test_margin_get_asset_details(client): + client.get_asset_details() + + +def test_margin_get_spot_delist_schedule(client): + client.get_spot_delist_schedule() + + +# Withdraw Endpoints + + +def test_margin_withdraw(client): + client.withdraw() + + +def test_margin_get_deposit_history(client): + client.get_deposit_history() + + +def test_margin_get_withdraw_history(client): + client.get_withdraw_history() + + +def test_margin_get_withdraw_history_id(client): + client.get_withdraw_history_id() + + +def test_margin_get_deposit_address(client): + client.get_deposit_address() + + +# Margin Trading Endpoints + + +def test_margin_get_margin_account(client): + client.get_margin_account() + + +def test_margin_get_isolated_margin_account(client): + client.get_isolated_margin_account() + + +def test_margin_enable_isolated_margin_account(client): + client.enable_isolated_margin_account() + + +def test_margin_disable_isolated_margin_account(client): + client.disable_isolated_margin_account() + + +def test_margin_get_enabled_isolated_margin_account_limit(client): + client.get_enabled_isolated_margin_account_limit() + + +def test_margin_get_margin_dustlog(client): + client.get_margin_dustlog() + + +def test_margin_get_margin_dust_assets(client): + client.get_margin_dust_assets() + + +def test_margin_transfer_margin_dust(client): + client.transfer_margin_dust() + + +def test_margin_get_cross_margin_collateral_ratio(client): + client.get_cross_margin_collateral_ratio() + + +def test_margin_get_small_liability_exchange_assets(client): + client.get_small_liability_exchange_assets() + + +def test_margin_exchange_small_liability_assets(client): + client.exchange_small_liability_assets() + + +def test_margin_get_small_liability_exchange_history(client): + client.get_small_liability_exchange_history() + + +def test_margin_get_future_hourly_interest_rate(client): + client.get_future_hourly_interest_rate() + + +def test_margin_get_margin_capital_flow(client): + client.get_margin_capital_flow() + + +def test_margin_get_margin_asset(client): + client.get_margin_asset() + + +def test_margin_get_margin_symbol(client): + client.get_margin_symbol() + + +def test_margin_get_margin_all_assets(client): + client.get_margin_all_assets() + + +def test_margin_get_margin_all_pairs(client): + client.get_margin_all_pairs() + + +def test_margin_create_isolated_margin_account(client): + client.create_isolated_margin_account() + + +def test_margin_get_isolated_margin_symbol(client): + client.get_isolated_margin_symbol() + + +def test_margin_get_all_isolated_margin_symbols(client): + client.get_all_isolated_margin_symbols() + + +def test_margin_get_isolated_margin_fee_data(client): + client.get_isolated_margin_fee_data() + + +def test_margin_get_isolated_margin_tier_data(client): + client.get_isolated_margin_tier_data() + + +def test_margin_margin_manual_liquidation(client): + client.margin_manual_liquidation() + + +def test_margin_toggle_bnb_burn_spot_margin(client): + client.toggle_bnb_burn_spot_margin() + + +def test_margin_get_bnb_burn_spot_margin(client): + client.get_bnb_burn_spot_margin() + + +def test_margin_get_margin_price_index(client): + client.get_margin_price_index() + + +def test_margin_transfer_margin_to_spot(client): + client.transfer_margin_to_spot() + + +def test_margin_transfer_spot_to_margin(client): + client.transfer_spot_to_margin() + + +def test_margin_transfer_isolated_margin_to_spot(client): + client.transfer_isolated_margin_to_spot() + + +def test_margin_transfer_spot_to_isolated_margin(client): + client.transfer_spot_to_isolated_margin() + + +def test_margin_get_isolated_margin_tranfer_history(client): + client.get_isolated_margin_tranfer_history() + + +def test_margin_create_margin_loan(client): + client.create_margin_loan() + + +def test_margin_repay_margin_loan(client): + client.repay_margin_loan() + + +def create_margin_ordertest_(client): + client.create_margin_order() + + +def test_margin_cancel_margin_order(client): + client.cancel_margin_order() + + +def test_margin_set_margin_max_leverage(client): + client.set_margin_max_leverage() + + +def test_margin_get_margin_transfer_history(client): + client.get_margin_transfer_history() + + +def test_margin_get_margin_loan_details(client): + client.get_margin_loan_details() + + +def test_margin_get_margin_repay_details(client): + client.get_margin_repay_details() + + +def test_margin_get_cross_margin_data(client): + client.get_cross_margin_data() + + +def test_margin_get_margin_interest_history(client): + client.get_margin_interest_history() + + +def test_margin_get_margin_force_liquidation_rec(client): + client.get_margin_force_liquidation_rec() + + +def test_margin_get_margin_order(client): + client.get_margin_order() + + +def test_margin_get_open_margin_orders(client): + client.get_open_margin_orders() + + +def test_margin_get_all_margin_orders(client): + client.get_all_margin_orders() + + +def test_margin_get_margin_trades(client): + client.get_margin_trades() + + +def test_margin_get_max_margin_loan(client): + client.get_max_margin_loan() + + +def test_margin_get_max_margin_transfer(client): + client.get_max_margin_transfer() + + +def test_margin_get_margin_delist_schedule(client): + client.get_margin_delist_schedule() + + +# Margin OCO + + +def test_margin_create_margin_oco_order(client): + client.create_margin_oco_order() + + +def test_margin_cancel_margin_oco_order(client): + client.cancel_margin_oco_order() + + +def test_margin_get_margin_oco_order(client): + client.get_margin_oco_order() + + +def test_margin_get_open_margin_oco_orders(client): + client.get_open_margin_oco_orders() + + +# Cross-margin + + +def test_margin_margin_stream_get_listen_key(client): + client.margin_stream_get_listen_key() + + +def test_margin_margin_stream_close(client): + client.margin_stream_close() + + +# Isolated margin + + +def test_margin_isolated_margin_stream_get_listen_key(client): + client.isolated_margin_stream_get_listen_key() + + +def test_margin_isolated_margin_stream_close(client): + client.isolated_margin_stream_close() + + +# Simple Earn Endpoints + + +def test_margin_get_simple_earn_flexible_product_list(client): + client.get_simple_earn_flexible_product_list() + + +def test_margin_get_simple_earn_locked_product_list(client): + client.get_simple_earn_locked_product_list() + + +def test_margin_subscribe_simple_earn_flexible_product(client): + client.subscribe_simple_earn_flexible_product() + + +def test_margin_subscribe_simple_earn_locked_product(client): + client.subscribe_simple_earn_locked_product() + + +def test_margin_redeem_simple_earn_flexible_product(client): + client.redeem_simple_earn_flexible_product() + + +def test_margin_redeem_simple_earn_locked_product(client): + client.redeem_simple_earn_locked_product() + + +def test_margin_get_simple_earn_flexible_product_position(client): + client.get_simple_earn_flexible_product_position() + + +def test_margin_get_simple_earn_locked_product_position(client): + client.get_simple_earn_locked_product_position() + + +def test_margin_get_simple_earn_account(client): + client.get_simple_earn_account() + + +# Lending Endpoints + + +def test_margin_get_fixed_activity_project_list(client): + client.get_fixed_activity_project_list() + + +def test_margin_change_fixed_activity_to_daily_position(client): + client.change_fixed_activity_to_daily_position() + + +# Staking Endpoints + + +def test_margin_get_staking_product_list(client): + client.get_staking_product_list() + + +def test_margin_purchase_staking_product(client): + client.purchase_staking_product() + + +def test_margin_redeem_staking_product(client): + client.redeem_staking_product() + + +def test_margin_get_staking_position(client): + client.get_staking_position() + + +def test_margin_get_staking_purchase_history(client): + client.get_staking_purchase_history() + + +def test_margin_set_auto_staking(client): + client.set_auto_staking() + + +def test_margin_get_personal_left_quota(client): + client.get_personal_left_quota() + + +# US Staking Endpoints + + +def test_margin_get_staking_asset_us(client): + client.get_staking_asset_us() + + +def test_margin_stake_asset_us(client): + client.stake_asset_us() + + +def test_margin_unstake_asset_us(client): + client.unstake_asset_us() + + +def test_margin_get_staking_balance_us(client): + client.get_staking_balance_us() + + +def test_margin_get_staking_history_us(client): + client.get_staking_history_us() + + +def test_margin_get_staking_rewards_history_us(client): + client.get_staking_rewards_history_us() + + +# Sub Accounts + + +def test_margin_get_sub_account_list(client): + client.get_sub_account_list() + + +def test_margin_get_sub_account_transfer_history(client): + client.get_sub_account_transfer_history() + + +def test_margin_get_sub_account_futures_transfer_history(client): + client.get_sub_account_futures_transfer_history() + + +def test_margin_create_sub_account_futures_transfer(client): + client.create_sub_account_futures_transfer() + + +def test_margin_get_sub_account_assets(client): + client.get_sub_account_assets() + + +def test_margin_query_subaccount_spot_summary(client): + client.query_subaccount_spot_summary() + + +def test_margin_get_subaccount_deposit_address(client): + client.get_subaccount_deposit_address() + + +def test_margin_get_subaccount_deposit_history(client): + client.get_subaccount_deposit_history() + + +def test_margin_get_subaccount_futures_margin_status(client): + client.get_subaccount_futures_margin_status() + + +def test_margin_enable_subaccount_margin(client): + client.enable_subaccount_margin() + + +def test_margin_get_subaccount_margin_details(client): + client.get_subaccount_margin_details() + + +def test_margin_get_subaccount_margin_summary(client): + client.get_subaccount_margin_summary() + + +def test_margin_enable_subaccount_futures(client): + client.enable_subaccount_futures() + + +def test_margin_get_subaccount_futures_details(client): + client.get_subaccount_futures_details() + + +def test_margin_get_subaccount_futures_summary(client): + client.get_subaccount_futures_summary() + + +def test_margin_get_subaccount_futures_positionrisk(client): + client.get_subaccount_futures_positionrisk() + + +def test_margin_make_subaccount_futures_transfer(client): + client.make_subaccount_futures_transfer() + + +def test_margin_make_subaccount_margin_transfer(client): + client.make_subaccount_margin_transfer() + + +def test_margin_make_subaccount_to_subaccount_transfer(client): + client.make_subaccount_to_subaccount_transfer() + + +def test_margin_make_subaccount_to_master_transfer(client): + client.make_subaccount_to_master_transfer() + + +def test_margin_get_subaccount_transfer_history(client): + client.get_subaccount_transfer_history() + + +def test_margin_make_subaccount_universal_transfer(client): + client.make_subaccount_universal_transfer() + + +def test_margin_get_universal_transfer_history(client): + client.get_universal_transfer_history() + + +# Fiat Endpoints + + +def test_margin_get_fiat_deposit_withdraw_history(client): + client.get_fiat_deposit_withdraw_history() + + +def test_margin_get_fiat_payments_history(client): + client.get_fiat_payments_history() + + +# C2C Endpoints + + +def test_margin_get_c2c_trade_history(client): + client.get_c2c_trade_history() + + +# Pay Endpoints + + +def test_margin_get_pay_trade_history(client): + client.get_pay_trade_history() + + +# Convert Endpoints + + +def test_margin_get_convert_trade_history(client): + client.get_convert_trade_history() + + +def test_margin_convert_request_quote(client): + client.convert_request_quote() + + +def test_margin_convert_accept_quote(client): + client.convert_accept_quote() + + +def test_margin_new_transfer_history(futuresClient): + futuresClient.new_transfer_history() + + +def test_margin_funding_wallet(futuresClient): + futuresClient.funding_wallet() + + +def test_margin_get_user_asset(futuresClient): + futuresClient.get_user_asset() + + +def test_margin_universal_transfer(futuresClient): + futuresClient.universal_transfer() + + +def test_margin_get_all_coins_info(client): + client.get_all_coins_info() + + +def test_margin_get_account_snapshot(client): + client.get_account_snapshot() + + +def test_margin_disable_fast_withdraw_switch(client): + client.disable_fast_withdraw_switch() + + +def test_margin_enable_fast_withdraw_switch(client): + client.enable_fast_withdraw_switch() diff --git a/tests/test_client_options.py b/tests/test_client_options.py new file mode 100644 index 000000000..8b6d09ba1 --- /dev/null +++ b/tests/test_client_options.py @@ -0,0 +1,123 @@ +import pytest + + +pytestmark = pytest.mark.options + + +@pytest.fixture +def options_symbol(liveClient): + prices = liveClient.options_price() + return prices[0]["symbol"] + + +def test_options_ping(liveClient): + liveClient.options_ping() + + +def test_options_time(liveClient): + liveClient.options_time() + + +@pytest.mark.skip(reason="Not implemented") +def test_options_info(liveClient): + liveClient.options_info() + + +def test_options_exchange_info(liveClient): + liveClient.options_exchange_info() + + +def test_options_index_price(liveClient): + liveClient.options_index_price(underlying="BTCUSDT") + + +def test_options_price(liveClient): + liveClient.options_price() + + +def test_options_mark_price(liveClient): + liveClient.options_mark_price() + + +def test_options_order_book(liveClient, options_symbol): + liveClient.options_order_book(symbol=options_symbol) + + +def test_options_klines(liveClient, options_symbol): + liveClient.options_klines(symbol=options_symbol, interval="1m") + + +def test_options_recent_trades(liveClient, options_symbol): + liveClient.options_recent_trades(symbol=options_symbol) + + +def test_options_historical_trades(liveClient, options_symbol): + liveClient.options_historical_trades(symbol=options_symbol) + + +# Account and trading interface endpoints + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_account_info(liveClient): + liveClient.options_account_info() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_funds_transfer(liveClient): + liveClient.options_funds_transfer() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_positions(liveClient): + liveClient.options_positions() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_bill(liveClient): + liveClient.options_bill() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_place_order(liveClient): + liveClient.options_place_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_test_options_place_batch_order(liveClient): + liveClient.test_options_place_batch_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_cancel_order(liveClient): + liveClient.options_cancel_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_cancel_batch_order(liveClient): + liveClient.options_cancel_batch_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_cancel_all_orders(liveClient): + liveClient.options_cancel_all_orders() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_query_order(liveClient): + liveClient.options_query_order() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_query_pending_orders(liveClient): + liveClient.options_query_pending_orders() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_query_order_history(liveClient): + liveClient.options_query_order_history() + + +@pytest.mark.skip(reason="No sandbox to environmnet to test") +def test_options_user_trades(liveClient): + liveClient.options_user_trades() diff --git a/tests/test_client_portfolio.py b/tests/test_client_portfolio.py new file mode 100644 index 000000000..d8f657e72 --- /dev/null +++ b/tests/test_client_portfolio.py @@ -0,0 +1,398 @@ +import pytest + +# Apply the 'portfolio' mark to all tests in this file +pytestmark = pytest.mark.portfolio + + +def test_papi_get_balance(client): + client.papi_get_balance() + + +def test_papi_get_account(client): + client.papi_get_account() + + +def test_papi_get_margin_max_borrowable(client): + client.papi_get_margin_max_borrowable() + + +def test_papi_get_margin_max_withdraw(client): + client.papi_get_margin_max_withdraw() + + +def test_papi_get_um_position_risk(client): + client.papi_get_um_position_risk() + + +def test_papi_get_cm_position_risk(client): + client.papi_get_cm_position_risk() + + +def test_papi_set_um_leverage(client): + client.papi_set_um_leverage() + + +def test_papi_set_cm_leverage(client): + client.papi_set_cm_leverage() + + +def test_papi_change_um_position_side_dual(client): + client.papi_change_um_position_side_dual() + + +def test_papi_get_um_position_side_dual(client): + client.papi_get_um_position_side_dual() + + +def test_papi_get_cm_position_side_dual(client): + client.papi_get_cm_position_side_dual() + + +def test_papi_get_um_leverage_bracket(client): + client.papi_get_um_leverage_bracket() + + +def test_papi_get_cm_leverage_bracket(client): + client.papi_get_cm_leverage_bracket() + + +def test_papi_get_um_api_trading_status(client): + client.papi_get_um_api_trading_status() + + +def test_papi_get_um_comission_rate(client): + client.papi_get_um_comission_rate() + + +def test_papi_get_cm_comission_rate(client): + client.papi_get_cm_comission_rate() + + +def test_papi_get_margin_margin_loan(client): + client.papi_get_margin_margin_loan() + + +def test_papi_get_margin_repay_loan(client): + client.papi_get_margin_repay_loan() + + +def test_papi_get_repay_futures_switch(client): + client.papi_get_repay_futures_switch() + + +def test_papi_repay_futures_switch(client): + client.papi_repay_futures_switch() + + +def test_papi_get_margin_interest_history(client): + client.papi_get_margin_interest_history() + + +def test_papi_repay_futures_negative_balance(client): + client.papi_repay_futures_negative_balance() + + +def test_papi_get_portfolio_interest_history(client): + client.papi_get_portfolio_interest_history() + + +def test_papi_fund_auto_collection(client): + client.papi_fund_auto_collection() + + +def test_papi_fund_asset_collection(client): + client.papi_fund_asset_collection() + + +def test_papi_bnb_transfer(client): + client.papi_bnb_transfer() + + +def test_papi_get_um_income_history(client): + client.papi_get_um_income_history() + + +def test_papi_get_cm_income_history(client): + client.papi_get_cm_income_history() + + +def test_papi_get_um_account(client): + client.papi_get_um_account() + + +def test_papi_get_um_account_v2(client): + client.papi_get_um_account_v2() + + +def test_papi_get_cm_account(client): + client.papi_get_cm_account() + + +def test_papi_get_um_account_config(client): + client.papi_get_um_account_config() + + +def test_papi_get_um_symbol_config(client): + client.papi_get_um_symbol_config() + + +def test_papi_get_um_trade_asyn(client): + client.papi_get_um_trade_asyn() + + +def test_papi_get_um_trade_asyn_id(client): + client.papi_get_um_trade_asyn_id() + + +def test_papi_get_um_order_asyn(client): + client.papi_get_um_order_asyn() + + +def test_papi_get_um_order_asyn_id(client): + client.papi_get_um_order_asyn_id() + + +def test_papi_get_um_income_asyn(client): + client.papi_get_um_income_asyn() + + +def test_papi_get_um_income_asyn_id(client): + client.papi_get_um_income_asyn_id() + + +# Public papi endpoints + + +def test_papi_ping(client): + client.papi_ping() + + +# Trade papi endpoints + + +def test_papi_create_um_order(client): + client.papi_create_um_order() + + +def test_papi_create_um_conditional_order(client): + client.papi_create_um_conditional_order() + + +def test_papi_create_cm_order(client): + client.papi_create_cm_order() + + +def test_papi_create_cm_conditional_order(client): + client.papi_create_cm_conditional_order() + + +def test_papi_create_margin_order(client): + client.papi_create_margin_order() + + +def test_papi_margin_loan(client): + client.papi_margin_loan() + + +def test_papi_repay_loan(client): + client.papi_repay_loan() + + +def test_papi_margin_order_oco(client): + client.papi_margin_order_oco() + + +def test_papi_cancel_um_order(client): + client.papi_cancel_um_order() + + +def test_papi_cancel_um_all_open_orders(client): + client.papi_cancel_um_all_open_orders() + + +def test_papi_cancel_um_conditional_order(client): + client.papi_cancel_um_conditional_order() + + +def test_papi_cancel_um_conditional_all_open_orders(client): + client.papi_cancel_um_conditional_all_open_orders() + + +def test_papi_cancel_cm_order(client): + client.papi_cancel_cm_order() + + +def test_papi_cancel_cm_all_open_orders(client): + client.papi_cancel_cm_all_open_orders() + + +def test_papi_cancel_cm_conditional_order(client): + client.papi_cancel_cm_conditional_order() + + +def test_papi_cancel_cm_conditional_all_open_orders(client): + client.papi_cancel_cm_conditional_all_open_orders() + + +def test_papi_cancel_margin_order(client): + client.papi_cancel_margin_order() + + +def test_papi_cancel_margin_order_list(client): + client.papi_cancel_margin_order_list() + + +def test_papi_cancel_margin_all_open_orders(client): + client.papi_cancel_margin_all_open_orders() + + +def test_papi_modify_um_order(client): + client.papi_modify_um_order() + + +def test_papi_modify_cm_order(client): + client.papi_modify_cm_order() + + +def test_papi_get_um_order(client): + client.papi_get_um_order() + + +def test_papi_get_um_all_orders(client): + client.papi_get_um_all_orders() + + +def test_papi_get_um_open_order(client): + client.papi_get_um_open_order() + + +def test_papi_get_um_open_orders(client): + client.papi_get_um_open_orders() + + +def test_papi_get_um_conditional_all_orders(client): + client.papi_get_um_conditional_all_orders() + + +def test_papi_get_um_conditional_open_orders(client): + client.papi_get_um_conditional_open_orders() + + +def test_papi_get_um_conditional_open_order(client): + client.papi_get_um_conditional_open_order() + + +def test_papi_get_um_conditional_order_history(client): + client.papi_get_um_conditional_order_history() + + +def test_papi_get_cm_order(client): + client.papi_get_cm_order() + + +def test_papi_get_cm_all_orders(client): + client.papi_get_cm_all_orders() + + +def test_papi_get_cm_open_order(client): + client.papi_get_cm_open_order() + + +def test_papi_get_cm_open_orders(client): + client.papi_get_cm_open_orders() + + +def test_papi_get_cm_conditional_all_orders(client): + client.papi_get_cm_conditional_all_orders() + + +def test_papi_get_cm_conditional_open_orders(client): + client.papi_get_cm_conditional_open_orders() + + +def test_papi_get_cm_conditional_open_order(client): + client.papi_get_cm_conditional_open_order() + + +def test_papi_get_cm_conditional_order_history(client): + client.papi_get_cm_conditional_order_history() + + +def test_papi_get_um_force_orders(client): + client.papi_get_um_force_orders() + + +def test_papi_get_cm_force_orders(client): + client.papi_get_cm_force_orders() + + +def test_papi_get_um_order_amendment(client): + client.papi_get_um_order_amendment() + + +def test_papi_get_cm_order_amendment(client): + client.papi_get_cm_order_amendment() + + +def test_papi_get_margin_force_orders(client): + client.papi_get_margin_force_orders() + + +def test_papi_get_um_user_trades(client): + client.papi_get_um_user_trades() + + +def test_papi_get_cm_user_trades(client): + client.papi_get_cm_user_trades() + + +def test_papi_get_um_adl_quantile(client): + client.papi_get_um_adl_quantile() + + +def test_papi_get_cm_adl_quantile(client): + client.papi_get_cm_adl_quantile() + + +def test_papi_set_um_fee_burn(client): + client.papi_set_um_fee_burn() + + +def test_papi_get_um_fee_burn(client): + client.papi_get_um_fee_burn() + + +def test_papi_get_margin_order(client): + client.papi_get_margin_order() + + +def test_papi_get_margin_open_orders(client): + client.papi_get_margin_open_orders() + + +def test_papi_get_margin_all_orders(client): + client.papi_get_margin_all_orders() + + +def test_papi_get_margin_order_list(client): + client.papi_get_margin_order_list() + + +def test_papi_get_margin_all_order_list(client): + client.papi_get_margin_all_order_list() + + +def test_papi_get_margin_open_order_list(client): + client.papi_get_margin_open_order_list() + + +def test_papi_get_margin_my_trades(client): + client.papi_get_margin_my_trades() + + +def test_papi_get_margin_repay_debt(client): + client.papi_get_margin_repay_debt() + + +def test_close_connection(client): + client.close_connection() diff --git a/tests/test_headers.py b/tests/test_headers.py new file mode 100644 index 000000000..b0fb270a1 --- /dev/null +++ b/tests/test_headers.py @@ -0,0 +1,92 @@ +import requests_mock +import pytest +from aioresponses import aioresponses + +from binance import Client, AsyncClient + +client = Client(api_key="api_key", api_secret="api_secret", ping=False) + + +def test_get_headers(): + with requests_mock.mock() as m: + m.get("https://api.binance.com/api/v3/account", json={}, status_code=200) + client.get_account() + headers = m.last_request._request.headers + assert "Content-Type" in headers + assert headers["Content-Type"] == "application/json" + + +def test_post_headers(): + with requests_mock.mock() as m: + m.post("https://api.binance.com/api/v3/order", json={}, status_code=200) + client.create_order(symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1) + headers = m.last_request._request.headers + assert "Content-Type" in headers + assert headers["Content-Type"] == "application/x-www-form-urlencoded" + + +def test_post_headers_overriden(): + with requests_mock.mock() as m: + m.post("https://api.binance.com/api/v3/order", json={}, status_code=200) + client.create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + headers={"Content-Type": "myvalue"}, + ) + headers = m.last_request._request.headers + assert "Content-Type" in headers + assert headers["Content-Type"] == "myvalue" + + +@pytest.mark.asyncio() +async def test_post_headers_async(): + clientAsync = AsyncClient( + api_key="api_key", api_secret="api_secret" + ) # reuse client later + with aioresponses() as m: + + def handler(url, **kwargs): + headers = kwargs["headers"] + assert "Content-Type" in headers + assert headers["Content-Type"] == "application/x-www-form-urlencoded" + + m.post( + "https://api.binance.com/api/v3/order", + payload={"id": 1}, + status=200, + callback=handler, + ) + await clientAsync.create_order( + symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1 + ) + await clientAsync.close_connection() + + +@pytest.mark.asyncio() +async def test_post_headers_overriden_async(): + clientAsync = AsyncClient( + api_key="api_key", api_secret="api_secret" + ) # reuse client later + with aioresponses() as m: + + def handler(url, **kwargs): + headers = kwargs["headers"] + assert "Content-Type" in headers + assert headers["Content-Type"] == "myvalue" + + m.post( + "https://api.binance.com/api/v3/order", + payload={"id": 1}, + status=200, + callback=handler, + ) + await clientAsync.create_order( + symbol="LTCUSDT", + side="BUY", + type="MARKET", + quantity=0.1, + headers={"Content-Type": "myvalue"}, + ) + await clientAsync.close_connection() diff --git a/tests/test_ids.py b/tests/test_ids.py index 857615833..d1fea008f 100644 --- a/tests/test_ids.py +++ b/tests/test_ids.py @@ -1,3 +1,4 @@ +import re import requests_mock import pytest from aioresponses import aioresponses @@ -73,13 +74,13 @@ def test_swap_id(): client.futures_create_order( symbol="LTCUSDT", side="BUY", type="MARKET", quantity=0.1 ) - url_dict = dict(pair.split("=") for pair in m.last_request.query.split("&")) + url_dict = dict(pair.split("=") for pair in m.last_request.text.split("&")) # why lowercase? check this later - assert url_dict["symbol"] == "ltcusdt" - assert url_dict["side"] == "buy" - assert url_dict["type"] == "market" + assert url_dict["symbol"] == "LTCUSDT" + assert url_dict["side"] == "BUY" + assert url_dict["type"] == "MARKET" assert url_dict["quantity"] == "0.1" - assert url_dict["newClientOrderId".lower()].startswith("x-Cb7ytekJ".lower()) + assert url_dict["newClientOrderId"].startswith("x-Cb7ytekJ") def test_swap_batch_id(): @@ -204,11 +205,11 @@ async def test_swap_id_async(): with aioresponses() as m: def handler(url, **kwargs): - client_order_id = kwargs["data"][0][1] - assert client_order_id.startswith("x-Cb7ytekJ") + assert "x-Cb7ytekJ" in kwargs["data"][0][1] + url_pattern = re.compile(r"https://fapi\.binance\.com/fapi/v1/order") m.post( - "https://fapi.binance.com/fapi/v1/order", + url_pattern, payload={"id": 1}, status=200, callback=handler, @@ -309,7 +310,7 @@ async def test_swap_batch_id_async(): clientAsync = AsyncClient(api_key="api_key", api_secret="api_secret") def handler(url, **kwargs): - assert "x-Cb7ytekJ" in kwargs["data"][0][1] + assert "x-Cb7ytekJ" in kwargs["data"] m.post( "https://fapi.binance.com/fapi/v1/batchOrders", diff --git a/tox.ini b/tox.ini index e860ef7ca..ab2427681 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ passenv = TEST_API_SECRET TEST_FUTURES_API_KEY TEST_FUTURES_API_SECRET -commands = pytest -v tests/ --doctest-modules --cov binance --cov-report term-missing +commands = pytest -n 1 -v tests/ --doctest-modules --cov binance --cov-report term-missing --reruns 3 --reruns-delay 120 [pep8] ignore = E501