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,514 @@
|
|
|
1
|
+
"""Event Study Visualization Module.
|
|
2
|
+
|
|
3
|
+
Provides interactive Plotly visualizations for event study analysis:
|
|
4
|
+
- CAAR time series with confidence bands
|
|
5
|
+
- Event drift heatmap
|
|
6
|
+
- AR distribution plots
|
|
7
|
+
|
|
8
|
+
All plots follow ML4T Diagnostic visualization standards.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
import plotly.graph_objects as go
|
|
16
|
+
from plotly.subplots import make_subplots
|
|
17
|
+
|
|
18
|
+
from ml4t.diagnostic.visualization.core import get_theme_config as get_theme
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from ml4t.diagnostic.results.event_results import (
|
|
22
|
+
AbnormalReturnResult,
|
|
23
|
+
EventStudyResult,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def plot_caar(
|
|
28
|
+
result: EventStudyResult,
|
|
29
|
+
show_confidence: bool = True,
|
|
30
|
+
show_aar_bars: bool = False,
|
|
31
|
+
theme: str | None = None,
|
|
32
|
+
width: int | None = None,
|
|
33
|
+
height: int | None = None,
|
|
34
|
+
) -> go.Figure:
|
|
35
|
+
"""Plot Cumulative Average Abnormal Returns (CAAR) time series.
|
|
36
|
+
|
|
37
|
+
Creates a line plot of CAAR over the event window with optional
|
|
38
|
+
confidence interval bands and vertical line at t=0.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
result : EventStudyResult
|
|
43
|
+
Event study results containing CAAR data.
|
|
44
|
+
show_confidence : bool, default True
|
|
45
|
+
Show confidence interval as shaded band.
|
|
46
|
+
show_aar_bars : bool, default False
|
|
47
|
+
Show AAR as bars in secondary y-axis.
|
|
48
|
+
theme : str | None
|
|
49
|
+
Visualization theme (None uses default).
|
|
50
|
+
width : int | None
|
|
51
|
+
Figure width in pixels.
|
|
52
|
+
height : int | None
|
|
53
|
+
Figure height in pixels.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
go.Figure
|
|
58
|
+
Plotly figure with CAAR visualization.
|
|
59
|
+
|
|
60
|
+
Examples
|
|
61
|
+
--------
|
|
62
|
+
>>> result = analysis.run()
|
|
63
|
+
>>> fig = plot_caar(result, show_confidence=True)
|
|
64
|
+
>>> fig.show()
|
|
65
|
+
"""
|
|
66
|
+
theme_config = get_theme(theme)
|
|
67
|
+
colors = theme_config.get("colors", {})
|
|
68
|
+
|
|
69
|
+
# Create figure
|
|
70
|
+
if show_aar_bars:
|
|
71
|
+
fig = make_subplots(
|
|
72
|
+
rows=2,
|
|
73
|
+
cols=1,
|
|
74
|
+
shared_xaxes=True,
|
|
75
|
+
vertical_spacing=0.1,
|
|
76
|
+
row_heights=[0.7, 0.3],
|
|
77
|
+
subplot_titles=("Cumulative Average Abnormal Return (CAAR)", "Daily AAR"),
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
fig = go.Figure()
|
|
81
|
+
|
|
82
|
+
# Get data
|
|
83
|
+
x = result.caar_dates
|
|
84
|
+
y = result.caar
|
|
85
|
+
|
|
86
|
+
# Add confidence band
|
|
87
|
+
if show_confidence:
|
|
88
|
+
fig.add_trace(
|
|
89
|
+
go.Scatter(
|
|
90
|
+
x=x + x[::-1],
|
|
91
|
+
y=result.caar_ci_upper + result.caar_ci_lower[::-1],
|
|
92
|
+
fill="toself",
|
|
93
|
+
fillcolor=colors.get("ci_fill", "rgba(31, 119, 180, 0.2)"),
|
|
94
|
+
line={"color": "rgba(0,0,0,0)"},
|
|
95
|
+
name=f"{int(result.confidence_level * 100)}% CI",
|
|
96
|
+
showlegend=True,
|
|
97
|
+
hoverinfo="skip",
|
|
98
|
+
),
|
|
99
|
+
row=1 if show_aar_bars else None,
|
|
100
|
+
col=1 if show_aar_bars else None,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Add CAAR line
|
|
104
|
+
fig.add_trace(
|
|
105
|
+
go.Scatter(
|
|
106
|
+
x=x,
|
|
107
|
+
y=y,
|
|
108
|
+
mode="lines+markers",
|
|
109
|
+
name="CAAR",
|
|
110
|
+
line={"color": colors.get("primary", "#1f77b4"), "width": 2},
|
|
111
|
+
marker={"size": 6},
|
|
112
|
+
hovertemplate="Day %{x}<br>CAAR: %{y:.4f}<extra></extra>",
|
|
113
|
+
),
|
|
114
|
+
row=1 if show_aar_bars else None,
|
|
115
|
+
col=1 if show_aar_bars else None,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Add vertical line at t=0
|
|
119
|
+
fig.add_vline(
|
|
120
|
+
x=0,
|
|
121
|
+
line_dash="dash",
|
|
122
|
+
line_color=colors.get("event_line", "red"),
|
|
123
|
+
annotation_text="Event",
|
|
124
|
+
annotation_position="top",
|
|
125
|
+
row=1 if show_aar_bars else None,
|
|
126
|
+
col=1 if show_aar_bars else None,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Add horizontal line at y=0
|
|
130
|
+
fig.add_hline(
|
|
131
|
+
y=0,
|
|
132
|
+
line_dash="dot",
|
|
133
|
+
line_color="gray",
|
|
134
|
+
row=1 if show_aar_bars else None,
|
|
135
|
+
col=1 if show_aar_bars else None,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Add AAR bars if requested
|
|
139
|
+
if show_aar_bars:
|
|
140
|
+
aar_x = list(result.aar_by_day.keys())
|
|
141
|
+
aar_y = list(result.aar_by_day.values())
|
|
142
|
+
|
|
143
|
+
bar_colors = [
|
|
144
|
+
colors.get("positive", "#2ca02c") if v >= 0 else colors.get("negative", "#d62728")
|
|
145
|
+
for v in aar_y
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
fig.add_trace(
|
|
149
|
+
go.Bar(
|
|
150
|
+
x=aar_x,
|
|
151
|
+
y=aar_y,
|
|
152
|
+
marker_color=bar_colors,
|
|
153
|
+
name="AAR",
|
|
154
|
+
hovertemplate="Day %{x}<br>AAR: %{y:.4f}<extra></extra>",
|
|
155
|
+
),
|
|
156
|
+
row=2,
|
|
157
|
+
col=1,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
fig.add_vline(x=0, line_dash="dash", line_color="red", row=2, col=1)
|
|
161
|
+
fig.add_hline(y=0, line_dash="dot", line_color="gray", row=2, col=1)
|
|
162
|
+
|
|
163
|
+
# Update layout
|
|
164
|
+
title = f"Event Study: CAAR (n={result.n_events} events)"
|
|
165
|
+
if result.is_significant:
|
|
166
|
+
title += f" - Significant at {result.confidence_level:.0%}"
|
|
167
|
+
|
|
168
|
+
fig.update_layout(
|
|
169
|
+
title=title,
|
|
170
|
+
xaxis_title="Days Relative to Event",
|
|
171
|
+
yaxis_title="CAAR",
|
|
172
|
+
width=width or 800,
|
|
173
|
+
height=height or (600 if show_aar_bars else 450),
|
|
174
|
+
template=theme_config.get("template", "plotly_white"),
|
|
175
|
+
hovermode="x unified",
|
|
176
|
+
legend={"orientation": "h", "yanchor": "bottom", "y": 1.02, "xanchor": "right", "x": 1},
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Add statistical info annotation
|
|
180
|
+
annotation_text = (
|
|
181
|
+
f"Test: {result.test_name}<br>"
|
|
182
|
+
f"Stat: {result.test_statistic:.3f}<br>"
|
|
183
|
+
f"p-value: {result.p_value:.4f}"
|
|
184
|
+
)
|
|
185
|
+
fig.add_annotation(
|
|
186
|
+
xref="paper",
|
|
187
|
+
yref="paper",
|
|
188
|
+
x=0.02,
|
|
189
|
+
y=0.98,
|
|
190
|
+
text=annotation_text,
|
|
191
|
+
showarrow=False,
|
|
192
|
+
font={"size": 10},
|
|
193
|
+
align="left",
|
|
194
|
+
bgcolor="rgba(255,255,255,0.8)",
|
|
195
|
+
bordercolor="gray",
|
|
196
|
+
borderwidth=1,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return fig
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def plot_event_heatmap(
|
|
203
|
+
ar_results: list[AbnormalReturnResult],
|
|
204
|
+
theme: str | None = None,
|
|
205
|
+
width: int | None = None,
|
|
206
|
+
height: int | None = None,
|
|
207
|
+
) -> go.Figure:
|
|
208
|
+
"""Plot event drift heatmap showing AR for each event by day.
|
|
209
|
+
|
|
210
|
+
Creates a heatmap with events on Y-axis and relative days on X-axis,
|
|
211
|
+
colored by abnormal return magnitude.
|
|
212
|
+
|
|
213
|
+
Parameters
|
|
214
|
+
----------
|
|
215
|
+
ar_results : list[AbnormalReturnResult]
|
|
216
|
+
Individual event abnormal return results.
|
|
217
|
+
theme : str | None
|
|
218
|
+
Visualization theme.
|
|
219
|
+
width : int | None
|
|
220
|
+
Figure width.
|
|
221
|
+
height : int | None
|
|
222
|
+
Figure height.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
go.Figure
|
|
227
|
+
Plotly figure with heatmap.
|
|
228
|
+
|
|
229
|
+
Examples
|
|
230
|
+
--------
|
|
231
|
+
>>> ar_results = analysis.compute_abnormal_returns()
|
|
232
|
+
>>> fig = plot_event_heatmap(ar_results)
|
|
233
|
+
>>> fig.show()
|
|
234
|
+
"""
|
|
235
|
+
if not ar_results:
|
|
236
|
+
raise ValueError("No abnormal return results provided")
|
|
237
|
+
|
|
238
|
+
theme_config = get_theme(theme)
|
|
239
|
+
|
|
240
|
+
# Collect all days and build matrix
|
|
241
|
+
all_days: set[int] = set()
|
|
242
|
+
for r in ar_results:
|
|
243
|
+
all_days.update(r.ar_by_day.keys())
|
|
244
|
+
sorted_days = sorted(all_days)
|
|
245
|
+
|
|
246
|
+
# Build heatmap matrix
|
|
247
|
+
z_matrix = []
|
|
248
|
+
y_labels = []
|
|
249
|
+
hover_texts = []
|
|
250
|
+
|
|
251
|
+
for r in ar_results:
|
|
252
|
+
row = []
|
|
253
|
+
hover_row = []
|
|
254
|
+
for day in sorted_days:
|
|
255
|
+
ar = r.ar_by_day.get(day, float("nan"))
|
|
256
|
+
row.append(ar)
|
|
257
|
+
hover_row.append(
|
|
258
|
+
f"Event: {r.event_id}<br>Asset: {r.asset}<br>Day: {day}<br>AR: {ar:.4f}"
|
|
259
|
+
if not (ar != ar) # not nan check
|
|
260
|
+
else f"Day {day}: No data"
|
|
261
|
+
)
|
|
262
|
+
z_matrix.append(row)
|
|
263
|
+
hover_texts.append(hover_row)
|
|
264
|
+
y_labels.append(f"{r.event_id} ({r.asset})")
|
|
265
|
+
|
|
266
|
+
fig = go.Figure(
|
|
267
|
+
data=go.Heatmap(
|
|
268
|
+
z=z_matrix,
|
|
269
|
+
x=sorted_days,
|
|
270
|
+
y=y_labels,
|
|
271
|
+
colorscale="RdBu_r",
|
|
272
|
+
zmid=0,
|
|
273
|
+
colorbar={"title": "AR"},
|
|
274
|
+
hovertemplate="%{text}<extra></extra>",
|
|
275
|
+
text=hover_texts,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Add vertical line at event day
|
|
280
|
+
fig.add_vline(x=0, line_dash="dash", line_color="black", line_width=2)
|
|
281
|
+
|
|
282
|
+
fig.update_layout(
|
|
283
|
+
title="Abnormal Returns by Event and Day",
|
|
284
|
+
xaxis_title="Days Relative to Event",
|
|
285
|
+
yaxis_title="Event",
|
|
286
|
+
width=width or 900,
|
|
287
|
+
height=height or max(400, len(ar_results) * 25 + 100),
|
|
288
|
+
template=theme_config.get("template", "plotly_white"),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return fig
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def plot_ar_distribution(
|
|
295
|
+
result: EventStudyResult,
|
|
296
|
+
day: int = 0,
|
|
297
|
+
show_kde: bool = True,
|
|
298
|
+
theme: str | None = None,
|
|
299
|
+
width: int | None = None,
|
|
300
|
+
height: int | None = None,
|
|
301
|
+
) -> go.Figure:
|
|
302
|
+
"""Plot distribution of abnormal returns for a specific day.
|
|
303
|
+
|
|
304
|
+
Creates a histogram with optional KDE overlay showing the
|
|
305
|
+
cross-sectional distribution of ARs on a given day.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
result : EventStudyResult
|
|
310
|
+
Event study results with individual event data.
|
|
311
|
+
day : int, default 0
|
|
312
|
+
Relative day to plot (0 = event day).
|
|
313
|
+
show_kde : bool, default True
|
|
314
|
+
Overlay kernel density estimate.
|
|
315
|
+
theme : str | None
|
|
316
|
+
Visualization theme.
|
|
317
|
+
width : int | None
|
|
318
|
+
Figure width.
|
|
319
|
+
height : int | None
|
|
320
|
+
Figure height.
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
go.Figure
|
|
325
|
+
Plotly figure with AR distribution.
|
|
326
|
+
"""
|
|
327
|
+
if result.individual_results is None:
|
|
328
|
+
raise ValueError("EventStudyResult must include individual_results")
|
|
329
|
+
|
|
330
|
+
theme_config = get_theme(theme)
|
|
331
|
+
colors = theme_config.get("colors", {})
|
|
332
|
+
|
|
333
|
+
# Collect ARs for the specified day
|
|
334
|
+
ars = []
|
|
335
|
+
for r in result.individual_results:
|
|
336
|
+
if day in r.ar_by_day:
|
|
337
|
+
ars.append(r.ar_by_day[day])
|
|
338
|
+
|
|
339
|
+
if not ars:
|
|
340
|
+
raise ValueError(f"No AR data available for day {day}")
|
|
341
|
+
|
|
342
|
+
import numpy as np
|
|
343
|
+
|
|
344
|
+
ars_array = np.array(ars)
|
|
345
|
+
|
|
346
|
+
fig = go.Figure()
|
|
347
|
+
|
|
348
|
+
# Add histogram
|
|
349
|
+
fig.add_trace(
|
|
350
|
+
go.Histogram(
|
|
351
|
+
x=ars_array,
|
|
352
|
+
nbinsx=min(20, len(ars)),
|
|
353
|
+
name="AR Distribution",
|
|
354
|
+
marker_color=colors.get("primary", "#1f77b4"),
|
|
355
|
+
opacity=0.7,
|
|
356
|
+
histnorm="probability density" if show_kde else None,
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Add KDE if requested
|
|
361
|
+
if show_kde and len(ars) >= 5:
|
|
362
|
+
from scipy import stats as sp_stats
|
|
363
|
+
|
|
364
|
+
kde = sp_stats.gaussian_kde(ars_array)
|
|
365
|
+
x_range = np.linspace(min(ars_array), max(ars_array), 100)
|
|
366
|
+
kde_y = kde(x_range)
|
|
367
|
+
|
|
368
|
+
fig.add_trace(
|
|
369
|
+
go.Scatter(
|
|
370
|
+
x=x_range,
|
|
371
|
+
y=kde_y,
|
|
372
|
+
mode="lines",
|
|
373
|
+
name="KDE",
|
|
374
|
+
line={"color": colors.get("secondary", "#ff7f0e"), "width": 2},
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# Add vertical line at mean
|
|
379
|
+
mean_ar = float(np.mean(ars_array))
|
|
380
|
+
fig.add_vline(
|
|
381
|
+
x=mean_ar,
|
|
382
|
+
line_dash="dash",
|
|
383
|
+
line_color="red",
|
|
384
|
+
annotation_text=f"Mean: {mean_ar:.4f}",
|
|
385
|
+
annotation_position="top right",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Add vertical line at 0
|
|
389
|
+
fig.add_vline(x=0, line_dash="dot", line_color="gray")
|
|
390
|
+
|
|
391
|
+
# Calculate statistics
|
|
392
|
+
std_ar = float(np.std(ars_array, ddof=1))
|
|
393
|
+
t_stat = mean_ar / (std_ar / np.sqrt(len(ars))) if std_ar > 0 else 0
|
|
394
|
+
p_val = 2 * (1 - sp_stats.t.cdf(abs(t_stat), df=len(ars) - 1)) if len(ars) > 1 else 1.0
|
|
395
|
+
|
|
396
|
+
day_label = "Event Day" if day == 0 else f"Day {day:+d}"
|
|
397
|
+
fig.update_layout(
|
|
398
|
+
title=f"Abnormal Return Distribution - {day_label}",
|
|
399
|
+
xaxis_title="Abnormal Return",
|
|
400
|
+
yaxis_title="Density" if show_kde else "Count",
|
|
401
|
+
width=width or 600,
|
|
402
|
+
height=height or 400,
|
|
403
|
+
template=theme_config.get("template", "plotly_white"),
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Add statistics annotation
|
|
407
|
+
annotation_text = (
|
|
408
|
+
f"n = {len(ars)}<br>"
|
|
409
|
+
f"Mean = {mean_ar:.4f}<br>"
|
|
410
|
+
f"Std = {std_ar:.4f}<br>"
|
|
411
|
+
f"t-stat = {t_stat:.3f}<br>"
|
|
412
|
+
f"p-value = {p_val:.4f}"
|
|
413
|
+
)
|
|
414
|
+
fig.add_annotation(
|
|
415
|
+
xref="paper",
|
|
416
|
+
yref="paper",
|
|
417
|
+
x=0.98,
|
|
418
|
+
y=0.98,
|
|
419
|
+
text=annotation_text,
|
|
420
|
+
showarrow=False,
|
|
421
|
+
font={"size": 10},
|
|
422
|
+
align="left",
|
|
423
|
+
bgcolor="rgba(255,255,255,0.8)",
|
|
424
|
+
bordercolor="gray",
|
|
425
|
+
borderwidth=1,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
return fig
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def plot_car_by_event(
|
|
432
|
+
ar_results: list[AbnormalReturnResult],
|
|
433
|
+
sort_by: str = "car",
|
|
434
|
+
top_n: int | None = None,
|
|
435
|
+
theme: str | None = None,
|
|
436
|
+
width: int | None = None,
|
|
437
|
+
height: int | None = None,
|
|
438
|
+
) -> go.Figure:
|
|
439
|
+
"""Plot cumulative abnormal returns by event as horizontal bar chart.
|
|
440
|
+
|
|
441
|
+
Shows CAR for each event, sorted by magnitude, useful for
|
|
442
|
+
identifying outliers and event heterogeneity.
|
|
443
|
+
|
|
444
|
+
Parameters
|
|
445
|
+
----------
|
|
446
|
+
ar_results : list[AbnormalReturnResult]
|
|
447
|
+
Individual event results.
|
|
448
|
+
sort_by : str, default "car"
|
|
449
|
+
Sort by "car" (magnitude) or "date".
|
|
450
|
+
top_n : int | None
|
|
451
|
+
Show only top N events by magnitude.
|
|
452
|
+
theme : str | None
|
|
453
|
+
Visualization theme.
|
|
454
|
+
width : int | None
|
|
455
|
+
Figure width.
|
|
456
|
+
height : int | None
|
|
457
|
+
Figure height.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
go.Figure
|
|
462
|
+
Plotly figure with CAR bar chart.
|
|
463
|
+
"""
|
|
464
|
+
if not ar_results:
|
|
465
|
+
raise ValueError("No abnormal return results provided")
|
|
466
|
+
|
|
467
|
+
theme_config = get_theme(theme)
|
|
468
|
+
colors = theme_config.get("colors", {})
|
|
469
|
+
|
|
470
|
+
# Sort results
|
|
471
|
+
if sort_by == "car":
|
|
472
|
+
sorted_results = sorted(ar_results, key=lambda x: abs(x.car), reverse=True)
|
|
473
|
+
else:
|
|
474
|
+
sorted_results = sorted(ar_results, key=lambda x: x.event_date)
|
|
475
|
+
|
|
476
|
+
# Limit to top_n if specified
|
|
477
|
+
if top_n is not None:
|
|
478
|
+
sorted_results = sorted_results[:top_n]
|
|
479
|
+
|
|
480
|
+
# Prepare data
|
|
481
|
+
labels = [f"{r.event_id} ({r.asset})" for r in sorted_results]
|
|
482
|
+
cars = [r.car for r in sorted_results]
|
|
483
|
+
bar_colors = [
|
|
484
|
+
colors.get("positive", "#2ca02c") if c >= 0 else colors.get("negative", "#d62728")
|
|
485
|
+
for c in cars
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
fig = go.Figure(
|
|
489
|
+
data=go.Bar(
|
|
490
|
+
y=labels,
|
|
491
|
+
x=cars,
|
|
492
|
+
orientation="h",
|
|
493
|
+
marker_color=bar_colors,
|
|
494
|
+
hovertemplate="Event: %{y}<br>CAR: %{x:.4f}<extra></extra>",
|
|
495
|
+
)
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
fig.add_vline(x=0, line_dash="solid", line_color="gray")
|
|
499
|
+
|
|
500
|
+
title = "Cumulative Abnormal Return by Event"
|
|
501
|
+
if top_n is not None:
|
|
502
|
+
title += f" (Top {top_n})"
|
|
503
|
+
|
|
504
|
+
fig.update_layout(
|
|
505
|
+
title=title,
|
|
506
|
+
xaxis_title="CAR",
|
|
507
|
+
yaxis_title="Event",
|
|
508
|
+
width=width or 700,
|
|
509
|
+
height=height or max(400, len(sorted_results) * 25 + 100),
|
|
510
|
+
template=theme_config.get("template", "plotly_white"),
|
|
511
|
+
yaxis={"autorange": "reversed"}, # Largest at top
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
return fig
|