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.
- ml4t/diagnostic/AGENT.md +25 -0
- ml4t/diagnostic/__init__.py +166 -0
- ml4t/diagnostic/backends/__init__.py +10 -0
- ml4t/diagnostic/backends/adapter.py +192 -0
- ml4t/diagnostic/backends/polars_backend.py +899 -0
- ml4t/diagnostic/caching/__init__.py +40 -0
- ml4t/diagnostic/caching/cache.py +331 -0
- ml4t/diagnostic/caching/decorators.py +131 -0
- ml4t/diagnostic/caching/smart_cache.py +339 -0
- ml4t/diagnostic/config/AGENT.md +24 -0
- ml4t/diagnostic/config/README.md +267 -0
- ml4t/diagnostic/config/__init__.py +219 -0
- ml4t/diagnostic/config/barrier_config.py +277 -0
- ml4t/diagnostic/config/base.py +301 -0
- ml4t/diagnostic/config/event_config.py +148 -0
- ml4t/diagnostic/config/feature_config.py +404 -0
- ml4t/diagnostic/config/multi_signal_config.py +55 -0
- ml4t/diagnostic/config/portfolio_config.py +215 -0
- ml4t/diagnostic/config/report_config.py +391 -0
- ml4t/diagnostic/config/sharpe_config.py +202 -0
- ml4t/diagnostic/config/signal_config.py +206 -0
- ml4t/diagnostic/config/trade_analysis_config.py +310 -0
- ml4t/diagnostic/config/validation.py +279 -0
- ml4t/diagnostic/core/__init__.py +29 -0
- ml4t/diagnostic/core/numba_utils.py +315 -0
- ml4t/diagnostic/core/purging.py +372 -0
- ml4t/diagnostic/core/sampling.py +471 -0
- ml4t/diagnostic/errors/__init__.py +205 -0
- ml4t/diagnostic/evaluation/AGENT.md +26 -0
- ml4t/diagnostic/evaluation/__init__.py +437 -0
- ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
- ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
- ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
- ml4t/diagnostic/evaluation/dashboard.py +715 -0
- ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
- ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
- ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
- ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
- ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
- ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
- ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
- ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
- ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
- ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
- ml4t/diagnostic/evaluation/event_analysis.py +647 -0
- ml4t/diagnostic/evaluation/excursion.py +390 -0
- ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
- ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
- ml4t/diagnostic/evaluation/framework.py +935 -0
- ml4t/diagnostic/evaluation/metric_registry.py +255 -0
- ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
- ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
- ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
- ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
- ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
- ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
- ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
- ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
- ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
- ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
- ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
- ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
- ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
- ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
- ml4t/diagnostic/evaluation/multi_signal.py +550 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
- ml4t/diagnostic/evaluation/report_generation.py +824 -0
- ml4t/diagnostic/evaluation/signal_selector.py +452 -0
- ml4t/diagnostic/evaluation/stat_registry.py +139 -0
- ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
- ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
- ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
- ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
- ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
- ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
- ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
- ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
- ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
- ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
- ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
- ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
- ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
- ml4t/diagnostic/evaluation/stats/moments.py +164 -0
- ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
- ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
- ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
- ml4t/diagnostic/evaluation/themes.py +330 -0
- ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
- ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
- ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
- ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
- ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
- ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
- ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
- ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
- ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
- ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
- ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
- ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
- ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
- ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
- ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
- ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
- ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
- ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
- ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
- ml4t/diagnostic/evaluation/validated_cv.py +535 -0
- ml4t/diagnostic/evaluation/visualization.py +1050 -0
- ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
- ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
- ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
- ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
- ml4t/diagnostic/integration/__init__.py +48 -0
- ml4t/diagnostic/integration/backtest_contract.py +671 -0
- ml4t/diagnostic/integration/data_contract.py +316 -0
- ml4t/diagnostic/integration/engineer_contract.py +226 -0
- ml4t/diagnostic/logging/__init__.py +77 -0
- ml4t/diagnostic/logging/logger.py +245 -0
- ml4t/diagnostic/logging/performance.py +234 -0
- ml4t/diagnostic/logging/progress.py +234 -0
- ml4t/diagnostic/logging/wandb.py +412 -0
- ml4t/diagnostic/metrics/__init__.py +9 -0
- ml4t/diagnostic/metrics/percentiles.py +128 -0
- ml4t/diagnostic/py.typed +1 -0
- ml4t/diagnostic/reporting/__init__.py +43 -0
- ml4t/diagnostic/reporting/base.py +130 -0
- ml4t/diagnostic/reporting/html_renderer.py +275 -0
- ml4t/diagnostic/reporting/json_renderer.py +51 -0
- ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
- ml4t/diagnostic/results/AGENT.md +24 -0
- ml4t/diagnostic/results/__init__.py +105 -0
- ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
- ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
- ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
- ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
- ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
- ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
- ml4t/diagnostic/results/barrier_results/validation.py +38 -0
- ml4t/diagnostic/results/base.py +177 -0
- ml4t/diagnostic/results/event_results.py +349 -0
- ml4t/diagnostic/results/feature_results.py +787 -0
- ml4t/diagnostic/results/multi_signal_results.py +431 -0
- ml4t/diagnostic/results/portfolio_results.py +281 -0
- ml4t/diagnostic/results/sharpe_results.py +448 -0
- ml4t/diagnostic/results/signal_results/__init__.py +74 -0
- ml4t/diagnostic/results/signal_results/ic.py +581 -0
- ml4t/diagnostic/results/signal_results/irtc.py +110 -0
- ml4t/diagnostic/results/signal_results/quantile.py +392 -0
- ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
- ml4t/diagnostic/results/signal_results/turnover.py +213 -0
- ml4t/diagnostic/results/signal_results/validation.py +147 -0
- ml4t/diagnostic/signal/AGENT.md +17 -0
- ml4t/diagnostic/signal/__init__.py +69 -0
- ml4t/diagnostic/signal/_report.py +152 -0
- ml4t/diagnostic/signal/_utils.py +261 -0
- ml4t/diagnostic/signal/core.py +275 -0
- ml4t/diagnostic/signal/quantile.py +148 -0
- ml4t/diagnostic/signal/result.py +214 -0
- ml4t/diagnostic/signal/signal_ic.py +129 -0
- ml4t/diagnostic/signal/turnover.py +182 -0
- ml4t/diagnostic/splitters/AGENT.md +19 -0
- ml4t/diagnostic/splitters/__init__.py +36 -0
- ml4t/diagnostic/splitters/base.py +501 -0
- ml4t/diagnostic/splitters/calendar.py +421 -0
- ml4t/diagnostic/splitters/calendar_config.py +91 -0
- ml4t/diagnostic/splitters/combinatorial.py +1064 -0
- ml4t/diagnostic/splitters/config.py +322 -0
- ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
- ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
- ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
- ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
- ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
- ml4t/diagnostic/splitters/group_isolation.py +329 -0
- ml4t/diagnostic/splitters/persistence.py +316 -0
- ml4t/diagnostic/splitters/utils.py +207 -0
- ml4t/diagnostic/splitters/walk_forward.py +757 -0
- ml4t/diagnostic/utils/__init__.py +42 -0
- ml4t/diagnostic/utils/config.py +542 -0
- ml4t/diagnostic/utils/dependencies.py +318 -0
- ml4t/diagnostic/utils/sessions.py +127 -0
- ml4t/diagnostic/validation/__init__.py +54 -0
- ml4t/diagnostic/validation/dataframe.py +274 -0
- ml4t/diagnostic/validation/returns.py +280 -0
- ml4t/diagnostic/validation/timeseries.py +299 -0
- ml4t/diagnostic/visualization/AGENT.md +19 -0
- ml4t/diagnostic/visualization/__init__.py +223 -0
- ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
- ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
- ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
- ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
- ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
- ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
- ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
- ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
- ml4t/diagnostic/visualization/barrier_plots.py +782 -0
- ml4t/diagnostic/visualization/core.py +1060 -0
- ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
- ml4t/diagnostic/visualization/dashboards/base.py +582 -0
- ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
- ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
- ml4t/diagnostic/visualization/dashboards.py +43 -0
- ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
- ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
- ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
- ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
- ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
- ml4t/diagnostic/visualization/feature_plots.py +888 -0
- ml4t/diagnostic/visualization/interaction_plots.py +618 -0
- ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
- ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
- ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
- ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
- ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
- ml4t/diagnostic/visualization/report_generation.py +1343 -0
- ml4t/diagnostic/visualization/signal/__init__.py +103 -0
- ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
- ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
- ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
- ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
- ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
- ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
- ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
- ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
- ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
- ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
- ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
- 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
|
+
]
|