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,334 @@
1
+ """Result classes for portfolio analysis.
2
+
3
+ This module provides dataclasses for portfolio analysis results:
4
+ - PortfolioMetrics: Complete portfolio performance metrics
5
+ - RollingMetricsResult: Rolling metrics over multiple windows
6
+ - DrawdownPeriod: Individual drawdown period details
7
+ - DrawdownResult: Detailed drawdown analysis
8
+ - DistributionResult: Returns distribution analysis
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ import polars as pl
17
+
18
+
19
+ @dataclass
20
+ class PortfolioMetrics:
21
+ """Complete portfolio performance metrics.
22
+
23
+ Comprehensive set of risk-adjusted return metrics combining
24
+ pyfolio's perf_stats with empyrical's additional metrics.
25
+
26
+ Attributes:
27
+ # Returns
28
+ total_return: Cumulative return over entire period
29
+ annual_return: Annualized return (CAGR)
30
+ annual_volatility: Annualized standard deviation
31
+
32
+ # Risk-adjusted
33
+ sharpe_ratio: Excess return / volatility
34
+ sortino_ratio: Excess return / downside deviation
35
+ calmar_ratio: Annual return / max drawdown
36
+ omega_ratio: P(gain) weighted gain / P(loss) weighted loss
37
+ tail_ratio: 95th percentile / abs(5th percentile)
38
+
39
+ # Drawdown
40
+ max_drawdown: Maximum peak-to-trough decline
41
+
42
+ # Distribution
43
+ skewness: Return distribution skewness
44
+ kurtosis: Return distribution excess kurtosis
45
+
46
+ # Risk
47
+ var_95: 95% Value at Risk (daily)
48
+ cvar_95: 95% Conditional VaR (expected shortfall)
49
+
50
+ # Stability
51
+ stability: R² of cumulative returns vs time
52
+
53
+ # Win/loss
54
+ win_rate: Fraction of positive return periods
55
+ profit_factor: Gross profit / gross loss
56
+
57
+ # Benchmark-relative (if benchmark provided)
58
+ alpha: Jensen's alpha (annualized)
59
+ beta: Market beta (CAPM)
60
+ information_ratio: Alpha / tracking error
61
+ up_capture: Performance in up markets vs benchmark
62
+ down_capture: Performance in down markets vs benchmark
63
+ """
64
+
65
+ # Returns
66
+ total_return: float
67
+ annual_return: float
68
+ annual_volatility: float
69
+
70
+ # Risk-adjusted
71
+ sharpe_ratio: float
72
+ sortino_ratio: float
73
+ calmar_ratio: float
74
+ omega_ratio: float
75
+ tail_ratio: float
76
+
77
+ # Drawdown
78
+ max_drawdown: float
79
+
80
+ # Distribution
81
+ skewness: float
82
+ kurtosis: float
83
+
84
+ # Risk
85
+ var_95: float
86
+ cvar_95: float
87
+
88
+ # Stability
89
+ stability: float
90
+
91
+ # Win/loss
92
+ win_rate: float
93
+ profit_factor: float
94
+ avg_win: float
95
+ avg_loss: float
96
+
97
+ # Benchmark-relative (optional)
98
+ alpha: float | None = None
99
+ beta: float | None = None
100
+ information_ratio: float | None = None
101
+ up_capture: float | None = None
102
+ down_capture: float | None = None
103
+
104
+ def to_dict(self) -> dict[str, float | None]:
105
+ """Convert to dictionary."""
106
+ return {
107
+ "total_return": self.total_return,
108
+ "annual_return": self.annual_return,
109
+ "annual_volatility": self.annual_volatility,
110
+ "sharpe_ratio": self.sharpe_ratio,
111
+ "sortino_ratio": self.sortino_ratio,
112
+ "calmar_ratio": self.calmar_ratio,
113
+ "omega_ratio": self.omega_ratio,
114
+ "tail_ratio": self.tail_ratio,
115
+ "max_drawdown": self.max_drawdown,
116
+ "skewness": self.skewness,
117
+ "kurtosis": self.kurtosis,
118
+ "var_95": self.var_95,
119
+ "cvar_95": self.cvar_95,
120
+ "stability": self.stability,
121
+ "win_rate": self.win_rate,
122
+ "profit_factor": self.profit_factor,
123
+ "avg_win": self.avg_win,
124
+ "avg_loss": self.avg_loss,
125
+ "alpha": self.alpha,
126
+ "beta": self.beta,
127
+ "information_ratio": self.information_ratio,
128
+ "up_capture": self.up_capture,
129
+ "down_capture": self.down_capture,
130
+ }
131
+
132
+ def to_dataframe(self) -> pl.DataFrame:
133
+ """Convert to single-row DataFrame."""
134
+ data = self.to_dict()
135
+ # Filter out None values for cleaner output
136
+ return pl.DataFrame({k: [v] for k, v in data.items() if v is not None})
137
+
138
+ def summary(self) -> str:
139
+ """Human-readable summary."""
140
+ lines = [
141
+ "=" * 50,
142
+ "Portfolio Performance Summary",
143
+ "=" * 50,
144
+ "",
145
+ "Returns",
146
+ "-" * 30,
147
+ f" Total Return: {self.total_return:>10.2%}",
148
+ f" Annual Return: {self.annual_return:>10.2%}",
149
+ f" Annual Volatility: {self.annual_volatility:>10.2%}",
150
+ "",
151
+ "Risk-Adjusted Returns",
152
+ "-" * 30,
153
+ f" Sharpe Ratio: {self.sharpe_ratio:>10.3f}",
154
+ f" Sortino Ratio: {self.sortino_ratio:>10.3f}",
155
+ f" Calmar Ratio: {self.calmar_ratio:>10.3f}",
156
+ f" Omega Ratio: {self.omega_ratio:>10.3f}",
157
+ f" Tail Ratio: {self.tail_ratio:>10.3f}",
158
+ "",
159
+ "Risk Metrics",
160
+ "-" * 30,
161
+ f" Max Drawdown: {self.max_drawdown:>10.2%}",
162
+ f" VaR (95%): {self.var_95:>10.2%}",
163
+ f" CVaR (95%): {self.cvar_95:>10.2%}",
164
+ "",
165
+ "Distribution",
166
+ "-" * 30,
167
+ f" Skewness: {self.skewness:>10.3f}",
168
+ f" Kurtosis: {self.kurtosis:>10.3f}",
169
+ f" Stability (R²): {self.stability:>10.3f}",
170
+ "",
171
+ "Win/Loss",
172
+ "-" * 30,
173
+ f" Win Rate: {self.win_rate:>10.2%}",
174
+ f" Profit Factor: {self.profit_factor:>10.2f}",
175
+ f" Avg Win: {self.avg_win:>10.2%}",
176
+ f" Avg Loss: {self.avg_loss:>10.2%}",
177
+ ]
178
+
179
+ if self.alpha is not None:
180
+ lines.extend(
181
+ [
182
+ "",
183
+ "Benchmark Comparison",
184
+ "-" * 30,
185
+ f" Alpha (annual): {self.alpha:>10.2%}",
186
+ f" Beta: {self.beta:>10.3f}",
187
+ f" Information Ratio: {self.information_ratio:>10.3f}",
188
+ f" Up Capture: {self.up_capture:>10.2%}",
189
+ f" Down Capture: {self.down_capture:>10.2%}",
190
+ ]
191
+ )
192
+
193
+ lines.append("=" * 50)
194
+ return "\n".join(lines)
195
+
196
+
197
+ @dataclass
198
+ class RollingMetricsResult:
199
+ """Rolling metrics over multiple windows.
200
+
201
+ Attributes:
202
+ windows: List of window sizes (in periods)
203
+ sharpe: Rolling Sharpe ratio by window
204
+ volatility: Rolling volatility by window
205
+ returns: Rolling returns by window
206
+ beta: Rolling beta by window (if benchmark provided)
207
+ dates: Date index for time series
208
+ """
209
+
210
+ windows: list[int]
211
+ dates: pl.Series
212
+ sharpe: dict[int, pl.Series] = field(default_factory=dict)
213
+ volatility: dict[int, pl.Series] = field(default_factory=dict)
214
+ returns: dict[int, pl.Series] = field(default_factory=dict)
215
+ beta: dict[int, pl.Series] = field(default_factory=dict)
216
+
217
+ def to_dataframe(self, metric: str = "sharpe") -> pl.DataFrame:
218
+ """Convert specific metric to DataFrame with all windows."""
219
+ metric_data = getattr(self, metric, {})
220
+ if not metric_data:
221
+ return pl.DataFrame()
222
+
223
+ data = {"date": self.dates}
224
+ for window, series in metric_data.items():
225
+ data[f"{metric}_{window}d"] = series
226
+
227
+ return pl.DataFrame(data)
228
+
229
+
230
+ @dataclass
231
+ class DrawdownPeriod:
232
+ """Individual drawdown period details."""
233
+
234
+ peak_date: Any # datetime
235
+ valley_date: Any # datetime
236
+ recovery_date: Any | None # datetime or None if not recovered
237
+ depth: float # Maximum depth (negative)
238
+ duration_days: int # Peak to valley
239
+ recovery_days: int | None # Valley to recovery
240
+
241
+
242
+ @dataclass
243
+ class DrawdownResult:
244
+ """Detailed drawdown analysis.
245
+
246
+ Attributes:
247
+ current_drawdown: Current drawdown level
248
+ max_drawdown: Maximum historical drawdown
249
+ avg_drawdown: Average of all drawdowns
250
+ underwater_curve: Drawdown at each point in time
251
+ top_drawdowns: List of top N drawdown periods
252
+ max_duration_days: Longest drawdown duration
253
+ avg_duration_days: Average drawdown duration
254
+ num_drawdowns: Total count of drawdown periods
255
+ """
256
+
257
+ current_drawdown: float
258
+ max_drawdown: float
259
+ avg_drawdown: float
260
+ underwater_curve: pl.Series
261
+ top_drawdowns: list[DrawdownPeriod]
262
+ max_duration_days: int
263
+ avg_duration_days: float
264
+ num_drawdowns: int
265
+ dates: pl.Series
266
+
267
+ def to_dataframe(self) -> pl.DataFrame:
268
+ """Convert underwater curve to DataFrame."""
269
+ return pl.DataFrame(
270
+ {
271
+ "date": self.dates,
272
+ "drawdown": self.underwater_curve,
273
+ }
274
+ )
275
+
276
+ def top_drawdowns_dataframe(self) -> pl.DataFrame:
277
+ """Convert top drawdowns to DataFrame."""
278
+ if not self.top_drawdowns:
279
+ return pl.DataFrame()
280
+
281
+ return pl.DataFrame(
282
+ {
283
+ "peak_date": [d.peak_date for d in self.top_drawdowns],
284
+ "valley_date": [d.valley_date for d in self.top_drawdowns],
285
+ "recovery_date": [d.recovery_date for d in self.top_drawdowns],
286
+ "depth": [d.depth for d in self.top_drawdowns],
287
+ "duration_days": [d.duration_days for d in self.top_drawdowns],
288
+ "recovery_days": [d.recovery_days for d in self.top_drawdowns],
289
+ }
290
+ )
291
+
292
+
293
+ @dataclass
294
+ class DistributionResult:
295
+ """Returns distribution analysis.
296
+
297
+ Attributes:
298
+ mean: Mean daily return
299
+ std: Standard deviation
300
+ skewness: Skewness
301
+ kurtosis: Excess kurtosis
302
+ jarque_bera_stat: JB test statistic
303
+ jarque_bera_pvalue: JB test p-value
304
+ is_normal: Whether returns are approximately normal (p > 0.05)
305
+ var_95: 95% VaR
306
+ var_99: 99% VaR
307
+ cvar_95: 95% CVaR
308
+ cvar_99: 99% CVaR
309
+ best_day: Best single day return
310
+ worst_day: Worst single day return
311
+ """
312
+
313
+ mean: float
314
+ std: float
315
+ skewness: float
316
+ kurtosis: float
317
+ jarque_bera_stat: float
318
+ jarque_bera_pvalue: float
319
+ is_normal: bool
320
+ var_95: float
321
+ var_99: float
322
+ cvar_95: float
323
+ cvar_99: float
324
+ best_day: float
325
+ worst_day: float
326
+
327
+
328
+ __all__ = [
329
+ "PortfolioMetrics",
330
+ "RollingMetricsResult",
331
+ "DrawdownPeriod",
332
+ "DrawdownResult",
333
+ "DistributionResult",
334
+ ]