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,275 @@
1
+ import os
2
+ import json
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import List, Dict
6
+ import numpy as np
7
+ import pandas as pd
8
+ from datetime import timezone
9
+
10
+ from .backtest_metrics import BacktestMetrics
11
+
12
+
13
+ @dataclass
14
+ class BacktestPermutationTest:
15
+ """
16
+ Represents the result of a permutation test on backtest metrics.
17
+
18
+ Attributes:
19
+ real_metrics (BacktestMetrics): The real backtest metrics.
20
+ permutated_metrics (List[BacktestMetrics]): A list of backtest
21
+ metrics objects from permuted backtests.
22
+ p_values (Dict[str, float]): A dictionary mapping metric names
23
+ to their permutation test p-values.
24
+ """
25
+
26
+ # Default set of metrics for permutation testing
27
+ DEFAULT_METRICS: List[str] = field(default_factory=lambda: [
28
+ "cagr",
29
+ "sharpe_ratio",
30
+ "sortino_ratio",
31
+ "calmar_ratio",
32
+ "profit_factor",
33
+ "annual_volatility",
34
+ "max_drawdown",
35
+ "win_rate",
36
+ "win_loss_ratio",
37
+ "average_monthly_return"
38
+ ])
39
+ real_metrics: BacktestMetrics = None
40
+ permutated_metrics: List[BacktestMetrics] = field(default_factory=list)
41
+ p_values: Dict[str, float] = field(default_factory=dict)
42
+ ohlcv_permutated_datasets: Dict[str, List[pd.DataFrame]] = \
43
+ field(default_factory=dict)
44
+ ohlcv_original_datasets: Dict[str, pd.DataFrame] = \
45
+ field(default_factory=dict)
46
+ backtest_start_date: pd.Timestamp = None
47
+ backtest_end_date: pd.Timestamp = None
48
+ backtest_date_range_name: str = None
49
+
50
+ def compute_p_values(
51
+ self, metrics: List[str] = None, one_sided: bool = True
52
+ ) -> None:
53
+ """
54
+ Compute p-values for the selected metrics based on the
55
+ permutation distribution.
56
+
57
+ Args:
58
+ metrics (List[str]): List of metric names to compute p-values for.
59
+ If None, uses DEFAULT_METRICS.
60
+ one_sided (bool): Whether to compute a one-sided
61
+ test (default: True).
62
+ """
63
+ if metrics is None:
64
+ metrics = self.DEFAULT_METRICS
65
+
66
+ self.p_values = {}
67
+
68
+ for metric in metrics:
69
+ real_value = getattr(self.real_metrics, metric, None)
70
+ if real_value is None:
71
+ continue
72
+
73
+ # Collect metric values across all permuted backtests
74
+ dist = np.array([
75
+ getattr(pm, metric, None)
76
+ for pm in self.permutated_metrics
77
+ if getattr(pm, metric, None) is not None
78
+ ])
79
+
80
+ if len(dist) == 0:
81
+ continue
82
+
83
+ if one_sided:
84
+ p = np.mean(dist >= real_value)
85
+ else:
86
+ p = np.mean(np.abs(dist) >= abs(real_value))
87
+
88
+ self.p_values[metric] = float(p)
89
+
90
+ def summary(
91
+ self, metrics: List[str] = None
92
+ ) -> Dict[str, Dict[str, float]]:
93
+ """
94
+ Return a summary of real values, mean permuted values, and p-values.
95
+
96
+ Args:
97
+ metrics (List[str]): List of metric names to include
98
+ in the summary. If None, uses DEFAULT_METRICS.
99
+
100
+ Returns:
101
+ Dict[str, Dict[str, float]]: A dictionary where each key
102
+ is a metric name and the value is another dictionary
103
+ with keys 'real', 'permuted_mean', and 'p_value'.
104
+ """
105
+
106
+ if metrics is None:
107
+ metrics = self.DEFAULT_METRICS
108
+
109
+ if not self.p_values: # lazy compute
110
+ self.compute_p_values(metrics=metrics)
111
+
112
+ summary_dict = {}
113
+ for metric in metrics:
114
+ real_value = getattr(self.real_metrics, metric, None)
115
+ if real_value is None:
116
+ continue
117
+
118
+ dist = np.array([
119
+ getattr(pm, metric, None)
120
+ for pm in self.permutated_metrics
121
+ if getattr(pm, metric, None) is not None
122
+ ])
123
+
124
+ # Filter out inf / nan
125
+ dist = dist[np.isfinite(dist)]
126
+
127
+ real_value = getattr(self.real_metrics, metric, None)
128
+
129
+ if real_value is None or not np.isfinite(real_value):
130
+ continue
131
+
132
+ if len(dist) == 0:
133
+ continue
134
+
135
+ summary_dict[metric] = {
136
+ "real": float(real_value),
137
+ "permuted_mean": float(np.mean(dist)),
138
+ "p_value": self.p_values.get(metric, None),
139
+ }
140
+
141
+ return summary_dict
142
+
143
+ def save(self, path: str) -> None:
144
+ """
145
+ Save the permutation test results to disk (JSON + Parquet).
146
+
147
+ Args:
148
+ path (str): The directory path where to save the results.
149
+
150
+ Returns:
151
+ None
152
+ """
153
+ def ensure_iso(value):
154
+ if hasattr(value, "isoformat"):
155
+ if value.tzinfo is None:
156
+ value = value.replace(tzinfo=timezone.utc)
157
+ return value.isoformat()
158
+ return value
159
+
160
+ os.makedirs(path, exist_ok=True)
161
+
162
+ # Save the real metrics
163
+ self.real_metrics.save(os.path.join(path, "original_metrics.json"))
164
+
165
+ permuted_dir = os.path.join(path, "permuted_metrics")
166
+ os.makedirs(permuted_dir, exist_ok=True)
167
+
168
+ for i, pm in enumerate(self.permutated_metrics):
169
+ pm.save(os.path.join(permuted_dir, f"permuted_{i}.json"))
170
+
171
+ # Save the P-values
172
+ with open(os.path.join(path, "p_values.json"), "w") as f:
173
+ json.dump(self.p_values, f)
174
+
175
+ # Create a metadata file to store additional info such as
176
+ # date range name, start and end dates
177
+ metadata = {
178
+ "backtest_start_date": ensure_iso(self.backtest_start_date),
179
+ "backtest_date_range_name": self.backtest_date_range_name,
180
+ "backtest_end_date": ensure_iso(self.backtest_end_date),
181
+ }
182
+
183
+ with open(os.path.join(path, "metadata.json"), "w") as f:
184
+ json.dump(metadata, f)
185
+
186
+ @staticmethod
187
+ def open(path: str) -> "BacktestPermutationTest":
188
+ """
189
+ Load the permutation test results from disk (JSON + Parquet).
190
+
191
+ Args:
192
+ path (str): The directory path where the results are saved.
193
+
194
+ Returns:
195
+ BacktestPermutationTest: The loaded permutation test results.
196
+ """
197
+ original_metrics = os.path.join(path, "original_metrics.json")
198
+
199
+ # Rehydrate BacktestMetrics
200
+ real_metrics = BacktestMetrics.open(original_metrics)
201
+
202
+ permuted_dir = os.path.join(path, "permuted_metrics")
203
+
204
+ permutated_metrics = []
205
+ if os.path.exists(permuted_dir):
206
+ for fname in os.listdir(permuted_dir):
207
+ if fname.startswith("permuted_"):
208
+ pm = BacktestMetrics.open(
209
+ os.path.join(permuted_dir, fname)
210
+ )
211
+ permutated_metrics.append(pm)
212
+
213
+ p_values_path = os.path.join(path, "p_values.json")
214
+ p_values = {}
215
+
216
+ if os.path.exists(p_values_path):
217
+ with open(p_values_path, "r") as f:
218
+ p_values = json.load(f)
219
+
220
+ # Load metadata
221
+ metadata_path = os.path.join(path, "metadata.json")
222
+ backtest_start_date = None
223
+ backtest_end_date = None
224
+ backtest_date_range_name = None
225
+
226
+ if os.path.exists(metadata_path):
227
+ with open(metadata_path, "r") as f:
228
+ metadata = json.load(f)
229
+
230
+ backtest_start_date = pd.to_datetime(
231
+ metadata.get("backtest_start_date"), utc=True
232
+ )
233
+ backtest_end_date = pd.to_datetime(
234
+ metadata.get("backtest_end_date"), utc=True
235
+ )
236
+ backtest_date_range_name = metadata.get(
237
+ "backtest_date_range_name"
238
+ )
239
+
240
+ return BacktestPermutationTest(
241
+ real_metrics=real_metrics,
242
+ permutated_metrics=permutated_metrics,
243
+ p_values=p_values,
244
+ backtest_start_date=backtest_start_date,
245
+ backtest_end_date=backtest_end_date,
246
+ backtest_date_range_name=backtest_date_range_name
247
+ )
248
+
249
+ def create_directory_name(self) -> str:
250
+ """
251
+ Create a directory name for the backtest run based on its attributes.
252
+
253
+ Returns:
254
+ str: A string representing the directory name.
255
+ """
256
+ start_str = self.real_metrics.backtest_start_date.strftime("%Y%m%d")
257
+ end_str = self.real_metrics.backtest_end_date.strftime("%Y%m%d")
258
+ dir_name = f"permutation_test_{start_str}_{end_str}"
259
+ return dir_name
260
+
261
+ def to_dict(self) -> Dict:
262
+ """
263
+ Convert the permutation test results to a dictionary.
264
+
265
+ Returns:
266
+ dict: A dictionary representation of the permutation test results.
267
+ """
268
+ return {
269
+ "real_metrics": self.real_metrics.to_dict(),
270
+ "permutated_metrics": [
271
+ pm.to_dict() for pm in self.permutated_metrics
272
+ ],
273
+ "p_values": self.p_values,
274
+ # Note: DataFrames are not included in the dict representation
275
+ }