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.

Files changed (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {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, add_metrics
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 .evaluation import BacktestReportsEvaluation
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
  ]