Mean Reversion strategy

Daily RSI Mean Reversion

A pure single-indicator setup: buy when RSI(3) crosses below 20, sell when it crosses above 70. No trend filter, no stop — just the RSI cross.

Production
Mean Reversion
SPY
Scanner
Backtest

Performance

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

77.9%
Win rate
2.37
Profit factor
+0.91%
Avg / trade
27.51%
Max drawdown
CAGR
not published
Exposure
not published
308
Total trades
8d
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

Daily RSI Mean Reversion — average trade trajectory (entry → exit)

Percent of the trade's holding period (0% = entry, 100% = RSI crosses above the exit level) · Average cumulative return (%). Empirical average trade path computed across ALL 308 completed backtest windows on real SPY daily closes. The holding period is variable, so each trade is normalized to its own duration (0–100%) before averaging; every point therefore uses the same 308 trades (no survivorship bias) and the 100% value equals the mean per-trade return. Labelled basis:"derived" (computed from our own backtest); the frozen v1 enum has no "empirical" value.

Daily RSI Mean Reversion strips mean reversion down to one signal — a fast RSI crossing thresholds:

  • BUY when RSI(3) crosses below 20 (a fresh oversold washout).
  • SELL when RSI(3) crosses above 70 (mean reversion complete).

That's the entire strategy. It runs on SPY as a market-timing signal (/api/strategies) and, via the scanner, on individual liquid names (/api/scan). Production defaults are RSI length 3, entry 20, exit 70; the engine keeps them configurable internally, and the public strategy exposes these defaults.

Why the strategy works

Over short horizons, sharp down moves in liquid instruments tend to over-extend and revert. A fast RSI crossing a low threshold flags that capitulation objectively, and the high (70) exit threshold lets the bounce run until short-term momentum is spent rather than taking a few ticks. The absence of filters keeps it responsive — but also unguarded (see Weaknesses).

Best for

Choppy, range-bound or gently rising markets that oscillate and mean-revert, where quick oversold dips get bought back.

Avoid when

Strong, sustained downtrends and momentum crashes, where "oversold" keeps getting more oversold. With no trend filter and no stop, the strategy simply holds until RSI(3) finally crosses 70 — which is why its worst trade was −22% (2008).

One pure core (dailyRsiMrSignalFromBars) backs the live SPY state, the scanner, and the backtest adapter, so the maths lives in exactly one place. It takes an optional params object ({ rsiLength, entryThreshold, exitThreshold }); the exported strategy uses the production defaults.

Entry

RSI(3) crosses below 20 — the previous bar's RSI was ≥ 20 and the current bar's RSI is < 20.

Exit

RSI(3) crosses above 70 — the previous bar's RSI was ≤ 70 and the current bar's RSI is > 70. This is the only exit.

Position management

  • No price stop, no profit target, no trailing logic. stopLoss, firstTarget, riskPercent, and rewardRiskRatio are reported as null (never invented). entryPrice is the signal-bar close.
  • Risk is controlled solely by the short holding period and the mean-reversion exit — there is no protection against a trend that keeps falling.

Scanner

The same per-symbol logic runs across a universe via GET /api/scan?strategy=daily_rsi_mean_reversion, returning each match's signal, a 0–100 confidence score, the passed-rule checklist, and metrics (rsi, prevRsi, close).

The API returns the labels of the rules that currently pass:

  • RSI(3) crossed below 20 (fresh oversold cross) → this is the BUY.
  • Enter at the signal-bar close.
  • No stop, no target — hold until the exit condition.
  • RSI(3) crossed above 70 → this is the SELL (exit).

Deterministic, from the shared buildConfidence framework. confidenceScore is a setup-quality measure (0–100), not a probability of profit. Components (maxima sum to 100):

