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