Event studies & seasonality
Does the market rally after big down days? Do earnings surprises drift? Is there really a Monday effect? Each is a claim that returns behave differently around a defined event — and the event study is the classical design for testing it: align every occurrence on a common clock, average out the noise, and ask whether what remains is distinguishable from zero. It is a beautiful machine, and — because you can define infinitely many events — one of the easiest to fool yourself with.
Any single post-event window is dominated by noise: the market fell 2% on Tuesday and rose Wednesday — so what? The event study’s move is to collect every 2%-down day in the sample, cut out the window of days around each one, stack the windows on a shared axis (day 0 = the event), and average across the stack. Idiosyncratic noise cancels at rate ; any systematic reaction survives the averaging. The design turns one unanswerable anecdote into repeated experiments. Everything else in this chapter — abnormal returns, CAR, its variance, the bootstrap null — is bookkeeping to make the averaging honest.
The design: estimation window vs event window#
For each event at date , define two disjoint windows on the event clock:
- Estimation window — for example the 60 days ending beforethe window of interest, used only to estimate what “normal” looks like: the benchmark mean return and variance .
- Event window — relative days around the event (say to ), where the hypothesised reaction lives. It must never overlap the estimation window, or the event contaminates its own benchmark.
The abnormal return is the raw return minus the benchmark. The simplest benchmark is the estimation-window mean (mean-adjustment, ); the next step up is market-adjustment, subtracting from a CAPM fit on the estimation window, so a market-wide move on the event day is not mistaken for a reaction. Either way the logic is identical: normal behaviour is estimated out of the event window and subtracted in it.
Defining the event without peeking#
The event definition itself must be causal: computable at the moment it fires, from information available then. “Daily return below ” qualifies. “Days in the worst 5% of the sample” does not — the threshold uses the whole sample, so early events are defined by data from their own future, the same look-ahead leak that poisons backtests. A clean mechanical option is the CUSUM filter, ek.timeseries.cusum_breaks: it accumulates standardised surprises and fires an event when the running sum crosses a threshold, using only past data — and it naturally spaces events out, which limits the window-overlap problem below.
AAR and CAR#
Average the abnormal returns across the events at each relative day, then accumulate through the window:
The average abnormal return (AAR) shows the shape of the reaction day by day; the cumulative abnormal return (CAR) is the total drift over the window — the number a trading rule could actually have captured. A real effect shows up as a CAR path that departs from zero after day 0 and stays departed; a CAR that wanders back is noise doing what noise does.
Under the null there is no event effect: each is just noise with variance (estimated from the estimation window). If events are independent of each other, averaging across events divides the variance by :
If daily abnormal returns are also serially uncorrelated within the window, the variance of a sum is the sum of variances, so over a window of days:
Read the denominator. Detectability improves with (more events) but degrades with (longer windows accumulate noise as fast as they accumulate effect). A 30bp drift over 3 days is far easier to detect than the same 30bp smeared over 30 days. And both independence assumptions are load-bearing: overlapping event windows (clustered events) correlate the across and shrink the effective — the same effective-sample-size trap as in cross-sectional inference.

The bootstrap null band#
The analytic t-test leans on Gaussian, independent noise — both dubious for daily returns. The nonparametric alternative asks the question directly: what CAR paths do randomly chosen non-event dates produce? Draw fake event dates at random, compute the CAR exactly as for the real events, repeat a few thousand times, and read the 2.5% and 97.5% quantiles at each relative day. That is a null bandbuilt from the actual return distribution — fat tails, clustering and all. A real CAR that exits the band is significant on the data’s own terms; this is the same placebo logic as the permutation tests in the gauntlet.

import edgekit as ek
# 1. define events mechanically — here, CUSUM breaks on the return series
events = ek.timeseries.cusum_breaks(close_returns, threshold=5.0)
# 2. run the study: market-adjusted ARs, AAR/CAR, analytic t-stat
es = ek.factors.event_study(returns, events,
window=(-5, 10), estimation=60)
es["aar"] # Series indexed by relative day
es["car"] # cumulative abnormal return path
es["t_car"] # t-stat on the full-window CAR
es["n_events"] # how many events actually had clean windows
# 3. bootstrap the null: same study on random placebo dates
rng = ek.core.bootstrap_rng(seed=11)
null_cars = []
for _ in range(2000):
fake = list(rng.choice(len(returns) - 20, size=es["n_events"]))
null_cars.append(ek.factors.event_study(
returns, fake, window=(-5, 10), estimation=60)["car"])Define the event as “daily return below ” and suppose a 10-year daily sample contains such days. To see what a detectable effect looks like, inject one: add a bp drift spread over the 3 days after each event in a simulated series with daily . The study should recover , and the t-stat follows from the variance formula:
A genuinely injected 30bp bounce and the design cannot see it with 48 events — the noise floor bp is nearly the size of the effect. You would need roughly events (or a tighter window) before . Now run it in reverse: on real data, a reported after down days, with , is exactly the size of finding the bootstrap band routinely produces — and 2%-down days cluster in crashes, so the windows overlap and the effective is well below 48. The honest summary: with rare events, daily noise, and clustering, most tradable-sized effects are statistically invisible — and most “statistically visible” ones are artifacts.
Seasonality is a repeated event study#
Every calendar effect — Monday effect, turn-of-month, December rally, options-expiry week — is an event study where the “event” is a date pattern rather than a market condition. The same machinery applies verbatim: the events are all Mondays, the window is the day itself, AAR is the average Monday return, and the null band comes from bootstrapping random weekdays. What changes is the multiplicity: the calendar offers an enormous menu of patterns to test, which is precisely the problem.
Ten years of daily data holds about Mondays. Suppose Mondays averaged bp against bp for other weekdays — an 8bp gap, the classic finding. With daily , the standard error of the Monday mean is bp, so the gap prints — unremarkable. The original studies reached significance with much longer samples; on post-publication data the effect flipped sign. Five hundred repetitions of a tiny effect against 1% daily noise is still a weak experiment — seasonality claims need either decades of data or effect sizes that would be visible to the naked eye.
The honesty section: infinite events, one dataset#
The event-study design has a silent free parameter: the definition of the event. Down 2% or down 3%? Close-to-close or intraday? Rally over 1 day, 3 days, or 10? Each choice is a new hypothesis, and the combinatorics explode:
| Menu | Choices | Cumulative tests |
|---|---|---|
| Event threshold | -1%, -2%, -3%, -5% | 4 |
| Response window | (0,1), (1,3), (1,5), (1,10) | 16 |
| Adjustment | mean, market, none | 48 |
| Calendar variants | Mondays, month-end, expiry, pre-holiday… | 300+ |
At 300 implicit tests, the 5% false-positive rate guarantees a shelf of “significant” effects in pure noise — the same arithmetic as the multiple-testing chapter, now wearing a calendar. This is how the finance literature accumulated dozens of seasonal anomalies that evaporated on fresh data.

Next: from reactions around events to a return source that needs no event at all — the yield an asset pays you just for holding it. Carry & term structure.


