Universe: full _ADJ close universe via framework.load_close_volume; earnings data/earnings_dates.parquet (date,symbol,surprise_pct). IS=2020-01-01..2022-12-31, OOS>=2023-01-01 (split 2023-01-01). Cost 10bps one-way decimal 0.001 on turnover (active-set weight changes); net = gross - cost.shift(1) (cost paid when weights start earning). sqrt(252).
Loop: iterate post-2020 trading index, maintain active_until dict (expiry = activation + 60 calendar days); lag-1 adds earnings with date < current day only. Announcement return = close[next trading day after date]/close[prev trading day]-1 via index positions (searchsorted), never same-day. Turnover = sum|w[t]-w[t-1]|; equal-weight; dead names excluded (renormalized mean). EqMom baseline L126/H21 top50 net and reversal-long (-ret5/H5 top50 net) via cross_section_portfolio_net (next_open, 10bps) on the same universe; market = EW mean.
| Variant | IS Ret | IS Sharpe | IS MaxDD | OOS Ret | OOS Sharpe | OOS MaxDD | corrEqMom | corrMkt | residSharpe | beta | p_vs_topQ_OOS | |---|---|---|---|---|---|---|---|---|---|---|---|---| | BASE any-positive H60 | -4.11% | 0.12 | -51.74% | +74.65% | 1.15 | -18.48% | 0.68 | 0.98 | -1.44 | 0.98 | 0.990 | | BASE top-Q H60 (cap50) | +4.09% | 0.21 | -51.74% | +115.58% | 1.37 | -20.11% | 0.75 | 0.94 | 0.06 | 1.08 | — | | A3a 52w>=95% (top-Q) | -19.65% | -0.37 | -34.94% | +43.59% | 0.72 | -20.58% | 0.68 | 0.66 | -0.30 | 0.72 | 0.970 | | A3a top-half proximity (top-Q) | -18.96% | -0.07 | -49.67% | +106.27% | 1.38 | -18.78% | 0.78 | 0.83 | 0.33 | 0.89 | 0.652 | | A3b jump>0 (top-Q) | -16.26% | -0.02 | -54.88% | +91.45% | 1.15 | -20.43% | 0.77 | 0.90 | -0.34 | 1.06 | 0.933 | | A3b jump>median (top-Q) | -13.17% | 0.01 | -49.24% | +103.78% | 1.20 | -21.93% | 0.78 | 0.88 | -0.12 | 1.08 | 0.681 | | C8 intersect low-ret5 (top-Q) | +1.47% | 0.20 | -50.29% | +85.73% | 0.97 | -24.99% | 0.71 | 0.88 | -0.59 | 1.19 | 0.842 | | C8 blend 0.5topQ+0.5reversal | +12.92% | 0.29 | -50.76% | +101.43% | 1.13 | -20.48% | 0.72 | 0.92 | -0.49 | 1.19 | 0.679 | | REF EqMom L126/H21 top50 net | +70.58% | 0.78 | -34.17% | +203.95% | 1.35 | -30.08% | 1.00 | 0.66 | 0.55 | 1.15 | — | | REF reversal -ret5/H5 top50 net | +17.94% | 0.34 | -53.68% | +85.54% | 0.88 | -21.48% | 0.64 | 0.84 | -0.61 | 1.31 | — | | REF EW market mean | +46.01% | 0.60 | -38.32% | +102.93% | 1.43 | -17.90% | 0.66 | 1.00 | -1.43 | 1.00 | — |
Verdicts (adopt requires OOS p<0.05 vs top-Q OR DD cut>=5pp with Sharpe within -0.10 and corr drop vs top-Q)
- BASE any-positive H60: REJECT (p=0.990, dDD=+1.63%, dSharpe=-0.22, dCorr=-0.07).
- BASE top-Q H60 (cap50): BASELINE — no verdict.
- A3a 52w>=95% (top-Q): REJECT (p=0.970, dDD=-0.47%, dSharpe=-0.65, dCorr=-0.07).
- A3a top-half proximity (top-Q): REJECT (p=0.652, dDD=+1.33%, dSharpe=+0.01, dCorr=+0.03).
- A3b jump>0 (top-Q): REJECT (p=0.933, dDD=-0.31%, dSharpe=-0.22, dCorr=+0.02).
- A3b jump>median (top-Q): REJECT (p=0.681, dDD=-1.81%, dSharpe=-0.17, dCorr=+0.03).
- C8 intersect low-ret5 (top-Q): REJECT (p=0.842, dDD=-4.88%, dSharpe=-0.39, dCorr=-0.04).
- C8 blend 0.5topQ+0.5reversal: REJECT (p=0.679, dDD=-0.37%, dSharpe=-0.23, dCorr=-0.03).
Notes
- BASE top-Q: surprise>=80th pct among that activation day's positives (>=5 names else any-positive); active cap 50 largest surprise.
- A3a-95 keeps active top-Q with close>=0.95*rolling252max; A3a-half keeps top-half by proximity (close/high252 >= median).
- A3b-pos keeps annret>0; A3b-med keeps annret>median that day (strict >).
- C8-blend = 0.5top-Q net + 0.5reversal net (no extra cost); C8-inter = top-Q names also in bottom-half of past-5d return (ret5 <= median).
- Guard:
assert_no_lookahead_dfon the a3b_med builder taking close_df (earnings dates fixed; perturbing future closes leaves past nets unchanged). - Turnover uses active-set weight changes; initial-entry cost is charged (1.0 turnover day 0 deducted day 1) — 10bps more conservative than framework's NaN-first-row convention; steady-state identical.