Scalping — VWAP-Deviation Fade Updated Analysis

A reproduction of this strategy's own backtest code (strategy_engine.py, vwap_cost_validate.py) run separately against Bybit's top-100 perps by 24h turnover and the original 166-symbol screened list, plus a from-scratch test of the actual VWAP Fade Model Pine Script indicator this research line was meant to validate.

1 · The bug, and filling in the missing cost step

finalize_trade()'s lookahead loop breaks — rather than falling through to the for…else time-stop branch — when a trade opens near the live edge of the dataset and runs out of forward bars before the 60‑minute window elapses. That leaves exit_px = None, which crashes on the very next line. Because the exception unwinds the whole symbol's trade list, it silently discarded every trade for that symbol, not just the one broken trade near the edge. First run on freshly-fetched 2026 data: only 31/100 and 101/166 symbols survived to produce a result. Patched to flatten at the last available bar (matching the code's own stated intent) — 60/100 and 135/166 symbols now complete; the rest are excluded by the engine's real 0.5%-median-ATR liquidity gate, not by crashes.

Separately: strategy_engine.py's trade backtest returns gross R-multiples only — no fee/slippage deduction anywhere in that file. vwap_cost_validate.py's event-level scan does apply costs, but that's a different, coarser signal-quality check, not the trade backtest. Since the front page's net figures clearly come from some cost step, just not one present in the shared code, the tables below apply the same cost basis vwap_cost_validate.py already uses elsewhere in the repo — 0.055% taker + 0.02% slippage per side, round trip — directly to every simulated trade.

2 · Does the edge generalize, or is it specific to the screened list?

The Overview page's universe (78 symbols passing an ATR filter) is a subset of the hand-curated 166-symbol screened list. Re-running the same unmodified E8 code on an entirely different, unscreened universe — Bybit's top 100 perps by 24h turnover, no pre-selection — answers whether the edge is a property of the signal or an artifact of which symbols made the list.

MetricTop 100 (unscreened)Screened (166)
Symbols used60 / 100135 / 166
Trades221,804624,676
Win rate55.0%53.6%
Event-scan gross 1h fade+0.363%+0.534%
Event-scan net (taker std. cost)+0.223%+0.394%
Trade-backtest gross avg R / PF+0.360 / 1.93+0.358 / 1.89
Trade-backtest net avg R / PF+0.123 / 1.25+0.150 / 1.30
Top 100 · net profit factor
1.25
Screened 166 · net profit factor
1.30

It generalizes. Win rate within 1.4 points, net PF within 0.05, net avg R within 0.03R. The screened list shows a larger gross pre-cost drift (median entry deviation 3.47 ATR vs 3.35, more volatile/less liquid names), but that gap mostly closes once realistic costs are applied. This is not a screened-universe artifact — but §5 below found that the edge being compared here was itself inflated by a VWAP lookahead bug, so read this as "the bug's effect generalizes," not "the strategy generalizes."

3 · Does the actual VWAP Fade Model indicator hold up?

E8 is a research proxy: fade the instant |close − VWAP| > 2×ATR, no confirmation. The indicator this research line is meant to validate is stricter — it only enters after a full confirmed-reversal sequence (trigger → armed → a reversal candle that opens beyond the band, closes back inside it, is the opposite color, and clears a volume-confirmation filter), and its stated target is price returning to the live VWAP, not a fixed R-multiple. Backtesting that indicator exactly as specified, across 18 combinations of volume filter and stop type, on the top-100 universe:

Configs tested
18
volume filter × stop type
Profitable configs
0 / 18
Best profit factor
0.59
no volume filter, low/high stop
Best avg R
−0.21R
z-score 2.0σ, low/high stop

Every configuration lost money. Stop-loss exits average about −1.3R; take-profit (VWAP-touch) exits average only about +0.3R — the stop sits a full ATR beyond the reversal candle's own extreme, and reversal candles are by definition wide, while the VWAP target is often much closer. Tighter volume confirmation roughly halves the loss but never flips the sign.

4 · Isolating why: entry timing, not exit design

Given E8 (loose trigger, fixed-R exit) is profitable and the indicator (confirmed trigger, VWAP-chase exit) is not, the natural question is which piece is doing the work. Swapping just the exit scheme — running the indicator's real confirmed-reversal entries through E8's own fixed-R exit (1×ATR stop from entry, 2R target, 60‑minute time stop) instead of the indicator's VWAP-chase target — isolates it. All three rows below use the same top-100 universe and the same cost basis.

EntryExit schemeTradesGross avg RGross PFNet avg RNet PF
Indicator's confirmed reversal Indicator's own (VWAP-chase target, candle-extreme stop) 151,555 −0.012 0.98 −0.373 0.58
Indicator's confirmed reversal E8's fixed-R (2R target, 1×ATR stop, 60m time stop) 195,075 +0.013 1.03 −0.758 0.56
E8's raw deviation trigger (no confirmation) E8's fixed-R 221,804 +0.360 1.93 +0.123 1.25
Confirmed entry · own exit
0.58
Confirmed entry · E8's exit
0.56
Raw trigger · E8's exit
1.25

Net profit factor, all three on the same top-100 universe. Grey tick = breakeven (PF 1.0).

Reading the result: swapping the exit scheme changes almost nothing — the confirmed-entry gross edge is ≈0 either way (PF 0.98 vs 1.03, a coin flip before costs). E8's real edge comes from firing the instant price first closes beyond 2×ATR. The indicator's confirmation sequence — waiting for an opposite-colored candle to open beyond the band, close back inside it, and clear a volume filter — means it enters only after most of the immediate reversion has already happened. What's left to trade at that point is close to noise, and at roughly 2,000 trades/symbol over 8 months (a trade every ~3 hours per symbol, all day), transaction costs alone turn that coin flip into a clear net loss.

01
It's not the volume filter. None / 1.8× MA / 2.0σ z-score entries all show the same ≈0 gross edge (+0.013 to +0.025 avg R) despite a 16× swing in trade count — tightening the filter changes how many trades fire, not their quality.
02
It's not the exit scheme. VWAP-chase vs. fixed-2R target, candle-extreme vs. entry-anchored stop — both pair with the confirmed entry to produce ≈breakeven gross results.
03
It looked like entry timing — removing the confirmation wait moved gross PF from ~1.0 to ~1.9. Digging into why that raw trigger was worth so much more led to §5 below, which supersedes this reading.

5 · The real root cause: a VWAP lookahead bug

Testing whether E8 fires on every qualifying bar or something more selective (see the questions this section answers) meant reading session_vwap() closely — and it isn't a running, cumulative VWAP at all. It computes a single value per day from bars[lo:hi], the entire day's bars, and then every bar that day — including one at 00:05 UTC — is compared against a VWAP that already reflects volume from 23:55 UTC that hasn't happened yet. vwap_cost_validate.py's event scan has the identical pattern. This is separate from, and larger than, the missing-cost issue in §1: it means the reference level E8 fades toward is not something a live trader could ever observe.

Re-running E8's exact entry rule and fixed-R exit scheme with a proper running VWAP (the same calculation the actual Pine indicator uses, and what this reproduction's engine already computes correctly) — same top-100 universe, same costs:

