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,715 @@
1
+ """Dashboard generation for ml4t-diagnostic evaluation results.
2
+
3
+ This module provides HTML dashboard generation with interactive Plotly
4
+ visualizations for comprehensive evaluation reports.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any, cast
10
+
11
+ import numpy as np
12
+ import pandas as pd
13
+ from jinja2 import Template
14
+
15
+ from .themes import apply_theme
16
+ from .visualization import (
17
+ plot_feature_distributions,
18
+ plot_ic_heatmap,
19
+ plot_quantile_returns,
20
+ plot_turnover_decay,
21
+ )
22
+
23
+ if TYPE_CHECKING:
24
+ from .framework import EvaluationResult
25
+
26
+
27
+ # HTML template for dashboard
28
+ DASHBOARD_TEMPLATE = """
29
+ <!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
+ <title>ml4t-diagnostic Evaluation Report - {{ title }}</title>
35
+ <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
36
+ <style>
37
+ :root {
38
+ --primary-color: #3366CC;
39
+ --success-color: #00CC88;
40
+ --danger-color: #FF4444;
41
+ --bg-color: #F8F9FA;
42
+ --text-color: #333333;
43
+ --border-color: #E0E0E0;
44
+ }
45
+
46
+ * {
47
+ box-sizing: border-box;
48
+ margin: 0;
49
+ padding: 0;
50
+ }
51
+
52
+ body {
53
+ font-family: Arial, sans-serif;
54
+ background-color: var(--bg-color);
55
+ color: var(--text-color);
56
+ line-height: 1.6;
57
+ }
58
+
59
+ .container {
60
+ max-width: 1400px;
61
+ margin: 0 auto;
62
+ padding: 20px;
63
+ }
64
+
65
+ header {
66
+ background-color: white;
67
+ padding: 30px;
68
+ border-radius: 8px;
69
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
70
+ margin-bottom: 30px;
71
+ }
72
+
73
+ h1 {
74
+ color: var(--primary-color);
75
+ margin-bottom: 10px;
76
+ }
77
+
78
+ .metadata {
79
+ color: #666;
80
+ font-size: 14px;
81
+ }
82
+
83
+ .summary-grid {
84
+ display: grid;
85
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
86
+ gap: 20px;
87
+ margin-bottom: 30px;
88
+ }
89
+
90
+ .metric-card {
91
+ background-color: white;
92
+ padding: 20px;
93
+ border-radius: 8px;
94
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
95
+ text-align: center;
96
+ }
97
+
98
+ .metric-card h3 {
99
+ font-size: 14px;
100
+ color: #666;
101
+ margin-bottom: 10px;
102
+ text-transform: uppercase;
103
+ }
104
+
105
+ .metric-value {
106
+ font-size: 32px;
107
+ font-weight: bold;
108
+ margin-bottom: 5px;
109
+ }
110
+
111
+ .metric-value.positive {
112
+ color: var(--success-color);
113
+ }
114
+
115
+ .metric-value.negative {
116
+ color: var(--danger-color);
117
+ }
118
+
119
+ .metric-detail {
120
+ font-size: 12px;
121
+ color: #999;
122
+ }
123
+
124
+ .tabs {
125
+ background-color: white;
126
+ border-radius: 8px;
127
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
128
+ margin-bottom: 20px;
129
+ overflow: hidden;
130
+ }
131
+
132
+ .tab-nav {
133
+ display: flex;
134
+ border-bottom: 2px solid var(--border-color);
135
+ }
136
+
137
+ .tab-button {
138
+ padding: 15px 30px;
139
+ border: none;
140
+ background: none;
141
+ cursor: pointer;
142
+ font-size: 16px;
143
+ color: #666;
144
+ transition: all 0.3s;
145
+ position: relative;
146
+ }
147
+
148
+ .tab-button:hover {
149
+ color: var(--primary-color);
150
+ background-color: rgba(51, 102, 204, 0.05);
151
+ }
152
+
153
+ .tab-button.active {
154
+ color: var(--primary-color);
155
+ font-weight: bold;
156
+ }
157
+
158
+ .tab-button.active::after {
159
+ content: '';
160
+ position: absolute;
161
+ bottom: -2px;
162
+ left: 0;
163
+ right: 0;
164
+ height: 2px;
165
+ background-color: var(--primary-color);
166
+ }
167
+
168
+ .tab-content {
169
+ display: none;
170
+ padding: 30px;
171
+ }
172
+
173
+ .tab-content.active {
174
+ display: block;
175
+ }
176
+
177
+ .plot-container {
178
+ background-color: white;
179
+ padding: 20px;
180
+ border-radius: 8px;
181
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
182
+ margin-bottom: 20px;
183
+ }
184
+
185
+ .plot-title {
186
+ font-size: 18px;
187
+ font-weight: bold;
188
+ margin-bottom: 15px;
189
+ color: var(--text-color);
190
+ }
191
+
192
+ .section {
193
+ margin-bottom: 40px;
194
+ }
195
+
196
+ .insights {
197
+ background-color: #FFF3CD;
198
+ border: 1px solid #FFE5A0;
199
+ border-radius: 8px;
200
+ padding: 20px;
201
+ margin-bottom: 20px;
202
+ }
203
+
204
+ .insights h4 {
205
+ color: #856404;
206
+ margin-bottom: 10px;
207
+ }
208
+
209
+ .insights ul {
210
+ margin-left: 20px;
211
+ color: #856404;
212
+ }
213
+
214
+ @media (max-width: 768px) {
215
+ .container {
216
+ padding: 10px;
217
+ }
218
+
219
+ .tab-button {
220
+ padding: 10px 15px;
221
+ font-size: 14px;
222
+ }
223
+
224
+ .summary-grid {
225
+ grid-template-columns: 1fr;
226
+ }
227
+ }
228
+
229
+ @media print {
230
+ .tabs {
231
+ display: none;
232
+ }
233
+
234
+ .tab-content {
235
+ display: block !important;
236
+ page-break-inside: avoid;
237
+ }
238
+
239
+ .plot-container {
240
+ box-shadow: none;
241
+ border: 1px solid #ddd;
242
+ }
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <div class="container">
248
+ <header>
249
+ <h1>{{ title }}</h1>
250
+ <div class="metadata">
251
+ <p><strong>Tier {{ tier }} Evaluation</strong> |
252
+ Generated: {{ timestamp }} |
253
+ Splitter: {{ splitter }}</p>
254
+ </div>
255
+ </header>
256
+
257
+ <!-- Summary Metrics -->
258
+ <div class="summary-grid">
259
+ {% for metric_name, metric_data in metrics.items() %}
260
+ <div class="metric-card">
261
+ <h3>{{ metric_name|upper|replace('_', ' ') }}</h3>
262
+ <div class="metric-value {% if metric_data.value > 0 %}positive{% else %}negative{% endif %}">
263
+ {{ "%.4f"|format(metric_data.value) }}
264
+ </div>
265
+ {% if metric_data.std %}
266
+ <div class="metric-detail">± {{ "%.4f"|format(metric_data.std) }}</div>
267
+ {% endif %}
268
+ {% if metric_data.significant is defined %}
269
+ <div class="metric-detail">
270
+ {% if metric_data.significant %}✓ Significant{% else %}Not Significant{% endif %}
271
+ </div>
272
+ {% endif %}
273
+ </div>
274
+ {% endfor %}
275
+ </div>
276
+
277
+ <!-- Insights Section -->
278
+ {% if insights %}
279
+ <div class="insights">
280
+ <h4>Key Insights</h4>
281
+ <ul>
282
+ {% for insight in insights %}
283
+ <li>{{ insight }}</li>
284
+ {% endfor %}
285
+ </ul>
286
+ </div>
287
+ {% endif %}
288
+
289
+ <!-- Tabbed Content -->
290
+ <div class="tabs">
291
+ <div class="tab-nav">
292
+ <button class="tab-button active" onclick="showTab(event, 'performance')">Performance</button>
293
+ <button class="tab-button" onclick="showTab(event, 'statistics')">Statistical Tests</button>
294
+ <button class="tab-button" onclick="showTab(event, 'stability')">Stability Analysis</button>
295
+ <button class="tab-button" onclick="showTab(event, 'distributions')">Distributions</button>
296
+ {% if custom_tabs %}
297
+ {% for tab_name in custom_tabs %}
298
+ <button class="tab-button" onclick="showTab(event, '{{ tab_name }}')">{{ tab_name|title }}</button>
299
+ {% endfor %}
300
+ {% endif %}
301
+ </div>
302
+
303
+ <!-- Performance Tab -->
304
+ <div id="performance" class="tab-content active">
305
+ <div class="section">
306
+ {% for plot_id, plot_title in performance_plots.items() %}
307
+ <div class="plot-container">
308
+ <div class="plot-title">{{ plot_title }}</div>
309
+ <div id="{{ plot_id }}"></div>
310
+ </div>
311
+ {% endfor %}
312
+ </div>
313
+ </div>
314
+
315
+ <!-- Statistical Tests Tab -->
316
+ <div id="statistics" class="tab-content">
317
+ <div class="section">
318
+ {% if statistical_tests %}
319
+ <table style="width: 100%; border-collapse: collapse;">
320
+ <thead>
321
+ <tr style="border-bottom: 2px solid #ddd;">
322
+ <th style="padding: 10px; text-align: left;">Test</th>
323
+ <th style="padding: 10px; text-align: right;">Statistic</th>
324
+ <th style="padding: 10px; text-align: right;">P-Value</th>
325
+ <th style="padding: 10px; text-align: center;">Significant</th>
326
+ </tr>
327
+ </thead>
328
+ <tbody>
329
+ {% for test_name, test_data in statistical_tests.items() %}
330
+ <tr style="border-bottom: 1px solid #eee;">
331
+ <td style="padding: 10px;">{{ test_name|upper|replace('_', ' ') }}</td>
332
+ <td style="padding: 10px; text-align: right;">{{ "%.4f"|format(test_data.statistic) }}</td>
333
+ <td style="padding: 10px; text-align: right;">{{ "%.4f"|format(test_data.p_value) }}</td>
334
+ <td style="padding: 10px; text-align: center;">
335
+ {% if test_data.p_value < 0.05 %}
336
+ <span style="color: var(--success-color);">✓</span>
337
+ {% else %}
338
+ <span style="color: var(--danger-color);">✗</span>
339
+ {% endif %}
340
+ </td>
341
+ </tr>
342
+ {% endfor %}
343
+ </tbody>
344
+ </table>
345
+ {% else %}
346
+ <p>No statistical tests were performed for this evaluation.</p>
347
+ {% endif %}
348
+ </div>
349
+ </div>
350
+
351
+ <!-- Stability Analysis Tab -->
352
+ <div id="stability" class="tab-content">
353
+ <div class="section">
354
+ {% for plot_id, plot_title in stability_plots.items() %}
355
+ <div class="plot-container">
356
+ <div class="plot-title">{{ plot_title }}</div>
357
+ <div id="{{ plot_id }}"></div>
358
+ </div>
359
+ {% endfor %}
360
+ </div>
361
+ </div>
362
+
363
+ <!-- Distributions Tab -->
364
+ <div id="distributions" class="tab-content">
365
+ <div class="section">
366
+ {% for plot_id, plot_title in distribution_plots.items() %}
367
+ <div class="plot-container">
368
+ <div class="plot-title">{{ plot_title }}</div>
369
+ <div id="{{ plot_id }}"></div>
370
+ </div>
371
+ {% endfor %}
372
+ </div>
373
+ </div>
374
+ </div>
375
+ </div>
376
+
377
+ <script>
378
+ // Tab functionality
379
+ function showTab(evt, tabName) {
380
+ var i, tabcontent, tabbuttons;
381
+
382
+ tabcontent = document.getElementsByClassName("tab-content");
383
+ for (i = 0; i < tabcontent.length; i++) {
384
+ tabcontent[i].classList.remove("active");
385
+ }
386
+
387
+ tabbuttons = document.getElementsByClassName("tab-button");
388
+ for (i = 0; i < tabbuttons.length; i++) {
389
+ tabbuttons[i].classList.remove("active");
390
+ }
391
+
392
+ document.getElementById(tabName).classList.add("active");
393
+ evt.currentTarget.classList.add("active");
394
+ }
395
+
396
+ // Plot data
397
+ {{ plot_data }}
398
+ </script>
399
+ </body>
400
+ </html>
401
+ """
402
+
403
+
404
+ class DashboardBuilder:
405
+ """Build comprehensive HTML evaluation reports."""
406
+
407
+ def __init__(self, result: "EvaluationResult", theme: str = "default"):
408
+ """Initialize dashboard builder.
409
+
410
+ Parameters
411
+ ----------
412
+ result : EvaluationResult
413
+ Evaluation result to visualize
414
+ theme : str, default "default"
415
+ Theme to apply: "default", "dark", "print"
416
+ """
417
+ self.result = result
418
+ self.theme = theme
419
+ self.figures: dict[str, Any] = {}
420
+ self.plot_data: dict[str, Any] = {}
421
+
422
+ def add_performance_plots(
423
+ self,
424
+ predictions: pd.DataFrame | None = None,
425
+ returns: pd.DataFrame | None = None,
426
+ ) -> None:
427
+ """Add performance-related plots.
428
+
429
+ Parameters
430
+ ----------
431
+ predictions : pd.DataFrame, optional
432
+ Model predictions for IC analysis
433
+ returns : pd.DataFrame, optional
434
+ Returns data for analysis
435
+ """
436
+ # IC Heatmap
437
+ if predictions is not None and returns is not None:
438
+ fig = plot_ic_heatmap(
439
+ predictions,
440
+ returns,
441
+ title="Information Coefficient Term Structure",
442
+ )
443
+ fig = apply_theme(fig, self.theme)
444
+ self.figures["ic_heatmap"] = fig
445
+ self.plot_data["ic_heatmap"] = fig.to_json()
446
+
447
+ # Quantile Returns
448
+ if predictions is not None and returns is not None:
449
+ # Use first column if DataFrame
450
+ pred_series = (
451
+ predictions.iloc[:, 0] if isinstance(predictions, pd.DataFrame) else predictions
452
+ )
453
+ ret_series = returns.iloc[:, 0] if isinstance(returns, pd.DataFrame) else returns
454
+
455
+ fig = plot_quantile_returns(
456
+ pred_series,
457
+ ret_series,
458
+ title="Returns by Prediction Quantile",
459
+ )
460
+ fig = apply_theme(fig, self.theme)
461
+ self.figures["quantile_returns"] = fig
462
+ self.plot_data["quantile_returns"] = fig.to_json()
463
+
464
+ def add_stability_plots(self, factor_values: pd.DataFrame | None = None) -> None:
465
+ """Add stability analysis plots.
466
+
467
+ Parameters
468
+ ----------
469
+ factor_values : pd.DataFrame, optional
470
+ Time series of factor values
471
+ """
472
+ if factor_values is not None:
473
+ fig = plot_turnover_decay(
474
+ factor_values,
475
+ title="Factor Turnover and Decay Analysis",
476
+ )
477
+ fig = apply_theme(fig, self.theme)
478
+ self.figures["turnover_decay"] = fig
479
+ self.plot_data["turnover_decay"] = fig.to_json()
480
+
481
+ def add_distribution_plots(self, features: pd.DataFrame | None = None) -> None:
482
+ """Add feature distribution plots.
483
+
484
+ Parameters
485
+ ----------
486
+ features : pd.DataFrame, optional
487
+ Feature values over time
488
+ """
489
+ if features is not None:
490
+ fig = plot_feature_distributions(
491
+ features,
492
+ title="Feature Distribution Analysis",
493
+ )
494
+ fig = apply_theme(fig, self.theme)
495
+ self.figures["feature_distributions"] = fig
496
+ self.plot_data["feature_distributions"] = fig.to_json()
497
+
498
+ def _generate_insights(self) -> list[str]:
499
+ """Generate key insights from evaluation results."""
500
+ insights = []
501
+
502
+ # Check IC significance
503
+ if "ic" in self.result.metrics_results:
504
+ ic_mean = self.result.metrics_results["ic"].get("mean", 0)
505
+ if abs(ic_mean) > 0.03:
506
+ insights.append(
507
+ f"Information Coefficient of {ic_mean:.3f} indicates "
508
+ f"{'positive' if ic_mean > 0 else 'negative'} predictive relationship",
509
+ )
510
+
511
+ # Check Sharpe ratio
512
+ if "sharpe" in self.result.metrics_results:
513
+ sharpe = self.result.metrics_results["sharpe"].get("mean", 0)
514
+ if sharpe > 1:
515
+ insights.append(
516
+ f"Sharpe ratio of {sharpe:.2f} suggests strong risk-adjusted returns",
517
+ )
518
+ elif sharpe < 0:
519
+ insights.append(
520
+ f"Negative Sharpe ratio ({sharpe:.2f}) indicates poor performance",
521
+ )
522
+
523
+ # Check statistical significance
524
+ if self.result.statistical_tests:
525
+ significant_tests = [
526
+ name
527
+ for name, test in self.result.statistical_tests.items()
528
+ if isinstance(test, dict) and test.get("p_value", 1) < 0.05
529
+ ]
530
+ if significant_tests:
531
+ insights.append(
532
+ f"Statistically significant results for: {', '.join(significant_tests)}",
533
+ )
534
+
535
+ # Check tier-specific insights
536
+ if self.result.tier == 1:
537
+ insights.append(
538
+ "Tier 1 evaluation provides rigorous multiple testing corrections",
539
+ )
540
+ elif self.result.tier == 2:
541
+ insights.append(
542
+ "Tier 2 evaluation includes HAC-adjusted significance tests",
543
+ )
544
+ else:
545
+ insights.append("Tier 3 evaluation provides fast screening metrics")
546
+
547
+ return insights
548
+
549
+ def generate_html(
550
+ self,
551
+ filename: str,
552
+ title: str | None = None,
553
+ _include_data: bool = True,
554
+ ) -> None:
555
+ """Generate complete HTML report.
556
+
557
+ Parameters
558
+ ----------
559
+ filename : str
560
+ Output filename for HTML report
561
+ title : str, optional
562
+ Report title. If None, auto-generated
563
+ include_data : bool, default True
564
+ Whether to include raw data in report
565
+ """
566
+ # Prepare template data
567
+ template_data = {
568
+ "title": title or f"ml4t-diagnostic Evaluation Report - Tier {self.result.tier}",
569
+ "tier": self.result.tier,
570
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
571
+ "splitter": self.result.splitter_name,
572
+ "metrics": {},
573
+ "statistical_tests": {},
574
+ "insights": self._generate_insights(),
575
+ "performance_plots": {},
576
+ "stability_plots": {},
577
+ "distribution_plots": {},
578
+ "custom_tabs": [],
579
+ "plot_data": "",
580
+ }
581
+
582
+ # Process metrics - cast nested dicts for type safety
583
+ metrics_dict = cast(dict[str, Any], template_data["metrics"])
584
+ for metric_name, metric_data in self.result.metrics_results.items():
585
+ if isinstance(metric_data, dict):
586
+ metrics_dict[metric_name] = {
587
+ "value": metric_data.get("mean", 0),
588
+ "std": metric_data.get("std"),
589
+ "significant": metric_data.get("significant"),
590
+ }
591
+ else:
592
+ metrics_dict[metric_name] = {
593
+ "value": metric_data,
594
+ "std": None,
595
+ "significant": None,
596
+ }
597
+
598
+ # Process statistical tests
599
+ stats_dict = cast(dict[str, Any], template_data["statistical_tests"])
600
+ for test_name, test_data in self.result.statistical_tests.items():
601
+ if isinstance(test_data, dict) and "error" not in test_data:
602
+ stats_dict[test_name] = {
603
+ "statistic": test_data.get(
604
+ "test_statistic",
605
+ test_data.get("dsr", test_data.get("ic", np.nan)),
606
+ ),
607
+ "p_value": test_data.get("p_value", np.nan),
608
+ }
609
+
610
+ # Prepare plot references
611
+ perf_plots_dict = cast(dict[str, Any], template_data["performance_plots"])
612
+ for plot_id in ["ic_heatmap", "quantile_returns"]:
613
+ if plot_id in self.plot_data:
614
+ perf_plots_dict[plot_id] = plot_id.replace("_", " ").title()
615
+
616
+ stability_plots_dict = cast(dict[str, Any], template_data["stability_plots"])
617
+ for plot_id in ["turnover_decay"]:
618
+ if plot_id in self.plot_data:
619
+ stability_plots_dict[plot_id] = plot_id.replace("_", " ").title()
620
+
621
+ dist_plots_dict = cast(dict[str, Any], template_data["distribution_plots"])
622
+ for plot_id in ["feature_distributions"]:
623
+ if plot_id in self.plot_data:
624
+ dist_plots_dict[plot_id] = plot_id.replace("_", " ").title()
625
+
626
+ # Generate JavaScript for plots
627
+ plot_js = []
628
+ for plot_id, plot_json in self.plot_data.items():
629
+ plot_js.append(
630
+ f"Plotly.newPlot('{plot_id}', {plot_json}.data, {plot_json}.layout);",
631
+ )
632
+ template_data["plot_data"] = "\n".join(plot_js)
633
+
634
+ # Render template
635
+ template = Template(DASHBOARD_TEMPLATE)
636
+ html_content = template.render(**template_data)
637
+
638
+ # Save to file
639
+ output_path = Path(filename)
640
+ output_path.write_text(html_content)
641
+
642
+ def export_plots(self, output_dir: str, format: str = "png") -> None:
643
+ """Export individual plots as static images.
644
+
645
+ Parameters
646
+ ----------
647
+ output_dir : str
648
+ Directory to save plots
649
+ format : str, default "png"
650
+ Export format: "png", "svg", "pdf"
651
+ """
652
+ output_path = Path(output_dir)
653
+ output_path.mkdir(exist_ok=True)
654
+
655
+ try:
656
+ import kaleido # noqa: F401 (availability check)
657
+ except ImportError:
658
+ raise ImportError( # noqa: B904
659
+ "kaleido is required for static export. Install with: pip install kaleido",
660
+ )
661
+
662
+ for name, fig in self.figures.items():
663
+ filename = output_path / f"{name}.{format}"
664
+ fig.write_image(str(filename))
665
+
666
+
667
+ def create_evaluation_dashboard(
668
+ result: "EvaluationResult",
669
+ output_file: str,
670
+ predictions: pd.DataFrame | None = None,
671
+ returns: pd.DataFrame | None = None,
672
+ features: pd.DataFrame | None = None,
673
+ theme: str = "default",
674
+ title: str | None = None,
675
+ ) -> None:
676
+ """Convenience function to create a complete evaluation dashboard.
677
+
678
+ Parameters
679
+ ----------
680
+ result : EvaluationResult
681
+ Evaluation results to visualize
682
+ output_file : str
683
+ Output HTML filename
684
+ predictions : pd.DataFrame, optional
685
+ Model predictions for visualizations
686
+ returns : pd.DataFrame, optional
687
+ Returns data for visualizations
688
+ features : pd.DataFrame, optional
689
+ Feature data for distribution analysis
690
+ theme : str, default "default"
691
+ Dashboard theme
692
+ title : str, optional
693
+ Dashboard title
694
+
695
+ Examples:
696
+ --------
697
+ >>> create_evaluation_dashboard(
698
+ ... result,
699
+ ... "evaluation_report.html",
700
+ ... predictions=pred_df,
701
+ ... returns=returns_df
702
+ ... )
703
+ """
704
+ builder = DashboardBuilder(result, theme)
705
+
706
+ # Add visualizations based on available data
707
+ if predictions is not None or returns is not None:
708
+ builder.add_performance_plots(predictions, returns)
709
+
710
+ if features is not None:
711
+ builder.add_stability_plots(features)
712
+ builder.add_distribution_plots(features)
713
+
714
+ # Generate HTML
715
+ builder.generate_html(output_file, title)