// TRADERVIEW — WALKTHROUGH

Tutorial index Docs hub
Progress
09 / 18

09.Backtest — stryke-JIT engine + walk-forward sweeper

Strategies compile to stryke bytecode, run through the JIT, and produce a per-trade ledger that flows into the same FIFO roll-up the real broker imports use. Walk-forward sweeper splits in-sample / out-of-sample and aggregates results across windows. Deterministic, reproducible, presetable.

Why stryke-JIT

A backtest is a tight loop: for each bar, evaluate N conditions, maybe place an order, maybe exit, accumulate state. Interpreted-AST evaluation is the bottleneck for any non-trivial sweep. TraderView's strategies compile to stryke bytecode (the same VM the backtest engine shares with the custom-indicator AST) and execute through a Cranelift JIT — orders of magnitude faster than a tree-walking interpreter, and fast enough that walk-forward sweeps with thousands of parameter combinations finish in minutes instead of hours.

The strategy DSL is small and intentional — entry / exit conditions, sizing, stops, targets, time-based filters. Anything more exotic compiles to a custom indicator (step 10) and feeds back into the DSL.

Defining a strategy

Strategies are stored in backtest_presets. Each preset has:

  • Universe — single symbol / watchlist / S&P 500 / Russell 3000 / custom universe query.
  • Timeframe — 1m through 1d.
  • Date range — IS / OOS split is configured at sweep time.
  • Entry conditions — boolean expression over indicators, price, volume, time-of-day, day-of-week.
  • Exit conditions — stop loss, profit target, time-based, trailing, signal-based.
  • Sizing — fixed shares, fixed dollar, fixed-fractional, Kelly, or a custom indicator value.
  • Costs — per-share commission, per-trade commission, fixed slippage (bps or cents).
// Pseudocode for an opening-range breakout preset
universe: small_caps_above_5
timeframe: 5m
entry: bar.high > or(0930, 0935, 0940).high
       AND volume > sma(volume, 20) * 2
       AND time < 1030
exit:  stop = entry - 1 * atr(14)
       target = entry + 2 * atr(14)
       time_stop = 1300
sizing: kelly_capped(0.25, max_dollar = 5000)
costs: 0.005/sh + 1bps slippage

Running a backtest

POST /api/backtest/run with the preset ID and any date-range override. The engine:

  1. Compiles the strategy DSL + referenced custom indicators to stryke bytecode.
  2. JIT-compiles hot loops via Cranelift.
  3. Streams bars from bars_* tables in chronological order (no look-ahead).
  4. Evaluates entries / exits per bar, emits virtual executions.
  5. Rolls up through the same FIFO logic that real trades use, into a parallel backtest_trades table keyed by run ID.
  6. Aggregates stats — same statistics the live Reports view uses (expectancy, R-distribution, drawdown, profit factor, SQN, Sharpe, Sortino, Calmar). Plus per-day equity series for the equity-curve chart.

Results land at GET /api/backtest/runs/{id}. The view renders equity curve, R-distribution, by-symbol breakdown, drawdown distribution, and a paginated trade list. Trade rows have the same right-click menu as live trades — drop into Charts with markers, open the journal slot, etc.

Walk-forward sweeper

POST /api/backtest/walk-forward. Splits the date range into N rolling windows; for each window:

  1. In-sample (IS) — sweep the parameter grid over the IS window. Pick the parameter combo with best objective (configurable: total return / Sharpe / Sortino / Calmar / expectancy).
  2. Out-of-sample (OOS) — re-run the chosen combo on the immediately-following OOS window.
  3. Roll the window forward and repeat.

The output is a per-window IS-vs-OOS table + a glued-together OOS equity curve. Big IS/OOS divergence is the curve-fitting smell test.

  • Parameter grid — Cartesian product of grid axes (e.g. stop = [1.0, 1.5, 2.0] × target = [1.5, 2.0, 2.5, 3.0] × ATR window = [10, 14, 20]).
  • Parallelism — runs fan out via Rayon across the host's cores. A 1 000-cell grid × 12 windows on a 16-core host finishes in minutes, not hours.
  • Determinism — same preset + same date range + same seed reproduces identical equity curves.

Presets & the Presets view

GET/POST /api/backtest-presets. Save / share / clone strategies. The Backtest Presets tile lists them with last-run R-multiple distribution and a small equity-curve sparkline so you can spot which presets are still alive.

Each preset has a unique slug; export / import as JSON (round-trips losslessly so you can pin a preset in version control and re-import on another install).

What backtests don't include

  • Real fills. A backtest evaluates "if the bar's high > trigger, fill at trigger + slippage". Live fills include queue position, latency, partial fills, and rejections. The fill-quality TCA report (step 05) compares your live fills to NBBO; the slippage assumption in the backtest is the simplest possible model.
  • Liquidity. The default model assumes you can always fill 100 % of the requested size at the bar. The liquidity report (step 05) and the optional liquidity guard in the sizing block (configurable %-of-bar-volume cap) bring this closer to reality.
  • Borrow cost. Short borrow is not modeled in the default cost block; configure it explicitly per-symbol if it matters for the strategy.
  • Survivorship bias. Universes built from "currently in the Russell 3000" are survivorship-biased. Universe definitions support point-in-time historical constituent lists if you've imported them.
Sanity A backtest result that's >2× live expectancy is almost certainly curve-fit, look-ahead biased, or both. The walk-forward sweeper is the cheapest way to find out which.