Skip to content

predict: probability-weighted inventory charge (DBU-732) - #1260

Closed
tonylee08 wants to merge 10 commits into
mainfrom
at/dbu-732-weighted-spread
Closed

tonylee08 wants to merge 10 commits into
mainfrom
at/dbu-732-weighted-spread

Conversation

@tonylee08

@tonylee08 tonylee08 commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Wires the probability-weighted inventory charge end to end: mints and closes are charged or rebated on the change in rate * D(W), where D(W) is the standard deviation of the pool's payout profile under a probability measure frozen at the market's first mint.
  • The payout index stores each boundary's frozen weight and carries mass-weighted first moments per subtree, so a trade's range read is O(log n) — the weighted analog of predict: add a tick-weighted first moment to the payout tree (DBU-732) #1248's tick-weighted moment, with the frozen survival function replacing the tick coordinate, which is also what makes unbounded legs finite without a measurement window.
  • Collected charges are escrowed in an isolated skew_reserve, rebates are paid only from it, and cumulative collections equal the current potential exactly; settlement releases the residual. inventory_skew_rate ships at 0 and is snapshotted per market.
D(W) = sqrt( sum q(S)*W(S)^2  -  ( sum q(S)*W(S) )^2 )

Why

This is the windowless alternative to the #1248 + #1250 pair: the same charge-on-deviation mechanism with one substitution — each settlement price counts by its frozen probability instead of equally. That substitution deletes the measurement window and everything the window owned: the fraction to size, the tenor scaling, the freeze on reference-tick landing, the mint gate behind it, and the zero-tick rounding edge. A price the market will almost certainly never reach carries almost no weight, so nothing has to be excluded.

The measure freezes from the first mint's own validated pricer, atomically with that mint. The book is empty at the freeze by construction, so every boundary the index ever creates carries a weight from one fixed surface; there is no admin step, no keeper, and no market-launch ordering constraint. Freezing is what makes the charge a state function: cycles telescope exactly and a round trip refunds to the unit, which the flow tests assert bit-for-bit through custody, across an oracle move.

Linear / Context