ComponentMaxRewards
entryCross70the required condition — RSI crossed below the entry threshold
oversoldDepth18deeper oversold RSI — how far below 20 RSI has pushed
reversalStrength7stronger reversal confirmation — a steeper drop into oversold
cleanCross5cleaner setup — the prior bar's RSI was well above the threshold (a decisive cross, not hovering)

A bare BUY (a shallow cross right at the threshold) scores ≈ 70 (Good); a deep, sharp, clean washout → 100 (Exceptional). On /api/scan the score is populated per match; on /api/strategies it reflects SPY's current reading. Bands: <70 Weak · 70–79 Good · 80–89 Strong · 90–100 Exceptional.

  • High hit-rate, positive expectancy — 77.9% win rate, 2.37 profit factor over 308 real trades / 34 years.
  • Dead-simple and fully objective — one indicator, two thresholds; no discretion, nothing to curve-fit beyond three parameters.
  • Dual use — the same core powers a SPY timing signal and a full stock scanner.
  • Responsive — a fast RSI(3) reacts immediately to washouts, catching sharp dips other filters would miss.
  • Configurable internally — length/entry/exit can be tuned by the engine while the public strategy ships sane defaults.
  • No stop — real tail risk. Worst trade −22.4%, max drawdown 27.5%. In a sustained downtrend the position bleeds until RSI reclaims 70.
  • No trend filter. It will buy dips in a bear market, exactly where mean reversion inverts.
  • Long left tail on holding — most trades exit in a few days, but some drag on for weeks (up to 40 sessions) waiting for the exit.
  • High win rate masks the risk — the loss distribution is fat-tailed; sizing must account for the occasional −20% trade.
  • Close-to-close model — no commissions/slippage; fills at the signal-bar close.
  • Static snapshot. historical.* and the pattern are baked as of 2026; the live /api/backtest always recomputes from current data.
  • Single-vendor, close-to-close. Yahoo prices; SPY history from 1993; no costs modelled.
  • Pattern is normalized-time, not raw sessions (a deliberate, documented choice to avoid survivorship bias for a variable-holding strategy).
  • Backtested on SPY. The scanner applies the same rules per-symbol; the published statistics are the SPY single-symbol backtest.

API examples

Real responses from the live endpoints for this strategy.

Live strategy

GET /api/strategies?id=daily_rsi_mean_reversion
GET /api/strategies?id=daily_rsi_mean_reversion
{
  "strategies": [
    {
      "id": "daily_rsi_mean_reversion",
      "name": "Daily RSI Mean Reversion",
      "category": "Technical",
      "asset": "SPY",
      "description": "WHAT IT IS: Daily RSI Mean Reversion is a pure, single-indicator mean-reversion strategy…",
      "historical": {
        "yearsTested": 34,
        "winRate": 0.7792,
        "averageReturn": 0.91,
        "bestYear": 32.79,
        "worstYear": -20.35,
        "maxDrawdown": 27.51,
        "averageHoldingPeriod": 8,
        "profitFactor": 2.37,
        "totalTrades": 308,
        "source": "Internal backtest · SPY daily bars (Yahoo Finance) · 308 trades · 1993–2026"
      },
      "historicalPattern": {
        "title": "Daily RSI Mean Reversion — average trade trajectory (entry → exit)",
        "xLabel": "Percent of the trade's holding period (0% = entry, 100% = RSI crosses above the exit level)",
        "yLabel": "Average cumulative return (%)",
        "basis": "derived",
        "source": "Internal backtest — 308 completed SPY trades (Yahoo Finance daily closes), 1993–2026",
        "note": "Empirical average trade path… each trade normalized to its own duration so every point averages the same 308 trades (no survivorship bias); the 100% value equals the mean per-trade return.",
        "series": [
          { "day": 0, "label": "Entry", "cumulativeReturn": 0.0 },
          { "day": 10, "label": "10%", "cumulativeReturn": -0.04 },
          { "day": 20, "label": "20%", "cumulativeReturn": -0.07 },
          { "day": 30, "label": "30%", "cumulativeReturn": -0.12 },
          { "day": 40, "label": "40%", "cumulativeReturn": -0.26 },
          { "day": 50, "label": "50%", "cumulativeReturn": -0.53 },
          { "day": 60, "label": "60%", "cumulativeReturn": -0.74 },
          { "day": 70, "label": "70%", "cumulativeReturn": -0.75 },
          { "day": 80, "label": "80%", "cumulativeReturn": -0.67 },
          { "day": 90, "label": "90%", "cumulativeReturn": -0.15 },
          { "day": 100, "label": "Exit", "cumulativeReturn": 0.91 }
        ]
      },
      "status": "Upcoming",
      "entryDate": "",
      "exitDate": "",
      "daysUntilEntry": 0,
      "entryPrice": 583.12,
      "stopLoss": null,
      "firstTarget": null,
      "riskPercent": null,
      "rewardRiskRatio": null,
      "confidenceScore": 8,
      "confidenceBreakdown": { "entryCross": 0, "oversoldDepth": 0, "reversalStrength": 3, "cleanCross": 5 },
      "explanation": "SPY does not currently trigger Daily RSI Mean Reversion — RSI(3) is 47.2, with no cross below 20…",
      "checklist": [],
      "score": 12,
      "recommended": false,
      "weight": "medium"
    }
  ]
}

