Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fe38ffc

Browse files
committed
[Binance] fix binance futures referral checks
1 parent 3d07c5c commit fe38ffc

File tree

6 files changed

+74
-1
lines changed

6 files changed

+74
-1
lines changed

tests/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,10 @@ def add_headers(self, headers_dict):
131131

132132

133133
class ExchangeManager:
134-
def __init__(self, is_margin=False, is_future=False):
134+
def __init__(self, is_margin=False, is_future=False, is_sandboxed=False):
135135
self.is_future = is_future
136136
self.is_margin = is_margin
137+
self.is_sandboxed = is_sandboxed
137138

138139

139140
class ExchangeWrapper:

tests/exchanges/test_binance.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,45 @@ async def test_is_valid_account(binance_exchange):
160160
_get_api_key_rights_mock.assert_not_called()
161161

162162

163+
# futures trading
164+
exchange._exchange.exchange_manager.is_future = True
165+
with mock.patch.object(
166+
exchange, "_get_api_key_rights", mock.AsyncMock(return_value=[
167+
trading_backend.enums.APIKeyRights.FUTURES_TRADING, trading_backend.enums.APIKeyRights.WITHDRAWALS
168+
])
169+
) as _get_api_key_rights_mock:
170+
with mock.patch.object(exchange._exchange.connector.client, "sapi_get_apireferral_ifnewuser",
171+
mock.AsyncMock(return_value={"rebateWorking": True, "ifNewUser": True})) \
172+
as sapi_get_apireferral_ifnewuser_mock:
173+
with mock.patch.object(exchange, "_allow_withdrawal_right", mock.Mock(return_value=True)) as \
174+
_allow_withdrawal_right_mock:
175+
assert (await exchange.is_valid_account()) == (True, None)
176+
sapi_get_apireferral_ifnewuser_mock.assert_not_called()
177+
_get_api_key_rights_mock.assert_called_once()
178+
_allow_withdrawal_right_mock.assert_called_once()
179+
with mock.patch.object(exchange, "_allow_withdrawal_right", mock.Mock(return_value=False)) as \
180+
_allow_withdrawal_right_mock:
181+
with pytest.raises(trading_backend.errors.APIKeyPermissionsError):
182+
assert (await exchange.is_valid_account()) == (True, None)
183+
_allow_withdrawal_right_mock.assert_called_once()
184+
with mock.patch.object(
185+
exchange, "_get_api_key_rights", mock.AsyncMock(return_value=[
186+
trading_backend.enums.APIKeyRights.SPOT_TRADING, trading_backend.enums.APIKeyRights.WITHDRAWALS
187+
])
188+
) as _get_api_key_rights_mock:
189+
with mock.patch.object(exchange._exchange.connector.client, "sapi_get_apireferral_ifnewuser",
190+
mock.AsyncMock(return_value={"rebateWorking": True, "ifNewUser": True})) \
191+
as sapi_get_apireferral_ifnewuser_mock:
192+
with mock.patch.object(exchange, "_allow_withdrawal_right", mock.Mock(return_value=True)) as \
193+
_allow_withdrawal_right_mock:
194+
with pytest.raises(trading_backend.errors.APIKeyPermissionsError):
195+
# futures trading permissions are required
196+
assert (await exchange.is_valid_account()) == (True, None)
197+
sapi_get_apireferral_ifnewuser_mock.assert_not_called()
198+
_get_api_key_rights_mock.assert_called_once()
199+
_allow_withdrawal_right_mock.assert_not_called()
200+
201+
163202
@pytest.mark.asyncio
164203
async def test_ensure_broker_status(binance_exchange):
165204
exchange = exchanges.Binance(binance_exchange)

tests/exchanges/test_exchange.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ async def test_is_valid_account(default_exchange):
5353
with pytest.raises(trading_backend.errors.TimeSyncError):
5454
assert await exchange.is_valid_account() == (True, None)
5555
fetch_balance_mock.assert_called_once()
56+
with (mock.patch.object(exchange, "_inner_is_valid_account",
57+
mock.AsyncMock(side_effect=trading_backend.errors.InvalidIdError))
58+
as _inner_is_valid_account_mock):
59+
with pytest.raises(trading_backend.errors.ExchangeAuthError):
60+
assert await exchange.is_valid_account() == (True, None)
61+
_inner_is_valid_account_mock.assert_called_once()
5662

5763

5864
@pytest.mark.asyncio

trading_backend/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ class ExchangeAuthError(RuntimeError):
2525

2626
class APIKeyPermissionsError(RuntimeError):
2727
pass
28+
29+
30+
class InvalidIdError(RuntimeError):
31+
pass

trading_backend/exchanges/binance.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import aiohttp.streams
1818

1919
import trading_backend.exchanges as exchanges
20+
import trading_backend.errors
2021
import trading_backend.enums
2122

2223
class Binance(exchanges.Exchange):
@@ -57,6 +58,9 @@ def get_orders_parameters(self, params=None) -> dict:
5758

5859
async def _ensure_broker_status(self):
5960
try:
61+
if self._exchange.exchange_manager.is_future:
62+
# no way to check on futures accounts (raising AgentCode is not exist)
63+
return f"Broker rebate can't be checked when trading futures."
6064
details = await self._get_account_referral_details()
6165
if not details.get("rebateWorking", False):
6266
if (ref_id := details.get("referrerId", None)) and ref_id == self.LEGACY_REF_ID:
@@ -106,6 +110,9 @@ async def _get_api_key_rights(self) -> list[trading_backend.enums.APIKeyRights]:
106110
async def _inner_is_valid_account(self) -> (bool, str):
107111
details = None
108112
try:
113+
if self._exchange.exchange_manager.is_future and not self._exchange.exchange_manager.is_sandboxed:
114+
# no way to check on futures accounts (raising AgentCode is not exist)
115+
return True, None
109116
details = await self._get_account_referral_details()
110117
if not details.get("rebateWorking", False):
111118
ref_id = details.get("referrerId", None)
@@ -129,6 +136,13 @@ async def _inner_is_valid_account(self) -> (bool, str):
129136
raise
130137
except ValueError as err:
131138
raise ccxt.AuthenticationError(f"Invalid key format ({err})")
139+
except ccxt.ExchangeError as err:
140+
if "AgentCode is not exist" in str(err):
141+
# err: BadRequest('binance {"code":-9000,"msg":"apiAgentCode is not exist"}')
142+
raise trading_backend.errors.InvalidIdError(
143+
f"{self.__class__.__name__} Agent code error (configured local id) is invalid. Please update it ({err})"
144+
)
145+
raise err
132146
except AttributeError:
133147
if isinstance(details, aiohttp.streams.StreamReader):
134148
return False, "Error when fetching exchange data (unreadable response)"

trading_backend/exchanges/exchange.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,15 @@ async def is_valid_account(self, always_check_key_rights=False) -> (bool, str):
153153
raise trading_backend.errors.TimeSyncError(err)
154154
except ccxt.ExchangeError as err:
155155
raise trading_backend.errors.ExchangeAuthError(err)
156+
except trading_backend.errors.InvalidIdError as err:
157+
try:
158+
import octobot_commons.logging as logging
159+
logging.get_logger(self.__class__.__name__).error(
160+
f"{trading_backend.errors.InvalidIdError.__name__} when checking account validity: {err}"
161+
)
162+
except ImportError:
163+
pass
164+
raise trading_backend.errors.ExchangeAuthError(err)
156165

157166
async def _inner_is_valid_account(self) -> (bool, str):
158167
# check account validity regarding exchange requirements, exchange specific

0 commit comments

Comments
 (0)