ml4t-diagnostic 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. ml4t/diagnostic/AGENT.md +25 -0
  2. ml4t/diagnostic/__init__.py +166 -0
  3. ml4t/diagnostic/backends/__init__.py +10 -0
  4. ml4t/diagnostic/backends/adapter.py +192 -0
  5. ml4t/diagnostic/backends/polars_backend.py +899 -0
  6. ml4t/diagnostic/caching/__init__.py +40 -0
  7. ml4t/diagnostic/caching/cache.py +331 -0
  8. ml4t/diagnostic/caching/decorators.py +131 -0
  9. ml4t/diagnostic/caching/smart_cache.py +339 -0
  10. ml4t/diagnostic/config/AGENT.md +24 -0
  11. ml4t/diagnostic/config/README.md +267 -0
  12. ml4t/diagnostic/config/__init__.py +219 -0
  13. ml4t/diagnostic/config/barrier_config.py +277 -0
  14. ml4t/diagnostic/config/base.py +301 -0
  15. ml4t/diagnostic/config/event_config.py +148 -0
  16. ml4t/diagnostic/config/feature_config.py +404 -0
  17. ml4t/diagnostic/config/multi_signal_config.py +55 -0
  18. ml4t/diagnostic/config/portfolio_config.py +215 -0
  19. ml4t/diagnostic/config/report_config.py +391 -0
  20. ml4t/diagnostic/config/sharpe_config.py +202 -0
  21. ml4t/diagnostic/config/signal_config.py +206 -0
  22. ml4t/diagnostic/config/trade_analysis_config.py +310 -0
  23. ml4t/diagnostic/config/validation.py +279 -0
  24. ml4t/diagnostic/core/__init__.py +29 -0
  25. ml4t/diagnostic/core/numba_utils.py +315 -0
  26. ml4t/diagnostic/core/purging.py +372 -0
  27. ml4t/diagnostic/core/sampling.py +471 -0
  28. ml4t/diagnostic/errors/__init__.py +205 -0
  29. ml4t/diagnostic/evaluation/AGENT.md +26 -0
  30. ml4t/diagnostic/evaluation/__init__.py +437 -0
  31. ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
  32. ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
  33. ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
  34. ml4t/diagnostic/evaluation/dashboard.py +715 -0
  35. ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
  36. ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
  37. ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
  38. ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
  39. ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
  40. ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
  41. ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
  42. ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
  43. ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
  44. ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
  45. ml4t/diagnostic/evaluation/event_analysis.py +647 -0
  46. ml4t/diagnostic/evaluation/excursion.py +390 -0
  47. ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
  48. ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
  49. ml4t/diagnostic/evaluation/framework.py +935 -0
  50. ml4t/diagnostic/evaluation/metric_registry.py +255 -0
  51. ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
  52. ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
  53. ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
  54. ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
  55. ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
  56. ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
  57. ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
  58. ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
  59. ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
  60. ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
  61. ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
  62. ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
  63. ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
  64. ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
  65. ml4t/diagnostic/evaluation/multi_signal.py +550 -0
  66. ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
  67. ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
  68. ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
  69. ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
  70. ml4t/diagnostic/evaluation/report_generation.py +824 -0
  71. ml4t/diagnostic/evaluation/signal_selector.py +452 -0
  72. ml4t/diagnostic/evaluation/stat_registry.py +139 -0
  73. ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
  74. ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
  75. ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
  76. ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
  77. ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
  78. ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
  79. ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
  80. ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
  81. ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
  82. ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
  83. ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
  84. ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
  85. ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
  86. ml4t/diagnostic/evaluation/stats/moments.py +164 -0
  87. ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
  88. ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
  89. ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
  90. ml4t/diagnostic/evaluation/themes.py +330 -0
  91. ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
  92. ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
  93. ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
  94. ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
  95. ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
  96. ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
  97. ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
  98. ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
  99. ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
  100. ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
  101. ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
  102. ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
  103. ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
  104. ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
  105. ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
  106. ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
  107. ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
  108. ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
  109. ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
  110. ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
  111. ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
  112. ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
  113. ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
  114. ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
  115. ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
  116. ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
  117. ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
  118. ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
  119. ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
  120. ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
  121. ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
  122. ml4t/diagnostic/evaluation/validated_cv.py +535 -0
  123. ml4t/diagnostic/evaluation/visualization.py +1050 -0
  124. ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
  125. ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
  126. ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
  127. ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
  128. ml4t/diagnostic/integration/__init__.py +48 -0
  129. ml4t/diagnostic/integration/backtest_contract.py +671 -0
  130. ml4t/diagnostic/integration/data_contract.py +316 -0
  131. ml4t/diagnostic/integration/engineer_contract.py +226 -0
  132. ml4t/diagnostic/logging/__init__.py +77 -0
  133. ml4t/diagnostic/logging/logger.py +245 -0
  134. ml4t/diagnostic/logging/performance.py +234 -0
  135. ml4t/diagnostic/logging/progress.py +234 -0
  136. ml4t/diagnostic/logging/wandb.py +412 -0
  137. ml4t/diagnostic/metrics/__init__.py +9 -0
  138. ml4t/diagnostic/metrics/percentiles.py +128 -0
  139. ml4t/diagnostic/py.typed +1 -0
  140. ml4t/diagnostic/reporting/__init__.py +43 -0
  141. ml4t/diagnostic/reporting/base.py +130 -0
  142. ml4t/diagnostic/reporting/html_renderer.py +275 -0
  143. ml4t/diagnostic/reporting/json_renderer.py +51 -0
  144. ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
  145. ml4t/diagnostic/results/AGENT.md +24 -0
  146. ml4t/diagnostic/results/__init__.py +105 -0
  147. ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
  148. ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
  149. ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
  150. ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
  151. ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
  152. ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
  153. ml4t/diagnostic/results/barrier_results/validation.py +38 -0
  154. ml4t/diagnostic/results/base.py +177 -0
  155. ml4t/diagnostic/results/event_results.py +349 -0
  156. ml4t/diagnostic/results/feature_results.py +787 -0
  157. ml4t/diagnostic/results/multi_signal_results.py +431 -0
  158. ml4t/diagnostic/results/portfolio_results.py +281 -0
  159. ml4t/diagnostic/results/sharpe_results.py +448 -0
  160. ml4t/diagnostic/results/signal_results/__init__.py +74 -0
  161. ml4t/diagnostic/results/signal_results/ic.py +581 -0
  162. ml4t/diagnostic/results/signal_results/irtc.py +110 -0
  163. ml4t/diagnostic/results/signal_results/quantile.py +392 -0
  164. ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
  165. ml4t/diagnostic/results/signal_results/turnover.py +213 -0
  166. ml4t/diagnostic/results/signal_results/validation.py +147 -0
  167. ml4t/diagnostic/signal/AGENT.md +17 -0
  168. ml4t/diagnostic/signal/__init__.py +69 -0
  169. ml4t/diagnostic/signal/_report.py +152 -0
  170. ml4t/diagnostic/signal/_utils.py +261 -0
  171. ml4t/diagnostic/signal/core.py +275 -0
  172. ml4t/diagnostic/signal/quantile.py +148 -0
  173. ml4t/diagnostic/signal/result.py +214 -0
  174. ml4t/diagnostic/signal/signal_ic.py +129 -0
  175. ml4t/diagnostic/signal/turnover.py +182 -0
  176. ml4t/diagnostic/splitters/AGENT.md +19 -0
  177. ml4t/diagnostic/splitters/__init__.py +36 -0
  178. ml4t/diagnostic/splitters/base.py +501 -0
  179. ml4t/diagnostic/splitters/calendar.py +421 -0
  180. ml4t/diagnostic/splitters/calendar_config.py +91 -0
  181. ml4t/diagnostic/splitters/combinatorial.py +1064 -0
  182. ml4t/diagnostic/splitters/config.py +322 -0
  183. ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
  184. ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
  185. ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
  186. ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
  187. ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
  188. ml4t/diagnostic/splitters/group_isolation.py +329 -0
  189. ml4t/diagnostic/splitters/persistence.py +316 -0
  190. ml4t/diagnostic/splitters/utils.py +207 -0
  191. ml4t/diagnostic/splitters/walk_forward.py +757 -0
  192. ml4t/diagnostic/utils/__init__.py +42 -0
  193. ml4t/diagnostic/utils/config.py +542 -0
  194. ml4t/diagnostic/utils/dependencies.py +318 -0
  195. ml4t/diagnostic/utils/sessions.py +127 -0
  196. ml4t/diagnostic/validation/__init__.py +54 -0
  197. ml4t/diagnostic/validation/dataframe.py +274 -0
  198. ml4t/diagnostic/validation/returns.py +280 -0
  199. ml4t/diagnostic/validation/timeseries.py +299 -0
  200. ml4t/diagnostic/visualization/AGENT.md +19 -0
  201. ml4t/diagnostic/visualization/__init__.py +223 -0
  202. ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
  203. ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
  204. ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
  205. ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
  206. ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
  207. ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
  208. ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
  209. ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
  210. ml4t/diagnostic/visualization/barrier_plots.py +782 -0
  211. ml4t/diagnostic/visualization/core.py +1060 -0
  212. ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
  213. ml4t/diagnostic/visualization/dashboards/base.py +582 -0
  214. ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
  215. ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
  216. ml4t/diagnostic/visualization/dashboards.py +43 -0
  217. ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
  218. ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
  219. ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
  220. ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
  221. ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
  222. ml4t/diagnostic/visualization/feature_plots.py +888 -0
  223. ml4t/diagnostic/visualization/interaction_plots.py +618 -0
  224. ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
  225. ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
  226. ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
  227. ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
  228. ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
  229. ml4t/diagnostic/visualization/report_generation.py +1343 -0
  230. ml4t/diagnostic/visualization/signal/__init__.py +103 -0
  231. ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
  232. ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
  233. ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
  234. ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
  235. ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
  236. ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
  237. ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
  238. ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
  239. ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
  240. ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
  241. ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
  242. ml4t_diagnostic-0.1.0a1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,281 @@
