Holds SPY only during the four annual earnings-season windows — in just after Jan/Apr/Jul/Oct 20, out just after Feb/May/Aug/Nov 5.
Real internal-backtest statistics from the registry — Internal backtest · SPY daily bars (Yahoo Finance) · 133 trades · 1993–2026.
CAGR and exposure are not published for the library yet — we show “—” rather than invent them. Max drawdown, best year and worst year are undefined for the pooled Momentum universe backtest. Win rate is a share of trades, average return is per trade, and holding period is in trading days.
Earnings Season — average cumulative return across the held window
Trading sessions from entry (0 = buy at the entry close; the fixed exit lands at ~session 10–12) · Average cumulative return (%). Empirical average path computed across all 133 completed backtest windows on real SPY daily closes. Holds cluster at 9–12 sessions; each trade's realized return is held flat after its exit, so every point averages the SAME 133 trades (no survivorship bias) and the final session equals the mean per-trade return. Labelled basis:"derived" (computed from our own backtest); the frozen v1 enum has no "empirical" value.
Every quarter, the bulk of S&P 500 companies report earnings in a concentrated ~3-week burst. This strategy is long SPY across those four windows and flat otherwise:
There are no indicators, no moving averages, no stop-loss, no profit target. The dates are the entire strategy.
Earnings season is when the market re-prices the fundamentals of its largest members all at once. Historically, the index has tended to drift higher through the heart of reporting season as results and forward guidance arrive and positioning adjusts; concentrating exposure there and holding cash between seasons aims to capture that drift while avoiding the quieter, choppier gaps.
The "earnings season effect" is a documented tendency for equity returns to cluster around reporting periods (an aggregate-market cousin of the well-studied post-earnings-announcement drift at the single-stock level). Positive aggregate surprises, buybacks resuming after blackout windows, and re-risking into results have all been cited. It is a tendency, not a guarantee.
Constructive markets where earnings and guidance are broadly met or beaten and the index grinds higher through reporting season.
Seasons dominated by misses / guidance cuts, or a macro shock (rate scare, risk-off event) that lands inside a window. With no stop, a bad window is taken in full — e.g., the August 2011 debt-ceiling window.
One pure seasonal core (earningsSeasonSignalFromBars) backs the live state, the calendar scanner, and the backtest adapter, using the shared trading calendar so all three agree on the anchors.
The first trading session after each of January 20, April 20, July 20, October 20 — i.e. the first open session on/after the 21st. BUY SPY at that session's close.
The first trading session after each of February 5, May 5, August 5, November 5 — i.e. the first open session on/after the 6th. SELL at that session's close.
Detected by boundary-crossing on actual bar dates (weekends/holidays skipped), so each fires exactly once per quarter and lands on the correct open session regardless of the calendar.
entryPrice/stopLoss/firstTarget/riskPercent/rewardRiskRatio are null (never invented). Risk control is the short, fixed hold and being in cash between seasons.GET /api/scan?strategy=earnings_season reuses the same core: it is a calendar scanner (the window is a market-wide date, so every symbol reads the same on a given day) — a BUY only on an entry session, a SELL only on an exit session, NONE otherwise.
Deterministic, using the shared seasonalConfidence model — the same philosophy as Santa Claus Rally. confidenceScore is a setup-quality measure (0–100), not a probability of profit. It depends on proximity to the window, whether the strategy is active, and the distance to entry:
| Component | Max | Awarded |
|---|---|---|
seasonalWindow | 60 | 60 while the window is Active (today between entry and exit), else 0. |
windowProximity | 40 | Ramps 40 × (10 − daysUntilEntry)/10 over the 10 calendar days before entry; 0 when far away or finished. |
Bands: <70 Weak · 70–79 Good · 80–89 Strong · 90–100 Exceptional.
The API returns ["Earnings-season window is active"] while a window is Active, else [].
basis enum gap** — the empirical pattern is labelled "derived" because the frozen v1 enum has no "empirical" value; the real provenance is in the note.historical.* and the pattern are baked as of 2026; the live /api/backtest always recomputes.historical block (which has no fields for them); the endpoint reports totalReturn.Real responses from the live endpoints for this strategy.
GET /api/strategies?id=earnings_season{
"strategies": [
{
"id": "earnings_season",
"name": "Earnings Season Strategy",
"category": "Seasonality",
"asset": "SPY",
"description": "WHAT IT IS: The Earnings Season Strategy is a pure seasonal, calendar-timed strategy…",
"historical": {
"yearsTested": 34,
"winRate": 0.6842,
"averageReturn": 0.87,
"bestYear": 15.98,
"worstYear": -12.33,
"maxDrawdown": 17.71,
"averageHoldingPeriod": 11,
"profitFactor": 2.02,
"totalTrades": 133,
"source": "Internal backtest · SPY daily bars (Yahoo Finance) · 133 trades · 1993–2026"
},
"historicalPattern": {
"title": "Earnings Season — average cumulative return across the held window",
"xLabel": "Trading sessions from entry (0 = buy at the entry close; the fixed exit lands at ~session 10–12)",
"yLabel": "Average cumulative return (%)",
"basis": "derived",
"source": "Internal backtest — 133 completed SPY windows (Yahoo Finance daily closes), 1993–2026",
"note": "Empirical average path… each trade's realized return held flat after its exit, so every point averages the same 133 trades and the final session equals the mean per-trade return.",
"series": [
{ "day": 0, "label": "Entry", "cumulativeReturn": 0.0 },
{ "day": 1, "label": "S1", "cumulativeReturn": 0.13 },
{ "day": 2, "label": "S2", "cumulativeReturn": 0.2 },
{ "day": 3, "label": "S3", "cumulativeReturn": 0.11 },
{ "day": 4, "label": "S4", "cumulativeReturn": 0.13 },
{ "day": 5, "label": "S5", "cumulativeReturn": 0.35 },
{ "day": 6, "label": "S6", "cumulativeReturn": 0.54 },
{ "day": 7, "label": "S7", "cumulativeReturn": 0.56 },
{ "day": 8, "label": "S8", "cumulativeReturn": 0.71 },
{ "day": 9, "label": "S9", "cumulativeReturn": 0.83 },
{ "day": 10, "label": "S10", "cumulativeReturn": 0.71 },
{ "day": 11, "label": "S11", "cumulativeReturn": 0.86 },
{ "day": 12, "label": "Exit", "cumulativeReturn": 0.87 }
]
},
"status": "Upcoming",
"entryDate": "2026-10-21",
"exitDate": "2026-11-06",
"daysUntilEntry": 77,
"entryPrice": null,
"stopLoss": null,
"firstTarget": null,
"riskPercent": null,
"rewardRiskRatio": null,
"confidenceScore": 0,
"confidenceBreakdown": { "seasonalWindow": 0, "windowProximity": 0 },
"explanation": "The next Earnings Season window opens 2026-10-21 (77 days away) and exits 2026-11-06…",
"checklist": [],
"score": 70,
"recommended": true,
"weight": "medium"
}
]
}GET /api/scan?strategy=earnings_season{
"strategy": "earnings_season",
"supported": true,
"scanned": 500,
"matches": [
{
"symbol": "SPY",
"signal": "BUY",
"score": 100,
"reasons": { "windowOpened": true, "windowClosed": false },
"entryPrice": null,
"stopLoss": null,
"firstTarget": null,
"riskPercent": null,
"rewardRiskRatio": null,
"confidenceScore": 100,
"confidenceBreakdown": { "seasonalWindow": 60, "windowProximity": 40 },
"explanation": "An Earnings Season window just opened (entry 2026-10-21, exit 2026-11-06) — BUY SPY at the close…",
"checklist": ["Earnings-season window is active"]
}
],
"skipped": []
}GET /api/backtest?strategy=earnings_season{
"strategy": "earnings_season",
"symbol": "SPY",
"range": { "requestedFrom": null, "requestedTo": null, "testedFrom": "1993-01-29", "testedTo": "2026-08-04", "barsTested": 8435 },
"metrics": {
"totalTrades": 133, "winningTrades": 91, "losingTrades": 42,
"winRate": 0.6842, "averageReturn": 0.0087, "medianReturn": 0.0107,
"bestTrade": 0.0953, "worstTrade": -0.1653, "averageHoldingPeriod": 11.18,
"profitFactor": 2.0211, "totalReturn": 1.9448, "maxDrawdown": 0.1771
},
"sampleTrades": [
{ "entryDate": "2026-04-21", "exitDate": "2026-05-06", "entryPrice": 566.10, "exitPrice": 590.02, "return": 0.0423, "holdingDays": 11 }
],
"openTradeEntryDate": null
}lib/market-data/prices.ts).