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.
Real internal-backtest statistics from the registry — Internal backtest · SPY daily bars (Yahoo Finance) · 308 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.
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:
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.
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).
Choppy, range-bound or gently rising markets that oscillate and mean-revert, where quick oversold dips get bought back.
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.
RSI(3) crosses below 20 — the previous bar's RSI was ≥ 20 and the current bar's RSI is < 20.
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.
stopLoss, firstTarget, riskPercent, and rewardRiskRatio are reported as null (never invented). entryPrice is the signal-bar close.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:
Deterministic, from the shared buildConfidence framework. confidenceScore is a setup-quality measure (0–100), not a probability of profit. Components (maxima sum to 100):
| Component | Max | Rewards |
|---|---|---|
entryCross | 70 | the required condition — RSI crossed below the entry threshold |
oversoldDepth | 18 | deeper oversold RSI — how far below 20 RSI has pushed |
reversalStrength | 7 | stronger reversal confirmation — a steeper drop into oversold |
cleanCross | 5 | cleaner 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.
historical.* and the pattern are baked as of 2026; the live /api/backtest always recomputes from current data.Real responses from the live endpoints for this strategy.
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"
}
]
}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": []
}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
}lib/market-data/prices.ts).