vike
Concepts

The paper exchange

Paper trading is the backtest fill model running behind the live core's own seam, built from the same primitives — so backtest equals paper by construction, and a gate proves it bit-for-bit rather than asserting it.

When a venue is mounted paper — the default under both gates, an unset arming ceiling and absent credentials alike — the ExecutionClient behind the engine is vike_paper::PaperExecutionClient. It is not a stub that acknowledges orders. Its own doc states what it does: submit publishes OrderSubmitted and OrderAccepted and rests the order; on_bar, driven by the core on each closed bar of the mounted series, fills the book with the engine's exact semantics — next-open market fills, limit and stop triggers, adverse-first intrabar resolution, slippage plus maker/taker fees. What it fills them with is the point of this page.

Built from the engine's own primitives

crates/vike-paper/src/lib.rs's module header states the design: the paper client owns a resting book and fills it with the engine's own public fill primitives — vike_fills::fill_model::BarFillModel::fill_price, vike_fills::fill_resolution::resolve_intrabar_fills, vike_fills::broker_sim::adverse_fill_price and vike_fills::broker_sim::fee. crates/vike-fills/src/lib.rs says why those live where they do: the paper exchange and the backtest engine are the same cost model seen from two sides, and a paper client mounted on a live feed must charge exactly what SimBroker charges or "backtest == paper == live" stops being checkable. There is one definition of what a fill costs and when it happens, and both sides call it — so backtest == paper holds by construction rather than by two implementations happening to agree. See fill models, fill resolution and the cost model for the primitives themselves.

That is possible because of the layering rule this workspace keeps re-learning: when two sides must not disagree, the cure is a shared crate below both. The paper client's header carries the history: it was folded into the backtest crate precisely to guarantee one fill definition, when co-location was the only way to guarantee it; once vike-fills existed, the two could share a dependency instead of a crate, and vike-paper split back out. The payoff is on the consumer side — vike-mount and vike-run had referenced the backtest crate for this client and nothing else (12 and 4 sites), compiling the entire simulator to obtain an ExecutionClient.

The gate that proves it

crates/vike-backtest/tests/r7_gate.rs takes the same strategy and the same bars, runs them through the backtest engine and through the live core in paper mode, and requires bit-identical fills — timestamp, side, quantity, price, fee, maker flag — plus identical realized PnL, fees and final position, every float compared through to_bits. The strategy logic is defined once, as a shared signal function, and mounted through each runtime's own trait; so the gate proves the runtimes agree, not that two copies of a strategy happen to. The gate stays in the backtest crate rather than beside the client because it drives the engine and the live core, not the client alone.

It proves the conditional verbs too. The stop-verb row agrees when the engine is configured with EngineParams::emulator_release_stops = true — the fired conditional releases as a market order and fills at the next bar's open, which is what live always does through its emulated ConditionalBook. The bracket row — an entry with a protective stop and a resting take-profit — agrees through the paper book's one-triggers-other / one-cancels-other triple: both stops fill via the one trigger oracle, and both sides cancel the surviving exit. The resolver those brackets share is contingent orders.

The chain this closes

The frozen golden fixtures prove the Rust backtest has not changed its arithmetic unnoticed; this gate proves the Rust backtest equals the Rust paper core. Together, the paper mode a strategy is first run against is the same arithmetic its backtest report was computed with — see the parity gate.

Where the equivalence stops, stated rather than implied

The header scopes the law precisely. It holds for Gtc, Ioc and Fok — everything both sides treat as "rests until filled or cancelled". It does not hold for the expiring time-in-forces, Gtd and Day: the paper client enforces their resting deadline (expire_due), while StrategyEngine/SimBroker ignore time-in-force entirely, so for such an order the engine keeps resting and filling where paper expires. Nothing gates that divergence — the gate only exercises Gtc — and nothing in the tree produces a non-Gtc order into either side today, so it is unreachable from a strategy; the header names mirroring the sweep in StrategyEngine::fill_pending as the follow-up that would close it. Two details of the paper side's own expiry are worth knowing before reading a paper fill: Ioc/Fok are not enforced there and rest exactly like Gtc (they are immediate-execution semantics, a change to the parity-gated fill pass, not a resting deadline), and a Day order is scoped to the UTC calendar day of the first bar it sees, never to the request's own timestamp, because nothing on the write path stamps one.

One more edge. The engine's default stop semantics — a same-bar fill at the trigger — deliberately diverge from live, where a stop is always emulated and releases as a market order. That divergence is pinned by its own law test in the backtester (tests/laws/trigger_law.rs), not by this gate; the gate proves agreement under the configuration that matches live.

A mount arms the halt sentinel; a simulation must not

crates/vike-ops/tests/paper_mount_arming_gate.rs classifies every file that constructs a PaperExecutionClient: a mount seam must arm the operator halt sentinel, and a simulation must not. The second half is not symmetry for its own sake — the r7 gate drives this same client, so a sentinel read on a simulation path would make backtest fills depend on a file lying around in <project>/settings/state/ and redden the equivalence gate for a reason nobody would find. The gate exists because a documented "each mount seam asserts on this" claim was already false when it was written: a third seam existed, unarmed, and per-seam assertions are evidence about the seams that have them, never about the set.

On this page