NQ · research

OB midpoint reversion — the 10:30–11:30 trade on range days

A live strategy layered on the 09:30–10:30 opening-range box. Four signals (MidShort / MidLong / FlipShort / FlipLong), order-of-formation FSM, and a four-part backtest (faithful replay + stop sweep + entry-cutoff sweep + breakeven sweep) across 1,370 sessions. Ship config: MidLong + FlipShort only, MN_INACTIVE only, cutoff 11:30, OB-extreme stop, TOWARD-VWAP gate on MidLong. +19.4 pts/trade across ~154 trades/year.

updated 2026-04-24
shipped opening-rangemean-reversionmidpointvwapmn-holdparameter-sweepstrategy-upgrade

The setup

After the 09:30–10:30 opening-range box finishes, price usually travels toward one extreme before retracing. The reversion trade: enter at the box midpoint, target the opposite extreme. Direction depends on which extreme formed first.

The MidpointReversion.cs strategy was already live with this logic and a dashboard claiming 70–83% win rates per signal. The full-sample OOS replay disagreed — sharply — and a parameter sweep produced a much cleaner config.

The FSM

Inside the 09:30–10:30 window, track four state variables bar-by-bar (all thresholds recomputed live each bar — this matters for flip detection):

VariableMeaning
rangeHigh / rangeLowrunning box high / low
rangeHighTime / rangeLowTimebar index where each extreme was set
obHighApproachedtrue once High ≥ (mid + rangeHigh)/2 during the window
obLowApproachedtrue once Low ≤ (mid + rangeLow)/2 during the window

At 10:30 the FSM produces:

Flip logic = “price came back toward the first-formed extreme after the second extreme printed” — the original one-sided travel was broken, so trade the other way.

Four signals fall out:

SignalConditionDirectionTarget
MidShorthigh-first, no flipshort at midrange low
MidLonglow-first, no fliplong at midrange high
FlipShortlow-first, flip triggeredshort at midrange low
FlipLonghigh-first, flip triggeredlong at midrange high

Part 1 — faithful replay (backtest_midpoint_reversion_v2.py)

1,370 MNQ sessions, straddle entry (any bar with Low ≤ mid ≤ High), entry window 10:30–14:00, stop = opposite OB extreme, target = opposite OB extreme + 10 pts extension (the v1 sweep winner).

SignalnWin%Exp/trTotal pts
MidShort36131.0%−0.01−4
MidLong35234.9%+6.11+2,152
FlipShort9048.9%+13.29+1,197
FlipLong9436.2%+3.36+316

MidShort is structurally break-even. FlipLong is marginal. The in-strategy dashboard’s 70–83% claims don’t survive the full sample. Two signals carry the edge: MidLong and FlipShort.

Cross-and-close vs straddle

The live strategy required cross-and-close (Low ≤ mid AND Close > mid for longs). I A/B’d it against straddle (any bar touching mid):

PatternnExp/tr
Straddle897+4.08
Cross-and-close862+1.43

Straddle wins. Cross-and-close waits for confirmation and eats 2.65 pts of edge per trade.

VWAP filter direction

The live strategy applied STRATEGY_PASS — long requires Close > VWAP, short requires Close < VWAP (trade with VWAP). My prior work on FILL signals used TOWARD_VWAP — long from below VWAP (mean-reversion toward VWAP). Tested head-to-head:

SignalSTRATEGY_PASS (with)TOWARD_VWAP (mean-rev)Winner
MidLong+3.81+7.25TOWARD
FlipShort+33.89 (n=14)−3.90STRATEGY_PASS
MidShort−2.98−1.34neither (dead)
FlipLong−6.47−1.48neither (dead)

Standard mean-reversion signals want TOWARD-VWAP. Flip signals are momentum-like (price already broke the originating extreme) — they want WITH-VWAP. The live strategy’s VWAP filter was backwards for MidLong and correct for FlipShort.

MN HOLD cross-filter

Applied the MN HOLD classifier from mn-hold-side:

MN modenExp/tr
MN_INACTIVE655+2.50
MN_OPPOSED107+2.40
MN_ALIGNED100−6.63
MN_PRIME_ALIGNED46−4.07

Symmetric opposite of the ORB × MN HOLD result: on trend days, mean-reversion at the OR midpoint gets run over — regardless of which direction you take it. MN_INACTIVE is the only safe universe.

The winning stack (Part 1 exit)

CohortnWin%Exp/trTotal
MidLong + MN_INACTIVE + TOWARD_VWAP11643.1%+10.57+1,226
FlipShort + MN_INACTIVE + STRATEGY_PASS1478.6%+33.89+475

Part 2 — stop / cutoff / breakeven sweep (backtest_midpoint_reversion_v3_def.py)

Took the two winning cohorts (plus three comparison cohorts) and ran three parameter sweeps back-to-back.

D — fixed-tick stop vs OB-extreme stop

Tested stops of OB / 40 / 30 / 25 / 20 / 15 / 10 pts.

CohortOB stop40p20p10p
MidLong + MN_INACTIVE + TOWARD+16.14+14.62+9.48+4.17
FlipShort + MN_INACTIVE+15.96+5.08+0.84+1.58

Tighter stops destroy the edge on every cohort. Average wins are +76 pts; average losses on the OB stop are −65 pts. The math only closes on the wide stop because the wins have to clear the losses by a full win’s worth, not a tight stop’s worth. Tight stops get hit before the reversion plays out.

E — entry cutoff sweep

Tested cutoffs 11:00 / 11:30 / 12:00 / 12:30 / 13:00 / 13:30 / 14:00. Winners in bold:

Cohort11:0011:3013:0014:00
MidLong + MN_INACTIVE + TOWARD+18.00+19.98 (n=86, 51.2% win)+17.80+16.14
FlipShort + MN_INACTIVE+17.23+18.53 (n=68, 54.4% win)+15.96+15.96
MidLong + MN_INACTIVE (all VWAP)+10.10 (n=161, 42.2%)+9.95+7.73+7.23

Pattern is universal: earlier is better, 11:30 is the sweet spot. The live strategy ran to 14:00 and caught a lot of fading post-lunch setups.

F — breakeven move

Tested moving stop to entry after price moved ≥ 50 pts in favour.

Configno-BEBE@20BE@30BE@40BE@50
MidLong + INACTIVE + TOWARD+19.98+10.48+15.21+20.65+20.44
FlipShort + INACTIVE+18.53+5.94+4.57+11.06+15.05

BE@40 is a rounding-error improvement on MidLong (+0.67/tr on 86 trades). On FlipShort and every other cohort the BE hurts materially. BE turns wins into flat exits faster than it saves losers, and the win-size distribution (+70–80 pt average) dwarfs the stop savings. Conclusion: don’t use breakeven.

The ship config

ParameterLegacy valueV3 valueReason
Signals enabledall 4MidLong + FlipShort onlyMidShort −2.42/tr, FlipLong −3.98/tr
Entry patterncross-and-closestraddle+2.65 pts/trade
MN HOLD gatenoneskip when activeMN_ALIGNED −6.63/tr
Entry cutoff14:0011:30earlier = cleaner setups
Stop40-tick fixed (or opposite OB)OB-extreme onlytight stops bleed edge
VWAP filter — MidLongClose > VWAPClose < VWAP (inverted)+3.44 pts/trade
VWAP filter — FlipShortClose < VWAPunchangedcorrect already
Breakevenavailableoffconverts wins to flats

Combined expected performance on the live stack (MidLong + FlipShort under V3 defaults):

Implementation

History