ml4t-diagnostic 0.1.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ml4t/diagnostic/AGENT.md +25 -0
- ml4t/diagnostic/__init__.py +166 -0
- ml4t/diagnostic/backends/__init__.py +10 -0
- ml4t/diagnostic/backends/adapter.py +192 -0
- ml4t/diagnostic/backends/polars_backend.py +899 -0
- ml4t/diagnostic/caching/__init__.py +40 -0
- ml4t/diagnostic/caching/cache.py +331 -0
- ml4t/diagnostic/caching/decorators.py +131 -0
- ml4t/diagnostic/caching/smart_cache.py +339 -0
- ml4t/diagnostic/config/AGENT.md +24 -0
- ml4t/diagnostic/config/README.md +267 -0
- ml4t/diagnostic/config/__init__.py +219 -0
- ml4t/diagnostic/config/barrier_config.py +277 -0
- ml4t/diagnostic/config/base.py +301 -0
- ml4t/diagnostic/config/event_config.py +148 -0
- ml4t/diagnostic/config/feature_config.py +404 -0
- ml4t/diagnostic/config/multi_signal_config.py +55 -0
- ml4t/diagnostic/config/portfolio_config.py +215 -0
- ml4t/diagnostic/config/report_config.py +391 -0
- ml4t/diagnostic/config/sharpe_config.py +202 -0
- ml4t/diagnostic/config/signal_config.py +206 -0
- ml4t/diagnostic/config/trade_analysis_config.py +310 -0
- ml4t/diagnostic/config/validation.py +279 -0
- ml4t/diagnostic/core/__init__.py +29 -0
- ml4t/diagnostic/core/numba_utils.py +315 -0
- ml4t/diagnostic/core/purging.py +372 -0
- ml4t/diagnostic/core/sampling.py +471 -0
- ml4t/diagnostic/errors/__init__.py +205 -0
- ml4t/diagnostic/evaluation/AGENT.md +26 -0
- ml4t/diagnostic/evaluation/__init__.py +437 -0
- ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
- ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
- ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
- ml4t/diagnostic/evaluation/dashboard.py +715 -0
- ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
- ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
- ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
- ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
- ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
- ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
- ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
- ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
- ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
- ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
- ml4t/diagnostic/evaluation/event_analysis.py +647 -0
- ml4t/diagnostic/evaluation/excursion.py +390 -0
- ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
- ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
- ml4t/diagnostic/evaluation/framework.py +935 -0
- ml4t/diagnostic/evaluation/metric_registry.py +255 -0
- ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
- ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
- ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
- ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
- ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
- ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
- ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
- ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
- ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
- ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
- ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
- ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
- ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
- ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
- ml4t/diagnostic/evaluation/multi_signal.py +550 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
- ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
- ml4t/diagnostic/evaluation/report_generation.py +824 -0
- ml4t/diagnostic/evaluation/signal_selector.py +452 -0
- ml4t/diagnostic/evaluation/stat_registry.py +139 -0
- ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
- ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
- ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
- ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
- ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
- ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
- ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
- ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
- ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
- ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
- ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
- ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
- ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
- ml4t/diagnostic/evaluation/stats/moments.py +164 -0
- ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
- ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
- ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
- ml4t/diagnostic/evaluation/themes.py +330 -0
- ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
- ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
- ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
- ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
- ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
- ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
- ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
- ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
- ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
- ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
- ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
- ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
- ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
- ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
- ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
- ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
- ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
- ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
- ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
- ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
- ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
- ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
- ml4t/diagnostic/evaluation/validated_cv.py +535 -0
- ml4t/diagnostic/evaluation/visualization.py +1050 -0
- ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
- ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
- ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
- ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
- ml4t/diagnostic/integration/__init__.py +48 -0
- ml4t/diagnostic/integration/backtest_contract.py +671 -0
- ml4t/diagnostic/integration/data_contract.py +316 -0
- ml4t/diagnostic/integration/engineer_contract.py +226 -0
- ml4t/diagnostic/logging/__init__.py +77 -0
- ml4t/diagnostic/logging/logger.py +245 -0
- ml4t/diagnostic/logging/performance.py +234 -0
- ml4t/diagnostic/logging/progress.py +234 -0
- ml4t/diagnostic/logging/wandb.py +412 -0
- ml4t/diagnostic/metrics/__init__.py +9 -0
- ml4t/diagnostic/metrics/percentiles.py +128 -0
- ml4t/diagnostic/py.typed +1 -0
- ml4t/diagnostic/reporting/__init__.py +43 -0
- ml4t/diagnostic/reporting/base.py +130 -0
- ml4t/diagnostic/reporting/html_renderer.py +275 -0
- ml4t/diagnostic/reporting/json_renderer.py +51 -0
- ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
- ml4t/diagnostic/results/AGENT.md +24 -0
- ml4t/diagnostic/results/__init__.py +105 -0
- ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
- ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
- ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
- ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
- ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
- ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
- ml4t/diagnostic/results/barrier_results/validation.py +38 -0
- ml4t/diagnostic/results/base.py +177 -0
- ml4t/diagnostic/results/event_results.py +349 -0
- ml4t/diagnostic/results/feature_results.py +787 -0
- ml4t/diagnostic/results/multi_signal_results.py +431 -0
- ml4t/diagnostic/results/portfolio_results.py +281 -0
- ml4t/diagnostic/results/sharpe_results.py +448 -0
- ml4t/diagnostic/results/signal_results/__init__.py +74 -0
- ml4t/diagnostic/results/signal_results/ic.py +581 -0
- ml4t/diagnostic/results/signal_results/irtc.py +110 -0
- ml4t/diagnostic/results/signal_results/quantile.py +392 -0
- ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
- ml4t/diagnostic/results/signal_results/turnover.py +213 -0
- ml4t/diagnostic/results/signal_results/validation.py +147 -0
- ml4t/diagnostic/signal/AGENT.md +17 -0
- ml4t/diagnostic/signal/__init__.py +69 -0
- ml4t/diagnostic/signal/_report.py +152 -0
- ml4t/diagnostic/signal/_utils.py +261 -0
- ml4t/diagnostic/signal/core.py +275 -0
- ml4t/diagnostic/signal/quantile.py +148 -0
- ml4t/diagnostic/signal/result.py +214 -0
- ml4t/diagnostic/signal/signal_ic.py +129 -0
- ml4t/diagnostic/signal/turnover.py +182 -0
- ml4t/diagnostic/splitters/AGENT.md +19 -0
- ml4t/diagnostic/splitters/__init__.py +36 -0
- ml4t/diagnostic/splitters/base.py +501 -0
- ml4t/diagnostic/splitters/calendar.py +421 -0
- ml4t/diagnostic/splitters/calendar_config.py +91 -0
- ml4t/diagnostic/splitters/combinatorial.py +1064 -0
- ml4t/diagnostic/splitters/config.py +322 -0
- ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
- ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
- ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
- ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
- ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
- ml4t/diagnostic/splitters/group_isolation.py +329 -0
- ml4t/diagnostic/splitters/persistence.py +316 -0
- ml4t/diagnostic/splitters/utils.py +207 -0
- ml4t/diagnostic/splitters/walk_forward.py +757 -0
- ml4t/diagnostic/utils/__init__.py +42 -0
- ml4t/diagnostic/utils/config.py +542 -0
- ml4t/diagnostic/utils/dependencies.py +318 -0
- ml4t/diagnostic/utils/sessions.py +127 -0
- ml4t/diagnostic/validation/__init__.py +54 -0
- ml4t/diagnostic/validation/dataframe.py +274 -0
- ml4t/diagnostic/validation/returns.py +280 -0
- ml4t/diagnostic/validation/timeseries.py +299 -0
- ml4t/diagnostic/visualization/AGENT.md +19 -0
- ml4t/diagnostic/visualization/__init__.py +223 -0
- ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
- ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
- ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
- ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
- ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
- ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
- ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
- ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
- ml4t/diagnostic/visualization/barrier_plots.py +782 -0
- ml4t/diagnostic/visualization/core.py +1060 -0
- ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
- ml4t/diagnostic/visualization/dashboards/base.py +582 -0
- ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
- ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
- ml4t/diagnostic/visualization/dashboards.py +43 -0
- ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
- ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
- ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
- ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
- ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
- ml4t/diagnostic/visualization/feature_plots.py +888 -0
- ml4t/diagnostic/visualization/interaction_plots.py +618 -0
- ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
- ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
- ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
- ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
- ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
- ml4t/diagnostic/visualization/report_generation.py +1343 -0
- ml4t/diagnostic/visualization/signal/__init__.py +103 -0
- ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
- ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
- ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
- ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
- ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
- ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
- ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
- ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
- ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
- ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
- ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
- ml4t_diagnostic-0.1.0a1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,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)
|