vike
Backtesting

Backtesting

One event engine and one vectorized kernel, both fed by the same fill primitives the paper exchange uses, driven by a TOML profile through a harness that runs bar or tick replay against the history store.

Vike Trader's backtester is the vike-backtest crate, and it offers exactly two simulation APIs. crates/vike-backtest/src/lib.rs's module header states the unification: engine holds the one event engine (StrategyEngine over SimBroker, any number of symbols) and vector_engine holds the one vectorized kernel (VectorBacktestEngine, any number of symbols); the older SingleSymbolEngine and fast_backtest are retired. Both return the same BacktestResult, which lives one crate down in vike-analytics and is re-exported here — the numbers are the same type whichever engine produced them.

The two engines, and which to reach for

The event engine is the general one. It steps aligned per-symbol bar series timestamp by timestamp — or, in tick mode, k-way-merges recorded quote, trade and book events — and hands each event to a strategy written against vike_model's Strategy<B: Broker> trait, the same trait that runs live (crates/vike-model/src/strategy/mod.rs's Strategy, whose on_bar doc names both firing points: once per symbol per step in a backtest, once per closed bar of the mounted series live). Everything with a resting order, a stop, a bracket, a cash gate, a margin call, a sizer or a maker quote goes here. Read the portfolio engine for the step loop and the sim broker for the fill engine it drives.

The vectorized kernel is deliberately narrow. crates/vike-backtest/src/vector_engine.rs's header draws the boundary: the cash_gate=False, market-order, target-weight path only — next-open fills, column-order shared-cash application, taker fees, slippage, funding, cost-basis averaging — and names what is out of scope: stops and brackets, the cash gate, volume and position caps, leverage capping, membership masks, liquidation, sub-bars and sizers. If your strategy is a T×S matrix of target weights and nothing else, it is the fast path. Read the vector engine.

The two are reconciliation partners, not alternatives that may drift: engine-versus-kernel agreement is a second oracle, gated as a test in crates/vike-backtest/tests/parity/engine_kernel_parity.rs, whose header requires the same trade count and a bit-identical final equity, and says never to widen that to a tolerance.

What a fill is, defined once

Neither engine owns its cost model. crates/vike-fills/src/lib.rs is the shared fill cluster — broker_sim (the three canonical cost scalars), fill_model (the bar, L1 tick and L2 book-walk tiers), fill_resolution (adverse-first intrabar ordering with the SL/TP bracket cap) and staleness (the opt-in stale-price wait) — and vike-backtest re-exports all four at its root. The reason it is a separate crate is the reason it matters to a reader of a report: the paper exchange mounted on a live feed must charge exactly what SimBroker charges, or "backtest == paper == live" stops being checkable. The primitives are documented under fill models, fill resolution, the cost model and staleness; the equivalence they buy is the paper exchange.

The arithmetic is held to the parity rules lib.rs restates from vike-model: f64 end to end, the same expression order, no mul_add or fast-math, and Python floor-mod on timestamps. That is what lets the parity gate compare bit patterns rather than tolerances.

The harness: from a TOML file to a result

Nobody constructs an EngineParams by hand to run a backtest. crates/vike-backtest/src/harness/mod.rs is the config-driven end-to-end runner: profile parses and validates a BacktestProfile, registry resolves strategy.name to a boxed strategy over SimBroker, run dispatches the profile to bar mode (StrategyEngine) or tick mode (the hist_replay loader), and report composes the metrics into a printable BacktestReport. The multi-run siblings — sweep, walkforward, euler and tpe — all take the same (&BacktestProfile, Arc<dyn HistStore>) shape and read their own profile section.

The harness is gated behind the hist-replay feature and compiles against the HistStore trait only; the concrete DataFusion store that the backtest binary actually opens sits behind a separate datafusion-store feature (crates/vike-backtest/Cargo.toml's hist-replay and datafusion-store entries state both halves). Depending on the harness therefore does not pull the Arrow tree, which is what lets vike-datahub's default build be DataFusion-free — a trait-only library, with the real server binary behind its own feature.

Reading order

Start with run profiles — the TOML is the whole user-facing surface — then running a backtest for the two ways a profile is executed, the harness registry for what strategy.name may say, and runs on disk for what a finished run leaves behind. The engine pages after that explain the number you got.

The rest of the tree

The opt-in realism knobs each have a page: the queue lane and queue models for resting maker limits, market impact for bar mode, latency models for the tick path, tick replay for how store rows become a replay stream, timeframes and scheduling for the strategy-facing clock. Search over a profile's grid is sweeps, search and objectives; out-of-sample stitching is walk-forward. The statistics a report is made of live under analytics.

On this page