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,276 @@
1
+ """HTML report export for dashboard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING
7
+
8
+ import numpy as np
9
+
10
+ if TYPE_CHECKING:
11
+ from ml4t.diagnostic.evaluation.trade_dashboard.types import DashboardBundle
12
+
13
+
14
+ def export_html_report(bundle: DashboardBundle) -> str:
15
+ """Generate comprehensive HTML report from dashboard data.
16
+
17
+ Parameters
18
+ ----------
19
+ bundle : DashboardBundle
20
+ Normalized dashboard data.
21
+
22
+ Returns
23
+ -------
24
+ str
25
+ Complete HTML report as string.
26
+ """
27
+ from ml4t.diagnostic.evaluation.trade_dashboard.stats import (
28
+ compute_return_summary,
29
+ probabilistic_sharpe_ratio,
30
+ )
31
+
32
+ # Compute summary if we have returns
33
+ summary_html = ""
34
+ if bundle.returns is not None and len(bundle.returns) > 0:
35
+ summary = compute_return_summary(bundle.returns)
36
+ psr_result = probabilistic_sharpe_ratio(
37
+ observed_sharpe=summary.sharpe,
38
+ benchmark_sharpe=0.0,
39
+ n_samples=summary.n_samples,
40
+ skewness=summary.skewness,
41
+ kurtosis=summary.kurtosis,
42
+ return_components=True,
43
+ )
44
+ psr = psr_result["psr"]
45
+
46
+ summary_html = f"""
47
+ <div class="section">
48
+ <h2>Statistical Summary</h2>
49
+ <div class="metrics-grid">
50
+ <div class="metric">
51
+ <div class="metric-label">Trades Analyzed</div>
52
+ <div class="metric-value">{bundle.n_trades_analyzed}</div>
53
+ </div>
54
+ <div class="metric">
55
+ <div class="metric-label">Sharpe Ratio</div>
56
+ <div class="metric-value">{summary.sharpe:.3f}</div>
57
+ </div>
58
+ <div class="metric">
59
+ <div class="metric-label">PSR (vs SR=0)</div>
60
+ <div class="metric-value">{psr:.3f}</div>
61
+ </div>
62
+ <div class="metric">
63
+ <div class="metric-label">Win Rate</div>
64
+ <div class="metric-value">{summary.win_rate:.1%}</div>
65
+ </div>
66
+ <div class="metric">
67
+ <div class="metric-label">Mean Return</div>
68
+ <div class="metric-value">{summary.mean:.4f}</div>
69
+ </div>
70
+ <div class="metric">
71
+ <div class="metric-label">Std Dev</div>
72
+ <div class="metric-value">{summary.std:.4f}</div>
73
+ </div>
74
+ </div>
75
+ <p class="caption">Returns based on: {bundle.returns_label}</p>
76
+ </div>
77
+ """
78
+
79
+ # Worst trades table
80
+ trades_html = ""
81
+ if not bundle.trades_df.empty:
82
+ worst_trades = bundle.trades_df.head(10)
83
+ rows = ""
84
+ for _, row in worst_trades.iterrows():
85
+ pnl = row.get("pnl")
86
+ pnl_str = f"${pnl:.2f}" if pnl is not None and not np.isnan(pnl) else "N/A"
87
+ return_pct = row.get("return_pct")
88
+ return_str = (
89
+ f"{return_pct:.2f}%"
90
+ if return_pct is not None and not np.isnan(return_pct)
91
+ else "N/A"
92
+ )
93
+
94
+ rows += f"""
95
+ <tr>
96
+ <td>{row.get("trade_id", "N/A")}</td>
97
+ <td>{row.get("symbol", "N/A")}</td>
98
+ <td>{pnl_str}</td>
99
+ <td>{return_str}</td>
100
+ <td>{row.get("top_feature", "N/A")}</td>
101
+ </tr>
102
+ """
103
+
104
+ trades_html = f"""
105
+ <div class="section">
106
+ <h2>Worst Trades (Top 10)</h2>
107
+ <table>
108
+ <thead>
109
+ <tr>
110
+ <th>Trade ID</th>
111
+ <th>Symbol</th>
112
+ <th>PnL</th>
113
+ <th>Return %</th>
114
+ <th>Top Feature</th>
115
+ </tr>
116
+ </thead>
117
+ <tbody>
118
+ {rows}
119
+ </tbody>
120
+ </table>
121
+ </div>
122
+ """
123
+
124
+ # Patterns section
125
+ patterns_html = ""
126
+ if not bundle.patterns_df.empty:
127
+ pattern_cards = ""
128
+ for _, pattern in bundle.patterns_df.iterrows():
129
+ hypothesis = pattern.get("hypothesis") or "No hypothesis generated"
130
+ actions = pattern.get("actions", [])
131
+ actions_list = ""
132
+ if actions:
133
+ for action in actions:
134
+ actions_list += f"<li>{action}</li>"
135
+ actions_html = f"<ul>{actions_list}</ul>"
136
+ else:
137
+ actions_html = "<p>No actions suggested</p>"
138
+
139
+ pattern_cards += f"""
140
+ <div class="pattern-card">
141
+ <h3>Pattern {pattern.get("cluster_id", "N/A")}: {pattern.get("n_trades", 0)} trades</h3>
142
+ <p><strong>Description:</strong> {pattern.get("description", "N/A")}</p>
143
+ <p><strong>Hypothesis:</strong> {hypothesis}</p>
144
+ <p><strong>Actions:</strong></p>
145
+ {actions_html}
146
+ </div>
147
+ """
148
+
149
+ patterns_html = f"""
150
+ <div class="section">
151
+ <h2>Error Patterns</h2>
152
+ {pattern_cards}
153
+ </div>
154
+ """
155
+
156
+ # Complete HTML document
157
+ html = f"""
158
+ <!DOCTYPE html>
159
+ <html lang="en">
160
+ <head>
161
+ <meta charset="UTF-8">
162
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
163
+ <title>Trade-SHAP Diagnostics Report</title>
164
+ <style>
165
+ body {{
166
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
167
+ line-height: 1.6;
168
+ max-width: 1200px;
169
+ margin: 0 auto;
170
+ padding: 2rem;
171
+ background: #f5f5f5;
172
+ }}
173
+ .header {{
174
+ background: #1f77b4;
175
+ color: white;
176
+ padding: 2rem;
177
+ border-radius: 8px;
178
+ margin-bottom: 2rem;
179
+ }}
180
+ .header h1 {{
181
+ margin: 0;
182
+ }}
183
+ .header p {{
184
+ margin: 0.5rem 0 0 0;
185
+ opacity: 0.9;
186
+ }}
187
+ .section {{
188
+ background: white;
189
+ padding: 1.5rem;
190
+ border-radius: 8px;
191
+ margin-bottom: 1.5rem;
192
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
193
+ }}
194
+ .section h2 {{
195
+ color: #1f77b4;
196
+ margin-top: 0;
197
+ border-bottom: 2px solid #e0e0e0;
198
+ padding-bottom: 0.5rem;
199
+ }}
200
+ .metrics-grid {{
201
+ display: grid;
202
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
203
+ gap: 1rem;
204
+ margin: 1rem 0;
205
+ }}
206
+ .metric {{
207
+ background: #f8f9fa;
208
+ padding: 1rem;
209
+ border-radius: 4px;
210
+ text-align: center;
211
+ }}
212
+ .metric-label {{
213
+ font-size: 0.85rem;
214
+ color: #666;
215
+ }}
216
+ .metric-value {{
217
+ font-size: 1.5rem;
218
+ font-weight: 600;
219
+ color: #1f77b4;
220
+ }}
221
+ table {{
222
+ width: 100%;
223
+ border-collapse: collapse;
224
+ margin: 1rem 0;
225
+ }}
226
+ th, td {{
227
+ padding: 0.75rem;
228
+ text-align: left;
229
+ border-bottom: 1px solid #e0e0e0;
230
+ }}
231
+ th {{
232
+ background: #f8f9fa;
233
+ font-weight: 600;
234
+ }}
235
+ .pattern-card {{
236
+ background: #f8f9fa;
237
+ padding: 1rem;
238
+ border-radius: 4px;
239
+ margin: 1rem 0;
240
+ border-left: 4px solid #1f77b4;
241
+ }}
242
+ .pattern-card h3 {{
243
+ margin-top: 0;
244
+ color: #1f77b4;
245
+ }}
246
+ .caption {{
247
+ font-size: 0.85rem;
248
+ color: #666;
249
+ font-style: italic;
250
+ }}
251
+ .footer {{
252
+ text-align: center;
253
+ color: #666;
254
+ font-size: 0.85rem;
255
+ margin-top: 2rem;
256
+ }}
257
+ </style>
258
+ </head>
259
+ <body>
260
+ <div class="header">
261
+ <h1>Trade-SHAP Diagnostics Report</h1>
262
+ <p>Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
263
+ </div>
264
+
265
+ {summary_html}
266
+ {trades_html}
267
+ {patterns_html}
268
+
269
+ <div class="footer">
270
+ <p>Generated by Trade-SHAP Diagnostics Dashboard</p>
271
+ </div>
272
+ </body>
273
+ </html>
274
+ """
275
+
276
+ return html
@@ -0,0 +1,166 @@
1
+ """Dashboard I/O operations.
2
+
3
+ Handles loading data from uploaded files with security considerations.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from ml4t.diagnostic.evaluation.trade_shap.models import TradeShapResult
13
+
14
+
15
+ class PickleDisabledError(Exception):
16
+ """Raised when pickle loading is attempted but disabled."""
17
+
18
+ pass
19
+
20
+
21
+ def load_result_from_upload(
22
+ uploaded_file: Any,
23
+ allow_pickle: bool = False,
24
+ ) -> TradeShapResult | dict[str, Any]:
25
+ """Load TradeShapResult from uploaded file.
26
+
27
+ Parameters
28
+ ----------
29
+ uploaded_file : streamlit.UploadedFile
30
+ Uploaded JSON or pickle file.
31
+ allow_pickle : bool, default False
32
+ Whether to allow pickle files. Disabled by default for security.
33
+ Pickle files can execute arbitrary code when loaded.
34
+
35
+ Returns
36
+ -------
37
+ TradeShapResult or dict
38
+ Loaded result object or dictionary.
39
+
40
+ Raises
41
+ ------
42
+ PickleDisabledError
43
+ If pickle file uploaded but allow_pickle=False.
44
+ ValueError
45
+ If file format is unsupported or invalid.
46
+
47
+ Security Warning
48
+ ----------------
49
+ Pickle files can execute arbitrary code when loaded. Only enable
50
+ pickle loading for files from trusted sources.
51
+ """
52
+ filename = uploaded_file.name
53
+
54
+ try:
55
+ # JSON files are safe
56
+ if filename.endswith(".json"):
57
+ content = uploaded_file.read()
58
+ if isinstance(content, bytes):
59
+ content = content.decode("utf-8")
60
+ data = json.loads(content)
61
+ return data
62
+
63
+ # Pickle files require explicit opt-in
64
+ elif filename.endswith((".pkl", ".pickle")):
65
+ if not allow_pickle:
66
+ raise PickleDisabledError(
67
+ "Pickle files are disabled for security. "
68
+ "Pickle can execute arbitrary code. "
69
+ "Use JSON format or enable allow_pickle_upload in config."
70
+ )
71
+
72
+ import pickle
73
+
74
+ data = pickle.loads(uploaded_file.read())
75
+ return data
76
+
77
+ else:
78
+ raise ValueError(
79
+ f"Unsupported file format: {filename}. Supported formats: .json, .pkl, .pickle"
80
+ )
81
+
82
+ except PickleDisabledError:
83
+ raise
84
+ except json.JSONDecodeError as e:
85
+ raise ValueError(f"Invalid JSON format: {e}") from e
86
+ except Exception as e:
87
+ raise ValueError(f"Failed to load data from file: {e}") from e
88
+
89
+
90
+ def coerce_result_to_dict(
91
+ result: TradeShapResult | dict[str, Any],
92
+ ) -> dict[str, Any]:
93
+ """Coerce TradeShapResult to dict format.
94
+
95
+ This is a transitional function to help normalize input.
96
+ Prefer using normalize_result() for full normalization.
97
+
98
+ Parameters
99
+ ----------
100
+ result : TradeShapResult or dict
101
+ Analysis result in either format.
102
+
103
+ Returns
104
+ -------
105
+ dict
106
+ Result as dictionary.
107
+ """
108
+ if isinstance(result, dict):
109
+ return result
110
+
111
+ # Convert TradeShapResult to dict
112
+ return {
113
+ "n_trades_analyzed": result.n_trades_analyzed,
114
+ "n_trades_explained": result.n_trades_explained,
115
+ "n_trades_failed": result.n_trades_failed,
116
+ "explanations": [_explanation_to_dict(exp) for exp in result.explanations],
117
+ "failed_trades": result.failed_trades,
118
+ "error_patterns": [_pattern_to_dict(p) for p in result.error_patterns],
119
+ }
120
+
121
+
122
+ def _explanation_to_dict(exp: Any) -> dict[str, Any]:
123
+ """Convert explanation object to dict."""
124
+ if isinstance(exp, dict):
125
+ return exp
126
+
127
+ result = {
128
+ "trade_id": exp.trade_id,
129
+ "timestamp": str(exp.timestamp) if hasattr(exp, "timestamp") else None,
130
+ "shap_vector": list(exp.shap_vector) if hasattr(exp, "shap_vector") else [],
131
+ "top_features": list(exp.top_features) if hasattr(exp, "top_features") else [],
132
+ }
133
+
134
+ # Include trade_metrics if available
135
+ if hasattr(exp, "trade_metrics") and exp.trade_metrics:
136
+ tm = exp.trade_metrics
137
+ result["trade_metrics"] = {
138
+ "pnl": getattr(tm, "pnl", None),
139
+ "return_pct": getattr(tm, "return_pct", None),
140
+ "entry_time": str(getattr(tm, "entry_time", "")) or None,
141
+ "exit_time": str(getattr(tm, "exit_time", "")) or None,
142
+ "duration_days": getattr(tm, "duration_days", None),
143
+ "entry_price": getattr(tm, "entry_price", None),
144
+ "exit_price": getattr(tm, "exit_price", None),
145
+ "symbol": getattr(tm, "symbol", None),
146
+ }
147
+
148
+ return result
149
+
150
+
151
+ def _pattern_to_dict(pattern: Any) -> dict[str, Any]:
152
+ """Convert error pattern object to dict."""
153
+ if isinstance(pattern, dict):
154
+ return pattern
155
+
156
+ return {
157
+ "cluster_id": pattern.cluster_id,
158
+ "n_trades": pattern.n_trades,
159
+ "description": getattr(pattern, "description", ""),
160
+ "top_features": list(pattern.top_features) if hasattr(pattern, "top_features") else [],
161
+ "separation_score": getattr(pattern, "separation_score", None),
162
+ "distinctiveness": getattr(pattern, "distinctiveness", None),
163
+ "hypothesis": getattr(pattern, "hypothesis", None),
164
+ "actions": list(pattern.actions) if hasattr(pattern, "actions") and pattern.actions else [],
165
+ "confidence": getattr(pattern, "confidence", None),
166
+ }