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-24The 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.
- High formed first, then low → price came down through mid once → on the next touch of mid from below, short targeting range low
- Low formed first, then high → price went up through mid once → on the next touch of mid from above, long targeting range high
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):
| Variable | Meaning |
|---|---|
rangeHigh / rangeLow | running box high / low |
rangeHighTime / rangeLowTime | bar index where each extreme was set |
obHighApproached | true once High ≥ (mid + rangeHigh)/2 during the window |
obLowApproached | true once Low ≤ (mid + rangeLow)/2 during the window |
At 10:30 the FSM produces:
isHighFirst = rangeHighTime < rangeLowTimeflipLong = isHighFirst AND obHighApproached AND obHighApproachedTime > rangeLowTimeflipShort = isLowFirst AND obLowApproached AND obLowApproachedTime > rangeHighTime
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:
| Signal | Condition | Direction | Target |
|---|---|---|---|
| MidShort | high-first, no flip | short at mid | range low |
| MidLong | low-first, no flip | long at mid | range high |
| FlipShort | low-first, flip triggered | short at mid | range low |
| FlipLong | high-first, flip triggered | long at mid | range 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).
| Signal | n | Win% | Exp/tr | Total pts |
|---|---|---|---|---|
| MidShort | 361 | 31.0% | −0.01 | −4 |
| MidLong | 352 | 34.9% | +6.11 | +2,152 |
| FlipShort | 90 | 48.9% | +13.29 | +1,197 |
| FlipLong | 94 | 36.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):
| Pattern | n | Exp/tr |
|---|---|---|
| Straddle | 897 | +4.08 |
| Cross-and-close | 862 | +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:
| Signal | STRATEGY_PASS (with) | TOWARD_VWAP (mean-rev) | Winner |
|---|---|---|---|
| MidLong | +3.81 | +7.25 | TOWARD |
| FlipShort | +33.89 (n=14) | −3.90 | STRATEGY_PASS |
| MidShort | −2.98 | −1.34 | neither (dead) |
| FlipLong | −6.47 | −1.48 | neither (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 mode | n | Exp/tr |
|---|---|---|
| MN_INACTIVE | 655 | +2.50 |
| MN_OPPOSED | 107 | +2.40 |
| MN_ALIGNED | 100 | −6.63 |
| MN_PRIME_ALIGNED | 46 | −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)
| Cohort | n | Win% | Exp/tr | Total |
|---|---|---|---|---|
| MidLong + MN_INACTIVE + TOWARD_VWAP | 116 | 43.1% | +10.57 | +1,226 |
| FlipShort + MN_INACTIVE + STRATEGY_PASS | 14 | 78.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.
| Cohort | OB stop | 40p | 20p | 10p |
|---|---|---|---|---|
| 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:
| Cohort | 11:00 | 11:30 | 13:00 | 14: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.
| Config | no-BE | BE@20 | BE@30 | BE@40 | BE@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
| Parameter | Legacy value | V3 value | Reason |
|---|---|---|---|
| Signals enabled | all 4 | MidLong + FlipShort only | MidShort −2.42/tr, FlipLong −3.98/tr |
| Entry pattern | cross-and-close | straddle | +2.65 pts/trade |
| MN HOLD gate | none | skip when active | MN_ALIGNED −6.63/tr |
| Entry cutoff | 14:00 | 11:30 | earlier = cleaner setups |
| Stop | 40-tick fixed (or opposite OB) | OB-extreme only | tight stops bleed edge |
| VWAP filter — MidLong | Close > VWAP | Close < VWAP (inverted) | +3.44 pts/trade |
| VWAP filter — FlipShort | Close < VWAP | unchanged | correct already |
| Breakeven | available | off | converts wins to flats |
Combined expected performance on the live stack (MidLong + FlipShort under V3 defaults):
- n ≈ 154 trades across 1,370 sessions (~11% strike rate)
- ~52% win rate
- ~+19.4 pts/trade expectancy
- ~+2,978 total pts
Implementation
MidpointReversion.cs— upgraded with seven new V3 parameters defaulting to the winning config:MnHoldSkipActive(readshud_data.txt, skips entries on active MN HOLD sessions)EntryCutoffHHMM(default 1130)UseStraddleEntry(default true)InvertMidLongVwap(default true — MidLong requiresClose < VWAP)EnableMidLong/EnableMidShort/EnableFlipShort/EnableFlipLong(individual kill switches; MidShort + FlipLong default OFF)HudDataPathfor the MN HOLD reader- Legacy behavior is still reachable via the toggles, so live A/B is possible without rebuilding.
PredictionModel.cs/PredictionModelWeb.cs— addedOB MIDandOB MID MAGNETto the MN HOLD confluence dictionary with{−5 aligned, −20/−18 opposed, 0 PRIME}. Mean-reversion signals get a light aligned penalty and a heavy opposed penalty because the backtest showed MN-active sessions hurt mid-reversion regardless of direction agreement.
Related
- Underlying line model: OB RANGE (model 10)
- Trend-day cross-filter: MN HOLD-SIDE (model 26) and the full research writeup
- Inverse case (why ORB direct breakouts want MN HOLD aligned): ORB break & retest targets
History
- 2026-04-24 — Full backtest + strategy upgrade shipped.
backtest_midpoint_reversion_v2.py(signal + VWAP + entry-pattern sweep) andbacktest_midpoint_reversion_v3_def.py(stop / cutoff / breakeven sweep) underC:\SMC\scripts\. Results archived atC:\SMC\backtest_results\midpoint_reversion_v2.txtandmidpoint_reversion_v3_def.txt.MidpointReversion.cspatched; confluence table updated in both indicators; this writeup published.