vike
Market making

Refresh tolerance

The pure "is this re-quote worth the wire?" predicate — a relative basis-point band on the resting price and size that skips a modify, gates only the re-price path, and is off by default.

crates/vike-mm/src/refresh.rs's within_tolerance is the anti-churn gate of the spread maker. Given what one side already has resting — a (price, size) pair — and the target the current tick just computed, it answers "close enough to leave alone". A true means the caller skips the modify and leaves the resting order, and its venue queue position, untouched.

Why it exists

The crate header (crates/vike-mm/src/lib.rs) states the cost it removes: without the gate the maker emits two venue modifies per book delta forever, which on a rate-limited CLOB reached over a long-haul link is its biggest wire cost. SpreadMaker::from_params adds the second reason in its reader comment — a quote that re-prices every tick forfeits its FIFO slot, so the tolerance is load-bearing for a queue-position fill backtest: a quote that never holds its place can never be hit by a taker trade.

The contract

Both axes are relative, measured in basis points of the resting value, held in crates/vike-model/src/strategy/maker_params.rs's RefreshTolerance (price_bps, size_bps). The header's reason for that unit is that it reads sanely on both a 0..1-priced prediction market and a five-figure crypto book without the strategy knowing either venue's tick grid. A skip needs both axes inside tolerance:

  • price: |target_px − resting_px| ≤ price_bps · 1e-4 · |resting_px|
  • size: |target_sz − resting_sz| ≤ size_bps · 1e-4 · |resting_sz|

The per-axis helper drift_within carries the edge rules. An exactly unchanged value always passes, whatever the threshold — there is nothing to send. A non-positive tol_bps therefore means "no tolerance on this axis": only exact equality passes it. A zero or non-finite resting reference has no relative scale to measure against, so it never passes, and NaN anywhere never skips — the conservative direction is to re-price rather than silently strand a stale quote. The comparison is multiplied through (delta · 10_000 ≤ tol_bps · scale) rather than divided: no reciprocal rounding, and NaN falls out as false. The in-file tests pin the boundary as inclusive — 25 bp of 100 is 0.25, and a target of 100.25 is inside.

Off is the default. A None tolerance never reaches the module at all, and an all-zero bag — including RefreshTolerance::default — returns false from every call, so an unconfigured maker re-prices on every tick exactly as it did before the feature, byte-identical.

Where it may and may not be consulted

Only the re-price path is gated

crates/vike-mm/src/quote.rs's refresh_skips is reachable only from the modify arm of requote_single_side. Placing a new quote and pulling a resting one — the fill-rate breaker's suppression cancel — are never tolerance-gated: a quote that must come off the book always comes off, whatever the tolerance is set to — crates/vike-mm/src/refresh.rs's header states it in those words, paired with the other invariant the module keeps: a fully-inert bag must stay byte-identical to the ungated maker.

There is a second refusal before the arithmetic runs. refresh_skips answers false immediately when the side's snapshot is stale — SideState::refresh_stale, set by Strategy::on_fill whenever a fill lands on that side. The reason is that SideState::own is the maker's intended quote, not the venue-side remainder: after a partial fill the venue holds less than the intended size, so an intended-versus-target comparison would read "no change" and the size top-up would be silently lost. The flag forces the next tick through the modify arm exactly once, which re-issues the full configured size. on_fill sets it before the breaker's early return, deliberately, because it is independent of the breaker; and it is inert while the gate is off, since it is "read ONLY by refresh_skips/reward_moas_holds (and the ladder's per-rung twin), which short-circuit to 're-quote'". The ladder is that twin: requote_ladder_side reads the flag directly rather than through either helper, and applies the same predicate per rung, with the same stale short-circuit and the same rule that a pull is never gated.

Tuning it against the edge

RefreshTolerance's doc carries the ceiling. The tolerance is a fraction of the resting price, while the edge the maker is paid for is half_spread away from mid; if price_bps · 1e-4 · price approaches or exceeds half_spread, a market move that stays inside the tolerance — and therefore sends nothing — can leave the resting quote at or through the new mid, quoting at zero or negative edge and inviting exactly the adverse fill the spread exists to price. The doc's useful ceiling is a small fraction, say a tenth, of half_spread / price · 1e4 basis points; past that the gate is no longer only suppressing churn, it is silently widening the maker's fill risk.

SpreadMaker::with_refresh_tolerance adds the size axis's interaction with the inventory skew. A size_bps of 0.0 demands bit-exact size equality, so it only ever fires on a fixed-size maker; with skew active the sizes are recomputed from a continuously varying position every tick, the size axis would essentially never compare equal, and the gate would never fire — a non-zero size_bps is effectively required there, sized to swallow the inventory jitter and still re-quote on a real re-size.

On a reward-bearing venue the tolerance also protects reward eligibility. The same doc quotes the measurement: Polymarket's rewards program enforces a minimum order age of 30 s (moas: 30 on GET /clob-markets/{condition_id}), so an order re-quoted more often than that scores zero. The liquidity-rewards fold adds its own min-order-age hold, reward_moas_holds, which composes with this gate — either may skip — rather than replacing it.

The TOML keys are refresh_price_bps and refresh_size_bps, both defaulting to 0.0 (off) in SpreadMaker::from_params. The cross-exchange maker exposes the same bag through XemmMaker::with_refresh_tolerance, with the same rule: consulted only on the re-price path. The behaviour suite is crates/vike-mm/src/tests/refresh_tolerance.rs — sub-tolerance drift skips the modify, supra-tolerance re-prices in place, a fill always forces the size top-up, and a pull is never tolerance-gated.

On this page