TremorDocs
Docs menu
Market mechanics

The two-sided quote

Inventory skew, projected variance, the bid/ask band and integral fill pricing.

Every market quotes two executable prices: an ask a buyer pays and a bid a holder can sell into before expiry. Both come from one number — the variance the market currently projects for the whole window — with a spread around it and the cap over it.

Nothing here is implied volatility. There is no option surface anywhere in Tremor; the quote is one market's price, and the app labels it market quote volatility for exactly that reason.

Inventory skew

The market keeps one signed number per series: skew, the accumulated price impact of net inventory sold. Buying raises it, exiting lowers it, and it decays back toward zero on the series' half-life.

decayedSkew = skew · 2^(−(now − lastTs) / halfLife)         (halfLife = 0 → never decays)
forward     = clamp(anchorVariance + decayedSkew, 0, capVariance)

Integer division truncates toward zero, so decay can only ever shrink the magnitude — it can never flip the sign or amplify.

Projection

The forward variance only applies to the part of the window that has not happened yet. What has happened is measured, not quoted:

elapsed   = checkpointedThrough − start
remaining = expiry − checkpointedThrough
projected = (realizedSoFar · elapsed + forward · remaining) / (elapsed + remaining)

Before the window opens the projection is exactly the forward variance; at expiry it is exactly the realized variance. This is why a market's quote converges on what actually happened rather than staying at the writer's opinion.

projected is deliberately not clamped to the cap. It is a measurement, and clamping belongs to the price.

The band

askVariance = min(ceil(projected · (1 + s)), cap)
bidVariance = min(floor(projected · (1 − s)), cap)          s = halfSpreadBps / 10,000

Per-unit prices are floor(unitNotional · variance / 1e18). The clamp to the cap matters: without it a market could quote an ask above what the receipt can ever pay, and the engine would have to refuse a fill it had just advertised.

Integral fill pricing

Selling u units raises the skew by impactPerUnit · u / 1e18, which reaches the ask through the projection weight remaining / duration and the ask multiplier (1 + s). Both are affine, so the marginal ask is affine in u with slope

askSlope = ceil(impactPerUnit · remaining · (1 + s) / duration)
bidSlope = floor(impactPerUnit · remaining · (1 − s) / duration)

and a fill is priced by the integral of that marginal price, not by the opening quote:

premium(u)  = ceil( unitNotional · (askVariance·u + ceil(askSlope·u²/2e18)) / 1e36 )
proceeds(u) = floor( unitNotional · (bidVariance·u − floor(bidSlope·u²/2e18)) / 1e36 )

This is what makes splitting a fill pointless. Buying u₁ then u₂ costs the same as buying u₁ + u₂ in one go, up to the two ceilings — and where the ceilings differ, they differ against the splitter.

Exact-in

Given amountIn USDC, with X = amountIn · 1e36 / unitNotional, the engine inverts the integral in a cancellation-free form:

u = 2X / (askVariance + ceil√(askVariance² + 2·askSlope·X/1e18))

which degenerates to X / askVariance when the slope is zero. The root rounds up, so a taker never receives more units than the exact real-valued solution.

Clamps

An ISSUE fill is the minimum of four limits, and a partial fill re-prices the amount that actually filled:

ClampWhy
Inventory still shipped on the ISSUE legYou cannot sell what was not minted
units to capThe marginal ask may not cross the cap
units to collateralThe vault's free collateral has to cover the new reservation at the cap
The buyer's amountInExact-in

An EXIT fill is clamped by the units actually outstanding, by the point where the falling bid would reach zero, by the collateral the burn releases, and by the leg's Aqua balance.

Quote and swap agree

The engine runs identical arithmetic in both directions. The only difference is that a swap writes state — the new skew, the new reservation — and a quote does not, guarded on SwapVM's isStaticContext. So quote and swap cannot disagree within a block, and the number in the ticket is the number you sign for.

Where the numbers come from

The Lens exposes the whole quote in one call: marketVariance, projectedVariance, realizedVarianceSoFar, bidVariance, askVariance, bidPerUnit, askPerUnit, maxPayoutPerUnit. The series page's chart plots realized volatility against market-quote volatility with the executable band behind them; the historical path is reconstructed by the backend from indexed fills and checkpoints, and the live point always comes from the chain.

web/src/lib/series.test.ts checks the frontend's replica of these formulas against the same 60-digit reference vectors the Solidity library is tested with, so a figure on a screen and a fill in a block agree to the last base unit.