A bar-by-bar futures backtest engine that reproduces TradingView trade-for-trade, plus a visual replay lab for inspecting what it did.
The engine exists to answer one question honestly: is this an edge, or is it noise? Most backtest code answers that question wrong in ways that are invisible until real money is on the line. The design here is organized around making those failure modes structurally impossible, or at least loudly detectable.
Rust + egui. ~9,500 lines of engine, 77 tests, no async runtime.
The replay lab paused mid-replay on synthetic bars: price with a stacked-EMA
trend ribbon and ATR level rails, a volume pane with relative-volume state, and
a phase oscillator below. The badges across the top (PHASE DISTRIB,
TIDE MIXED, RANGE —, PRESS BUY 0.7x) are semantic states emitted by the
indicator engine, not drawing output — the same values a strategy sees on
that bar. Transport controls scrub the replay bar-by-bar.
BarSource ──▶ BarSeries ──▶ replay engine ──▶ Broker (TV fill semantics) ──▶ Trade[]
│ │
▼ ▼
Indicator engine fixture comparator
(semantic states, not (trade-for-trade vs
drawing commands) TradingView export)
│
▼
egui replay lab
The engine owns the Vec<Bar>. On step i a strategy receives
Ctx { bars: &bars[..=i], .. } — a slice that ends at the current bar.
There is no API that yields a future bar. Indicators and higher-timeframe
aggregates are incremental state machines fed one bar at a time; they cannot be
precomputed over the full series, because nothing downstream of the engine ever
holds the full series.
This is a type-level guarantee rather than a code-review convention, which matters because lookahead bias is the single most common way a backtest lies.
Bar.time is always UTC unix seconds at bar open. Vendor CSVs and
TradingView exports are US-Eastern wall clock and convert at the boundary.
Session gates evaluate in ET. The futures daily session (18:00 ET → next 17:00
ET, labeled by the ending civil date) has exactly one implementation.
Timezone bugs in backtests are silent — they shift entries by an hour twice a year and quietly change your results. So this layer has its own tests for DST transitions, session-date rollover, and wall-clock round-tripping.
Orders emitted while processing bar i activate on bar i+1. Market orders
fill at next bar open ± slippage. Stops check open before low. Limits take
no slippage. For a stop and limit on the same bar, TV's intrabar path heuristic
is reproduced (high − open < open − low ⇒ assume open→high→low→close) and the
first level touched fills while the other cancels.
None of this is documented by TradingView. It was established empirically by fixture archaeology and is written down in docs/BACKTEST.md, which is the most substantive document in this repo.
A strategy is not "ported" when it runs. It is ported when, replayed bar-by-bar over the fixture period, it reproduces a TradingView List-of-Trades export trade-for-trade — entry and exit bar times exact, prices within a tick, signal labels matching.
replay-lab --backtest orb-tue --data data/cache/MNQ_300.csv --fixture export.csv
The comparator reports matched / missing / extra / price-off with per-trade diffs. Across the private research run this harness was built for: 11 fixtures, 766/798 trades exact (96%), 4 formal 100% passes — with every residual trade individually characterized (vendor bar differences, roll weeks, half-tick prints) rather than dismissed as noise.
Discoveries that only surfaced because verification was trade-for-trade:
- A contract roll rule TradingView doesn't publish.
MNQ1!does not switch at expiry. Price-bracketing across six quarterly rolls pinned the switch to the Monday 18:00 ET session reopen of expiry week. This matters well beyond roll week: a wrong roll date injects a false true-range spike that feeds daily ATR(14) for ~14 sessions afterward. - An undocumented pivot tie-break.
ta.pivothigh/ta.pivotlowlet a pivot tie earlier values but require it to strictly beat later ones. Established by sweeping all four tie rules against a 201-trade fixture (196 matches vs 187 / 195 / 195). - Ratio-adjusted vendor data is ~8% off at 2024-02 (file 19124.72 vs TV-implied 17636.75) — fine for walk-forward, disqualifying for price verification. The two are kept strictly separate.
Full board, with the book redacted: docs/VERDICTS.md.
Strategies were run against a bar written down before any results were seen: PF ≥ 1.15 at live costs, net positive excluding the top-3 winners, 2022 positive or a coherent regime story, and a flat walk-forward plateau.
The headline finding is a negative one, and it's why pdl-sweep ships here as
a bundled example. Its TradingView backtest looked excellent. The port verifies
26/27 against the fixture, so the disagreement is real, not a porting bug.
The .pine requests 30-min EMAs with lookahead_on and no [1] offset — the
trend gate reads the forming bucket's final close up to 25 minutes early.
Measured: +$1,148 with the lookahead vs +$621 without. 85% of the "edge" was manufactured by reading the future.
And the honest remainder still fails the bar: ex-top-3 P&L is −$3.50 over seven years. The entire remaining edge is three trades.
Both variants ship — pdl-sweep (live-causal) and pdl-sweep-tv (lookahead
re-enabled to reproduce TV). The gap between them is the deliverable.
A second result, from --intraday-dll: a widely-circulated prop-firm analysis
models a daily loss limit as an end-of-day floor, max(day × M, −cap), which
assumes full upside capture. Real firms enforce the limit intraday — the
moment combined equity touches −cap you are flat, so a day that would have
closed green but dipped intraday is forfeited. Reconstructing the true
intraday equity path from per-bar mark-to-market shows upside capture
collapsing 71% → 43% as the size multiplier goes 1 → 5. The naive model
inverts the conclusion.
# Regime table by year + rolling walk-forward (IS-optimized vs default OOS)
replay-lab --walkforward orb-tue --data <1min.csv> --daily <1day.csv> --mode wf
# Day-aligned portfolio: correlation, worst-day attribution, prop-firm overlay
replay-lab --portfolio --data <1min.csv> --daily <1day.csv> --book orb-tue,pdl-sweep
# Intraday daily-loss-limit sweep: cap policy × size multiplier
replay-lab --intraday-dll --data <1min.csv> --daily <1day.csv> --cap 500Walk-forward exists to catch curve-fitting, and it earns its keep: across nearly every strategy tested, in-sample optimization performed ≤ default parameters out-of-sample. The engine's own recommendation is to not re-optimize.
The other half: an egui replay UI for watching bars, indicator state, and trades advance one at a time.
cargo run --release # synthetic bars
cargo run --release -- --csv data/es_1min.csv --symbol ES --tf 10m
cargo run --release -- --view chart --shot out.png # headless renderControls: play/pause, jump, speed (bars/sec), window (visible bars), seek.
The design rule is that indicators emit states, not drawings.
compute_phase returns a PhaseSample per bar (coil young/building/mature,
release up/down/neutral); the app layer decides what a MatureCoil looks like.
That seam is what lets the renderer swap to wgpu later, and lets the same
semantic state feed alerts or journaling instead of only pixels.
It also renders trade-review frames from a replay-gym run — freezing the chart at each fill, overlaying side/price/outcome, and writing an index plus PNGs per closed trade:
replay-lab --gym-run <run-dir> [--gym-tf 1m] [--gym-window 120] [--gym-force]Bars come from Interactive Brokers TWS on localhost:7497 via ibapi
(sync). Requires TWS with Enable ActiveX and Socket Clients on, plus a market
data subscription. Synthetic bars are the offline fallback.
cargo run --example tws_smoke -- ES Futures
replay-lab --backfill MNQ --tf 10m --cache data/cacheWorking: replay engine, TV broker semantics, fixture verification harness,
higher-timeframe security() emulation, walk-forward, portfolio and intraday
risk analysis, TWS backfill, Phase Oscillator, replay UI, headless rendering.
In progress: ATR Cascade / Pivot Ribbon / Volume Stack render engines, the multi-timeframe wave-propagation view, wgpu renderer, backgrounding the TWS fetch (it currently blocks the UI for ~1s).
cargo build --release
cargo test # 77 testsNo data files are included — vendor futures data isn't redistributable. The
TWS backfill path (--backfill) regenerates a usable cache from a TWS
connection. Two tests are gated behind data that isn't in the repo and skip
cleanly without it.
This is extracted from a larger private research repo. The engine, the
verification harness, the analyses, and the documented findings are here in
full. Of ~20 ported strategies, two are included — orb-tue, which
verifies 56/56 exact, and pdl-sweep, the lookahead case study. The rest, and
the identities in the verdicts board, are redacted. The engineering is the
point; the book isn't mine to publish.
MIT — see LICENSE. Indicator concept attribution is in NOTICE.
