Skip to content

Commit b26ae86

Browse files
committed
[Linter] fixes
1 parent 4d2986c commit b26ae86

9 files changed

Lines changed: 157 additions & 22 deletions

File tree

packages/commons/octobot_commons/symbols/symbol.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
_FULL_SYMBOL_GROUPS_REGEX = r"([^//]*)\/([^:]*):?([^-]*)-?([^-]*)-?([^-]*)-?([^-]*)"
2222

2323

24-
# pylint: disable=R0902
24+
# pylint: disable=R0902,R0913
2525
class Symbol:
2626
# base / quote : settlement-identifier-strike price-type
2727
# Inspired from CCXT https://docs.ccxt.com/#/README?id=contract-naming-conventions:
@@ -84,7 +84,7 @@ def parse_symbol(self, symbol_str: str):
8484
Parse the specified symbol
8585
:param symbol_str: the symbol to parse
8686
"""
87-
trading_symbol, self.network, self.dex = _extract_network_and_dex(
87+
trading_symbol, self.network, self.dex = extract_network_and_dex(
8888
symbol_str, self.network_separator, self.dex_separator
8989
)
9090
if self.settlement_separator in trading_symbol:
@@ -258,11 +258,18 @@ def __repr__(self):
258258
return str(self)
259259

260260

261-
def _extract_network_and_dex(
261+
def extract_network_and_dex(
262262
symbol_str: str,
263263
network_separator: str = octobot_commons.NETWORK_SEPARATOR,
264264
dex_separator: str = octobot_commons.DEX_SEPARATOR,
265265
) -> typing.Tuple[str, typing.Optional[str], typing.Optional[str]]:
266+
"""
267+
Split a symbol into its trading part and optional network/DEX suffix.
268+
:param symbol_str: the symbol to parse (e.g. ``BTC/USDT@SOL!RAYDIUM``)
269+
:param network_separator: separator before the network suffix (default ``@``)
270+
:param dex_separator: separator between network and dex (default ``!``)
271+
:return: trading symbol, network name (or None), dex name (or None)
272+
"""
266273
if network_separator not in symbol_str:
267274
return symbol_str, None, None
268275
trading_symbol, network_and_dex = symbol_str.rsplit(network_separator, 1)

packages/commons/octobot_commons/symbols/symbol_util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def merge_symbol(symbol: str) -> str:
4343
:param symbol: the specified symbol
4444
:return: merged currency and market without /
4545
"""
46-
trading_symbol, _, _ = octobot_commons.symbols.symbol._extract_network_and_dex(symbol)
46+
trading_symbol, _, _ = octobot_commons.symbols.symbol.extract_network_and_dex(symbol)
4747
return trading_symbol.replace(octobot_commons.MARKET_SEPARATOR, "").replace(
4848
octobot_commons.SETTLEMENT_ASSET_SEPARATOR, "_"
4949
)
@@ -118,7 +118,7 @@ def convert_symbol(
118118
:return:
119119
"""
120120
if base_and_quote_only:
121-
symbol, _, _ = octobot_commons.symbols.symbol._extract_network_and_dex(symbol)
121+
symbol, _, _ = octobot_commons.symbols.symbol.extract_network_and_dex(symbol)
122122
symbol = symbol.split(octobot_commons.SETTLEMENT_ASSET_SEPARATOR)[0]
123123
if should_uppercase:
124124
return symbol.replace(symbol_separator, new_symbol_separator).upper()

packages/flow/tests/functionnal_tests/trading_modes_actions/simulator/test_index_trading_mode_action.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ async def test_simulator_index_rebalance_after_index_content_switch_btc_eth_to_b
430430
assert insert_trading_signal_mock.await_count == 3
431431
_assert_trading_signal_btc_eth_usdt_index_portfolio(insert_trading_signal_mock.await_args_list[0].args[0])
432432
_assert_trading_signal_btc_eth_sol_usdt_after_btc_sol_rebalance(insert_trading_signal_mock.await_args_list[1].args[0])
433-
_assert_trading_signal_btc_eth_sol_usdt_after_btc_sol_rebalance(insert_trading_signal_mock.await_args_list[2].args[0])
433+
_assert_trading_signal_btc_eth_sol_usdt_after_btc_sol_rebalance(insert_trading_signal_mock.await_args_list[2].args[0], allow_zero_ratio_assets=frozenset({"ETH"}))
434434
else:
435435
insert_trading_signal_mock.assert_not_awaited()
436436

packages/sync/octobot_sync/constants.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,11 @@
3838
# changing it changes every user's derived identity — and MUST be identical on the
3939
# client (cap provider) and server (allowlist + bridge wallet resolver).
4040
SYNC_BOOTSTRAP_CHALLENGE = "octobot:sync-bootstrap"
41+
42+
EXCHANGE_ACCOUNTS_STATE_VERSION = "1.0.0"
43+
USER_ACCOUNTS_AUTH_STATE_VERSION = "1.0.0"
44+
USER_ACCOUNTS_TRADING_STATE_VERSION = "1.0.0"
45+
USER_STRATEGIES_STATE_VERSION = "1.0.0"
46+
USER_DATA_STATE_VERSION = "1.0.0"
47+
USER_ACTIONS_STATE_VERSION = "1.0.0"
48+
DEBUG_STATE_VERSION = "1.0.0"

packages/sync/tests/sync/collection_providers/test_account_trading_backend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ def test_save_and_load_state_per_account_id(self, tmp_path):
139139
loaded_state = provider.load_state(_TEST_ADDRESS, _TEST_ACCOUNT_ID)
140140
provider.save_state(_TEST_ADDRESS, _TEST_ACCOUNT_ID, updated_state)
141141
reloaded_state = provider.load_state(_TEST_ADDRESS, _TEST_ACCOUNT_ID)
142-
assert loaded_state.updated_at == fixture_time
143-
assert reloaded_state.updated_at == updated_time
142+
assert loaded_state.account_trading[0].updated_at == fixture_time
143+
assert reloaded_state.account_trading[0].updated_at == updated_time
144144

145145
def test_load_state_encrypted_reads_persisted_blob(self, tmp_path):
146146
provider = trading_provider_module.AccountTradingProvider(base_folder=str(tmp_path))

packages/trading/octobot_trading/exchanges/connectors/ccxt/ccxt_connector.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ async def load_symbol_markets(
257257
try:
258258
await self._load_markets(self.client, reload, market_filter=market_filter)
259259
ccxt_client_util.set_ccxt_client_cache(self.client, authenticated_cache)
260+
except ccxt.async_support.OBIPWhitelistError as err:
261+
raise octobot_trading.errors.InvalidAPIKeyIPWhitelistError(
262+
f"Invalid IP whitelist error: {html_util.get_html_summary_if_relevant(err)}"
263+
) from err
260264
except (
261265
ccxt.async_support.AuthenticationError,
262266
ccxt.async_support.ArgumentsRequired,
@@ -283,10 +287,6 @@ async def load_symbol_markets(
283287
f"Failed to load_symbol_markets: {err.__class__.__name__} "
284288
f"on {html_util.get_html_summary_if_relevant(err)}"
285289
) from err
286-
except ccxt.async_support.OBIPWhitelistError as err:
287-
raise octobot_trading.errors.InvalidAPIKeyIPWhitelistError(
288-
f"Invalid IP whitelist error: {html_util.get_html_summary_if_relevant(err)}"
289-
) from err
290290
except ccxt.async_support.ExchangeError as err:
291291
# includes AuthenticationError but also auth error not identified as such by ccxt
292292
if not self.force_authentication and self.is_authenticated:

packages/trading/octobot_trading/exchanges/connectors/simulator/ccxt_client_simulation.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import cachetools
1919
import ccxt.async_support as async_ccxt
2020

21+
import octobot_commons.logging as commons_logging
2122
import octobot_trading.exchanges.connectors.ccxt.ccxt_client_util as ccxt_client_util
2223
import octobot_trading.enums as enums
2324

@@ -44,20 +45,31 @@ def get_contract_size(market_status: dict) -> decimal.Decimal:
4445
def get_option_value(
4546
exchange_name: str, option_key: enums.ExchangeClientOptions
4647
) -> typing.Union[bool, float, int, str, None]:
47-
return ccxt_client_util.get_option_value(_temp_client(exchange_name), option_key)
48+
return ccxt_client_util.get_option_value(_temp_client(exchange_name, allow_fallback=True), option_key)
4849

4950

5051
@cachetools.cached(cachetools.LRUCache(maxsize=256))
5152
def supports_bundled_orders(exchange_name: str, exchange_type: enums.ExchangeTypes, order_type: enums.TradeOrderType) -> bool:
5253
return ccxt_client_util.supports_bundled_orders(
53-
_temp_client(exchange_name), exchange_type, order_type
54+
_temp_client(exchange_name, allow_fallback=True), exchange_type, order_type
5455
)
5556

5657

57-
def _temp_client(exchange_name: str, additional_client_config: typing.Optional[dict] = None) -> async_ccxt.Exchange:
58-
exchange_class = ccxt_client_util.ccxt_exchange_class_factory(exchange_name)
59-
config = {
60-
**(additional_client_config or {}),
61-
**ccxt_client_util.get_custom_domain_config(exchange_class)
62-
}
63-
return exchange_class(config)
58+
def _temp_client(
59+
exchange_name: str, additional_client_config: typing.Optional[dict] = None, allow_fallback: bool = False
60+
) -> async_ccxt.Exchange:
61+
try:
62+
exchange_class = ccxt_client_util.ccxt_exchange_class_factory(exchange_name)
63+
config = {
64+
**(additional_client_config or {}),
65+
**ccxt_client_util.get_custom_domain_config(exchange_class)
66+
}
67+
return exchange_class(config)
68+
except AttributeError as err:
69+
if not allow_fallback:
70+
raise
71+
commons_logging.get_logger("ccxt_client_simulation").warning(
72+
f"Error creating temporary client for {exchange_name}: {err}. Using fallback generic exchange."
73+
)
74+
return async_ccxt.Exchange()
75+

packages/trading/octobot_trading/personal_data/orders/decimal_order_adapter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import octobot_trading.constants as constants
2222
import octobot_trading.errors as errors
2323
import octobot_trading.enums as enums
24-
import octobot_trading.exchanges as exchanges
2524
import octobot_trading.personal_data as personal_data
2625
from octobot_trading.enums import ExchangeConstantsMarketStatusColumns as Ecmsc
2726

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Drakkar-Software OctoBot-Trading
2+
# Copyright (c) Drakkar-Software, All rights reserved.
3+
#
4+
# This library is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation; either
7+
# version 3.0 of the License, or (at your option) any later version.
8+
#
9+
# This library is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# Lesser General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Lesser General Public
15+
# License along with this library.
16+
import mock
17+
import pytest
18+
19+
import octobot_trading.exchanges.connectors.ccxt.ccxt_client_util as ccxt_client_util
20+
import octobot_trading.exchanges.connectors.simulator.ccxt_client_simulation as ccxt_client_simulation_module
21+
22+
23+
class TestTempClient:
24+
def test_returns_client_with_merged_config(self):
25+
exchange_instance = mock.Mock()
26+
exchange_class = mock.Mock(return_value=exchange_instance)
27+
custom_domain_config = {"hostname": "custom"}
28+
with (
29+
mock.patch.object(
30+
ccxt_client_util,
31+
"ccxt_exchange_class_factory",
32+
return_value=exchange_class,
33+
) as factory_mock,
34+
mock.patch.object(
35+
ccxt_client_util,
36+
"get_custom_domain_config",
37+
return_value=custom_domain_config,
38+
) as domain_config_mock,
39+
):
40+
client = ccxt_client_simulation_module._temp_client(
41+
"binance",
42+
additional_client_config={"apiKey": "k"},
43+
)
44+
factory_mock.assert_called_once_with("binance")
45+
domain_config_mock.assert_called_once_with(exchange_class)
46+
exchange_class.assert_called_once_with({"apiKey": "k", "hostname": "custom"})
47+
assert client is exchange_instance
48+
49+
def test_uses_empty_config_when_additional_client_config_is_none(self):
50+
exchange_instance = mock.Mock()
51+
exchange_class = mock.Mock(return_value=exchange_instance)
52+
custom_domain_config = {"hostname": "custom"}
53+
with (
54+
mock.patch.object(
55+
ccxt_client_util,
56+
"ccxt_exchange_class_factory",
57+
return_value=exchange_class,
58+
),
59+
mock.patch.object(
60+
ccxt_client_util,
61+
"get_custom_domain_config",
62+
return_value=custom_domain_config,
63+
),
64+
):
65+
client = ccxt_client_simulation_module._temp_client("binance")
66+
exchange_class.assert_called_once_with(custom_domain_config)
67+
assert client is exchange_instance
68+
69+
def test_raises_attribute_error_when_factory_fails_without_fallback(self):
70+
with mock.patch.object(
71+
ccxt_client_util,
72+
"ccxt_exchange_class_factory",
73+
side_effect=AttributeError("unknown exchange"),
74+
):
75+
with pytest.raises(AttributeError, match="unknown exchange"):
76+
ccxt_client_simulation_module._temp_client(
77+
"unknown_exchange",
78+
allow_fallback=False,
79+
)
80+
81+
def test_returns_generic_exchange_when_factory_fails_with_allow_fallback(self):
82+
fallback_exchange = mock.Mock()
83+
logger_mock = mock.Mock()
84+
simulation_module_path = "octobot_trading.exchanges.connectors.simulator.ccxt_client_simulation"
85+
with (
86+
mock.patch.object(
87+
ccxt_client_util,
88+
"ccxt_exchange_class_factory",
89+
side_effect=AttributeError("unknown exchange"),
90+
),
91+
mock.patch(
92+
f"{simulation_module_path}.async_ccxt.Exchange",
93+
return_value=fallback_exchange,
94+
) as exchange_ctor,
95+
mock.patch(
96+
f"{simulation_module_path}.commons_logging.get_logger",
97+
return_value=logger_mock,
98+
),
99+
):
100+
client = ccxt_client_simulation_module._temp_client(
101+
"unknown_exchange",
102+
allow_fallback=True,
103+
)
104+
exchange_ctor.assert_called_once_with()
105+
logger_mock.warning.assert_called_once()
106+
error_message = logger_mock.warning.call_args[0][0]
107+
assert "unknown_exchange" in error_message
108+
assert "fallback generic exchange" in error_message
109+
assert client is fallback_exchange

0 commit comments

Comments
 (0)