# Close Read feasibility study — 15:30→16:00 ET NQ window **Date:** 2026-07-27 · **Data:** /fp-data/glbx/NQ/1m (NQ.c.0, UTC ts), 3,948 full RTH sessions 2010-06-07 → 2026-07-24 · **Scripts:** `fp-backtest/close_{dataset,study,deep_dive,regime,robustness}.py` · **Cost model:** 1.0 pt round trip (consistent with MBP-10 fill audit 2026-07-26: 0.75–1.00 pt spread + ~0.17 pt slip at 1–2 lots) ## Question Is there a validated, backtestable edge in the last 30 minutes before the cash close that justifies a "Close Read" product analogous to the 9:20 Desk Read? ## Method Per-session features computed strictly as-of 15:30 ET (no lookahead; DST handled via tz_convert, never fixed UTC offset). Label = 15:30→16:00 log return. Half days excluded (≥330 cash bars required). ~25 hypothesis variants screened, survivors run through: threshold monotonicity, delayed entry, winsorization/trimming, bootstrap (10k), vol-regime and era splits, event-day proxy split, side symmetry. ## REFUTED (no edge at any tested configuration) | Hypothesis | Best result | Note | |---|---|---| | Unconditional last-30 drift | −0.02 bps, t=0 | dead | | First-30m → last-30m momentum (Gao et al.) | t=−0.28; OOS hit 0.476 | re-refuted on NQ, consistent with 2026-07 library-claims verdict | | Day-move continuation (leveraged-ETF/MOC rebalance) | t=1.34 all; IS t=1.79 → OOS t=0.48 | decays out of sample; fade version also dead | | VWAP reversion (fade dev >0.2%/0.4%) | t=−1.7 (wrong sign = continuation, but that too sub-2 and IS-driven) | dead both directions | | Day-range extreme fade | t=−1.17 | dead | | PM-range edge alone (no momentum) | +0.61 bps, t=0.69 | the OOS-only t=2.03 in screening was momentum overlap, not an independent edge | | Trend-day composite (\|day\|>1% & range edge) | t=0.33; OOS negative | dead | | Overnight hold extension (carry the close signal to next 09:30) | −6.73 bps, t=−1.17, net −10.1 pt | REFUTED — fights the unconditional overnight long drift, which re-confirmed at +4.28 bps t=3.32 | ## THE SURVIVOR — late-day momentum continuation **Signal:** r = log(p15:30 / p15:00). When |r| > 0.30%, position sign(r) at 15:30–15:35, exit at the 16:00 cash close. **Base result (n=540, 32 trades/yr):** +5.79 bps/trade, t=2.17, net +4.78 pt after 1.0 pt cost. IS (2010–19) +3.69 bps, OOS (2020–26) +7.61 bps — sign holds in both halves, meeting the desk bar (|t|≥2 overall + OOS sign consistency + n≥30 + cost-adjusted). Validation gauntlet: - **Dose–response monotone:** +2.3 bps @0.15% threshold → +9.8 bps @0.50%. Vol-normalized variant same shape (|z|>2: +11.3 bps, t=2.30, n=87). - **Not stale-trigger fragile:** entry at 15:31 t=2.31; at 15:35 still t=2.07, net +3.4 pt. - **Not outlier-driven:** winsorized 1/99 t=2.40; trimmed 2/98 t=2.56. Median +1.1 pt. - **Bootstrap p (one-sided) = 0.016.** Gross +3,121 pt over 16 yrs, maxDD −942 pt, worst day −304 pt. - **Not an FOMC artifact:** excluding 14:00-event-proxy days, t=2.27. - **Mechanism-consistent:** mean cum-P&L path accelerates 15:50→16:00 (~3.4 of 5.8 pt in the last 10 min) — the MOC-flow/auction-herding signature, and the reason exit must be the closing print. **Honest caveats:** 1. **Hit rate is NOT significant** (51.5%; binomial n.s.). The expectancy is tail-driven — it pays by catching big continuation closes (2020–22 short-side median +21 pt). Every signal must be taken; cherry-picking destroys it. 2. **~25 variants screened** — family-wise, t=2.17 alone would be marginal. The verdict leans on dose–response + delay robustness + the auction mechanism, not the t-stat alone. 3. **Regime-dependent (the big one):** - Low-vol regime (trailing 20d mean day-range < ~1.37%): −0.28 bps, dead. Mid/high vol: +8.7/+9.1 bps. - 2010–2014: dead both sides. 2015–2022: strongly positive both sides. **2023–2026: longs +2.5 pt net (56% hit) but shorts −8.9 pt net (41% hit).** 2025 alone: −16.2 pt/trade, concentrated Apr–May (tariff-crash V-reversals) and almost all on the short side — late down-moves have been systematically bought since 2023. - No tested conditioner (day-move agreement, day-range >1.5%, vol tercile) fully rescues 2023/2025 shorts. The prime suspect is dealer-gamma regime (positive-gamma close pinning), which is **unbacktestable**: the GEX archive starts 2026-07-26. **Conditioners that add (all-sample):** momentum agrees with day-move sign: +7.51 bps, t=2.28 (against: +2.3 bps n.s.). Day range >1.5%: +7.72 bps, t=2.10. Combined: +9.79 bps, t=2.16, net +7.5 pt, ~15 trades/yr — but each OOS half alone is t<2 at that frequency. ## Cross-symbol check (2026-07-27, `fp-backtest/close_cross_symbol.py`) Same signal at matched fire rates, per-symbol costs (ES 0.5pt, YM 2pt, RTY 0.3pt round trip): | sym | n | gross bps | t | net bps | net $/contract | yrs+ | |---|---|---|---|---|---|---| | NQ | 541 | +5.79 | 2.18 | +4.93 | **+$95** | 11/17 | | ES | 542 | +5.76 | 2.29 | +4.40 | +$67 | 11/17 | | YM | 541 | +5.63 | 2.32 | +4.99 | +$61 | 12/17 | | RTY | 304 | +4.96 | 1.24 | +4.12 | +$38 | 8/10 | - **The effect is index-wide, not an NQ artifact** — same magnitude on ES/YM, signal corr NQ↔ES = 0.96 (one systemic late-day flow phenomenon; confirmation against data-mining, but NOT four independent samples). - **2023–2026 is negative on the base rule on ALL four symbols** (NQ −2.3, ES −4.0, YM −0.9, RTY −0.6 bps, all n.s.) — the regime break is systemic, strengthening the gamma-regime suspicion and the case that the current-era read must lean on the conditioners (long-preferred, vol gate, agreement). - **Symbol choice:** NQ best net per contract at 1–5 lots (home instrument, existing infra). ES is the scaling vehicle only — 10–30× book depth; even triggered off the NQ signal, trading ES nets +$51/trade (t=1.99). YM/RTY add nothing. ## Calendar study (2026-07-27): OPEX / quad-witching / month-end / quarter-end - **OPEX Friday (non-quad):** no unconditional close drift (+0.2 bps, n=125). Momentum-signal days on OPEX: n=15, +32.9 bps, 87% hit — **suggestive, far below the n≥30 bar; do NOT treat as validated**, but no reason to stand down on OPEX either. - **Month-end:** no unconditional drift (−0.8 bps, n=194); signal works normally on month-end (n=30, +10.8 bps, t=1.1). Last-30 vol is ~40% higher — size accordingly. - **Quarter-end:** weak SHORT drift into the close (long −6.1 bps, t=−1.45, n=65) — below bar, not tradeable. **Rebalance-reversal folk claim REFUTED:** fading the day move into a quarter-end close loses (−8.0 bps); following it wins (+8.0 bps, 68% hit, n=34, t=1.2 — suggestive only). - **Quad-witching: UNTESTABLE from this archive — and a live-system bug.** NQ.c.0 (calendar roll) has **zero RTH bars on all 62 quad-witching Fridays** — the front contract expires 09:30 ET and c.0 doesn't remap until after. Consequences: (a) every c.0-based backtest in this repo has silently excluded those days (this study, the confirm-entry study's raw pulls, etc.); (b) `sessions/nqfull-*.json` on those dates hold overnight bars only (verified 2025-03-21: 669 bars, 0 RTH), so **`gradeSession` has been silently skipping the 4 quad-witching days each year** (the ≥15:55-candle guard returns null forever). Fixes: resolve the lead month explicitly (or `.v.0` volume roll) on expiry Fridays in `lib/databento.ts` + `fp_data_sync.mjs`; until then the Close Read should **stand down on quad-witching days** (unvalidated, structurally different flows). ## VERDICT **VALIDATED, conditional — ship as a shadow-mode Close Read, not a sized trade.** A directional read exists at 15:30 (momentum-continuation into the auction, vol-gated, long-preferred in the current regime), but the short side is broken in the 2023+ regime and the suspected conditioner (gamma) can only be tested forward. ## SHIPPED 2026-07-28 (shadow mode) | Piece | Where | What | |---|---|---| | 15:20 ET read | `lib/closeRead.ts`, `/api/cron/close-read` (19:20+20:20 UTC) | Deterministic gates + Fable-5 synthesis (news/econ/gamma as context tier). Emits a PROVISIONAL lean only. | | 15:31 ET bell confirm | `lib/closeBell.ts`, `/api/cron/close-bell` (19:31/19:33 + 20:31/20:33 UTC) | No LLM. Measures the realized 15:00→15:30 trigger and arms or voids. `:33` slots are retries, idempotent per date. | | Record + grader | `lib/closeReadStore.ts`, rides the 16:50 `close-report` cron | Blob per session `close-read/YYYY-MM-DD.json`; P&L = side × (close − entry) − 1.0pt; grades feed the next day's prompt. | Implementation notes worth keeping: - **Two-stage is forced, not stylistic.** The trigger doesn't exist until 15:30 and the edge decays with entry delay, so no LLM call may sit inside the entry window. - **Vol gate uses an hourly proxy** (trailing-20d mean of the 09:00–16:00 ET hourly range / price, gate ≤1.4502%) because the app can't afford a 30-day 1-minute pull at runtime. Calibrated against the study's 1-minute RTH definition: corr 0.9976, 98.3% classification agreement, and it reproduces the performance split as shipped (low-vol −0.24bp t=−0.09 n=175; normal +7.99bp t=2.09 n=362; stands down 32.6% of signal days). - **Fail-closed on data.** Databento historical lags 15–35 min, so at 15:31 the near leg comes from the Fly live gateway (which is also the realistic fill) and the 15:00 leg must come from history. Any missing input VOIDS rather than assuming an arm. - **Every session is recorded**, including stand-downs and no-trigger days, with the realized 15:30→16:00 move — the 2023+ short question can only be settled forward, and needs the declined days too. ### Replay of the shipped rules, last 252 sessions 150 stand-down (mostly low-vol) · 86 no-trigger · **16 armed** (~16/yr, matching the conditioned rule's expected cadence). Armed net **+110pt (+$2,200/contract), mean +6.88pt**, 7/16 wins — tail-driven exactly as characterized. **The short side broke again, live:** longs 5/8 for **+223.8pt** (mean +28.0), shorts 2/8 for **−113.8pt** (mean −14.2). The most recent 12 months independently reproduce the 2023–2026 breakdown. Shorts ship at conviction 1 and are recorded but, on this evidence, **should be suppressed entirely if the product ever graduates to size** unless the accruing gamma series rescues them. ## Recommended shape (clone the GC Asia Read template) - **Cron ~15:32 ET** (UTC slots 32 19 + 32 20, M–F; the 15:25–16:00 window is free). Signal window closes 15:30; entry validated through 15:35, so a 15:32 build is inside the tested envelope. Exit = 16:00 close, always — no stop overlay tested; risk control is size, matching the Asia carry finding. - **Deterministic gate, LLM phrases but cannot override** (Asia Read pattern): - |15:00→15:30| < 0.30% → **NO READ** (stand-down; ~85% of days). - Low-vol regime (trailing 20d day-range < ~1.4%) → stand-down. - Long signal: crown, conviction 2; +1 if day-move agrees AND day range >1.5% (cap 3). - Short signal: conviction 1, flagged "short side unvalidated in current regime" until forward gamma data adjudicates. - **Context tier (unvalidated, forward-collect):** live UW gamma regime at build time (long-gamma close = pin risk against the signal); 16:45 ET GEX snapshot already archives the full-session spot_exposures series — consider adding a ~15:15 ET snapshot to com.fpdata.gex so the wall state *at decision time* accrues history. - **Grader:** piggyback the existing close-report cron (16:50 ET) — read of record blob `close-read/YYYY-MM-DD.json`, P&L = sign × (close − 15:31 open) − 1.0 pt, grades fed back into the next day's context (last-5 block), same as Asia. - **Do NOT ship:** any overnight-hold extension (refuted above); short-side sizing; VWAP/range-fade scenarios. - **Promotion bar:** one quarter shadow (~8 signals under the conditioned rule, ~15–30 under base) plus the accruing GEX-at-15:15 series before any size. n will be small — judge against the archived per-era expectancies, not the quarter's raw P&L alone.