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