Scanner

GET /api/scan?strategy=daily_rsi_mean_reversion
GET /api/scan?strategy=daily_rsi_mean_reversion
{
  "strategy": "daily_rsi_mean_reversion",
  "supported": true,
  "scanned": 500,
  "matches": [
    {
      "symbol": "XYZ",
      "signal": "BUY",
      "score": 94,
      "reasons": { "crossedBelowEntry": true, "crossedAboveExit": false },
      "metrics": { "rsi": 8.3, "prevRsi": 34.1, "close": 151.30 },
      "entryPrice": 151.30,
      "stopLoss": null,
      "firstTarget": null,
      "riskPercent": null,
      "rewardRiskRatio": null,
      "confidenceScore": 96,
      "confidenceBreakdown": { "entryCross": 70, "oversoldDepth": 18, "reversalStrength": 7, "cleanCross": 5 },
      "explanation": "XYZ rates 96/100 — an exceptional setup by the Daily RSI Mean Reversion rules…",
      "checklist": ["RSI(3) crossed below 20"]
    }
  ],
  "skipped": []
}

Backtest

GET /api/backtest?strategy=daily_rsi_mean_reversion
GET /api/backtest?strategy=daily_rsi_mean_reversion
{
  "strategy": "daily_rsi_mean_reversion",
  "symbol": "SPY",
  "range": { "requestedFrom": null, "requestedTo": null, "testedFrom": "1993-01-29", "testedTo": "2026-08-04", "barsTested": 8435 },
  "metrics": {
    "totalTrades": 308, "winningTrades": 240, "losingTrades": 68,
    "winRate": 0.7792, "averageReturn": 0.0091, "medianReturn": 0.0135,
    "bestTrade": 0.0857, "worstTrade": -0.2236, "averageHoldingPeriod": 8.26,
    "profitFactor": 2.3737, "totalReturn": 12.9561, "maxDrawdown": 0.2751
  },
  "sampleTrades": [
    { "entryDate": "2026-07-29", "exitDate": "2026-07-31", "entryPrice": 585.10, "exitPrice": 599.19, "return": 0.0241, "holdingDays": 2 }
  ],
  "openTradeEntryDate": null
}

References

  • Larry Connors & Cesar Alvarez, Short Term Trading Strategies That Work — short-period RSI mean reversion (the lineage of low-RSI-entry / high-RSI-exit systems).
  • Wilder, J. W. (1978). New Concepts in Technical Trading Systems — the original RSI.
  • Backtest data: SPY daily OHLCV via Yahoo Finance, 1993–2026 (lib/market-data/prices.ts).