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,349 @@
1
+ """Event Study Result Classes.
2
+
3
+ This module provides result containers for event study analysis,
4
+ storing abnormal returns, cumulative abnormal returns, and
5
+ statistical test results.
6
+
7
+ Classes
8
+ -------
9
+ AbnormalReturnResult
10
+ Per-event abnormal return results
11
+ EventStudyResult
12
+ Aggregated event study results with CAAR and statistics
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ import polars as pl
20
+ from pydantic import Field
21
+
22
+ from ml4t.diagnostic.results.base import BaseResult
23
+
24
+
25
+ class AbnormalReturnResult(BaseResult):
26
+ """Per-event abnormal return results.
27
+
28
+ Stores abnormal returns for a single event across the event window,
29
+ including cumulative abnormal returns (CAR).
30
+
31
+ Attributes
32
+ ----------
33
+ event_id : str
34
+ Unique identifier for the event
35
+ asset : str
36
+ Asset/security identifier
37
+ event_date : str
38
+ Date of the event (ISO format)
39
+ ar_by_day : dict[int, float]
40
+ Abnormal returns by relative day {-5: 0.01, -4: -0.005, ...}
41
+ car : float
42
+ Cumulative abnormal return over event window
43
+ estimation_alpha : float | None
44
+ Market model alpha (if market_model used)
45
+ estimation_beta : float | None
46
+ Market model beta (if market_model used)
47
+ estimation_r2 : float | None
48
+ Market model R-squared (if market_model used)
49
+ estimation_residual_std : float | None
50
+ Estimation period residual std (for standardization)
51
+
52
+ Examples
53
+ --------
54
+ >>> result = AbnormalReturnResult(
55
+ ... event_id="EVT001",
56
+ ... asset="AAPL",
57
+ ... event_date="2023-06-15",
58
+ ... ar_by_day={-5: 0.01, -4: 0.005, -3: -0.002, -2: 0.008, -1: 0.015,
59
+ ... 0: 0.05, 1: 0.02, 2: -0.01, 3: 0.005, 4: 0.002, 5: -0.003},
60
+ ... car=0.10
61
+ ... )
62
+ """
63
+
64
+ analysis_type: str = Field(default="abnormal_return", description="Result type")
65
+
66
+ event_id: str = Field(..., description="Unique event identifier")
67
+ asset: str = Field(..., description="Asset/security identifier")
68
+ event_date: str = Field(..., description="Event date (ISO format)")
69
+ ar_by_day: dict[int, float] = Field(
70
+ ...,
71
+ description="Abnormal returns by relative day",
72
+ )
73
+ car: float = Field(..., description="Cumulative abnormal return")
74
+
75
+ # Market model parameters (optional)
76
+ estimation_alpha: float | None = Field(
77
+ default=None,
78
+ description="Market model intercept (alpha)",
79
+ )
80
+ estimation_beta: float | None = Field(
81
+ default=None,
82
+ description="Market model slope (beta)",
83
+ )
84
+ estimation_r2: float | None = Field(
85
+ default=None,
86
+ description="Market model R-squared",
87
+ )
88
+ estimation_residual_std: float | None = Field(
89
+ default=None,
90
+ description="Estimation period residual standard deviation",
91
+ )
92
+
93
+ def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
94
+ """Get abnormal returns as DataFrame.
95
+
96
+ Returns
97
+ -------
98
+ pl.DataFrame
99
+ DataFrame with columns: relative_day, abnormal_return
100
+ """
101
+ return pl.DataFrame(
102
+ {
103
+ "relative_day": list(self.ar_by_day.keys()),
104
+ "abnormal_return": list(self.ar_by_day.values()),
105
+ }
106
+ ).sort("relative_day")
107
+
108
+ def summary(self) -> str:
109
+ """Human-readable summary."""
110
+ lines = [
111
+ f"Event: {self.event_id}",
112
+ f"Asset: {self.asset}",
113
+ f"Date: {self.event_date}",
114
+ f"CAR: {self.car:.4f} ({self.car * 100:.2f}%)",
115
+ ]
116
+ if self.estimation_beta is not None:
117
+ lines.append(f"Beta: {self.estimation_beta:.3f}")
118
+ return "\n".join(lines)
119
+
120
+
121
+ class EventStudyResult(BaseResult):
122
+ """Complete event study results with aggregated statistics.
123
+
124
+ Contains average abnormal returns (AAR), cumulative average
125
+ abnormal returns (CAAR), confidence intervals, and statistical
126
+ test results.
127
+
128
+ Attributes
129
+ ----------
130
+ aar_by_day : dict[int, float]
131
+ Average abnormal return by relative day (cross-sectional mean)
132
+ caar : list[float]
133
+ Cumulative AAR time series
134
+ caar_dates : list[int]
135
+ Relative days corresponding to CAAR values
136
+ caar_std : list[float]
137
+ Standard deviation of CAAR at each point
138
+ caar_ci_lower : list[float]
139
+ Lower confidence interval bound
140
+ caar_ci_upper : list[float]
141
+ Upper confidence interval bound
142
+ test_statistic : float
143
+ Test statistic value (t-stat, BMP, or Corrado)
144
+ p_value : float
145
+ P-value for the test
146
+ test_name : str
147
+ Name of statistical test used
148
+ n_events : int
149
+ Number of events in the study
150
+ model_name : str
151
+ Name of model used (market_model, mean_adjusted, market_adjusted)
152
+ event_window : tuple[int, int]
153
+ Event window used
154
+ confidence_level : float
155
+ Confidence level for intervals
156
+ individual_results : list[AbnormalReturnResult] | None
157
+ Optional individual event results
158
+
159
+ Examples
160
+ --------
161
+ >>> result = EventStudyResult(
162
+ ... aar_by_day={-5: 0.001, ..., 0: 0.025, ..., 5: -0.002},
163
+ ... caar=[0.001, 0.003, 0.008, ...],
164
+ ... caar_dates=[-5, -4, -3, ...],
165
+ ... caar_std=[0.01, 0.012, ...],
166
+ ... caar_ci_lower=[...],
167
+ ... caar_ci_upper=[...],
168
+ ... test_statistic=2.45,
169
+ ... p_value=0.014,
170
+ ... test_name="boehmer",
171
+ ... n_events=50
172
+ ... )
173
+ """
174
+
175
+ analysis_type: str = Field(default="event_study", description="Result type")
176
+
177
+ # Average abnormal returns
178
+ aar_by_day: dict[int, float] = Field(
179
+ ...,
180
+ description="Average abnormal return by relative day",
181
+ )
182
+
183
+ # Cumulative average abnormal returns
184
+ caar: list[float] = Field(
185
+ ...,
186
+ description="Cumulative AAR time series",
187
+ )
188
+ caar_dates: list[int] = Field(
189
+ ...,
190
+ description="Relative days for CAAR values",
191
+ )
192
+ caar_std: list[float] = Field(
193
+ ...,
194
+ description="Standard deviation of CAAR",
195
+ )
196
+ caar_ci_lower: list[float] = Field(
197
+ ...,
198
+ description="Lower confidence interval",
199
+ )
200
+ caar_ci_upper: list[float] = Field(
201
+ ...,
202
+ description="Upper confidence interval",
203
+ )
204
+
205
+ # Statistical test results
206
+ test_statistic: float = Field(
207
+ ...,
208
+ description="Test statistic value",
209
+ )
210
+ p_value: float = Field(
211
+ ...,
212
+ ge=0.0,
213
+ le=1.0,
214
+ description="P-value for significance test",
215
+ )
216
+ test_name: str = Field(
217
+ ...,
218
+ description="Name of statistical test (t_test, boehmer, corrado)",
219
+ )
220
+
221
+ # Metadata
222
+ n_events: int = Field(
223
+ ...,
224
+ ge=1,
225
+ description="Number of events analyzed",
226
+ )
227
+ model_name: str = Field(
228
+ default="market_model",
229
+ description="Model used for expected returns",
230
+ )
231
+ event_window: tuple[int, int] = Field(
232
+ default=(-5, 5),
233
+ description="Event window (start, end)",
234
+ )
235
+ confidence_level: float = Field(
236
+ default=0.95,
237
+ description="Confidence level for intervals",
238
+ )
239
+
240
+ # Optional detailed results
241
+ individual_results: list[AbnormalReturnResult] | None = Field(
242
+ default=None,
243
+ description="Individual event results (optional)",
244
+ )
245
+
246
+ @property
247
+ def is_significant(self) -> bool:
248
+ """Whether CAAR is statistically significant at the configured level."""
249
+ return self.p_value < (1 - self.confidence_level)
250
+
251
+ @property
252
+ def final_caar(self) -> float:
253
+ """CAAR at the end of the event window."""
254
+ return self.caar[-1] if self.caar else 0.0
255
+
256
+ @property
257
+ def event_day_aar(self) -> float:
258
+ """AAR on the event day (t=0)."""
259
+ return self.aar_by_day.get(0, 0.0)
260
+
261
+ def get_dataframe(self, name: str | None = None) -> pl.DataFrame:
262
+ """Get results as DataFrame.
263
+
264
+ Parameters
265
+ ----------
266
+ name : str | None
267
+ DataFrame name: "caar" (default), "aar", or "events"
268
+
269
+ Returns
270
+ -------
271
+ pl.DataFrame
272
+ Requested DataFrame
273
+ """
274
+ if name is None or name == "caar":
275
+ return pl.DataFrame(
276
+ {
277
+ "relative_day": self.caar_dates,
278
+ "caar": self.caar,
279
+ "caar_std": self.caar_std,
280
+ "ci_lower": self.caar_ci_lower,
281
+ "ci_upper": self.caar_ci_upper,
282
+ }
283
+ )
284
+ elif name == "aar":
285
+ return pl.DataFrame(
286
+ {
287
+ "relative_day": list(self.aar_by_day.keys()),
288
+ "aar": list(self.aar_by_day.values()),
289
+ }
290
+ ).sort("relative_day")
291
+ elif name == "events" and self.individual_results:
292
+ return pl.DataFrame(
293
+ [
294
+ {
295
+ "event_id": r.event_id,
296
+ "asset": r.asset,
297
+ "event_date": r.event_date,
298
+ "car": r.car,
299
+ }
300
+ for r in self.individual_results
301
+ ]
302
+ )
303
+ else:
304
+ raise ValueError(f"Unknown DataFrame name: {name}. Available: caar, aar, events")
305
+
306
+ def list_available_dataframes(self) -> list[str]:
307
+ """List available DataFrame views."""
308
+ views = ["caar", "aar"]
309
+ if self.individual_results:
310
+ views.append("events")
311
+ return views
312
+
313
+ def summary(self) -> str:
314
+ """Human-readable summary of event study results."""
315
+ significance = "significant" if self.is_significant else "not significant"
316
+ alpha = 1 - self.confidence_level
317
+
318
+ lines = [
319
+ "=" * 50,
320
+ "EVENT STUDY RESULTS",
321
+ "=" * 50,
322
+ f"Events analyzed: {self.n_events}",
323
+ f"Event window: [{self.event_window[0]}, {self.event_window[1]}]",
324
+ f"Model: {self.model_name}",
325
+ "",
326
+ "CUMULATIVE AVERAGE ABNORMAL RETURN (CAAR)",
327
+ f" Event day AAR (t=0): {self.event_day_aar:+.4f} ({self.event_day_aar * 100:+.2f}%)",
328
+ f" Final CAAR: {self.final_caar:+.4f} ({self.final_caar * 100:+.2f}%)",
329
+ f" 95% CI: [{self.caar_ci_lower[-1]:.4f}, {self.caar_ci_upper[-1]:.4f}]",
330
+ "",
331
+ "STATISTICAL TEST",
332
+ f" Test: {self.test_name}",
333
+ f" Test statistic: {self.test_statistic:.4f}",
334
+ f" P-value: {self.p_value:.4f}",
335
+ f" Result: {significance} at α={alpha:.2f}",
336
+ "=" * 50,
337
+ ]
338
+ return "\n".join(lines)
339
+
340
+ def to_dict(self, *, exclude_none: bool = False) -> dict[str, Any]:
341
+ """Export to dictionary.
342
+
343
+ Overridden to handle individual_results serialization.
344
+ """
345
+ data = super().to_dict(exclude_none=exclude_none)
346
+ # Convert individual results if present
347
+ if self.individual_results and "individual_results" in data:
348
+ data["individual_results"] = [r.to_dict() for r in self.individual_results]
349
+ return data