Loading Action Center...
Loading 0DTE Options Game...
What each rule version does, what changed and why, and how to add the next one. Default values below are read live from coach-rules.ts — they always match the running engine.
Live candles. The coach's price was live, but its candles came from Polygon, which is 15 minutes behind on this plan. VWAP, EMA, MACD, the chop filter and the session-range gate were all a quarter-hour late: at 10:17 CT it reported a 0.37% range when the true range was 0.42%, the low having printed at 10:10 CT, and its PUT came at 10:29 CT. It now uses the IBKR bridge's SPY/QQQ 1-minute bars, and says on the card when it has to fall back to delayed ones.
Late-entry guard (on). A new call/put is refused when SPY has already run 0.30% its way in the last hour and sits within 0.10% of the session high/low, or when the range gate only just opened because of that move. On 68 recorded entries the blocked trades were no worse than the kept ones (7 target/14 stop vs 9/18): it skips the "bought the bottom" entries, it does not create an edge.
Early watch (shown, never traded). A 5-minute close outside the prior 30-minute box shows a heads-up on the card (and, if you turn it on, a low-priority push). On 24 Sep it flagged the 9:45 CT fade and the 11:15 CT rip, plus one false break at 9:05 CT; over 34 sessions (5–6 a day) 42% reached +0.25% before −0.20%, just under break-even.
Volume ignition (watch, pushed). The 11:15 CT rip left an hour-long base on the S1 pivot on about 9× the base's volume; the coach, on stale candles and in its lunch block, called at 11:30 CT. A 1-minute close outside a ≤0.30% 45-minute base on 5× volume now shows and pushes as a watch. Over 21 sessions it fired 9 times and only 2 kept running for 30 minutes — it tells you to look, not what to buy.
History. Today's calls show OPEN, with the contract's live price, until 16:00 ET. They used to be graded WIN/LOSS against the latest delayed print mid-session.
No BUY or EXIT phone push, no VWAP-retest push, no Robo entry and no auto-execute from the SPX Live cockpit. The card and history label each signal PAPER.
Why: checked against SPY prices, the 126 recorded entries from 26 Jun to 23 Sep reached +0.25% first 25 times and −0.25% first 37 times, and were flat on average an hour later. v4.2's own 22 live entries moved about 0.1% either way within the hour, too little to beat 0DTE theta. v5, replayed on 18 sessions of 1-minute bars, hit its target 14 times and its stop 48 times.
Also new that day: a new put at S3 (call at R3) waits for a completed 5-minute close through the level, unless it is a failed-retest or stretched-extreme entry. S3 is where an open position takes partial profit; S4/S5 still block outright.
Profit lock (on by default): once SPY has moved 0.20% in an open position's favour, the coach records EXIT when half of that best move is given back. It also finds the open position past lunch or breadth stand-downs, which used to hide it from every exit rule. Replayed on 130 entries it left the average option return about the same, but cut near-total losses from 76 to 55 and raised +30% trades from 33 to 46.
Turn live alerts back on in Settings only when the history page shows an edge.
Trade window: 10:00–15:15 ET (09:00–14:15 CT); scheduled macro blackouts still block entries.
Data: at least 30 completed 1m bars and six completed 5m bars. Latest completed 1m bar no more than 90 seconds old; each of the six consecutive 5m bars must have five valid native 1m candles.
Trigger: a trend pullback rejection confirmed on 1m and 5m, or a completed stretched-extreme reversal. Edge score alone cannot create an entry.
Price: no entry more than 0.5× 5m ATR from the trigger close; no trigger candle larger than 1.5× ATR. A new pullback must form after a missed move.
Risk: a structural stop and at least 1.5R to the first unbroken support/resistance, or VWAP for a reversal. Levels are in SPY; they are not SPX strike prices.
Execution: a fresh executable option bid/ask is required. Missing quotes produce WATCH, never BUY. History stores the engine, trigger, levels and quote evidence.
Lotto break: an eight-minute range releases on at least 1.8× prior 20-minute average volume. This setup permits a trigger range up to 2.5× ATR and expires after 45 seconds. It still requires 1.5R and the 0.5× ATR no-chase limit.
Lotto execution: exact SPXW bid/ask no older than 15 seconds, spread at most 20% of mid, ask $0.20–$1.00, and at most 3× an observed pre-trigger ask for that same contract within ten minutes. Missing baseline means WATCH. These checks do not predict a return multiple.
Thresholds are initial hypotheses. Replay results describe underlying price paths, not option profits.
Single short-ATR window pauses everything. One edge-gone bar exits the streak. Re-entry locks for the full cooldown regardless of conviction.
Trade-off: safe, but sits out real moves — a strong −19 PUT lean gets blocked the same as random noise.
The coach fired PUT −8 then −18, won both, then got kicked out on a 1-bar wobble and missed the whole 752→750 slide. v3 adds: conviction override on chop, fast same-direction re-entry, edge-gone streak guard, and the closing-window scout.
Trade-off: reaches more real moves, but still has no rule preventing a counter-trend CALL.
The engine said BUY CALL while price was below VWAP, below EMA80, and MACD was red — because the mean-reversion "buy the oversold dip" edges out-weighed the trend. That is the wrong-way 0DTE trade.
v3.1 = all of v3 + a hard trend veto that runs last and cannot be overridden.
The other expensive 0DTE mistake isn't the wrong way — it's chasing the right way too late. Red day → buy put after price already slid into S4/S5 → bounce → the fresh put bleeds.
v3.3 = all of v3.1 + a pivot exhaustion gate: pivots become a map of how early/late you are (S1 target → S5 exhaustion), not a blind entry trigger. The veto here keys on the MACD histogram.
A signal-history audit showed ~50% directional accuracy — a money-loser for long 0DTE. Three structural causes: contradictory edges net to a verdict, the score peaks at extremes, and the slow edges lag the inflection.
v3.4 = all of v3.3 + edge-agreement gate (≥ 70% of |edge weight| must agree), + progressive VWAP-extension haircut, and + VWAP-retest entry promotion (MTF 1-min trigger / 5-min confirm).
July 1 live replay: a PUT fired at the 09:48 washout low (reversed +5.5), the 11:45 R1 tag-and-fail at RSI ~76 was forgotten so 12:00/13:35 CALLs chased into the fade, the afternoon slow bleed stayed chop-blocked, and no directional call carried a stop.
v4 = all of v3.4 + pivot-rejection memory, + washout-low guard, + slow-bleed drift override, + lid escalation, and + a published stop on every BUY one-liner.
v4.1 corrects the live composition: lid RSI uses only today's RTH 5m bars, its 0.4% band is configurable, and lid/rejection/washout remain entry suppressors rather than automatic exits for an already-open position.
All three conditions must agree before a direction is "confirmed", so a single indicator flipping never vetoes on its own. When a verdict is vetoed it becomes STAND_DOWN and the reason is recorded in v3Notes / the phone alert, e.g. "CALL blocked: SPY below VWAP and below EMA80 with MACD negative."
Tunable on the settings page: toggle the veto on/off and change the trend EMA period (default 80). The veto runs when the engine is set to v3.1 or v3.3 (v3.3 keys it on the MACD histogram sign only).
Instead of "BUY PUT because the market is red", the coach now says "Bear trend active, but price is into S4 — do NOT chase; take profit or wait for a failed bounce." Levels are extended floor pivots from the prior day's H/L/C.
Deferred to a later pass (need new data / infra): real-account daily P&L profit-lock, news/catalyst gate (e.g. FOMC days), breadth / Ichimoku / Bollinger / volume confirmation, and candle-pattern entry triggers (rejection candle → break of low → volume expands).
The retest promotion bypasses only the agreement gate (the retest IS the price-action confirm that leads the edge consensus) — the trend veto, pivot exhaustion gate, and always-on exhaustion veto still apply to it. All three v3.4 gates are toggleable on the settings page (agreementGateEnabled, vwapHaircutEnabled, vwapRetestEntryEnabled).
Suppressor order after the agreement gate: lid escalation → pivot-rejection memory → washout guard — strictest wins. A VWAP-retest-promoted entry bypasses the washout guard only (the retest IS the "wait for rejection" confirmation); everything else still applies. Each block is toggleable on the settings page (pivotRejectionEnabled, washoutGuardEnabled, driftOverrideEnabled, lidEscalationEnabled).
| Rule | v2 · retired | v3 · retired | v3.1 · retired | v3.3 · retired | v3.4 · retired | v4 · retired |
|---|---|---|---|---|---|---|
| Directional entry | Fire CALL/PUT when |score| ≥ 4 with ≥ 3 same-side edges. | Same as v2. | Same as v2 — then the trend veto runs last and can cancel it. | Same as v3.1 — then the pivot exhaustion gate also runs and can cancel it. | Same as v3.3 — then the edge-agreement gate also runs: blocked unless ≥ 70% of |edge weight| agrees. | Same as v3.4 — then the v4 suppressors (lid escalation, pivot-rejection memory, washout guard) run last and can cancel it. |
| Chop filter (5m ATR) | Hard stop: if 5m ATR < 0.2% of price, STAND DOWN. A strong −19 lean gets blocked the same as noise. | Conviction override: bypass the chop gate when |score| ≥ 11 with ≥ 5 confirms. A decisive lean is not "chop". | Same as v3. | Same as v3. | Same as v3. | Drift override: a slow one-way bleed (≥ 0.35% over 18 bars, ≥ 60% one-color, wrong side of VWAP) releases the chop gate in the drift direction only. |
| Re-entry after an exit | Locked for 15m (same dir) / 20m (opposite flip), unless |score| ≥ 10. | Same-direction re-entry allowed after just 5m when |score| ≥ 8. Re-entering the same trend is not a flip. | Same as v3. | Same as v3. | Same as v3. | Same as v3. |
| Edge-gone exit | A single low-score bar closes the streak — one wobble kicks you out. | Requires 2 consecutive low-score bars AND a ≥ 50% drop from the streak peak before exiting. | Same as v3. | Same as v3. | Same as v3. | Same as v3. |
| Closing-window scout | None — chop / lunch / cooldown filters sit out the late-day premium slam. | Arms 14.5–15.75 ET on a one-way day (|day move| ≥ 0.3%); enters at |score| ≥ 6, bypassing other gates. | Same as v3 — but the trend veto still applies to whatever the scout proposes. | Same as v3.1. | Same as v3.1. | Same as v3.1. |
| Hard trend veto | None. | None — counter-trend bounce/scout edges can still fire a CALL into a downtrend. | BLOCK CALL when price < VWAP AND price < EMA80 AND MACD line+hist negative (mirror for PUT). Runs last; nothing overrides it. | Same idea, keyed on the MACD histogram sign only: BLOCK CALL when price < VWAP AND price < EMA80 AND hist < 0 (mirror for PUT). | Same as v3.3. | Same as v3.3. |
| Pivot exhaustion gate | None — pivots are scored as entry edges (held S1 = bullish, lost S2 = bearish). | None. | None. | Pivots become a location map, not a trigger. New PUT blocked into S4/S5 support (within $0.5 SPY), tags S3 as take-partial; mirror R3/R4/R5 for calls. Stops the "buy the put after the slide" loss. | Same as v3.3. | Same as v3.3 — plus lid escalation: near R1/R2 with RSI ≥ 70, a new CALL is hard-blocked (mirror S1/S2 for puts). |
| Edge-agreement gate | None — the score is a net sum, so "BUY CALLS +5" can hide a near-even bull/bear split. | None. | None. | None. | Directional verdict blocked unless the supporting side owns ≥ 70% of total |edge weight|. A genuine trend has lopsided agreement; chop nets to a thin edge. | Same as v3.4. |
| VWAP-extension haircut | None — trend edges all peak at the local top, so confidence is highest right where you should fade. | None. | None. | None (only the rare binary RSI ≥ 78 exhaustion veto). | Confidence scales down progressively as price stretches from VWAP: no penalty below 0.4%, down to ×0.4 at 1.2%. Sizes down a chased entry without a hard block. | Same as v3.4. |
| VWAP-retest entry | None — the slow edges lag the inflection, so a failed retest / reclaim goes un-called. | None. | None. | None. | A clean MTF retest (1-min trigger, 5-min confirm) promotes a STAND_DOWN to its side at ≥ 55 confidence. Trend/pivot/exhaustion gates still apply; only the agreement gate is bypassed. | Same as v3.4 — and a retest-promoted entry also bypasses the washout guard (the retest IS the confirmation the guard waits for). |
| Pivot-rejection memory | None — the engine forgets an R1 tag-and-fail within minutes. | None. | None. | None. | None — on July 1 the 11:45 R1 749.38 rejection at RSI ~76 was forgotten and 12:00/13:35 CALLs fired into the fade. | Recomputed from today's 5m bars each request: a tag of R1/R2 (within 0.15%) at RSI ≥ 68 that then fades ≥ 0.2% blocks new CALLs below the level until it is reclaimed on a close above. Mirror for S1/S2 defenses vs PUTs. |
| Washout-low guard | None — a red score at the flush low still says BUY PUT. | None. | None. | None. | None — on July 1 the 09:48 PUT fired at SPY 743.79 right at the washout low; it reversed +5.5. | New PUT blocked when price dropped ≥ 0.5% over the last 4 bars AND sits within 0.25% of the session low ("buy the put after rejection, not at the low"). Mirror blocks CALLs at a vertical rip high. A VWAP-retest-promoted entry bypasses it. |
| Published stop | None — directional one-liners never state an exit level. | None. | None. | None. | None. | Every BUY one-liner appends "Stop X.XX": swing low/high of the last 5 bars, never worse than 0.45% from entry. |
Say you want a v6 that also requires the VIX to be falling before any CALL. Five edits:
src/lib/coach-rule-versions.ts:export type CoachRuleVersion = ... | 'v4.2' | 'v5' | 'v6'; export const COACH_RULE_VERSIONS = [..., 'v4.2', 'v5', 'v6'] as const; export const ACTIVE_COACH_RULE_VERSIONS = ['v4.2', 'v5', 'v6'] as const;
COACH_RULE_VERSIONS is every engine that ever recorded history (the history page lists them all). ACTIVE_COACH_RULE_VERSIONS is what can run: it whitelists the rules API POST, the A/B view and ?engine=. Retiring a version means removing it from the active list only — its history stays readable, and a saved config that still names it runs the default.CoachRules interface, a default in DEFAULT_COACH_RULES, and a range in COACH_RULE_BOUNDS. Boolean toggles are excluded from bounds and MUST be added to COACH_RULE_BOOLEAN_KEYS instead — mergeRules and the POST whitelist both key off it, and a unit test fails if a boolean default is missing from it.src/app/api/zero-dte-coach/route.ts. Reuse the inheritance pattern:const isV6 = rules.ruleVersion === 'v6';
// ...then, after the verdict + existing gates:
if (isV6 && verdict === 'CALL' && vixRising) {
verdict = 'STAND_DOWN';
v6Note = 'v6 — CALL blocked: VIX rising.';
}Add the note to v3Notes and the oneLiner.<SelectItem value="v6"> option and any new fields in src/app/action/zero-dte/settings/page.tsx (the POST whitelist follows ACTIVE_COACH_RULE_VERSIONS / COACH_RULE_BOOLEAN_KEYS automatically).v6 column / rows to the comparison table on this page.Pattern to keep: a later version inherits the earlier one. v4 reuses every v3.4 gate via isV34Plus and only adds its suppressors via isV4. Do the same with isV4Plus so you never re-implement existing rules — you only add the new one. New gates run LAST so the strictest rule wins.