investing-algorithm-framework 7.19.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  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 +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -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/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,242 @@
1
+ from enum import Enum
2
+
3
+
4
+ class BacktestEvaluationFocus(Enum):
5
+ """
6
+ Enumeration for backtest evaluation focus areas.
7
+
8
+ The available metrics are:
9
+ - backtest_start_date
10
+ - backtest_end_date
11
+ - equity_curve
12
+ - final_value
13
+ - total_growth
14
+ - total_growth_percentage
15
+ - total_net_gain
16
+ - total_net_gain_percentage
17
+ - total_loss
18
+ - total_loss_percentage
19
+ - cumulative_return
20
+ - cumulative_return_series
21
+ - cagr
22
+ - sharpe_ratio
23
+ - rolling_sharpe_ratio
24
+ - sortino_ratio
25
+ - calmar_ratio
26
+ - profit_factor
27
+ - annual_volatility
28
+ - monthly_returns
29
+ - yearly_returns
30
+ - drawdown_series
31
+ - max_drawdown
32
+ - max_drawdown_absolute
33
+ - max_daily_drawdown
34
+ - max_drawdown_duration
35
+ - trades_per_year
36
+ - trade_per_day
37
+ - exposure_ratio
38
+ - cumulative_exposure
39
+ - best_trade
40
+ - worst_trade
41
+ - number_of_positive_trades
42
+ - percentage_positive_trades
43
+ - number_of_negative_trades
44
+ - percentage_negative_trades
45
+ - average_trade_duration
46
+ - average_trade_size
47
+ - average_trade_loss
48
+ - average_trade_loss_percentage
49
+ - average_trade_gain
50
+ - average_trade_gain_percentage
51
+ - average_trade_return
52
+ - average_trade_return_percentage
53
+ - median_trade_return
54
+ - number_of_trades
55
+ - number_of_trades_closed
56
+ - number_of_trades_opened
57
+ - number_of_trades_open_at_end
58
+ - win_rate
59
+ - current_win_rate
60
+ - win_loss_ratio
61
+ - current_win_loss_ratio
62
+ - percentage_winning_months
63
+ - percentage_winning_years
64
+ - average_monthly_return
65
+ - average_monthly_return_losing_months
66
+ - average_monthly_return_winning_months
67
+ - best_month
68
+ - best_year
69
+ - worst_month
70
+ - worst_year
71
+ - total_number_of_days
72
+ - current_average_trade_gain
73
+ - current_average_trade_return
74
+ - current_average_trade_duration
75
+ - current_average_trade_loss
76
+ """
77
+ BALANCED = "balanced"
78
+ PROFIT = "profit"
79
+ FREQUENCY = "frequency"
80
+ RISK_ADJUSTED = "risk_adjusted"
81
+
82
+ @staticmethod
83
+ def from_string(value: str):
84
+
85
+ if isinstance(value, str):
86
+
87
+ for entry in BacktestEvaluationFocus:
88
+
89
+ if value.upper() == entry.value:
90
+ return entry
91
+
92
+ raise ValueError(
93
+ f"Could not convert {value} to BacktestEvaluationFocus"
94
+ )
95
+ return None
96
+
97
+ @staticmethod
98
+ def from_value(value):
99
+
100
+ if isinstance(value, str):
101
+ return BacktestEvaluationFocus.from_string(value)
102
+
103
+ if isinstance(value, BacktestEvaluationFocus):
104
+
105
+ for entry in BacktestEvaluationFocus:
106
+
107
+ if value == entry:
108
+ return entry
109
+
110
+ raise ValueError(
111
+ f"Could not convert {value} to BacktestEvaluationFocus"
112
+ )
113
+
114
+ def equals(self, other):
115
+
116
+ if isinstance(other, Enum):
117
+ return self.value == other.value
118
+ else:
119
+ return BacktestEvaluationFocus.from_string(other) == self
120
+
121
+ def get_weights(self):
122
+ """
123
+ Get evaluation weights for different focus areas.
124
+ Returns a dictionary with metric weights where:
125
+ - Positive weights favor higher values
126
+ - Negative weights favor lower values (penalties)
127
+ - Zero weights ignore the metric
128
+ """
129
+
130
+ if self == BacktestEvaluationFocus.BALANCED:
131
+ return {
132
+ # Core profitability metrics (moderate weight)
133
+ "total_net_gain_percentage": 2.0,
134
+ "cagr": 1.5,
135
+ "average_trade_return_percentage": 1.0,
136
+
137
+ # Risk-adjusted returns (important for balance)
138
+ "sharpe_ratio": 2.0,
139
+ "sortino_ratio": 1.5,
140
+ "calmar_ratio": 1.0,
141
+ "profit_factor": 1.5,
142
+
143
+ # Risk management (penalties for bad metrics)
144
+ "max_drawdown": -1.5,
145
+ "max_drawdown_duration": -0.5,
146
+ "annual_volatility": -0.8,
147
+
148
+ # Trading consistency
149
+ "win_rate": 1.5,
150
+ "win_loss_ratio": 1.0,
151
+ "number_of_trades": 0.8, # Some activity needed
152
+
153
+ # Efficiency metrics
154
+ "exposure_ratio": 0.5,
155
+ "trades_per_year": 0.3,
156
+ }
157
+
158
+ elif self == BacktestEvaluationFocus.PROFIT:
159
+ return {
160
+ # Maximize absolute and relative profits
161
+ "total_net_gain_percentage": 3.0,
162
+ "cagr": 2.5,
163
+ "total_net_gain": 2.0,
164
+ "average_trade_return_percentage": 1.5,
165
+ "best_trade": 1.0,
166
+
167
+ # Profit consistency
168
+ "win_rate": 2.0,
169
+ "profit_factor": 2.0,
170
+ "percentage_positive_trades": 1.0,
171
+
172
+ # Risk secondary (but still important)
173
+ "sharpe_ratio": 1.0,
174
+ "max_drawdown": -1.0,
175
+
176
+ # Activity level (need some trades)
177
+ "number_of_trades": 0.5,
178
+
179
+ # Monthly/yearly consistency
180
+ "percentage_winning_months": 0.8,
181
+ "average_monthly_return": 1.0,
182
+ }
183
+
184
+ elif self == BacktestEvaluationFocus.FREQUENCY:
185
+ return {
186
+ # High trading activity with good results
187
+ "number_of_trades": 3.0,
188
+ "trades_per_year": 2.5,
189
+ "exposure_ratio": 2.0,
190
+
191
+ # Profitability per trade (scaled for frequency)
192
+ "average_trade_return_percentage": 2.0,
193
+ "win_rate": 2.5,
194
+ "total_net_gain_percentage": 1.5,
195
+
196
+ # Consistency across many trades
197
+ "win_loss_ratio": 1.5,
198
+ "percentage_positive_trades": 1.0,
199
+
200
+ # Risk management for frequent trading
201
+ "max_drawdown": -1.5,
202
+ "sharpe_ratio": 1.0,
203
+ "profit_factor": 1.2,
204
+
205
+ # Duration efficiency
206
+ "average_trade_duration": -0.3, # Prefer shorter trades
207
+ }
208
+
209
+ elif self == BacktestEvaluationFocus.RISK_ADJUSTED:
210
+ return {
211
+ # Risk-adjusted performance metrics (highest priority)
212
+ "sharpe_ratio": 3.0,
213
+ "sortino_ratio": 2.5,
214
+ "calmar_ratio": 2.0,
215
+
216
+ # Risk management (strong penalties)
217
+ "max_drawdown": -3.0,
218
+ "max_drawdown_duration": -1.5,
219
+ "annual_volatility": -2.0,
220
+ "worst_trade": -1.0,
221
+
222
+ # Consistent performance
223
+ "win_rate": 2.0,
224
+ "win_loss_ratio": 1.5,
225
+ "percentage_winning_months": 1.5,
226
+
227
+ # Stable returns
228
+ "average_trade_return_percentage": 1.5,
229
+ "total_net_gain_percentage": 1.0,
230
+ "profit_factor": 1.8,
231
+
232
+ # Reasonable activity
233
+ "number_of_trades": 0.5,
234
+
235
+ # Downside protection
236
+ "average_trade_loss_percentage": -1.0,
237
+ "percentage_negative_trades": -1.0,
238
+ }
239
+
240
+ # Fallback to balanced if unknown focus
241
+ return self.get_weights() \
242
+ if self != BacktestEvaluationFocus.BALANCED else {}
@@ -0,0 +1,459 @@
1
+ import os
2
+ from pathlib import Path
3
+ from dataclasses import dataclass, field
4
+ from logging import getLogger
5
+ from typing import Tuple, List, Dict
6
+ from datetime import datetime, date
7
+ import json
8
+ import pandas as pd
9
+
10
+ from investing_algorithm_framework.domain.models import Trade
11
+
12
+
13
+ logger = getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class BacktestMetrics:
18
+ """
19
+ Represents the result of a backtest, including metrics such as
20
+ total return, annualized return, volatility, Sharpe ratio,
21
+ and maximum drawdown.
22
+
23
+ Attributes:
24
+ backtest_start_date (datetime): The start date of the backtest.
25
+ backtest_end_date (datetime): The end date of the backtest.
26
+ final_value (float): The final value of the portfolio at the end
27
+ of the backtest.
28
+ equity_curve (List[Tuple[datetime, float]]): A list of
29
+ tuples representing the equity curve, where each tuple
30
+ contains a date and the corresponding portfolio value.
31
+ total_growth (float): The growth of the portfolio over the
32
+ backtest period.
33
+ total_growth_percentage (float): The percentage growth of the portfolio
34
+ over the backtest period.
35
+ total_net_gain (float): The total return of the backtest.
36
+ total_net_gain_percentage (float): The total return percentage
37
+ total_loss (float): The total loss of the backtest.
38
+ total_loss_percentage (float): The total loss percentage
39
+ cagr (float): The compound annual growth rate of the backtest.
40
+ sharpe_ratio (float): The Sharpe ratio of the backtest, indicating
41
+ risk-adjusted return.
42
+ rolling_sharpe_ratio (List[Tuple[datetime, float]): A list of rolling
43
+ Sharpe ratios over the backtest period.
44
+ sortino_ratio (float): The Sortino ratio of the backtest, focusing
45
+ on downside risk.
46
+ calmar_ratio (float): The Calmar ratio of the backtest, comparing
47
+ CAGR to maximum drawdown.
48
+ profit_factor (float): The profit factor of the backtest, calculated
49
+ as total profit divided by total loss.
50
+ annual_volatility (float): The annualized volatility of the
51
+ portfolio returns.
52
+ monthly_returns (List[Tuple[datetime, float]]): A list of monthly
53
+ returns during the backtest.
54
+ yearly_returns (List[Tuple[datetime, float]]): A list of yearly returns
55
+ during the backtest.
56
+ drawdown_series (List[Tuple[datetime, float]]): A list of drawdown
57
+ values over the backtest period.
58
+ max_drawdown (float): The maximum drawdown observed during
59
+ the backtest.
60
+ max_drawdown_absolute (float): The maximum absolute drawdown
61
+ observed during the backtest.
62
+ max_daily_drawdown (float): The maximum daily drawdown
63
+ observed during the backtest.
64
+ max_drawdown_duration (int): The duration of the maximum
65
+ drawdown in days.
66
+ trades_per_year (float): The average number of trades
67
+ executed per year.
68
+ trade_per_day (float): The average number of trades executed per day.
69
+ exposure_ratio (float): The exposure ratio, indicating the
70
+ average exposure of the portfolio.
71
+ cumulative_exposure (float): The cumulative exposure, indicating the
72
+ total exposure of the portfolio over the backtest period.
73
+ average_trade_size (float): The average size of trades executed
74
+ during the backtest.
75
+ average_trade_loss (float): The average loss from losing trades.
76
+ average_trade_loss_percentage (float): The average loss percentage
77
+ from losing trades.
78
+ average_trade_gain (float): The average gain from winning trades.
79
+ average_trade_gain_percentage (float): The average gain percentage
80
+ from winning trades.
81
+ average_trade_return (float): The average return from all trades.
82
+ average_trade_return_percentage (float): The average return percentage
83
+ from all trades.
84
+ median_trade_return (float): The median return from all trades.
85
+ median_trade_return_percentage (float): The median return percentage
86
+ from all trades.
87
+ number_of_positive_trades (int): The total number of profitable trades
88
+ executed during the backtest.
89
+ number_of_negative_trades (int): The total number of unprofitable
90
+ trades executed during the backtest.
91
+ best_trade (float): A string representation of the best trade,
92
+ including net gain and percentage.
93
+ worst_trade (float): A string representation of the worst trade,
94
+ including net loss and percentage.
95
+ average_trade_duration (float): The average duration of
96
+ trades in hours.
97
+ number_of_trades (int): The total number of trades executed
98
+ during the backtest.
99
+ number_of_trades_closed (int): The total number of trades close
100
+ during the backtest.
101
+ number_of_trades_opened (int): The total number of trades opened
102
+ during the backtest.
103
+ number_of_trades_open_at_end (int): The number of trades
104
+ still open at the end of the backtest.
105
+ win_rate (float): The win rate of the trades, expressed
106
+ as a percentage.
107
+ current_win_rate (float): The current win rate of the trades,
108
+ including open trades.
109
+ win_loss_ratio (float): The ratio of winning trades
110
+ to losing trades.
111
+ current_win_loss_ratio (float): The current ratio of winning
112
+ trades to losing trades, including open trades.
113
+ percentage_winning_months (float): The percentage of months
114
+ with positive returns.
115
+ percentage_winning_years (float): The percentage of years with
116
+ positive returns.
117
+ percentage_positive_trades (float): The percentage of trades that
118
+ were profitable.
119
+ percentage_negative_trades (float): The percentage of trades that
120
+ were unprofitable.
121
+ average_monthly_return (float): The average monthly return
122
+ of the portfolio.
123
+ average_monthly_return_losing_months (float): The average monthly
124
+ return during losing months.
125
+ average_monthly_return_winning_months (float): The average monthly
126
+ return during winning months.
127
+ best_month (datetime): A string representation of the best month,
128
+ including return and date.
129
+ best_year (datetime): A string representation of the best year,
130
+ including return and date.
131
+ worst_month (datetime): A string representation of the worst month,
132
+ including return and date.
133
+ worst_year (datetime): A string representation of the worst year,
134
+ including return and date.
135
+ metadata (Dict[str, str]): A dictionary to store any additional
136
+ metadata related to the backtest.
137
+ """
138
+ backtest_start_date: datetime
139
+ backtest_end_date: datetime
140
+ equity_curve: List[Tuple[float, datetime]] = field(default_factory=list)
141
+ total_growth: float = 0.0
142
+ total_growth_percentage: float = 0.0
143
+ total_net_gain: float = 0.0
144
+ total_net_gain_percentage: float = 0.0
145
+ total_loss: float = 0.0
146
+ total_loss_percentage: float = 0.0
147
+ final_value: float = 0.0
148
+ cumulative_return: float = 0.0
149
+ cumulative_return_series: List[Tuple[float, datetime]] = \
150
+ field(default_factory=list)
151
+ cagr: float = 0.0
152
+ sharpe_ratio: float = 0.0
153
+ rolling_sharpe_ratio: List[Tuple[float, datetime]] = \
154
+ field(default_factory=list)
155
+ sortino_ratio: float = 0.0
156
+ calmar_ratio: float = 0.0
157
+ profit_factor: float = 0.0
158
+ gross_profit: float = None
159
+ gross_loss: float = None
160
+ annual_volatility: float = 0.0
161
+ monthly_returns: List[Tuple[float, datetime]] = field(default_factory=list)
162
+ yearly_returns: List[Tuple[float, date]] = field(default_factory=list)
163
+ drawdown_series: List[Tuple[float, datetime]] = field(default_factory=list)
164
+ max_drawdown: float = 0.0
165
+ max_drawdown_absolute: float = 0.0
166
+ max_daily_drawdown: float = 0.0
167
+ max_drawdown_duration: int = 0
168
+ trades_per_year: float = 0.0
169
+ trade_per_day: float = 0.0
170
+ exposure_ratio: float = 0.0
171
+ cumulative_exposure: float = 0.0
172
+ best_trade: Trade = None
173
+ worst_trade: Trade = None
174
+ number_of_positive_trades: int = 0
175
+ percentage_positive_trades: float = 0.0
176
+ number_of_negative_trades: int = 0
177
+ percentage_negative_trades: float = 0.0
178
+ average_trade_duration: float = 0.0
179
+ average_trade_size: float = 0.0
180
+ average_trade_loss: float = 0.0
181
+ average_trade_loss_percentage: float = 0.0
182
+ average_trade_gain: float = 0.0
183
+ average_trade_gain_percentage: float = 0.0
184
+ average_trade_return: float = 0.0
185
+ average_trade_return_percentage: float = 0.0
186
+ current_average_trade_gain: float = 0.0
187
+ current_average_trade_gain_percentage: float = 0.0
188
+ current_average_trade_return: float = 0.0
189
+ current_average_trade_return_percentage: float = 0.0
190
+ current_average_trade_duration: float = 0.0
191
+ current_average_trade_loss: float = 0.0
192
+ current_average_trade_loss_percentage: float = 0.0
193
+ median_trade_return: float = 0.0
194
+ median_trade_return_percentage: float = 0.0
195
+ number_of_trades: int = 0
196
+ number_of_trades_closed: int = 0
197
+ number_of_trades_opened: int = 0
198
+ number_of_trades_open_at_end: int = 0
199
+ win_rate: float = 0.0
200
+ current_win_rate: float = 0.0
201
+ win_loss_ratio: float = 0.0
202
+ current_win_loss_ratio: float = 0.0
203
+ percentage_winning_months: float = 0.0
204
+ percentage_winning_years: float = 0.0
205
+ average_monthly_return: float = 0.0
206
+ average_monthly_return_losing_months: float = 0.0
207
+ average_monthly_return_winning_months: float = 0.0
208
+ best_month: Tuple[float, datetime] = None
209
+ best_year: Tuple[float, date] = None
210
+ worst_month: Tuple[float, datetime] = None
211
+ worst_year: Tuple[float, date] = None
212
+ total_number_of_days: int = None
213
+ metadata: Dict[str, str] = field(default_factory=dict)
214
+
215
+ def __post_init__(self):
216
+ self.total_number_of_days = (self.backtest_end_date -
217
+ self.backtest_start_date).days
218
+
219
+ def to_dict(self) -> dict:
220
+ """
221
+ Convert the BacktestMetrics instance to a dictionary.
222
+ Ensures all datetime values are serialized to ISO format, but
223
+ leaves strings unchanged.
224
+
225
+ Returns:
226
+ dict: A dictionary representation of the BacktestMetrics instance.
227
+ """
228
+
229
+ def ensure_iso(value):
230
+ return value.isoformat() \
231
+ if hasattr(value, "isoformat") else value
232
+
233
+ return {
234
+ "backtest_start_date": ensure_iso(self.backtest_start_date),
235
+ "backtest_end_date": ensure_iso(self.backtest_end_date),
236
+ "equity_curve": [(value, ensure_iso(date))
237
+ for value, date in self.equity_curve],
238
+ "final_value": self.final_value,
239
+ "total_net_gain": self.total_net_gain,
240
+ "total_net_gain_percentage": self.total_net_gain_percentage,
241
+ "total_growth": self.total_growth,
242
+ "total_growth_percentage": self.total_growth_percentage,
243
+ "total_loss": self.total_loss,
244
+ "total_loss_percentage": self.total_loss_percentage,
245
+ "cumulative_return": self.cumulative_return,
246
+ "cumulative_return_series": [(value, ensure_iso(date))
247
+ for value, date in
248
+ self.cumulative_return_series],
249
+ "cagr": self.cagr,
250
+ "sharpe_ratio": self.sharpe_ratio,
251
+ "rolling_sharpe_ratio": [(value, ensure_iso(date))
252
+ for value, date in
253
+ self.rolling_sharpe_ratio],
254
+ "sortino_ratio": self.sortino_ratio,
255
+ "calmar_ratio": self.calmar_ratio,
256
+ "profit_factor": self.profit_factor,
257
+ "annual_volatility": self.annual_volatility,
258
+ "monthly_returns": [(value, ensure_iso(date))
259
+ for value, date in self.monthly_returns],
260
+ "yearly_returns": [(value, ensure_iso(date))
261
+ for value, date in self.yearly_returns],
262
+ "drawdown_series": [(value, ensure_iso(date))
263
+ for value, date in self.drawdown_series],
264
+ "max_drawdown": self.max_drawdown,
265
+ "max_drawdown_absolute": self.max_drawdown_absolute,
266
+ "max_daily_drawdown": self.max_daily_drawdown,
267
+ "max_drawdown_duration": self.max_drawdown_duration,
268
+ "trades_per_year": self.trades_per_year,
269
+ "trade_per_day": self.trade_per_day,
270
+ "exposure_ratio": self.exposure_ratio,
271
+ "cumulative_exposure": self.cumulative_exposure,
272
+ "average_trade_gain": self.average_trade_gain,
273
+ "average_trade_gain_percentage":
274
+ self.average_trade_gain_percentage,
275
+ "average_trade_loss": self.average_trade_loss,
276
+ "average_trade_loss_percentage":
277
+ self.average_trade_loss_percentage,
278
+ "average_trade_return": self.average_trade_return,
279
+ "average_trade_return_percentage":
280
+ self.average_trade_return_percentage,
281
+ "median_trade_return": self.median_trade_return,
282
+ "median_trade_return_percentage":
283
+ self.median_trade_return_percentage,
284
+ "number_of_positive_trades": self.number_of_positive_trades,
285
+ "percentage_positive_trades": self.percentage_positive_trades,
286
+ "number_of_negative_trades": self.number_of_negative_trades,
287
+ "percentage_negative_trades": self.percentage_negative_trades,
288
+ "best_trade": self.best_trade.to_dict()
289
+ if self.best_trade else None,
290
+ "worst_trade": self.worst_trade.to_dict()
291
+ if self.worst_trade else None,
292
+ "average_trade_duration": self.average_trade_duration,
293
+ "average_trade_size": self.average_trade_size,
294
+ "number_of_trades": self.number_of_trades,
295
+ "number_of_trades_closed": self.number_of_trades_closed,
296
+ "number_of_trades_opened": self.number_of_trades_opened,
297
+ "win_rate": self.win_rate,
298
+ "current_win_rate": self.current_win_rate,
299
+ "win_loss_ratio": self.win_loss_ratio,
300
+ "current_win_loss_ratio": self.current_win_loss_ratio,
301
+ "percentage_winning_months": self.percentage_winning_months,
302
+ "percentage_winning_years": self.percentage_winning_years,
303
+ "average_monthly_return": self.average_monthly_return,
304
+ "average_monthly_return_losing_months":
305
+ self.average_monthly_return_losing_months,
306
+ "average_monthly_return_winning_months":
307
+ self.average_monthly_return_winning_months,
308
+ "best_month": self.best_month,
309
+ "best_year": self.best_year,
310
+ "worst_month": self.worst_month,
311
+ "worst_year": self.worst_year
312
+ }
313
+
314
+ def save(self, file_path: str | Path) -> None:
315
+ """
316
+ Save the backtest metrics to a file in JSON format. The metrics will
317
+ always be saved in a file named `metrics.json`
318
+
319
+ Args:
320
+ file_path (str): The directory where the metrics
321
+ file will be saved.
322
+ """
323
+ with open(file_path, 'w') as file:
324
+ json.dump(self.to_dict(), file, indent=4, default=str)
325
+
326
+ @staticmethod
327
+ def _parse_tuple_list_datetime(
328
+ data: List[List]
329
+ ) -> List[Tuple[float, datetime]]:
330
+ """
331
+ Parse a list of [value, datetime_string]
332
+ into List[Tuple[float, datetime]]
333
+ """
334
+ return [
335
+ (float(value), datetime.fromisoformat(date_str))
336
+ for value, date_str in data
337
+ ]
338
+
339
+ @staticmethod
340
+ def _parse_tuple_list_date(data: List[List]) -> List[Tuple[float, date]]:
341
+ """
342
+ Parse a list of [value, date_string] into List[Tuple[float, date]]
343
+ """
344
+ return [
345
+ (float(value), datetime.fromisoformat(date_str).date())
346
+ for value, date_str in data
347
+ ]
348
+
349
+ @staticmethod
350
+ def _parse_tuple_datetime(data) -> Tuple[float, datetime]:
351
+ """Parse a [value, datetime_string] into Tuple[float, datetime]"""
352
+ if data is None:
353
+ return None, None
354
+
355
+ # Check if the value is NaN or None
356
+ if pd.isna(data[0]) or data[0] is None:
357
+ value = pd.NA
358
+ else:
359
+ value = float(data[0])
360
+
361
+ # Parse the datetime string
362
+ if data[1] is None or pd.isna(data[1]):
363
+ value = None
364
+ else:
365
+ # Convert the string to a datetime object
366
+ date = datetime.fromisoformat(data[1])
367
+
368
+ return (value, date)
369
+
370
+ @staticmethod
371
+ def _parse_tuple_date(data) -> Tuple[float, date]:
372
+ """Parse a [value, date_string] into Tuple[float, date]"""
373
+ if data is None:
374
+ return None, None
375
+
376
+ # Check if the value is
377
+ if pd.isna(data[0]) or data[0] is None:
378
+ value = pd.NA
379
+ else:
380
+ value = float(data[0])
381
+
382
+ # Parse the date string
383
+ if data[1] is None or pd.isna(data[1]):
384
+ date = None
385
+ else:
386
+ date = datetime.fromisoformat(data[1]).date()
387
+
388
+ return (value, date)
389
+
390
+ @staticmethod
391
+ def open(file_path: str | Path) -> 'BacktestMetrics':
392
+ """
393
+ Open a backtest metrics file from a directory and
394
+ return a BacktestMetrics instance.
395
+
396
+ Args:
397
+ file_path (str): The path to the metrics file.
398
+
399
+ Returns:
400
+ BacktestMetrics: An instance of BacktestMetrics
401
+ loaded from the file.
402
+ """
403
+ if not os.path.exists(file_path):
404
+ raise FileNotFoundError(f"Metrics file not found at {file_path}")
405
+
406
+ with open(file_path, 'r') as file:
407
+ data = json.load(file)
408
+
409
+ # Parse datetime fields
410
+ data['backtest_start_date'] = datetime.fromisoformat(
411
+ data['backtest_start_date']
412
+ )
413
+ data['backtest_end_date'] = datetime.fromisoformat(
414
+ data['backtest_end_date']
415
+ )
416
+
417
+ # Parse tuple lists with datetime
418
+ data['equity_curve'] = BacktestMetrics._parse_tuple_list_datetime(
419
+ data.get('equity_curve', [])
420
+ )
421
+ data['rolling_sharpe_ratio'] = BacktestMetrics\
422
+ ._parse_tuple_list_datetime(data.get('rolling_sharpe_ratio', []))
423
+ data['monthly_returns'] = BacktestMetrics\
424
+ ._parse_tuple_list_datetime(data.get('monthly_returns', []))
425
+ data['drawdown_series'] = BacktestMetrics\
426
+ ._parse_tuple_list_datetime(data.get('drawdown_series', []))
427
+
428
+ # Parse tuple lists with date
429
+ data['yearly_returns'] = BacktestMetrics\
430
+ ._parse_tuple_list_date(data.get('yearly_returns', []))
431
+
432
+ # Parse single tuples
433
+ data['best_month'] = BacktestMetrics\
434
+ ._parse_tuple_datetime(data.get('best_month'))
435
+ data['worst_month'] = BacktestMetrics\
436
+ ._parse_tuple_datetime(data.get('worst_month'))
437
+ data['best_year'] = BacktestMetrics\
438
+ ._parse_tuple_date(data.get('best_year'))
439
+ data['worst_year'] = BacktestMetrics\
440
+ ._parse_tuple_date(data.get('worst_year'))
441
+
442
+ # Parse Trade objects if they exist
443
+ if data.get('best_trade'):
444
+ data['best_trade'] = Trade.from_dict(data['best_trade'])
445
+ if data.get('worst_trade'):
446
+ data['worst_trade'] = Trade.from_dict(data['worst_trade'])
447
+
448
+ return BacktestMetrics(**data)
449
+
450
+ def __repr__(self):
451
+ """
452
+ Return a string representation of the Backtest instance.
453
+
454
+ Returns:
455
+ str: A string representation of the Backtest instance.
456
+ """
457
+ return json.dumps(
458
+ self.to_dict(), indent=4, sort_keys=True, default=str
459
+ )