Does a volatility-band breakout on BTCUSDT have an edge — and can it be recovered with confluence filters or a market-regime gate? A negative result, reported in full.
Question. Does a volatility/band breakout on BTCUSDT have an edge, and can that edge be recovered with (a) confluence filters or (b) a market-regime gate?
Answer. No. The base breakout trigger has no durable edge, and neither confluence filters nor a regime gate produced a robust, statistically significant improvement. The one apparently strong result — breakouts in an “up-trend, high-volatility” regime looked much better in 2026 — failed to replicate on 2025, where the best regime was in fact a down regime. Regime gating reduced losses substantially, but that was almost entirely a reduction in trade count and fees, not a discovered edge. Even gated, the strategy lost money in both 2025 and 2026.
The hypothesis that a breakout trigger plus confluence or a regime gate yields a durable BTCUSDT edge is not supported by 2025–2026 data. Timeframe is the only robust lever.
up_highvol regime, 202635.0% vs 17.8%up_highvol regime, 202519.8% vs 18.8%The lab’s reference strategy is a volatility/band breakout: price crosses above a volatility band on expanding volume. The working hypothesis was:
H1. Raw breakouts are noise, but a confluence of conditions — volume, order-flow, trend, volatility regime, derivatives — selects the subset that trends. Working hypothesis
A second hypothesis followed from the data:
H2. The edge is regime-dependent — breakouts work in trending/expanding regimes and fail in range/contraction regimes. A regime classifier should therefore separate signal from noise. Follow-on hypothesis
Both were tested on a design period and a separate validation period, with a pre-registered outcome definition, and H2 was additionally stress-tested by replicating 2026 on 2025.
All data from Binance Public Data bulk archives (data.binance.vision), via a resumable, checksum-verified downloader (quant_lab.data.sources.binance).
| dataset | market | 2025 rows | 2026 Jan–Aug rows | notes |
|---|---|---|---|---|
| bars 1m | futures | 525,600 | 349,920 | OHLCV + taker-buy volume |
| bars 1m | spot | 525,600 | 349,920 | for spot–perp basis |
| funding | futures | 1,095 | 729 | 8-hourly |
| open interest | futures | 105,117 | 69,984 | 5-minute, derived from daily metrics |
Ranges: 2025-01-01 → 2025-12-31 and 2026-01-01 → 2026-08-31. Also present: BTCUSDT/ETHUSDT futures 2024-01..03, and 2024-01/02 funding plus five days of OI.
Canonical schemas (quant_lab.data.schemas), Parquet partitioned by exchange/market_type/dataset/symbol/timeframe/year/month.parquet, queried via DuckDB. Validation (quant_lab.data.validate) checks missing, duplicate and non-monotonic timestamps, invalid prices and quantities, gaps, outliers and timezone consistency.
Every dataset used here passed validation cleanly. No data was fabricated or silently repaired.
open_interest is derived from the daily metrics archive (5-minute grid).| strategy | definition | params (defaults) |
|---|---|---|
bollinger_breakout | close crosses above upper Bollinger band; exit close < mid | window 20, 2σ |
volatility_breakout | close > EMA and |Δclose| > k·ATR and volume > m·mean(volume) | EMA 100, ATR 14, k 1.5, m 1.5 |
squeeze_release | Bollinger cross only after a compressed-bandwidth squeeze, with bandwidth expanding | window 20, squeeze z ≤ −0.5, lookback 96 |
range_breakout_hold | close above prior N-bar high and holds for M consecutive closes | lookback 60, hold 2, exit 30 |
All strategies are long-only by default, use trailing (causal) windows only, and are validated for causality by tests that assert signal and feature values do not change when future bars are appended.
Recorded with every run; defaults are non-zero.
| assumption | value |
|---|---|
| initial capital | 100,000 |
| fees (per side) | 0.0004 |
| slippage (per side) | 0.0002 |
| position size | 100% of equity |
| leverage | 1× |
| execution delay | 1 bar |
| timing | signal at close t → fill at open t+1 |
For every signal, the forward path is measured from the close of the signal bar over a 7-day horizon:
Reversion and excursion are also reported as a cumulative reversion curve and a bucketed excursion table (disjoint reversion-speed buckets).
Each filter is a single explicit boolean condition evaluated at the signal bar (no model): volume ≥ 1.5×mean, taker_buy/volume ≥ 0.55, 20-bar CVD > 0, close > EMA200, strong close (close in top 30% of bar range), bandwidth z ≤ −0.5, spot–perp basis > 0, funding ≤ 0, OI 1h change > 0. Combinations are greedy ANDs of the best standalone filters on the design period.
A causal classifier (quant_lab.analytics.regimes), computed on a 1h regime timeframe from trailing statistics only:
up/down/range from a volatility-normalised trailing move: the 24h log return divided by realized_vol × √24, threshold ±1.0.highvol/lowvol from realized vol vs its trailing median.{trend}_{vol} (e.g. up_highvol), shifted one regime bar so a signal only ever sees regimes that had already closed.2,492 Bollinger breakout signals, 1m, close-basis reversion, 7-day horizon. Median time to reversion was 1 bar (1 min), mean 51.2 min, p75 4 min; 21 signals (0.8%) never reverted within 7 days.
Figure 1. Cumulative fraction of 1m breakout signals that have reverted by each horizon. More than half revert within a single minute; 99.2% within seven days.
| bucket | n | mean | median | min | max | mean % |
|---|---|---|---|---|---|---|
| ≤1m | 1,390 | 10.4 | 2.8 | 0.0 | 384.9 | 0.015% |
| 1m–5m | 532 | 43.9 | 34.1 | 0.1 | 460.9 | 0.066% |
| 5m–15m | 216 | 98.6 | 70.3 | 2.9 | 1,409.5 | 0.143% |
| 15m–1h | 178 | 201.6 | 161.6 | 9.2 | 979.3 | 0.300% |
| 1h–4h | 88 | 384.7 | 311.9 | 52.6 | 2,171.4 | 0.564% |
| 4h–1d | 42 | 702.1 | 567.5 | 129.3 | 2,404.7 | 1.048% |
| 1d–7d | 25 | 1,997.6 | 1,831.5 | 607.7 | 4,299.7 | 2.955% |
| not reverted | 21 | 9,748.8 | 9,343.2 | 1,952.0 | 16,947.6 | 14.871% |
Figure 2. Mean excursion (percent, log scale) by reversion-speed bucket. Excursion and reversion time are mechanically linked — only signals that keep running stay out of reversion. The raw 1m trigger is essentially microstructure noise.
Design = July, validate = August, 0.25% label. Baseline P(good): 6.28% design, 6.16% validate.
Figure 3. Out-of-sample lift of each 1m confluence filter. No filter was significant; volume flipped sign between periods.
Jul/Aug split, 0.5% label. The base rate rises roughly 4× (15m) to 6× (1h) versus 1m — but signal counts collapse and range_breakout_hold is too restrictive to evaluate. No filter was significant at any timeframe.
Figure 4. P(good) by trigger and timeframe, design (open) vs validate (filled). Timeframe is the only lever that moves the base rate consistently.
| trigger | tf | baseline design | baseline validate | best filter (design / validate lift) |
|---|---|---|---|---|
| bollinger | 1m | 5.0% (n=8,304) | 5.0% (n=1,234) | none > 1.11, n.s. |
| bollinger | 15m | 17.7% (n=566) | 19.5% (n=82) | volume+EMA200: 1.19 / 1.23 (z=0.59) |
| bollinger | 1h | 35.5% (n=138) | 32.1% (n=28) | none (all ≈1.0 or below) |
| squeeze_release | 15m | 18.1% (n=507) | 21.9% (n=64) | volume+EMA200: 1.17 validate |
| squeeze_release | 1h | 36.2% (n=116) | 29.4% (n=17) | none; OI negative |
At 15m, two filters were positive in both windows: volume ≥ 1.5× (1.13/1.14) and close > EMA200 (1.11/1.15); their combination gave 1.19/1.23 — the first directionally consistent signal, though not significant. Adding oi_change_1h produced a large validation lift (2.05, n=20, z=1.94) but its design lift was only ~1.06 — treated as regime-specific, not real. At 1h no filter helped; breakouts there are already good about a third of the time.
Baseline: 17.7% design (Jan–Jul), 19.5% validate (Aug). up_highvol was the best regime in both windows, and down-regimes were worst. At this point H2 looked supported — pending the 2025 replication.
| regime | design n | design P(good) | validate n | validate P(good) |
|---|---|---|---|---|
| up_highvol | 46 | 23.9% | 16 | 62.5% |
| up_lowvol | 55 | 16.4% | 3 | 0.0% |
| range_highvol | 168 | 18.5% | 22 | 13.6% |
| range_lowvol | 212 | 17.5% | 14 | 7.1% |
| down_lowvol | 36 | 11.1% | — | — |
| down_highvol | 24 | 12.5% | 4 | 0.0% |
Baseline: 18.8% (2025), 17.8% (2026 Jan–Aug).
Figure 5. Regime performance across the two years. H2 is refuted. In 2025 up_highvol (19.8%) is indistinguishable from the 18.8% baseline and is not the best regime — down_highvol (24.6%) is. The strong 2026 up_highvol effect did not replicate.
| regime | 2025 n | 2025 P(good) | 2026 n | 2026 P(good) |
|---|---|---|---|---|
| up_highvol | 81 | 19.8% | 60 | 35.0% |
| up_lowvol | 80 | 15.0% | 62 | 14.5% |
| range_highvol | 282 | 21.6% | 186 | 17.7% |
| range_lowvol | 400 | 17.2% | 250 | 16.4% |
| down_highvol | 57 | 24.6% | 27 | 11.1% |
| down_lowvol | 41 | 12.2% | 39 | 10.3% |
Gate = up_highvol + up_lowvol.
Figure 6. Total return, gated vs ungated. Gating cut losses sharply, but trade count fell 5–6× and fees fell correspondingly — the improvement is fee/turnover avoidance, not recovered edge. Both gated variants still lose money with profit factor below 1.
| period | variant | trades | total return | max DD | Sharpe | win | PF | fees |
|---|---|---|---|---|---|---|---|---|
| 2025 | ungated | 619 | −78.5% | −79.3% | −4.29 | 27.8% | 0.68 | 34,189 |
| 2025 | gated | 107 | −25.4% | −26.4% | −3.11 | 23.4% | 0.46 | 7,363 |
| 2026 | ungated | 417 | −58.0% | −62.6% | −5.28 | 25.2% | 0.56 | 23,008 |
| 2026 | gated | 79 | −7.0% | −19.2% | −0.87 | 29.1% | 0.86 | 6,116 |
up_highvol result is a single-sample artifact; 2025 contradicts it. With ~60–80 signals per regime-year, confidence intervals are wide enough to manufacture such effects by chance.good label is path-based, not PnL-based; it ignores the exit rule, funding and slippage on exits.The hypothesis that a breakout trigger plus confluence or a regime gate yields a durable BTCUSDT edge is not supported by 2025–2026 data.
volume + EMA200 at 15m) was directionally consistent but weak (1.23× out-of-sample, z≈0.6).up_highvol was special only in 2026; in 2025 a down regime was best.Stop refining this trigger family. If research continues, change the source of edge rather than the filters — e.g. funding/basis carry, cross-sectional relative strength across a universe of perps, or a mean-reversion trigger with a proper risk model — and design the study around a PnL-based objective with enough sample to detect the effect size of interest.
quantlab data build binance --market futures --symbol BTCUSDT --interval 1m \
--start 2025-01-01 --end 2026-01-01
quantlab data build binance --market futures --symbol BTCUSDT --dataset funding \
--start 2025-01-01 --end 2026-01-01
quantlab data build binance --market futures --symbol BTCUSDT --dataset open_interest \
--start 2025-01-01 --end 2026-01-01
quantlab data build binance --market spot --symbol BTCUSDT --interval 1m \
--start 2025-01-01 --end 2026-01-01
# (repeat for 2026-01-01 → 2026-09-01)
quantlab data validate --symbol BTCUSDT --dataset bars --interval 1m
quantlab analyze excursions --strategy bollinger_breakout --symbol BTCUSDT \
--timeframe 1m --start 2026-07-01 --end 2026-09-01 \
--reversion-only --horizon-days 7 --reversion-basis close \
--out experiments/reports/bb_reversion_only
quantlab analyze confluence --strategy bollinger_breakout --symbol BTCUSDT \
--timeframe 15m --source-timeframe 1m \
--start 2025-01-01 --end 2026-01-01 \
--validate-start 2026-01-01 --validate-end 2026-09-01 \
--excursion-pct 0.005 --min-reversion-hours 1 --horizon-days 7 \
--by-regime --regime-timeframe 1h \
--out experiments/reports/regime_2025_2026
quantlab analyze regime-backtest --strategy bollinger_breakout --symbol BTCUSDT \
--timeframe 15m --start 2025-01-01 --end 2026-01-01 \
--allow-regimes up_highvol,up_lowvol \
--out experiments/reports/regime_bt_2025
| path | contents |
|---|---|
experiments/reports/bb_reversion_only/ | reversion CDF, excursion-by-bucket, per-signal events |
experiments/reports/confluence_bb/ | 1m confluence tables (design/validate) |
experiments/reports/cf2_*/ | full-2026 confluence by trigger/timeframe |
experiments/reports/regime_bb_15m/ | 2026 regime tables |
experiments/reports/regime_2025_2026/ | 2025 vs 2026 regime tables |
experiments/reports/regime_bt_2025/, regime_bt_2026/ | gated vs ungated metrics |
| module | responsibility |
|---|---|
data/sources/binance.py, data/build.py | download + normalize |
data/resample.py | canonical OHLCV resampling |
data/validate.py | data-quality reporting |
features/core.py, features/market.py | indicators and flow features |
strategies/* | breakout triggers |
backtest/engine.py, backtest/metrics.py | VectorBT engine, metrics |
analytics/excursions.py | reversion / excursion analysis |
analytics/confluence.py | filter evaluation, design/validate |
analytics/regimes.py | regime classifier + gate |
analytics/gating.py | regime-gated backtest |
uv run pytest — 97 tests, including deterministic end-to-end, lookahead/causality, resampling, regime and gating tests.