investing-algorithm-framework 7.19.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +197 -0
- investing_algorithm_framework/app/__init__.py +47 -0
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +2204 -0
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1667 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/__init__.py +35 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
- investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
- investing_algorithm_framework/app/strategy.py +675 -0
- investing_algorithm_framework/app/task.py +41 -0
- investing_algorithm_framework/app/web/__init__.py +5 -0
- investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
- investing_algorithm_framework/app/web/controllers/orders.py +20 -0
- investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
- investing_algorithm_framework/app/web/controllers/positions.py +18 -0
- investing_algorithm_framework/app/web/create_app.py +20 -0
- investing_algorithm_framework/app/web/error_handler.py +59 -0
- investing_algorithm_framework/app/web/responses.py +20 -0
- investing_algorithm_framework/app/web/run_strategies.py +4 -0
- investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
- investing_algorithm_framework/app/web/schemas/order.py +12 -0
- investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
- investing_algorithm_framework/app/web/schemas/position.py +15 -0
- investing_algorithm_framework/app/web/setup_cors.py +6 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +207 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +54 -0
- investing_algorithm_framework/dependency_container.py +155 -0
- investing_algorithm_framework/domain/__init__.py +148 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +111 -0
- investing_algorithm_framework/domain/constants.py +83 -0
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +112 -0
- investing_algorithm_framework/domain/models/__init__.py +43 -0
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +25 -0
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +384 -0
- investing_algorithm_framework/domain/models/order/order_side.py +36 -0
- investing_algorithm_framework/domain/models/order/order_status.py +37 -0
- investing_algorithm_framework/domain/models/order/order_type.py +30 -0
- investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +4 -0
- investing_algorithm_framework/domain/models/position/position.py +68 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +153 -0
- investing_algorithm_framework/domain/models/time_interval.py +124 -0
- investing_algorithm_framework/domain/models/time_unit.py +149 -0
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
- investing_algorithm_framework/domain/models/trade/trade.py +388 -0
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/positions/__init__.py +4 -0
- investing_algorithm_framework/domain/positions/position_size.py +41 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/stateless_actions.py +7 -0
- investing_algorithm_framework/domain/strategy.py +44 -0
- investing_algorithm_framework/domain/utils/__init__.py +27 -0
- investing_algorithm_framework/domain/utils/csv.py +104 -0
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +41 -0
- investing_algorithm_framework/domain/utils/signatures.py +17 -0
- investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
- investing_algorithm_framework/domain/utils/synchronized.py +12 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +50 -0
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
- investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +132 -0
- investing_algorithm_framework/services/backtesting/__init__.py +5 -0
- investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
- investing_algorithm_framework/services/configuration_service.py +96 -0
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
- investing_algorithm_framework/services/repository_service.py +40 -0
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
- investing_algorithm_framework/services/trade_service/__init__.py +3 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
- investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
- investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
- investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
- investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
- investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
4
|
+
AbstractPortfolioSyncService, ENVIRONMENT, Environment
|
|
5
|
+
from investing_algorithm_framework.services.trade_service import TradeService
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PortfolioSyncService(AbstractPortfolioSyncService):
|
|
11
|
+
"""
|
|
12
|
+
Service to sync the portfolio with the exchange.
|
|
13
|
+
|
|
14
|
+
This service will sync the portfolio with the exchange
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
trade_service: TradeService object
|
|
18
|
+
configuration_service: ConfigurationService object
|
|
19
|
+
order_service: OrderService object
|
|
20
|
+
position_repository: PositionRepository object
|
|
21
|
+
portfolio_repository: PortfolioRepository object
|
|
22
|
+
market_credential_service: MarketCredentialService object
|
|
23
|
+
portfolio_configuration_service: PortfolioConfigurationService object
|
|
24
|
+
portfolio_provider_lookup: PortfolioProviderLookup object
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
trade_service: TradeService,
|
|
30
|
+
configuration_service,
|
|
31
|
+
order_service,
|
|
32
|
+
position_repository,
|
|
33
|
+
portfolio_repository,
|
|
34
|
+
portfolio_configuration_service,
|
|
35
|
+
market_credential_service,
|
|
36
|
+
portfolio_provider_lookup
|
|
37
|
+
):
|
|
38
|
+
self.trade_service = trade_service
|
|
39
|
+
self.configuration_service = configuration_service
|
|
40
|
+
self.order_service = order_service
|
|
41
|
+
self.position_repository = position_repository
|
|
42
|
+
self.portfolio_repository = portfolio_repository
|
|
43
|
+
self.market_credential_service = market_credential_service
|
|
44
|
+
self.portfolio_configuration_service = portfolio_configuration_service
|
|
45
|
+
self.portfolio_provider_lookup = portfolio_provider_lookup
|
|
46
|
+
|
|
47
|
+
def sync_unallocated(self, portfolio):
|
|
48
|
+
"""
|
|
49
|
+
Method to sync the unallocated balance of the portfolio with the
|
|
50
|
+
available balance on the exchange. This method will retrieve the
|
|
51
|
+
available balance of the portfolio from the exchange and update the
|
|
52
|
+
unallocated balance of the portfolio accordingly.
|
|
53
|
+
|
|
54
|
+
If the portfolio already exists (exists in the database),
|
|
55
|
+
then a check is done if the exchange has the available
|
|
56
|
+
balance of the portfolio unallocated balance. If the exchange
|
|
57
|
+
does not have the available balance of the portfolio,
|
|
58
|
+
an OperationalException will be raised.
|
|
59
|
+
|
|
60
|
+
If the portfolio does not exist, the portfolio will be created with
|
|
61
|
+
the unallocated balance of the portfolio set to the available
|
|
62
|
+
balance on the exchange. If also a initial balance is set in
|
|
63
|
+
the portfolio configuration, the unallocated balance will be set
|
|
64
|
+
to the initial balance (given the balance is available on
|
|
65
|
+
the exchange). If the initial balance is not set, the
|
|
66
|
+
unallocated balance will be set to the available balance
|
|
67
|
+
on the exchange.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
portfolio: Portfolio object
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Portfolio object
|
|
74
|
+
"""
|
|
75
|
+
market_credential = self.market_credential_service.get(
|
|
76
|
+
portfolio.market
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if market_credential is None:
|
|
80
|
+
raise OperationalException(
|
|
81
|
+
f"No market credential found for market "
|
|
82
|
+
f"{portfolio.market}. Cannot sync unallocated amount."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
portfolio_provider = self.portfolio_provider_lookup\
|
|
86
|
+
.get_portfolio_provider(portfolio.market)
|
|
87
|
+
position = portfolio_provider.get_position(
|
|
88
|
+
portfolio, portfolio.trading_symbol, market_credential
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if not portfolio.initialized:
|
|
92
|
+
# Check if the portfolio has an initial balance set
|
|
93
|
+
if portfolio.initial_balance is not None:
|
|
94
|
+
available = position.amount
|
|
95
|
+
|
|
96
|
+
if portfolio.initial_balance > available:
|
|
97
|
+
raise OperationalException(
|
|
98
|
+
"The initial balance of the " +
|
|
99
|
+
"portfolio configuration " +
|
|
100
|
+
f"({portfolio.initial_balance} "
|
|
101
|
+
f"{portfolio.trading_symbol}) is more " +
|
|
102
|
+
"than the available balance on the exchange. " +
|
|
103
|
+
"Please make sure that the initial balance of " +
|
|
104
|
+
"the portfolio configuration is less " +
|
|
105
|
+
"than the available balance on the " +
|
|
106
|
+
f"exchange {available} {portfolio.trading_symbol}."
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
unallocated = portfolio.initial_balance
|
|
110
|
+
else:
|
|
111
|
+
# If the portfolio does not have an initial balance
|
|
112
|
+
# set, get the available balance on the exchange
|
|
113
|
+
if position is None:
|
|
114
|
+
raise OperationalException(
|
|
115
|
+
f"There is no available balance on the exchange for "
|
|
116
|
+
f"{portfolio.trading_symbol.upper()} on market "
|
|
117
|
+
f"{portfolio.market}. Please make sure that you have "
|
|
118
|
+
f"an available balance on the exchange for "
|
|
119
|
+
f"{portfolio.trading_symbol.upper()} on market "
|
|
120
|
+
f"{portfolio.market}."
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
unallocated = position.amount
|
|
124
|
+
|
|
125
|
+
update_data = {
|
|
126
|
+
"unallocated": unallocated,
|
|
127
|
+
"net_size": unallocated,
|
|
128
|
+
"initialized": True
|
|
129
|
+
}
|
|
130
|
+
portfolio = self.portfolio_repository.update(
|
|
131
|
+
portfolio.id, update_data
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Update also a trading symbol position
|
|
135
|
+
trading_symbol_position = self.position_repository.find(
|
|
136
|
+
{
|
|
137
|
+
"symbol": portfolio.trading_symbol,
|
|
138
|
+
"portfolio_id": portfolio.id
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
self.position_repository.update(
|
|
142
|
+
trading_symbol_position.id, {"amount": unallocated}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
else:
|
|
146
|
+
# Check if the portfolio unallocated balance is
|
|
147
|
+
# available on the exchange
|
|
148
|
+
if portfolio.unallocated > 0:
|
|
149
|
+
if position is None or portfolio.unallocated > position.amount:
|
|
150
|
+
raise OperationalException(
|
|
151
|
+
f"Out of sync: the unallocated balance"
|
|
152
|
+
" of the exiting portfolio is more than the available"
|
|
153
|
+
" balance on the exchange. Please make sure"
|
|
154
|
+
" that you have at least "
|
|
155
|
+
f"{portfolio.unallocated}"
|
|
156
|
+
f" {portfolio.trading_symbol.upper()} available"
|
|
157
|
+
" on the exchange."
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return portfolio
|
|
161
|
+
|
|
162
|
+
def sync_orders(self, portfolio):
|
|
163
|
+
"""
|
|
164
|
+
Function to sync all local orders with the orders on the exchange.
|
|
165
|
+
This method will go over all local open orders and check if they are
|
|
166
|
+
changed on the exchange. If they are, the local order will be
|
|
167
|
+
updated to match the status on the exchange.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
portfolio: Portfolio object
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
None
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
config = self.configuration_service.get_config()
|
|
177
|
+
|
|
178
|
+
if ENVIRONMENT in config \
|
|
179
|
+
and Environment.BACKTEST.equals(config[ENVIRONMENT]):
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
self.order_service.check_pending_orders(portfolio)
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.services.repository_service import \
|
|
4
|
+
RepositoryService
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PositionService(RepositoryService):
|
|
11
|
+
|
|
12
|
+
def __init__(self, repository, portfolio_repository):
|
|
13
|
+
"""
|
|
14
|
+
Initialize the PositionService.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
repository (Repository): The repository to use for storing
|
|
18
|
+
positions.
|
|
19
|
+
portfolio_repository (Repository): The repository to use for
|
|
20
|
+
storing portfolios.
|
|
21
|
+
"""
|
|
22
|
+
super().__init__(repository)
|
|
23
|
+
self.portfolio_repository = portfolio_repository
|
|
24
|
+
|
|
25
|
+
def update(self, position_id, data):
|
|
26
|
+
"""
|
|
27
|
+
Function to update a position.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
position_id (str): The id of the position to update.
|
|
31
|
+
data (dict): The data to update the position with.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Position: The updated position.
|
|
35
|
+
"""
|
|
36
|
+
position = self.get(position_id)
|
|
37
|
+
logger.info(
|
|
38
|
+
f"Updating position {position_id} ({position.get_symbol()}) "
|
|
39
|
+
f"with data: {data}"
|
|
40
|
+
)
|
|
41
|
+
return super().update(position_id, data)
|
|
42
|
+
|
|
43
|
+
def update_positions_with_created_buy_order(self, order):
|
|
44
|
+
"""
|
|
45
|
+
Function to update positions with created order.
|
|
46
|
+
If the order is filled then also the amount of the position
|
|
47
|
+
is updated.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
order (Order): The order that has been created.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
None
|
|
54
|
+
"""
|
|
55
|
+
position = self.get(order.position_id)
|
|
56
|
+
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
57
|
+
size = order.get_size()
|
|
58
|
+
filled = order.get_filled()
|
|
59
|
+
|
|
60
|
+
logger.info(
|
|
61
|
+
f"Syncing trading symbol {portfolio.get_trading_symbol()} "
|
|
62
|
+
"position with created buy "
|
|
63
|
+
f"order {order.get_id()} with size {size}"
|
|
64
|
+
)
|
|
65
|
+
trading_symbol_position = self.find(
|
|
66
|
+
{
|
|
67
|
+
"portfolio": portfolio.id,
|
|
68
|
+
"symbol": portfolio.trading_symbol
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
self.update(
|
|
72
|
+
trading_symbol_position.id,
|
|
73
|
+
{
|
|
74
|
+
"amount": trading_symbol_position.get_amount() - size
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if filled > 0:
|
|
79
|
+
logger.info(
|
|
80
|
+
f"Syncing position {position.get_symbol()} with created buy "
|
|
81
|
+
f"order {order.get_id()} with filled size {order.get_filled()}"
|
|
82
|
+
)
|
|
83
|
+
self.update(
|
|
84
|
+
position.id,
|
|
85
|
+
{
|
|
86
|
+
"amount": position.get_amount() + order.get_filled(),
|
|
87
|
+
"cost": position.get_cost() + size,
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def update_positions_with_buy_order_filled(self, order, filled_amount):
|
|
92
|
+
"""
|
|
93
|
+
Function to update positions with filled order.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
order (Order): The order that has been filled.
|
|
97
|
+
filled_amount (float): The amount that has been filled.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
None
|
|
101
|
+
"""
|
|
102
|
+
# Calculate the filled size
|
|
103
|
+
filled_size = filled_amount * order.get_price()
|
|
104
|
+
|
|
105
|
+
if filled_amount <= 0:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
logger.info(
|
|
109
|
+
f"Syncing position with filled buy "
|
|
110
|
+
f"order {order.get_id()} with filled amount "
|
|
111
|
+
f"{filled_amount}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Update the position
|
|
115
|
+
position = self.get(order.position_id)
|
|
116
|
+
self.update(
|
|
117
|
+
position.id,
|
|
118
|
+
{
|
|
119
|
+
"amount": position.get_amount() + filled_amount,
|
|
120
|
+
"cost":
|
|
121
|
+
position.get_cost() + filled_size
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def update_positions_with_created_sell_order(self, order):
|
|
126
|
+
"""
|
|
127
|
+
Function to update positions with created order.
|
|
128
|
+
If the order is filled then also the amount of the position
|
|
129
|
+
is updated.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
order (Order): The order that has been created.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
None
|
|
136
|
+
"""
|
|
137
|
+
position = self.get(order.position_id)
|
|
138
|
+
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
139
|
+
filled = order.get_filled()
|
|
140
|
+
filled_size = filled * order.get_price()
|
|
141
|
+
|
|
142
|
+
logger.info(
|
|
143
|
+
f"Syncing position {position.get_symbol()} "
|
|
144
|
+
"with created sell "
|
|
145
|
+
f"order {order.get_id()} with amount {order.get_amount()}"
|
|
146
|
+
)
|
|
147
|
+
self.update(
|
|
148
|
+
position.id,
|
|
149
|
+
{
|
|
150
|
+
"amount": position.get_amount() - order.get_amount(),
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if filled > 0:
|
|
155
|
+
|
|
156
|
+
logger.info(
|
|
157
|
+
f"Syncing trading symbol {portfolio.get_trading_symbol()} "
|
|
158
|
+
"position with created sell "
|
|
159
|
+
f"order {order.get_id()} with filled size {filled_size}"
|
|
160
|
+
)
|
|
161
|
+
trading_symbol_position = self.find(
|
|
162
|
+
{
|
|
163
|
+
"portfolio": portfolio.id,
|
|
164
|
+
"symbol": portfolio.trading_symbol
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
self.update(
|
|
168
|
+
trading_symbol_position.id,
|
|
169
|
+
{
|
|
170
|
+
"amount":
|
|
171
|
+
trading_symbol_position.get_amount() + filled_size
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def update_positions_with_sell_filled_order(self, order, filled_amount):
|
|
176
|
+
"""
|
|
177
|
+
Function to update positions with filled order.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
order:
|
|
181
|
+
filled_amount:
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
position = self.get(order.position_id)
|
|
187
|
+
portfolio = self.portfolio_repository.get(position.portfolio_id)
|
|
188
|
+
trading_symbol_position = self.find(
|
|
189
|
+
{
|
|
190
|
+
"portfolio": portfolio.id,
|
|
191
|
+
"symbol": portfolio.trading_symbol
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
filled_size = filled_amount * order.get_price()
|
|
195
|
+
|
|
196
|
+
logger.info(
|
|
197
|
+
"Syncing trading symbol position "
|
|
198
|
+
f"{portfolio.get_trading_symbol()} "
|
|
199
|
+
f"with filled sell "
|
|
200
|
+
f"order {order.get_id()} with filled size "
|
|
201
|
+
f"{filled_size} {portfolio.get_trading_symbol()}"
|
|
202
|
+
)
|
|
203
|
+
# Update the trading symbol position
|
|
204
|
+
self.update(
|
|
205
|
+
trading_symbol_position.id,
|
|
206
|
+
{
|
|
207
|
+
"amount":
|
|
208
|
+
trading_symbol_position.get_amount() + filled_size
|
|
209
|
+
}
|
|
210
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from investing_algorithm_framework.services.repository_service \
|
|
2
|
+
import RepositoryService
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PositionSnapshotService(RepositoryService):
|
|
6
|
+
|
|
7
|
+
def create_snapshot(self, portfolio_snapshot_id, position):
|
|
8
|
+
self.create(
|
|
9
|
+
{
|
|
10
|
+
"portfolio_snapshot_id": portfolio_snapshot_id,
|
|
11
|
+
"symbol": position.symbol,
|
|
12
|
+
"amount": position.amount,
|
|
13
|
+
"cost": position.cost,
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def get_snapshots(self, portfolio_snapshot_id):
|
|
18
|
+
pass
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class RepositoryService:
|
|
2
|
+
|
|
3
|
+
def __init__(self, repository):
|
|
4
|
+
self.repository = repository
|
|
5
|
+
|
|
6
|
+
def create(self, data, save=True):
|
|
7
|
+
return self.repository.create(data, save=save)
|
|
8
|
+
|
|
9
|
+
def get(self, object_id):
|
|
10
|
+
return self.repository.get(object_id)
|
|
11
|
+
|
|
12
|
+
def get_all(self, query_params=None):
|
|
13
|
+
return self.repository.get_all(query_params)
|
|
14
|
+
|
|
15
|
+
def update(self, object_id, data):
|
|
16
|
+
return self.repository.update(object_id, data)
|
|
17
|
+
|
|
18
|
+
def update_all(self, query_params, data):
|
|
19
|
+
return self.repository.update_all(query_params, data)
|
|
20
|
+
|
|
21
|
+
def delete(self, object_id):
|
|
22
|
+
return self.repository.delete(object_id)
|
|
23
|
+
|
|
24
|
+
def delete_all(self, query_params):
|
|
25
|
+
return self.repository.delete_all(query_params)
|
|
26
|
+
|
|
27
|
+
def find(self, query_params):
|
|
28
|
+
return self.repository.find(query_params)
|
|
29
|
+
|
|
30
|
+
def count(self, query_params=None):
|
|
31
|
+
return self.repository.count(query_params)
|
|
32
|
+
|
|
33
|
+
def exists(self, query_params):
|
|
34
|
+
return self.repository.exists(query_params)
|
|
35
|
+
|
|
36
|
+
def save(self, object):
|
|
37
|
+
return self.repository.save(object)
|
|
38
|
+
|
|
39
|
+
def save_all(self, objects):
|
|
40
|
+
return self.repository.save_objects(objects)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
2
|
+
from .backtest_trade_oder_evaluator import BacktestTradeOrderEvaluator
|
|
3
|
+
from .default_trade_order_evaluator import DefaultTradeOrderEvaluator
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"TradeOrderEvaluator",
|
|
7
|
+
"BacktestTradeOrderEvaluator",
|
|
8
|
+
"DefaultTradeOrderEvaluator"
|
|
9
|
+
]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import OrderSide, OrderStatus, \
|
|
6
|
+
Trade, Order
|
|
7
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BacktestTradeOrderEvaluator(TradeOrderEvaluator):
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
trade_service,
|
|
15
|
+
order_service
|
|
16
|
+
):
|
|
17
|
+
super().__init__(trade_service, order_service)
|
|
18
|
+
|
|
19
|
+
def evaluate(
|
|
20
|
+
self,
|
|
21
|
+
open_trades: List[Trade],
|
|
22
|
+
open_orders: List[Order],
|
|
23
|
+
ohlcv_data: Dict[str, pl.DataFrame]
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Evaluate trades and orders based on OHLCV data.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
open_orders (List[Order]): List of open Order objects.
|
|
30
|
+
open_trades (List[Trade]): List of open Trade objects.
|
|
31
|
+
ohlcv_data (dict[str, pl.DataFrame]): Mapping of
|
|
32
|
+
symbol -> OHLCV Polars DataFrame.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List[dict]: Updated trades with latest prices and execution status.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# First check pending orders
|
|
39
|
+
for open_order in open_orders:
|
|
40
|
+
data = ohlcv_data.get(open_order.symbol)
|
|
41
|
+
self._check_has_executed(open_order, data)
|
|
42
|
+
|
|
43
|
+
# self.order_service.save_all(orders)
|
|
44
|
+
if len(open_trades) > 0:
|
|
45
|
+
for open_trade in open_trades:
|
|
46
|
+
data = ohlcv_data[open_trade.symbol]
|
|
47
|
+
|
|
48
|
+
if data is None or data.is_empty():
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
# Get last row of data
|
|
52
|
+
last_row = data.tail(1)
|
|
53
|
+
update_data = {
|
|
54
|
+
"last_reported_price": last_row["Close"][0],
|
|
55
|
+
"last_reported_price_datetime": last_row["Datetime"][0],
|
|
56
|
+
"updated_at": last_row["Datetime"][0]
|
|
57
|
+
}
|
|
58
|
+
open_trade.update(update_data)
|
|
59
|
+
|
|
60
|
+
self.trade_service.save_all(open_trades)
|
|
61
|
+
|
|
62
|
+
stop_losses_orders_data = self.trade_service \
|
|
63
|
+
.get_triggered_stop_loss_orders()
|
|
64
|
+
|
|
65
|
+
for stop_loss_order in stop_losses_orders_data:
|
|
66
|
+
self.order_service.create(stop_loss_order)
|
|
67
|
+
|
|
68
|
+
take_profits_orders_data = self.trade_service \
|
|
69
|
+
.get_triggered_take_profit_orders()
|
|
70
|
+
|
|
71
|
+
for take_profit_order in take_profits_orders_data:
|
|
72
|
+
self.order_service.create(take_profit_order)
|
|
73
|
+
|
|
74
|
+
def _check_has_executed(self, order, ohlcv_df):
|
|
75
|
+
"""
|
|
76
|
+
Check if the order has been executed based on OHLCV data.
|
|
77
|
+
|
|
78
|
+
BUY ORDER filled Rules:
|
|
79
|
+
- Only uses prices after the last update_at of the order.
|
|
80
|
+
- If the lowest low price of the series is below or equal
|
|
81
|
+
to the order price, e.g. if you buy asset at price 100
|
|
82
|
+
and the low price of the series is 99, then the order is filled.
|
|
83
|
+
|
|
84
|
+
SELL ORDER filled Rules:
|
|
85
|
+
- Only uses prices after the last update_at of the order.
|
|
86
|
+
- If the highest high price of the series is above or equal
|
|
87
|
+
to the order price, e.g. if you sell asset at price 100
|
|
88
|
+
and the high price of the series is 101, then the order is filled.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
order (Order): Order.
|
|
92
|
+
ohlcv_df (pl.DataFrame): OHLCV DataFrame for the symbol.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
None
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
if ohlcv_df.is_empty():
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Extract attributes from the order object
|
|
102
|
+
updated_at = order.updated_at
|
|
103
|
+
order_side = order.order_side
|
|
104
|
+
order_price = order.price
|
|
105
|
+
ohlcv_data_after_order = ohlcv_df.filter(
|
|
106
|
+
pl.col('Datetime') >= updated_at
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if ohlcv_data_after_order.is_empty():
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
if OrderSide.BUY.equals(order_side):
|
|
113
|
+
# Check if the low price drops below or equals the order price
|
|
114
|
+
if (ohlcv_data_after_order['Low'] <= order_price).any():
|
|
115
|
+
self.order_service.update(
|
|
116
|
+
order.id, {
|
|
117
|
+
'status': OrderStatus.CLOSED.value,
|
|
118
|
+
'remaining': 0,
|
|
119
|
+
'filled': order.amount
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
elif OrderSide.SELL.equals(order_side):
|
|
124
|
+
# Check if the high price goes above or equals the order price
|
|
125
|
+
if (ohlcv_data_after_order['High'] >= order_price).any():
|
|
126
|
+
self.order_service.update(
|
|
127
|
+
order.id, {
|
|
128
|
+
'status': OrderStatus.CLOSED.value,
|
|
129
|
+
'remaining': 0,
|
|
130
|
+
'filled': order.amount
|
|
131
|
+
}
|
|
132
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import Trade, Order
|
|
6
|
+
from .trade_order_evaluator import TradeOrderEvaluator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DefaultTradeOrderEvaluator(TradeOrderEvaluator):
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
trade_service,
|
|
14
|
+
order_service
|
|
15
|
+
):
|
|
16
|
+
super().__init__(trade_service, order_service)
|
|
17
|
+
|
|
18
|
+
def evaluate(
|
|
19
|
+
self,
|
|
20
|
+
open_trades: List[Trade],
|
|
21
|
+
open_orders: List[Order],
|
|
22
|
+
ohlcv_data: Dict[str, pl.DataFrame]
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Evaluate trades and orders based on OHLCV data.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
open_orders (List[Order]): List of open Order objects.
|
|
29
|
+
open_trades (List[Trade]): List of open Trade objects.
|
|
30
|
+
ohlcv_data (dict[str, pl.DataFrame]): Mapping of
|
|
31
|
+
symbol -> OHLCV Polars DataFrame.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List[dict]: Updated trades with latest prices and execution status.
|
|
35
|
+
"""
|
|
36
|
+
self.order_service.check_pending_orders()
|
|
37
|
+
|
|
38
|
+
if len(open_trades) > 0:
|
|
39
|
+
for open_trade in open_trades:
|
|
40
|
+
data = ohlcv_data[open_trade.symbol]
|
|
41
|
+
|
|
42
|
+
if data is None or data.is_empty():
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
# Get last row of data
|
|
46
|
+
last_row = data.tail(1)
|
|
47
|
+
update_data = {
|
|
48
|
+
"last_reported_price": last_row["Close"][0],
|
|
49
|
+
"last_reported_price_datetime": last_row["Datetime"][0],
|
|
50
|
+
"updated_at": last_row["Datetime"][0]
|
|
51
|
+
}
|
|
52
|
+
open_trade.update(update_data)
|
|
53
|
+
|
|
54
|
+
self.trade_service.save_all(open_trades)
|
|
55
|
+
|
|
56
|
+
stop_losses_orders_data = self.trade_service \
|
|
57
|
+
.get_triggered_stop_loss_orders()
|
|
58
|
+
|
|
59
|
+
for stop_loss_order in stop_losses_orders_data:
|
|
60
|
+
self.order_service.create(stop_loss_order)
|
|
61
|
+
|
|
62
|
+
take_profits_orders_data = self.trade_service \
|
|
63
|
+
.get_triggered_take_profit_orders()
|
|
64
|
+
|
|
65
|
+
for take_profit_order in take_profits_orders_data:
|
|
66
|
+
self.order_service.create(take_profit_order)
|