TradingAgents/docs/iterations/research/2026-04-14-pead-earnings-be...

82 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research: Post-Earnings Announcement Drift (PEAD)
**Date:** 2026-04-14
**Mode:** autonomous
## Summary
PEAD is one of finance's most-studied anomalies: stocks that beat earnings estimates
continue drifting upward for days to weeks after the announcement. QuantPedia backtests
(19872004) show 15% annualized returns; the effect is strongest in small-to-mid caps
with >10% EPS surprise. Our pipeline has an `earnings_calendar` scanner that predicts
upcoming earnings but nothing that captures the drift *after* a beat — this is the gap.
## Sources Reviewed
- **QuantPedia — Post-Earnings Announcement Effect**: Combined EAR+SUE strategy generates
~12.5% abnormal returns p.a. (19872004); optimal hold ~60 trading days; effect strongest
in small caps; most returns on long side; -11.2% max drawdown observed.
- **Ball & Brown (1968) / Bernard & Thomas (1989)**: Foundational PEAD literature;
B&T (1989) documented ~18% annualized abnormal returns; magnitude has declined since
but effect persists — particularly in small caps.
- **DayTrading.com PEAD guide**: Drift persists through approximately day 9 before
plateauing; 520 day hold periods are optimal for tactical implementations.
- **SSRN / Philadelphia Fed (PEAD.txt, 2021)**: NLP-enhanced PEAD achieves 8.01%
drift over 1-year window; suggests signal is durable when combined with text signals.
- **QuantConnect price+earnings momentum**: Combined momentum strategy showed mixed results
(Sharpe -0.27) when using *price* momentum alongside earnings growth — not the same as
surprise-based PEAD.
- **Alpha Architect — 13F data quality warning**: 13F-based institutional signals have 45-day
lag and data quality issues — screened out as alternative. PEAD is clearly superior for
short-horizon plays.
- **Finnhub API docs / finnhub-python**: `earnings_calendar(from_date, to_date)` returns
`epsActual` and `epsEstimate` for all US stocks in the window. Surprise detection requires
only a lookback call — no extra data sources needed.
## Fit Evaluation
| Dimension | Score | Notes |
|-----------|-------|-------|
| Data availability | ✅ | `finnhub_api.get_earnings_calendar()` already integrated; returns `epsActual` + `epsEstimate`; lookback call detects recent beats |
| Complexity | moderate | ~3h: query past-14d earnings calendar, filter for beats, compute surprise%, sort by magnitude |
| Signal uniqueness | low overlap | `earnings_calendar` scanner = UPCOMING earnings; PEAD scanner = RECENT beats + drift capture; different timing and signal |
| Evidence quality | backtested | QuantPedia: 15% annualized returns (19872004); Bernard & Thomas (1989); 60+ years of academic literature |
## Recommendation
**Implement** — All auto-implement thresholds pass.
Key implementation notes:
- Focus on small-to-mid cap stocks where PEAD effect is strongest (B&T 1989)
- Minimum 5% surprise threshold to filter noise
- CRITICAL at >20% surprise, HIGH at 1020%, MEDIUM at 510%
- Hold horizon: 714 days (primary drift window per DayTrading.com)
- Declining US large-cap PEAD mitigated by: small-cap bias + significant surprise filter
## Known Failure Modes
- US large-cap PEAD has declined since 1989 (more efficient pricing); strategy most
effective for small/mid caps and significant surprises (>10%)
- SUE reversal after 3 quarters (price reverts on next earnings); this is beyond our
30d evaluation window so not immediately harmful
- Overlapping earnings: same ticker may appear in `earnings_calendar` (upcoming) and
`earnings_beat` (recent); ranker should treat these as separate signals
## Proposed Scanner Spec
- **Scanner name:** `earnings_beat`
- **Strategy:** `pead_drift`
- **Pipeline:** `events`
- **Data source:** `tradingagents/dataflows/finnhub_api.py``get_earnings_calendar(from_date, to_date, return_structured=True)`
- **Signal logic:**
- Query past `lookback_days` (default 14) of earnings calendar
- Compute `surprise_pct = (epsActual - epsEstimate) / abs(epsEstimate) * 100`
- Filter: `surprise_pct >= min_surprise_pct` (default 5.0%)
- Filter: `epsEstimate != 0` and both fields not None
- Sort by `surprise_pct` descending
- **Priority rules:**
- CRITICAL if `surprise_pct >= 20`
- HIGH if `surprise_pct >= 10`
- MEDIUM otherwise
- **Context format:** `"Earnings beat Xd ago: actual $A vs est $B (+Z% surprise) — PEAD drift window open"`