factrix.metrics.caar ¶
CAAR (Cumulative Average Abnormal Return) significance tests.
Tests \(H_0\): event abnormal return = 0, using two complementary methods: compute_caar — per-event-date weighted abnormal return series caar — CAAR t-test (parametric, non-overlapping sampling) bmp_test — BMP standardized AR test (robust to event-induced variance)
Notes
Pipeline. Per-event-date weighted abnormal return (per-event-date step) then non-overlapping cross-event sample; \(t\)-test on CAAR, or BMP standardized AR \(z\)-test for event-induced variance.
References
- MacKinlay (1997), "Event Studies in Economics and Finance."
- Boehmer, Musumeci & Poulsen (1991), "Event-study Methodology Under Conditions of Event-induced Variance."
factrix.metrics.caar.compute_caar ¶
compute_caar(df: DataFrame, *, factor_col: str = 'factor', return_col: str = 'forward_return') -> DataFrame
Per-event-date weighted abnormal return series.
Magnitude is preserved — no .sign() coercion. factrix accepts
two input contracts; everything else (including signed
\(\{-1, 0, +1\}\)) is just a special case of the second:
Input factor |
signed_car reduces to |
Statistic tested |
|---|---|---|
| \(\{0, 1\}\) | \(\text{return}\) on event rows | Average event-day return |
| \(\{0, R\}\), \(R \in \mathbb{R}\) | \(\text{return} \times R\) | Magnitude-weighted CAAR |
Caveat on \(\{-1, 0, +1\}\): it lands in the second row as a weight-\(\pm 1\) case, which gives a magnitude-weighted CAAR, not the textbook MacKinlay (1997) signed CAAR (the latter averages direction-flipped abnormal returns and is a different estimator at finite samples when the negative-leg vol differs from the positive- leg vol). If literature-standard signed CAAR is what you want, pre-compute it externally; factrix's primitive treats \(\pm 1\) as weights, not as direction labels.
Aggregation
cs-first. For each event date, take the cross-sectional mean of
signed_car \(= r \times f\) across event rows where \(f \neq 0\);
the resulting n_event_dates-length CAAR series feeds a
downstream Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) \(t\)-test on the mean.
Scale
CAAR magnitude tracks the units of factor (bps, z-score,
unit-less ±1). Hypothesis tests via the downstream caar
t-statistic are scale-invariant (numerator and denominator both
scale linearly), but cross-factor effect-size comparisons
require commensurate units.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Panel with |
required |
factor_col
|
str
|
Numeric column. Magnitude is preserved as a weight in the per-row product; only zero rows are filtered. |
'factor'
|
return_col
|
str
|
Column with forward/abnormal return. |
'forward_return'
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with columns |
Notes
Per event date \(d\),
\(\mathrm{CAAR}_d = \mathrm{mean}_{i \in \mathrm{events}(d)} (\mathrm{return}_i \times \mathrm{factor}_i)\).
Magnitude of factor is preserved as a weight; only rows with
factor == 0 are dropped.
factrix follows the MacKinlay (1997) event-window vocabulary
(factor as the event indicator / sign on the announcement date)
but generalises signed_car to numeric factor magnitude. With
a continuous factor column, the resulting CAAR is the
per-event regression-slope statistic in the
Sefcik-Thompson (1986) lineage rather than the equal-weighted
MacKinlay CAAR.
When factor_col triggers _is_sparse_magnitude_weighted
the primitive emits a Python UserWarning directly. The
sparse PANEL procedures additionally attach
WarningCode.SPARSE_MAGNITUDE_WEIGHTED to
FactorProfile.warnings independently — the dual emission is
deliberate so batch runs that silence Python warnings still
surface the regime-switch through the structured channel.
References
- MacKinlay (1997). "Event Studies in Economics
and Finance." Journal of Economic Literature, 35(1), 13–39.
Standardised event-window / estimation-window vocabulary
inherited by
EventConfig. - Sefcik & Thompson (1986). "An Approach
to Statistical Inference in Cross-Sectional Models with
Security Abnormal Returns as Dependent Variable." Journal of
Accounting Research, 24(2), 316–334. Per-event regression-
slope ancestor of the magnitude-weighted CAAR produced when
factoris continuous. - Brown & Warner (1985). "Using Daily Stock Returns: The Case of Event Studies." Journal of Financial Economics, 14(1), 3–31. Daily event-study methodology backing the parametric-test path.
Examples:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.caar import compute_caar
>>> panel = compute_forward_return(
... fx.datasets.make_event_panel(n_assets=50, n_dates=400, seed=0),
... forward_periods=5,
... )
>>> caar_df = compute_caar(panel)
>>> set(caar_df.columns) >= {"date", "caar"}
True
factrix.metrics.caar.caar ¶
caar(caar_df: DataFrame, *, forward_periods: int = 5) -> MetricOutput
CAAR significance: is mean CAAR significantly different from zero?
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
caar_df
|
DataFrame
|
Output of |
required |
forward_periods
|
int
|
Sampling interval for non-overlapping dates.
Maps to |
5
|
Returns:
| Type | Description |
|---|---|
MetricOutput
|
MetricOutput with value=mean CAAR, stat=t from non-overlapping sampling. |
Notes
\(t = \mathrm{mean}(\mathrm{CAAR}) / (\mathrm{std}(\mathrm{CAAR}) / \sqrt{n})\)
on a non-overlap subsample (stride forward_periods) of the
per-event-date \(\mathrm{CAAR}\) series;
\(H_0: \mathbb{E}[\mathrm{CAAR}] = 0\).
factrix uses non-overlap resampling rather than Newey-West (NW) heteroskedasticity-and-autocorrelation-consistent (HAC) for the
default CAAR test — the same convention as ic — and exposes
bmp_test as the variance-robust sibling for event-induced
variance regimes.
References
- Brown & Warner (1985). "Using Daily Stock Returns: The Case of Event Studies." Journal of Financial Economics, 14(1), 3–31. Daily event-study t-test specification at standard sample sizes.
- MacKinlay (1997). "Event Studies in Economics and Finance." Journal of Economic Literature, 35(1), 13–39. Event-window vocabulary.
Examples:
Chain from :func:compute_caar output:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.caar import compute_caar, caar
>>> panel = compute_forward_return(
... fx.datasets.make_event_panel(n_assets=50, n_dates=400, seed=0),
... forward_periods=5,
... )
>>> caar_df = compute_caar(panel)
>>> result = caar(caar_df, forward_periods=5)
>>> result.name
'caar'
factrix.metrics.caar.bmp_test ¶
bmp_test(df: DataFrame, *, factor_col: str = 'factor', return_col: str = 'forward_return', estimation_window: int = 60, forward_periods: int = 5, kolari_pynnonen_adjust: bool = False, include_prediction_error_variance: bool = False) -> MetricOutput
Boehmer-Musumeci-Poulsen Standardized Abnormal Return test.
Standardizes each event's abnormal return by the asset's pre-event residual volatility, making the test robust to event-induced variance inflation that biases the ordinary CAAR \(t\)-test.
Uses price column for estimation-window volatility if available;
falls back to per-asset historical forward_return std otherwise.
Steps
- For each event (\(\text{factor} \neq 0\)), look back
estimation_windowperiods of the same asset's returns to estimate \(\sigma_i\). - Scale \(\sigma_i\) to match the forward_return horizon.
- \(\mathrm{SAR}_i = \mathrm{AR}^{\mathrm{signed}}_i / \sigma^{\text{scaled}}_i\).
- \(z = \mathrm{mean}(\mathrm{SAR}) / (\mathrm{std}(\mathrm{SAR}) / \sqrt{N})\).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Full panel (including non-event rows) with |
required |
estimation_window
|
int
|
Number of periods before each event for volatility estimation (default 60). |
60
|
forward_periods
|
int
|
Return horizon for vol scaling (default 5).
When using price-derived daily vol, scales by
|
5
|
kolari_pynnonen_adjust
|
bool
|
When True, apply the
Kolari-Pynnönen (2010) adjustment for
cross-sectional correlation of SAR:
\(z_{\mathrm{KP}} = z_{\mathrm{BMP}} \cdot \sqrt{(1 - \hat r) / (1 + (N_{\mathrm{eff}} - 1) \cdot \hat r)}\)
where \(\hat r\) is the ICC-style within-date correlation of
SAR and
|
False
|
include_prediction_error_variance
|
bool
|
When True, inflate the
per-event standardiser by \(\sqrt{1 + 1/T_{\mathrm{est}}}\)
(with \(T_{\mathrm{est}}\) = Caveat: |
False
|
Returns:
| Type | Description |
|---|---|
MetricOutput
|
MetricOutput(name="bmp_test", value=mean_SAR, stat=z_bmp, ...). |
Notes
For each event \(i\): estimate pre-event vol \(\sigma_i\) over the
estimation_window, scaled to the forward horizon by
\(1/\sqrt{h}\) (with \(h\) = forward_periods) when daily prices are available;
\(\mathrm{SAR}_i = \mathrm{AR}^{\mathrm{signed}}_i / \sigma_i\); aggregate to
\(z = \mathrm{mean}(\mathrm{SAR}) / (\mathrm{std}(\mathrm{SAR}) / \sqrt{N})\).
With kolari_pynnonen_adjust=True, scale \(z\) by
\(\sqrt{(1 - \hat r) / (1 + (N_{\mathrm{eff}} - 1)\, \hat r)}\).
factrix simplifies the original BMP by omitting the prediction-
error term from the standardiser (using mean-adjusted residuals
rather than market-model residuals) — adequate for the default
Brown-Warner / MacKinlay event-study path; pair with the K-P
adjustment when clustering_hhi flags same-date shock sharing.
Pass include_prediction_error_variance=True for the strict
BMP denominator \(\sigma_i \cdot \sqrt{1 + 1/T_{\mathrm{est}}}\).
References
- Boehmer, Musumeci & Poulsen (1991). "Event-study Methodology Under Conditions of Event-induced Variance." Journal of Financial Economics, 30(2), 253–272. The BMP standardised AR test factrix simplifies (mean-adjusted residuals, no prediction-error correction by default).
- Kolari & Pynnönen (2010). "Event Study
Testing with Cross-sectional Correlation of Abnormal Returns."
Review of Financial Studies, 23(11), 3996–4025. Clustering-
adjusted BMP variant; enabled via
kolari_pynnonen_adjust=Trueon this function.
Examples:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.caar import bmp_test
>>> panel = compute_forward_return(
... fx.datasets.make_event_panel(n_assets=50, n_dates=400, seed=0),
... forward_periods=5,
... )
>>> result = bmp_test(panel, forward_periods=5)
>>> result.name
'bmp_test'
Event-study contracts
signed_car, the estimation_window consumed by bmp_test, and
factrix's confounded-event handling are documented in
Metric applicability § Event-study contracts.
factrix computes CAR (sum of per-period abnormal returns), not
BHAR; see the same section for the distinction.
Use cases¶
-
Per-event-date CAAR series
Build the per-event-date weighted abnormal return series from a long-format panel before any inferential test. Pre-step for
caarand (where the magnitude-weighted form is wanted) for downstream slicing. -
Mean-CAAR significance, non-overlapping
Test \(H_0: \mathbb{E}[\mathrm{CAAR}] = 0\) on the every-
forward_periodssubsample of the per-event-date CAAR series to avoid the autocorrelation induced by overlapping forward returns. Default parametric test for the event-sparse cell. -
Event-induced variance, BMP \(z\)-test
Standardise each event's abnormal return by the asset's pre-event residual volatility before pooling. Robust to event-induced variance inflation that biases the ordinary CAAR \(t\)-test; pair with
kolari_pynnonen_adjust=Truewhen the event-date Herfindahl-Hirschman index (HHI) flags same-date shock sharing. -
Magnitude-weighted CAAR
With a continuous
factorcolumn,compute_caarreturns the per-event regression-slope statistic in the Sefcik-Thompson (1986) lineage rather than the textbook equal-weighted MacKinlay CAAR — see the docstring for the input-contract table.
Choosing a function¶
| Goal | Function |
|---|---|
| Per-event-date CAAR table for downstream inspection / slicing | compute_caar |
| Mean-CAAR significance, deterministic non-overlap subsample | caar |
| Variance-robust event-induced significance (BMP standardised \(z\)) | bmp_test |
Worked example — per-event-date CAAR then mean significance¶
compute_caar → caar on a synthetic event panel
import factrix as fx
from factrix.metrics.caar import compute_caar, caar, bmp_test
from factrix.preprocess import compute_forward_return
raw = fx.datasets.make_event_panel(
n_assets=200, n_dates=500, event_rate=0.02,
post_event_drift=0.004, seed=2024,
)
panel = compute_forward_return(raw, forward_periods=5)
caar_df = compute_caar(panel)
print(caar_df.head())
# ┌────────────┬───────────┐
# │ date ┆ caar │
# ├────────────┼───────────┤
# │ 2024-01-04 ┆ 0.0041 │
# │ 2024-01-11 ┆ 0.0037 │
# │ ... ┆ ... │
# └────────────┴───────────┘
out = caar(caar_df, forward_periods=5)
print(out.value, out.stat, out.metadata["p_value"])
# 0.0039 6.42 1.4e-09 (approximate)
# Variance-robust alternative when same-date clustering is high:
z_bmp = bmp_test(panel, estimation_window=60, forward_periods=5,
kolari_pynnonen_adjust=True)
See also¶
-
clustering_diagnostic
Event-date HHI — when to switch on the Kolari-Pynnönen adjustment or read
caar's \(t\) with caution. -
by_slice
Axis-agnostic slice dispatcher for per-slice CAAR summaries.
-
slice_pairwise_test/slice_joint_test
Cross-slice CAAR inference (Wald \(\chi^2\) + Holm / Romano-Wolf adjusted \(p\)).
-
Statistical methods
CAAR cross-event \(t\), BMP standardised AR \(z\), Kolari-Pynnönen clustering adjustment.
-
Metric applicability reference
When this metric applies, sample-size guards, and the event-study contracts that fix
signed_car. -
Individual × Sparse landing
Adjacent event-study metrics in the same cell.