vike
Market making

Short-term alpha

The Cartea–Jaimungal additive reservation shift, the book-imbalance and order-flow-imbalance signals that feed it, and the OFI-synthesized toxicity fallback.

crates/vike-mm/src/alpha.rs is "the Cartea–Jaimungal short-term-alpha slot the A–S reservation currently lacks — the additive drift term a·h − φ·q·h and its two microstructure signal inputs, isolated so they unit-test without a runtime." It is pure f64 with naive folds and no mul_add, "the reservation stack pins fold order".

The header names both sources: Cartea, Jaimungal and Penalva's Algorithmic and High-Frequency Trading (2015) for "the inventory-penalized short-alpha market-making formulation: the optimal quotes ride a reservation price shifted by an additive signal term a·(T−t) and a running inventory penalty −φ·q·(T−t)", and Cont, Kukanov and Stoikov's The Price Impact of Order Book Events (Journal of Financial Econometrics 12(1):47–88) for "the signed order-flow-imbalance (OFI) variable e_n built from best-quote queue changes".

The two signals

Book imbalance. book_imbalance is the "normalized queue lean at the touch": (bid_sz − ask_sz) / (bid_sz + ask_sz), "+1 all bid, −1 all ask, 0 balanced". A non-positive denominator returns 0.0, "which also guards the division". It is read from the top level of the same book view the maker prices from, so an own-filtered view feeds an own-filtered imbalance.

Order-flow imbalance. OfiTracker is "an EWMA-decayed rolling sum of the signed best-quote queue change e_n per book update". With the previous best (b', bs', a', as'):

e_bid = (bid >= b' ? bid_sz : 0) − (bid <= b' ? bs' : 0)
e_ask = (ask <= a' ? ask_sz : 0) − (ask >= a' ? as' : 0)
e_n   = e_bid − e_ask

"A bid stepping up or growing adds size (buy pressure, e_n > 0); a bid stepping down or an ask stepping down/growing subtracts (sell pressure, e_n < 0)." The first update has no previous best and only seeds state. Then ofi = ofi·decay + e_n, the decay "in [0, 1): 0 = memoryless, only the latest e_n; near 1 = a long-memory sum." The default ofi_decay on AsParams is 0.9, and "a live re-tune preserves the warm tracker (like σ̂²), so this is inert on a re-tune."

combine_alpha joins them linearly, beta·imbalance + lambda·ofi, "so the caller tunes each channel independently (either weight 0 drops that channel)."

The shift

cj_reservation_shift returns alpha_signal · horizon − phi · q_norm · horizon. "Positive alpha RAISES the reservation (lean into predicted up-drift); long inventory (q_norm > 0) with phi > 0 LOWERS it (lean to unwind). horizon = 0 (terminal) ⇒ both terms vanish."

Where it is applied is AsState::price in crates/vike-mm/src/avellaneda.rs, on the Group-B override path described on Avellaneda–Stoikov. Two details of that call site matter for tuning. The OFI tracker is advanced "ONLY when λ is active, so an imbalance-only (or penalty-only) maker never pays for it." And the horizon passed is 1.0: "for a continuously-requoting live maker the CJ time horizon is folded into the gains (β/λ/φ), so the normalized unit horizon h = 1.0 is used rather than a wall-clock (T−t)." The three weights are therefore per-tick gains in price units, not rates.

The knobs on AsParams: alpha_beta_imbalance (0.0 default — "positive β leans the reservation into the heavier book side"), alpha_lambda_ofi (0.0 default — "the OFI channel is off AND the OFI tracker is never advanced ⇒ byte-identical + free"), ofi_decay, and running_penalty_phi (0.0 default — "an ADDITIONAL, horizon-scaled pull" on top of the A-S γ·V skew, which is unchanged). All at their defaults, price takes the verbatim fast path and never constructs the shift at all.

The toxicity fallback

ofi_toxicity synthesizes "a flow-toxicity INTENSITY in [0, 1] from an order-flow-imbalance magnitude — the internal fallback the SpreadMaker feeds its flow-toxicity guard when no external Strategy::on_flow reading exists": tox = 1 − exp(−|ofi| / scale), "reaching ~0.63 (1 − e⁻¹) at |ofi| = scale", scale <= 0 disabling it. It "depends on |ofi| only, so it is symmetric in the sign of the OFI (the CALLER derives the toxic SIDE from that sign)."

The caller is stage (3c-i) of SpreadMaker::requote in crates/vike-mm/src/quote.rs. Its side mapping: "positive OFI = buy pressure ⇒ aggressive BUY takers lift our ASK ⇒ the ASK is the toxic/adverse side"; negative OFI marks the bid. The guard then widens the toxic side by tox · widen · half and cuts its size by (1 − tox · size_cut).max(0), "the size-to-zero case routing through the maker's existing per-side suppression PULL path."

Three conditions, and an external reading wins for good

The synthesis runs only when a ToxicityParams guard is configured, ofi_toxicity_scale != 0, and "NO external on_flow reading has ever arrived". The flow_external latch on SpreadMaker is "set once Strategy::on_flow ever fires and never cleared, so an external toxicity feed takes PRECEDENCE permanently … and never clobbers the real reading." And because the tracker only advances while alpha_lambda_ofi != 0, the AsParams::ofi_toxicity_scale doc notes that "a live synthesized signal also requires that channel on."

From a registry table the guard is built when either toxicity_widen or toxicity_size_cut is present, and ofi_toxicity_scale "pairs with" them "so the internally-synthesized OFI reading can actually fire from a registry table — no external on_flow feed exists in a backtest."

The standalone depth-imbalance study in the order-flow tree is a separate implementation — "pure functions over &L2Book + ImbalanceTracker, a small streaming wrapper that recomputes on every book update"; the signal here is computed inside the maker from the view it is about to price.

On this page