Skip to content

Commit 4829515

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

97 files changed

Lines changed: 3660 additions & 542 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: 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_trading_details_provider as trading_details_provider
19+
20+
21+
def get_account_trading_details_state_encrypted(address: str) -> dict[str, str] | None:
22+
try:
23+
return trading_details_provider.AccountTradingDetailsProvider.instance().list_items_encrypted(address)
24+
except collection_errors.CollectionNoDataError:
25+
return None

packages/node/octobot_node/protocol/automations.py

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import octobot_commons.logging as octobot_commons_logging
2222
import octobot_commons.timestamp_util as octobot_commons_timestamp_util
2323
import octobot_flow.entities as flow_entities
24+
import octobot_node.constants as node_constants
2425
import octobot_node.models as node_models
2526
import octobot_node.scheduler.octobot_flow_client as octobot_flow_client
2627
import octobot_protocol.models as protocol_models
@@ -159,24 +160,39 @@ def _protocol_action_from_flow(
159160

160161

161162
def _enrich_protocol_assets(
162-
base_assets: list[protocol_models.Asset],
163+
base_assets: list[protocol_models.DetailedAsset],
163164
portfolio: exchange_data_import.PortfolioDetails,
164-
unit: typing.Optional[str],
165-
) -> list[protocol_models.Asset]:
166-
enriched: list[protocol_models.Asset] = []
165+
) -> list[protocol_models.DetailedAsset]:
166+
enriched: list[protocol_models.DetailedAsset] = []
167167
for asset in base_assets:
168-
update_fields: dict[str, typing.Any] = {}
169168
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))
169+
enriched.append(
170+
asset.model_copy(update={"value": float(portfolio.asset_values[asset.symbol])})
171+
)
175172
else:
176173
enriched.append(asset)
177174
return enriched
178175

179176

177+
def _build_protocol_portfolio_content(
178+
portfolio: exchange_data_import.PortfolioDetails,
179+
unit: typing.Optional[str],
180+
) -> protocol_models.PortfolioContent | None:
181+
if not portfolio.content:
182+
return None
183+
enriched_assets = _enrich_protocol_assets(
184+
octobot_trading_portfolios_protocol.to_protocol_assets(portfolio.content),
185+
portfolio,
186+
)
187+
portfolio_unit = unit or node_constants.DEFAULT_PORTFOLIO_VALUATION_UNIT
188+
portfolio_total = sum(float(asset.value or 0) for asset in enriched_assets)
189+
return protocol_models.PortfolioContent(
190+
total=portfolio_total,
191+
unit=portfolio_unit,
192+
assets=enriched_assets,
193+
)
194+
195+
180196
def _order_summaries_from_open_orders(open_orders: list[dict]) -> list[protocol_models.OrderSummary]:
181197
order_columns = octobot_trading_enums.ExchangeConstantsOrderColumns
182198
summaries: list[protocol_models.OrderSummary] = []
@@ -247,16 +263,17 @@ def _fill_protocol_automation_state(
247263
exchange_account_ids = [exchange_details.metadata.id]
248264
# Derive portfolio and trading summaries from automation exchange elements.
249265
exchange_elements = flow_automation_state.automation.exchange_account_elements
250-
assets: typing.Optional[list[protocol_models.Asset]] = None
266+
portfolio_content: typing.Optional[protocol_models.PortfolioContent] = None
251267
orders: typing.Optional[list[protocol_models.OrderSummary]] = None
252268
trades: typing.Optional[list[protocol_models.TradeSummary]] = None
253269
positions: typing.Optional[list[protocol_models.PositionSummary]] = None
254270
if exchange_elements:
255271
portfolio = exchange_elements.portfolio
256272
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)
273+
portfolio_unit = None
274+
if exchange_details and exchange_details.portfolio:
275+
portfolio_unit = exchange_details.portfolio.unit
276+
portfolio_content = _build_protocol_portfolio_content(portfolio, portfolio_unit)
260277
orders = _order_summaries_from_open_orders(exchange_elements.orders.open_orders) or None
261278
positions = _position_summaries(exchange_elements.positions) or None
262279
trades = _trade_summaries(exchange_elements.trades) or None
@@ -267,7 +284,7 @@ def _fill_protocol_automation_state(
267284
"priority_actions": priority_actions or None,
268285
"exchanges": exchanges,
269286
"exchange_account_ids": exchange_account_ids,
270-
"assets": assets,
287+
"portfolio_content": portfolio_content,
271288
"orders": orders,
272289
"trades": trades,
273290
"positions": positions,

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ 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,
135136
)
136137

137138
match inner_configuration:
@@ -162,6 +163,7 @@ def _create_automation_actions(self, user_action: protocol_models.UserAction) ->
162163
init_action,
163164
market_making_configuration,
164165
protocol_account,
166+
self._wallet_address,
165167
),
166168
]
167169
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)