Module 15 — Intraday Volatility & Regime Routing
Part III · Intraday Design, Alpha & Playbooks · Priority 🎯 Core Status: Draft v0.2 · Scope: commodity ETFs/ETPs + BTC/ETH spot & linear perps · Horizon: M1/M5 (M15 where appropriate), same-session flat Prerequisites: M5, M6, M7, M8, M9, M10, M11, M22, M23
Overview
You start this module with the venue and executable price, not with an indicator.
On the commodity ETP lane, shares trade U.S. regular trading hours (RTH) at market prices that drift from net asset value (NAV). Authorized participants create and redeem shares only in large creation units SEC ETF bulletin. A physical gold trust like GLD holds allocated bullion and reports daily vault holdings. This wrapper differs fundamentally from a futures pool that rolls front contracts, where roll yield diverges from spot when curves slope Pomegra on commodity ETF liquidity and roll. Three liquidity layers dictate price: quoted top-of-book spread, creation/redemption depth behind it, and underlying market depth. These layers explain why ETP spreads widen at the open and compress midday Pomegra on commodity ETF liquidity and roll and why even tight RTH spreads on GLD represent a mandatory toll on every turn ContentWave on GLD liquidity 2026.
On the crypto lane, a BTC or ETH linear perpetual contract never expires. Instead, longs and shorts exchange recurring funding payments. This schedule is venue-specific and must be declared before holding positions across funding timestamps Coinbase perpetual fundingCoinbase on funding rates. Commodity ETP spreads follow a predictable U-shaped hourly curve, whereas crypto spreads remain tight but spike violently on liquidity gaps TOS Indicators on ETF hourly volatility models.
THE THERMOSTAT & TRAFFIC LIGHT
┌─────────────────────────────────────────────────────────────────┐
│ M15 REGIME ROUTER │
│ │
│ [ GREEN LIGHT ] Expansion (ATR high + Fast RV > Slow RV) │
│ --> Trend/Breakout Sleeves ON (M10) │
│ │
│ [ YELLOW LIGHT ] Quiet (ATR low + Spread low) │
│ --> Mean-Reversion Sleeves ON (M11) │
│ │
│ [ RED LIGHT ] Late Expansion OR Stressed / Wide Spread │
│ --> Kill ALL New Entries (Preserve Capital) │
│ │
│ [ STICKY SWITCH ] Hysteresis & Minimum Dwell (3-6 bars) │
│ --> Prevents whipsaw & fee churn │
└─────────────────────────────────────────────────────────────────┘
Volatility reliably predicts itself. Today’s range forecasts tomorrow’s range far better than today’s return forecasts direction. Volatility clusters: quiet bars follow quiet bars, and violent bars follow violent bars. Furthermore, volatility levels mean-revert. Expansions exhaust, and compressions eventually break. Because volatility forecasting is far more tractable than return forecasting, regime routing is essential. You do not need to predict market direction. You only need to assess whether the tape currently pays for continuation or reversion.
The preregistered hypothesis is narrow: on these venues, a causal technical regime — realized volatility percentile, ATR bucket, spread bucket, and vol-of-vol/jump filters computed on trailing completed M1/M5/M15 bars (with completed H1/H4/D1 only as released gates) — routes or suppresses frozen M10 trend and M11 reversion candidates so that routed net expectancy beats always-on baselines after spread, fees, and conservative same-bar execution, surviving purged out-of-sample testing.
Information arrives in session waves. At the RTH open, overnight order imbalances expand the initial range. Midday lull represents an empty warehouse where mean-reversion thrives. Fresh expansion with fast realized volatility exceeding slow realized volatility signals heavy institutional flow paying for breakout continuation.
Who funds your edge?
- The always-on mean reversion trader fading genuine macro expansion.
- The always-on momentum trader churning fees inside dead midday chop.
- The late-expansion chaser buying after fast volatility has already rolled over.
Evidence status: hypothesis, mechanics-supported. ETP liquidity tiers, session schedules, and volatility clustering are established market mechanics. Simple ATR percentile buckets that adjust sizing during high volatility are documented risk controls Tenth Meridian on ATR percentile regimes. The speed-versus-accuracy tradeoff across threshold, HMM, and classifier models represents a standard engineering choice VolatilityBox on volatility regime detection. Walk-forward HMM templates on BTC provide structural reference but do not prove net profitability after execution costs QuantInsti on regime-adaptive HMM trading.
The primary failure modes are:
- Regime whipsaw and transition churn: oscillating across thresholds flips books constantly, burning capital on spread and fees.
- Late-expansion mirage: entering trend continuation after fast volatility has rolled below slow volatility.
- Ambiguous-bar optimism: assuming favorable fills on bars touching both stop and target ohlcv.io on bar resolution.
- Same-close execution bias: pricing fills at signal close rather than next eligible open Saral on next-bar-open execution.
💡 Intuition: A regime router functions like a building thermostat or a traffic light with sticky switches. Green Light (Expansion) powers the trend crew. Yellow Light (Quiet) powers the reversion crew. Red Light (Wide spread or spent vol) clears the floor. The sticky switch (hysteresis and dwell) prevents the light from flickering every five minutes, protecting your account from commission churn.
📌 Data Contract: Dataset is OHLCV + observed spread only. Signals evaluate on the close of bar
tand execute at the open of bart+1with full spread deducted. Bars touching both stop and target resolve adverse-first. Completed H1/H4/D1 bars act as causal gates joined at release timestamp. Intraday only, same-session flat (15:55 ET for ETPs, 23:50 UTC for crypto).
How It Works
Volatility serves two distinct roles. First, estimators measure current market turbulence. Second, classifiers convert measurements into routing decisions. The cost surface (spread-to-ATR and cost-to-target) determines whether trading is economically viable.
1. Volatility Estimators
Close-to-close volatility discards all intrabar price action. Range-based estimators extract significant information from high, low, and open prices. This efficiency matters on intraday horizons with limited sample sizes (78 M5 bars per RTH session).
| Estimator | Bar Inputs | Information Added | Primary Use Case |
|---|---|---|---|
| Close-to-Close | close, prior close | Baseline return dispersion | Reference benchmark, ML feature |
| ATR (Wilder) | high, low, prior close | Absolute price range units | Sizing, stops, regime percentiles Investopedia on ATR |
| Parkinson | high, low | Intrabar range variance | Rapid convergence from small samples |
| Garman-Klass | open, high, low, close | Splits range from open-to-close drift | Efficient OHLC variance estimate |
| Yang-Zhang | OHLC + prior close | Continuous drift + overnight gap | Gap-aware multi-session tracking |
# One bar through four estimators — each input tells a story
# Q: which price path happened inside the bar, not just where it closed?
import numpy as np
o, h, l, c, prev_c = 49.20, 49.45, 49.10, 49.30, 49.15
ann = np.sqrt(252 * 78) # intraday M5 annualization placeholder — scaling only
close_to_close = abs(np.log(c / prev_c)) * ann
parkinson = np.sqrt(np.log(h / l)**2 / (4 * np.log(2))) * ann
garman_klass = np.sqrt(max(
0.5*np.log(h/l)**2 - (2*np.log(2) - 1)*np.log(c/o)**2, 0)) * ann
true_range = max(h - l, abs(h - prev_c), abs(l - prev_c))
for name, value in [("close-to-close", close_to_close),
("Parkinson", parkinson), ("Garman-Klass", garman_klass)]:
print(f"{name:14s} {value:.2%}")
print(f"true range {true_range:.2f} price units")
# -> close-to-close 4.31% | Parkinson 6.89% | Garman-Klass 5.92% | TR 0.35
What this means for your P&L: Close-to-close registered only 4.31% because the bar settled near the prior close. Yet true range was 0.35 with Parkinson at 6.89%, proving substantial intrabar traversal. Large range with small net change indicates rotational chop (reversion weather). Large range with large net change indicates institutional trend (continuation weather).
# Causal realized vol and ATR percentile — the two knobs every router needs
# Q: how windy is it lately, and how windy is that versus its own history?
import pandas as pd
import numpy as np
def atr_percentile(atr: pd.Series, lookback=500):
# Percentile converts raw units to a 0-100 scale that travels across symbols
# Low ATR on GLD and low ATR on BTC perp mean different dollars, same dial
pct = atr.rolling(lookback, min_periods=250).rank(pct=True)
return pct
def realized_vol(log_returns: pd.Series, window=20):
# Standard deviation of log returns over window, times root-window for scaling
rv = log_returns.rolling(window).std() * np.sqrt(window)
return rv
🧪 Evidence: ATR percentile ranking establishes a normalized 0–100 scale across disparate assets, adjusting position size during volatility spikes Tenth Meridian on ATR percentile regimes. The broader spectrum of detectors (thresholds, crossovers, HMM, classifiers) balances responsiveness against stability VolatilityBox on volatility regime detection.
2. Spread as the Second Regime
Volatility and liquidity operate as coupled dimensions. An asset can exhibit high volatility with tight spreads (macro catalyst on liquid names) or low volatility with wide spreads (midday liquidity drought). Because hourly volatility curves vary across assets, identical ATR readings at 10:00 and 12:30 carry different net trading implications TOS Indicators on ETF hourly volatility models.
You maintain two explicit dials:
- Volatility Dial: ATR percentile combined with the fast-to-slow realized volatility ratio (
RV_fast / RV_slow) to separate fresh expansion from late exhaustion. - Liquidity Dial: Trailing spread percentile combined with spread-to-ATR and cost-to-target ratios.
Trading activates only when both dials confirm affordability.
3. The Regime Master Switch
The regime router acts as a breaker panel for the trading book Tenth Meridian on ATR percentile regimes. It does not select direction; it authorizes active sleeves:
| Regime Label | Volatility Condition | Spread Condition | Mean Reversion (M11) | Momentum / Breakout (M10) |
|---|---|---|---|---|
| Quiet | ATR %ile < 30 | Spread %ile < 70 | ON — Full Size | OFF |
| Normal | ATR %ile 30 to 70 | Spread %ile < 70 | ON — Half Size | Watchlist Only |
| Expansion | ATR %ile ≥ 70 AND Fast RV > Slow RV | Spread %ile < 85 | OFF | ON — Full Size |
| Late Expansion | ATR %ile ≥ 70 AND Fast RV ≤ Slow RV | Any | OFF | OFF (Flat) |
| Stressed / Wide | Any | Spread %ile ≥ 85 | OFF (Kill) | OFF (Kill) |
State stability is load-bearing. To prevent whipsaw across threshold boundaries, enforce three rules:
- Hysteresis Bands: Enter Quiet below the 30th percentile; exit Quiet only above the 45th. Enter Expansion above the 70th; exit below the 60th.
- Confirmation: New regime state must hold for 2 to 3 consecutive bar closes before flipping the book.
- Minimum Dwell: Once armed, a regime remains active for at least 3 to 6 M5 bars. Spread-kill overrides dwell immediately.
flowchart TD
bars["OHLCV + spread<br/>M1/M5 + completed<br/>H1/H4/D1 releases"] --> vol["Vol engine<br/>ATR %ile + RV fast/slow<br/>spread %ile + vol-of-vol"]
vol --> delay{"Causal gate?<br/>completed bar +<br/>publish lag"}
delay -->|"no: forming bar"| hold["Hold prior regime<br/>forward fill"]
delay -->|"yes: released"| route{"Route with<br/>hysteresis + dwell?"}
hold --> route
route -->|"quiet"| rev["Reversion sleeve<br/>M11 candidates only"]
route -->|"expansion"| brk["Breakout sleeve<br/>M10 candidates only"]
route -->|"normal: half reversion<br/>late/ stressed: flat"| flat["Risk-off or half<br/>no new entries"]
rev --> book["Combined book<br/>positions + next-open fills<br/>spread deducted"]
brk --> book
flat --> book
book -->|"each close t<br/>re-evaluate t+1 open"| vol
classDef data fill:#e8f0fe,stroke:#4a86e8
classDef process fill:#f3f3f3,stroke:#888
classDef decision fill:#fff4d6,stroke:#d6a300
classDef risk fill:#fde8e8,stroke:#c0392b
classDef ok fill:#e6f4ea,stroke:#2e7d32
class bars,book data
class vol,hold,flat process
class delay,route decision
class rev,brk okWorked Sizing Example: ETP at $49.00, M5 ATR(14) = $0.12, observed spread = $0.012.
- Spread-to-ATR:
$0.012 / $0.12 = 10%(Passes 20% ceiling). - Cost-to-Target: Target = $0.20.
($0.012 + $0.004 fee stress) / $0.20 = 8.0%(Passes 12% ceiling). - Position Sizing: Equity $20,000, 1.0% risk = $200. Stop distance = 1.0 × ATR = $0.12. Size =
200 / 0.12 = 1,666 shares($199.92 risk).
4. What GMM and HMM Add
- Threshold Router: Fixed mathematical rules, zero training parameters, instant deployment, highly robust with hysteresis.
- GMM (Gaussian Mixture Model): Soft unsupervised clustering across standardized features (
z_rv,z_atr_pctile). Provides posterior probabilities for smoother transitions. - HMM (Hidden Markov Model): Learns transition matrices where current state depends on prior state. Encodes dwell stickiness natively but requires walk-forward fitting with strict embargoes to prevent over-smoothing.
Strategy Rules
All routing follows one execution skeleton: compute causal volatility metrics, publish regime at bar close t, route frozen sleeves at open t+1, and flatten unconditionally at session end.
1. Contract Surfaces
| Surface | Specification | P&L Impact |
|---|---|---|
| Instrument Lane | Physical trust vs futures pool; BTC/ETH spot vs linear perp | Wrapper governs roll yield; venue governs funding and short availability |
| Horizon & Hold | M1/M5 primary, next eligible open fill, max hold T_max bars |
Horizon dictates transaction cost budget |
| Session Bounds | ETP RTH (09:30–16:00 ET) or Crypto UTC synthetic (00:00–23:50 UTC) | Session bounds define bar count and flatten timestamps |
| Cost Schedule | Timestamped observed spread, venue fee tiers, slippage stress | Deducted per turn before computing net returns |
| Causal HTF Join | Completed H1/H4/D1 right-labeled with 1-bar lag | Eliminates look-ahead bias from forming bars |
2. Gate Set (Evaluated at Close t, Executed at Open t+1)
| Gate | Expression | Preregistered Ceiling | Action on Breach |
|---|---|---|---|
| Spread-to-ATR | spread[t] / ATR_M5[t] |
≤ 0.20 | Block new entry |
| Cost-to-Target | (spread[t] + fee[t] + slip) / target_dist[t] |
≤ 0.12 | Block new entry |
| Spread Regime | spread_percentile[t] |
< 0.85 | Block new entry (Stressed) |
| Ambiguous Share | Bars touching both stop and target | < 15% of total trades | Reject strategy backtest |
3. Stability Controls
- Hysteresis: Quiet enter < 0.30, exit > 0.45; Expansion enter ≥ 0.70 (with Fast RV > Slow RV), exit < 0.60.
- Confirmation: Require 2 consecutive closes before switching state.
- Minimum Dwell: Hold active regime for minimum 4 M5 bars (spread kill overrides dwell).
- Kill-State: Spread %ile ≥ 0.85 or Vol-of-Vol > 95th %ile forces immediate flat and 4-bar cooldown.
Building It In Python
# Features that actually drive the router — causal, OHLCV + spread only
# Q: how windy + how expensive is this bar versus its own history?
import polars as pl
import numpy as np
def true_range(frame: pl.DataFrame) -> pl.Series:
h, l, c = frame["high"], frame["low"], frame["close"]
prev_c = c.shift(1)
return pl.max_horizontal([(h - l).abs(), (h - prev_c).abs(), (l - prev_c).abs()])
def atr_wilder(high, low, close, n=14):
tr = true_range(pl.DataFrame({"high": high, "low": low, "close": close}))
# Wilder smoothing: seeded by SMA, then 1/n update — causal, no look-ahead
atr = tr.ewm_mean(alpha=1/n, adjust=False, min_periods=n)
return atr
def rolling_percentile(s: pl.Series, lookback=500):
# Rank over trailing window only — no full-sample leakage
return s.rolling(lookback, min_periods=max(250, lookback//2)).rank(method="average") / s.rolling(lookback).count()
def realized_vol(log_ret: pl.Series, window=20):
# std of log returns, scaled by window root — bar-based, not tick
return log_ret.rolling(window).std() * np.sqrt(window)
# Bucket router — hysteresis + confirmation + minimum dwell, walk-forward safe
# Q: which crew is allowed today, and did we avoid flipping on noise?
def bucket_router(
atr_pct: np.ndarray, # 0..1 trailing ATR percentile
rv_fast: np.ndarray, # 10-bar RV
rv_slow: np.ndarray, # 40-bar RV
spread_pct: np.ndarray, # 0..1 trailing spread percentile
quiet_enter=0.30, quiet_exit=0.45,
exp_enter=0.70, exp_exit=0.60,
confirm=2, dwell=4,
):
n = len(atr_pct)
raw = np.full(n, "normal", dtype=object)
for i in range(n):
if spread_pct[i] >= 0.85:
raw[i] = "stressed"
elif atr_pct[i] < quiet_enter and np.isfinite(atr_pct[i]):
raw[i] = "quiet"
elif atr_pct[i] >= exp_enter and rv_fast[i] > rv_slow[i]:
raw[i] = "expansion"
elif atr_pct[i] >= exp_enter and rv_fast[i] <= rv_slow[i]:
raw[i] = "late_expansion"
# Causal confirmation: new label must hold 'confirm' closes before it sticks
confirmed = raw.copy()
for i in range(1, n):
if raw[i] != confirmed[i-1]:
streak = 1
for j in range(i-1, max(-1, i-confirm), -1):
if raw[j] == raw[i]:
streak += 1
else:
break
if streak < confirm:
confirmed[i] = confirmed[i-1] # keep prior regime
# Minimum dwell: once flipped, hold at least 'dwell' bars
dwelled = confirmed.copy()
last_flip = 0
for i in range(1, n):
if dwelled[i] != dwelled[i-1]:
if i - last_flip < dwell and dwelled[i] != "stressed":
dwelled[i] = dwelled[i-1] # suppress flip — dwell says wait
else:
last_flip = i
# Map to permission flags
perm = []
for r in dwelled:
if r == "quiet":
perm.append({"reversion": True, "breakout": False, "size_mult": 1.0})
elif r == "normal":
perm.append({"reversion": True, "breakout": False, "size_mult": 0.5})
elif r == "expansion":
perm.append({"reversion": False, "breakout": True, "size_mult": 1.0})
else: # late_expansion or stressed
perm.append({"reversion": False, "breakout": False, "size_mult": 0.0})
return dwelled, perm
# HMM and GMM challengers — same features, same overlay, walk-forward only
# Q: does a fitted latent-state model beat the ruler-drawn buckets after cost?
from sklearn.mixture import GaussianMixture
from hmmlearn.hmm import GaussianHMM
def fit_gmm_router(features_train: np.ndarray, features_test: np.ndarray, k=3):
gmm = GaussianMixture(n_components=k, covariance_type="diag", random_state=42)
gmm.fit(features_train)
comp_means = gmm.means_[:, 0]
order = np.argsort(comp_means)
resp = gmm.predict_proba(features_test)
hard = gmm.predict(features_test)
label_map = {order[0]: "quiet", order[1]: "normal", order[2]: "expansion"}
raw_labels = np.array([label_map[c] for c in hard])
return raw_labels, resp
def fit_hmm_router(features_train: np.ndarray, features_test: np.ndarray, k=3):
hmm = GaussianHMM(n_components=k, covariance_type="diag", n_iter=100, random_state=42)
hmm.fit(features_train)
train_states = hmm.predict(features_train)
s_means = np.array([features_train[train_states == s, 0].mean() for s in range(k)])
order = np.argsort(s_means)
label_map = {order[0]: "quiet", order[1]: "normal", order[2]: "expansion"}
test_states = hmm.predict(features_test)
raw_labels = np.array([label_map[s] for s in test_states])
return raw_labels, hmm.transmat_
def cost_gates(atr: np.ndarray, spread: np.ndarray, target_dist: np.ndarray,
spread_atr_cap=0.20, cost_target_cap=0.12, fee_and_slip=0.004):
spread_to_atr = spread / np.maximum(atr, 1e-9)
cost_to_target = (spread + fee_and_slip) / np.maximum(target_dist, 1e-9)
pass_gate = (spread_to_atr <= spread_atr_cap) & (cost_to_target <= cost_target_cap)
return pass_gate, spread_to_atr, cost_to_target
def replay_one_bar(signal_close_t: float, next_open: float, spread_full: float,
stop: float, target: float, time_stop_hit: bool,
bar_high_next: float, bar_low_next: float):
entry = next_open
hit_target = (bar_high_next >= target) if target > entry else (bar_low_next <= target)
hit_stop = (bar_low_next <= stop) if target > entry else (bar_high_next >= stop)
if hit_target and hit_stop:
return {"outcome": "stop", "pnl": stop - entry - spread_full, "ambiguous": True}
if hit_stop:
return {"outcome": "stop", "pnl": stop - entry - spread_full, "ambiguous": False}
if hit_target:
return {"outcome": "target", "pnl": target - entry - spread_full, "ambiguous": False}
if time_stop_hit:
return {"outcome": "time", "pnl": -spread_full, "ambiguous": False}
return {"outcome": "hold", "pnl": 0.0, "ambiguous": False}
Testing It Honestly
A combined equity curve alone proves nothing. If mean reversion loses money in quiet regimes, the router is not at fault — the sleeve is broken. Separate book evaluation is standard institutional practice.
Five Validation Checks
| Check | Core Objective | Evaluation Metric | Reject Condition |
|---|---|---|---|
| 1. Regime Attribution | Prove sleeves profit inside their specific habitats | Net P&L by sleeve × regime | Reversion negative in Quiet, or Trend negative in Expansion |
| 2. Routed vs. Baselines | Confirm routing generates positive net lift | Routed vs Always-On vs Opposite Placebo | Routed fails to beat Always-On or Opposite Placebo |
| 3. Model Complexity | Verify GMM/HMM justifies added parameters | Net OOS lift of HMM vs. Bucket baseline | Fitted models fail to beat threshold router net of churn |
| 4. Transition Churn | Ensure switching costs do not consume profits | Flips per session & spread paid | > 2–3 flips per RTH session or churn > 33% of lift |
| 5. Parameter Plateau | Verify stability across parameter shifts | Sensitivity sweeps (cuts ±10%, dwell ±2 bars) | Sharp cliff edges in P&L sensitivity traces |
# Purged walk-forward that respects label overlap — the only OOS that audits a router
# Q: did routing survive being evaluated on bars whose labels never touched the training bars?
def purged_walk_forward_splits(n_bars, train_bars=252*78, test_bars=21*78, embargo_bars=3):
splits = []
step = test_bars
for end_train in range(train_bars, n_bars - test_bars + 1, step):
start_train = end_train - train_bars
start_test = end_train + embargo_bars
end_test = start_test + test_bars
if end_test > n_bars:
break
splits.append(((start_train, end_train), (start_test, end_test)))
return splits
⚠️ Pitfall: Testing only during calm macro environments produces false confidence. Demand at least one high-volatility expansion week per test split.
Variants & Extensions
| Variant | Modification | Valid Context | Required Ablation |
|---|---|---|---|
| M15 Primary Horizon | Execute on M15 bars, hold 2–4 bars | Thin ETP hours where M5 spread-to-ATR exceeds ceiling | M5 vs. M15 on identical regime logic |
| 2D Spread-Vol Router | Joint grid: Volatility %ile × Spread %ile | Assets where volatility and spread decouple midday | 2D surface vs. Vol-only router |
| Vol-of-Vol / Jump Kill | Rolling std of RV as a size suppressor | Sudden volatility shocks without spread widening | Kill-on vs. Kill-off baseline |
| Prior D1 ATR Context | Completed D1 ATR gates intraday position sizing | High range prior day predicts wide intraday swings | D1-gated vs. D1-blind execution |
| VIX Implied Proxy (Transfer) | Implied vol index replaces realized vol | MT5 index CFDs (transfer evidence only) | Transfer note only — not canonical |
Hands-On Project
Deliverable: docs/research/m15_regime_routing_lab.md and notebooks/m15_regime_routing.ipynb reproducing all tables, atlases, regime timelines, and equity curves.
Project Card — M15 Intraday Volatility & Regime Routing (v1.0)
| Area | Declaration |
|---|---|
| Identity | Commodity ETP (GLD/PDBC proxy) on NYSE Arca Pomegra on commodity ETF liquidity and roll; Crypto BTC/ETH spot & perp on approved CEX. Fixed spread units |
| Horizon | M1/M5 primary (M15 delta). Max hold T_max. Unconditional flatten: ETP 15:55 ET, Perp 23:50 UTC |
| Clock | ETP RTH 09:30–16:00 ET. Crypto synthetic 00:00–23:50 UTC. Exclude or book funding at Coinbase perpetual funding |
| Execution | Close t → Open t+1. Full spread deducted. Adverse-first ambiguous handling ohlcv.io on bar resolutionSaral on next-bar-open execution |
| Costs | Observed timestamped spread, exchange fee tiers, p90 spread stress M22 |
| Information | Completed bars only. H1/H4/D1 joined causally via release timestamp M9 |
| Risk | Fixed fractional risk, ATR stops, daily loss cap, spread-kill triggers M23 |
| Validation | Chronological purged OOS with embargo. Threshold vs. GMM vs. HMM ablation |
| Acceptance | Routed net beats Always-On and Placebo; sleeves profitable in habitat; parameter stability plateau |
Key Takeaways
- The wage must cover the invoice: Compute spread-to-ATR and cost-to-target before evaluating any strategy signal.
- Normalize volatility: Raw ATR cannot cross symbols; trailing ATR percentiles create a portable 0–100 scale Tenth Meridian on ATR percentile regimes.
- The router is a breaker panel: Quiet arms mean reversion; Expansion arms trend; Late Expansion and Wide Spreads arm nothing.
- Simple routers set the bar: Threshold routers capture most available lift; fitted GMM/HMM models must justify their complexity net of churn.
- Stability protects capital: Hysteresis, 2-bar confirmation, and 4-bar minimum dwell prevent destructive whipsaw.
- Spread is a hard regime: Spread %ile ≥ 0.85 acts as an absolute kill-switch regardless of volatility readings.
- Enforce causal hygiene: HTF context gates execution but never extends same-session flatten mandates.
References
- U.S. Securities and Exchange Commission — Updated Investor Bulletin: Exchange-Traded Funds — https://www.sec.gov/investor/alerts/etfs.pdf — mechanics. Intraday ETP share trading at market price, NAV, premiums/discounts, creation/redemption via APs.
- SSGA — SPDR Gold Shares (GLD) — prospectus and product page — https://www.ssga.com/us/en/intermediary/etfs/spdr-gold-shares-gld — mechanics. Physical gold trust: allocated bullion, daily holdings, wrapper behavior.
- Pomegra Learn Library — Commodity ETF & ETN Liquidity — https://pomegra.io/learn/library/track-d-other-assets/commodities/chapter-09-commodity-etfs-and-etns/commodity-etf-liquidity — mechanics. Bid-ask spread, creation/redemption, and underlying-market liquidity layers.
- ContentWave — SPDR Gold Shares (GLD) Liquidity, Fees & Taxes (2026 review) — https://contentwave.net/article/review-spdr-gold-shares-gld-liquidity-costs-and-risks-2026 — mechanics. GLD narrow spreads during RTH on high volume, implicit vs explicit costs, custody and operational risk.
- Coinbase — US Perpetual-Style Futures Funding Rate Mechanism — https://help.coinbase.com/en/derivatives/perpetual-style-futures/funding-rate — mechanics. Perpetual funding exchanged between longs/shorts to anchor the perp to spot.
- Coinbase Learn — Understanding Funding Rates in Perpetual Futures — https://www.coinbase.com/learn/perpetual-futures/understanding-funding-rates-in-perpetual-futures — mechanics. Funding as trading-cost layer.
- TOS Indicators — Hourly Volatility Models for Stocks - Demo on ETFs (SPY, QQQ, GLD, TLT) — https://tosindicators.com/research/etf-volatility-models — mechanics / transfer support. U-shaped intraday volatility by hour, asset-class-specific curves.
- Investopedia — Average True Range (ATR) definition — https://www.investopedia.com/terms/a/atr.asp — mechanics. ATR as volatility yardstick for spread-to-ATR and volatility-adjusted stops.
- Tenth Meridian Research — ATR Percentile Ranking for Volatility Regime Classification — https://research.tenthmeridian.co/s02-volatility-regime-filter — mechanics / implementation reference. Three-bucket ATR percentile router, vol-adjusted sizing.
- VolatilityBox — Volatility Regime Detection: From Simple Rules to Machine Learning — https://volatilitybox.com/research/volatility-regime-detection/ — mechanics / synthesis. Four-category taxonomy and speed-versus-accuracy tradeoff.
- QuantInsti — Step-by-Step Python Guide for Regime-Specific Trading Using HMM and Walk-Forward (Bitcoin) — https://blog.quantinsti.com/regime-adaptive-trading-python/ — transfer evidence / template. Hidden-Markov detection, specialist models per regime on BTC.
- LiveVolatile — Bitcoin’s Volatility Regime Shift in Q1 2026 — https://www.livevolatile.com/blog/btc-volatility-regime-shift-q1-2026 — mechanics / transfer support. Post-ETF BTC market structure.
- ResearchPips — ATR & Volatility Regimes — https://researchpips.com/university/atr-volatility/ — mechanics. ATR as realized-movement gauge versus direction.
- ohlcv.io — The Bar Resolution Problem (Backtesting Pitfalls 04) — https://ohlcv.io/posts/backtesting-pitfalls/04-bar-resolution/ — mechanics. OHLC information loss, intrabar path ambiguity, adverse-first rule.
- Saral Money — Backtest Execution Timing: Fill at the Next Bar’s Open — https://saral.money/blog/next-bar-open-execution-timing/ — mechanics. Same-close versus next-open execution bias.
- GitHub — marketregime_hmm (blackswan-quants) — https://github.com/blackswan-quants/marketregime_hmm — tooling reference. HMM market-regime detector implementation.
- GitHub — market-regime-detection (taylorjmellon) — https://github.com/taylorjmellon/market-regime-detection — tooling reference. Discrete regime detection implementation.
- GitHub — RegimeSense (ishwari05) — https://github.com/ishwari05/RegimeSense-Market-Regime-Detection-Strategy-Engine — tooling reference. Regime detection and strategy engine.
- Springer — Impact of US Bitcoin ETF Introduction on BTC and ETH Intraday Regime Dynamics — https://link.springer.com/chapter/10.1007/978-3-031-73122-8_3 — transfer evidence. Intraday regime dynamics around ETF flows on BTC/ETH.
- Next in sequence: Module 16 — Commodity ETP & Crypto Intraday Playbooks
- Strategy compendium: Module 15 Strategies