vike
Market making

Intensity fits

The κ estimators the spread's adverse-selection floor reads — the public-tape MLE, the censored own-fill hazard fit, the shrink between them, and the base-intensity profile GLFT prices with.

crates/vike-mm/src/fits.rs holds "the A-S fill-intensity ESTIMATORS — the κ fits and the base-intensity A profile". They are "four pure functions over a tape, no state and no venue types, each the maximiser of one likelihood the A-S/GLFT closed forms consume." Why κ matters: per crates/vike-model/src/strategy/maker_params.rs's KappaMode, "κ sets the spread's adverse-selection floor (1/γ)·ln(1+γ/κ); only κ (not the base rate A) enters the spread."

Three modes

KappaMode selects which estimate AsState::effective_kappa prices with.

  • Fixed (the default): kappa_default. "The online MLE is still maintained from the trade tape but is NOT used to price — 'available but gated'."
  • LiveFit: the public-print MLE below, "gated by the n_min sample floor (below it, or on a degenerate window, it falls back to κ_default)."
  • OwnFillFit: the censored fit over the maker's own outcomes, shrunk toward the public fit below n_min own fills. "Where LiveFit reads the whole tape's prints, this reads the fill intensity THIS maker actually realises at its own quoted distances, so κ reflects the adverse selection it truly faces."

From a profile the key is kappa_mode (fixed | live_fit | own_fill_fit), with kappa_default, kappa_min, kappa_max, n_min and trade_window_ms beside it; SpreadMaker::from_params calls this "the audit-F3 selector that makes the whole #790 κ-fit family reachable from a registry/harness table."

The public tape

fit_kappa is the "size-weighted exponential MLE for the fill-intensity decay κ: κ̂ = Σw / Σ(w·δ), with the n_min sample floor and the degenerate-window guard (Σ(w·δ) <= 0) both falling back to κ_default, and a trusted fit clamped to [κ_min, κ_max]." The clamps have reasons on AsParams: "κ → 0 blows ln(1+γ/κ) → the spread explodes" and "κ → ∞ collapses the spread."

The tape is fed by Strategy::on_trade_tick in crates/vike-mm/src/strategy_impl.rs — a no-op when A-S is off — through AsState::record_trade, which folds "each executed trade's size-weighted distance-from-mid into the online intensity estimator's running sums over an event-time window (O(1) amortised)". δ is the trade's distance from last_quote_mid, the mid requote last priced off; trades arriving before the first priced tick are ignored. Entries older than trade_window_ms are evicted from the front and subtracted from both sums.

The own-fill tape

fit_kappa_censored models "each order's fill intensity as λ(δ) = A·exp(−κ·δ) (δ = distance-to-mid at placement) and treats the order as an exponential survival with that distance-dependent rate: a FILLED order contributes its fill, a PULLED/unfilled order is a RIGHT-CENSORED exposure." Profiling A out leaves κ as the root of S1(κ)/S0(κ) = mean-fill-δ, where the two sums run over all orders "(fills AND censored — the censored exposures carry the survival information)" and the right-hand side is the mean δ over filled orders only. The ratio is monotone decreasing in κ, "so the root is found by BISECTION on [κ_min, κ_max] (deterministic, no RNG)" — 80 halvings, with a target outside the bracket clamped to the near bound. No fills means None: "the likelihood carries nothing to locate κ".

shrink_kappa keeps a thin own tape honest: "a LINEAR blend w·own + (1−w)·public with w = min(n_fill / n_min, 1)", all public at zero fills and all own at or beyond n_min, "so a maker prices with the robust public estimate until it has accumulated enough of its OWN fills to trust their intensity."

The tape is built by SpreadMaker::feed_own_outcome in crates/vike-mm/src/lib.rs, "a GUARDED no-op unless the A-S layer is on AND its kappa_mode is OwnFillFit". δ is the resting quote's distance to the fair mid it was last priced against; exposure is the event time since that side's last place or re-price (quoted_ts). The breaker's suppression pull in quote emission records "a CENSORED own outcome … BEFORE the resting snapshot / placement clock is cleared." AsState::record_own_outcome drops non-finite inputs, floors δ and exposure at 0, evicts by the same trade_window_ms window, and re-fits "HERE — on the RARE own-outcome event, never on the per-tick requote path — and caches its result."

What survives a restart

crates/vike-mm/src/strategy_impl.rs's AsStateV1 persists the public tape (trades, sum_w, sum_w_delta) alongside σ̂². The own-outcome tape does not travel: own_obs is "NOT persisted — it re-warms after a restart, like the fill-rate breaker's fill window."

The base intensity

fit_base_intensity profiles A "from the SAME censored own-order tape fit_kappa_censored fits κ on, given a known/fitted kappa": once κ is fixed the log-likelihood "has a CLOSED-FORM maximizer in A: ∂/∂A = D/A − S0(κ) = 0 ⇒  = D / S0(κ)", D the count of filled orders. Units: exposure in ms gives  in "orders/MILLISECOND, matching the per-ms σ̂² the GLFT S consumes".

Only the Guéant model reads it. AsState::effective_a returns the cached  "once the own-fill tape carries fills (i.e. under KappaMode::OwnFillFit), else the fixed AsParams::base_intensity_a. Only consulted under SpreadModel::Gueant (A-S ignores A), so an A-S maker is byte-identical either way." The fixed default is 1.0, described on the field as "a placeholder that MUST be calibrated to the venue's touch flow before a live GLFT mount".

What the tests pin

The estimators are bit-anchored — the header says the expression shapes "must not be 'simplified'" — and the own-fill suite in fits.rs pins recovery: on a synthetic tape with fill counts proportional to 0.5ʲ at distances (j+1)·Δ, censored_mle_recovers_known_kappa requires κ̂ within 1e-3 of ln 2 / Δ, and that κ̂ solves the score equation to 1e-6. The censored orders that equalise each level "are ESSENTIAL: drop them and the weighting (hence the recovered κ) changes." effective_kappa_owns_fit_and_off_modes_ignore_the_tape pins that Fixed never reads the own tape even when populated. The formulas themselves sit inside Avellaneda–Stoikov.

On this page