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,305 @@
|
|
|
1
|
+
"""Time-to-target analysis results for barrier outcomes.
|
|
2
|
+
|
|
3
|
+
This module provides the TimeToTargetResult class for storing time-to-target
|
|
4
|
+
metrics (mean, median, std bars to TP/SL/timeout) by signal quantile.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import polars as pl
|
|
10
|
+
from pydantic import Field, model_validator
|
|
11
|
+
|
|
12
|
+
from ml4t.diagnostic.results.barrier_results.validation import _validate_quantile_dict_keys
|
|
13
|
+
from ml4t.diagnostic.results.base import BaseResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TimeToTargetResult(BaseResult):
|
|
17
|
+
"""Results from time-to-target analysis by signal decile.
|
|
18
|
+
|
|
19
|
+
Analyzes how quickly different signal quantiles reach their barrier
|
|
20
|
+
outcomes (TP, SL, or timeout).
|
|
21
|
+
|
|
22
|
+
Examples
|
|
23
|
+
--------
|
|
24
|
+
>>> result = time_to_target_result
|
|
25
|
+
>>> print(result.summary())
|
|
26
|
+
>>> df = result.get_dataframe()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
analysis_type: str = Field(default="barrier_time_to_target", frozen=True)
|
|
30
|
+
|
|
31
|
+
# ==========================================================================
|
|
32
|
+
# Configuration
|
|
33
|
+
# ==========================================================================
|
|
34
|
+
|
|
35
|
+
n_quantiles: int = Field(
|
|
36
|
+
...,
|
|
37
|
+
description="Number of quantiles used",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
quantile_labels: list[str] = Field(
|
|
41
|
+
...,
|
|
42
|
+
description="Labels for each quantile (e.g., ['D1', 'D2', ..., 'D10'])",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ==========================================================================
|
|
46
|
+
# Mean Time to Exit by Quantile and Outcome
|
|
47
|
+
# ==========================================================================
|
|
48
|
+
|
|
49
|
+
mean_bars_tp: dict[str, float] = Field(
|
|
50
|
+
...,
|
|
51
|
+
description="Mean bars to TP per quantile",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
mean_bars_sl: dict[str, float] = Field(
|
|
55
|
+
...,
|
|
56
|
+
description="Mean bars to SL per quantile",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
mean_bars_timeout: dict[str, float] = Field(
|
|
60
|
+
...,
|
|
61
|
+
description="Mean bars to timeout per quantile",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
mean_bars_all: dict[str, float] = Field(
|
|
65
|
+
...,
|
|
66
|
+
description="Mean bars to any exit per quantile",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# ==========================================================================
|
|
70
|
+
# Median Time to Exit
|
|
71
|
+
# ==========================================================================
|
|
72
|
+
|
|
73
|
+
median_bars_tp: dict[str, float] = Field(
|
|
74
|
+
...,
|
|
75
|
+
description="Median bars to TP per quantile",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
median_bars_sl: dict[str, float] = Field(
|
|
79
|
+
...,
|
|
80
|
+
description="Median bars to SL per quantile",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
median_bars_all: dict[str, float] = Field(
|
|
84
|
+
...,
|
|
85
|
+
description="Median bars to any exit per quantile",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# ==========================================================================
|
|
89
|
+
# Standard Deviation
|
|
90
|
+
# ==========================================================================
|
|
91
|
+
|
|
92
|
+
std_bars_tp: dict[str, float] = Field(
|
|
93
|
+
...,
|
|
94
|
+
description="Std dev of bars to TP per quantile",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
std_bars_sl: dict[str, float] = Field(
|
|
98
|
+
...,
|
|
99
|
+
description="Std dev of bars to SL per quantile",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
std_bars_all: dict[str, float] = Field(
|
|
103
|
+
...,
|
|
104
|
+
description="Std dev of bars to any exit per quantile",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# ==========================================================================
|
|
108
|
+
# Counts
|
|
109
|
+
# ==========================================================================
|
|
110
|
+
|
|
111
|
+
count_tp: dict[str, int] = Field(
|
|
112
|
+
...,
|
|
113
|
+
description="Number of TP outcomes per quantile",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
count_sl: dict[str, int] = Field(
|
|
117
|
+
...,
|
|
118
|
+
description="Number of SL outcomes per quantile",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
count_timeout: dict[str, int] = Field(
|
|
122
|
+
...,
|
|
123
|
+
description="Number of timeout outcomes per quantile",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# ==========================================================================
|
|
127
|
+
# Overall Statistics
|
|
128
|
+
# ==========================================================================
|
|
129
|
+
|
|
130
|
+
overall_mean_bars: float = Field(
|
|
131
|
+
...,
|
|
132
|
+
description="Overall mean bars to exit",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
overall_median_bars: float = Field(
|
|
136
|
+
...,
|
|
137
|
+
description="Overall median bars to exit",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
overall_mean_bars_tp: float = Field(
|
|
141
|
+
...,
|
|
142
|
+
description="Overall mean bars to TP",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
overall_mean_bars_sl: float = Field(
|
|
146
|
+
...,
|
|
147
|
+
description="Overall mean bars to SL",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
n_observations: int = Field(
|
|
151
|
+
...,
|
|
152
|
+
description="Total number of observations",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# ==========================================================================
|
|
156
|
+
# Speed Analysis
|
|
157
|
+
# ==========================================================================
|
|
158
|
+
|
|
159
|
+
tp_faster_than_sl: dict[str, bool] = Field(
|
|
160
|
+
...,
|
|
161
|
+
description="Whether TP is reached faster than SL on average per quantile",
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
speed_advantage_tp: dict[str, float] = Field(
|
|
165
|
+
...,
|
|
166
|
+
description="Speed advantage of TP over SL (positive = TP faster) per quantile",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# ==========================================================================
|
|
170
|
+
# Validation
|
|
171
|
+
# ==========================================================================
|
|
172
|
+
|
|
173
|
+
@model_validator(mode="after")
|
|
174
|
+
def _validate_quantile_keys(self) -> TimeToTargetResult:
|
|
175
|
+
"""Validate that all quantile-keyed dicts have consistent keys."""
|
|
176
|
+
if self.n_quantiles != len(self.quantile_labels):
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"n_quantiles ({self.n_quantiles}) != len(quantile_labels) ({len(self.quantile_labels)})"
|
|
179
|
+
)
|
|
180
|
+
_validate_quantile_dict_keys(
|
|
181
|
+
self.quantile_labels,
|
|
182
|
+
[
|
|
183
|
+
("mean_bars_tp", self.mean_bars_tp),
|
|
184
|
+
("mean_bars_sl", self.mean_bars_sl),
|
|
185
|
+
("mean_bars_timeout", self.mean_bars_timeout),
|
|
186
|
+
("mean_bars_all", self.mean_bars_all),
|
|
187
|
+
("median_bars_tp", self.median_bars_tp),
|
|
188
|
+
("median_bars_sl", self.median_bars_sl),
|
|
189
|
+
("median_bars_all", self.median_bars_all),
|
|
190
|
+
("std_bars_tp", self.std_bars_tp),
|
|
191
|
+
("std_bars_sl", self.std_bars_sl),
|
|
192
|
+
("std_bars_all", self.std_bars_all),
|
|
193
|
+
("count_tp", self.count_tp),
|
|
194
|
+
("count_sl", self.count_sl),
|
|
195
|
+
("count_timeout", self.count_timeout),
|
|
196
|
+
("tp_faster_than_sl", self.tp_faster_than_sl),
|
|
197
|
+
("speed_advantage_tp", self.speed_advantage_tp),
|
|
198
|
+
],
|
|
199
|
+
)
|
|
200
|
+
return self
|
|
201
|
+
|
|
202
|
+
def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
|
|
203
|
+
"""Get results as Polars DataFrame.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
name : str | None
|
|
208
|
+
DataFrame to retrieve:
|
|
209
|
+
- None or "time_to_target": Mean times by quantile and outcome
|
|
210
|
+
- "detailed": Full statistics including median and std
|
|
211
|
+
- "summary": Overall statistics
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
pl.DataFrame
|
|
216
|
+
Requested DataFrame
|
|
217
|
+
"""
|
|
218
|
+
if name is None or name == "time_to_target":
|
|
219
|
+
return pl.DataFrame(
|
|
220
|
+
{
|
|
221
|
+
"quantile": self.quantile_labels,
|
|
222
|
+
"mean_bars_tp": [self.mean_bars_tp[q] for q in self.quantile_labels],
|
|
223
|
+
"mean_bars_sl": [self.mean_bars_sl[q] for q in self.quantile_labels],
|
|
224
|
+
"mean_bars_timeout": [self.mean_bars_timeout[q] for q in self.quantile_labels],
|
|
225
|
+
"mean_bars_all": [self.mean_bars_all[q] for q in self.quantile_labels],
|
|
226
|
+
"tp_faster": [self.tp_faster_than_sl[q] for q in self.quantile_labels],
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
if name == "detailed":
|
|
231
|
+
return pl.DataFrame(
|
|
232
|
+
{
|
|
233
|
+
"quantile": self.quantile_labels,
|
|
234
|
+
"mean_bars_tp": [self.mean_bars_tp[q] for q in self.quantile_labels],
|
|
235
|
+
"median_bars_tp": [self.median_bars_tp[q] for q in self.quantile_labels],
|
|
236
|
+
"std_bars_tp": [self.std_bars_tp[q] for q in self.quantile_labels],
|
|
237
|
+
"mean_bars_sl": [self.mean_bars_sl[q] for q in self.quantile_labels],
|
|
238
|
+
"median_bars_sl": [self.median_bars_sl[q] for q in self.quantile_labels],
|
|
239
|
+
"std_bars_sl": [self.std_bars_sl[q] for q in self.quantile_labels],
|
|
240
|
+
"count_tp": [self.count_tp[q] for q in self.quantile_labels],
|
|
241
|
+
"count_sl": [self.count_sl[q] for q in self.quantile_labels],
|
|
242
|
+
"count_timeout": [self.count_timeout[q] for q in self.quantile_labels],
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if name == "summary":
|
|
247
|
+
return pl.DataFrame(
|
|
248
|
+
{
|
|
249
|
+
"metric": [
|
|
250
|
+
"n_observations",
|
|
251
|
+
"n_quantiles",
|
|
252
|
+
"overall_mean_bars",
|
|
253
|
+
"overall_median_bars",
|
|
254
|
+
"overall_mean_bars_tp",
|
|
255
|
+
"overall_mean_bars_sl",
|
|
256
|
+
],
|
|
257
|
+
"value": [
|
|
258
|
+
float(self.n_observations),
|
|
259
|
+
float(self.n_quantiles),
|
|
260
|
+
self.overall_mean_bars,
|
|
261
|
+
self.overall_median_bars,
|
|
262
|
+
self.overall_mean_bars_tp,
|
|
263
|
+
self.overall_mean_bars_sl,
|
|
264
|
+
],
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
raise ValueError(
|
|
269
|
+
f"Unknown DataFrame name: {name}. Available: 'time_to_target', 'detailed', 'summary'"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def list_available_dataframes(self) -> list[str]:
|
|
273
|
+
"""List available DataFrame views."""
|
|
274
|
+
return ["time_to_target", "detailed", "summary"]
|
|
275
|
+
|
|
276
|
+
def summary(self) -> str:
|
|
277
|
+
"""Get human-readable summary of time-to-target results."""
|
|
278
|
+
lines = [
|
|
279
|
+
"=" * 60,
|
|
280
|
+
"Barrier Time-to-Target Analysis",
|
|
281
|
+
"=" * 60,
|
|
282
|
+
"",
|
|
283
|
+
f"Observations: {self.n_observations:>10,}",
|
|
284
|
+
f"Quantiles: {self.n_quantiles:>10}",
|
|
285
|
+
"",
|
|
286
|
+
"Overall Time to Exit:",
|
|
287
|
+
f" Mean Bars: {self.overall_mean_bars:>10.1f}",
|
|
288
|
+
f" Median Bars: {self.overall_median_bars:>10.1f}",
|
|
289
|
+
f" Mean Bars (TP): {self.overall_mean_bars_tp:>10.1f}",
|
|
290
|
+
f" Mean Bars (SL): {self.overall_mean_bars_sl:>10.1f}",
|
|
291
|
+
"",
|
|
292
|
+
"-" * 60,
|
|
293
|
+
"Mean Bars to Exit by Quantile:",
|
|
294
|
+
"-" * 60,
|
|
295
|
+
f"{'Quantile':<10} {'TP':>8} {'SL':>8} {'Timeout':>8} {'All':>8} {'TP Faster?':>12}",
|
|
296
|
+
]
|
|
297
|
+
|
|
298
|
+
for q in self.quantile_labels:
|
|
299
|
+
tp_faster = "Yes" if self.tp_faster_than_sl[q] else "No"
|
|
300
|
+
lines.append(
|
|
301
|
+
f"{q:<10} {self.mean_bars_tp[q]:>8.1f} {self.mean_bars_sl[q]:>8.1f} "
|
|
302
|
+
f"{self.mean_bars_timeout[q]:>8.1f} {self.mean_bars_all[q]:>8.1f} {tp_faster:>12}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Validation helper functions for barrier analysis results.
|
|
2
|
+
|
|
3
|
+
This module provides validation utilities for quantile-keyed dictionaries
|
|
4
|
+
used across all barrier result classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _validate_quantile_dict_keys(
|
|
13
|
+
quantile_labels: list[str],
|
|
14
|
+
dicts: list[tuple[str, dict[str, Any]]],
|
|
15
|
+
) -> None:
|
|
16
|
+
"""Validate that all quantile-keyed dicts have the expected keys.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
quantile_labels : list[str]
|
|
21
|
+
Expected quantile labels (keys).
|
|
22
|
+
dicts : list[tuple[str, dict]]
|
|
23
|
+
List of (field_name, dict) tuples to validate.
|
|
24
|
+
|
|
25
|
+
Raises
|
|
26
|
+
------
|
|
27
|
+
ValueError
|
|
28
|
+
If any dict has different keys than quantile_labels.
|
|
29
|
+
"""
|
|
30
|
+
expected_keys = set(quantile_labels)
|
|
31
|
+
for name, d in dicts:
|
|
32
|
+
actual_keys = set(d.keys())
|
|
33
|
+
if actual_keys != expected_keys:
|
|
34
|
+
missing = expected_keys - actual_keys
|
|
35
|
+
extra = actual_keys - expected_keys
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"Key mismatch in '{name}': missing={missing or 'none'}, extra={extra or 'none'}"
|
|
38
|
+
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Base result class with common functionality for all evaluation results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import polars as pl
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseResult(BaseModel):
|
|
13
|
+
"""Base class for all evaluation results.
|
|
14
|
+
|
|
15
|
+
Provides common functionality:
|
|
16
|
+
- Metadata (timestamp, version, analysis type)
|
|
17
|
+
- JSON export via model_dump_json()
|
|
18
|
+
- Dict export via to_dict()
|
|
19
|
+
- DataFrame conversion via get_dataframe()
|
|
20
|
+
- Human-readable summary via summary()
|
|
21
|
+
|
|
22
|
+
All result schemas inherit from this class to ensure consistent behavior.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> result = FeatureDiagnosticsResult(...)
|
|
26
|
+
>>> json_str = result.model_dump_json(indent=2)
|
|
27
|
+
>>> df = result.get_dataframe()
|
|
28
|
+
>>> available = result.list_available_dataframes()
|
|
29
|
+
>>> print(result.summary())
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(
|
|
33
|
+
extra="forbid", # Catch typos
|
|
34
|
+
validate_assignment=True, # Validate on mutation
|
|
35
|
+
arbitrary_types_allowed=True, # Allow Polars types if needed
|
|
36
|
+
use_enum_values=True, # Serialize enums as values
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Metadata fields - present in all results
|
|
40
|
+
created_at: str = Field(
|
|
41
|
+
default_factory=lambda: datetime.now(UTC).isoformat(),
|
|
42
|
+
description="ISO timestamp of result creation (UTC)",
|
|
43
|
+
)
|
|
44
|
+
analysis_type: str = Field(
|
|
45
|
+
...,
|
|
46
|
+
description="Type of analysis performed (e.g., 'feature_diagnostics')",
|
|
47
|
+
)
|
|
48
|
+
version: str = Field(
|
|
49
|
+
default="2.0.0",
|
|
50
|
+
description="ML4T Diagnostic version used to generate results",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def to_dict(self, *, exclude_none: bool = False) -> dict[str, Any]:
|
|
54
|
+
"""Export to Python dictionary.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
exclude_none: Exclude fields with None values
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary representation of result
|
|
61
|
+
"""
|
|
62
|
+
return self.model_dump(exclude_none=exclude_none, mode="python")
|
|
63
|
+
|
|
64
|
+
def to_json_string(self, *, indent: int | None = 2) -> str:
|
|
65
|
+
"""Export to JSON string.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
indent: Indentation level (None for compact)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
JSON string representation
|
|
72
|
+
"""
|
|
73
|
+
return self.model_dump_json(indent=indent)
|
|
74
|
+
|
|
75
|
+
def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
|
|
76
|
+
"""Get results as Polars DataFrame.
|
|
77
|
+
|
|
78
|
+
Provides programmatic access to underlying data for QEngine storage
|
|
79
|
+
and further analysis. Subclasses should override to provide specific
|
|
80
|
+
DataFrame views.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: Optional DataFrame name to retrieve specific view.
|
|
84
|
+
If None, returns primary/default DataFrame.
|
|
85
|
+
Use list_available_dataframes() to see available names.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Polars DataFrame with results
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
NotImplementedError: If not implemented by subclass
|
|
92
|
+
ValueError: If requested DataFrame name not available
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
>>> result = FeatureDiagnosticsResult(...)
|
|
96
|
+
>>> df = result.get_dataframe() # Primary DataFrame
|
|
97
|
+
>>> df = result.get_dataframe("stationarity") # Specific view
|
|
98
|
+
>>> available = result.list_available_dataframes()
|
|
99
|
+
>>> for name in available:
|
|
100
|
+
... df = result.get_dataframe(name)
|
|
101
|
+
"""
|
|
102
|
+
raise NotImplementedError(f"{self.__class__.__name__} must implement get_dataframe()")
|
|
103
|
+
|
|
104
|
+
def list_available_dataframes(self) -> list[str]:
|
|
105
|
+
"""List available DataFrame views for this result.
|
|
106
|
+
|
|
107
|
+
Returns names that can be passed to get_dataframe() to retrieve
|
|
108
|
+
specific data views. Useful for discovery and QEngine integration.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of available DataFrame names
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
>>> result.list_available_dataframes()
|
|
115
|
+
['primary', 'stationarity', 'autocorrelation', 'volatility']
|
|
116
|
+
"""
|
|
117
|
+
# Default implementation - subclasses should override
|
|
118
|
+
return ["primary"]
|
|
119
|
+
|
|
120
|
+
def get_dataframe_schema(self, name: str | None = None) -> dict[str, str]:
|
|
121
|
+
"""Get schema information for a DataFrame.
|
|
122
|
+
|
|
123
|
+
Returns column names and types for a DataFrame without loading data.
|
|
124
|
+
Useful for QEngine to understand data structure before retrieval.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
name: DataFrame name (None for primary)
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dictionary mapping column names to Polars dtype strings
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
>>> result.get_dataframe_schema("stationarity")
|
|
134
|
+
{
|
|
135
|
+
'feature': 'String',
|
|
136
|
+
'adf_pvalue': 'Float64',
|
|
137
|
+
'is_stationary': 'Boolean'
|
|
138
|
+
}
|
|
139
|
+
"""
|
|
140
|
+
# Get actual DataFrame and extract schema
|
|
141
|
+
df = self.get_dataframe(name)
|
|
142
|
+
return {col: str(dtype) for col, dtype in zip(df.columns, df.dtypes, strict=False)}
|
|
143
|
+
|
|
144
|
+
def summary(self) -> str:
|
|
145
|
+
"""Get human-readable summary of results.
|
|
146
|
+
|
|
147
|
+
This method should be overridden by subclasses to provide
|
|
148
|
+
meaningful summaries of their specific data.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Formatted summary string
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
NotImplementedError: If not implemented by subclass
|
|
155
|
+
"""
|
|
156
|
+
raise NotImplementedError(f"{self.__class__.__name__} must implement summary()")
|
|
157
|
+
|
|
158
|
+
def interpret(self) -> list[str]:
|
|
159
|
+
"""Get human-readable interpretation of results.
|
|
160
|
+
|
|
161
|
+
Returns actionable insights and recommendations based on
|
|
162
|
+
the analysis results. Default implementation returns empty list.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of interpretation strings with insights and recommendations
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
>>> result.interpret()
|
|
169
|
+
['Strategy is statistically significant (DSR=98.2% > 95%)',
|
|
170
|
+
'Recommendation: Strategy shows robust performance']
|
|
171
|
+
"""
|
|
172
|
+
# Default implementation - subclasses can override
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
def __repr__(self) -> str:
|
|
176
|
+
"""Concise representation showing type and timestamp."""
|
|
177
|
+
return f"{self.__class__.__name__}(analysis_type={self.analysis_type!r}, created_at={self.created_at!r})"
|