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,301 @@
|
|
|
1
|
+
"""Base configuration classes and shared utilities.
|
|
2
|
+
|
|
3
|
+
This module provides foundation classes used throughout the config system:
|
|
4
|
+
- BaseConfig: Serialization, validation utilities, comparison
|
|
5
|
+
- StatisticalTestConfig: Base for all statistical tests
|
|
6
|
+
- RuntimeConfig: Execution settings (n_jobs, caching, verbosity)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseConfig(BaseModel):
|
|
20
|
+
"""Base configuration class with serialization and comparison utilities.
|
|
21
|
+
|
|
22
|
+
All ML4T Diagnostic configs inherit from this class to get consistent behavior
|
|
23
|
+
for serialization, validation, and comparison.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> class MyConfig(BaseConfig):
|
|
27
|
+
... value: int = 42
|
|
28
|
+
>>> config = MyConfig()
|
|
29
|
+
>>> config.to_yaml("config.yaml")
|
|
30
|
+
>>> loaded = MyConfig.from_yaml("config.yaml")
|
|
31
|
+
>>> assert config == loaded
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(
|
|
35
|
+
extra="forbid", # Catch typos in field names
|
|
36
|
+
validate_assignment=True, # Validate on attribute assignment
|
|
37
|
+
arbitrary_types_allowed=True, # Allow Path, etc.
|
|
38
|
+
use_enum_values=True, # Serialize enums as values
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def to_dict(self, *, exclude_none: bool = False, mode: str = "python") -> dict[str, Any]:
|
|
42
|
+
"""Convert config to dictionary.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
exclude_none: Exclude fields with None values
|
|
46
|
+
mode: "python" for Python objects, "json" for JSON-serializable
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary representation of config
|
|
50
|
+
"""
|
|
51
|
+
return self.model_dump(exclude_none=exclude_none, mode=mode)
|
|
52
|
+
|
|
53
|
+
def to_json(self, file_path: str | Path, *, indent: int = 2) -> None:
|
|
54
|
+
"""Save config to JSON file.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
file_path: Output file path
|
|
58
|
+
indent: JSON indentation (default 2)
|
|
59
|
+
"""
|
|
60
|
+
path = Path(file_path)
|
|
61
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
with path.open("w") as f:
|
|
64
|
+
json.dump(self.to_dict(mode="json"), f, indent=indent)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def from_json(cls, file_path: str | Path) -> BaseConfig:
|
|
68
|
+
"""Load config from JSON file.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
file_path: Input file path
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Config instance
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
FileNotFoundError: If file doesn't exist
|
|
78
|
+
ValueError: If JSON is invalid
|
|
79
|
+
"""
|
|
80
|
+
path = Path(file_path)
|
|
81
|
+
with path.open() as f:
|
|
82
|
+
data = json.load(f)
|
|
83
|
+
return cls(**data)
|
|
84
|
+
|
|
85
|
+
def to_yaml(self, file_path: str | Path) -> None:
|
|
86
|
+
"""Save config to YAML file.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
file_path: Output file path
|
|
90
|
+
"""
|
|
91
|
+
path = Path(file_path)
|
|
92
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
|
|
94
|
+
with path.open("w") as f:
|
|
95
|
+
yaml.dump(self.to_dict(mode="json"), f, default_flow_style=False, sort_keys=False)
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def from_yaml(cls, file_path: str | Path) -> BaseConfig:
|
|
99
|
+
"""Load config from YAML file.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
file_path: Input file path
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Config instance
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
FileNotFoundError: If file doesn't exist
|
|
109
|
+
ValueError: If YAML is invalid
|
|
110
|
+
"""
|
|
111
|
+
path = Path(file_path)
|
|
112
|
+
with path.open() as f:
|
|
113
|
+
data = yaml.safe_load(f)
|
|
114
|
+
return cls(**data)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_dict(cls, data: dict[str, Any]) -> BaseConfig:
|
|
118
|
+
"""Load config from dictionary with validation.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
data: Dictionary representation of config
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Config instance
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ValidationError: If data is invalid
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
>>> config = MyConfig.from_dict({"value": 42})
|
|
131
|
+
>>> assert config.value == 42
|
|
132
|
+
"""
|
|
133
|
+
return cls(**data)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_file(cls, file_path: str | Path) -> BaseConfig:
|
|
137
|
+
"""Auto-detect file type (YAML/JSON) and load config.
|
|
138
|
+
|
|
139
|
+
Detects file type based on extension (.yaml, .yml, .json).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
file_path: Input file path
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Config instance
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
FileNotFoundError: If file doesn't exist
|
|
149
|
+
ValueError: If file type is unsupported or content is invalid
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
>>> config = MyConfig.from_file("config.yaml")
|
|
153
|
+
>>> config = MyConfig.from_file("config.json")
|
|
154
|
+
"""
|
|
155
|
+
path = Path(file_path)
|
|
156
|
+
|
|
157
|
+
if not path.exists():
|
|
158
|
+
raise FileNotFoundError(f"Config file not found: {path}")
|
|
159
|
+
|
|
160
|
+
suffix = path.suffix.lower()
|
|
161
|
+
|
|
162
|
+
if suffix in (".yaml", ".yml"):
|
|
163
|
+
return cls.from_yaml(path)
|
|
164
|
+
elif suffix == ".json":
|
|
165
|
+
return cls.from_json(path)
|
|
166
|
+
else:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Unsupported file type: {suffix}. Supported types: .yaml, .yml, .json"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def validate_fully(self) -> list[str]:
|
|
172
|
+
"""Run all validators and return list of validation issues.
|
|
173
|
+
|
|
174
|
+
This method re-validates the config and collects any validation errors
|
|
175
|
+
or warnings. Useful for checking configuration validity after modifications.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of validation error messages (empty if valid)
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
>>> config = MyConfig(value=42)
|
|
182
|
+
>>> errors = config.validate_fully()
|
|
183
|
+
>>> if errors:
|
|
184
|
+
... print(f"Validation errors: {errors}")
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
# Trigger validation by creating a new instance with same data
|
|
188
|
+
self.model_validate(self.model_dump())
|
|
189
|
+
return []
|
|
190
|
+
except ValidationError as e:
|
|
191
|
+
# Parse validation errors from Pydantic
|
|
192
|
+
errors = []
|
|
193
|
+
for error in e.errors():
|
|
194
|
+
loc = ".".join(str(x) for x in error["loc"])
|
|
195
|
+
msg = error["msg"]
|
|
196
|
+
errors.append(f"{loc}: {msg}")
|
|
197
|
+
return errors
|
|
198
|
+
except Exception as e:
|
|
199
|
+
return [str(e)]
|
|
200
|
+
|
|
201
|
+
def diff(self, other: BaseConfig) -> dict[str, tuple[Any, Any]]:
|
|
202
|
+
"""Compare this config with another, returning differences.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
other: Config to compare against
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dictionary mapping field paths to (self_value, other_value) tuples
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
>>> config1 = MyConfig(value=42)
|
|
212
|
+
>>> config2 = MyConfig(value=100)
|
|
213
|
+
>>> config1.diff(config2)
|
|
214
|
+
{'value': (42, 100)}
|
|
215
|
+
"""
|
|
216
|
+
if type(self) is not type(other):
|
|
217
|
+
raise TypeError(f"Cannot compare {type(self)} with {type(other)}")
|
|
218
|
+
|
|
219
|
+
differences = {}
|
|
220
|
+
self_dict = self.to_dict()
|
|
221
|
+
other_dict = other.to_dict()
|
|
222
|
+
|
|
223
|
+
def _compare_nested(d1: dict, d2: dict, prefix: str = "") -> None:
|
|
224
|
+
"""Recursively compare nested dictionaries."""
|
|
225
|
+
all_keys = set(d1.keys()) | set(d2.keys())
|
|
226
|
+
for key in all_keys:
|
|
227
|
+
path = f"{prefix}.{key}" if prefix else key
|
|
228
|
+
v1, v2 = d1.get(key), d2.get(key)
|
|
229
|
+
|
|
230
|
+
if v1 != v2:
|
|
231
|
+
if isinstance(v1, dict) and isinstance(v2, dict):
|
|
232
|
+
_compare_nested(v1, v2, path)
|
|
233
|
+
else:
|
|
234
|
+
differences[path] = (v1, v2)
|
|
235
|
+
|
|
236
|
+
_compare_nested(self_dict, other_dict)
|
|
237
|
+
return differences
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class StatisticalTestConfig(BaseConfig):
|
|
241
|
+
"""Base configuration for statistical tests.
|
|
242
|
+
|
|
243
|
+
Provides common fields for hypothesis tests (significance level, etc.)
|
|
244
|
+
that are inherited by specific test configs.
|
|
245
|
+
|
|
246
|
+
Attributes:
|
|
247
|
+
enabled: Whether to run this test
|
|
248
|
+
significance_level: Significance level for hypothesis test (0.01, 0.05, or 0.10)
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
enabled: bool = Field(True, description="Whether to run this test")
|
|
252
|
+
significance_level: float = Field(
|
|
253
|
+
0.05,
|
|
254
|
+
ge=0.001,
|
|
255
|
+
le=0.10,
|
|
256
|
+
description="Significance level for hypothesis tests (common: 0.01, 0.05, 0.10)",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class RuntimeConfig(BaseConfig):
|
|
261
|
+
"""Configuration for execution settings.
|
|
262
|
+
|
|
263
|
+
Centralizes computational resources, caching, and randomness across all
|
|
264
|
+
evaluation functions. Pass as a separate parameter to analysis functions.
|
|
265
|
+
|
|
266
|
+
Attributes:
|
|
267
|
+
n_jobs: Number of parallel jobs (-1 for all cores, 1 for serial)
|
|
268
|
+
cache_enabled: Enable caching of expensive computations
|
|
269
|
+
cache_dir: Directory for cache storage
|
|
270
|
+
cache_ttl: Cache time-to-live in seconds (None for no expiration)
|
|
271
|
+
verbose: Enable verbose output
|
|
272
|
+
random_state: Random seed for reproducibility
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
>>> from ml4t.diagnostic.config import RuntimeConfig, DiagnosticConfig
|
|
276
|
+
>>> runtime = RuntimeConfig(n_jobs=4, verbose=True)
|
|
277
|
+
>>> result = analyze_features(df, config=DiagnosticConfig(), runtime=runtime)
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
n_jobs: int = Field(
|
|
281
|
+
-1,
|
|
282
|
+
ge=-1,
|
|
283
|
+
description="Number of parallel jobs (-1 for all cores, 1 for serial)",
|
|
284
|
+
)
|
|
285
|
+
cache_enabled: bool = Field(True, description="Enable caching of expensive computations")
|
|
286
|
+
cache_dir: Path = Field(
|
|
287
|
+
default_factory=lambda: Path.home() / ".cache" / "ml4t-diagnostic",
|
|
288
|
+
description="Directory for cache storage",
|
|
289
|
+
)
|
|
290
|
+
cache_ttl: int | None = Field(
|
|
291
|
+
None,
|
|
292
|
+
ge=0,
|
|
293
|
+
description="Cache time-to-live in seconds (None for no expiration)",
|
|
294
|
+
)
|
|
295
|
+
verbose: bool = Field(False, description="Enable verbose output")
|
|
296
|
+
random_state: int | None = Field(None, ge=0, description="Random seed for reproducibility")
|
|
297
|
+
|
|
298
|
+
def model_post_init(self, __context: Any) -> None:
|
|
299
|
+
"""Create cache directory if it doesn't exist."""
|
|
300
|
+
if self.cache_enabled:
|
|
301
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Event Study Configuration.
|
|
2
|
+
|
|
3
|
+
This module provides configuration for event study analysis following
|
|
4
|
+
MacKinlay (1997) "Event Studies in Economics and Finance".
|
|
5
|
+
|
|
6
|
+
Consolidated Config:
|
|
7
|
+
- EventConfig: Full configuration with window settings inlined
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import Field, field_validator
|
|
15
|
+
|
|
16
|
+
from ml4t.diagnostic.config.base import BaseConfig
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class WindowSettings(BaseConfig):
|
|
20
|
+
"""Settings for event study windows.
|
|
21
|
+
|
|
22
|
+
Defines the estimation window (for computing normal returns) and
|
|
23
|
+
the event window (for measuring abnormal returns).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
estimation_start: int = Field(
|
|
27
|
+
default=-252,
|
|
28
|
+
le=-1,
|
|
29
|
+
description="Estimation window start relative to t=0",
|
|
30
|
+
)
|
|
31
|
+
estimation_end: int = Field(
|
|
32
|
+
default=-20,
|
|
33
|
+
le=-1,
|
|
34
|
+
description="Estimation window end relative to t=0 (must be negative)",
|
|
35
|
+
)
|
|
36
|
+
event_start: int = Field(
|
|
37
|
+
default=-5,
|
|
38
|
+
description="Event window start relative to t=0",
|
|
39
|
+
)
|
|
40
|
+
event_end: int = Field(
|
|
41
|
+
default=5,
|
|
42
|
+
description="Event window end relative to t=0",
|
|
43
|
+
)
|
|
44
|
+
gap: int = Field(
|
|
45
|
+
default=5,
|
|
46
|
+
ge=0,
|
|
47
|
+
description="Buffer days between estimation and event windows",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@field_validator("estimation_end")
|
|
51
|
+
@classmethod
|
|
52
|
+
def validate_estimation_end(cls, v: int, info) -> int:
|
|
53
|
+
"""Ensure estimation window is properly ordered."""
|
|
54
|
+
if info.data.get("estimation_start") is not None:
|
|
55
|
+
if info.data["estimation_start"] >= v:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"estimation_start ({info.data['estimation_start']}) must be < estimation_end ({v})"
|
|
58
|
+
)
|
|
59
|
+
return v
|
|
60
|
+
|
|
61
|
+
@field_validator("event_end")
|
|
62
|
+
@classmethod
|
|
63
|
+
def validate_event_end(cls, v: int, info) -> int:
|
|
64
|
+
"""Ensure event window is properly ordered."""
|
|
65
|
+
if info.data.get("event_start") is not None:
|
|
66
|
+
if info.data["event_start"] >= v:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"event_start ({info.data['event_start']}) must be < event_end ({v})"
|
|
69
|
+
)
|
|
70
|
+
return v
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def estimation_window(self) -> tuple[int, int]:
|
|
74
|
+
"""Estimation window as tuple for backward compatibility."""
|
|
75
|
+
return (self.estimation_start, self.estimation_end)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def event_window(self) -> tuple[int, int]:
|
|
79
|
+
"""Event window as tuple for backward compatibility."""
|
|
80
|
+
return (self.event_start, self.event_end)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def estimation_length(self) -> int:
|
|
84
|
+
"""Length of estimation window in days."""
|
|
85
|
+
return self.estimation_end - self.estimation_start
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def event_length(self) -> int:
|
|
89
|
+
"""Length of event window in days."""
|
|
90
|
+
return self.event_end - self.event_start + 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class EventConfig(BaseConfig):
|
|
94
|
+
"""Configuration for event study analysis.
|
|
95
|
+
|
|
96
|
+
Configures the event study methodology including window parameters,
|
|
97
|
+
abnormal return model, and statistical test.
|
|
98
|
+
|
|
99
|
+
Attributes
|
|
100
|
+
----------
|
|
101
|
+
window : WindowSettings
|
|
102
|
+
Window configuration (estimation and event periods)
|
|
103
|
+
model : str
|
|
104
|
+
Model for computing normal/expected returns
|
|
105
|
+
test : str
|
|
106
|
+
Statistical test for significance
|
|
107
|
+
confidence_level : float
|
|
108
|
+
Confidence level for intervals
|
|
109
|
+
min_estimation_obs : int
|
|
110
|
+
Minimum observations in estimation window
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
>>> config = EventConfig(
|
|
115
|
+
... window=WindowSettings(estimation_start=-252, event_end=10),
|
|
116
|
+
... model="market_model",
|
|
117
|
+
... test="boehmer",
|
|
118
|
+
... )
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
window: WindowSettings = Field(
|
|
122
|
+
default_factory=WindowSettings,
|
|
123
|
+
description="Window configuration",
|
|
124
|
+
)
|
|
125
|
+
model: Literal["market_model", "mean_adjusted", "market_adjusted"] = Field(
|
|
126
|
+
default="market_model",
|
|
127
|
+
description="Model for computing expected returns",
|
|
128
|
+
)
|
|
129
|
+
test: Literal["t_test", "boehmer", "corrado"] = Field(
|
|
130
|
+
default="boehmer",
|
|
131
|
+
description="Statistical test for significance",
|
|
132
|
+
)
|
|
133
|
+
confidence_level: float = Field(
|
|
134
|
+
default=0.95,
|
|
135
|
+
gt=0.0,
|
|
136
|
+
lt=1.0,
|
|
137
|
+
description="Confidence level for intervals",
|
|
138
|
+
)
|
|
139
|
+
min_estimation_obs: int = Field(
|
|
140
|
+
default=100,
|
|
141
|
+
ge=30,
|
|
142
|
+
description="Minimum observations in estimation window",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def alpha(self) -> float:
|
|
147
|
+
"""Significance level (1 - confidence_level)."""
|
|
148
|
+
return 1.0 - self.confidence_level
|