vike
Market making

Quote styles

The four pure placement styles, the book view they price from, and the two own-order filtrations that stop a maker joining or leaning on its own quote.

Where the two quote prices land on the book, when the Avellaneda–Stoikov layer is not pricing, is a QuoteStyle. crates/vike-model/src/strategy/maker_params.rs holds the enum — "a small registry of PURE styles" that lives in the bottom layer "so it can ride the typed live-params payload" — and crates/vike-mm/src/book.rs holds the arithmetic, because "it needs this crate's private book view."

The book view

BookView is "a normalized, best-first view of one book that the QuoteStyle registry + own-order filtration are PURE functions of. bids are highest-price first, asks lowest-price first; each level is (price, size)." tick_size is the venue grid: an L2 book carries its own (BookView::from_l2, the n best levels), and the L1 lane supplies "the venue tick LEARNED from the L2 book lane (learned_book_tick) when one has been seen, else the configured tick_size param (pure-L1 fallback)" (BookView::from_quote, exactly one level per side) — so one *_half_spread_ticks can't resolve to two different spreads across the lanes. A 0.0 grid means "Top can't step and filtration is inert."

The four styles

BookView::priced(style, half_spread, depth_levels) returns None when the book lacks a side. Otherwise:

stylebidasknote
Mid (default)mid − half_spreadmid + half_spreadmid = 0.5·(best_bid + best_ask) — "the ORIGINAL SpreadMaker pricing, reproduced bit-for-bit"
Joinbest_bidbest_ask"quote at the touch"
Topbest_bid + tickbest_ask − tick"one tick in front; … a 0.0 grid collapses Top to the touch"
Depthbids[n].priceasks[n].pricen = depth_levels, "clamped to the deepest available level (so a 1-level L1 view collapses to the touch)"

half_spread is read by Mid only and depth_levels by Depth only. The builder is SpreadMaker::with_quote_style(style, depth_levels, tick_size); from a profile the keys are style (top | join | mid | depth) and depth_levels, which from_params exposes so "a backtest profile can sweep the fill-rate-driving placement, not just the pricing model."

The style prices only when A-S does not

read_pricing_chains_as on SpreadMaker::from_params: pricing = "as" — and absent — chains the A-S layer, "which makes the style key INERT (requote bypasses the QuoteStyle placement whenever A-S state is Some)". pricing = "style" skips A-S "so the selected placement (style/depth_levels) actually prices — the registry path where the style key finally does something (audit F8)." A style line under the default pricing changes nothing.

Single-snapshot filtration

filter_own in book.rs subtracts "our OWN resting orders from the public book so the maker never joins or leans on its own quote — the correctness fix for quoting on the same feed it consumes." For each own (price, size) it finds the level at the same tick on that side and subtracts the size; "a level whose remaining size is <= 0 is REMOVED, so the derived best shifts to the next genuine market level (exactly what stops a Top/Join maker from stepping in front of — or resting on — itself)." Matching is by tick index through subtract_own, which "mirrors L2Book's own quantization"; with no grid it is a no-op.

It is enabled by with_own_order_filtration and is OFF by default, which crates/vike-mm/src/lib.rs lists among the opt-in refinements that COMPOSE with the rest. Turning it on changes pricing bit-for-bit. The subtracted pair is the per-side SideState::own snapshot — "the maker's INTENDED quote, not the venue-side remainder".

The own-order ladder

crates/vike-mm/src/own_book.rs exists because the single snapshot "is wrong in two ways the moment either assumption breaks": "Multiple own orders per level" — a ladder or an in-flight replace has N orders at a price, and "the single snapshot can only subtract the last one it remembered" — and "The accepted→public race": a venue acks "microseconds before the public market-data feed shows it. Subtracting it in that window removes depth the feed never added — inventing a phantom-empty level and pushing a Top/Join maker to quote THROUGH real resting depth."

OwnOrderBook is, per side, "a BTreeMap<price_ticks, level> (price-ordered …), where a level is an insertion-ordered IndexMap<coid, OwnOrder> — insertion order IS venue queue order, and it also makes the per-level f64 qty fold deterministic." Removals use shift_remove.

An OwnOrder has "deliberately only three states": Submitted ("NOT in the public book, so it must never be subtracted — enforced structurally: a Submitted order has no ts_accepted"), Accepted, and PendingCancel ("still resting at the venue … until the cancel lands, so by default it still counts"); terminal outcomes remove the order. OwnOrder::is_visible is the race gate: true "only when the venue has accepted it AND ts_accepted + accepted_buffer_ns <= now_ns", and "a 0 buffer means 'trust the accept immediately' — the behavior of the pre-existing single-snapshot filtration." StatusMask::RESTING (accepted + pending-cancel) is the default; ACCEPTED_ONLY is "for a maker that wants to lean on the depth it is about to reclaim."

on_modify is chosen "so the book never OVER-subtracts": a price move re-enters the new level "at the BACK (a venue re-price forfeits queue priority)" as Submitted with the stamp cleared; a size increase re-arms the stamp; a reduction keeps it, "since subtracting the smaller new size can only under-subtract, which is the safe direction." subtract_levels is "byte-identical to book::subtract_own's arithmetic for the single-own-order case".

Who drives it

SpreadMaker::with_own_order_book(tick_size, accepted_buffer) is "the whole opt-in" — it implies filter_own. The maker drives the ladder through OwnBookFiltration::place "with an optimistic ack": a Strategy never sees client-order-ids or venue accepts, "so the maker keys the book by its own tag ("bid"/"ask") and stamps ts_accepted itself at the placing event ts", the buffer "measured from send instead of ack." A runtime that knows the tag↔coid mapping drives the real lifecycle verbs through SpreadMaker::own_book_mut, "after which the buffer covers market-data propagation alone."

Units, and what is not saved

"The _ns suffixes follow the HFT nanosecond convention; the code never converts or divides a timestamp, so any single consistent unit is valid." The live lanes feed epoch-millis, "so a maker-side accepted_buffer_ns is configured in MILLIS." The ladder is mount-structural — not in SpreadMakerParams, untouched by a re-tune — and "nothing here is persisted": the cache is "rediscovered from the venue by the runtime's reconcile pass." It keeps its own fixed grid rather than adopting the L2 book's, "so on the L2 lane pass that venue's grid here explicitly."

The place in the tick where either filtration runs is stage (1) of quote emission.

On this page