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,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
|