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,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)
|