vike
Market making

Multi-asset

The correlation-aware Guéant core — a covariance-matrix inventory skew whose value is entirely in the off-diagonal terms — and the fact that no live path calls it yet.

crates/vike-mm/src/multi_asset.rs is the "multi-asset (correlation-aware) Guéant market-making pure core — the covariance-matrix generalization of the single-asset A-S/GLFT inventory penalty". It ports the Bergault–Guéant result (Guéant, Optimal market making; Bergault and Guéant, Size matters for OTC market makers, arXiv 1606.01862 and 1810.04383): "for a maker holding an inventory VECTOR q across N correlated assets with a returns covariance matrix Σ (per-ms σ², symmetric PSD) and risk aversion γ, the reservation-price skew for asset i is the i-th component of the matrix-vector product".

skew = γ · (Σ · q)          →     skewᵢ = γ · Σⱼ Σ[i][j]·q[j]
rᵢ   = mᵢ − skewᵢ

Built, tested, and called by nothing on a live path

crates/vike-mm/src/lib.rs declares the module under #[allow(dead_code)]: it was "written ahead of the live multi-symbol maker mount (a DEFERRED follow-up), so nothing on a live path calls it yet; #[allow(dead_code)] keeps the merge-gate clippy green until the mount wires it in." Every statement below is about a pure function with in-file tests, not about a maker you can mount.

Why a matrix

The header's argument: "This is the exact generalization of the single-asset reservation term r = s − q·γ·V: the SCALAR variance penalty q·γ·V becomes the VECTOR penalty γ·(Σ·q). On a DIAGONAL Σ (uncorrelated assets) it collapses back to N independent single-asset makers — skewᵢ = γ·Σ[i][i]·q[i] — so the value of the matrix form is entirely in the OFF-DIAGONAL terms: a position in a CORRELATED asset j leans asset i's reservation too. Holding a long BTC-perp inventory therefore already skews the ETH-perp quote down, hedging the correlated exposure a fleet of independent makers would each ignore — that cross-asset lean is the whole reason to run one correlated maker over N separate ones."

The single-asset term it generalizes is crates/vike-mm/src/avellaneda.rs's as_reservation_price, on the Avellaneda–Stoikov page.

The two functions

multi_asset_skew(inventory, cov, gamma) returns the vector γ · (Σ · q); element i is "gamma · Σⱼ cov[i][j]·inventory[j] (naive left-to-right fold, no mul_add)". Its guard is deliberate: "a DIM MISMATCH — cov not square-of-side inventory.len() … returns a ZERO vector of length inventory.len() (the inert, no-skew answer), rather than panicking or silently reading a ragged matrix." An empty inventory returns an empty vector.

multi_asset_reservation(mids, inventory, cov, gamma) returns mids[i] − skew[i], "the multi-asset twin of as_reservation_price's s − q_norm·γ·V. The shared quote assembly would post each side around mids[i] − skewᵢ." On a mids/inventory length disagreement it falls back to the raw mids — the same inert answer, one level up.

The dense matrix math is hand-rolled: "no linear-algebra crate — the matrices are tiny (N assets a maker quotes, single digits), so a plain nested naive fold is both simplest and keeps the workspace dependency/cargo deny surface flat." Two things are the caller's job, not this module's: "Σ symmetry/PSD is the CALLER's contract (a covariance estimator's job), NOT enforced here."

What the tests pin

  • diagonal_cov_reduces_to_independent_single_asset: on a diagonal Σ each skewᵢ equals γ·cov[i][i]·q[i], and the reservation matches as_reservation_price per asset "bit-for-parity".
  • positive_off_diagonal_leans_correlated_asset: a long position in A with B flat and a positive cov[1][0] gives B a positive skew, so "B's reservation mid_B − skew_B is pulled DOWN even with zero B inventory: the cross lean."
  • negative_off_diagonal_flips_the_lean: a negative off-diagonal leans a flat B up.
  • hand_computed_2x2_correlated_example: with γ = 0.5, q = [2, −3] and Σ = [[2, 1], [1, 2]], the skew is [0.5, −2.0] and mids [100, 50] become [99.5, 52.0].
  • dim_mismatch_returns_zeros: wrong row count, a ragged row, and a mids/inventory mismatch all take the inert path.

The read a mount will need

The single-symbol seam cannot supply an inventory vector. crates/vike-model/src/strategy/mod.rs's MultiHftBroker is "the MULTI-SYMBOL extension of HftBroker: the ONE extra read a correlation-aware market maker needs that the single-symbol HftBroker cannot express — the signed net inventory of a NAMED symbol", position_of(symbol), "so a maker quoting N correlated instruments can read the WHOLE inventory vector q its covariance-matrix skew (γ·Σ·q, see vike_mm::multi_asset) folds over." Because MultiHftBroker: HftBroker, a broker that answers position_of can still mount any single-symbol maker, "and a plain single-symbol engine simply cannot mount a maker that needs the multi read (the type system documents it)."

That trait exists; the maker that consumes it, and the covariance estimator that would own Σ, are the deferred follow-up the module was written ahead of. The index lists what is mounted today.

On this page