investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__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 +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- 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 +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- 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 +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- 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_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- 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 +605 -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 +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- 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/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- 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/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
from datetime import datetime, timedelta, timezone
|
|
2
|
+
from time import sleep
|
|
3
|
+
from typing import List, Set, Dict
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
|
|
7
|
+
from investing_algorithm_framework.domain import Environment, ENVIRONMENT, \
|
|
8
|
+
OrderStatus, DataSource, DataType, tqdm, \
|
|
9
|
+
TradeStatus, SNAPSHOT_INTERVAL, SnapshotInterval, OperationalException, \
|
|
10
|
+
LAST_SNAPSHOT_DATETIME, INDEX_DATETIME
|
|
11
|
+
from investing_algorithm_framework.services import TradeOrderEvaluator
|
|
12
|
+
|
|
13
|
+
from .algorithm import Algorithm
|
|
14
|
+
from .strategy import TradingStrategy
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EventLoopService:
|
|
18
|
+
"""
|
|
19
|
+
A service that manages the event loop for the trading bot.
|
|
20
|
+
This service is responsible for running the trading strategy and handling
|
|
21
|
+
events in its lifecycle, such as pending orders changes, stop loss changes,
|
|
22
|
+
take profit changes, and price data updates.
|
|
23
|
+
|
|
24
|
+
The event loop runs strategies in a so called interation, which consists
|
|
25
|
+
out of various tasks. An iteration consists out of the following tasks:
|
|
26
|
+
|
|
27
|
+
- Collect all strategies and tasks that need to be
|
|
28
|
+
run (overdue on schedule)
|
|
29
|
+
- Collect all market data for the strategies
|
|
30
|
+
- Check pending orders, stop losses, and take profits
|
|
31
|
+
- Run all tasks
|
|
32
|
+
- Run all strategies
|
|
33
|
+
- Run all on_strategy_run hooks
|
|
34
|
+
- Snapshot the portfolios based on the defined snapshot interval
|
|
35
|
+
|
|
36
|
+
The goal of this service is to provide a way to run the trading in the
|
|
37
|
+
most efficient way possible in both live trading and backtesting. This
|
|
38
|
+
is achieved by running strategies and tasks in a loop, where each
|
|
39
|
+
iteration checks which strategies and tasks are due to run based on their
|
|
40
|
+
defined intervals and time units (seconds, minutes, hours). The next run
|
|
41
|
+
times for each strategy are initialized to the current time in UTC.
|
|
42
|
+
The service also collects all data configurations from the strategies and
|
|
43
|
+
tasks, and runs them in a single iteration to avoid multiple calls to the
|
|
44
|
+
data provider service, which can be expensive in terms of performance.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
context,
|
|
50
|
+
order_service,
|
|
51
|
+
trade_service,
|
|
52
|
+
portfolio_service,
|
|
53
|
+
configuration_service,
|
|
54
|
+
data_provider_service,
|
|
55
|
+
portfolio_snapshot_service
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Initializes the event loop service with the provided algorithm.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
order_service: The service responsible for managing orders.
|
|
62
|
+
portfolio_service: The service responsible for managing portfolios.
|
|
63
|
+
configuration_service: The service responsible for configuration.
|
|
64
|
+
"""
|
|
65
|
+
self.tasks = []
|
|
66
|
+
self.context = context
|
|
67
|
+
self._algorithm = None
|
|
68
|
+
self.strategies = []
|
|
69
|
+
self._strategies_lookup = {}
|
|
70
|
+
self._snapshots = []
|
|
71
|
+
self._tasks_lookup = {}
|
|
72
|
+
self._order_service = order_service
|
|
73
|
+
self._trade_service = trade_service
|
|
74
|
+
self._portfolio_service = portfolio_service
|
|
75
|
+
self._configuration_service = configuration_service
|
|
76
|
+
self._data_provider_service = data_provider_service
|
|
77
|
+
self._trade_order_evaluator = None
|
|
78
|
+
self._portfolio_snapshot_service = portfolio_snapshot_service
|
|
79
|
+
self.data_sources = set()
|
|
80
|
+
self.next_run_times = {}
|
|
81
|
+
self.history = {}
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _get_data_sources_for_iteration(
|
|
85
|
+
strategy_data_sources
|
|
86
|
+
) -> Set[DataSource]:
|
|
87
|
+
"""
|
|
88
|
+
Return a list of data sources that need to be fetched for the
|
|
89
|
+
current iteration based on the strategies.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
strategy_data_sources: List of data sources defined
|
|
93
|
+
in the strategies.
|
|
94
|
+
Returns:
|
|
95
|
+
Set[DataSource]: A set of data sources that need to
|
|
96
|
+
be fetched.
|
|
97
|
+
"""
|
|
98
|
+
data_sources = set(strategy_data_sources)
|
|
99
|
+
return data_sources
|
|
100
|
+
|
|
101
|
+
def _get_pending_orders_and_trades_data_for_iteration(
|
|
102
|
+
self, pending_order, open_trades, date
|
|
103
|
+
) -> Dict:
|
|
104
|
+
"""
|
|
105
|
+
Returns a set of data sources that need to be fetched for the
|
|
106
|
+
current iteration based on the pending orders and open trades.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
pending_order: List of pending orders.
|
|
110
|
+
open_trades: List of open trades.
|
|
111
|
+
date: The current date for which the data is being fetched.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Set[DataSource]: A set of data sources that need to be fetched.
|
|
115
|
+
"""
|
|
116
|
+
symbol_set = set()
|
|
117
|
+
data_sources = set()
|
|
118
|
+
data = {}
|
|
119
|
+
|
|
120
|
+
for order in pending_order:
|
|
121
|
+
if order.symbol not in symbol_set:
|
|
122
|
+
data_sources.add(
|
|
123
|
+
DataSource(
|
|
124
|
+
symbol=order.symbol,
|
|
125
|
+
data_type=DataType.TICKER
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
symbol_set.add(order.symbol)
|
|
129
|
+
|
|
130
|
+
for trade in open_trades:
|
|
131
|
+
|
|
132
|
+
if trade.symbol not in symbol_set:
|
|
133
|
+
data_sources.add(
|
|
134
|
+
DataSource(symbol=trade.symbol, data_type=DataType.TICKER))
|
|
135
|
+
symbol_set.add(trade.symbol)
|
|
136
|
+
|
|
137
|
+
for symbol in symbol_set:
|
|
138
|
+
data[symbol] = self._data_provider_service\
|
|
139
|
+
.get_ohlcv_data(
|
|
140
|
+
symbol=symbol, date=date
|
|
141
|
+
)
|
|
142
|
+
return data
|
|
143
|
+
|
|
144
|
+
def _get_strategies(
|
|
145
|
+
self, strategy_ids: List[str]
|
|
146
|
+
) -> List[TradingStrategy]:
|
|
147
|
+
"""
|
|
148
|
+
Returns a list of strategies based on the provided strategy IDs.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
strategy_ids: A list of strategy IDs to retrieve.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List[TradingStrategy]: A list of strategies matching the
|
|
155
|
+
provided IDs.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
if not strategy_ids:
|
|
159
|
+
return self.strategies
|
|
160
|
+
|
|
161
|
+
return [
|
|
162
|
+
self._strategies_lookup[strategy_id]
|
|
163
|
+
for strategy_id in strategy_ids
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
def _get_due_strategies(self, current_datetime=None):
|
|
167
|
+
"""
|
|
168
|
+
Checks which strategies are due to run based on their defined intervals
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
current_datetime: Optional; the datetime to check against.
|
|
172
|
+
If None, uses the current datetime in UTC.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List[TradingStrategy]: A list of strategies that are due to run.
|
|
176
|
+
"""
|
|
177
|
+
due = []
|
|
178
|
+
|
|
179
|
+
if current_datetime is None:
|
|
180
|
+
current_datetime = datetime.now(timezone.utc)
|
|
181
|
+
|
|
182
|
+
for strategy in self.strategies:
|
|
183
|
+
time_unit = strategy.time_unit
|
|
184
|
+
interval = strategy.interval
|
|
185
|
+
interval = timedelta(
|
|
186
|
+
minutes=time_unit.amount_of_minutes * interval
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if current_datetime >= \
|
|
190
|
+
self.next_run_times[strategy.strategy_id]["next_run"]:
|
|
191
|
+
due.append(strategy)
|
|
192
|
+
self.next_run_times[strategy.strategy_id]["next_run"] = \
|
|
193
|
+
current_datetime + interval
|
|
194
|
+
|
|
195
|
+
return due
|
|
196
|
+
|
|
197
|
+
def _snapshot(
|
|
198
|
+
self,
|
|
199
|
+
current_datetime,
|
|
200
|
+
open_orders,
|
|
201
|
+
created_orders
|
|
202
|
+
):
|
|
203
|
+
"""
|
|
204
|
+
Takes a snapshot of the current state of the portfolios and trades.
|
|
205
|
+
This method is called based on the defined snapshot interval in the
|
|
206
|
+
configuration service. It creates a snapshot of the portfolio and
|
|
207
|
+
appends it to the snapshots list.
|
|
208
|
+
|
|
209
|
+
The function accepts the current datetime, open orders,
|
|
210
|
+
open trades, and created orders as parameters. The reason why
|
|
211
|
+
the orders and trades are passed is for efficiency, as we
|
|
212
|
+
do not want to fetch them again from the database if they are
|
|
213
|
+
already available in memory.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
current_datetime: The current datetime in UTC.
|
|
217
|
+
open_orders: List of open orders.
|
|
218
|
+
created_orders: List of created orders.
|
|
219
|
+
"""
|
|
220
|
+
snapshot_interval = self._configuration_service\
|
|
221
|
+
.config[SNAPSHOT_INTERVAL]
|
|
222
|
+
portfolio = self._portfolio_service.get_all()[0]
|
|
223
|
+
if SnapshotInterval.STRATEGY_ITERATION.equals(snapshot_interval):
|
|
224
|
+
snapshot = self._portfolio_snapshot_service.create_snapshot(
|
|
225
|
+
created_at=current_datetime,
|
|
226
|
+
portfolio=portfolio,
|
|
227
|
+
open_orders=open_orders,
|
|
228
|
+
created_orders=created_orders,
|
|
229
|
+
save=False,
|
|
230
|
+
)
|
|
231
|
+
self._snapshots.append(snapshot)
|
|
232
|
+
self._configuration_service.add_value(
|
|
233
|
+
LAST_SNAPSHOT_DATETIME, current_datetime
|
|
234
|
+
)
|
|
235
|
+
elif SnapshotInterval.DAILY.equals(snapshot_interval):
|
|
236
|
+
last_snapshot_datetime = self._configuration_service.config[
|
|
237
|
+
LAST_SNAPSHOT_DATETIME
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
# Check if the time difference is greater than 24 hours
|
|
241
|
+
if last_snapshot_datetime is None or \
|
|
242
|
+
(current_datetime - last_snapshot_datetime)\
|
|
243
|
+
.total_seconds() >= 86400:
|
|
244
|
+
snapshot = self._portfolio_snapshot_service.create_snapshot(
|
|
245
|
+
created_at=current_datetime,
|
|
246
|
+
portfolio=portfolio,
|
|
247
|
+
open_orders=open_orders,
|
|
248
|
+
created_orders=created_orders,
|
|
249
|
+
save=False,
|
|
250
|
+
)
|
|
251
|
+
self._snapshots.append(snapshot)
|
|
252
|
+
self._configuration_service.add_value(
|
|
253
|
+
LAST_SNAPSHOT_DATETIME, current_datetime
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def initialize(
|
|
257
|
+
self,
|
|
258
|
+
algorithm: Algorithm,
|
|
259
|
+
trade_order_evaluator: TradeOrderEvaluator
|
|
260
|
+
):
|
|
261
|
+
"""
|
|
262
|
+
Initializes the event loop service by calculating the schedule for
|
|
263
|
+
running strategies and tasks based on their defined intervals and time
|
|
264
|
+
units (seconds, minutes, hours).
|
|
265
|
+
|
|
266
|
+
The next run times for each strategy are initialized to the current
|
|
267
|
+
time in UTC.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
algorithm (Algorithm): The trading algorithm to be run by
|
|
271
|
+
the event loop. This should contain the strategies and
|
|
272
|
+
tasks to be executed.
|
|
273
|
+
trade_order_evaluator (TradeOrderEvaluator): The evaluator
|
|
274
|
+
responsible for checking and updating pending orders,
|
|
275
|
+
stop losses, and take profits.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
None
|
|
279
|
+
"""
|
|
280
|
+
self._algorithm = algorithm
|
|
281
|
+
self.strategies = algorithm.strategies
|
|
282
|
+
|
|
283
|
+
if len(self.strategies) == 0:
|
|
284
|
+
raise OperationalException(
|
|
285
|
+
"No strategies are defined in the algorithm. "
|
|
286
|
+
"Please add at least one strategy to the algorithm."
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
self._trade_order_evaluator = trade_order_evaluator
|
|
290
|
+
start_date = self._configuration_service.config[INDEX_DATETIME]
|
|
291
|
+
|
|
292
|
+
self.next_run_times = {
|
|
293
|
+
strategy.strategy_id: {
|
|
294
|
+
"next_run": start_date,
|
|
295
|
+
"data_sources": strategy.data_sources
|
|
296
|
+
}
|
|
297
|
+
for strategy in self.strategies
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# Collect all data sources and initialize history
|
|
301
|
+
for strategy in self.strategies:
|
|
302
|
+
|
|
303
|
+
if strategy.data_sources is not None:
|
|
304
|
+
self.data_sources = self.data_sources.union(
|
|
305
|
+
set(strategy.data_sources)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
self.history[strategy.strategy_id] = {"runs": []}
|
|
309
|
+
self._strategies_lookup[strategy.strategy_profile.strategy_id] \
|
|
310
|
+
= strategy
|
|
311
|
+
|
|
312
|
+
if self._trade_order_evaluator is None:
|
|
313
|
+
raise OperationalException(
|
|
314
|
+
"No trade order evaluator is set for the event loop service."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def cleanup(self):
|
|
318
|
+
self._portfolio_snapshot_service.save_all(self._snapshots)
|
|
319
|
+
|
|
320
|
+
def start(
|
|
321
|
+
self,
|
|
322
|
+
number_of_iterations=None,
|
|
323
|
+
schedule: pl.DataFrame = None,
|
|
324
|
+
show_progress: bool = False
|
|
325
|
+
):
|
|
326
|
+
"""
|
|
327
|
+
Runs the event loop for the trading algorithm. You can run the
|
|
328
|
+
event loop with different specifications:
|
|
329
|
+
|
|
330
|
+
- If `number_of_iterations` is provided, the event loop will run
|
|
331
|
+
for that many iterations.
|
|
332
|
+
- If `schedule` is provided, the event loop will run according to
|
|
333
|
+
the schedule, iterating through each row and using the "date"
|
|
334
|
+
column to determine the current date for that iteration.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
number_of_iterations: Optional; the number of iterations to run.
|
|
338
|
+
If None, runs indefinitely.
|
|
339
|
+
schedule: Dict Optional; a schedule to run the event loop with.
|
|
340
|
+
show_progress: Optional; whether to show progress bar for the
|
|
341
|
+
event loop. Defaults to False.
|
|
342
|
+
Returns:
|
|
343
|
+
None
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
if schedule is not None:
|
|
347
|
+
sorted_times = sorted(schedule.keys())
|
|
348
|
+
|
|
349
|
+
if show_progress:
|
|
350
|
+
for current_time in tqdm(
|
|
351
|
+
sorted_times,
|
|
352
|
+
total=len(sorted_times),
|
|
353
|
+
colour="GREEN",
|
|
354
|
+
desc="Running event backtest"
|
|
355
|
+
):
|
|
356
|
+
self._configuration_service.add_value(
|
|
357
|
+
INDEX_DATETIME, current_time
|
|
358
|
+
)
|
|
359
|
+
strategy_ids = schedule[current_time]["strategy_ids"]
|
|
360
|
+
# task_ids = schedule[current_time]["task_ids"]
|
|
361
|
+
strategies = self._get_strategies(strategy_ids)
|
|
362
|
+
self._run_iteration(strategies=strategies, tasks=[])
|
|
363
|
+
|
|
364
|
+
else:
|
|
365
|
+
for current_time in sorted_times:
|
|
366
|
+
self._configuration_service.add_value(
|
|
367
|
+
INDEX_DATETIME, current_time
|
|
368
|
+
)
|
|
369
|
+
strategy_ids = schedule[current_time]["strategy_ids"]
|
|
370
|
+
# task_ids = schedule[current_time]["task_ids"]
|
|
371
|
+
strategies = self._get_strategies(strategy_ids)
|
|
372
|
+
self._run_iteration(strategies=strategies, tasks=[])
|
|
373
|
+
else:
|
|
374
|
+
if number_of_iterations is None:
|
|
375
|
+
try:
|
|
376
|
+
config = self._configuration_service.config
|
|
377
|
+
current_time = config[INDEX_DATETIME]
|
|
378
|
+
strategies = self._get_due_strategies(current_time)
|
|
379
|
+
self._run_iteration(strategies)
|
|
380
|
+
current_time = datetime.now(timezone.utc)
|
|
381
|
+
self._configuration_service.add_value(
|
|
382
|
+
INDEX_DATETIME, current_time
|
|
383
|
+
)
|
|
384
|
+
sleep(1)
|
|
385
|
+
except KeyboardInterrupt:
|
|
386
|
+
exit(0)
|
|
387
|
+
else:
|
|
388
|
+
|
|
389
|
+
if show_progress:
|
|
390
|
+
for _ in tqdm(
|
|
391
|
+
range(number_of_iterations),
|
|
392
|
+
colour="GREEN"
|
|
393
|
+
):
|
|
394
|
+
try:
|
|
395
|
+
config = self._configuration_service.config
|
|
396
|
+
current_time = config[INDEX_DATETIME]
|
|
397
|
+
strategies = self._get_due_strategies(current_time)
|
|
398
|
+
self._run_iteration(strategies)
|
|
399
|
+
current_time = datetime.now(timezone.utc)
|
|
400
|
+
self._configuration_service.add_value(
|
|
401
|
+
INDEX_DATETIME, current_time
|
|
402
|
+
)
|
|
403
|
+
sleep(1)
|
|
404
|
+
except KeyboardInterrupt:
|
|
405
|
+
exit(0)
|
|
406
|
+
|
|
407
|
+
for _ in range(number_of_iterations):
|
|
408
|
+
try:
|
|
409
|
+
config = self._configuration_service.config
|
|
410
|
+
current_time = config[INDEX_DATETIME]
|
|
411
|
+
strategies = self._get_due_strategies(current_time)
|
|
412
|
+
self._run_iteration(strategies)
|
|
413
|
+
current_time = datetime.now(timezone.utc)
|
|
414
|
+
self._configuration_service.add_value(
|
|
415
|
+
INDEX_DATETIME, current_time
|
|
416
|
+
)
|
|
417
|
+
sleep(1)
|
|
418
|
+
except KeyboardInterrupt:
|
|
419
|
+
exit(0)
|
|
420
|
+
|
|
421
|
+
self.cleanup()
|
|
422
|
+
|
|
423
|
+
def _run_iteration(
|
|
424
|
+
self,
|
|
425
|
+
strategies: List[TradingStrategy] = None,
|
|
426
|
+
tasks: List = None
|
|
427
|
+
):
|
|
428
|
+
"""
|
|
429
|
+
Runs a single iteration of the event loop. This method collects all
|
|
430
|
+
due strategies, fetches their data configurations, and runs the
|
|
431
|
+
strategies with the collected data. It also checks for pending orders,
|
|
432
|
+
stop loss orders, and take profit orders, and updates their status if
|
|
433
|
+
needed. Finally, it runs all tasks and strategies, and takes a snapshot
|
|
434
|
+
of the portfolios if needed.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
strategies: Optional; a list of strategies to
|
|
438
|
+
run in this iteration. If None, uses the strategies
|
|
439
|
+
defined in the event loop service.
|
|
440
|
+
tasks: Optional; a list of tasks to run in this iteration.
|
|
441
|
+
If None, uses the tasks defined in the event loop service.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
None
|
|
445
|
+
"""
|
|
446
|
+
config = self._configuration_service.get_config()
|
|
447
|
+
environment = config[ENVIRONMENT]
|
|
448
|
+
current_datetime = config[INDEX_DATETIME]
|
|
449
|
+
|
|
450
|
+
# Step 1: Collect all data for the strategies and for the
|
|
451
|
+
# pending orders
|
|
452
|
+
open_orders = self._order_service.get_all(
|
|
453
|
+
{
|
|
454
|
+
"status": OrderStatus.OPEN,
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
open_trades = self._trade_service.get_all(
|
|
458
|
+
{
|
|
459
|
+
"status": TradeStatus.OPEN,
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
data_sources = []
|
|
463
|
+
|
|
464
|
+
for strategy in strategies:
|
|
465
|
+
|
|
466
|
+
if strategy.data_sources is None:
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
data_sources.extend(strategy.data_sources)
|
|
470
|
+
|
|
471
|
+
data_sources = self._get_data_sources_for_iteration(
|
|
472
|
+
strategy_data_sources=data_sources,
|
|
473
|
+
)
|
|
474
|
+
data_object = {}
|
|
475
|
+
orders_trades_update_ohlcv_data = \
|
|
476
|
+
self._get_pending_orders_and_trades_data_for_iteration(
|
|
477
|
+
pending_order=open_orders,
|
|
478
|
+
open_trades=open_trades,
|
|
479
|
+
date=current_datetime,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
if Environment.BACKTEST.equals(environment):
|
|
483
|
+
|
|
484
|
+
for data_source in data_sources:
|
|
485
|
+
# For backtesting, we use the start date and end date
|
|
486
|
+
# from the data source to fetch the data
|
|
487
|
+
data_object[data_source.get_identifier()] = \
|
|
488
|
+
self._data_provider_service.get_backtest_data(
|
|
489
|
+
data_source=data_source,
|
|
490
|
+
backtest_index_date=current_datetime,
|
|
491
|
+
start_date=data_source.start_date,
|
|
492
|
+
end_date=data_source.end_date,
|
|
493
|
+
)
|
|
494
|
+
else:
|
|
495
|
+
for data_source in data_sources:
|
|
496
|
+
data_object[data_source.get_identifier()] = \
|
|
497
|
+
self._data_provider_service.get_data(
|
|
498
|
+
data_source=data_source,
|
|
499
|
+
date=current_datetime,
|
|
500
|
+
start_date=data_source.start_date,
|
|
501
|
+
end_date=data_source.end_date,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Step 3: Check pending orders, stop losses, take profits
|
|
505
|
+
self._trade_order_evaluator.evaluate(
|
|
506
|
+
open_trades=open_trades,
|
|
507
|
+
open_orders=open_orders,
|
|
508
|
+
ohlcv_data=orders_trades_update_ohlcv_data
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
# Step 4: Run all tasks
|
|
512
|
+
for task in self.tasks:
|
|
513
|
+
task.run(data_object)
|
|
514
|
+
|
|
515
|
+
# Step 5: Run all strategies
|
|
516
|
+
if not strategies:
|
|
517
|
+
return
|
|
518
|
+
|
|
519
|
+
for strategy in strategies:
|
|
520
|
+
|
|
521
|
+
if strategy.data_sources is not None:
|
|
522
|
+
data = {
|
|
523
|
+
data_source.get_identifier(): data_object[
|
|
524
|
+
data_source.get_identifier()]
|
|
525
|
+
for data_source in strategy.data_sources
|
|
526
|
+
}
|
|
527
|
+
else:
|
|
528
|
+
data = {}
|
|
529
|
+
|
|
530
|
+
# Select data for the strategy
|
|
531
|
+
strategy.run_strategy(context=self.context, data=data)
|
|
532
|
+
|
|
533
|
+
# # Step 6: Run all on_strategy_run hooks
|
|
534
|
+
# for strategy in due_strategies:
|
|
535
|
+
# strategy.run_on_strategy_run_hooks(context=self.context)
|
|
536
|
+
|
|
537
|
+
# Step 7: Snapshot the portfolios if needed and update history
|
|
538
|
+
created_orders = self._order_service.get_all(
|
|
539
|
+
{
|
|
540
|
+
"status": OrderStatus.CREATED,
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
open_orders = self._order_service.get_all(
|
|
544
|
+
{
|
|
545
|
+
"status": OrderStatus.OPEN,
|
|
546
|
+
}
|
|
547
|
+
)
|
|
548
|
+
self._snapshot(
|
|
549
|
+
current_datetime=current_datetime,
|
|
550
|
+
open_orders=open_orders,
|
|
551
|
+
created_orders=created_orders
|
|
552
|
+
)
|
|
553
|
+
self._update_history(
|
|
554
|
+
current_datetime=current_datetime,
|
|
555
|
+
strategies=strategies,
|
|
556
|
+
hooks=[]
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
def _update_history(self, current_datetime, strategies, hooks):
|
|
560
|
+
"""
|
|
561
|
+
Updates the history of the event loop with the current datetime.
|
|
562
|
+
This method is called at the end of each iteration to keep track of
|
|
563
|
+
the iterations.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
current_datetime: The current datetime in UTC.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
None
|
|
570
|
+
"""
|
|
571
|
+
|
|
572
|
+
for strategy in strategies:
|
|
573
|
+
runs = self.history.get(strategy.strategy_id, {}).get("runs", [])
|
|
574
|
+
runs.append(current_datetime)
|
|
575
|
+
self.history[strategy.strategy_id] = {
|
|
576
|
+
"runs": runs,
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
@property
|
|
580
|
+
def total_number_of_runs(self):
|
|
581
|
+
"""
|
|
582
|
+
Returns the total number of runs for all strategies in the event loop.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
int: The total number of runs.
|
|
586
|
+
"""
|
|
587
|
+
return sum(
|
|
588
|
+
len(self.history[strategy_id]["runs"])
|
|
589
|
+
for strategy_id in self.history
|
|
590
|
+
)
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
from .generate import add_html_report
|
|
1
|
+
from .generate import add_html_report
|
|
2
2
|
from .backtest_report import BacktestReport
|
|
3
3
|
from .ascii import pretty_print_backtest, pretty_print_positions, \
|
|
4
4
|
pretty_print_trades, pretty_print_orders
|
|
5
|
-
from .
|
|
5
|
+
from .charts import get_equity_curve_with_drawdown_chart, \
|
|
6
|
+
get_rolling_sharpe_ratio_chart, \
|
|
7
|
+
get_monthly_returns_heatmap_chart, \
|
|
8
|
+
get_yearly_returns_bar_chart, \
|
|
9
|
+
get_ohlcv_data_completeness_chart, \
|
|
10
|
+
get_entry_and_exit_signals, \
|
|
11
|
+
get_equity_curve_chart
|
|
6
12
|
|
|
7
13
|
__all__ = [
|
|
8
14
|
"add_html_report",
|
|
9
|
-
"add_metrics",
|
|
10
15
|
"BacktestReport",
|
|
11
16
|
"pretty_print_backtest",
|
|
12
|
-
"BacktestReportsEvaluation",
|
|
13
17
|
"pretty_print_positions",
|
|
14
18
|
"pretty_print_trades",
|
|
15
|
-
"pretty_print_orders"
|
|
19
|
+
"pretty_print_orders",
|
|
20
|
+
"get_equity_curve_with_drawdown_chart",
|
|
21
|
+
"get_rolling_sharpe_ratio_chart",
|
|
22
|
+
"get_monthly_returns_heatmap_chart",
|
|
23
|
+
"get_yearly_returns_bar_chart",
|
|
24
|
+
"get_ohlcv_data_completeness_chart",
|
|
25
|
+
"get_entry_and_exit_signals",
|
|
26
|
+
"get_equity_curve_chart"
|
|
16
27
|
]
|