SPY 0DTE options · exact rules + independent backtest · prepared for the implementer · 2026-06-15
Backtest verdict: no durable edge SPY 0DTE · 3-min signal / 1-min fills real Alpaca data · strictly causalThese are the precise rules to implement (Section 1–2). I already built and backtested them on real SPY/option data, strictly causally (no look-ahead). Over 12 months the strategy is net-negative and shows no durable directional edge — so build it to verify/iterate, but don't fund it on these numbers. The single most promising change is the volume baseline (Section 5).
On the 3-minute chart, after price makes a new high-of-day (long bias) or new low-of-day (short bias), wait for it to retrace back to VWAP. When price holds on the trade side of VWAP and a follow-through candle confirms, buy a same-day-expiry (0DTE) call (after a HOD) or put (after a LOD) to ride the continuation. Two gates must also pass: rising volume and 20/50 SMA trend agreement. Manage with a scale-out ladder and a breakeven stop on the runners.
Direction is set by the extreme: a new HOD arms a CALL setup; a new LOD arms a PUT setup. Below is the CALL path; the PUT path is the exact mirror (retrace up to VWAP, hold below, bearish confirm).
armed=CALL and reset the retrace/hold flags.retraced=true once a candle's low reaches VWAP (price pulled back down to it).// CALL path (PUT = mirror). Per closed 3-min candle, in order:
if (new_HOD) { armed = CALL; retraced = false; hold = false; }
if (armed != CALL) continue;
if (low <= vwap) retraced = true; // pulled back to VWAP
above = (close - vwap)/vwap > EPS; // EPS ~ 0.0003 (3 bp)
below = (vwap - close)/vwap > EPS;
if (!hold) {
if (retraced && above) hold = true; // candle 1: held above
} else {
if (below) { disarm(); } // broke back below -> dead
else if (above && close > open) { // candle 2: bullish confirm
enter_at_open_of_next_candle(CALL); // candle 3 open (if gates pass)
disarm();
}
// else (above but not bullish): keep hold=true, re-check next candle
}
The confirm candle's volume must be ≥ 1.25× the average (target 1.25–1.30×, i.e. Roman's "25–30% above average"). In this build, "average" = the rolling mean of the prior 20 completed 3-min bars (causal — current bar excluded). If volume isn't expanding, skip the trade even if price held. See Section 5 — this baseline is the main thing worth changing.
|delta| ≥ 0.30
(delta from Black-Scholes on the fill premium).contracts = floor(275 / (premium×100)); skip if even 1 contract exceeds the cap).Engine: a single causal Python backtest on real Alpaca 1-min SPY + 0DTE option premiums (no look-ahead; the entry uses only data available at candle-3's open). The key lever is the volume threshold, so it's swept.
| Volume gate | Trades | Win % | Net P/L | Ex-top-5 | Green months |
|---|---|---|---|---|---|
| OFF (raw entry edge) | 135 | 49% | −$336 | −$2,645 | 7/13 |
| ≥ 1.10× avg | 15 | 27% | −$1,301 | −$1,929 | 2/13 |
| ≥ 1.25× avg (spec) | 11 | 27% | −$748 | −$1,218 | 1/13 |
| ≥ 1.30× avg | 11 | 27% | −$748 | −$1,218 | 1/13 |
Read it two ways, both negative. With the gate off (135 trades — the cleanest read of the raw entry) it's −$336 net but −$2,645 once you remove the 5 best trades: the near-breakeven headline is a top-5 mirage, not an edge. At the spec's 1.25× the sample collapses to ~11 trades/year that are also net-negative, green in only 1 of 13 months.
On Dec 15 → Jun 12 alone, tightening the volume gate looked like it filtered out losers — net improved monotonically:
| Volume gate (6-mo) | Trades | Win % | Net P/L | Ex-top-5 |
|---|---|---|---|---|
| OFF | 63 | 46% | −$1,234 | −$3,091 |
| ≥ 1.00× | 12 | 33% | −$758 | −$1,429 |
| ≥ 1.10× | 9 | 33% | −$530 | −$876 |
| ≥ 1.25× | 5 | 40% | +$23 | +$0 |
That +$23 is just 5 trades (remove them and nothing's left). Extending to 12 months reverses it to −$748 / 1-of-13 green months. Classic small-sample mirage — don't trust a config that only looks good on < ~30 trades.
The gates are extremely selective. Tracing one month of setups:
528 new extremes armed
→ 81 actually retraced to VWAP
→ 66 held on the trade side
→ 37 got a directional confirm candle
→ 3 passed the gates & traded (volume gate killed 28 of 37; SMA killed 3)
So ~76% of otherwise-valid setups die at the volume gate. On 3-min bars a retrace-and-resume candle usually prints below the trailing-20-bar average, because that average is inflated by the high-volume impulse that made the extreme in the first place. The threshold is doing what it says — it's just punishing on this timeframe and baseline.
The volume gate currently compares a candle to the trailing 20 bars of the same day. Intraday volume is U-shaped (heavy at the open/close, light midday), and VWAP retraces often happen midday — so the bar gets measured against an inflated average and almost always fails.
Try instead: relative volume = this 3-min bar's volume ÷ the average volume for that same clock-slot across the prior N days (e.g. the 09:51 bar vs the average 09:51 bar over the last 20 sessions). Then "25–30% above average" (RVOL ≥ 1.25–1.30) stops being a midday death sentence and starts meaning what a trader means by it. It won't fix a broken entry, but it's the cleanest way to get a real sample and a fair test of whether volume-confirmation has any signal.
Data: real Alpaca historical SPY 1-min underlying + 0DTE option 1-min premiums. Methodology: single causal clock, decisions use only bars at/before the moment of action (entry at candle-3 open); delta via Black-Scholes on the fill premium. "Ex-top-5" = net P/L after removing the five largest winners (a fragility check). Backtest/research only — not financial advice.