Skip to content

Commit

Permalink
Merge pull request #1435 from carlosmiei/positionRiskv3
Browse files Browse the repository at this point in the history
feat(client): positionRisk to v3 and allow version override in params
  • Loading branch information
carlosmiei authored Oct 21, 2024
2 parents a89c9b8 + 2b2873f commit 1db369d
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 12 deletions.
55 changes: 47 additions & 8 deletions binance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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 = ''
Expand Down Expand Up @@ -231,7 +232,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:
Expand All @@ -244,7 +245,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:
Expand Down Expand Up @@ -284,6 +285,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 uuid22(length=22):
return format(random.getrandbits(length * 4), 'x')
Expand Down Expand Up @@ -406,6 +415,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:
version = self._get_version(version, **kwargs)
uri = self._create_futures_api_uri(path, version)

return self._request(method, uri, signed, True, **kwargs)
Expand All @@ -416,11 +426,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:
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:
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)
Expand All @@ -431,6 +443,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:
version = self._get_version(version, **kwargs)
uri = self._create_margin_api_uri(path, version)

return self._request(method, uri, signed, **kwargs)
Expand Down Expand Up @@ -4502,6 +4515,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):
Expand Down Expand Up @@ -7472,10 +7487,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.
Expand Down Expand Up @@ -7523,7 +7538,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.
Expand Down Expand Up @@ -7592,6 +7607,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
Expand Down Expand Up @@ -8418,6 +8440,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):
Expand Down Expand Up @@ -8820,6 +8844,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:
version = self._get_version(version, **kwargs)
uri = self._create_futures_api_uri(path, version=version)

return await self._request(method, uri, signed, False, **kwargs)
Expand All @@ -8830,11 +8855,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:
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:
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)
Expand All @@ -8845,6 +8872,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:
version = self._get_version(version, **kwargs)
uri = self._create_margin_api_uri(path, version)

return await self._request(method, uri, signed, **kwargs)
Expand Down Expand Up @@ -9547,6 +9575,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__

Expand Down Expand Up @@ -9967,9 +9997,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)
Expand All @@ -9987,7 +10017,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)
Expand Down Expand Up @@ -10026,6 +10056,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):
Expand Down Expand Up @@ -10269,6 +10306,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):
Expand Down
40 changes: 40 additions & 0 deletions tests/test_futures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

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'

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'
4 changes: 0 additions & 4 deletions tests/test_ids.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down

0 comments on commit 1db369d

Please sign in to comment.