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.
Files changed (242) hide show
  1. ml4t/diagnostic/AGENT.md +25 -0
  2. ml4t/diagnostic/__init__.py +166 -0
  3. ml4t/diagnostic/backends/__init__.py +10 -0
  4. ml4t/diagnostic/backends/adapter.py +192 -0
  5. ml4t/diagnostic/backends/polars_backend.py +899 -0
  6. ml4t/diagnostic/caching/__init__.py +40 -0
  7. ml4t/diagnostic/caching/cache.py +331 -0
  8. ml4t/diagnostic/caching/decorators.py +131 -0
  9. ml4t/diagnostic/caching/smart_cache.py +339 -0
  10. ml4t/diagnostic/config/AGENT.md +24 -0
  11. ml4t/diagnostic/config/README.md +267 -0
  12. ml4t/diagnostic/config/__init__.py +219 -0
  13. ml4t/diagnostic/config/barrier_config.py +277 -0
  14. ml4t/diagnostic/config/base.py +301 -0
  15. ml4t/diagnostic/config/event_config.py +148 -0
  16. ml4t/diagnostic/config/feature_config.py +404 -0
  17. ml4t/diagnostic/config/multi_signal_config.py +55 -0
  18. ml4t/diagnostic/config/portfolio_config.py +215 -0
  19. ml4t/diagnostic/config/report_config.py +391 -0
  20. ml4t/diagnostic/config/sharpe_config.py +202 -0
  21. ml4t/diagnostic/config/signal_config.py +206 -0
  22. ml4t/diagnostic/config/trade_analysis_config.py +310 -0
  23. ml4t/diagnostic/config/validation.py +279 -0
  24. ml4t/diagnostic/core/__init__.py +29 -0
  25. ml4t/diagnostic/core/numba_utils.py +315 -0
  26. ml4t/diagnostic/core/purging.py +372 -0
  27. ml4t/diagnostic/core/sampling.py +471 -0
  28. ml4t/diagnostic/errors/__init__.py +205 -0
  29. ml4t/diagnostic/evaluation/AGENT.md +26 -0
  30. ml4t/diagnostic/evaluation/__init__.py +437 -0
  31. ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
  32. ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
  33. ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
  34. ml4t/diagnostic/evaluation/dashboard.py +715 -0
  35. ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
  36. ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
  37. ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
  38. ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
  39. ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
  40. ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
  41. ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
  42. ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
  43. ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
  44. ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
  45. ml4t/diagnostic/evaluation/event_analysis.py +647 -0
  46. ml4t/diagnostic/evaluation/excursion.py +390 -0
  47. ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
  48. ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
  49. ml4t/diagnostic/evaluation/framework.py +935 -0
  50. ml4t/diagnostic/evaluation/metric_registry.py +255 -0
  51. ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
  52. ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
  53. ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
  54. ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
  55. ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
  56. ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
  57. ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
  58. ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
  59. ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
  60. ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
  61. ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
  62. ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
  63. ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
  64. ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
  65. ml4t/diagnostic/evaluation/multi_signal.py +550 -0
  66. ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
  67. ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
  68. ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
  69. ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
  70. ml4t/diagnostic/evaluation/report_generation.py +824 -0
  71. ml4t/diagnostic/evaluation/signal_selector.py +452 -0
  72. ml4t/diagnostic/evaluation/stat_registry.py +139 -0
  73. ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
  74. ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
  75. ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
  76. ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
  77. ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
  78. ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
  79. ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
  80. ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
  81. ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
  82. ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
  83. ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
  84. ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
  85. ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
  86. ml4t/diagnostic/evaluation/stats/moments.py +164 -0
  87. ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
  88. ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
  89. ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
  90. ml4t/diagnostic/evaluation/themes.py +330 -0
  91. ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
  92. ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
  93. ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
  94. ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
  95. ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
  96. ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
  97. ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
  98. ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
  99. ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
  100. ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
  101. ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
  102. ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
  103. ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
  104. ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
  105. ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
  106. ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
  107. ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
  108. ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
  109. ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
  110. ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
  111. ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
  112. ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
  113. ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
  114. ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
  115. ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
  116. ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
  117. ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
  118. ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
  119. ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
  120. ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
  121. ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
  122. ml4t/diagnostic/evaluation/validated_cv.py +535 -0
  123. ml4t/diagnostic/evaluation/visualization.py +1050 -0
  124. ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
  125. ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
  126. ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
  127. ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
  128. ml4t/diagnostic/integration/__init__.py +48 -0
  129. ml4t/diagnostic/integration/backtest_contract.py +671 -0
  130. ml4t/diagnostic/integration/data_contract.py +316 -0
  131. ml4t/diagnostic/integration/engineer_contract.py +226 -0
  132. ml4t/diagnostic/logging/__init__.py +77 -0
  133. ml4t/diagnostic/logging/logger.py +245 -0
  134. ml4t/diagnostic/logging/performance.py +234 -0
  135. ml4t/diagnostic/logging/progress.py +234 -0
  136. ml4t/diagnostic/logging/wandb.py +412 -0
  137. ml4t/diagnostic/metrics/__init__.py +9 -0
  138. ml4t/diagnostic/metrics/percentiles.py +128 -0
  139. ml4t/diagnostic/py.typed +1 -0
  140. ml4t/diagnostic/reporting/__init__.py +43 -0
  141. ml4t/diagnostic/reporting/base.py +130 -0
  142. ml4t/diagnostic/reporting/html_renderer.py +275 -0
  143. ml4t/diagnostic/reporting/json_renderer.py +51 -0
  144. ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
  145. ml4t/diagnostic/results/AGENT.md +24 -0
  146. ml4t/diagnostic/results/__init__.py +105 -0
  147. ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
  148. ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
  149. ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
  150. ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
  151. ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
  152. ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
  153. ml4t/diagnostic/results/barrier_results/validation.py +38 -0
  154. ml4t/diagnostic/results/base.py +177 -0
  155. ml4t/diagnostic/results/event_results.py +349 -0
  156. ml4t/diagnostic/results/feature_results.py +787 -0
  157. ml4t/diagnostic/results/multi_signal_results.py +431 -0
  158. ml4t/diagnostic/results/portfolio_results.py +281 -0
  159. ml4t/diagnostic/results/sharpe_results.py +448 -0
  160. ml4t/diagnostic/results/signal_results/__init__.py +74 -0
  161. ml4t/diagnostic/results/signal_results/ic.py +581 -0
  162. ml4t/diagnostic/results/signal_results/irtc.py +110 -0
  163. ml4t/diagnostic/results/signal_results/quantile.py +392 -0
  164. ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
  165. ml4t/diagnostic/results/signal_results/turnover.py +213 -0
  166. ml4t/diagnostic/results/signal_results/validation.py +147 -0
  167. ml4t/diagnostic/signal/AGENT.md +17 -0
  168. ml4t/diagnostic/signal/__init__.py +69 -0
  169. ml4t/diagnostic/signal/_report.py +152 -0
  170. ml4t/diagnostic/signal/_utils.py +261 -0
  171. ml4t/diagnostic/signal/core.py +275 -0
  172. ml4t/diagnostic/signal/quantile.py +148 -0
  173. ml4t/diagnostic/signal/result.py +214 -0
  174. ml4t/diagnostic/signal/signal_ic.py +129 -0
  175. ml4t/diagnostic/signal/turnover.py +182 -0
  176. ml4t/diagnostic/splitters/AGENT.md +19 -0
  177. ml4t/diagnostic/splitters/__init__.py +36 -0
  178. ml4t/diagnostic/splitters/base.py +501 -0
  179. ml4t/diagnostic/splitters/calendar.py +421 -0
  180. ml4t/diagnostic/splitters/calendar_config.py +91 -0
  181. ml4t/diagnostic/splitters/combinatorial.py +1064 -0
  182. ml4t/diagnostic/splitters/config.py +322 -0
  183. ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
  184. ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
  185. ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
  186. ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
  187. ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
  188. ml4t/diagnostic/splitters/group_isolation.py +329 -0
  189. ml4t/diagnostic/splitters/persistence.py +316 -0
  190. ml4t/diagnostic/splitters/utils.py +207 -0
  191. ml4t/diagnostic/splitters/walk_forward.py +757 -0
  192. ml4t/diagnostic/utils/__init__.py +42 -0
  193. ml4t/diagnostic/utils/config.py +542 -0
  194. ml4t/diagnostic/utils/dependencies.py +318 -0
  195. ml4t/diagnostic/utils/sessions.py +127 -0
  196. ml4t/diagnostic/validation/__init__.py +54 -0
  197. ml4t/diagnostic/validation/dataframe.py +274 -0
  198. ml4t/diagnostic/validation/returns.py +280 -0
  199. ml4t/diagnostic/validation/timeseries.py +299 -0
  200. ml4t/diagnostic/visualization/AGENT.md +19 -0
  201. ml4t/diagnostic/visualization/__init__.py +223 -0
  202. ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
  203. ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
  204. ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
  205. ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
  206. ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
  207. ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
  208. ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
  209. ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
  210. ml4t/diagnostic/visualization/barrier_plots.py +782 -0
  211. ml4t/diagnostic/visualization/core.py +1060 -0
  212. ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
  213. ml4t/diagnostic/visualization/dashboards/base.py +582 -0
  214. ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
  215. ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
  216. ml4t/diagnostic/visualization/dashboards.py +43 -0
  217. ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
  218. ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
  219. ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
  220. ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
  221. ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
  222. ml4t/diagnostic/visualization/feature_plots.py +888 -0
  223. ml4t/diagnostic/visualization/interaction_plots.py +618 -0
  224. ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
  225. ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
  226. ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
  227. ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
  228. ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
  229. ml4t/diagnostic/visualization/report_generation.py +1343 -0
  230. ml4t/diagnostic/visualization/signal/__init__.py +103 -0
  231. ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
  232. ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
  233. ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
  234. ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
  235. ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
  236. ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
  237. ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
  238. ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
  239. ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
  240. ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
  241. ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
  242. 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