VariantTradesGross avg RGross PFNet avg RNet PF
E8 as coded (lookahead VWAP)221,804+0.3601.93+0.1231.25
E8, corrected VWAP, any bar after flat680,967+0.0391.04−0.7930.57
E8, corrected VWAP, fresh-cross only (first bar of each new excursion, never re-chases)198,508+0.0351.05−0.7660.56

With VWAP computed correctly, E8's gross edge collapses to essentially zero — the same coin-flip territory as the confirmed-reversal indicator in §3-4. Requiring a fresh excursion instead of allowing E8 to re-chase a sustained deviation doesn't help. Neither does restricting to a specific session:

Session (UTC)TradesNet avg RNet PF
Asia 00–08179,728−0.8420.56
Europe 08–14190,244−0.8430.53
US 14–21211,363−0.6920.61
Late 21–2499,632−0.8220.55

Every session is a net loser; US hours are the least bad (PF 0.61) but still lose money. This means §2's generalization result and §4's isolation result are both still procedurally correct — the edge really does generalize across universes, and swapping the exit scheme really doesn't matter — but the edge they're comparing was itself an artifact of comparing prices to a VWAP computed with hindsight. Once that's fixed, there isn't a real edge left for either signal to generalize or for an exit scheme to preserve.

6 · What this means going forward

The apparent mean-reversion drift E8 was built to capture doesn't survive computing its own reference level honestly. Neither loosening the entry (trading every qualifying bar vs. only fresh excursions) nor restricting to a specific trading session recovers a net edge once VWAP is a true running value. Before iterating further on stop/target levels or confirmation logic, the two threads worth pulling are: whether a different anchor entirely (e.g. a shorter rolling VWAP, or a structural level like a value-area edge) reacts fast enough to be tradeable in real time, and whether any of E8's sibling strategies (E3–E5) share the same VWAP-computation pattern and need the same fix.

7 · Checked at 1-minute execution precision too

Since E8's lookahead bug is a full-day VWAP information leak rather than an entry-timing issue, there was no strong reason to expect bar size to matter here — and it doesn't. Re-running the as-coded (bug-intact) reproduction with entries and the SL/TP walk-forward at 1-minute precision instead of 5-minute: gross PF 1.93→1.84, net PF 1.25→1.27, essentially unchanged. The corrected, lookahead-free version (fade toward a true running VWAP) is similarly stable: net PF 0.575→0.577. Full detail, including two more real bugs this rebuild caught (ATR and the trend/range efficiency ratio both need to stay on the 5-minute signal definition even when execution runs at 1-minute precision — see why), is on the ATR-VWAP variant study page, §6.

All figures above come from a from-scratch reproduction: Bybit V5 public kline API, 5‑minute bars, Jan 1 – Aug 25 2026, top 100 perps by 24h USDT turnover plus the original 166-symbol screened list (159 still listed on Bybit). Cost basis: 0.055% taker + 0.02% slippage per side, round-trip, applied uniformly across every result on this page. "Gross" = raw price R-multiple, no costs. "Net" = after the cost basis above. Position sizing throughout is a 1R-per-trade convention (not compounded equity) unless otherwise noted. Different window (2026 YTD vs. the Overview page's rolling 60-day window) and universe from the front page, so absolute trade counts aren't directly comparable — the win-rate / R / PF ratios are the fair comparison.