Key decisions

  • Raw accumulators, no division in the fold. The totals carry payout * mass products at the 1e9 scale, with division deferred to the read. The earlier statistic-only commit divided inside the fold, which floored differently on open and close and ratcheted residue upward — fifty open-close cycles at a non-dividing payout/mass pair left an empty book scoring 273,861. Raw carriage makes the round trip exact by construction; a ragged-pair regression pins it.
  • Freeze at first mint, not at creation or by admin step. Market creation has no pricer in hand; a separate initialization step (as in predict: add frozen-grid inventory impact #1252) creates an ordering constraint and an empty-book requirement. The first mint always has a validated pricer and an empty book, and the quote-vs-mutation consistency is structural: an unfrozen quote prices from the same live surface its mint installs.
  • Escrow senior to fee revenue on the close path. A close can be charged (closing a leg of a complete set unbalances the book); the charge is the first claim on the payout and the fee is clamped against the remainder, so deep out-of-the-money closes stay closable. A close whose charge exceeds everything it releases aborts rather than under-collecting — predeploy/response-policies.md RP-29 records the trigger, blast radius, and why clamping is worse.
  • Boundary weights live in the index nodes. Rotations and resummaries never evaluate a surface; a reused boundary must arrive with the weight it stores, and a mismatch aborts. Zero-rate markets pass zero weights and never read them.

Scope / Descoped

  • In scope: the statistic, the index moments and range read, the frozen surface, quote/commit wiring, mint and close custody, config plumbing with bounds and their relational guards, events, docs, and unit/flow coverage.
  • Descoped: any per-market admin rate path — the rate is template-snapshotted only, like the occupancy rate.

Measurement caveat

The earlier draft body leaned on rank-correlation-with-dispersion columns (0.983 / 0.951). An independent re-derivation reproduced those numbers exactly but showed the dispersion target rewards functional form: a control with the same shape and one constant volatility per cadence — no surface at all — scores 0.9948, and the target carries no per-market volatility variation for a measure to be credited with tracking. On the one metric grounded in realised loss at the single actual settlement price, this measure scores 0.4330 against 0.4488 for #1252 and 0.3918 for the windowed form. The case for this design is therefore not the dispersion columns: it is that it beats the windowed form on the clean metric while deleting the window's defect class, at a per-mint marginal of ~209 instruction units against the windowed form's ~294 (rate-zero baseline 179 against main's 175), with ~30 units attributable to the charge itself. On a rebuilt dispersion benchmark whose target varies per market, the measure's surface holds while every no-information control falls, so its rank quality survives the control that invalidated the original benchmark.

Tests

  • sui move test --path packages/predict --gas-limit 100000000000 (CI-pinned toolchain): 536 passed, 0 failed, on the branch merged with current main (referral fees, config-history events, settlement fallback, and the calibrated default economics all composed and re-verified; the skew flow tests pin their own fee floor, so they are default-independent).
  • New: inventory_weighted_tests (raw-unit arithmetic, ragged round trip, ratchet-free cycles, stacked drains), strike_payout_tree_weighted_sum_tests (hand-derived per-position identities, sentinel legs, rebalanced/drained books, weight-mismatch and inversion aborts), inventory_skew_flow_tests (custody end to end: charge into escrow, complete-set rebate exact, round-trip refund across an oracle move, close-side charge and drain, two half-closes draining like one full close, skew and inventory-impact escrows stacking in isolation, settlement release, zero-rate inertness, and the RP-29 abort reached via a worthless-leg close), plus config bounds, the rate/fee-floor relational guards, the skew-escrow guard's abort test, and an exact end-to-end magnitude pin: for one ATM digital the deviation is flat in the frozen mass to first order, so the scipy-derived reference up price pins the quoted charge at exactly 2,499,999 across the whole reference budget — closing the seam between the frozen surface, the mass, and the rate that relational tests alone cannot see.
  • The accumulator, read, and escrow identities are additionally pinned off-chain by an exact-integer reference simulation over randomized adversarial books (interleaved partial opens/closes, flat-stretch and cliff weight profiles): committed accumulators match from-scratch recomputation bit for bit, the escrow equals the potential after every operation, and the deviation matches the exact rational standard deviation.

Risk

Ships inert (inventory_skew_rate = 0): no market charges, freezes, or evaluates a weight until an admin sets a template rate, and existing markets never change. The mechanism's custody is isolated in its own reserve with the backing invariant asserted after every cash-mutating flow.

🤖 Generated with Claude Code

tonylee08 and others added 2 commits August 21, 2026 12:17
Adds the arithmetic for an inventory charge on the spread of the pool's payout
book, counting each settlement price by how likely it is rather than equally:

    D(W) = sqrt( sum q(S)*W(S)^2 - ( sum q(S)*W(S) )^2 )

Nothing calls it. The module owns the statistic and its fold; the accumulators
and the range reads they need are a separate change.

Weighting by probability is what removes the window. Counting every tick equally
over an unbounded ladder is meaningless, so the unweighted form has to bound it,
and then owns a parameter that has to be sized, frozen in the right place, kept
above one tick of resolution, and re-derived whenever it changes because the
charge scales with the window's width. A price the market will almost certainly
never reach contributes almost nothing here without being excluded, so none of
that exists.

Both totals are plain sums with no division, so the fold is exact: a range opened
and closed returns them bit for bit, and splitting a trade into pieces reaches
the same totals as making it whole. Both are asserted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…BU-732)

The statistic module now carries its totals raw — payout times 1e9-scaled
mass, division deferred to the read — so a fold is integer sums and products
with no division at all. The previous form divided inside the fold and
floored differently on open and close: fifty open-close cycles at a payout
and mass whose product does not divide left an empty book scoring 273,861.
Raw carriage makes the round trip exact by construction, and the ragged
regression pins it.

The payout index stores each boundary's frozen weight and carries
mass-weighted first moments per subtree, so range_weighted_payout_sum
resolves a range's sum(q*W) in O(log n) by the same two reads
range_max_payout performs — the weighted analog of a tick-weighted first
moment, with the frozen survival function replacing the tick coordinate,
which is also what makes unbounded legs finite.

The measure freezes from the first mint's own validated pricer, atomically
with that mint: the book is empty at the freeze by construction, no admin
step or keeper exists, and a market whose snapshotted rate is zero never
evaluates a weight. Charges and rebates are differences of the floored
potential rate * D(W), so collections telescope to the current potential
exactly; the escrow is isolated in skew_reserve, senior to fee revenue on
the close path (RP-29 owns that ordering), and released at settlement.

