Buy US small/mid caps that beat consensus EPS by ≥10% and hold for 90 trading days. The classic “concordant” reaction-day filter adds nothing; the real, statistically significant edge is surprise magnitude.
The PEAD magnitude effect is real and statistically significant on US small/mid caps: buying stocks whose EPS beats consensus by ≥10% earns ~+0.97pp per 60 days over the universe baseline (p = 0.0001, n = 11,858 on the broad Russell 2000; +3.40pp, p = 0.002 on curated small caps).
However, in a realistic concurrent-portfolio model the best configuration returns +70.5% over 8.6 years (PF 1.29) — while simply buying the same universe equal-weight returned +120% (buy & hold) or +799% (daily rebalanced). The differential is real; the market beta eats it. Use as a portfolio tilt or an alpha overlay, not a standalone book.
Post-Earnings Announcement Drift (Bernard & Thomas 1989) is the observation that stock prices continue to drift in the direction of an earnings surprise for weeks after the announcement, instead of adjusting instantly. The academic effect is strongest in smaller, less-covered stocks.
The widely-circulated rule trades “concordant” events only: a beat that also rose on announcement day (long) or a miss that fell (short), entered at the next session’s open with a 60-trading-day hold.
The concordant reaction-day filter adds no value — and loses money in the realistic portfolio model at every holding period. On small caps, beats that fell on announcement day had higher 60-day forward returns than beats that rose (reaction-day mean reversion).
Surprise magnitude. Monotonic in the beat size: 0–2% → +1.95%, 2–5% → +1.02%, 5–10% → +5.08%, >10% → +8.12% per 60 days on small caps. Only the >10% bucket is statistically significant. This is the classic, academically-documented PEAD magnitude effect.
Best configuration found across all tested variants (R2000 expanded universe).
| Component | Rule | Notes |
|---|---|---|
| Universe | US Russell 2000 (point-in-time Jan-2016 snapshot) | 909 of 1,909 names had data; 862 delisted names excluded by design (survivorship guard) |
| Liquidity filter | Average daily volume ≥ $2M | 22,113 of 26,674 events pass |
| Signal | Reported EPS beats consensus by ≥ 10% | Surprise = actual vs pre-announcement analyst consensus (Yahoo) |
| Direction | Long only | Short side (miss + down) has significant differential (−3.18pp, p=0.035) but poor absolute economics — hedge only |
| Entry | Open of next trading session after announcement | Reaction window uses only prices known before entry — no lookahead |
| Hold | 90 trading days (30 / 45 / 60 / 90 tested) | 90d best; 60d per Bernard & Thomas |
| Exit | Close of the Nth trading day | No stops, no intermediate exits in the basic version |
| Sizing | 10% of equity per signal, max gross exposure 100% | Capacity-aware: min(pos% × equity, equity − committed) |
| Costs | 10 bps round-trip baseline | Survives 20 bps; profit factor < 1 at 50 bps |
| Market | Edge | Evidence | Verdict |
|---|---|---|---|
| US small/mid caps (curated, 37 names) | +3.40pp / 60d | p = 0.002 · n = 462 · 61% win · PF 3.2 (gross, standalone) | Strongest signal |
| US Russell 2000 (broad, 909 names) | +0.97pp / 60d | p = 0.0001 · n = 11,858 | Real but thin |
| US large / mega caps (45 names) | +0.05pp / 60d | p = 0.93 — no exploitable edge; all groups ≈ baseline beta | No edge |
| ASX large caps (13 names) | +0.84pp / 60d | p = 0.39 · every variant underperforms B&H of the same names (+104%) | No edge |
| Short side (miss + down, small caps) | −3.18pp / 60d | p = 0.035 — significant differential, weak absolute economics | Hedge only |
Realistic concurrent portfolio with daily mark-to-market, 10 bps round-trip costs, $100k start, 2018-01-01 → 2026-08-27. Long-only >10% beats.
| Hold | Trades | Final | Total ret | CAGR | Max DD | Win rate | PF |
|---|---|---|---|---|---|---|---|
| 30d | 1,157 | $139,245 | +39.2% | 3.5% | −41.0% | 49.1% | 1.08 |
| 45d | 881 | $142,638 | +42.6% | 3.7% | −47.9% | 49.4% | 1.11 |
| 60d | 846 | $126,442 | +26.4% | 2.4% | −41.4% | 54.4% | 1.08 |
| 90d | 812 | $170,532 | +70.5% | 5.6% | −51.6% | 50.6% | 1.29 |
| Round-trip cost | Net return | Profit factor | Edge status |
|---|---|---|---|
| 0 bps | +26.4% | 1.12 | Alive |
| 10 bps | +26.2% | 1.08 | Alive |
| 20 bps | +25.9% | 1.05 | Marginal |
| 50 bps | +25.2% | 0.96 | Dead (PF < 1) |
Best public chart: 60d hold portfolio (the full R2000 results set also has a static PNG below). The strategy line is the big-beat long-only portfolio; benchmarks are equal-weight constructions of the same 909-name universe.
Final values (same window): strategy $126,442 (+26%) · buy & hold $219,897 (+120%) · daily rebalanced $899,434 (+799%). The +799% figure quoted in the source README corresponds to the daily-rebalanced series.
src/chart_r2000.py (matplotlib, 130 dpi).
The honest way to read a long-only backtest: compare each event group’s 60-day forward return against the universe baseline. Compounded dollar totals are leveraged-beta artifacts; these differentials are the signal.
| Group | n | Avg 60d | Diff | p |
|---|---|---|---|---|
| beat >10% (LONG) | 462 | +8.12% | +3.40pp | 0.002 |
| beat 5–10% | 191 | +5.08% | +0.37pp | 0.83 |
| beat_up (concordant LONG) | 503 | +4.90% | +0.18pp | 0.84 |
| beat_down | 385 | +6.69% | +1.98pp | 0.10 |
| miss_down (concordant SHORT) | 188 | +1.54% | −3.18pp | 0.035 |
| Group | n | Avg 60d | Diff | p |
|---|---|---|---|---|
| beat_up (concordant LONG) | 651 | +4.26% | +0.05pp | 0.93 |
| beat_down | 592 | +3.68% | −0.52pp | 0.36 |
| beat >10% | 477 | +5.58% | +1.38pp | 0.073 |
| miss_down (concordant SHORT) | 149 | +4.36% | +0.16pp | 0.91 |
Unconditional 60-day forward returns on US mega-caps were +4.2% (≈ market beta). Every long rule compounds into large dollar figures; the differential vs baseline is the only honest signal.
At 909 names the >10%-beat edge thins to +0.97pp/60d vs +3.40pp on the curated 37. Statistical significance ≠ standalone investability.
The R2000 universe is survivorship-free, but the curated 37-name small-cap set uses names alive today — likely overstates that leg.
R2000 micro-caps gap, halt, and have missing days. Portfolio MTM uses as-of lookups; fill assumptions at 10bps are optimistic for the smallest names. Capacity is limited.
13 names, 137 events, time-series (YoY) surprise instead of analyst consensus — the null result there is suggestive, not conclusive.
Single 2018–2026 window dominated by a small-cap bull phase. Walk-forward framework exists in the repo but headline results are full-window.
All evidence comes from jahrfm/pead. Data is regenerable from free keyless sources.
# build data (yfinance + Market Index API + PDF parsing)
pip install -r requirements.txt
python src/build_r2000_data.py # Russell 2000 (point-in-time 2016 snapshot)
python src/build_us_data.py # US large caps
python src/build_us_small_data.py # US small caps
python src/build_au_data.py # ASX
# run backtests
python src/run_r2000.py # expanded R2000 (recommended)
python src/run_us_backtest.py # US large/small
python src/run_au_backtest.py # ASX
results/, full results JSON in
results/us_r2000_results.json, 15k-word backtesting best-practices guide in
docs/BACKTESTING_BEST_PRACTICES.md.