investing-algorithm-framework 3.7.0__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 (256) hide show
  1. investing_algorithm_framework/__init__.py +168 -45
  2. investing_algorithm_framework/app/__init__.py +32 -1
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +1933 -589
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1725 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -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/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
  37. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
  38. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
  39. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
  40. investing_algorithm_framework/app/strategy.py +664 -84
  41. investing_algorithm_framework/app/task.py +5 -3
  42. investing_algorithm_framework/app/web/__init__.py +2 -1
  43. investing_algorithm_framework/app/web/create_app.py +4 -2
  44. investing_algorithm_framework/cli/__init__.py +0 -0
  45. investing_algorithm_framework/cli/cli.py +226 -0
  46. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
  47. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  48. investing_algorithm_framework/cli/initialize_app.py +603 -0
  49. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  50. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  51. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  52. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  53. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  54. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  55. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  56. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  57. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  58. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  59. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  60. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  61. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  62. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  63. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  64. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  65. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  66. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  67. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  68. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  69. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  70. investing_algorithm_framework/create_app.py +40 -6
  71. investing_algorithm_framework/dependency_container.py +72 -56
  72. investing_algorithm_framework/domain/__init__.py +71 -47
  73. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  74. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  75. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  76. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  77. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  78. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  79. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  80. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  81. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  82. investing_algorithm_framework/domain/config.py +59 -91
  83. investing_algorithm_framework/domain/constants.py +13 -38
  84. investing_algorithm_framework/domain/data_provider.py +334 -0
  85. investing_algorithm_framework/domain/data_structures.py +3 -2
  86. investing_algorithm_framework/domain/exceptions.py +51 -1
  87. investing_algorithm_framework/domain/models/__init__.py +17 -12
  88. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  89. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  90. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  91. investing_algorithm_framework/domain/models/event.py +35 -0
  92. investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
  93. investing_algorithm_framework/domain/models/order/order.py +77 -83
  94. investing_algorithm_framework/domain/models/order/order_status.py +2 -2
  95. investing_algorithm_framework/domain/models/order/order_type.py +1 -3
  96. investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
  97. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
  98. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
  99. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  100. investing_algorithm_framework/domain/models/position/position.py +12 -0
  101. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  102. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  103. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  104. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  105. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  106. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  107. investing_algorithm_framework/domain/models/time_frame.py +37 -0
  108. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  109. investing_algorithm_framework/domain/models/time_unit.py +66 -2
  110. investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
  111. investing_algorithm_framework/domain/models/trade/trade.py +295 -171
  112. investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
  113. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
  114. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
  115. investing_algorithm_framework/domain/order_executor.py +112 -0
  116. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  117. investing_algorithm_framework/domain/services/__init__.py +2 -9
  118. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
  119. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  120. investing_algorithm_framework/domain/strategy.py +1 -29
  121. investing_algorithm_framework/domain/utils/__init__.py +12 -7
  122. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  123. investing_algorithm_framework/domain/utils/dates.py +57 -0
  124. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  125. investing_algorithm_framework/domain/utils/polars.py +53 -0
  126. investing_algorithm_framework/domain/utils/random.py +29 -0
  127. investing_algorithm_framework/download_data.py +108 -0
  128. investing_algorithm_framework/infrastructure/__init__.py +31 -18
  129. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  130. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  131. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  132. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  133. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  134. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
  135. investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
  136. investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
  137. investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
  138. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  139. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  140. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
  141. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
  142. investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
  143. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  144. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  145. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
  146. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
  147. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  148. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  149. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  150. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  151. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  152. investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
  153. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  154. investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
  155. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  156. investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
  157. investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
  158. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  159. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
  160. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
  161. investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
  162. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  163. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  164. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  165. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  166. investing_algorithm_framework/services/__init__.py +113 -16
  167. investing_algorithm_framework/services/backtesting/__init__.py +0 -7
  168. investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
  169. investing_algorithm_framework/services/configuration_service.py +77 -11
  170. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  171. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  172. investing_algorithm_framework/services/market_credential_service.py +16 -1
  173. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  174. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  175. investing_algorithm_framework/services/metrics/beta.py +0 -0
  176. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  177. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  178. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  179. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  180. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  181. investing_algorithm_framework/services/metrics/generate.py +358 -0
  182. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  183. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  184. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  185. investing_algorithm_framework/services/metrics/returns.py +452 -0
  186. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  187. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  188. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  189. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  190. investing_algorithm_framework/services/metrics/trades.py +500 -0
  191. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  192. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  193. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  194. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  195. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  196. investing_algorithm_framework/services/order_service/__init__.py +3 -1
  197. investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
  198. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  199. investing_algorithm_framework/services/order_service/order_service.py +407 -326
  200. investing_algorithm_framework/services/portfolios/__init__.py +3 -1
  201. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
  202. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
  203. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  204. investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
  205. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
  206. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
  207. investing_algorithm_framework/services/positions/__init__.py +7 -0
  208. investing_algorithm_framework/services/positions/position_service.py +210 -0
  209. investing_algorithm_framework/services/repository_service.py +8 -2
  210. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  211. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  212. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  213. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  214. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  215. investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
  216. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  217. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  218. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  219. investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
  220. investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
  221. investing_algorithm_framework/app/algorithm.py +0 -1105
  222. investing_algorithm_framework/domain/graphs.py +0 -382
  223. investing_algorithm_framework/domain/metrics/__init__.py +0 -6
  224. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
  225. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
  226. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  227. investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
  228. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
  229. investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
  230. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  231. investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
  232. investing_algorithm_framework/domain/services/market_service.py +0 -153
  233. investing_algorithm_framework/domain/singleton.py +0 -9
  234. investing_algorithm_framework/domain/utils/backtesting.py +0 -472
  235. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
  236. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
  237. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
  238. investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
  239. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  240. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
  241. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  242. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  243. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
  244. investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
  245. investing_algorithm_framework/services/backtesting/graphs.py +0 -61
  246. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
  247. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
  248. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
  249. investing_algorithm_framework/services/position_service.py +0 -31
  250. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
  251. investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
  252. investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
  253. /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
  254. /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
  255. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  256. {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
@@ -0,0 +1,605 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from datetime import datetime, timezone
5
+ from dataclasses import dataclass, field
6
+ from logging import getLogger
7
+ from typing import Union, List, Optional, Dict
8
+
9
+ from investing_algorithm_framework.domain.exceptions \
10
+ import OperationalException
11
+ from investing_algorithm_framework.domain.models.order import Order, \
12
+ OrderSide, OrderStatus
13
+ from investing_algorithm_framework.domain.models.position import Position
14
+ from investing_algorithm_framework.domain.models.trade import Trade
15
+ from investing_algorithm_framework.domain.models.portfolio import \
16
+ PortfolioSnapshot
17
+ from investing_algorithm_framework.domain.models.trade.trade_status import \
18
+ TradeStatus
19
+ from investing_algorithm_framework.domain.models.trade.trade_stop_loss import \
20
+ TradeStopLoss
21
+ from investing_algorithm_framework.domain.models.trade.trade_take_profit \
22
+ import TradeTakeProfit
23
+
24
+
25
+ from .backtest_metrics import BacktestMetrics
26
+
27
+
28
+ logger = getLogger(__name__)
29
+
30
+
31
+ @dataclass
32
+ class BacktestRun:
33
+ """
34
+ Represents a backtest of an algorithm. It contains the backtest metrics,
35
+ backtest results, and paths to strategy and data files.
36
+
37
+ Attributes:
38
+ backtest_metrics (Optional[List[BacktestMetrics]]): A list of
39
+ backtest metrics objects, each representing the performance
40
+ metrics of a single backtest run.
41
+ backtest_start_date (datetime): The start date of the backtest.
42
+ backtest_end_date (datetime): The end date of the backtest.
43
+ backtest_date_range_name (str): The name of the date range used for
44
+ the backtest.
45
+ trading_symbol (str): The trading symbol used in the backtest.
46
+ initial_unallocated (float): The initial unallocated amount in the
47
+ backtest.
48
+ number_of_runs (int): The number of runs in the backtest.
49
+ portfolio_snapshots (List[PortfolioSnapshot]): A list of portfolio
50
+ snapshots taken during the backtest.
51
+ trades (List[Trade]): A list of trades executed during the backtest.
52
+ orders (List[Order]): A list of orders placed during the backtest.
53
+ positions (List[Position]): A list of positions held during the
54
+ backtest.
55
+ created_at (datetime): The date and time when the backtest was created.
56
+ symbols (List[str]): A list of trading symbols involved in
57
+ the backtest.
58
+ number_of_days (int): The total number of days the backtest ran.
59
+ number_of_trades (int): The total number of trades executed during
60
+ the backtest.
61
+ number_of_trades_closed (int): The total number of trades that were
62
+ closed during the backtest.
63
+ number_of_trades_open (int): The total number of trades that are
64
+ still open at the end of the backtest.
65
+ number_of_orders (int): The total number of orders placed during
66
+ the backtest.
67
+ number_of_positions (int): The total number of positions held
68
+ during the backtest.
69
+ """
70
+ backtest_start_date: datetime
71
+ backtest_end_date: datetime
72
+ trading_symbol: str
73
+ initial_unallocated: float = 0.0
74
+ number_of_runs: int = 0
75
+ portfolio_snapshots: List[PortfolioSnapshot] = field(default_factory=list)
76
+ trades: List[Trade] = field(default_factory=list)
77
+ orders: List[Order] = field(default_factory=list)
78
+ positions: List[Position] = field(default_factory=list)
79
+ created_at: datetime = None,
80
+ symbols: List[str] = field(default_factory=list)
81
+ number_of_days: int = 0
82
+ number_of_trades: int = 0
83
+ number_of_trades_closed: int = 0
84
+ number_of_trades_open: int = 0
85
+ number_of_orders: int = 0
86
+ number_of_positions: int = 0
87
+ backtest_metrics: BacktestMetrics = None
88
+ backtest_date_range_name: str = None
89
+ data_sources: List[Dict] = field(default_factory=list)
90
+ metadata: Dict[str, str] = field(default_factory=dict)
91
+
92
+ def to_dict(self) -> dict:
93
+ """
94
+ Convert the Backtest instance to a dictionary with all
95
+ date/datetime fields as ISO strings (always UTC).
96
+ """
97
+ def ensure_iso(value):
98
+ if hasattr(value, "isoformat"):
99
+ if value.tzinfo is None:
100
+ value = value.replace(tzinfo=timezone.utc)
101
+ return value.isoformat()
102
+ return value
103
+
104
+ backtest_metrics = self.backtest_metrics.to_dict() \
105
+ if self.backtest_metrics else None
106
+ return {
107
+ "backtest_metrics": backtest_metrics,
108
+ "backtest_start_date": ensure_iso(self.backtest_start_date),
109
+ "backtest_date_range_name": self.backtest_date_range_name,
110
+ "backtest_end_date": ensure_iso(self.backtest_end_date),
111
+ "trading_symbol": self.trading_symbol,
112
+ "initial_unallocated": self.initial_unallocated,
113
+ "number_of_runs": self.number_of_runs,
114
+ "portfolio_snapshots": [
115
+ ps.to_dict() for ps in self.portfolio_snapshots
116
+ ],
117
+ "trades": [trade.to_dict() for trade in self.trades],
118
+ "orders": [order.to_dict() for order in self.orders],
119
+ "positions": [position.to_dict() for position in self.positions],
120
+ "created_at": ensure_iso(self.created_at),
121
+ "symbols": self.symbols,
122
+ "number_of_days": self.number_of_days,
123
+ "number_of_trades": self.number_of_trades,
124
+ "number_of_trades_closed": self.number_of_trades_closed,
125
+ "number_of_trades_open": self.number_of_trades_open,
126
+ "number_of_orders": self.number_of_orders,
127
+ "number_of_positions": self.number_of_positions,
128
+ "metadata": self.metadata,
129
+ }
130
+
131
+ @staticmethod
132
+ def open(directory_path: Union[str, Path]) -> 'BacktestRun':
133
+ """
134
+ Open a backtest report from a directory and return a Backtest instance.
135
+
136
+ Args:
137
+ directory_path (str): The path to the directory containing the
138
+ backtest report files.
139
+
140
+ Returns:
141
+ Backtest: An instance of Backtest with the loaded metrics
142
+ and results.
143
+
144
+ Raises:
145
+ OperationalException: If the directory does not exist or if
146
+ there is an error loading the files.
147
+ """
148
+ backtest_metrics = None
149
+
150
+ if not os.path.exists(directory_path):
151
+ raise OperationalException(
152
+ f"The directory {directory_path} does not exist."
153
+ )
154
+
155
+ # Load combined backtest metrics
156
+ metrics_file = os.path.join(directory_path, "metrics.json")
157
+
158
+ if os.path.isfile(metrics_file):
159
+ backtest_metrics = BacktestMetrics.open(metrics_file)
160
+
161
+ # Load backtest results
162
+ run_file = os.path.join(directory_path, "run.json")
163
+
164
+ if os.path.isfile(run_file):
165
+ data = json.load(open(run_file, 'r'))
166
+ else:
167
+ raise OperationalException(
168
+ f"The run file {run_file} does not exist."
169
+ )
170
+
171
+ # Parse datetime fields
172
+ data["backtest_start_date"] = datetime.strptime(
173
+ data["backtest_start_date"], "%Y-%m-%d %H:%M:%S"
174
+ )
175
+ data["backtest_end_date"] = datetime.strptime(
176
+ data["backtest_end_date"], "%Y-%m-%d %H:%M:%S"
177
+ )
178
+ data["created_at"] = datetime.strptime(
179
+ data["created_at"], "%Y-%m-%d %H:%M:%S"
180
+ )
181
+ # Convert all to utc timezone
182
+ data["backtest_start_date"] = data[
183
+ "backtest_start_date"].replace(tzinfo=timezone.utc)
184
+ data["backtest_end_date"] = data[
185
+ "backtest_end_date"].replace(tzinfo=timezone.utc)
186
+ data["created_at"] = data["created_at"].replace(tzinfo=timezone.utc)
187
+
188
+ # Parse orders
189
+ data["orders"] = [
190
+ Order.from_dict(order) for order in data.get("orders", [])
191
+ ]
192
+
193
+ # Parse positions
194
+ data["positions"] = [
195
+ Position.from_dict(position)
196
+ for position in data.get("positions", [])
197
+ ]
198
+
199
+ # Parse trades
200
+ data["trades"] = [
201
+ Trade.from_dict(trade) for trade in data.get("trades", [])
202
+ ]
203
+
204
+ # Parse portfolio snapshots
205
+ data["portfolio_snapshots"] = [
206
+ PortfolioSnapshot.from_dict(ps)
207
+ for ps in data.get("portfolio_snapshots", [])
208
+ ]
209
+
210
+ return BacktestRun(
211
+ backtest_metrics=backtest_metrics,
212
+ **data
213
+ )
214
+
215
+ def create_directory_name(self) -> str:
216
+ """
217
+ Create a directory name for the backtest run based on its attributes.
218
+
219
+ Returns:
220
+ str: A string representing the directory name.
221
+ """
222
+ start_str = self.backtest_start_date.strftime("%Y%m%d")
223
+ end_str = self.backtest_end_date.strftime("%Y%m%d")
224
+ dir_name = f"backtest_{self.trading_symbol}_{start_str}_{end_str}"
225
+ return dir_name
226
+
227
+ def save(self, directory_path: Union[str, Path]) -> None:
228
+ """
229
+ Save the backtest run to a directory.
230
+
231
+ Args:
232
+ directory_path (str): The directory where the metrics
233
+ file will be saved.
234
+
235
+ Raises:
236
+ OperationalException: If the directory does not exist or if
237
+ there is an error saving the files.
238
+
239
+ Returns:
240
+ None: This method does not return anything, it saves the
241
+ metrics to a file.
242
+ """
243
+
244
+ metrics_path = os.path.join(directory_path, "metrics.json")
245
+ run_path = os.path.join(directory_path, "run.json")
246
+
247
+ if not os.path.exists(directory_path):
248
+ os.makedirs(directory_path)
249
+
250
+ # Call the save method of BacktestMetrics
251
+ if self.backtest_metrics:
252
+ self.backtest_metrics.save(metrics_path)
253
+
254
+ # Save the run data
255
+ with open(run_path, 'w') as f:
256
+ # string format datetime objects
257
+ data = self.to_dict()
258
+
259
+ # Remove backtest_metrics to avoid redundancy
260
+ data.pop("backtest_metrics", None)
261
+
262
+ # Ensure datetime objects are in UTC before formatting
263
+ backtest_start_date = self.backtest_start_date
264
+
265
+ if backtest_start_date.tzinfo is None:
266
+ # Naive datetime - treat as UTC
267
+ backtest_start_date = backtest_start_date.replace(
268
+ tzinfo=timezone.utc
269
+ )
270
+ else:
271
+ # Timezone-aware - convert to UTC
272
+ backtest_start_date = backtest_start_date.astimezone(
273
+ timezone.utc
274
+ )
275
+
276
+ backtest_end_date = self.backtest_end_date
277
+ if backtest_end_date.tzinfo is None:
278
+ backtest_end_date = backtest_end_date.replace(
279
+ tzinfo=timezone.utc
280
+ )
281
+ else:
282
+ backtest_end_date = backtest_end_date.astimezone(timezone.utc)
283
+
284
+ created_at = self.created_at
285
+ if created_at.tzinfo is None:
286
+ created_at = created_at.replace(tzinfo=timezone.utc)
287
+ else:
288
+ created_at = created_at.astimezone(timezone.utc)
289
+
290
+ data["backtest_start_date"] = backtest_start_date.strftime(
291
+ "%Y-%m-%d %H:%M:%S"
292
+ )
293
+ data["backtest_end_date"] = backtest_end_date.strftime(
294
+ "%Y-%m-%d %H:%M:%S"
295
+ )
296
+ data["created_at"] = created_at.strftime(
297
+ "%Y-%m-%d %H:%M:%S"
298
+ )
299
+ json.dump(data, f, default=str)
300
+
301
+ def get_trade(self, trade_id: str) -> Optional[Trade]:
302
+ """
303
+ Get a trade by its ID from the backtest report
304
+
305
+ Args:
306
+ trade_id (str): The trade ID
307
+
308
+ Returns:
309
+ Trade: The trade with the given ID, or None if not found
310
+ """
311
+ for trade in self.trades:
312
+ if trade.trade_id == trade_id:
313
+ return trade
314
+
315
+ return None
316
+
317
+ def get_trades(
318
+ self,
319
+ target_symbol: str = None,
320
+ trade_status: Union[TradeStatus, str] = None,
321
+ opened_at: datetime = None,
322
+ opened_at_lt: datetime = None,
323
+ opened_at_lte: datetime = None,
324
+ opened_at_gt: datetime = None,
325
+ opened_at_gte: datetime = None,
326
+ order_id: str = None
327
+ ) -> List[Trade]:
328
+ """
329
+ Get the trades of a backtest report
330
+
331
+ Args:
332
+ target_symbol (str): The target_symbol
333
+ trade_status (Union[TradeStatus, str]): The trade status
334
+ opened_at (datetime): The created_at date to filter the trades
335
+ opened_at_lt (datetime): The created_at date to filter the trades
336
+ opened_at_lte (datetime): The created_at date to filter the trades
337
+ opened_at_gt (datetime): The created_at date to filter the trades
338
+ opened_at_gte (datetime): The created_at date to filter the trades
339
+ order_id (str): The order ID to filter the trades
340
+
341
+ Returns:
342
+ list: The trades of the backtest report
343
+ """
344
+ selection = self.trades
345
+
346
+ if target_symbol is not None:
347
+ selection = [
348
+ trade for trade in selection
349
+ if trade.target_symbol.lower() == target_symbol.lower()
350
+ ]
351
+
352
+ if trade_status is not None:
353
+ trade_status = TradeStatus.from_value(trade_status)
354
+ selection = [
355
+ trade for trade in selection
356
+ if trade.status == trade_status.value
357
+ ]
358
+
359
+ if opened_at is not None:
360
+ selection = [
361
+ trade for trade in selection
362
+ if trade.opened_at == opened_at
363
+ ]
364
+
365
+ if opened_at_lt is not None:
366
+ selection = [
367
+ trade for trade in selection
368
+ if trade.opened_at < opened_at_lt
369
+ ]
370
+
371
+ if opened_at_lte is not None:
372
+ selection = [
373
+ trade for trade in selection
374
+ if trade.opened_at <= opened_at_lte
375
+ ]
376
+
377
+ if opened_at_gt is not None:
378
+ selection = [
379
+ trade for trade in selection
380
+ if trade.opened_at > opened_at_gt
381
+ ]
382
+
383
+ if opened_at_gte is not None:
384
+ selection = [
385
+ trade for trade in selection
386
+ if trade.opened_at >= opened_at_gte
387
+ ]
388
+
389
+ if order_id is not None:
390
+ new_selection = []
391
+ for trade in selection:
392
+
393
+ for order in trade.orders:
394
+ if order.order_id == order_id:
395
+ new_selection.append(trade)
396
+ break
397
+
398
+ selection = new_selection
399
+
400
+ return selection
401
+
402
+ def get_stop_losses(
403
+ self,
404
+ trade_id: str = None,
405
+ triggered: bool = None
406
+ ) -> List[TradeStopLoss]:
407
+ """
408
+ Get the stop losses of the backtest report
409
+
410
+ Args:
411
+ trade_id (str): The trade ID to filter the stop losses
412
+ triggered (bool): Whether to filter by triggered stop losses
413
+
414
+ Returns:
415
+ list: The stop losses of the backtest report
416
+ """
417
+ stop_losses = []
418
+
419
+ for trade in self.trades:
420
+ if trade_id is not None and trade.id != trade_id:
421
+ continue
422
+
423
+ for sl in trade.stop_losses:
424
+ if isinstance(sl, TradeStopLoss):
425
+ if triggered is not None:
426
+ if sl.triggered == triggered:
427
+ stop_losses.append(sl)
428
+ else:
429
+ stop_losses.append(sl)
430
+
431
+ return stop_losses
432
+
433
+ def get_take_profits(
434
+ self,
435
+ trade_id: str = None,
436
+ triggered: bool = None
437
+ ) -> List[TradeStopLoss]:
438
+ """
439
+ Get the take profits of the backtest report
440
+
441
+ Args:
442
+ trade_id (str): The trade ID to filter the take profits
443
+ triggered (bool): Whether to filter by triggered take profits
444
+
445
+ Returns:
446
+ list: The take profits of the backtest report
447
+ """
448
+ take_profits = []
449
+
450
+ for trade in self.trades:
451
+ if trade_id is not None and trade.id != trade_id:
452
+ continue
453
+
454
+ for tp in trade.take_profits:
455
+ if isinstance(tp, TradeTakeProfit):
456
+ if triggered is not None:
457
+ if tp.triggered == triggered:
458
+ take_profits.append(tp)
459
+ else:
460
+ take_profits.append(tp)
461
+
462
+ return take_profits
463
+
464
+ def get_portfolio_snapshots(
465
+ self,
466
+ created_at_lt: Optional[datetime] = None,
467
+ created_at_lte: Optional[datetime] = None,
468
+ created_at_gt: Optional[datetime] = None,
469
+ created_at_gte: Optional[datetime] = None
470
+ ) -> List[PortfolioSnapshot]:
471
+ """
472
+ Get the portfolio snapshots of the backtest report
473
+
474
+ Args:
475
+ created_at_lt (datetime): The created_at date to filter
476
+ the snapshots
477
+ created_at_lte (datetime): The created_at date to filter
478
+ the snapshots
479
+ created_at_gt (datetime): The created_at date to filter
480
+ the snapshots
481
+ created_at_gte (datetime): The created_at date to filter
482
+ the snapshots
483
+
484
+ Returns:
485
+ list: The portfolio snapshots of the backtest report
486
+ """
487
+ selection = self.portfolio_snapshots
488
+
489
+ if created_at_lt is not None:
490
+ selection = [
491
+ snapshot for snapshot in selection
492
+ if snapshot.created_at < created_at_lt
493
+ ]
494
+
495
+ if created_at_lte is not None:
496
+ selection = [
497
+ snapshot for snapshot in selection
498
+ if snapshot.created_at <= created_at_lte
499
+ ]
500
+
501
+ if created_at_gt is not None:
502
+ selection = [
503
+ snapshot for snapshot in selection
504
+ if snapshot.created_at > created_at_gt
505
+ ]
506
+
507
+ if created_at_gte is not None:
508
+ selection = [
509
+ snapshot for snapshot in selection
510
+ if snapshot.created_at >= created_at_gte
511
+ ]
512
+
513
+ return selection
514
+
515
+ def get_orders(
516
+ self,
517
+ target_symbol: str = None,
518
+ order_side: str = None,
519
+ order_status: Union[OrderStatus, str] = None,
520
+ created_at: datetime = None,
521
+ created_at_lt: datetime = None,
522
+ created_at_lte: datetime = None,
523
+ created_at_gt: datetime = None,
524
+ created_at_gte: datetime = None
525
+ ) -> List[Order]:
526
+ """
527
+ Get the orders of a backtest report
528
+
529
+ Args:
530
+ target_symbol (str): The target_symbol
531
+ order_side (str): The order side
532
+ order_status (Union[OrderStatus, str]): The order status
533
+ created_at (datetime): The created_at date to filter the orders
534
+ created_at_lt (datetime): The created_at date to filter the orders
535
+ created_at_lte (datetime): The created_at date to filter the orders
536
+ created_at_gt (datetime): The created_at date to filter the orders
537
+ created_at_gte (datetime): The created_at date to filter the orders
538
+
539
+ Returns:
540
+ list: The orders of the backtest report
541
+ """
542
+ selection = self.orders
543
+
544
+ if created_at is not None:
545
+ selection = [
546
+ order for order in selection
547
+ if order.created_at == created_at
548
+ ]
549
+
550
+ if created_at_lt is not None:
551
+ selection = [
552
+ order for order in selection
553
+ if order.created_at < created_at_lt
554
+ ]
555
+
556
+ if created_at_lte is not None:
557
+ selection = [
558
+ order for order in selection
559
+ if order.created_at <= created_at_lte
560
+ ]
561
+
562
+ if created_at_gt is not None:
563
+ selection = [
564
+ order for order in selection
565
+ if order.created_at > created_at_gt
566
+ ]
567
+
568
+ if created_at_gte is not None:
569
+ selection = [
570
+ order for order in selection
571
+ if order.created_at >= created_at_gte
572
+ ]
573
+
574
+ if target_symbol is not None:
575
+ selection = [
576
+ order for order in selection
577
+ if order.target_symbol == target_symbol
578
+ ]
579
+
580
+ if order_side is not None:
581
+ order_side = OrderSide.from_value(order_side)
582
+ selection = [
583
+ order for order in selection
584
+ if order.order_side == order_side.value
585
+ ]
586
+
587
+ if order_status is not None:
588
+ status = OrderStatus.from_value(order_status)
589
+ selection = [
590
+ order for order in selection
591
+ if order.status == status.value
592
+ ]
593
+
594
+ return selection
595
+
596
+ def __repr__(self):
597
+ """
598
+ Return a string representation of the Backtest instance.
599
+
600
+ Returns:
601
+ str: A string representation of the Backtest instance.
602
+ """
603
+ return json.dumps(
604
+ self.to_dict(), indent=4, sort_keys=True, default=str
605
+ )