1
+ """Result schemas for portfolio evaluation (Module D).
2
+
3
+ Portfolio-level metrics and Bayesian strategy comparison.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ import polars as pl
11
+ from pydantic import Field
12
+
13
+ from ml4t.diagnostic.results.base import BaseResult
14
+
15
+
16
+ class PortfolioMetrics(BaseResult):
17
+ """Standard portfolio performance metrics.
18
+
19
+ Comprehensive set of risk-adjusted return metrics commonly used
20
+ in quantitative finance.
21
+
22
+ Attributes:
23
+ total_return: Cumulative return over period
24
+ annualized_return: Annualized return (CAGR)
25
+ annualized_volatility: Annualized standard deviation of returns
26
+ sharpe_ratio: Sharpe ratio (excess return / volatility)
27
+ sortino_ratio: Sortino ratio (excess return / downside deviation)
28
+ max_drawdown: Maximum peak-to-trough decline
29
+ calmar_ratio: Return / max drawdown
30
+ omega_ratio: Probability-weighted ratio of gains vs losses
31
+ win_rate: Fraction of positive return periods
32
+ avg_win: Average return on winning periods
33
+ avg_loss: Average return on losing periods
34
+ profit_factor: Gross profit / gross loss
35
+ skewness: Return distribution skewness
36
+ kurtosis: Return distribution kurtosis
37
+ """
38
+
39
+ analysis_type: str = "portfolio_metrics"
40
+
41
+ # Returns
42
+ total_return: float = Field(..., description="Cumulative return")
43
+ annualized_return: float = Field(..., description="Annualized return (CAGR)")
44
+
45
+ # Risk
46
+ annualized_volatility: float = Field(..., description="Annualized standard deviation")
47
+ max_drawdown: float = Field(..., description="Maximum drawdown (peak to trough)")
48
+
49
+ # Risk-adjusted returns
50
+ sharpe_ratio: float = Field(..., description="Sharpe ratio")
51
+ sortino_ratio: float = Field(..., description="Sortino ratio")
52
+ calmar_ratio: float = Field(..., description="Calmar ratio (return / max DD)")
53
+ omega_ratio: float | None = Field(None, description="Omega ratio")
54
+
55
+ # Win/loss statistics
56
+ win_rate: float = Field(..., description="Fraction of winning periods")
57
+ avg_win: float = Field(..., description="Average winning return")
58
+ avg_loss: float = Field(..., description="Average losing return")
59
+ profit_factor: float = Field(..., description="Gross profit / gross loss")
60
+
61
+ # Distribution characteristics
62
+ skewness: float = Field(..., description="Return skewness")
63
+ kurtosis: float = Field(..., description="Return excess kurtosis")
64
+
65
+ def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
66
+ """Get metrics as single-row DataFrame.
67
+
68
+ Returns:
69
+ DataFrame with all metrics
70
+ """
71
+ data = {
72
+ "total_return": [self.total_return],
73
+ "annualized_return": [self.annualized_return],
74
+ "annualized_volatility": [self.annualized_volatility],
75
+ "sharpe_ratio": [self.sharpe_ratio],
76
+ "sortino_ratio": [self.sortino_ratio],
77
+ "max_drawdown": [self.max_drawdown],
78
+ "calmar_ratio": [self.calmar_ratio],
79
+ "omega_ratio": [self.omega_ratio],
80
+ "win_rate": [self.win_rate],
81
+ "avg_win": [self.avg_win],
82
+ "avg_loss": [self.avg_loss],
83
+ "profit_factor": [self.profit_factor],
84
+ "skewness": [self.skewness],
85
+ "kurtosis": [self.kurtosis],
86
+ }
87
+ return pl.DataFrame(data)
88
+
89
+ def summary(self) -> str:
90
+ """Human-readable summary of portfolio metrics."""
91
+ lines = ["Portfolio Metrics Summary", "=" * 40]
92
+ lines.append(f"Total Return: {self.total_return:.2%}")
93
+ lines.append(f"Annualized Return: {self.annualized_return:.2%}")
94
+ lines.append(f"Annualized Volatility: {self.annualized_volatility:.2%}")
95
+ lines.append(f"Sharpe Ratio: {self.sharpe_ratio:.3f}")
96
+ lines.append(f"Sortino Ratio: {self.sortino_ratio:.3f}")
97
+ lines.append(f"Max Drawdown: {self.max_drawdown:.2%}")
98
+ lines.append(f"Calmar Ratio: {self.calmar_ratio:.3f}")
99
+ lines.append("")
100
+ lines.append(f"Win Rate: {self.win_rate:.2%}")
101
+ lines.append(f"Profit Factor: {self.profit_factor:.2f}")
102
+ lines.append(f"Skewness: {self.skewness:.3f}")
103
+ lines.append(f"Kurtosis: {self.kurtosis:.3f}")
104
+ return "\n".join(lines)
105
+
106
+
107
+ class BayesianComparisonResult(BaseResult):
108
+ """Bayesian strategy comparison results.
109
+
110
+ Uses Bayesian inference to compare two strategies, accounting for
111
+ parameter uncertainty and providing probabilistic conclusions.
112
+
113
+ Reference: Bailey & López de Prado (2012) "The Sharpe Ratio Efficient Frontier"
114
+
115
+ Attributes:
116
+ strategy_a_name: Name of first strategy
117
+ strategy_b_name: Name of second strategy
118
+ prior_sharpe_mean: Prior belief about Sharpe ratio mean
119
+ prior_sharpe_std: Prior belief about Sharpe ratio std
120
+ posterior_sharpe_mean: Updated belief after observing data
121
+ posterior_sharpe_std: Updated uncertainty
122
+ probability_a_better: P(Sharpe_A > Sharpe_B | data)
123
+ credible_interval_95: 95% credible interval for Sharpe difference
124
+ """
125
+
126
+ analysis_type: str = "bayesian_comparison"
127
+
128
+ strategy_a_name: str = Field(..., description="First strategy name")
129
+ strategy_b_name: str = Field(..., description="Second strategy name")
130
+
131
+ prior_sharpe_mean: float = Field(..., description="Prior Sharpe mean")
132
+ prior_sharpe_std: float = Field(..., description="Prior Sharpe std")
133
+
134
+ posterior_sharpe_mean: float = Field(..., description="Posterior Sharpe mean")
135
+ posterior_sharpe_std: float = Field(..., description="Posterior Sharpe std")
136
+
137
+ probability_a_better: float = Field(..., description="P(strategy A > strategy B | data)")
138
+ credible_interval_95: tuple[float, float] = Field(
139
+ ..., description="95% credible interval for Sharpe difference"
140
+ )
141
+
142
+ def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
143
+ """Get comparison results as single-row DataFrame.
144
+
145
+ Returns:
146
+ DataFrame with comparison statistics
147
+ """
148
+ data = {
149
+ "strategy_a": [self.strategy_a_name],
150
+ "strategy_b": [self.strategy_b_name],
151
+ "prior_mean": [self.prior_sharpe_mean],
152
+ "prior_std": [self.prior_sharpe_std],
153
+ "posterior_mean": [self.posterior_sharpe_mean],
154
+ "posterior_std": [self.posterior_sharpe_std],
155
+ "prob_a_better": [self.probability_a_better],
156
+ "ci_lower": [self.credible_interval_95[0]],
157
+ "ci_upper": [self.credible_interval_95[1]],
158
+ }
159
+ return pl.DataFrame(data)
160
+
161
+ def summary(self) -> str:
162
+ """Human-readable summary of Bayesian comparison."""
163
+ lines = ["Bayesian Strategy Comparison", "=" * 40]
164
+ lines.append(f"Strategy A: {self.strategy_a_name}")
165
+ lines.append(f"Strategy B: {self.strategy_b_name}")
166
+ lines.append("")
167
+ lines.append(
168
+ f"Prior: Sharpe ~ N({self.prior_sharpe_mean:.3f}, {self.prior_sharpe_std:.3f})"
169
+ )
170
+ lines.append(
171
+ f"Posterior: Sharpe ~ N({self.posterior_sharpe_mean:.3f}, {self.posterior_sharpe_std:.3f})"
172
+ )
173
+ lines.append("")
174
+ lines.append(
175
+ f"P({self.strategy_a_name} > {self.strategy_b_name}): {self.probability_a_better:.1%}"
176
+ )
177
+ lines.append(
178
+ f"95% Credible Interval: [{self.credible_interval_95[0]:.3f}, {self.credible_interval_95[1]:.3f}]"
179
+ )
180
+
181
+ # Interpretation
182
+ if self.probability_a_better > 0.95:
183
+ lines.append(f"\nConclusion: Strong evidence for {self.strategy_a_name}")
184
+ elif self.probability_a_better > 0.80:
185
+ lines.append(f"\nConclusion: Moderate evidence for {self.strategy_a_name}")
186
+ elif self.probability_a_better < 0.05:
187
+ lines.append(f"\nConclusion: Strong evidence for {self.strategy_b_name}")
188
+ elif self.probability_a_better < 0.20:
189
+ lines.append(f"\nConclusion: Moderate evidence for {self.strategy_b_name}")
190
+ else:
191
+ lines.append("\nConclusion: No clear winner (inconclusive)")
192
+
193
+ return "\n".join(lines)
194
+
195
+
196
+ class PortfolioEvaluationResult(BaseResult):
197
+ """Complete results from Module D: Portfolio Evaluation.
198
+
199
+ Comprehensive portfolio-level analysis including:
200
+ - Standard performance metrics
201
+ - Bayesian strategy comparison (if applicable)
202
+ - Time-series metrics at different frequencies
203
+ - Detailed drawdown analysis
204
+
205
+ Attributes:
206
+ metrics: Standard portfolio metrics
207
+ bayesian_comparison: Bayesian comparison results (if comparing strategies)
208
+ time_series_metrics: Metrics aggregated by time period
209
+ drawdown_analysis: Detailed drawdown statistics
210
+ """
211
+
212
+ analysis_type: str = "portfolio_evaluation"
213
+
214
+ metrics: PortfolioMetrics = Field(..., description="Standard portfolio metrics")
215
+
216
+ bayesian_comparison: BayesianComparisonResult | None = Field(
217
+ None, description="Bayesian strategy comparison"
218
+ )
219
+
220
+ time_series_metrics: dict[str, Any] = Field(
221
+ default_factory=dict,
222
+ description="Metrics by period (daily, weekly, monthly)",
223
+ )
224
+
225
+ drawdown_analysis: dict[str, Any] = Field(
226
+ default_factory=dict,
227
+ description="Detailed drawdown statistics",
228
+ )
229
+
230
+ def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
231
+ """Get portfolio metrics as DataFrame.
232
+
233
+ Args:
234
+ name: 'metrics', 'comparison', or None (default: metrics)
235
+
236
+ Returns:
237
+ Requested DataFrame
238
+ """
239
+ if name == "comparison" and self.bayesian_comparison:
240
+ return self.bayesian_comparison.get_dataframe()
241
+ else:
242
+ return self.metrics.get_dataframe()
243
+
244
+ def get_time_series_dataframe(self, frequency: str = "daily") -> pl.DataFrame:
245
+ """Get time-series metrics as DataFrame.
246
+
247
+ Args:
248
+ frequency: 'daily', 'weekly', or 'monthly'
249
+
250
+ Returns:
251
+ DataFrame with time-series metrics
252
+ """
253
+ if frequency not in self.time_series_metrics:
254
+ return pl.DataFrame()
255
+
256
+ data = self.time_series_metrics[frequency]
257
+ return pl.DataFrame(data)
258
+
259
+ def summary(self) -> str:
260
+ """Human-readable summary of portfolio evaluation."""
261
+ lines = ["Portfolio Evaluation Summary", "=" * 40]
262
+ lines.append("")
263
+ lines.append(self.metrics.summary())
264
+
265
+ if self.bayesian_comparison:
266
+ lines.append("")
267
+ lines.append(self.bayesian_comparison.summary())
268
+
269
+ if self.drawdown_analysis:
270
+ lines.append("")
271
+ lines.append("Drawdown Analysis")
272
+ lines.append("-" * 40)
273
+ dd = self.drawdown_analysis
274
+ if "max_duration_days" in dd:
275
+ lines.append(f"Max drawdown duration: {dd['max_duration_days']} days")
276
+ if "avg_drawdown" in dd:
277
+ lines.append(f"Average drawdown: {dd['avg_drawdown']:.2%}")
278
+ if "num_drawdowns" in dd:
279
+ lines.append(f"Number of drawdowns: {dd['num_drawdowns']}")
280
+
281
+ return "\n".join(lines)