Liquidity rewards
Reward-aware quoting for a venue that pays for resting liquidity near the midpoint — the pure scoring model, the band clamp and min-order-age hold that fold it into the quote, and the two safety authorities that outrank it.
crates/vike-mm/src/reward.rs is two things in one file: the pure reward model — how a
sampling-reward program scores a resting order — and the
spread maker's fold that uses that shape to place its
quote. It is net-new Rust with no Python twin — the header says parity here means self-consistent
reference-value pinning and neutral reduction, not oracle bits — and every fold is a naive f64
fold with no mul_add, per the crate rule.
The model
The header describes the shape it models — Polymarket's sampling reward. A rewardable market pays a proportional share of a per-market daily pool for resting liquidity near the midpoint, sampled per minute. Per order the closeness score is
S(v, s) = ((v − s) / v)²where v is the market's max_spread from the midpoint in cents and s the order's spread from
the size-cutoff-adjusted midpoint in cents. The score is quadratic — closer to the mid earns
superlinearly more — and an order counts only when it is within the band (s ≤ v) and at least
min_size shares. The per-side sum Σ S·size is combined two-sidedly: with the midpoint inside
[0.10, 0.90] the market scores max(min(Q_one, Q_two), max(Q_one, Q_two) / c) with c = 3, so
quoting one side alone earns a third of it; strictly outside that range it scores
min(Q_one, Q_two) — both sides required, a missing side scores nothing. The absolute payout is
irrelevant to the maker; only this shape drives its quote.
The three constants are public — REWARD_MIDPOINT_LO (0.10), REWARD_MIDPOINT_HI (0.90) and
REWARD_SINGLE_SIDED_DIVISOR (3.0) — and so are the three scoring functions spread_score,
side_score and q_min. The crate root explains why the module is pub: a Polymarket mount or an
analysis can compute expected reward with them, while the maker's own fold helpers stay
crate-private. spread_score returns 1.0 at the mid, 0.0 at the band edge and zero outside it,
for a negative spread or for a non-positive band; the tests pin that the same one-cent tightening
is worth more near the mid than near the edge.
The fold
clamp_into_band(mid, bid_px, ask_px, params) pulls a candidate two-sided quote into the band.
With band = max_spread_cents / 100 in 0..1 price units, each side's spread from the mid is capped
at s_target = band · (1 − weight). The doc states the two properties that matter: the maker never
quotes wider than the Avellaneda–Stoikov candidate wanted — a tighter A-S side is honoured — but a
too-wide side is pulled in to at most s_target, which is at most the band, so the side always
qualifies. Because it never widens a side, it can only ever raise the reward score. A larger
weight means a tighter cap, closer to the mid, more reward and more adverse selection: weight
is the reward-versus-A-S dial. The pulled price is deliberately left un-snapped — the A-S
grid-snap and wall-clamp already ran upstream and the venue mount re-snaps. Off (weight <= 0, or
a non-positive band) returns the input unchanged bit-for-bit, the byte-identical default.
moas_holds is the min-order-age gate. It answers true — skip the re-price, let the quote keep
ageing toward the reward floor — only while reward chasing is active with a positive
min_order_age_ms, the side has rested less than that, and the resting quote is still safely
in-band around the mid (spread in [0, band]). Out-of-band or crossed returns false, so a safety
re-price always fires. It is never consulted on a place, pull or fill path; quote.rs's
reward_moas_holds also answers false on a fill-stale side, and it composes with the
refresh tolerance — either may skip — rather than
replacing it.
Where it sits in the tick
crates/vike-mm/src/quote.rs's requote runs the fold at its stage (2b), immediately after
pricing. Two details there are load-bearing. The band is scored off the book midpoint, computed
from the touch, not off the A-S reservation the candidate is centred on. And the fold is skipped
entirely inside the A-S near-resolution blackout: inside it the maker quotes at the walls, and
reward-clamping it back into the band would farm rewards straight into the resolution vol spike.
Stage (3b) then floors each reward-active side's size up to min_size — never down — after the
inventory skew has shaped it, and that floored base flows
into rung 0 of a ladder.
Two safety authorities outrank the fold
The header names them: the A-S blackout and the fill-rate breaker. The caller skips the fold inside
a blackout, and a suppressed side is pulled regardless of any reward hold. A rewards-shaped quote
never quotes through a blackout to farm rewards. The behaviour suite,
crates/vike-mm/src/tests/liquidity_rewards.rs, tests exactly that pair alongside the band clamp
and the age hold.
The knobs
crates/vike-model/src/strategy/maker_params.rs's RewardParams is nested as
Option<RewardParams> in the maker's params; None, and any weight <= 0, is off. weight is
the intensity in [0, 1], clamped outside it. max_spread_cents is the band half-width in cents —
3.0 is ±3¢, ±0.03 on a 0..1 market — and a non-positive value is off. min_size is the
eligibility floor in shares; 0.0 imposes none. min_order_age_ms is the program's minimum order
age, Polymarket's being about 30_000; a non-positive value disables the age gate.
RewardParams::default is a Polymarket-shaped starting point that is still off — weight 0.0, 3.0 cents, no size floor, 30_000 ms — so a Some(RewardParams::default()) is inert until
weight is raised. SpreadMaker::with_liquidity_rewards is the builder.
The type's doc draws the layering line: this is the strategy half. A
Polymarket mount parses its live market config — max_spread,
min_size, min_order_age/moas — and feeds it here as plain parameters, so vike-mm needs no
venue dependency. The rest of quoting a bounded [0,1] token — the LMSR fair value and the spread
sources — is on binary outcomes, and the resolution-side
mechanics on settlement.
Taker flatten
The Guilbaud–Pham impulse-control leg — a pure decision that crosses the book with a market order when inventory breaches a hard band, cutting only the excess over the band.
Settlement
The resolution-aware risk heuristics for a binary market — a linear force-flatten schedule that shifts the reservation toward flat as time runs out, and a time-accelerated pull that trips the fill-rate breaker sooner.