Seasonal strategy

Earnings Season Strategy

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.

Production
Seasonal
SPY
Scanner
Backtest

Performance

Real internal-backtest statistics from the registry — Internal backtest · SPY daily bars (Yahoo Finance) · 133 trades · 1993–2026.

68.4%
Win rate
2.02
Profit factor
+0.87%
Avg / trade
17.71%
Max drawdown
CAGR
not published
Exposure
not published
133
Total trades
11d
Avg holding

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.

Historical pattern

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:

  • Buy at the close of the first trading day after Jan 20 / Apr 20 / Jul 20 / Oct 20.
  • Sell at the close of the first trading day after Feb 5 / May 5 / Aug 5 / Nov 5.
  • Four trades per year; ~11 trading sessions each; in cash ~82% of the time.

There are no indicators, no moving averages, no stop-loss, no profit target. The dates are the entire strategy.

Why the strategy exists

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.

Historical rationale

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.

Best for

Constructive markets where earnings and guidance are broadly met or beaten and the index grinds higher through reporting season.

Avoid when

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.

Entry rules

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.

Exit rules

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.

Position management

  • Date-defined, not price-defined: no stop, no target. entryPrice/stopLoss/firstTarget/riskPercent/rewardRiskRatio are null (never invented). Risk control is the short, fixed hold and being in cash between seasons.

Calendar scanner

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:

ComponentMaxAwarded
seasonalWindow6060 while the window is Active (today between entry and exit), else 0.
windowProximity40Ramps 40 × (10 − daysUntilEntry)/10 over the 10 calendar days before entry; 0 when far away or finished.
  • Inside a window → 100 (Exceptional).
  • Approaching (≤10 days) → rises with proximity (e.g. 5 days out → 20).
  • Far / between seasons → 0.

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 [].

  • It is on/after the first trading day after the quarter's 20th (Jan/Apr/Jul/Oct).
  • Position entered at that session's close in SPY.
  • No stop or target — the exit is the date.
  • Exit at the close of the first trading day after the 5th (Feb/May/Aug/Nov).
  • Flat (in cash) between earnings seasons.
  • Very low time-in-market (~18%) with a positive edge — capital is idle (or reusable) ~82% of the year.
  • Objective and mechanical — fixed calendar dates; no indicators, no discretion, nothing to curve-fit.
  • Solid per-trade stats — 68% win rate, 2.02 profit factor, +0.87% average over 133 real trades / 34 years.
  • Non-correlated — a seasonal earnings overlay sits apart from trend/mean-reversion books.
  • Concentrated in a real catalyst window — exposure aligns with when fundamentals actually re-price.
  • No stop → real single-window risk. Worst trade −16.5%, worst year −12.3%, 17.7% max drawdown.
  • Modest per-trade edge (~0.9%) — sensitive to costs and slippage across four trades a year.
  • Low CAGR (~3.3%) precisely because it's out of the market most of the time — it underperforms buy-and-hold in strong trends (the flip side of low exposure).
  • Seasonal edges decay as they become known and arbitraged.
  • Close-to-close model — no commissions/slippage.
  • **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.
  • Static snapshothistorical.* and the pattern are baked as of 2026; the live /api/backtest always recomputes.
  • Single-vendor, close-to-close — Yahoo prices; SPY history from 1993; no costs modelled.
  • CAGR / exposure are documented here but not in the frozen historical block (which has no fields for them); the endpoint reports totalReturn.
  • Calendar scanner is symbol-agnostic by nature (the window is a market date), so a universe scan returns the same signal for every symbol.

API examples

Real responses from the live endpoints for this strategy.

Live strategy

GET /api/strategies?id=earnings_season
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"
    }
  ]
}

Scanner

GET /api/scan?strategy=earnings_season
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": []
}

Backtest

GET /api/backtest?strategy=earnings_season
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
}

References

  • Post-earnings-announcement drift literature (Bernard & Thomas, 1989) — the single-stock cousin of the aggregate earnings-season effect.
  • Practitioner "earnings season" seasonality studies of index returns around quarterly reporting peaks.
  • Backtest data: SPY daily OHLCV via Yahoo Finance, 1993–2026 (lib/market-data/prices.ts).