Scalping — IVA Confirmed Breakout Updated Analysis

A reproduction of this strategy's own backtest code (strategy_engine.py), run against Bybit's top-100 perps by 24h turnover and the original 166-symbol screened list, after finding the take-profit target uses information from later in the day than the entry.

1 · The bug

E3 enters on the bar after a confirmed breakout — the entry side of the trade is honest. The take-profit target, however, comes from the day's volume-profile node structure:

tp = nearest_node(day["profile"]["nodes"], entry_px, direction) # profile built from the WHOLE day's bars

day["profile"]["nodes"] is built once per day in build_store.py's profile_with_nodes(db), where db is every bar in that calendar day — including bars that happen hours after a morning entry. A node that only forms from afternoon or evening volume can still end up as a signal's take-profit target if the signal fired that morning. The 30-minute IVA (vah/val) used for the stop is correctly built from only the first 30 minutes and is unaffected — this bug is specific to the profit target.

tp = nearest_node(causal_profile(day, up_to=signal_time)["nodes"], entry_px, direction) # profile built only from bars available at signal time

2 · A concrete example

AGLDUSDT, long entered 2026-01-05 14:45 UTC. Entry 0.2902, stop 0.289586.

VersionTP levelOutcomeR
As-coded (full-day profile)0.292655TP hit+4.00R
Causal-fixed (profile up to signal time only)0.293405 (farther out — no later-forming node existed yet)Price reversed, stop hit instead−1.00R

Same signal, same entry, opposite outcome — purely a function of which day's worth of volume the profit target was allowed to see.

3 · Corrected results, both universes

Universe / versionTradesSymbolsGross PFNet PF
Top 100, as-coded (reproduces the Overview page)20,56310/1001.1310.763
Top 100, causal-fixed profile51,30924/1001.0480.760
Screened 166, as-coded87,54642/1661.1800.890
Screened 166, causal-fixed profile144,99566/1661.0520.829

The as-coded screened-universe gross PF (1.180) matches the originally published 1.17 within rounding, confirming this reproduction. Symbol counts jump sharply between as-coded and fixed — not because more symbols became tradable, but because of a separate, shared bug (§3.1).

3.1 · The shared finalize_trade() crash bug hits E3 harder than E4/E8

The crash-and-silently-drop-a-whole-symbol's-trades bug already documented on the E4 and E8 updated-analysis pages affects E3 far more severely, simply because E3 fires roughly 2,000+ trades per symbol over the test window — a much higher signal rate than E4 or E8 — so it hits the unhandled end-of-data path constantly. As-coded, only 10/100 (top-100) and 42/166 (screened) symbols survive to produce a result; patching just the crash (independent of the profile-target fix) recovers 24/100 and 66/166. The trade-count and symbol-count jump in the table above between “as-coded” and “fixed” rows reflects both the crash patch and the profile fix applied together.

4 · A second, separate bug: day-level ATR sizing

Independently of the profile-target bug, E3's stop distance is sized off day["atr15"] — a single scalar per calendar day, computed in build_store.py as the last 15-minute bar's ATR reading for that day, not a value available at signal time. A morning signal is sized using an ATR that reflects that evening's volatility too. Spot-checked on ETHUSDT, 2026-01-01, 09:00 UTC: stored day-level ATR = 6.283 vs. a causal as-of-09:00 value of 3.793 — a 65% overstatement. Across 24,141 morning observations checked project-wide, the day-level/causal ratio ranges 0.48×–1.99× (5th–95th percentile) — a broad, material distortion, not an edge case. Re-testing E3 with a proper causal ATR instead of the day-level value, on top of the profile-target fix already applied:

UniverseNet PF, day-level ATRNet PF, causal ATR
Top 1000.7600.741
Screened 1660.8290.808

The ATR fix moves net PF by only a few hundredths in either direction — a real bug, worth fixing, but not one that changes E3's verdict. This same day-level-ATR bug also affects E4 and E5's reproductions on this site (see their own updated-analysis pages); it does not affect the separate ATR-VWAP variant study, which computes its own proper causal rolling ATR independently and never reads this field.

5 · What this means

E3's core idea — a confirmed breakout beyond the opening value area continuing in that direction — is honestly weaker than published even before costs: fixing the lookahead in its own profit target roughly halves the gross edge (1.13–1.18 → 1.05), which is close enough to breakeven that costs alone were always going to decide the outcome. The net verdict is unchanged either way — E3 remains a net loser, now on a more honestly-computed number rather than a partially-inflated one.

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. Cost basis: 0.055% taker + 0.02% slippage per side, round-trip. “Gross” = raw price R-multiple, no costs. “Net” = after the cost basis above. The shared finalize_trade() crash bug (documented on the E4/E8 pages) was patched for every run in this table. See the OI+CVD+Volume confluence page, §11–14, for the broader hardening pass this bug was found during.