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.
crates/vike-mm/src/avellaneda.rs is the optional pricing layer SpreadMaker::with_avellaneda_stoikov
turns on. It is "net-new Rust with NO Python twin (like SpreadMaker itself), so 'parity' means
self-consistent reference-value pinning + neutral-reduction, not oracle bits." Its output is a
(bid, ask) pair that "still flows UNCHANGED into the size-skew → breaker → filtration →
modify-in-place tail" of quote emission, "so A-S inherits
the adverse-selection guard it classically lacks."
The closed forms
The module header states them, "adapted to Polymarket's 0–1 bounded prices":
reservation price r = s − q_norm · γ · V
optimal half-spread δ_half = ½·γ·V + (1/γ)·ln(1 + γ/κ)
effective variance V = min(σ̂²·H, p·(1−p))s is the fair mid, q_norm the size-normalised signed inventory, γ risk aversion, κ the
fill-intensity decay, H the effective horizon (min(τ_hold, T−t) where a resolution time is
known, τ_hold alone otherwise), and p = s the current probability. "The Bernoulli cap p(1−p)
makes skew and spread vanish at the 0/1 walls (V→0) and peak at p=0.5 — the defensible adaptation
that keeps A-S from over-penalising inventory near a wall." The half-spread is decomposed into
as_vol_halfspread (½·γ·V) and as_intensity_halfspread ((1/γ)·ln(1+γ/κ)), "so the floor is
a clean bit-anchor when V == 0".
Each input has one owner. AsState::fair_value takes the raw mid, or with use_micro_price the
micro-price "weighted toward the side with LESS resting size"; a one-sided book returns None.
q_norm = position / q_scale, with q_scale = 0 treated as 1. update_sigma2 is one EWMA step
of the per-ms mid variance, inst = Δ²/Δt, with α = 1 − 0.5^(1/half_life). horizon_ms returns
τ_hold under HorizonMode::ConstantTau, and min(τ_hold, T−t) under TimeToResolution once a
resolution_ts is known — without one it returns τ_hold there too.
bounded_variance implements the three VarianceModes — LocalCapped (the default),
PureBernoulli, RawLocal — clamped at 0. κ comes from effective_kappa, the subject of
intensity fits.
AsParams::default is "the recommended Polymarket starting configuration": gamma 0.1,
tau_hold_ms 3 600 000, resolution_blackout_ms 60 000, a kappa_default of 50 — "used directly
in KappaMode::Fixed", the default mode, and unclamped there — with kappa_min 1 and
kappa_max 1000, which clamp a fitted κ in crates/vike-mm/src/fits.rs's fit_kappa,
sigma_half_life 32, trade_window_ms 60 000, n_min 20, q_scale 100 and a one-tick
min_standoff_ticks. horizon_mode defaults to TimeToResolution with resolution_ts unset, so
on the recommended configuration that hour is the whole horizon until a profile names a resolution
time.
The price domain
The 0–1 geometry "is now ONE case of a PriceDomain knob". domain_bounds resolves the posted
walls: UnitInterval gives (tick, 1 − tick) "with the SAME arithmetic the layer hard-coded before
the generalization"; Unbounded gives (−∞, +∞), for which the clamp "is an exact no-op";
Band { lo, hi } gives explicit walls. Two pairings "are load-bearing for a $-scale mount":
VarianceMode::RawLocal, because "the Bernoulli cap goes negative for p > 1", and a
min_half_spread_ticks floor, because "the adverse-selection half-spread is sub-tick at $-scale,
so without it bid/ask snap to one tick."
The width ladder
bounded_half_spread is "THE WIDTH LADDER — the single authority on how a raw model half-spread
becomes the posted one". Three stages, and "the ORDER is the whole design": the
min_half_spread_ticks floor (the sub-tick guard); the max_half_spread_ticks cap, re-floored so
"a max < min misconfig can never sacrifice the two-sided quote"; and the break-even fee_floor
from break_even_half_spread, which is ½ · round_trip_fee_rate · s — "one leg's rate times the
price, even though the bar being cleared is the whole round trip."
The third stage does not join the max(floor, cap) idiom. When fee_floor_exceeds_cap, "the
answer is None: post nothing", because the cap answers "how wide may I go and still hope to
fill?" and the fee floor "how wide must I be to not lose money?", and when they disagree "that is a
fact about the INSTRUMENT … not about the tuning". The measurement behind the rule: "on prod2's
bybit BTCUSDT mount: a 60-tick ceiling ⇒ ≤ 1.903 bp of capture against a 4.000 bp round-trip fee,
i.e. −3.94 bp per round trip, forever."
The fee floor makes an impossible mount refuse; it does not make it profitable
"The cure for fee_floor_exceeds_cap is a different instrument or a different fee tier, never a
wider cap." AsState::note_fee_floor warns on the EDGE into and out of the refusal — twice per
episode, never per tick, because the requote is the vike-core hot fold — and round_trip_fee_rate
is None on a hand-built AsParams: None means "nobody could name this venue's maker fee as a
fraction of price", which is not the same statement as a measured Some(0.0).
Assembly
assemble_quote is "the affine two-sided posting geometry shared by A-S/GLFT and the Group-B
model overrides": bid (r − d).min(s − standoff), ask (r + d).max(s + standoff) so "inventory
skew can't push a quote across s", the wall clamp, a grid snap through snap_to_tick (which uses
vike_model::round_to_step, round-half-to-even), and acceptance only of "a genuine, correctly-ordered
two-sided quote that still straddles the fair mid" — lo ≤ bid < s < ask ≤ hi. Otherwise None,
the same hold contract as a one-sided book.
Three resolution-aware terms live in this file too. Inside the blackout — in_blackout, only under
TimeToResolution with a known resolution_ts and a positive window — price returns
widest_quote, "stay two-sided but push fills out to the extreme walls"; effective_blackout_ms
widens that window near the money by 1 + atm_blackout_scale·exp(−z²/2). terminal_var adds
γ_term · w(τ) · p(1−p) into V over the last terminal_ramp_ms, because the settlement variance
"does NOT vanish as τ → 0 — unlike the diffusion term". blended_anchor pulls the priced mid
toward p_up(underlying) by underlying_weight; "only the pricing sees this — the σ̂²/κ estimators
keep the raw book mid." The rest of the resolution machinery is on
settlement.
GLFT through the same assembly
gueant_skew_coeff is the Guéant–Lehalle–Fernandez-Tapia stationary coefficient
S = √(σ̂²·γ / (2·κ·A) · (1 + γ/κ)^(1 + κ/γ)), A the base fill intensity at the touch "per ms,
matching the per-ms σ̂² — the units MUST agree". effective_variance feeds it to the shared
assembly as V_eff = S/γ, "which makes the posted depths c1 + (½ ± q)·S — the SAME affine form
A-S emits with γ·V"; the horizon, the settlement term and the Bernoulli cap "are all IGNORED here
(GLFT is horizon-free)". The glft_equals_avellaneda_stoikov_at_matching_variance test pins the
identity as bit-exact.
Two paths through price
With ReservationModel::AsLinear, SpreadSource::AsOptimal, both alpha weights,
running_penalty_phi and force-flatten all off, AsState::price returns as_quotes — "the
verbatim pre-Group-B call, so the fast path is byte-identical BY CONSTRUCTION". Anything else takes
the override path: swap the reservation (the LMSR walk on
binary outcomes), add the Cartea–Jaimungal shift
(short-term alpha) and the force-flatten skew, swap the
half-spread source, then run "the SAME width ladder as_quotes runs" and the same assemble_quote.
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.
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.