Skip to content

Commit ba57a7f

Browse files
committed
[Sync] add auth, history and trading details collections
1 parent bcc9657 commit ba57a7f

112 files changed

Lines changed: 4039 additions & 625 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/node/octobot_node/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
FAILURE_ERROR_DETAILS_MAX_LENGTH = 8_000
4343

4444
EXCHANGE_ACCOUNTS_STATE_VERSION = "1.0.0"
45+
USER_ACCOUNTS_AUTH_DETAILS_STATE_VERSION = "1.0.0"
46+
USER_ACCOUNTS_TRADING_DETAILS_STATE_VERSION = "1.0.0"
4547
USER_STRATEGIES_STATE_VERSION = "1.0.0"
4648
USER_DATA_STATE_VERSION = "1.0.0"
4749
USER_ACTIONS_STATE_VERSION = "1.0.0"
50+
51+
DEFAULT_PORTFOLIO_VALUATION_UNIT = "USDT"

packages/node/octobot_node/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class AccountNotFoundError(UserActionError):
6262
"""Raised when fetching an account via AccountProvider fails."""
6363

6464

65+
class AccountAuthenticationDetailsNotFoundError(UserActionError):
66+
"""Raised when fetching account authentication details via AccountAuthenticationDetailsProvider fails."""
67+
68+
6569
class AutomationStrategyNotFoundError(UserActionError):
6670
"""Raised when the referenced strategy does not exist in StrategyProvider."""
6771

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This file is part of OctoBot Node (https://github.com/Drakkar-Software/OctoBot-Node)
2+
# Copyright (c) 2025 Drakkar-Software, All rights reserved.
3+
#
4+
# OctoBot Node is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation; either version 3.0 of the License, or (at
7+
# your option) any later version.
8+
#
9+
# OctoBot 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+
# General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License along
15+
# with OctoBot. If not, see <https://www.gnu.org/licenses/>.
16+
17+
import octobot_sync.sync.collection_backend.errors as collection_errors
18+
import octobot_sync.sync.collection_providers.user_account_authentication_details_provider as auth_details_provider
19+
20+
21+
def get_accounts_authentication_details_state_encrypted(address: str) -> dict[str, str] | None:
22+
try:
23+
return auth_details_provider.AccountAuthenticationDetailsProvider.instance().list_items_encrypted(address)
24+
except collection_errors.CollectionNoDataError:
25+
return None
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# This file is part of OctoBot Node (https://github.com/Drakkar-Software/OctoBot-Node)
2+
# Copyright (c) 2025 Drakkar-Software, All rights reserved.
3+
#
4+
# OctoBot Node is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation; either version 3.0 of the License, or (at
7+
# your option) any later version.
8+
#
9+
# OctoBot 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+
# General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License along
15+
# with OctoBot. If not, see <https://www.gnu.org/licenses/>.
16+
17+
import octobot_sync.sync.collection_backend.errors as collection_errors
18+
import octobot_sync.sync.collection_providers.user_account_trading_details_provider as trading_details_provider
19+
20+
21+
def get_account_trading_details_state_encrypted(
22+
address: str,
23+
account_id: str,
24+
) -> dict[str, str] | None:
25+
try:
26+
return trading_details_provider.AccountTradingDetailsProvider.instance().load_state_encrypted(
27+
address,
28+
account_id,
29+
)
30+
except collection_errors.CollectionNoDataError:
31+
return None

packages/node/octobot_node/protocol/automations.py

Lines changed: 42 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import octobot_trading.constants as octobot_trading_constants
2828
import octobot_trading.enums as octobot_trading_enums
2929
import octobot_trading.personal_data.portfolios.protocol as octobot_trading_portfolios_protocol
30-
import octobot_trading.exchanges.util.exchange_data as exchange_data_import
3130

3231

3332
logger = octobot_commons_logging.get_logger("AutomationsProtocol")
@@ -158,65 +157,6 @@ def _protocol_action_from_flow(
158157
)
159158

160159

161-
def _enrich_protocol_assets(
162-
base_assets: list[protocol_models.Asset],
163-
portfolio: exchange_data_import.PortfolioDetails,
164-
unit: typing.Optional[str],
165-
) -> list[protocol_models.Asset]:
166-
enriched: list[protocol_models.Asset] = []
167-
for asset in base_assets:
168-
update_fields: dict[str, typing.Any] = {}
169-
if asset.symbol in portfolio.asset_values:
170-
update_fields["value"] = float(portfolio.asset_values[asset.symbol])
171-
if unit:
172-
update_fields["unit"] = unit
173-
if update_fields:
174-
enriched.append(asset.model_copy(update=update_fields))
175-
else:
176-
enriched.append(asset)
177-
return enriched
178-
179-
180-
def _order_summaries_from_open_orders(open_orders: list[dict]) -> list[protocol_models.OrderSummary]:
181-
order_columns = octobot_trading_enums.ExchangeConstantsOrderColumns
182-
summaries: list[protocol_models.OrderSummary] = []
183-
for order in open_orders:
184-
inner = order.get(octobot_trading_constants.STORAGE_ORIGIN_VALUE, order)
185-
if not isinstance(inner, dict):
186-
inner = order
187-
order_id = inner.get(order_columns.EXCHANGE_ID.value) or inner.get(order_columns.ID.value)
188-
symbol = inner.get(order_columns.SYMBOL.value)
189-
if order_id is None or symbol is None:
190-
continue
191-
summaries.append(protocol_models.OrderSummary(id=str(order_id), symbol=str(symbol)))
192-
return summaries
193-
194-
195-
def _position_summaries(positions: list[typing.Any]) -> list[protocol_models.PositionSummary]:
196-
position_columns = octobot_trading_enums.ExchangeConstantsPositionColumns
197-
summaries: list[protocol_models.PositionSummary] = []
198-
for position_details in positions:
199-
position_dict = position_details.position
200-
position_id = position_dict.get(position_columns.ID.value)
201-
symbol = position_dict.get(position_columns.SYMBOL.value)
202-
if position_id is None or symbol is None:
203-
continue
204-
summaries.append(protocol_models.PositionSummary(id=str(position_id), symbol=str(symbol)))
205-
return summaries
206-
207-
208-
def _trade_summaries(trades: list[dict]) -> list[protocol_models.TradeSummary]:
209-
order_columns = octobot_trading_enums.ExchangeConstantsOrderColumns
210-
summaries: list[protocol_models.TradeSummary] = []
211-
for trade in trades:
212-
trade_id = trade.get(order_columns.EXCHANGE_TRADE_ID.value) or trade.get(order_columns.EXCHANGE_ID.value)
213-
symbol = trade.get(order_columns.SYMBOL.value)
214-
if trade_id is None or symbol is None:
215-
continue
216-
summaries.append(protocol_models.TradeSummary(id=str(trade_id), symbol=str(symbol)))
217-
return summaries
218-
219-
220160
def _fill_protocol_automation_state(
221161
protocol_automation_state: protocol_models.AutomationState,
222162
flow_automation_state: flow_entities.AutomationState,
@@ -247,16 +187,14 @@ def _fill_protocol_automation_state(
247187
exchange_account_ids = [exchange_details.metadata.id]
248188
# Derive portfolio and trading summaries from automation exchange elements.
249189
exchange_elements = flow_automation_state.automation.exchange_account_elements
250-
assets: typing.Optional[list[protocol_models.Asset]] = None
190+
assets: typing.Optional[list[protocol_models.DetailedAsset]] = None
251191
orders: typing.Optional[list[protocol_models.OrderSummary]] = None
252192
trades: typing.Optional[list[protocol_models.TradeSummary]] = None
253193
positions: typing.Optional[list[protocol_models.PositionSummary]] = None
254194
if exchange_elements:
255195
portfolio = exchange_elements.portfolio
256196
if portfolio.content:
257-
base_assets = octobot_trading_portfolios_protocol.to_protocol_assets(portfolio.content)
258-
unit_for_assets = exchange_details.portfolio.unit if exchange_details else None
259-
assets = _enrich_protocol_assets(base_assets, portfolio, unit_for_assets)
197+
assets = octobot_trading_portfolios_protocol.to_protocol_assets(portfolio.content)
260198
orders = _order_summaries_from_open_orders(exchange_elements.orders.open_orders) or None
261199
positions = _position_summaries(exchange_elements.positions) or None
262200
trades = _trade_summaries(exchange_elements.trades) or None
@@ -273,3 +211,43 @@ def _fill_protocol_automation_state(
273211
"positions": positions,
274212
}
275213
)
214+
215+
216+
def _order_summaries_from_open_orders(open_orders: list[dict]) -> list[protocol_models.OrderSummary]:
217+
order_columns = octobot_trading_enums.ExchangeConstantsOrderColumns
218+
summaries: list[protocol_models.OrderSummary] = []
219+
for order in open_orders:
220+
inner = order.get(octobot_trading_constants.STORAGE_ORIGIN_VALUE, order)
221+
if not isinstance(inner, dict):
222+
inner = order
223+
order_id = inner.get(order_columns.EXCHANGE_ID.value) or inner.get(order_columns.ID.value)
224+
symbol = inner.get(order_columns.SYMBOL.value)
225+
if order_id is None or symbol is None:
226+
continue
227+
summaries.append(protocol_models.OrderSummary(id=str(order_id), symbol=str(symbol)))
228+
return summaries
229+
230+
231+
def _position_summaries(positions: list[typing.Any]) -> list[protocol_models.PositionSummary]:
232+
position_columns = octobot_trading_enums.ExchangeConstantsPositionColumns
233+
summaries: list[protocol_models.PositionSummary] = []
234+
for position_details in positions:
235+
position_dict = position_details.position
236+
position_id = position_dict.get(position_columns.ID.value)
237+
symbol = position_dict.get(position_columns.SYMBOL.value)
238+
if position_id is None or symbol is None:
239+
continue
240+
summaries.append(protocol_models.PositionSummary(id=str(position_id), symbol=str(symbol)))
241+
return summaries
242+
243+
244+
def _trade_summaries(trades: list[dict]) -> list[protocol_models.TradeSummary]:
245+
order_columns = octobot_trading_enums.ExchangeConstantsOrderColumns
246+
summaries: list[protocol_models.TradeSummary] = []
247+
for trade in trades:
248+
trade_id = trade.get(order_columns.EXCHANGE_TRADE_ID.value) or trade.get(order_columns.EXCHANGE_ID.value)
249+
symbol = trade.get(order_columns.SYMBOL.value)
250+
if trade_id is None or symbol is None:
251+
continue
252+
summaries.append(protocol_models.TradeSummary(id=str(trade_id), symbol=str(symbol)))
253+
return summaries

packages/node/octobot_node/scheduler/user_actions/user_actions_executor/account_user_action_executor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def _build_failure_user_action_result(
4949
def _get_error_message(self, exc: BaseException) -> protocol_models.AccountActionResultErrorMessage:
5050
if isinstance(exc, (node_errors.AccountNotFoundError, collection_errors.ItemNotFoundError)):
5151
return protocol_models.AccountActionResultErrorMessage.ACCOUNT_NOT_FOUND
52+
if isinstance(exc, node_errors.AccountAuthenticationDetailsNotFoundError):
53+
return protocol_models.AccountActionResultErrorMessage.ACCOUNT_AUTHENTICATION_DETAILS_NOT_FOUND
5254
if isinstance(
5355
exc,
5456
(

packages/node/octobot_node/scheduler/user_actions/user_actions_executor/automation_user_action_executor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def _get_error_message(self, exc: BaseException) -> protocol_models.AutomationAc
5454
return protocol_models.AutomationActionResultErrorMessage.AUTOMATION_NOT_FOUND
5555
if isinstance(exc, node_errors.AccountNotFoundError):
5656
return protocol_models.AutomationActionResultErrorMessage.ACCOUNT_NOT_FOUND
57+
if isinstance(exc, node_errors.AccountAuthenticationDetailsNotFoundError):
58+
return protocol_models.AutomationActionResultErrorMessage.ACCOUNT_AUTHENTICATION_DETAILS_NOT_FOUND
5759
if isinstance(exc, node_errors.AutomationStrategyNotFoundError):
5860
return protocol_models.AutomationActionResultErrorMessage.STRATEGY_NOT_FOUND
5961
if isinstance(exc, node_errors.AutomationStrategyVersionMismatchError):

packages/node/octobot_node/scheduler/user_actions/user_actions_executor/create_account.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ async def _do_execute(
4444
user_action: protocol_models.UserAction,
4545
) -> None:
4646
create_payload = _get_create_account_payload(user_action)
47-
checked_account = await account_state_updater.update_account_state(create_payload.configuration)
47+
checked_account = await account_state_updater.update_account_state(
48+
create_payload.configuration,
49+
self._wallet_address,
50+
)
4851
collection_providers.AccountProvider.instance().create_item(
4952
self._wallet_address,
5053
checked_account,

packages/node/octobot_node/scheduler/user_actions/user_actions_executor/create_automation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ def _create_automation_actions(self, user_action: protocol_models.UserAction) ->
132132
automation_id=user_action.id,
133133
protocol_account=protocol_account,
134134
strategy_reference=automation_configuration.strategy,
135+
wallet_address=self._wallet_address,
136+
reference_market=stored_strategy.reference_market,
135137
)
136138

137139
match inner_configuration:
@@ -162,6 +164,8 @@ def _create_automation_actions(self, user_action: protocol_models.UserAction) ->
162164
init_action,
163165
market_making_configuration,
164166
protocol_account,
167+
self._wallet_address,
168+
stored_strategy.reference_market,
165169
),
166170
]
167171
case _:

packages/node/octobot_node/scheduler/user_actions/user_actions_executor/edit_account.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ async def _do_execute(
5252
raise node_errors.InvalidUserActionPayloadError(
5353
"EditAccountConfiguration.id must match configuration.id."
5454
)
55-
checked_account = await account_state_updater.update_account_state(edit_payload.configuration)
55+
checked_account = await account_state_updater.update_account_state(
56+
edit_payload.configuration,
57+
self._wallet_address,
58+
)
5659
collection_providers.AccountProvider.instance().update_item(
5760
self._wallet_address,
5861
checked_account,

0 commit comments

Comments
 (0)