vike
Market making

Binary outcomes

Pricing a bounded [0,1] outcome token — the LMSR log-odds reservation, and the LS-LMSR and Glosten–Milgrom half-spread sources that replace the A-S vol/κ term.

The Avellaneda–Stoikov layer was first written for Polymarket's 0–1 outcome tokens, and its Bernoulli cap, unit-interval walls and resolution blackout are the bounded-price adaptations on Avellaneda–Stoikov. Two further modules give the reservation and the half-spread models native to a binary venue. Both are "Group-B pluggables" — ReservationModel and SpreadSource on crates/vike-model/src/strategy/maker_params.rs — and both default to the A-S path, byte-identical.

The LMSR reservation

crates/vike-mm/src/fairvalue.rs is the "LMSR logistic fair-value / inventory-skew reservation for a bounded [0,1] outcome token". It ports Hanson's Logarithmic Market Scoring Rule "to a live-anchored form: the binary LMSR price is the logistic 1/(1+exp(-Δ/b)) of net inventory Δ over liquidity depth b." The reason for log-odds: "a linear cash-skew would walk a near-0/near-1 quote straight off the edge", while log-odds "keeps the reservation on the bounded [0,1] simplex and reproduces the LMSR 'wall' near the boundaries, where a unit of inventory moves price by mid·(1−mid)/b, vanishingly small at the edges and largest at the coin-flip mid."

lmsr_reservation(mid, net_inventory, b) converts the live mid to log-odds, walks it, and maps back:

logit_mid = ln(mid / (1 − mid))
r         = 1 / (1 + exp( −(logit_mid − net_inventory / b) ))

Long YES (net_inventory > 0) lands "strictly BELOW mid (skew DOWN to sell the position off)"; short lands strictly above. Two guards keep a bad input from moving a quote: "a degenerate depth (b <= 0) or a mid outside the OPEN interval (0, 1) … returns mid unchanged". Zero inventory short-circuits to mid exactly rather than round-tripping through ln and the logistic, because "two independently-rounded transcendentals need not compose back to mid bit-for-bit".

At the call site in AsState::price, LMSR "walks the live mid in log-odds by SIGNED net inventory (position, not q_norm) over depth b; inert (returns anchor) off the unit interval, so a $-scale book is unchanged." Select it with reservation_model = "lmsr" (or "logistic"); depth is lmsr_b, default 100.0, "larger ⇒ flatter walk, more subsidy at risk".

Two half-spread sources

crates/vike-mm/src/spread_source.rs supplies "two [0,1]-native half-spread SOURCES for the maker's half_spread slot". Both are alternatives to the A-S ½·[γ·V + (2/γ)·ln(1 + γ/κ)]: "they price the spread from prediction-market microstructure instead of an inventory/vol/liquidity trio, which is the natural fit for a [0,1] binary-outcome venue (Polymarket up/down)."

LS-LMSR (Othman and Sandholm, A Practical Liquidity-Sensitive Automated Market Maker, EC'10). The liquidity parameter scales with volume, b(q) = α·Σ_i q_i, "so the market maker's built-in overround (Σ_i p_i − 1) IS a volume-scaled spread. We read that overround off as a half-spread." ls_lmsr_prices implements the two instantaneous prices ∂C/∂q_i of the binary cost function, with the derivation in its doc — the ordinary softmax price plus a liquidity-sensitivity correction — and ls_lmsr_half_spread returns (p_yes + p_no − 1)/2. At a symmetric book the result is exactly α·ln(2), "the pinned identity"; "more balanced books carry a WIDER spread than lopsided ones (a near-resolved book, p→(1,0), has overround → 0)". α ≤ 0 or Q ≤ 0 gives 0.0. The maker feeds it the probability split (anchor, 1 − anchor), so it is "widest at p≈0.5, tight toward the walls."

Glosten–Milgrom (JFE 14:71–100, 1985). "The spread is pure adverse selection: an informed fraction μ trades on a known binary value V∈{0,1}, noise traders flip a coin, and the competitive dealer's zero-profit bid/ask are the conditional expectations E[V|sell]/E[V|buy]. The half-spread is the price of being picked off." glosten_milgrom_quotes:

ask = E[V|buy]  = π·(1+μ) / ( π·(1+μ) + (1−π)·(1−μ) )
bid = E[V|sell] = π·(1−μ) / ( π·(1−μ) + (1−π)·(1+μ) )

μ=0 ⇒ ask==bid==π exactly; μ=1 ⇒ (0, 1). PI_GUARD (1e-12) clamps the belief strictly inside (0, 1) so the degenerate priors "never form 0/0". glosten_milgrom_half_spread is (ask − bid)/2, with "the π·(1−π) shape: maximised near π=0.5 and → 0 as π → 0 or π → 1".

Select with spread_source = "ls_lmsr" or "glosten_milgrom" ("gm"); the constants are ls_lmsr_alpha and gm_mu, both 0.0 by default. On gm_mu, "a live μ̂ estimate (VPIN / OFI toxicity) is the follow-up feed" — the VPIN study is the named candidate; today gm_mu is a hand-set constant.

A zero read must not collapse the quote — floor it

Both sources "are floored/capped by the existing min/max_half_spread_ticks so a zero read never silently collapses the quote (use a min_half_spread_ticks floor with GM/LS-LMSR)." At their defaults both read 0.0, and AsState::price runs the result through the same bounded_half_spread ladder the A-S path uses — "the stage-1 floor still guards a zero LS-LMSR/GM read from collapsing the two-sided quote; stage 3 refuses when no width clears the round-trip fee under the cap."

Orthogonal by design

The two knobs are independent: ReservationModel "only swaps how r responds to inventory" and SpreadSource is "orthogonal to ReservationModel" — "all three feed the same half_spread slot, then the shared min/max_half_spread_ticks floor/cap and grid snap." Either non-default selection moves price off its verbatim fast path onto the override path, where the Cartea–Jaimungal shift of short-term alpha and the force-flatten of settlement are added to whichever reservation was chosen, and the posting geometry is the same assemble_quote. The round_trip_fee_rate field notes that vike_run::MakerMountConfig::polymarket cannot arm the break-even floor, because "the Free/ProbabilityScaled shapes are refused" — the operational side is on Polymarket operations.

On this page