Flow tests drive the custody end to end: a complete set's second leg is
rebated the first leg's charge exactly, a round trip across an oracle move
refunds to the unit, a close that unbalances a flat book is charged through
the clamp chain and the drain refunds it, and settlement releases the
residual.
@tonylee08 tonylee08 changed the title predict: add the probability-weighted inventory statistic (DBU-732) predict: probability-weighted inventory charge (DBU-732) Aug 21, 2026
…pread

# Conflicts:
#	packages/predict/docs/concepts/fees-and-rebates.md
#	packages/predict/docs/design/decisions.md
#	packages/predict/sources/config/config_constants.move
#	packages/predict/sources/events/order_events.move
…-732)

Ports the reaching test for ESkewChargeExceedsCloseProceeds — a complete set
plus a deep out-of-the-money spot makes the closed leg worthless, so its skew
charge exceeds everything the close releases — closing the register's open
pinning gap. Adds partial-close coverage (two half-closes drain the escrow
like one full close, with the invariant held at the midpoint) and a
stacked-charges flow proving skew and inventory impact keep isolated escrows
under one mint.
…d (DBU-732)

The skew reserve guard gets the mirror of the impact reserve's abort test, and
both escrows are pinned folding into required cash and out of free cash
together. The concentrating-mint flow now asserts the exact charge — for one
ATM digital the deviation is flat in the frozen mass to first order, so the
scipy-derived reference up price pins rate * D at exactly 2,499,999 across the
whole reference budget, closing the seam between the frozen surface, the mass,
and the rate that relational tests alone could not see.

Drops the unused SkewTerms getter, trims SkewAdjustment and SkewTerms to
copy + drop (nothing stores them), and adds the skew leg to the fee-routing
table and diagram. C-1 carries a note that the payout node grew and a
skew-enabled trade costs two surface evaluations plus two weighted reads,
so the flush and batch bounds want re-measuring if skew ships enabled.
/// admin-tunable with a floor of zero: unlike the premium relation, whose both
/// sides are upgrade-required constants, this one is reachable through config.
public(package) fun snapshot(config: &StrikeExposureConfig): StrikeExposureConfig {
assert!(config.inventory_skew_rate <= 2 * config.min_fee, ESkewRateExceedsFeeFloor);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we assert this earlier, when inventory_skew_rate is being set? if this assertion is ever triggerable, then it bricks market creation, right?

OrderMinted's skew_charge and skew_rebate were asserted only at rate zero by
the referral mirrors; a charging mint now pins the whole event byte-for-byte
with the charge at its independently derived ATM magnitude, so the amount the
trader pays and the amount the event reports are the same number by test.
inventory_impact_charge: u64,
/// Inventory-skew amounts for this mint; at most one is nonzero. A rebate
/// reduces the withdrawal rather than paying the trader.
skew_charge: u64,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we rename to inventory_charge / inventory_rebate?

…batch (DBU-732)

Monitoring the skew fee needs no delta replay: OrderMinted and
LiveOrderRedeemed carry the post-trade skew_reserve, and MarketSettled carries
the released residual — every event that moves the escrow emits its post-state,
making the family the canonical stream per the single-owner-facts rule, sampled
after the transition. The mint-side event is pinned byte-exact with the reserve
at the independently derived ATM magnitude, and the template config-history
test now drives the skew setter so the emitted rate is asserted at a set value,
not its default.

The coverage batch closes the review gaps: the close-side charge and the
half-close midpoint get exact pins in place of range asserts (both derived by
hand from the m <-> S-m symmetry of the ATM mass); a re-mint after a full drain
repays the frozen ATM charge exactly, proving the measure survives an empty
book and an oracle move; a worthless flattening close collects its full rebate
with every fee clamped to the zero payout, pinning the rebated branch of the
close ordering; two markets prove isolated frozen measures and escrows; a
u64-max payout executes the width audit; the dust clamps in the fold and the
weighted read get direct coverage; zero-payout and zero-mass folds pin their
no-ops; the snapshot guard gets its pass-side boundary; and the three
structurally unreachable asserts now say so at the assert site.
… (DBU-732)

The relation was asserted only at snapshot, so a template holding an
individually-valid but jointly-bad pairing — a low fee floor set after a high
skew rate — would abort every market creation until an admin repaired it.
Both setters now refuse the pairing at write time, the template can never
hold it, and the snapshot assert becomes a structurally unreachable tripwire
at the moment the pairing freezes into a market. The snapshot-level test is
replaced by one per setter, covering the raising and the lowering side.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants