Appearance
Hourly Pullback Campaign (H5, H6)
Phase 2c's pullback mean reversion (H1) is the project's one real edge, and Phase 2d graduated it under sizing and slippage. The open question the retro flagged is breadth: does the pullback structure improve when executed — or defined — on the already-cached hourly bars (zero new ingestion)? Two candidates test it, each its own ledger row, both on MES and MNQ hourly bars. Code: hourly_exec_pullback.py (H5); H6 reuses pullback_reversion.py on hourly bars.
Pre-registration. Both rules, their fitted-vs-fixed parameters, and the shared hourly pass bar below were fixed before the real-data run — the trend filter, time stops, and 1-tick slippage are fixed priors, never fitted. Only Results were filled afterward.
Scoring on hourly bars (shared)
Both candidates mark equity on hourly bars, then the curve is resampled to daily closes before Sharpe / drawdown / bootstrap CI, so the 252 annualization stays correct and every number is directly comparable with the daily ledger (hourly returns are never annualized directly). The MES/MNQ hourly frames resample to exactly the daily frame's 2,239-day calendar (Sun–Fri), validated in Phase 2d workstream 0d. Walk-forward windows are stated in hourly bars (train 9,548 / test 2,388 ≈ the daily 504/126 calendar span, ~19 bars/CME day), so the stitched OOS covers ~2020-12 → 2026-03 — the same span as daily H1.
Daily-derived gates are served on a one-day lag (protocol 7): an hourly bar inside day D cannot know D's close, so the 200-day trend filter serves D−1's value on day D, via features.lagged_entry_gate_from_features.
H5 — daily signal, hourly execution
Hypothesis. Phase 2c showed the bounce persists to the next day's open, so waiting for the daily close to enter may leave entry edge on the table. H5 keeps H1's signal untouched and only refines execution: react to the prior-low breach the moment it happens intraday, not hours later at the close.
Rule. Long/flat, single entry per cycle. Compute the prior N-day low and M-day high from daily bars through D−1 (both known before day D opens). Enter long on the first hourly close below the N-day low, gated by the one-day- lagged 200-day trend filter. Exit on the first hourly close above the M-day high, or after a 10-trading-day time stop (≈ 230 hourly bars), whichever first.
H5's entry level for day D (min of daily lows D−N…D−1) is exactly the level daily H1 compares D's close against — the only difference is reacting hours earlier, so a positive result is a pure execution improvement.
| Parameter | Kind | Values |
|---|---|---|
| N — prior-low entry lookback (daily) | fitted (grid) | |
| M — prior-high exit lookback (daily) | fitted (grid) | |
| 200-day trend filter (lagged 1 day) | fixed prior | on |
| Time stop | fixed prior | ~230 hourly bars (10 trading days) |
| Slippage | fixed prior | 1 tick/side |
H6 — native hourly pullback reversion
Hypothesis. Does reversion also operate at an intra-week horizon that daily bars cannot see? H6 redefines the signal on hourly bars — fading a prior N-hour low rather than a prior N-day low.
Rule. Long/flat, single entry per cycle. Enter long on an hourly close below the prior N-hour low, gated by the one-day-lagged 200-day daily trend filter; exit on the first hourly close above the prior M-hour high, or after the time stop. This is the daily H1 machinery (PullbackReversion) run on hourly bars.
| Parameter | Kind | Values |
|---|---|---|
| N — prior-low entry lookback (hours) | fitted (grid) | {24, 48, 96} (~1–4 days) |
| M — prior-high exit lookback (hours) | fitted (grid) | |
| 200-day trend filter (lagged 1 day) | fixed prior | on |
| Time stop | fixed prior | 115 hourly bars (~5 trading days) |
| Slippage | fixed prior | 1 tick/side |
Hourly pass bar (shared, pre-register)
A candidate passes only if, with slippage on, its stitched walk-forward OOS result on both MES and MNQ:
- beats buy-and-hold (same span) and the re-fit
swing_breakoutbaseline on the same hourly bars on Sharpe (the campaign bar, transposed), and - beats daily H1's stitched OOS Sharpe (0.85 MES / 0.86 MNQ) over the matching calendar span — H1 is the champion; clearing the old bar while losing to H1 is verdict "no improvement — daily H1 remains champion", not a pass, and
- has a bootstrap 90% CI (on the daily-resampled OOS returns) excluding 0, with drawdown not materially worse than daily H1's.
Hourly candidates trade more, so fee + slippage drag scales with trade count — each result reports fee_drag, roll columns, and a 0/1/2-tick slippage sensitivity table so the cost side of the verdict is visible.
Results
Real-data walk-forward, 2019-05-06 → 2026-07-12, hourly bars, 1 tick/side slippage on. Walk-forward windows 9,548 / 2,388 hourly bars (13 splits, stitched OOS 2020-12-15 → 2026-03-19 — the same calendar span as daily H1), US$100k account, 1 contract. Both candidates ran through the standard evaluate path with --bars 1h.
H5 — daily signal, hourly execution (OOS):
| Metric | MES H5 | MES baseline | MES buy&hold | MNQ H5 | MNQ baseline | MNQ buy&hold | |
|---|---|---|---|---|---|---|---|
| Sharpe | 0.33 | 0.47 | 0.47 | 0.45 | 0.66 | 0.42 | |
| Sharpe 90% CI | [−0.20, 0.91] | [−0.08, 0.99] | |||||
| Total return | +4.1% | +6.3% | +11.2% | +10.3% | +13.8% | +17.5% | |
| Max drawdown | −3.3% | −3.4% | −6.0% | −5.8% | −4.3% | −11.1% | |
| Exposure | 29.0% | 43.3% | 100% | 27.6% | 30.8% | 100% | |
| Trades | 82 | 456 | 1 | 73 | 468 | 1 | |
| Fee drag | 0.22% | 1.25% | 0.00% | 0.20% | 1.28% | 0.00% |
H6 — native hourly pullback (OOS):
| Metric | MES H6 | MES baseline | MES buy&hold | MNQ H6 | MNQ baseline | MNQ buy&hold | |
|---|---|---|---|---|---|---|---|
| Sharpe | 0.30 | 0.47 | 0.47 | 0.53 | 0.66 | 0.42 | |
| Sharpe 90% CI | [−0.17, 0.82] | [0.02, 1.16] | |||||
| Total return | +2.6% | +6.3% | +11.2% | +8.2% | +13.8% | +17.5% | |
| Max drawdown | −2.1% | −3.4% | −6.0% | −4.1% | −4.3% | −11.1% | |
| Exposure | 16.3% | 43.3% | 100% | 16.2% | 30.8% | 100% | |
| Trades | 165 | 456 | 1 | 174 | 468 | 1 | |
| Fee drag | 0.05% | 1.25% | 0.00% | 0.05% | 1.28% | 0.00% |
Slippage sensitivity (OOS Sharpe, tick/side):
| tick/side | MES H5 | MNQ H5 | MES H6 | MNQ H6 |
|---|---|---|---|---|
| 0 | 0.34 | 0.45 | 0.34 | 0.54 |
| 1 (prior) | 0.33 | 0.45 | 0.30 | 0.53 |
| 2 | 0.31 | 0.44 | 0.25 | 0.52 |
Champion for reference — daily H1 OOS Sharpe: 0.85 (MES) / 0.86 (MNQ).
Verdict — both fail; daily H1 remains champion
Neither hourly candidate clears the pass bar on either symbol:
- H5 (hourly execution) fails. OOS Sharpe 0.33 (MES) / 0.45 (MNQ) is below the re-fit
swing_breakoutbaseline (0.47 / 0.66) on both symbols, both bootstrap CIs include 0, and it is nowhere near daily H1 (0.85 / 0.86). The hypothesis — that reacting to the prior-low breach hours earlier than the daily close improves the fill — is not just unsupported, it is backwards: H5 trades a similar number of times as daily H1 (82 vs 71 OOS on MES) at similar exposure, so this is not a cost artifact — entering on the first intraday breach catches breaches that the daily close would not have confirmed, and those revert less reliably. The daily-close confirmation is part of the edge, not merely a lag to shave. - H6 (native hourly reversion) fails. OOS Sharpe 0.30 (MES) / 0.53 (MNQ): below the baseline on both (0.47 / 0.66), MES CI includes 0, and both far below daily H1. Fading a prior N-hour low does not capture a real edge — the reversion structure that works on daily bars does not exist at the intra-week horizon.
The sharpest read comes from the baseline: on hourly bars the long-only Donchian breakout posts 0.47 / 0.66 OOS — its best showing anywhere in the project, and better than either reversion candidate — the mirror image of the daily result, where breakout failed (≈0) and reversion won. Short-horizon mean reversion is a daily-bar phenomenon on MES/MNQ; at the hourly horizon momentum/continuation dominates instead. Slippage barely moves any of these numbers (≤0.05 across 0/1/2 ticks), so the failures are structural, not a cost artifact — the edge simply is not there on hourly bars.
Both recorded as fails in the ledger. Daily H1 — graduated under sizing and slippage — remains the project's one and only edge.
Data lineage note
H5's daily entry/exit levels are computed from the hourly frame aggregated to daily (hourly_exec_pullback.daily_from_hourly), not the standalone daily frame. The two frames are Panama back-adjusted independently, so their price bases diverge by hundreds of points on older data (Phase 2d workstream 0d measured a −332-point mean offset on MES); comparing hourly closes against levels from the standalone daily frame would (and in a first pass did) produce almost no entries. Aggregating the hourly bars to daily keeps levels and fills on one basis. H6 is immune — it derives everything from the hourly stream, and its only external input is the (boolean) lagged trend gate.