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.
The history store
One Parquet store under the project, thirteen declared layouts, a gate that derives the live set from the code, many parts per day, and the resolver that decides where the store lives when nobody said.
The parity gate
Frozen golden fixtures compared bit-for-bit, a second oracle in the vectorized kernel, and one rule that outlived the oracle the fixtures came from — a tolerance is never widened to make a test pass.