Market making
The SpreadMaker reference strategy, the HftBroker seam it is written against, the layers it composes and the rule that each one is byte-identical when off.
crates/vike-mm/src/lib.rs is "the HFT market-maker crate: the SpreadMaker reference strategy
and its pure pricing/risk helpers, extracted from vike-core … into a dedicated, CI-gated crate that
depends on vike-model alone. It does NOT own the runtime — vike-core's live core still MOUNTS the
maker (via a Box<dyn Strategy<LiveBroker>>), because LiveBroker: HftBroker; only the maker code
moved here, engine-agnostic over the trait."
The seam
crates/vike-model/src/strategy/mod.rs's HftBroker is "the HFT extension of Broker: the
tagged-resting-order verbs a market-making strategy needs that are NOT on the portable Broker
common denominator" — a signed position read plus submit_limit_tagged, modify_tagged and
cancel_tagged. A tag is "a strategy-chosen stable id naming one resting order, so a later
modify/cancel resolves it WITHOUT the strategy ever seeing a client-order-id". The maker is written
impl<B: HftBroker> Strategy<B>, and because HftBroker: Broker "a plain Broker-only backtest
engine still cannot mount them, so the type system keeps documenting the HFT requirement". The
resting bid and ask carry the tags "bid" and "ask"; the maker re-prices them in place through
modify, "which preserves venue queue priority", rather than cancel/replace. The portable
strategy seam is the parent contract.
What composes
The crate header tells the maker's history as a stack of additions, each with the same contract:
off by default, and byte-identical to the maker beneath it when off. In the SpreadMaker doc's
words, "the breaker decides WHICH sides quote, the skew decides their SIZES, the style + filter
decide their PRICES."
| layer | builder | off state | page |
|---|---|---|---|
fixed mid ± half_spread | SpreadMaker::new(qty, half_spread) | the base | quote styles |
| inventory-skew sizes | with_skew | skew = 0.0 ⇒ both multipliers exactly 1.0 | inventory skew |
| per-side fill-rate breaker | with_fill_breaker | any of window/threshold/cooldown non-positive | inventory skew |
| quote-style registry + own-order filtration | with_quote_style, with_own_order_filtration, with_own_order_book | Mid, filter_own = false | quote styles |
| Avellaneda–Stoikov pricing | with_avellaneda_stoikov | as_state = None | Avellaneda–Stoikov |
| refresh tolerance | with_refresh_tolerance | None, or an all-zero bag | refresh tolerance |
| ladder | with_ladder | levels <= 1 | ladder quoting |
| liquidity rewards | with_liquidity_rewards | weight = 0.0 | liquidity rewards |
| flow toxicity | with_flow_toxicity | both knobs 0.0, or no reading ever delivered | short-term alpha |
The breaker is the adverse-selection guard the plain maker lacked: it "NETS the two sides against each other (round-trip netting): a bid fill later offset by an ask fill is a completed, inventory-neutral round-trip … and must NOT trip the guard — only NET one-directional accumulation on one side does." A tripped side is pulled until its cooldown expires "while the other side keeps quoting (and re-skewing) normally."
Every layer feeds one shared tick step, requote — quote emission
walks its stages in order.
Config and state are split
SpreadMaker::cfg holds "the maker's whole CONFIG bag — every live-tunable knob, held as the SAME
SpreadMakerParams shape the live-parameter plane transports", and it is "deliberately
CONFIG-ONLY". The runtime state — the per-side resting snapshot, ladder rungs, breaker deadlines,
the fill window, the own-order ladder, the learned book tick — lives in sibling fields that
apply_params never touches, "so a live re-tune keeps resting orders, queue position, and learned
state." Strategy::on_params_updated hot-swaps the bag without unmounting and bumps
params_epoch, which "starts at 0; observable so a caller/test can confirm a re-tune actually
landed."
One exception is written into the field doc: cfg.avellaneda_stoikov is "PINNED None — the live
A-S params' single authority is as_state.params", re-attached by SpreadMaker::params on the way
out and stripped on the way in, so no second copy can drift.
Across a restart, crates/vike-mm/src/strategy_impl.rs's SpreadMakerStateV1 persists "ONLY the
per-side fill-rate breaker's suppression deadlines and (if A-S is enabled) the online estimator's
accumulators" — not the fill window, not the resting-order cache (rediscovered by the reconcile
pass), and not the A-S params, so "a restart with a DIFFERENT tuning should price with the new
tuning, not a stale saved one."
The lanes
crates/vike-mm/src/strategy_impl.rs owns the Strategy impl. on_quote_tick and
on_order_book both run requote; the book lane also learns the venue's real grid, adopted on the
L1 lane "so one *_half_spread_ticks can't resolve to two different spreads." on_trade_tick
feeds the κ tape, on_mark the A-S underlying anchor, on_flow the toxicity reading — none of
the three touches the broker; the reaction lands "at the NEXT quote in requote".
From a profile
SpreadMaker::from_params is the registry reader: "a bare {} yields the recommended A-S maker
(AsParams::default) and each present key overrides one knob." It "mirrors how
vike_run::build_maker composes the live maker (fixed seed → with_quote_style → A-S), minus the
reward mount-only sub-bag." The template registry carries both names —
spread_maker and
gueant_maker — and with_spread_model is how "the
gueant_maker registry alias forces SpreadModel::Gueant so the same spread_maker knobs price
with the GLFT closed form."
A typo in an enum key fails the profile — it does not fall back
ParamError exists "because Option could not say which of two things happened": each enum reader
once returned None for an absent key and for an unrecognized one alike, so
spread_model = "gueant_lehale" "silently ran Avellaneda–Stoikov while the profile named GLFT". A
present-but-unrecognized variance_mode, horizon_mode, price_domain, spread_model,
reservation_model, spread_source, kappa_mode or style now refuses to resolve. The one
residual is named on read_pricing_chains_as: pricing maps to a bool, so pricing = "styel"
still reads as "as".
The from_params table also names the crypto profile: an unbounded $-scale asset "wants
price_domain = "unbounded", variance_mode = "raw_local", horizon_mode = "constant_tau",
min_half_spread_ticks = 2, max_half_spread_ticks = 60 — the exact knobs
vike_run::MakerMountConfig::crypto sets."
Three crate rules
Every fold in the crate is a naive left-to-right f64 fold with no mul_add, and
crates/vike-mm/src/fits.rs's header carries the reason: "the expression shapes are bit-anchored
by the tests below and must not be 'simplified'." Each module restates it as house style — "naïve
folds, no mul_add, per the crate rule."
The other two are crates/vike-mm/src/own_book.rs's header. Own-book removals go through
shift_remove, "never swap_remove, so queue order survives a cancel in the middle of a level",
because "insertion order IS venue queue order". And every own-book timestamp is "ONE caller-chosen
EVENT clock (never wall-clock)": the _ns suffixes "follow the HFT nanosecond convention", but
"the code never converts or divides a timestamp", and the maker feeds it an event ts that is
"epoch-MILLIS on the live quote/book/fill lanes".
The crate's second reference strategy, XemmMaker (crates/vike-mm/src/xemm/mod.rs), is complete
and tested and is reached by no binary: crates/vike-run/src/xemm.rs's build_live_xemm_core
has no caller, and crates/vike-strategy/src/registry.rs's strategy_by_name has no xemm arm —
so no profile, flag or feature can resolve it.
Cross-exchange market making carries that record.
Model pins
The two constants that decide whether a model file and the crate agree — what each proves, what neither proves, the equality gate that is the real bump proof, and the category map that is a wire format with no version of its own.
Avellaneda–Stoikov
The reservation-price pricing layer — its three closed forms, the bounded-variance adaptation, the width ladder that refuses rather than bleeds, and the GLFT model that rides the same assembly.