From da33b9817e8d1b623ef906f95846e36e9ebcb8b9 Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:09:06 +0100 Subject: [PATCH 1/5] feat(client): update positionRisk to v3 and allow version override in params --- binance/client.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/binance/client.py b/binance/client.py index 87a3ce46..c5e3bc5a 100755 --- a/binance/client.py +++ b/binance/client.py @@ -43,6 +43,7 @@ class BaseClient: MARGIN_API_VERSION4 = 'v4' FUTURES_API_VERSION = 'v1' FUTURES_API_VERSION2 = 'v2' + FUTURES_API_VERSION3 = 'v3' OPTIONS_API_VERSION = 'v1' BASE_ENDPOINT_DEFAULT = '' @@ -226,7 +227,7 @@ def _create_futures_api_uri(self, path: str, version: int = 1) -> str: url = self.FUTURES_URL if self.testnet: url = self.FUTURES_TESTNET_URL - options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2} + options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2, 3: self.FUTURES_API_VERSION3} return url + '/' + options[version] + '/' + path def _create_futures_data_api_uri(self, path: str) -> str: @@ -239,7 +240,7 @@ def _create_futures_coin_api_url(self, path: str, version: int = 1) -> str: url = self.FUTURES_COIN_URL if self.testnet: url = self.FUTURES_COIN_TESTNET_URL - options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2} + options = {1: self.FUTURES_API_VERSION, 2: self.FUTURES_API_VERSION2, 3: self.FUTURES_API_VERSION3} return url + "/" + options[version] + "/" + path def _create_futures_coin_data_api_url(self, path: str, version: int = 1) -> str: @@ -395,6 +396,9 @@ def _request_api( return self._request(method, uri, signed, **kwargs) def _request_futures_api(self, method, path, signed=False, version: int = 1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_api_uri(path, version) return self._request(method, uri, signed, True, **kwargs) @@ -405,11 +409,17 @@ def _request_futures_data_api(self, method, path, signed=False, **kwargs) -> Dic return self._request(method, uri, signed, True, **kwargs) def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_coin_api_url(path, version=version) return self._request(method, uri, signed, True, **kwargs) def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_coin_data_api_url(path, version=version) return self._request(method, uri, signed, True, **kwargs) @@ -420,6 +430,9 @@ def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: return self._request(method, uri, signed, True, **kwargs) def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_margin_api_uri(path, version) return self._request(method, uri, signed, **kwargs) @@ -7469,7 +7482,7 @@ def futures_position_information(self, **params): https://binance-docs.github.io/apidocs/futures/en/#position-information-user_data """ - return self._request_futures_api('get', 'positionRisk', True, 2, data=params) + return self._request_futures_api('get', 'positionRisk', True, 3, data=params) def futures_account_trades(self, **params): """Get trades for the authenticated account and symbol. @@ -8766,6 +8779,9 @@ async def _request_api(self, method, path, signed=False, version=BaseClient.PUBL return await self._request(method, uri, signed, **kwargs) async def _request_futures_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_api_uri(path, version=version) return await self._request(method, uri, signed, True, **kwargs) @@ -8776,11 +8792,17 @@ async def _request_futures_data_api(self, method, path, signed=False, **kwargs) return await self._request(method, uri, signed, True, **kwargs) async def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_coin_api_url(path, version=version) return await self._request(method, uri, signed, True, **kwargs) async def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_futures_coin_data_api_url(path, version=version) return await self._request(method, uri, signed, True, **kwargs) @@ -8791,6 +8813,9 @@ async def _request_options_api(self, method, path, signed=False, **kwargs) -> Di return await self._request(method, uri, signed, True, **kwargs) async def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: + if 'version' in kwargs: + version = kwargs['version'] + del kwargs['version'] uri = self._create_margin_api_uri(path, version) return await self._request(method, uri, signed, **kwargs) @@ -9925,7 +9950,7 @@ async def futures_position_margin_history(self, **params): return await self._request_futures_api('get', 'positionMargin/history', True, data=params) async def futures_position_information(self, **params): - return await self._request_futures_api('get', 'positionRisk', True, version=2, data=params) + return await self._request_futures_api('get', 'positionRisk', True, version=3, data=params) async def futures_account_trades(self, **params): return await self._request_futures_api('get', 'userTrades', True, data=params) From 723c65277f67102901ff85386913417b1584da53 Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Mon, 21 Oct 2024 10:24:26 +0100 Subject: [PATCH 2/5] allow version override --- binance/client.py | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/binance/client.py b/binance/client.py index c5e3bc5a..1cb6c705 100755 --- a/binance/client.py +++ b/binance/client.py @@ -280,6 +280,14 @@ def _generate_signature(self, data: Dict) -> str: query_string = '&'.join([f"{d[0]}={d[1]}" for d in self._order_params(data)]) return sig_func(query_string) + @staticmethod + def _get_version(version: int, **kwargs) -> int: + if 'data' in kwargs and 'version' in kwargs['data']: + version_override = kwargs['data'].get('version') + del kwargs['data']['version'] + return version_override + return version + @staticmethod def _order_params(data: Dict) -> List[Tuple[str, str]]: """Convert params to list with signature as last element @@ -396,9 +404,7 @@ def _request_api( return self._request(method, uri, signed, **kwargs) def _request_futures_api(self, method, path, signed=False, version: int = 1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_futures_api_uri(path, version) return self._request(method, uri, signed, True, **kwargs) @@ -409,17 +415,13 @@ def _request_futures_data_api(self, method, path, signed=False, **kwargs) -> Dic return self._request(method, uri, signed, True, **kwargs) def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_futures_coin_api_url(path, version=version) return self._request(method, uri, signed, True, **kwargs) def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + 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) @@ -430,9 +432,7 @@ def _request_options_api(self, method, path, signed=False, **kwargs) -> Dict: return self._request(method, uri, signed, True, **kwargs) def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_margin_api_uri(path, version) return self._request(method, uri, signed, **kwargs) @@ -8779,9 +8779,7 @@ async def _request_api(self, method, path, signed=False, version=BaseClient.PUBL return await self._request(method, uri, signed, **kwargs) async def _request_futures_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_futures_api_uri(path, version=version) return await self._request(method, uri, signed, True, **kwargs) @@ -8792,17 +8790,13 @@ async def _request_futures_data_api(self, method, path, signed=False, **kwargs) return await self._request(method, uri, signed, True, **kwargs) async def _request_futures_coin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_futures_coin_api_url(path, version=version) return await self._request(method, uri, signed, True, **kwargs) async def _request_futures_coin_data_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + 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) @@ -8813,9 +8807,7 @@ async def _request_options_api(self, method, path, signed=False, **kwargs) -> Di return await self._request(method, uri, signed, True, **kwargs) async def _request_margin_api(self, method, path, signed=False, version=1, **kwargs) -> Dict: - if 'version' in kwargs: - version = kwargs['version'] - del kwargs['version'] + version = self._get_version(version, **kwargs) uri = self._create_margin_api_uri(path, version) return await self._request(method, uri, signed, **kwargs) From 4d234345f51ac8d5dc0adc2df24d54f69e7b523b Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:59:51 +0100 Subject: [PATCH 3/5] add more ids --- binance/client.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/binance/client.py b/binance/client.py index 0af3b477..0681df8f 100755 --- a/binance/client.py +++ b/binance/client.py @@ -4476,6 +4476,8 @@ def create_margin_order(self, **params): BinanceOrderInactiveSymbolException """ + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() return self._request_margin_api('post', 'margin/order', signed=True, data=params) def cancel_margin_order(self, **params): @@ -8392,6 +8394,8 @@ def options_place_order(self, **params): :type recvWindow: int """ + if 'clientOrderId' not in params: + params['clientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() return self._request_options_api('post', 'order', signed=True, data=params) def options_place_batch_order(self, **params): @@ -9521,6 +9525,8 @@ async def repay_margin_loan(self, **params): repay_margin_loan.__doc__ = Client.repay_margin_loan.__doc__ async def create_margin_order(self, **params): + if 'newClientOrderId' not in params: + params['newClientOrderId'] = self.SPOT_ORDER_PREFIX + self.uuid22() return await self._request_margin_api('post', 'margin/order', signed=True, data=params) create_margin_order.__doc__ = Client.create_margin_order.__doc__ @@ -10243,6 +10249,8 @@ async def options_bill(self, **params): return await self._request_options_api('post', 'bill', signed=True, data=params) async def options_place_order(self, **params): + if 'clientOrderId' not in params: + params['clientOrderId'] = self.CONTRACT_ORDER_PREFIX + self.uuid22() return await self._request_options_api('post', 'order', signed=True, data=params) async def options_place_batch_order(self, **params): From dd3a1d3eb1bb294a66ab751f208a9dab3c117776 Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:05:57 +0100 Subject: [PATCH 4/5] add tests --- tests/test_futures.py | 26 ++++++++++++++++++++++++++ tests/test_ids.py | 4 ---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 tests/test_futures.py diff --git a/tests/test_futures.py b/tests/test_futures.py new file mode 100644 index 00000000..3ce52650 --- /dev/null +++ b/tests/test_futures.py @@ -0,0 +1,26 @@ + +import requests_mock +import pytest +import json +from binance.client import Client, AsyncClient +import re + +client = Client(api_key="api_key", api_secret="api_secret", ping=False) + +def test_futures_position_information(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v3\/positionRisk\?.+") + response = [{'symbol': 'LTCUSDT', 'positionSide': 'LONG', 'positionAmt': '0.700', 'entryPrice': '75.6', 'breakEvenPrice': '75.63024', 'markPrice': '73.18000000', 'unRealizedProfit': '-1.69400000', 'liquidationPrice': '0', 'isolatedMargin': '0', 'notional': '51.22600000', 'marginAsset': 'USDT', 'isolatedWallet': '0', 'initialMargin': '10.24520000', 'maintMargin': '0.33296900', 'positionInitialMargin': '10.24520000', 'openOrderInitialMargin': '0', 'adl': 0, 'bidNotional': '0', 'askNotional': '0', 'updateTime': 1729436057076}] + m.register_uri("GET", url_matcher, json=json.dumps(response), status_code=200) + pos = client.futures_position_information(symbol="LTCUSDT") + assert m.last_request.qs['symbol'][0] == 'LTCUSDT'.lower() + assert m.last_request.path == '/fapi/v3/positionrisk' + +def test_futures_position_information_version_override(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v2\/positionRisk\?.+") + response = [{'symbol': 'LTCUSDT', 'positionSide': 'LONG', 'positionAmt': '0.700', 'entryPrice': '75.6', 'breakEvenPrice': '75.63024', 'markPrice': '73.18000000', 'unRealizedProfit': '-1.69400000', 'liquidationPrice': '0', 'isolatedMargin': '0', 'notional': '51.22600000', 'marginAsset': 'USDT', 'isolatedWallet': '0', 'initialMargin': '10.24520000', 'maintMargin': '0.33296900', 'positionInitialMargin': '10.24520000', 'openOrderInitialMargin': '0', 'adl': 0, 'bidNotional': '0', 'askNotional': '0', 'updateTime': 1729436057076}] + m.register_uri("GET", url_matcher, json=json.dumps(response), status_code=200) + pos = client.futures_position_information(symbol="LTCUSDT", version=2) + assert m.last_request.qs['symbol'][0] == 'LTCUSDT'.lower() + assert m.last_request.path == '/fapi/v2/positionrisk' diff --git a/tests/test_ids.py b/tests/test_ids.py index 792d15e4..f08fe81b 100644 --- a/tests/test_ids.py +++ b/tests/test_ids.py @@ -1,10 +1,6 @@ import requests_mock -import os, sys import pytest from aioresponses import aioresponses -root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.append(root) - from binance.client import Client, AsyncClient From 2b2873f29aa11a7149a9b06ad1e2d2aca86d84ac Mon Sep 17 00:00:00 2001 From: carlosmiei <43336371+carlosmiei@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:15:57 +0100 Subject: [PATCH 5/5] add missing methods --- binance/client.py | 22 ++++++++++++++++++---- tests/test_futures.py | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/binance/client.py b/binance/client.py index 0681df8f..3d133b8d 100755 --- a/binance/client.py +++ b/binance/client.py @@ -7448,10 +7448,10 @@ def futures_countdown_cancel_all(self, **params): def futures_account_balance(self, **params): """Get futures account balance - https://binance-docs.github.io/apidocs/futures/en/#future-account-balance-user_data + https://developers.binance.com/docs/derivatives/usds-margined-futures/account/rest-api/Futures-Account-Balance-V3 """ - return self._request_futures_api('get', 'balance', True, 2, data=params) + return self._request_futures_api('get', 'balance', True, 3, data=params) def futures_account(self, **params): """Get current account information. @@ -7568,6 +7568,13 @@ def futures_stream_close(self, listenKey): } return self._request_futures_api('delete', 'listenKey', signed=False, data=params) + # new methods + def futures_account_config(self, **params): + return self._request_futures_api('get', 'accountConfig', signed=True, version=1, data=params) + + def futures_symbol_config(self, **params): + return self._request_futures_api('get', 'symbolConfig', signed=True, version=1, data=params) + # COIN Futures API def futures_coin_ping(self): """Test connectivity to the Rest API @@ -9947,9 +9954,9 @@ async def futures_cancel_orders(self, **params): async def futures_countdown_cancel_all(self, **params): return await self._request_futures_api('post', 'countdownCancelAll', True, data=params) - + async def futures_account_balance(self, **params): - return await self._request_futures_api('get', 'balance', True, version=2, data=params) + return await self._request_futures_api('get', 'balance', True, version=3, data=params) async def futures_account(self, **params): return await self._request_futures_api('get', 'account', True, version=2, data=params) @@ -10006,6 +10013,13 @@ async def futures_stream_close(self, listenKey): } return await self._request_futures_api('delete', 'listenKey', signed=False, data=params) + # new methods + async def futures_account_config(self, **params): + return await self._request_futures_api('get', 'accountConfig', signed=True, version=1, data=params) + + async def futures_symbol_config(self, **params): + return await self._request_futures_api('get', 'symbolConfig', signed=True, version=1, data=params) + # COIN Futures API async def futures_coin_ping(self): diff --git a/tests/test_futures.py b/tests/test_futures.py index 3ce52650..fc512bc1 100644 --- a/tests/test_futures.py +++ b/tests/test_futures.py @@ -24,3 +24,17 @@ def test_futures_position_information_version_override(): pos = client.futures_position_information(symbol="LTCUSDT", version=2) assert m.last_request.qs['symbol'][0] == 'LTCUSDT'.lower() assert m.last_request.path == '/fapi/v2/positionrisk' + +def test_futures_account_balance(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v3\/balance\?.+") + m.register_uri("GET", url_matcher, json={}, status_code=200) + client.futures_account_balance() + assert m.last_request.path == '/fapi/v3/balance' + +def test_futures_account_config(): + with requests_mock.mock() as m: + url_matcher = re.compile(r"https:\/\/fapi.binance.com\/fapi\/v1\/accountConfig\?.+") + m.register_uri("GET", url_matcher, json={}, status_code=200) + client.futures_account_config() + assert m.last_request.path == '/fapi/v1/accountconfig' \ No newline at end of file