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,79 @@
|
|
|
1
|
+
"""Dashboard styling constants.
|
|
2
|
+
|
|
3
|
+
CSS styling for the Trade SHAP diagnostics dashboard.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
# Professional CSS styling for styled mode
|
|
9
|
+
STYLED_CSS = """
|
|
10
|
+
<style>
|
|
11
|
+
/* Professional theme colors */
|
|
12
|
+
:root {
|
|
13
|
+
--primary-color: #1f77b4;
|
|
14
|
+
--success-color: #51CF66;
|
|
15
|
+
--warning-color: #FF9800;
|
|
16
|
+
--error-color: #FF6B6B;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Headers with professional styling */
|
|
20
|
+
h1 {
|
|
21
|
+
color: var(--primary-color);
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
padding-bottom: 1rem;
|
|
24
|
+
border-bottom: 2px solid #e0e0e0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h2 {
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
margin-top: 1.5rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Enhanced metrics */
|
|
33
|
+
[data-testid="stMetricValue"] {
|
|
34
|
+
font-size: 2rem;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Polished containers */
|
|
39
|
+
.stExpander {
|
|
40
|
+
border: 1px solid #e0e0e0;
|
|
41
|
+
border-radius: 8px;
|
|
42
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Professional buttons */
|
|
46
|
+
.stButton>button {
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
transition: all 0.2s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.stButton>button:hover {
|
|
53
|
+
transform: translateY(-1px);
|
|
54
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Download buttons styling */
|
|
58
|
+
.stDownloadButton>button {
|
|
59
|
+
background-color: var(--primary-color) !important;
|
|
60
|
+
color: white !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Sidebar styling */
|
|
64
|
+
[data-testid="stSidebar"] {
|
|
65
|
+
background-color: #f8f9fa;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Tab styling */
|
|
69
|
+
.stTabs [data-baseweb="tab"] {
|
|
70
|
+
padding: 0.5rem 1rem;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Progress bars */
|
|
75
|
+
.stProgress > div > div {
|
|
76
|
+
background-color: var(--success-color);
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
79
|
+
"""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Dashboard tab modules.
|
|
2
|
+
|
|
3
|
+
Each tab module provides a `render_tab(st, bundle)` function that renders
|
|
4
|
+
the tab content using the Streamlit instance and DashboardBundle.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from ml4t.diagnostic.evaluation.trade_dashboard.tabs import (
|
|
10
|
+
patterns,
|
|
11
|
+
shap_analysis,
|
|
12
|
+
stat_validation,
|
|
13
|
+
worst_trades,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"patterns",
|
|
18
|
+
"shap_analysis",
|
|
19
|
+
"stat_validation",
|
|
20
|
+
"worst_trades",
|
|
21
|
+
]
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Error Patterns tab.
|
|
2
|
+
|
|
3
|
+
Displays clustered error patterns with hypotheses and recommended actions.
|
|
4
|
+
Applies Benjamini-Hochberg FDR correction to feature significance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ml4t.diagnostic.evaluation.trade_dashboard.types import DashboardBundle
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_tab(st: Any, bundle: DashboardBundle) -> None:
|
|
18
|
+
"""Render the Error Patterns tab.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
st : streamlit
|
|
23
|
+
Streamlit module instance.
|
|
24
|
+
bundle : DashboardBundle
|
|
25
|
+
Normalized dashboard data.
|
|
26
|
+
"""
|
|
27
|
+
st.header("Error Patterns & Recommendations")
|
|
28
|
+
|
|
29
|
+
st.info(
|
|
30
|
+
"Error patterns are identified by clustering trades with similar "
|
|
31
|
+
"SHAP profiles. Each pattern includes hypotheses and actionable "
|
|
32
|
+
"recommendations for improvement."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
patterns_df = bundle.patterns_df
|
|
36
|
+
|
|
37
|
+
if patterns_df.empty:
|
|
38
|
+
st.warning("No error patterns found. Ensure clustering was performed during analysis.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
patterns = patterns_df.to_dict("records")
|
|
42
|
+
|
|
43
|
+
# Sidebar filters
|
|
44
|
+
with st.sidebar:
|
|
45
|
+
st.divider()
|
|
46
|
+
st.subheader("Pattern Filters")
|
|
47
|
+
|
|
48
|
+
sort_by = st.selectbox(
|
|
49
|
+
"Sort patterns by",
|
|
50
|
+
options=[
|
|
51
|
+
"Pattern ID",
|
|
52
|
+
"Number of Trades (Desc)",
|
|
53
|
+
"Confidence (Desc)",
|
|
54
|
+
"Distinctiveness (Desc)",
|
|
55
|
+
],
|
|
56
|
+
index=1,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
max_n_trades = max(p.get("n_trades", 0) for p in patterns) if patterns else 1
|
|
60
|
+
min_trades = st.slider(
|
|
61
|
+
"Min trades in pattern",
|
|
62
|
+
min_value=1,
|
|
63
|
+
max_value=max(1, max_n_trades),
|
|
64
|
+
value=1,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
show_only_with_hypothesis = st.checkbox(
|
|
68
|
+
"Only patterns with hypotheses",
|
|
69
|
+
value=False,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Filter patterns
|
|
73
|
+
filtered_patterns = [
|
|
74
|
+
p
|
|
75
|
+
for p in patterns
|
|
76
|
+
if p.get("n_trades", 0) >= min_trades
|
|
77
|
+
and (not show_only_with_hypothesis or p.get("hypothesis"))
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Sort patterns
|
|
81
|
+
if sort_by == "Pattern ID":
|
|
82
|
+
filtered_patterns.sort(key=lambda p: p.get("cluster_id", 0))
|
|
83
|
+
elif sort_by == "Number of Trades (Desc)":
|
|
84
|
+
filtered_patterns.sort(key=lambda p: p.get("n_trades", 0), reverse=True)
|
|
85
|
+
elif sort_by == "Confidence (Desc)":
|
|
86
|
+
filtered_patterns.sort(key=lambda p: p.get("confidence") or 0, reverse=True)
|
|
87
|
+
elif sort_by == "Distinctiveness (Desc)":
|
|
88
|
+
filtered_patterns.sort(key=lambda p: p.get("distinctiveness") or 0, reverse=True)
|
|
89
|
+
|
|
90
|
+
# Display count
|
|
91
|
+
if len(filtered_patterns) < len(patterns):
|
|
92
|
+
st.caption(f"Showing {len(filtered_patterns)} of {len(patterns)} patterns")
|
|
93
|
+
|
|
94
|
+
# Display patterns
|
|
95
|
+
for i, pattern in enumerate(filtered_patterns):
|
|
96
|
+
_render_pattern_card(st, pattern, expanded=(i == 0))
|
|
97
|
+
|
|
98
|
+
# Summary statistics
|
|
99
|
+
st.divider()
|
|
100
|
+
_render_pattern_summary(st, patterns, filtered_patterns)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _render_pattern_card(st: Any, pattern: dict[str, Any], expanded: bool = False) -> None:
|
|
104
|
+
"""Render a single pattern card."""
|
|
105
|
+
cluster_id = pattern.get("cluster_id", 0)
|
|
106
|
+
n_trades = pattern.get("n_trades", 0)
|
|
107
|
+
description = pattern.get("description", "No description")
|
|
108
|
+
hypothesis = pattern.get("hypothesis")
|
|
109
|
+
actions = pattern.get("actions", [])
|
|
110
|
+
confidence = pattern.get("confidence")
|
|
111
|
+
separation_score = pattern.get("separation_score")
|
|
112
|
+
distinctiveness = pattern.get("distinctiveness")
|
|
113
|
+
top_features = pattern.get("top_features", [])
|
|
114
|
+
|
|
115
|
+
with st.expander(
|
|
116
|
+
f"**Pattern {cluster_id}**: {n_trades} trades - {description}",
|
|
117
|
+
expanded=expanded,
|
|
118
|
+
):
|
|
119
|
+
# Metrics row
|
|
120
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
121
|
+
|
|
122
|
+
with col1:
|
|
123
|
+
st.metric("Trades", n_trades)
|
|
124
|
+
|
|
125
|
+
with col2:
|
|
126
|
+
if confidence is not None:
|
|
127
|
+
st.metric("Confidence", f"{confidence:.1%}")
|
|
128
|
+
else:
|
|
129
|
+
st.metric("Confidence", "N/A")
|
|
130
|
+
|
|
131
|
+
with col3:
|
|
132
|
+
if separation_score is not None:
|
|
133
|
+
st.metric(
|
|
134
|
+
"Separation",
|
|
135
|
+
f"{separation_score:.2f}",
|
|
136
|
+
help="Distance to nearest cluster (higher = more distinct)",
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
st.metric("Separation", "N/A")
|
|
140
|
+
|
|
141
|
+
with col4:
|
|
142
|
+
if distinctiveness is not None:
|
|
143
|
+
st.metric(
|
|
144
|
+
"Distinctiveness",
|
|
145
|
+
f"{distinctiveness:.2f}",
|
|
146
|
+
help="Uniqueness of SHAP profile (higher = more unique)",
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
st.metric("Distinctiveness", "N/A")
|
|
150
|
+
|
|
151
|
+
st.divider()
|
|
152
|
+
|
|
153
|
+
# Hypothesis
|
|
154
|
+
st.markdown("### Hypothesis")
|
|
155
|
+
if hypothesis:
|
|
156
|
+
st.markdown(f"> {hypothesis}")
|
|
157
|
+
else:
|
|
158
|
+
st.markdown("*No hypothesis generated for this pattern.*")
|
|
159
|
+
|
|
160
|
+
st.divider()
|
|
161
|
+
|
|
162
|
+
# Actions
|
|
163
|
+
st.markdown("### Recommended Actions")
|
|
164
|
+
if actions:
|
|
165
|
+
for idx, action in enumerate(actions, 1):
|
|
166
|
+
st.markdown(f"{idx}. {action}")
|
|
167
|
+
else:
|
|
168
|
+
st.markdown("*No actions suggested for this pattern.*")
|
|
169
|
+
|
|
170
|
+
# Top features with FDR correction
|
|
171
|
+
if top_features:
|
|
172
|
+
st.divider()
|
|
173
|
+
_render_pattern_features(st, top_features)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _render_pattern_features(st: Any, top_features: list[Any]) -> None:
|
|
177
|
+
"""Render pattern features with BH-FDR correction."""
|
|
178
|
+
st.markdown("### Key Features")
|
|
179
|
+
|
|
180
|
+
# Apply BH-FDR correction to p-values
|
|
181
|
+
features_with_fdr = _apply_fdr_correction(top_features)
|
|
182
|
+
|
|
183
|
+
feature_names = [f["feature"] for f in features_with_fdr[:10]]
|
|
184
|
+
feature_shap = [f["shap_value"] for f in features_with_fdr[:10]]
|
|
185
|
+
feature_sig = [f["significant_fdr"] for f in features_with_fdr[:10]]
|
|
186
|
+
|
|
187
|
+
# Create visualization
|
|
188
|
+
import plotly.graph_objects as go
|
|
189
|
+
|
|
190
|
+
colors = ["#51CF66" if sig else "#ADB5BD" for sig in feature_sig]
|
|
191
|
+
|
|
192
|
+
fig = go.Figure()
|
|
193
|
+
|
|
194
|
+
fig.add_trace(
|
|
195
|
+
go.Bar(
|
|
196
|
+
x=feature_shap,
|
|
197
|
+
y=feature_names,
|
|
198
|
+
orientation="h",
|
|
199
|
+
marker={"color": colors},
|
|
200
|
+
text=[f"{val:.4f}" for val in feature_shap],
|
|
201
|
+
textposition="auto",
|
|
202
|
+
hovertemplate="<b>%{y}</b><br>Mean SHAP: %{x:.4f}<extra></extra>",
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
fig.update_layout(
|
|
207
|
+
title="Top Features for This Pattern (FDR-corrected significance)",
|
|
208
|
+
xaxis_title="Mean SHAP Value",
|
|
209
|
+
yaxis_title="Feature",
|
|
210
|
+
height=max(300, len(feature_names) * 30),
|
|
211
|
+
yaxis={"autorange": "reversed"},
|
|
212
|
+
showlegend=False,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
216
|
+
|
|
217
|
+
# Features table with FDR info
|
|
218
|
+
features_df = pd.DataFrame(
|
|
219
|
+
[
|
|
220
|
+
{
|
|
221
|
+
"Feature": f["feature"],
|
|
222
|
+
"Mean SHAP": f"{f['shap_value']:.4f}",
|
|
223
|
+
"P-value": f"{f['p_value']:.4f}" if f["p_value"] is not None else "N/A",
|
|
224
|
+
"Adj. P-value (BH)": f"{f['adjusted_p']:.4f}"
|
|
225
|
+
if f["adjusted_p"] is not None
|
|
226
|
+
else "N/A",
|
|
227
|
+
"Significant": "Yes" if f["significant_fdr"] else "No",
|
|
228
|
+
}
|
|
229
|
+
for f in features_with_fdr[:10]
|
|
230
|
+
]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
st.dataframe(features_df, hide_index=True, use_container_width=True)
|
|
234
|
+
|
|
235
|
+
with st.expander("Understanding Feature Significance (FDR-corrected)"):
|
|
236
|
+
st.markdown(
|
|
237
|
+
"""
|
|
238
|
+
**Benjamini-Hochberg FDR Correction:**
|
|
239
|
+
|
|
240
|
+
Raw p-values are corrected for multiple comparisons using the
|
|
241
|
+
Benjamini-Hochberg procedure, which controls the False Discovery Rate.
|
|
242
|
+
|
|
243
|
+
- **P-value**: Raw p-value from statistical test
|
|
244
|
+
- **Adj. P-value (BH)**: FDR-adjusted p-value
|
|
245
|
+
- **Significant**: Yes if adjusted p-value < 0.05
|
|
246
|
+
|
|
247
|
+
**Interpretation:**
|
|
248
|
+
- Green bars: Statistically significant after FDR correction
|
|
249
|
+
- Gray bars: Not significant (may be noise)
|
|
250
|
+
|
|
251
|
+
Focus on significant features for most reliable insights.
|
|
252
|
+
"""
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _apply_fdr_correction(top_features: list[Any]) -> list[dict[str, Any]]:
|
|
257
|
+
"""Apply Benjamini-Hochberg FDR correction to feature p-values.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
top_features : list
|
|
262
|
+
List of feature tuples. Expected formats:
|
|
263
|
+
- (name, shap_value)
|
|
264
|
+
- (name, shap_value, p_value)
|
|
265
|
+
- (name, shap_value, p_value_t, p_value_mw, significant)
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
list[dict]
|
|
270
|
+
Features with FDR-corrected significance.
|
|
271
|
+
"""
|
|
272
|
+
from ml4t.diagnostic.evaluation.trade_dashboard.stats import benjamini_hochberg_fdr
|
|
273
|
+
|
|
274
|
+
# Parse features into consistent format
|
|
275
|
+
parsed = []
|
|
276
|
+
for item in top_features:
|
|
277
|
+
if len(item) >= 2:
|
|
278
|
+
feature = item[0]
|
|
279
|
+
shap_val = item[1]
|
|
280
|
+
# Use t-test p-value if available (index 2), otherwise None
|
|
281
|
+
p_value = item[2] if len(item) > 2 else None
|
|
282
|
+
parsed.append(
|
|
283
|
+
{
|
|
284
|
+
"feature": feature,
|
|
285
|
+
"shap_value": shap_val,
|
|
286
|
+
"p_value": p_value,
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if not parsed:
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
# Extract p-values for FDR correction
|
|
294
|
+
p_values = [f["p_value"] for f in parsed if f["p_value"] is not None]
|
|
295
|
+
|
|
296
|
+
if len(p_values) >= 2:
|
|
297
|
+
# Apply BH-FDR correction
|
|
298
|
+
fdr_result = benjamini_hochberg_fdr(p_values, alpha=0.05)
|
|
299
|
+
adjusted_p_values = fdr_result["adjusted_p_values"]
|
|
300
|
+
rejected = fdr_result["rejected"]
|
|
301
|
+
|
|
302
|
+
# Map back to features
|
|
303
|
+
p_idx = 0
|
|
304
|
+
for f in parsed:
|
|
305
|
+
if f["p_value"] is not None:
|
|
306
|
+
f["adjusted_p"] = adjusted_p_values[p_idx]
|
|
307
|
+
f["significant_fdr"] = rejected[p_idx]
|
|
308
|
+
p_idx += 1
|
|
309
|
+
else:
|
|
310
|
+
f["adjusted_p"] = None
|
|
311
|
+
f["significant_fdr"] = False
|
|
312
|
+
else:
|
|
313
|
+
# Not enough p-values for FDR
|
|
314
|
+
for f in parsed:
|
|
315
|
+
f["adjusted_p"] = f["p_value"]
|
|
316
|
+
f["significant_fdr"] = f["p_value"] is not None and f["p_value"] < 0.05
|
|
317
|
+
|
|
318
|
+
return parsed
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _render_pattern_summary(
|
|
322
|
+
st: Any,
|
|
323
|
+
all_patterns: list[dict[str, Any]],
|
|
324
|
+
filtered_patterns: list[dict[str, Any]],
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Render pattern summary statistics."""
|
|
327
|
+
st.subheader("Pattern Summary")
|
|
328
|
+
|
|
329
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
330
|
+
|
|
331
|
+
with col1:
|
|
332
|
+
st.metric("Total Patterns", len(all_patterns))
|
|
333
|
+
if len(filtered_patterns) < len(all_patterns):
|
|
334
|
+
st.caption(f"({len(filtered_patterns)} shown)")
|
|
335
|
+
|
|
336
|
+
with col2:
|
|
337
|
+
patterns_with_hypotheses = sum(1 for p in all_patterns if p.get("hypothesis"))
|
|
338
|
+
st.metric("With Hypotheses", patterns_with_hypotheses)
|
|
339
|
+
st.caption(f"{patterns_with_hypotheses}/{len(all_patterns)} patterns")
|
|
340
|
+
|
|
341
|
+
with col3:
|
|
342
|
+
total_trades = sum(p.get("n_trades", 0) for p in all_patterns)
|
|
343
|
+
st.metric("Total Trades", total_trades)
|
|
344
|
+
|
|
345
|
+
with col4:
|
|
346
|
+
avg_confidence = [
|
|
347
|
+
float(p["confidence"]) for p in all_patterns if p.get("confidence") is not None
|
|
348
|
+
]
|
|
349
|
+
if avg_confidence:
|
|
350
|
+
import numpy as np
|
|
351
|
+
|
|
352
|
+
st.metric("Avg Confidence", f"{float(np.mean(avg_confidence)):.1%}")
|
|
353
|
+
else:
|
|
354
|
+
st.metric("Avg Confidence", "N/A")
|