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