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.
Journal divergence
The journal-divergence reconciliation divergence: what raises it, and what each of the four policies does with it.
Run profiles
The BacktestProfile TOML — a data slice, an engine cost configuration and a strategy by registry name — with every opt-in knob validated at load so a typo fails the profile rather than silently changing the result.