factrix.metrics.event_horizon ¶
Multi-horizon event analysis — how does the signal behave across time?
Answers
- Is there pre-event leakage? (T-6..T-1 should be ~0)
- At which horizon is the signal strongest?
- Does the alpha persist or decay quickly?
Metrics
compute_event_returns — per-event, per-offset raw return data event_around_return — return profile summary at each offset
Notes
Pipeline. Per-event return profile across k offsets
(per-event step); descriptive curve plus binomial test on
per-horizon hit rate.
factrix.metrics.event_horizon.compute_event_returns ¶
compute_event_returns(df: DataFrame, *, offsets: list[int] | None = None, factor_col: str = 'factor', price_col: str = 'price') -> DataFrame
Per-event return at multiple time offsets relative to event date.
For each event (factor != 0) and each offset k:
- Post-event (k > 0): cumulative return from t+1 entry.
signed_return = sign(factor) × (price[t+1+k] / price[t+1] - 1)
- Pre-event (k < 0): single-bar return at that offset.
return = price[t+k] / price[t+k-1] - 1 (unsigned, for leakage check)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Panel with |
required |
offsets
|
list[int] | None
|
Time offsets relative to event date.
Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with |
DataFrame
|
Empty if |
Notes
For each event (t_event, asset) and offset k::
k > 0: signed_return = sign(factor) *
(price[t+1+k] / price[t+1] - 1)
k < 0: signed_return = price[t+k] / price[t+k-1] - 1
factrix asymmetrically signs only post-event returns. Pre-event single-bar returns are unsigned because they are read for leakage detection, where the absolute response (independent of the eventual signal direction) is what matters.
Examples:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.event_horizon import compute_event_returns
>>> panel = compute_forward_return(
... fx.datasets.make_event_panel(n_assets=50, n_dates=400, seed=0),
... forward_periods=5,
... )
>>> per_event = compute_event_returns(panel)
>>> set(per_event.columns) >= {"offset", "date", "asset_id", "signed_return"}
True
factrix.metrics.event_horizon.event_around_return ¶
event_around_return(df: DataFrame, *, offsets: list[int] | None = None, factor_col: str = 'factor', price_col: str = 'price') -> MetricOutput
Return profile at multiple offsets around event date.
Summarizes per-offset: mean, median, p25, p75, hit_rate, n.
The primary value is the pre-event leakage score: mean absolute return at pre-event offsets (should be ~0). High leakage → signal may be reactive, not predictive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
Panel with |
required |
offsets
|
list[int] | None
|
Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
MetricOutput
|
MetricOutput with per-offset stats in metadata. When price data is |
MetricOutput
|
unavailable, returns a short-circuit MetricOutput ( |
MetricOutput
|
|
MetricOutput
|
single return contract. |
Notes
For each offset k: mean, median, p25, p75, hit_rate
across events with valid signed_return. The headline
value = mean_{k < 0} |mean_k| summarises pre-event leakage —
a healthy signal has flat pre-event means.
factrix uses |mean| rather than absolute returns to avoid
rewarding offsets where positive and negative pre-event drifts
cancel — leakage with consistent direction would be missed by
mean(|return|).
Examples:
>>> import factrix as fx
>>> from factrix.preprocess import compute_forward_return
>>> from factrix.metrics.event_horizon import event_around_return
>>> panel = compute_forward_return(
... fx.datasets.make_event_panel(n_assets=50, n_dates=400, seed=0),
... forward_periods=5,
... )
>>> result = event_around_return(panel)
>>> result.name
'event_around_return'
Offset conventions
Defaults: offsets = [-6, -3, -1, 1, 6, 12, 24]. Offset 0 is the
event date itself and is excluded from the defaults; user-supplied
offsets lists are honoured verbatim.
| \(k\) | Anchor | Formula | Sign-adjusted |
|---|---|---|---|
| \(k > 0\) (post-event) | Cumulative from \(t+1\) entry | price[t+1+k] / price[t+1] − 1 |
Yes — multiplied by sign(factor). The reading is signal quality. |
| \(k < 0\) (pre-event) | Single bar at offset | price[t+k] / price[t+k−1] − 1 |
No — the reading is leakage, where the bar's directional response matters independent of the eventual signal sign. |
| \(k = 0\) (corner) | Single bar at event | price[t] / price[t−1] − 1 |
No — falls into the pre-event branch. Pass with care; the event-day bar is usually contaminated by the announcement itself. |
The pre/post asymmetry is intentional. Mixing the two conventions on a single chart (post-event cumulative + pre-event single-bar) is the default factrix presentation; downstream consumers should not re-cumulate the pre-event leg.
Serial correlation across offsets
The binomial null at each offset assumes per-event independence at that offset. Adjacent post-event offsets are serially correlated within the same event — \(k = 6\) and \(k = 12\) share the \(t+1\) entry price and overlap on bars \([t+2, t+7]\). The reported per-offset \(p\)-values therefore have understated variance under the joint null across offsets; treat the curve as descriptive and read the binomial \(p\) one offset at a time. See also the confounded-event note on within-asset event clustering, which compounds the same issue.
Use cases¶
-
Right-size the event window
Read the post-event mean curve over \(k = 1 \ldots K\) to locate the horizon where signed drift peaks before reverting. Drives the choice of
EventConfig.event_window_postfor downstream MFE/MAE and CAAR work. -
Pre-event leakage check
Inspect \(k < 0\) mean returns: a healthy signal has flat pre-event means. The headline
event_around_return.valueis \(\mathrm{mean}_{k < 0} |\mathrm{mean}_k|\), summarising the leakage score in a single number.
Choosing a function¶
| Goal | Function |
|---|---|
| Per-event, per-offset raw return table for custom plots / cuts | compute_event_returns |
| Per-offset summary (mean / median / quartiles / hit-rate) with pre-event leakage headline | event_around_return |
Worked example — leakage score + per-offset curve¶
compute_event_returns → event_around_return on a synthetic event panel
import factrix as fx
from factrix.metrics.event_horizon import (
compute_event_returns, event_around_return,
)
panel = fx.datasets.make_event_panel(
n_assets=200, n_dates=500, event_rate=0.02,
post_event_drift=0.004, with_price=True, seed=2024,
)
rets = compute_event_returns(panel, offsets=[-6, -3, -1, 1, 6, 12, 24])
print(rets.head())
# ┌────────┬────────────┬──────────┬────────────────┐
# │ offset ┆ date ┆ asset_id ┆ signed_return │
# ├────────┼────────────┼──────────┼────────────────┤
# │ -6 ┆ 2024-01-04 ┆ A0001 ┆ 0.0012 │
# │ 1 ┆ 2024-01-04 ┆ A0001 ┆ 0.0041 │
# │ ... ┆ ... ┆ ... ┆ ... │
# └────────┴────────────┴──────────┴────────────────┘
out = event_around_return(panel)
print(out.value) # mean |pre-event mean|
print(out.metadata["per_offset"][6]["mean"]) # post-event signed mean at k=6
# 0.0007 0.0094 (approximate)
See also¶
-
mfe_mae
Per-event excursion analysis on the same post-event window — peak favourable / adverse move and bars-to-peak.
-
caar/bmp_test
Inferential CAAR / BMP tests on the chosen
forward_periodshorizon. -
clustering_diagnostic
Event-date Herfindahl-Hirschman index (HHI) — the serial-correlation caveat above compounds with same-date clustering.
-
Metric applicability reference
Event-window / estimation-window contracts and confounded-event handling.
-
Individual × Sparse landing
Adjacent event-study metrics in the same cell.