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,514 @@
|
|
|
1
|
+
"""Portfolio dashboard combining all visualizations.
|
|
2
|
+
|
|
3
|
+
Creates comprehensive tear sheets with multiple panels.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
import plotly.graph_objects as go
|
|
13
|
+
from plotly.subplots import make_subplots
|
|
14
|
+
|
|
15
|
+
from ml4t.diagnostic.visualization.core import (
|
|
16
|
+
get_theme_config,
|
|
17
|
+
validate_theme,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from ml4t.diagnostic.evaluation.portfolio_analysis import (
|
|
22
|
+
PortfolioAnalysis,
|
|
23
|
+
PortfolioMetrics,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PortfolioTearSheet:
|
|
29
|
+
"""Container for portfolio tear sheet with all figures and data.
|
|
30
|
+
|
|
31
|
+
Provides methods to display in notebooks, export to HTML,
|
|
32
|
+
and access individual figures.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
metrics: Portfolio performance metrics
|
|
36
|
+
figures: Dictionary of individual Plotly figures
|
|
37
|
+
html_content: Pre-rendered HTML content (if generated)
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
metrics: PortfolioMetrics
|
|
41
|
+
figures: dict[str, go.Figure] = field(default_factory=dict)
|
|
42
|
+
html_content: str | None = None
|
|
43
|
+
_analysis: PortfolioAnalysis | None = None
|
|
44
|
+
|
|
45
|
+
def show(self) -> None:
|
|
46
|
+
"""Display tear sheet in Jupyter notebook."""
|
|
47
|
+
# Display metrics first
|
|
48
|
+
print(self.metrics.summary())
|
|
49
|
+
print()
|
|
50
|
+
|
|
51
|
+
# Display each figure
|
|
52
|
+
for name, fig in self.figures.items():
|
|
53
|
+
print(f"\n{'=' * 50}")
|
|
54
|
+
print(f" {name}")
|
|
55
|
+
print("=" * 50)
|
|
56
|
+
fig.show()
|
|
57
|
+
|
|
58
|
+
def save_html(
|
|
59
|
+
self,
|
|
60
|
+
path: str | Path,
|
|
61
|
+
include_plotlyjs: bool | str = "cdn",
|
|
62
|
+
full_html: bool = True,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Save tear sheet as self-contained HTML file.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
path : str or Path
|
|
69
|
+
Output file path
|
|
70
|
+
include_plotlyjs : bool or str, default "cdn"
|
|
71
|
+
How to include Plotly.js:
|
|
72
|
+
- True: Embed full library (~3MB)
|
|
73
|
+
- "cdn": Link to CDN (smaller file, requires internet)
|
|
74
|
+
- False: Don't include (for embedding in larger page)
|
|
75
|
+
full_html : bool, default True
|
|
76
|
+
Include full HTML structure (<!DOCTYPE>, <head>, etc.)
|
|
77
|
+
"""
|
|
78
|
+
path = Path(path)
|
|
79
|
+
|
|
80
|
+
# Build HTML content
|
|
81
|
+
html_parts = []
|
|
82
|
+
|
|
83
|
+
if full_html:
|
|
84
|
+
html_parts.append("""<!DOCTYPE html>
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<meta charset="utf-8">
|
|
88
|
+
<title>Portfolio Tear Sheet</title>
|
|
89
|
+
<style>
|
|
90
|
+
body {
|
|
91
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
92
|
+
max-width: 1400px;
|
|
93
|
+
margin: 0 auto;
|
|
94
|
+
padding: 20px;
|
|
95
|
+
background: #f5f5f5;
|
|
96
|
+
}
|
|
97
|
+
.metrics-summary {
|
|
98
|
+
background: white;
|
|
99
|
+
border-radius: 8px;
|
|
100
|
+
padding: 20px;
|
|
101
|
+
margin-bottom: 20px;
|
|
102
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
103
|
+
}
|
|
104
|
+
.metrics-summary pre {
|
|
105
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
line-height: 1.5;
|
|
108
|
+
}
|
|
109
|
+
.plot-container {
|
|
110
|
+
background: white;
|
|
111
|
+
border-radius: 8px;
|
|
112
|
+
padding: 15px;
|
|
113
|
+
margin-bottom: 20px;
|
|
114
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
115
|
+
}
|
|
116
|
+
h1 {
|
|
117
|
+
color: #2c3e50;
|
|
118
|
+
border-bottom: 2px solid #3498db;
|
|
119
|
+
padding-bottom: 10px;
|
|
120
|
+
}
|
|
121
|
+
h2 {
|
|
122
|
+
color: #34495e;
|
|
123
|
+
margin-top: 0;
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
126
|
+
</head>
|
|
127
|
+
<body>
|
|
128
|
+
<h1>Portfolio Tear Sheet</h1>
|
|
129
|
+
""")
|
|
130
|
+
|
|
131
|
+
# Add metrics summary
|
|
132
|
+
html_parts.append(f"""
|
|
133
|
+
<div class="metrics-summary">
|
|
134
|
+
<h2>Performance Summary</h2>
|
|
135
|
+
<pre>{self.metrics.summary()}</pre>
|
|
136
|
+
</div>
|
|
137
|
+
""")
|
|
138
|
+
|
|
139
|
+
# Add each figure
|
|
140
|
+
for name, fig in self.figures.items():
|
|
141
|
+
fig_html = fig.to_html(
|
|
142
|
+
include_plotlyjs=include_plotlyjs
|
|
143
|
+
if name == list(self.figures.keys())[0]
|
|
144
|
+
else False,
|
|
145
|
+
full_html=False,
|
|
146
|
+
)
|
|
147
|
+
html_parts.append(f"""
|
|
148
|
+
<div class="plot-container">
|
|
149
|
+
<h2>{name}</h2>
|
|
150
|
+
{fig_html}
|
|
151
|
+
</div>
|
|
152
|
+
""")
|
|
153
|
+
|
|
154
|
+
if full_html:
|
|
155
|
+
html_parts.append("""
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
""")
|
|
159
|
+
|
|
160
|
+
# Write to file
|
|
161
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
162
|
+
f.write("".join(html_parts))
|
|
163
|
+
|
|
164
|
+
def get_figures(self) -> dict[str, go.Figure]:
|
|
165
|
+
"""Get dictionary of all figures.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
dict[str, go.Figure]
|
|
170
|
+
Named Plotly figures
|
|
171
|
+
"""
|
|
172
|
+
return self.figures.copy()
|
|
173
|
+
|
|
174
|
+
def to_dict(self) -> dict[str, Any]:
|
|
175
|
+
"""Get all underlying data as dictionary.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
dict
|
|
180
|
+
Contains metrics and figure data
|
|
181
|
+
"""
|
|
182
|
+
return {
|
|
183
|
+
"metrics": self.metrics.to_dict(),
|
|
184
|
+
"figures": {name: fig.to_dict() for name, fig in self.figures.items()},
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def create_portfolio_dashboard(
|
|
189
|
+
analysis: PortfolioAnalysis,
|
|
190
|
+
theme: str | None = None,
|
|
191
|
+
include_positions: bool = True,
|
|
192
|
+
include_transactions: bool = True,
|
|
193
|
+
include_benchmark: bool = True,
|
|
194
|
+
height_per_row: int = 300,
|
|
195
|
+
width: int | None = None,
|
|
196
|
+
) -> PortfolioTearSheet:
|
|
197
|
+
"""Create comprehensive portfolio tear sheet.
|
|
198
|
+
|
|
199
|
+
Generates a complete portfolio analysis dashboard with:
|
|
200
|
+
- Performance metrics summary
|
|
201
|
+
- Cumulative returns chart
|
|
202
|
+
- Drawdown underwater curve
|
|
203
|
+
- Rolling Sharpe ratio
|
|
204
|
+
- Rolling volatility
|
|
205
|
+
- Monthly returns heatmap
|
|
206
|
+
- Returns distribution
|
|
207
|
+
|
|
208
|
+
Parameters
|
|
209
|
+
----------
|
|
210
|
+
analysis : PortfolioAnalysis
|
|
211
|
+
Portfolio analysis object with returns data
|
|
212
|
+
theme : str, optional
|
|
213
|
+
Plot theme ("default", "dark", "print", "presentation")
|
|
214
|
+
include_positions : bool, default True
|
|
215
|
+
Include position analysis (if positions data available)
|
|
216
|
+
include_transactions : bool, default True
|
|
217
|
+
Include transaction analysis (if transactions data available)
|
|
218
|
+
include_benchmark : bool, default True
|
|
219
|
+
Include benchmark comparison (if benchmark available)
|
|
220
|
+
height_per_row : int, default 300
|
|
221
|
+
Height per subplot row
|
|
222
|
+
width : int, optional
|
|
223
|
+
Figure width
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
PortfolioTearSheet
|
|
228
|
+
Complete tear sheet with all figures and metrics
|
|
229
|
+
|
|
230
|
+
Examples
|
|
231
|
+
--------
|
|
232
|
+
>>> analysis = PortfolioAnalysis(returns=daily_returns)
|
|
233
|
+
>>> tear_sheet = create_portfolio_dashboard(analysis)
|
|
234
|
+
>>> tear_sheet.show() # Display in notebook
|
|
235
|
+
>>> tear_sheet.save_html("report.html") # Export to file
|
|
236
|
+
"""
|
|
237
|
+
theme = validate_theme(theme)
|
|
238
|
+
get_theme_config(theme)
|
|
239
|
+
|
|
240
|
+
# Compute all metrics
|
|
241
|
+
metrics = analysis.compute_summary_stats()
|
|
242
|
+
|
|
243
|
+
# Import visualization functions
|
|
244
|
+
from .drawdown_plots import (
|
|
245
|
+
plot_drawdown_periods,
|
|
246
|
+
plot_drawdown_underwater,
|
|
247
|
+
)
|
|
248
|
+
from .returns_plots import (
|
|
249
|
+
plot_annual_returns_bar,
|
|
250
|
+
plot_cumulative_returns,
|
|
251
|
+
plot_monthly_returns_heatmap,
|
|
252
|
+
plot_returns_distribution,
|
|
253
|
+
)
|
|
254
|
+
from .risk_plots import (
|
|
255
|
+
plot_rolling_beta,
|
|
256
|
+
plot_rolling_sharpe,
|
|
257
|
+
plot_rolling_volatility,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
figures = {}
|
|
261
|
+
|
|
262
|
+
# 1. Cumulative returns
|
|
263
|
+
figures["Cumulative Returns"] = plot_cumulative_returns(
|
|
264
|
+
analysis,
|
|
265
|
+
theme=theme,
|
|
266
|
+
show_benchmark=include_benchmark and analysis.has_benchmark,
|
|
267
|
+
height=height_per_row,
|
|
268
|
+
width=width,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# 2. Drawdown underwater
|
|
272
|
+
figures["Drawdown"] = plot_drawdown_underwater(
|
|
273
|
+
analysis,
|
|
274
|
+
theme=theme,
|
|
275
|
+
height=int(height_per_row * 0.8),
|
|
276
|
+
width=width,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# 3. Rolling metrics
|
|
280
|
+
rolling_result = analysis.compute_rolling_metrics(
|
|
281
|
+
windows=[21, 63, 252],
|
|
282
|
+
metrics=["sharpe", "volatility", "returns"],
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
figures["Rolling Sharpe Ratio"] = plot_rolling_sharpe(
|
|
286
|
+
rolling_result=rolling_result,
|
|
287
|
+
windows=[63, 252],
|
|
288
|
+
theme=theme,
|
|
289
|
+
height=height_per_row,
|
|
290
|
+
width=width,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
figures["Rolling Volatility"] = plot_rolling_volatility(
|
|
294
|
+
rolling_result=rolling_result,
|
|
295
|
+
windows=[21, 63, 252],
|
|
296
|
+
theme=theme,
|
|
297
|
+
height=height_per_row,
|
|
298
|
+
width=width,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# 4. Rolling beta (if benchmark available)
|
|
302
|
+
if include_benchmark and analysis.has_benchmark:
|
|
303
|
+
beta_rolling = analysis.compute_rolling_metrics(
|
|
304
|
+
windows=[126],
|
|
305
|
+
metrics=["beta"],
|
|
306
|
+
)
|
|
307
|
+
figures["Rolling Beta"] = plot_rolling_beta(
|
|
308
|
+
rolling_result=beta_rolling,
|
|
309
|
+
window=126,
|
|
310
|
+
theme=theme,
|
|
311
|
+
height=height_per_row,
|
|
312
|
+
width=width,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# 5. Annual returns
|
|
316
|
+
figures["Annual Returns"] = plot_annual_returns_bar(
|
|
317
|
+
analysis,
|
|
318
|
+
theme=theme,
|
|
319
|
+
show_benchmark=include_benchmark and analysis.has_benchmark,
|
|
320
|
+
height=height_per_row,
|
|
321
|
+
width=width,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# 6. Monthly returns heatmap
|
|
325
|
+
figures["Monthly Returns Heatmap"] = plot_monthly_returns_heatmap(
|
|
326
|
+
analysis,
|
|
327
|
+
theme=theme,
|
|
328
|
+
height=int(height_per_row * 1.2),
|
|
329
|
+
width=width,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# 7. Returns distribution
|
|
333
|
+
figures["Returns Distribution"] = plot_returns_distribution(
|
|
334
|
+
analysis,
|
|
335
|
+
theme=theme,
|
|
336
|
+
height=height_per_row,
|
|
337
|
+
width=width,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# 8. Top drawdowns
|
|
341
|
+
figures["Top Drawdowns"] = plot_drawdown_periods(
|
|
342
|
+
analysis,
|
|
343
|
+
top_n=5,
|
|
344
|
+
theme=theme,
|
|
345
|
+
height=height_per_row,
|
|
346
|
+
width=width,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return PortfolioTearSheet(
|
|
350
|
+
metrics=metrics,
|
|
351
|
+
figures=figures,
|
|
352
|
+
_analysis=analysis,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def create_simple_dashboard(
|
|
357
|
+
analysis: PortfolioAnalysis,
|
|
358
|
+
theme: str | None = None,
|
|
359
|
+
height: int = 800,
|
|
360
|
+
width: int | None = None,
|
|
361
|
+
) -> go.Figure:
|
|
362
|
+
"""Create simple 4-panel dashboard as single figure.
|
|
363
|
+
|
|
364
|
+
A quick overview with:
|
|
365
|
+
- Cumulative returns (top left)
|
|
366
|
+
- Drawdown (top right)
|
|
367
|
+
- Rolling Sharpe (bottom left)
|
|
368
|
+
- Monthly heatmap (bottom right)
|
|
369
|
+
|
|
370
|
+
Parameters
|
|
371
|
+
----------
|
|
372
|
+
analysis : PortfolioAnalysis
|
|
373
|
+
Portfolio analysis object
|
|
374
|
+
theme : str, optional
|
|
375
|
+
Plot theme
|
|
376
|
+
height : int, default 800
|
|
377
|
+
Total figure height
|
|
378
|
+
width : int, optional
|
|
379
|
+
Figure width
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
go.Figure
|
|
384
|
+
Single combined figure
|
|
385
|
+
"""
|
|
386
|
+
theme = validate_theme(theme)
|
|
387
|
+
theme_config = get_theme_config(theme)
|
|
388
|
+
|
|
389
|
+
import numpy as np
|
|
390
|
+
|
|
391
|
+
fig = make_subplots(
|
|
392
|
+
rows=2,
|
|
393
|
+
cols=2,
|
|
394
|
+
subplot_titles=[
|
|
395
|
+
"Cumulative Returns",
|
|
396
|
+
"Drawdown",
|
|
397
|
+
"Rolling Sharpe (252d)",
|
|
398
|
+
"Monthly Returns",
|
|
399
|
+
],
|
|
400
|
+
vertical_spacing=0.12,
|
|
401
|
+
horizontal_spacing=0.08,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
dates = analysis.dates.to_list()
|
|
405
|
+
|
|
406
|
+
# === Cumulative Returns (1,1) ===
|
|
407
|
+
cum_returns = (1 + analysis.returns).cumprod()
|
|
408
|
+
fig.add_trace(
|
|
409
|
+
go.Scatter(
|
|
410
|
+
x=dates,
|
|
411
|
+
y=cum_returns,
|
|
412
|
+
mode="lines",
|
|
413
|
+
name="Strategy",
|
|
414
|
+
line={"color": theme_config["colorway"][0], "width": 2},
|
|
415
|
+
showlegend=False,
|
|
416
|
+
),
|
|
417
|
+
row=1,
|
|
418
|
+
col=1,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if analysis.has_benchmark and analysis.benchmark is not None:
|
|
422
|
+
bench_cum = (1 + analysis.benchmark).cumprod()
|
|
423
|
+
fig.add_trace(
|
|
424
|
+
go.Scatter(
|
|
425
|
+
x=dates,
|
|
426
|
+
y=bench_cum,
|
|
427
|
+
mode="lines",
|
|
428
|
+
name="Benchmark",
|
|
429
|
+
line={"color": theme_config["colorway"][1], "width": 2, "dash": "dash"},
|
|
430
|
+
showlegend=False,
|
|
431
|
+
),
|
|
432
|
+
row=1,
|
|
433
|
+
col=1,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# === Drawdown (1,2) ===
|
|
437
|
+
dd_result = analysis.compute_drawdown_analysis()
|
|
438
|
+
underwater = dd_result.underwater_curve.to_numpy()
|
|
439
|
+
fig.add_trace(
|
|
440
|
+
go.Scatter(
|
|
441
|
+
x=dates,
|
|
442
|
+
y=underwater,
|
|
443
|
+
mode="lines",
|
|
444
|
+
name="Drawdown",
|
|
445
|
+
line={"color": theme_config["colorway"][1], "width": 1},
|
|
446
|
+
fill="tozeroy",
|
|
447
|
+
fillcolor="rgba(231, 76, 60, 0.3)",
|
|
448
|
+
showlegend=False,
|
|
449
|
+
),
|
|
450
|
+
row=1,
|
|
451
|
+
col=2,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# === Rolling Sharpe (2,1) ===
|
|
455
|
+
rolling = analysis.compute_rolling_metrics(windows=[252], metrics=["sharpe"])
|
|
456
|
+
sharpe_252 = rolling.sharpe[252].to_numpy()
|
|
457
|
+
fig.add_trace(
|
|
458
|
+
go.Scatter(
|
|
459
|
+
x=dates,
|
|
460
|
+
y=sharpe_252,
|
|
461
|
+
mode="lines",
|
|
462
|
+
name="Sharpe",
|
|
463
|
+
line={"color": theme_config["colorway"][2], "width": 1.5},
|
|
464
|
+
showlegend=False,
|
|
465
|
+
),
|
|
466
|
+
row=2,
|
|
467
|
+
col=1,
|
|
468
|
+
)
|
|
469
|
+
fig.add_hline(y=0, line_dash="solid", line_color="gray", row=2, col=1)
|
|
470
|
+
fig.add_hline(y=1, line_dash="dash", line_color="green", row=2, col=1)
|
|
471
|
+
|
|
472
|
+
# === Monthly Returns Heatmap (2,2) ===
|
|
473
|
+
matrix = analysis.get_monthly_returns_matrix()
|
|
474
|
+
years = matrix["year"].to_list()
|
|
475
|
+
months = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"]
|
|
476
|
+
|
|
477
|
+
z_values = []
|
|
478
|
+
for row in matrix.iter_rows():
|
|
479
|
+
z_values.append([row[i] if row[i] is not None else np.nan for i in range(1, 13)])
|
|
480
|
+
|
|
481
|
+
z_array = np.array(z_values)
|
|
482
|
+
max_abs = np.nanmax(np.abs(z_array)) if not np.all(np.isnan(z_array)) else 0.1
|
|
483
|
+
|
|
484
|
+
fig.add_trace(
|
|
485
|
+
go.Heatmap(
|
|
486
|
+
z=z_array,
|
|
487
|
+
x=months,
|
|
488
|
+
y=years,
|
|
489
|
+
colorscale=[
|
|
490
|
+
[0.0, "#d73027"],
|
|
491
|
+
[0.5, "#ffffff"],
|
|
492
|
+
[1.0, "#1a9850"],
|
|
493
|
+
],
|
|
494
|
+
zmin=-max_abs,
|
|
495
|
+
zmax=max_abs,
|
|
496
|
+
showscale=False,
|
|
497
|
+
),
|
|
498
|
+
row=2,
|
|
499
|
+
col=2,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Update layout
|
|
503
|
+
fig.update_layout(
|
|
504
|
+
title="Portfolio Overview",
|
|
505
|
+
height=height,
|
|
506
|
+
width=width,
|
|
507
|
+
**theme_config["layout"],
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Format axes
|
|
511
|
+
fig.update_yaxes(tickformat=".0%", row=1, col=2) # Drawdown
|
|
512
|
+
fig.update_yaxes(autorange="reversed", row=2, col=2) # Heatmap
|
|
513
|
+
|
|
514
|
+
return fig
|