Strategies
The one Strategy trait, the Broker handle it trades through, and why a strategy written against the common denominator runs unchanged on the simulator and on the live core.
Vike Trader has ONE strategy trait. vike_model::strategy's Strategy is generic over its broker,
and Broker is, in that module's own words, "the strategy's window onto whichever execution stack
is running it": in a backtest the simulator core implements Broker with direct mutation (the fast
path), while on the live and paper stacks the per-on_bar live context implements it by BUFFERING
submissions, drained through the one live path — mint, then RiskGate, then the venue client —
after the handler returns. A strategy never learns which of the two it is talking to.
The write-once rule
A strategy written as impl<B: Broker> Strategy<B> runs UNCHANGED on both stacks, and the r7 gate
proves the results are bit-identical. A strategy that needs backtest-only power — schedule
registration, weighted or bracket submits, sizers, multi-timeframe reads — is written against the
concrete simulator broker instead, impl Strategy<SimBroker>, and the type system then documents
that it is not live-portable. Portability is pinned by COMPILATION rather than by assertion:
crates/vike-backtest/src/harness/registry.rs's buy_hold_mounts_on_any_broker is a generic probe
function whose body is type-checked at its definition, so narrowing an impl back to one concrete
broker stops compiling.
Dispatch is generic — monomorphized, never dyn — in the backtest hot loop, because a vtable per
verb would regress the roughly 70 ns/bar engine. The live side may box the strategy
(Box<dyn Strategy<LiveBroker> + Send>): one virtual call per closed bar is noise there, and a
boxed strategy is itself a Strategy, every hook forwarding through one deref.
The Broker surface
The verb surface is deliberately the COMMON DENOMINATOR of the two stacks. Order intake is
submit_market and submit_limit. Reads are position, price, equity, bars (closed bars up
to and including the current step, so no look-ahead), index and now (engine time in epoch
milliseconds). Engine-specific verbs stay inherent methods on the concrete broker types.
Two pre-trade liquidity reads are defaulted, so an engine with no book answers "I don't know".
quote_vwap is the volume-weighted price of consuming the resting book best-level-outward; its
None means the answer is unavailable or the displayed depth cannot honour the size, and the trait
is explicit that it "is never a price" — a strategy must treat it as do not trade this size, not
as assume the last price. depth_within_price is the sizing half: total displayed size at a
limit price or better, defaulting to 0.0, which correctly sizes a book-less engine to no trade.
HftBroker and MultiHftBroker
HftBroker: Broker adds the tagged-resting-order verbs a market maker needs: submit_limit_tagged,
modify_tagged (re-price in place, preserving venue queue priority) and cancel_tagged, plus a
symbol-less position for the single mounted series. A tag is a strategy-chosen stable id naming
one resting order, so a later modify or cancel resolves it without the strategy ever seeing a
client-order-id — the registry belongs to the runtime. MultiHftBroker: HftBroker adds exactly one
more read, position_of(symbol), so a correlation-aware maker can assemble the whole inventory
vector.
The handlers
Every handler defaults to a no-op; implement only what the strategy needs. on_bar,
on_quote_tick and on_trade_tick are skipped until index >= warmup() — the R2 gate, enforced
by the engines rather than by the trait, and on_order_book sits behind it on the live side only:
the backtest engine dispatches a book event before the gate, and a book event does not advance the
index. Beyond on_start / on_bar / on_quote_tick / on_trade_tick / on_order_book /
on_schedule / on_fill / on_stop, the seam carries the occasional-lane hooks: on_feed_status
(the feed disconnected, went stale, or recovered — the "pull my quotes" trigger), on_mark (a
reference mark for a series the strategy watches but is not mounted on), on_reference_quote (an
L1 touch from a declared leg on a DIFFERENT venue, deliberately a distinct hook so a maker cannot
mistake the reference venue's touch for its own), on_flow (per-side flow toxicity), on_order_event
(the non-fill order transitions — see order lifecycle) and
on_params_updated. None of those occasional-lane hooks is warmup-gated, and the trait says so hook
by hook: a feed death, a reference mark, a foreign-venue touch, a toxicity reading, an order outcome
and a re-tune are market, account or control facts regardless of bar count, so each fires from the
first one that arrives — as on_fill always has. save_state / load_state let a strategy
survive a process restart.
The fill itself is never delivered through on_order_event: quantity, price and the resulting
position and equity stay on the richer on_fill path, which fires first.
Where the line runs
The recurring proposal — add the missing accessors to Broker and every strategy becomes portable
— is answered in the module doc rather than re-derived: what keeps a backtest-only strategy
backtest-only is not a missing READ but a CONCEPT the live stack does not have. Five of them, each
owned by the simulator alone: the position sizer, the shared-cash gate's weight argument, the
protective stop attached to the entry, the symbol universe, and the bar-count schedule.
Parameters
A strategy family's tuning parameters enter the bottom domain crate on exactly one criterion: the
family supports a LIVE re-tune, so its params ride StrategyParams, a serde field of the runtime's
journaled command lane. Two families qualify — the Avellaneda–Stoikov maker
(market making) and the
position controller. Every other family keeps its knobs as
plain fields beside its own implementation and reads them from the harness TOML table.
Next: the registry resolves a name to a strategy, authoring in Rust writes a new one, and the templates document the ten portable names.
Guards
The xEMM's pure safety verdicts — the freshness bounds, the soft and hard naked-inventory bands, and the halt vocabulary every stop is named in.
The portable registry
Name to boxed strategy, generic over the broker — the one table that serves both the simulator and the live daemon, and the four refusals it makes possible.