How this market works

A plain-language guide to trading on BMM — a prediction market where you bet on an unknown number, the price tells you the crowd's best estimate, and the market gets smarter every time someone trades. No maths required. If you'd like the formulas and the engine internals instead, flip the switch to Developer.

bet on a number price = the estimate you move the market paid out at the end

1What is this?#

A place to bet on “what number will it be?” — and a price that turns the whole crowd's opinion into a single, readable estimate.

Every market is about one unknown number that will be settled later — call it the outcome. It could be “the price of BTC at month-end”, “the winning vote share”, “Friday's high temperature”. Nobody knows it yet.

You don't trade against other people directly. You trade against the market maker — an automated counterparty that's always willing to take the other side of your bet at a price it quotes. That price reflects what the market currently believes about the outcome. When you think the maker's estimate is wrong, you trade — and if you're right, you profit when the outcome is revealed.

The whole idea in five lines
  1. The market holds a best guess about the outcome (a centre, and how unsure it is).
  2. It quotes a price for any bet — that price is its estimate.
  3. You buy if you think the real answer is higher/more likely, sell if lower/less likely.
  4. Your trade nudges the estimate toward your view — bigger trades nudge it more.
  5. When the outcome is known, every bet pays out based on what actually happened.

2The market's best guess#

At any moment the market summarises everything it “thinks” with two simple ideas:

The centre

Its single best guess for the outcome — the most likely value right now. If a market's centre is 100, the crowd's consensus is “around 100”.

The uncertainty

How sure it is. A narrow market is confident the answer is close to the centre; a wide one is still very unsure and could land far away.

Picture a bell-shaped hill of probability sitting over the number line: tall and narrow when the market is confident, low and spread-out when it isn't. As people trade, that hill slides toward the value informed traders are backing, and it gets taller and tighter as more information arrives. The market literally learns.

You never have to think about the shape of that hill to trade — but it's why the price moves the way it does, and why a brand-new market reacts more than a mature one.

3What you can bet on#

A market offers a few kinds of bet (“contracts”) on the same underlying outcome. You don't need any formulas — here's what each one means and when it pays:

BetIn plain wordsYou win when…
The number itselfDirect exposure to the value — buy it to bet the outcome ends up high (it pays out the final number)the outcome settles above what you paid (to bet on a fall, buy a Put instead)
Above a line (Call)“It'll finish above K” — and the more above, the more you makethe outcome ends above your line; payout grows with how far above
Below a line (Put)“It'll finish below Kthe outcome ends below your line; payout grows with how far below
Yes / No (Binary)A simple “will it clear K, yes or no?”the condition is true at the end — pays a fixed 1, else 0
In a range“It'll land between a and bthe outcome falls inside your range — pays 1, else 0
Close to a target“It'll be near c” — a precision betthe outcome is close to your target; the closer, the bigger the payout
Yes/No and range bets are just probabilities For these, the price is the market's estimated chance of the event. A price of 0.62 means “about a 62% chance”. Buy below what you think the real chance is, sell above it.

4Reading the price#

The price of a bet is simply its average payout under the market's current best guess — what it's worth if the market is right. That single number is the most useful thing on the screen:

  • For a Yes/No or range bet, the price is the probability — 0.30 means “≈30% likely”.
  • For an above/below bet, it's the expected payout — bigger when the line is easy to clear, near zero when it's far out of reach.
  • For the number-itself bet, it's just the market's centre — its best guess for the number.

Try it: drag the market's best guess and the line. The shaded area is the chance the outcome lands above the line — and that's exactly the Yes price.

“Will it finish above the line?”live
the shaded chance is the Yes price
the market's viewyour linechance above

5Making a trade#

Trading is two buttons: buy if you think the bet is underpriced, sell if you think it's overpriced (or to close something you hold). You choose a size, and the maker quotes you an exec price.

The spread is the cost of trading

The maker never trades at exactly the fair estimate. It charges a small margin: you buy a touch above the fair price and sell a touch below it. That gap is the spread — the maker's only profit, and effectively your trading fee. It gets a little wider when:

Your order is large

Big orders move the market more, so they cost a bit more per unit.

The maker is already loaded

If lots of people are on one side, the maker charges more to take even more of that risk.

The market is jumpy

More uncertainty → a wider spread, to protect the maker.

See it for yourself

This is a live market (best guess 100, betting on “above 100”). Pick a side and size and watch the price you'd get, the small spread you'd pay, and how your trade shifts the market's estimate:

Place a tradelive
real engine — same maths the server runs

“Sell all” in the real app liquidates every position you hold in a market at once. Because each sale nudges the price down a little, later units fill a touch lower than the first — the estimate you see before confirming is at current prices.

6You move the price#

This is the heart of a prediction market: your trade is information. When you buy, you're telling the market “I think this is more likely / higher than you do” — so the maker shifts its best guess toward you. Sellers push it the other way.

Bigger = stronger

A large order signals more conviction, so it moves the estimate more. A tiny one barely registers — the market treats it as noise.

New markets move most

An uncertain, young market shifts a lot per trade. A mature, confident one barely budges — it already “knows” a lot.

The crowd converges

As informed traders pile in, the price homes in on the true answer. The final price is the crowd's best collective estimate.

What this means for you If you trade big in a thin market, you'll push the price against yourself as you go — your last units fill at a worse price than your first. Sizing sensibly keeps your average price close to where you started.

7Your positions & profit#

When you buy, you open (or add to) a position. The app tracks your average entry price — the typical price you paid across all your buys.

  • Selling closes a position. In v1 you can only hold the “bought” side, so a sell reduces something you already hold and books your profit or loss against your average entry.
  • Profit = exit − entry. If your average entry was 4.00 and you sell at 5.20, you've made 1.20 per unit on the units you sold.
  • Unrealised vs realised. Before you sell, your profit is “on paper” and moves with the price. Once you sell (or the market settles), it's locked in.

You can hold until the market settles instead of selling — at settlement every position pays out automatically based on the real outcome (next section).

8When the market ends#

Eventually the true outcome becomes known and an administrator settles the market. Each open bet pays its payoff for the real outcome:

Your betWhat you're paid at the end
The number itselfthe outcome value itself, per unit
Above a line (Call)how far the outcome finished above your line (0 if it didn't)
Below a line (Put)how far it finished below your line (0 if it didn't)
Yes / No1 if the condition came true, 0 if not
In a range1 if the outcome landed in your range, 0 if not
Close to a targetmore the closer it landed; e.g. with a tolerance of 1000, being 500 off pays ≈0.88, being 2000 off pays ≈0.14

Your final profit is what you're paid minus what the position cost you. Range and “close-to” bets reward being accurate, not just directionally right — handy when you have a strong view on roughly where the answer will land.

9Funding a market — LPs & creators#

Someone has to put up the cash the maker pays out from. Those people are liquidity providers (LPs), and the person who opens a market is its first one.

You set the opening view

As creator you pick the starting best guess and how uncertain it is. A wider opening makes a more forgiving market that needs more trading to sharpen up.

You fund the reserve

You put up starting cash — the reserve — that backs the maker's worst-case payouts. This makes you the market's first liquidity provider.

You earn the spread

Every trade pays the maker a small spread. As an LP you own a share of the pool, so that income accrues to you — your stake grows as the market does business.

You carry the risk

In exchange, you're on the hook for payouts if the outcome goes against the maker's book. More reserve = the market can safely take bigger trades before it has to shrink them.

LPs hold shares of the pool, priced at the pool's value per share. Deposits buy in at the current value and withdrawals cash out at it — so nobody dilutes anyone. If the maker earns more in spread than it pays out, the pool's value rises and every LP's share is worth more.

Two ways to play Trade if you have a view on the outcome and want to bet on it. Provide liquidity if you'd rather earn the spread from others' trading and are comfortable carrying the market's risk in return.

10Risks & smart habits#

  • You can lose. If the outcome goes against your bet, your position can be worth far less than you paid — or nothing.
  • The spread is a real cost. You buy above and sell below fair, so a quick in-and-out round-trip loses the spread even if the price doesn't move. Trade when you have an edge, not for the sake of it.
  • Big orders move the price against you. Split large trades or accept that your average price will drift as you fill.
  • Orders can be partially filled. If a trade would push the maker past what its reserve can safely cover, it fills only as much as is safe and tells you. That protects the pool — and you — from a market that can't pay out.
  • Settlement timing is set by the market. Know when a market resolves and on what source before you put money in.
One sentence You're trading against an automated maker that prices every bet as its expected payout, charges a small spread, and learns from the order flow — so over time the price becomes the crowd's best estimate of the outcome, and your job is to spot when it's wrong.

11Capacity — when a market fills up#

Every market is backed by a pool of cash (the reserve you fund in §9). That cash is what pays winners when the market resolves, so a market can only take on as much betting as its cash can safely cover — its capacity. Think of it like an insurer: it can write only as many policies as its capital can pay claims on.

Here is the part that surprises people: buying is not just money flowing in. When you buy, you pay a small premium now — but the maker takes on the obligation to pay you a possibly much larger amount if your bet wins. A few cents in, potentially many dollars owed later. As more people pile onto the same side, the pool's worst-case bill grows far faster than the premiums it collects.

To guarantee winners always get paid, the maker holds back a reserve — enough cash to cover the worst realistic outcome — and only takes on new risk while it still has a comfortable buffer of cash above that reserve. When a market gets crowded on one side, the reserve climbs toward the cash, the buffer shrinks, and:

  • Buys get smaller, then pause. A new bet that would add risk is trimmed to what's safe (a partial fill) or, once the buffer is gone, declined — even though the market still looks open.
  • Selling and the other side still work. Closing a position, or betting the opposite way, reduces the maker's risk, so those are never blocked — and they free up capacity for everyone.

If you hit “this market won't let me buy,” it isn't a glitch — the market is simply full on that risk, like an insurer that has hit its book limit. Two things open it back up:

  • Add liquidity. Anyone can fund the pool (the Manage-liquidity page). More cash → more capacity → buying reopens.
  • Let it unwind. As holders sell or bet the other way, the reserve falls and capacity returns on its own.
Why it exists Without this limit, a market could keep selling cheap bets it can't actually pay out, and winners would be left short at settlement. Capacity is the rule that keeps the pool always able to pay — it protects you, not the house.

12Glossary#

TermWhat it means for you
OutcomeThe unknown number the market settles to.
Market makerThe automated counterparty you trade against; always quotes a price.
Best guess / centreThe market's current single estimate of the outcome.
Fair priceA bet's average payout under that best guess — the mid price before spread.
SpreadThe small margin you pay: buy above fair, sell below. The maker's profit.
PositionA bet you hold, tracked at your average entry price.
SettleThe market ends, the real outcome is set, and every position pays out.
Liquidity provider (LP)Someone who funds the pool and earns the spread while carrying its risk.
ReserveCash the maker holds back to cover worst-case payouts; caps trade size.
CapacityHow much betting a market can take on before its cash can't safely back more; when it's full, buying pauses until liquidity is added or positions unwind.
Partial fillWhen only part of your order can be safely taken, and the rest is declined.

Want to know exactly how the price, spread, learning and reserve are computed? Flip the switch at the top-left to Developer for the full mathematics — every formula, with worked examples and live plots.

BMM — The Mathematics

A complete, developer-facing tour of every formula behind this Bayesian Market Maker: how a contract is priced, how the market learns from each trade, how it stays solvent, and how it settles. Every plot on this page is computed live by a faithful browser port of packages/core, so what you drag is exactly what the server runs.

Gaussian belief closed-form pricing precision-weighted Bayes Monte-Carlo reserve numeric(20,8) money

0The big idea#

A prediction market that quotes a price for any claim about an unknown number, and gets smarter every time someone trades.

Every market is about a single unknown real number θ (theta) — the outcome that will eventually be observed: “BTC price at month-end”, “the vote share”, “the temperature on Friday”. Until settlement, nobody knows θ. The market maker (the “MM”) holds a belief about it: a probability distribution p(θ). The belief we build all the intuition on is a Gaussian \( \mathcal N(\mu,\sigma^2) \) — a bell curve with a centre μ (its best guess) and a width σ (how unsure it is). A market can also run one of four richer densities — fat-tailed, multi-camp, or a skewed / bimodal shape — chosen at creation; §2 lays out the full family of five. We start with the Gaussian because every other model reuses its machinery.

A contract is a bet that pays some function of the outcome, \( f_C(\theta) \). Its fair price is simply the average payoff under the current belief — what it’s worth if the belief is right. The MM quotes a little above fair to buyers and a little below to sellers (the spread), and that gap is its only source of profit.

The loop — this is the whole engine
  1. Quote. Given belief \(p\), compute fair price \( \mathbb E_p[f_C(\theta)] \) and a spread.
  2. Trade. A user buys or sells \(q\) units at the exec price (fair ± spread).
  3. Extract a signal. The trade reveals what the trader believes — decoded into a signal \(s\) with a reliability weight \(w\).
  4. Update. Bayes shifts the belief: \( \mu \to \mu' \), and \( \sigma \) tightens. The crowd’s information is now baked in.
  5. Re-price. The next quote reflects the new belief. Repeat.
  6. Stay solvent. Before committing, check the MM’s cash covers its worst-case liability (the reserve); shrink or reject the trade otherwise.
  7. Settle. The true \( \theta^\star \) is revealed; every position pays \( q\cdot f_C(\theta^\star) \).

Because the belief moves toward informed flow and tightens with volume, the market price is the crowd’s consensus density over θ — readable live. The rest of this document is each of those seven steps, made precise.

Where the code lives The deterministic math is the pure packages/core package (pricing.ts, spread.ts, signal.ts, bayes.ts, solvency.ts, gaussian.ts, numerics.ts, config.ts). The stateful orchestration (locks, transactions, ledger) lives in apps/api/src/services. This page mirrors the former.

1Core concepts & notation#

SymbolMeaningNotes
θThe outcome — the unknown real number the market resolves to“theta”; θ* is its final settled value
p(θ)The MM’s belief: a probability density over θone of five models (§2); Gaussian \( \mathcal N(\mu,\sigma^2) \) is the baseline
μ, σBelief mean and standard deviation; \( \sigma^2 \) is the variancecentre & width of the bell curve
C, f_C(θ)A contract and its settlement payoff functione.g. CALL pays \( \max(0,\theta-K) \)
qSigned trade size: \( q>0 \) buy, \( q<0 \) sellunits of the contract
mmShort[C]Net units of C that users collectively hold = MM’s shortmoves by \(q\) on every fill
Fair, SpreadMid price \( \mathbb E_p[f_C] \) and the half-spread added/subtractedAsk = Fair+Spread, Bid = Fair−Spread
cashThe MM’s liquid pool (funded by LPs + premiums)must cover the reserve
ReserveWorst-case liability at the 99th percentile of θcapital the MM must keep
φ, ΦStandard normal PDF and CDF\( \varphi(x)=\tfrac{1}{\sqrt{2\pi}}e^{-x^2/2} \), \( \Phi \) its integral

Money is stored as numeric(20,8) and every arithmetic result is passed through round8 (round-half-even to 8 decimals) — see §17. Formulas below omit the rounding for clarity; the code never does. Positions and MM inventory aggregate by a canonical contract key (type + params normalised to 12 significant figures, contractKey), so two specs that price identically share one book entry.

2The belief model#

The belief is a Gaussian density over the outcome. Everything the engine “knows” at any instant is these two numbers, μ and σ:

$$ p(\theta)=\mathcal N(\theta;\mu,\sigma^2)=\frac{1}{\sqrt{2\pi\sigma^2}}\,\exp\!\left(-\frac{(\theta-\mu)^2}{2\sigma^2}\right) $$ gaussian.ts

From it we read the density, the cumulative probability, and quantiles (for confidence intervals and sampling):

$$ \text{pdf}(\theta)=\frac{\varphi\!\left(\tfrac{\theta-\mu}{\sigma}\right)}{\sigma}, \qquad \text{cdf}(\theta)=\Phi\!\left(\frac{\theta-\mu}{\sigma}\right), \qquad \text{quantile}(p)=\mu+\sigma\,\Phi^{-1}(p) $$

The 80% credible interval the UI shows is \( [\,\mu-1.2816\,\sigma,\ \mu+1.2816\,\sigma\,] \), since \( \Phi^{-1}(0.9)\approx 1.2816 \). Drag μ and σ and watch the mass move and stretch:

Background — the probability toolkit (everything the rest of the page assumes)

A few standard definitions, so nothing below needs an outside reference:

  • Random variable & density. θ is a random variable — a quantity whose value is uncertain. Its probability density \( p(\theta) \) measures relative likelihood; the probability θ lands in \( [a,b] \) is the area \( \int_a^b p(\theta)\,d\theta \), and the total \( \int_{-\infty}^{\infty} p=1 \).
  • CDF & quantile. The cumulative \( F(\theta)=P(\le\theta)=\int_{-\infty}^{\theta} p \) runs 0→1. Its inverse, the quantile function \( F^{-1}(u) \), answers the reverse question — “below which value does fraction \(u\) of the mass sit?”. For the Gaussian, \( F=\Phi(\tfrac{\theta-\mu}{\sigma}) \) and \( F^{-1}(u)=\mu+\sigma\,\Phi^{-1}(u) \).
  • Expectation (the average). \( \mathbb E[g(\theta)]=\int g(\theta)\,p(\theta)\,d\theta \) — the value of any function \(g\), weighted by likelihood. (This “plug the function inside the integral” rule is the law of the unconscious statistician.) The mean is \( \mathbb E[\theta]=\mu \), and it is exactly the fair price of §4.
  • Variance. Spread around the mean: \( \operatorname{Var}[\theta]=\mathbb E[(\theta-\mu)^2]=\mathbb E[\theta^2]-\mu^2 \). That last identity — second moment minus mean-squared — is reused all through §13. For the Gaussian, \( \operatorname{Var}=\sigma^2 \).
  • Standardization (the z-score). Subtracting the mean and dividing by σ turns any \( \mathcal N(\mu,\sigma^2) \) into the standard \( \mathcal N(0,1) \): \( z=\tfrac{\theta-\mu}{\sigma} \). That is why the standard PDF/CDF \( \varphi,\Phi \) suffice everywhere — each formula just evaluates them at a z-score such as \( d=\tfrac{\mu-K}{\sigma} \).
  • Credible interval. The Bayesian “the value is in here with probability X%”. The symmetric central X% interval runs between the \( \tfrac{1-X}{2} \) and \( \tfrac{1+X}{2} \) quantiles. For 80% that is the 0.10 and 0.90 quantiles, i.e. \( \mu\pm\Phi^{-1}(0.9)\,\sigma=\mu\pm1.2816\,\sigma \).

Made concrete on the belief \( \mathcal N(100,10^2) \): the density peaks at \( \theta=100 \); the CDF reads \( \text{cdf}(110)=\Phi(1)=0.8413 \), so 84% of the mass sits below 110; the 0.9-quantile is \( 100+10\cdot1.2816=112.82 \); and the 80% credible interval is \( [\,87.18,\ 112.82\,] \). Every later section just feeds a different z-score into these same three functions \( \varphi,\ \Phi,\ \Phi^{-1} \) — there is nothing else to learn.

The belief over θlive
μ slides it · σ widens it
p(θ)80% CIμ
Implementation GaussianBelief(mu, sigma2) stores the variance and exposes pdf/cdf/quantile/sample. Sampling uses the inverse-CDF of a seeded PRNG so Monte-Carlo results are reproducible (§17).

The belief is now a family — five shipped models

The Gaussian is the baseline, not the whole story. The belief is an interface — anything that can report mean / variance / pdf / cdf / quantile / sample can drive the engine — and a market now picks one of five shipped models at creation via a model tag. Pricing, reserve, spread, settlement and stats are written against the interface, so they are identical across all five; only the density and its learning rule differ.

Model tagDensity kindShape it addsLearning rulePriced by
gaussiangaussianone symmetric bell (baseline)conjugate Normal–Normal (exact)closed form
student_tstudent_tfat tails (ν fixed at creation)variance-domain Normal–Normalquadrature
mixturemixture\(K\) explicit campsresponsibility (EM-style)closed-form \(\sum_k\pi_k\Phi\)
gen_basismixtureany number of camps, data-drivenresponsibility + spawn / placementclosed-form \(\sum_k\pi_k\Phi\)
gen_exactgen_exactskew · peak · flat · bimodalmoment-projection (shape adapts)quadrature

The model tag is the creator-facing choice; the belief_kind is the underlying density class. They are not the same column: Gen·basis is a mixture belief run with mode-spawning and the placement primitive switched on, so it shares the mixture's closed-form pricing and responsibility update — it is a behaviour layer over the same density, not a new density. The two general families (Gen·basis ⛓, Gen·exact ★) are detailed in §7 (their updates) and §21 (the design study that produced them).

Then → Now — the belief representation
Then · single Gaussian

Every market was a lone bell \(\mathcal N(\mu,\sigma^2)\). Two numbers, one mode, symmetric, exponentially-thin tails — and the only response to flow was to slide \(\mu\) and shrink \(\sigma\).

Now · pluggable model family

One of five densities, fixed at creation. The same belief can carry genuine disagreement (two camps), fat tails (a t), or an authored skew/flat/bimodal shape (Gen·exact) — and Gen·basis grows or collapses its mode count from the order flow itself.

Why · and what changed in the market

Statistics: a Gaussian has zero skew and fixed (zero excess) kurtosis, so it cannot represent an asymmetric or two-camp consensus — it averages them into a single mode nobody holds. Economics & market behaviour: that phantom centre is mispriced. A contract straddling the valley between two camps, or a far-tail long-shot, was quoted off a density the crowd never believed; the richer families price those off the shape the crowd actually expressed, so the quoted price tracks consensus instead of smearing it. The cost is pricing path — only the Gaussian-tailed, closed-form families stay cheap/on-chain-feasible; the special-function families (Student-t, Gen·exact) price by quadrature, which is why they are off-chain (§21).

V2 — multi-modal beliefs (Gaussian mixture & Student-t)

The single Gaussian can only ever be one symmetric bump, so genuine disagreement (“half the market expects 60, half expects 80”) collapses into an average nobody holds. V2 keeps the same BeliefModel interface but adds two richer kinds; every pricing / reserve / stats caller is unchanged because they only touch mean/variance/pdf/cdf/quantile/sample.

Gaussian mixture — a weighted sum of \(K\) Gaussian components, \(\pi_k\ge 0,\ \sum_k\pi_k=1\):

$$ p(\theta)=\sum_{k=1}^{K}\pi_k\,\mathcal N(\theta;\mu_k,\sigma_k^2),\qquad \text{cdf}(\theta)=\sum_k \pi_k\,\Phi\!\left(\frac{\theta-\mu_k}{\sigma_k}\right) $$ mixture.ts

Its moments follow from the law of total variance — a between-component spread added to the average within-component spread:

$$ \mu=\mathbb E[\theta]=\sum_k\pi_k\mu_k,\qquad \operatorname{Var}[\theta]=\underbrace{\sum_k\pi_k\sigma_k^2}_{\text{within}} +\underbrace{\sum_k\pi_k\mu_k^2-\mu^2}_{\text{between}} $$

There is no closed-form quantile, so it is found by bisection on the (monotone) cdf; a draw picks a component with probability \(\pi_k\) then samples that Gaussian.

Student-t — same center \(\mu\) but fat tails set by the degrees of freedom \(\nu\) (small \(\nu\) → heavy tails, \(\nu\to\infty\) → Gaussian). Location–scale form with scale \(s\) (we require \(\nu>2\) so the variance is finite):

$$ p(\theta)=\frac{1}{s}\,t_\nu\!\Big(\tfrac{\theta-\mu}{s}\Big),\qquad \operatorname{Var}[\theta]=s^2\,\frac{\nu}{\nu-2} $$ student_t.ts

The cdf uses the regularized incomplete beta \(I_x(\tfrac{\nu}{2},\tfrac12)\) with \(x=\tfrac{\nu}{\nu+t^2}\); a draw is \( \theta=\mu+s\,Z/\sqrt{W/\nu} \) with \(Z\sim\mathcal N(0,1)\), \(W\sim\chi^2_\nu\) (a seeded gamma sampler).

A market’s kind is fixed at creation (belief_kind); existing Gaussian markets are byte-for-byte unchanged.

“Why do my markets still look Gaussian?” Two reasons, both by design. (1) The kind is chosen at creation and never changes. A Gaussian market stays a single bump no matter what trades arrive — trades move \(\mu\) and \(\sigma\), they do not grow a second mode or fatten the tails. To see multi-modal or fat-tailed behaviour you must create the market as mixture or student_t. (2) A Student-t with moderate \(\nu\) genuinely looks almost Gaussian — its difference is in the far tails, not the body, so the eye reads it as “Gaussian-ish” even though it prices tail-heavy contracts differently. The sandbox below makes both effects visible: it draws each model against the equal-(μ, σ) Gaussian, so any gap you see is pure shape — the part a single Gaussian can never capture.
Belief-model sandbox — Gaussian vs Mixture vs Student-tlive
pick a model · compare to the equal-(μ,σ) Gaussian · commit a trade
this model p(θ)Gaussian(μ,σ)contract payoffstrike K
What to look for. Mixture: crank mode separation up and the one bump splits into two camps — the equal-σ Gaussian fills the valley between them with mass nobody believes in, and the fair price of a contract straddling the gap diverges (watch model vs Gaussian %). Tilt π₁ and the camps reweight. Student-t: drop \(\nu\) toward 3 and the body barely moves while the tails lift — push the strike \(K\) far out and the t prices the long-shot contract well above the Gaussian, because it never dismissed the tail. Commit a trade and each model updates by its own rule: the Gaussian just slides, the mixture concentrates weight on the camp the order flow favours (and merges back toward consensus), the Student-t re-centres while keeping ν — its tails — fixed.

3Contracts & payoffs#

A contract is defined entirely by its payoff at settlement, \( f_C(\theta^\star) \). A holder of q units receives \( q\cdot f_C(\theta^\star) \). The seven v1 types:

TypePayoff \(f(\theta)\)RangeBet shape
LINEAR\( \theta \)unboundeddirect exposure to the value
CALL\( \max(0,\ \theta-K) \)\([0,\infty)\)bullish above strike \(K\)
PUT\( \max(0,\ K-\theta) \)\([0,\infty)\)bearish below \(K\)
BINARY_CALL\( \mathbf{1}[\theta\ge K] \)\(\{0,1\}\)“will it clear \(K\)?”
BINARY_PUT\( \mathbf{1}[\theta\le K] \)\(\{0,1\}\)“will it stay under \(K\)?”
SPREAD\( \mathbf{1}[a\le\theta\le b] \)\(\{0,1\}\)“will it land in \([a,b]\)?”
GAUSSIAN\( \exp\!\big(-\tfrac{(\theta-c)^2}{2w^2}\big) \)\((0,1]\)proximity to a target \(c\) (width \(w\))

The binary and spread payoffs are indicators (0 or 1), so their fair price is literally a probability. The GAUSSIAN payoff is a smooth “closeness” reward: full payout 1 exactly at \(c\), decaying with distance — the built-in proximity contract.

// packages/core/src/contracts.ts
case 'CALL': return Math.max(0, theta - spec.strike);
case 'GAUSSIAN': return Math.exp(-((theta - c) ** 2) / (2 * w * w));

Extension contracts — wider trade curves (Phase G5)

The orthogonality of belief and contract (the price is just \(\mathbb E_p[f]\)) means a new payoff is "add an \(f\) (+ its kinks, + a closed form where one exists)" — the belief side is untouched. Two tiers were added. G5.1 — bounded, \(f\in[0,1]\) (zero risk-model change, drop in everywhere):

TypePayoff \(f(\theta)\)RangeBet shape
SKEW_GAUSSIAN\( \exp\!\big(-\tfrac{(\theta-c)^2}{2w_\pm^2}\big),\ w_-=w_L\ (\theta<c),\ w_+=w_R \)\((0,1]\)asymmetric proximity to \(c\)
TENT\( \max\!\big(0,\ 1-\tfrac{|\theta-c|}{w}\big) \)\([0,1]\)triangular band, peak at \(c\)
TRAPEZOID\( 1 \) on \([a,b]\), linear ramps of run \(w\)\([0,1]\)a range with soft edges
SIGMOID\( \big(1+e^{-(\theta-c)/w}\big)^{-1} \)\((0,1)\)"above \(c\), softly" (smoothed binary)

G5.2 — unbounded, conditionally compatible (allowed only where they integrate and where liability can be bounded — see the compatibility table in §4):

TypePayoff \(f(\theta)\)RangeBet shape
POLYNOMIAL\( \sum_{k} a_k\,\theta^k \) (degree \(\le 4\))unboundede.g. \((\theta-c)^2\): a "distance / variance" bet
EXPONENTIAL\( e^{a(\theta-c)} \)\((0,\infty)\)convex, one-sided "tail" bet

4Fair price = E[payoff]#

The single most important equation in the system. The fair price of a contract is the expected payoff under the current belief:

$$ \text{Price}_\text{fair}(C)=\mathbb E_{p}\!\left[f_C(\theta)\right]=\int_{-\infty}^{\infty} f_C(\theta)\,p(\theta)\,d\theta $$ pricing.ts

For a Gaussian belief, every v1 contract has a closed form — no integration at runtime. Using \( d=\tfrac{\mu-K}{\sigma} \) (the z-score of the strike — how many σ the mean sits above \(K\), see §2):

TypeFair price (closed form)Reading
LINEAR\( \mu \)just the mean
CALL\( \sigma\,\varphi(d)+(\mu-K)\,\Phi(d) \)Bachelier (normal) call
PUT\( \sigma\,\varphi(d)-(\mu-K)\,\Phi(-d) \)normal put
BINARY_CALL\( \Phi(d)=\Phi\!\big(\tfrac{\mu-K}{\sigma}\big) \)\( =P(\theta\ge K) \)
BINARY_PUT\( \Phi\!\big(\tfrac{K-\mu}{\sigma}\big) \)\( =P(\theta\le K) \)
SPREAD\( \Phi\!\big(\tfrac{b-\mu}{\sigma}\big)-\Phi\!\big(\tfrac{a-\mu}{\sigma}\big) \)\( =P(a\le\theta\le b) \)
GAUSSIAN\( \sqrt{\tfrac{w^2}{w^2+\sigma^2}}\;\exp\!\big(-\tfrac{(c-\mu)^2}{2(w^2+\sigma^2)}\big) \)two Gaussians convolved
Where the CALL and GAUSSIAN formulas come from (full derivation)

The CALL. By the definition of expectation (§2), \( \mathbb E[\max(0,\theta-K)]=\int_K^{\infty}(\theta-K)\,p(\theta)\,d\theta =\underbrace{\int_K^{\infty}\theta\,p\,d\theta}_{A}-K\underbrace{\int_K^{\infty} p\,d\theta}_{B} \). The easy piece is \( B=P(\theta\ge K)=\Phi(d) \). For \(A\), standardize with \( z=\tfrac{\theta-\mu}{\sigma} \) (so \( \theta=\mu+\sigma z \), \( p\,d\theta=\varphi(z)\,dz \), and the lower limit becomes \( z=-d \)): \( A=\int_{-d}^{\infty}(\mu+\sigma z)\,\varphi(z)\,dz=\mu\Phi(d)+\sigma\!\int_{-d}^{\infty} z\,\varphi(z)\,dz \). The trick is that \( z\,\varphi(z)=-\varphi'(z) \) (differentiate \( \varphi(z)=\tfrac{1}{\sqrt{2\pi}}e^{-z^2/2} \)), so \( \int_{-d}^{\infty} z\,\varphi(z)\,dz=\big[-\varphi(z)\big]_{-d}^{\infty}=\varphi(d) \) (using \( \varphi(-d)=\varphi(d) \)). Hence \( A=\mu\Phi(d)+\sigma\varphi(d) \) and \( \text{CALL}=A-KB=(\mu-K)\Phi(d)+\sigma\varphi(d) \). The PUT follows the same way — or instantly from put–call parity \( \text{CALL}-\text{PUT}=\mu-K \).

The GAUSSIAN. Its price is the overlap of two bell curves, \( \int_{-\infty}^{\infty} e^{-(\theta-c)^2/2w^2}\,\mathcal N(\theta;\mu,\sigma^2)\,d\theta \). The whole job is to collect the two exponents into a single squared term in θ plus a constant. Write \( a=\tfrac{1}{w^2}+\tfrac{1}{\sigma^2}=\tfrac{w^2+\sigma^2}{w^2\sigma^2} \) for the coefficient of \( \theta^2 \); completing the square gives

$$ -\frac{(\theta-c)^2}{2w^2}-\frac{(\theta-\mu)^2}{2\sigma^2} \;=\; -\frac{a}{2}\,(\theta-b)^2 \;-\; \frac{(c-\mu)^2}{2(w^2+\sigma^2)},\qquad b=\frac{c/w^2+\mu/\sigma^2}{a}. $$

The first term still depends on θ; the second does not. So the integral factors into a θ-Gaussian times a constant. The θ-Gaussian, integrated against the belief’s \( \tfrac{1}{\sqrt{2\pi\sigma^2}} \) normalizer, contributes \( \tfrac{1}{\sqrt{2\pi\sigma^2}}\cdot\sqrt{\tfrac{2\pi}{a}}=\tfrac{1}{\sqrt{\sigma^2 a}}=\sqrt{\tfrac{w^2}{w^2+\sigma^2}} \); the constant survives untouched. Multiplying the two:

$$ \text{Price}_\text{GAUSSIAN}=\sqrt{\frac{w^2}{w^2+\sigma^2}}\;\exp\!\Big(-\frac{(c-\mu)^2}{2(w^2+\sigma^2)}\Big). $$

Read it off: the two uncertainties \( w \) (contract tolerance) and \( \sigma \) (belief width) add in quadrature, \( (w^2+\sigma^2) \) — the exact rule for convolving two normals — so a wider belief or a more forgiving contract both flatten the peak and lower the price. The prefactor \( \sqrt{w^2/(w^2+\sigma^2)}\le1 \) is the height penalty for that spreading; the exponential measures how far the target \( c \) sits from the mean \( \mu \) in the combined scale. This same complete-the-square step — two Gaussians multiplied, the θ² coefficient giving a precision and the θ coefficient giving a centre — is precisely the engine of the Bayesian update in §7; there we keep the θ-Gaussian and discard the constant, here we do the reverse.

Worked example Belief \( \mathcal N(100,10^2) \). A BINARY_CALL at \(K=105\) is worth \( \Phi(\tfrac{100-105}{10})=\Phi(-0.5)=0.3085 \) — the market gives \( \theta\ge105 \) a 30.85% chance. The plain CALL at the same strike, with \( d=-0.5,\ \varphi(d)=0.3521 \), is \( 10(0.3521)+(-5)(0.3085)=\mathbf{1.978} \). And a SPREAD over \([95,105]\) prices to \( \Phi(0.5)-\Phi(-0.5)=\mathbf{0.3829} \), i.e. a 38.29% chance of landing in the band.

Closed forms for the extension contracts (Phase G5)

Every extension contract still prices closed-form under a Gaussian (and, by linearity, \(\text{Price}(f,\sum_k\pi_k\mathcal N_k)=\sum_k\pi_k\,\text{Price}(f,\mathcal N_k)\), under a mixture — including Gen·basis):

TypeFair price under \(\mathcal N(\mu,\sigma^2)\)Why it's exact
SKEW_GAUSSIAN\( \sum_{\pm}\sqrt{\tfrac{w_\pm^2}{w_\pm^2+\sigma^2}}\,e^{-\frac{(c-\mu)^2}{2(w_\pm^2+\sigma^2)}}\cdot\Phi(\mp z_\pm) \)each half-bell = the GAUSSIAN form × the \(\Phi\) truncation factor of the product-Gaussian
TENT\( \tfrac1w\big[R(c{-}w)-2R(c)+R(c{+}w)\big] \)exact sum of CALL ramps \(R(K)=\mathbb E[\max(0,\theta{-}K)]\) — closed-form under every kind
TRAPEZOID\( \tfrac1w\big[R(a{-}w)-R(a)-R(b)+R(b{+}w)\big] \)likewise a CALL-ramp sum
SIGMOIDbounded quadrature (no Gaussian closed form)smooth, bounded \(\Rightarrow\) cheap fixed Simpson window
POLYNOMIAL\( \sum_k a_k\,m_k,\quad m_k=\mathbb E[\theta^k] \)raw Gaussian moments \(m_k=\mu m_{k-1}+(k{-}1)\sigma^2 m_{k-2}\)
EXPONENTIAL\( e^{a(\mu-c)+\frac12 a^2\sigma^2} \)the Gaussian MGF \(\mathbb E[e^{a\theta}]=e^{a\mu+\frac12 a^2\sigma^2}\)

The price sensitivity \( \partial\,\text{Price}/\partial\mu \) used by the adverse-selection spread (§5) is kind-agnostic for all of these via the translation identity \( \partial_\mu\mathbb E[f(\theta)]=\mathbb E[f'(\theta)] \): TENT/TRAPEZOID differentiate their CALL decomposition, POLYNOMIAL drops to its derivative polynomial \(\sum_k k\,a_k\theta^{k-1}\), and EXPONENTIAL gives simply \( a\cdot\text{Price} \).

Which contracts are compatible — integrability & boundedness

A contract is usable only if its price \( \mathbb E_p[f] \) is finite (a race between how fast \(f\) grows and how fast the belief's tail \(p\) decays) and its liability is boundable for the reserve (§6). The two conditions:

Belief tail\(p(\theta)\) decays likeSafe \(f(\theta)\) growth
Gaussian / Gen·basis / Gen·exact\( e^{-c\theta^2} \) (or thinner)anything sub-\(e^{\theta^2}\) — every polynomial and exponential integrates
Student-t \((\nu)\)\( \theta^{-(\nu+1)} \) (polynomial)only \( \mathbb E[|\theta|^n]<\infty\iff n<\nu \): degree-\(n\) POLYNOMIAL needs \(n<\nu\); EXPONENTIAL never (price diverges)

So EXPONENTIAL is rejected on a Student-t market (its \( \mathbb E[e^{a\theta}] \) is infinite), and a POLYNOMIAL of degree \(n\) needs \(n<\nu\) there. Independently, both unbounded contracts are allowed only on bounded-outcome markets (\(\theta^\star\in[\theta_{\min},\theta_{\max}]\)) so the worst-case payout — and the reserve — is finite; the EXPONENTIAL rate is additionally capped to \(|a|(\theta_{\max}-\theta_{\min})\le 20\). This is a precise, kind-dependent rule enforced at quote and trade (contractBeliefCompatible), not a nicety. A separate well-formedness check (validateContract) also rejects a constant POLYNOMIAL (degree \(\ge 1\) required — a flat payoff is not a bet) and a zero-rate EXPONENTIAL (\(a\ne 0\)).

The plot draws the payoff \( f(\theta) \) and the belief \( p(\theta) \) (green, scaled to fit). The fair price is the area of their product — change the belief or the contract and watch it move:

Payoff × belief → fair pricelive
pick a contract, drag μ / σ / K
payoff f(θ)belief p(θ)μ

For arbitrary or custom payoffs without a closed form, the engine falls back to composite Simpson integration over \( \mu\pm10\sigma \) (expectF, 4000 nodes).

V2 — pricing a mixture or Student-t belief

Because expectation is linear, the fair price under a mixture is just the weight-sum of the per-component prices — and each component is a Gaussian, so we reuse the exact closed forms above with no new integration:

$$ \text{Price}\Big(f,\ \textstyle\sum_k\pi_k\mathcal N_k\Big)=\sum_k \pi_k\,\text{Price}(f,\mathcal N_k) $$ pricing.ts

The mean-sensitivity generalizes the same way — a rigid shift of all component means by \(d\mu\) (which moves the belief mean by \(d\mu\)): \( \partial\text{Price}/\partial\mu=\sum_k\pi_k\,\partial\text{Price}_k/\partial\mu_k \). For a Student-t belief there is no general closed form, so the price uses the same Simpson quadrature on the payoff and \( \partial\text{Price}/\partial\mu \) is a central difference in \(\mu\).

Worked example — why multi-modal matters Take the bimodal belief \( \tfrac12\mathcal N(60,5^2)+\tfrac12\mathcal N(80,5^2) \) (mean 70). A BINARY_CALL at \(K=75\) prices to \( \tfrac12\Phi(\tfrac{60-75}{5})+\tfrac12\Phi(\tfrac{80-75}{5})=\tfrac12\Phi(-3)+\tfrac12\Phi(1) =\tfrac12(0.00135)+\tfrac12(0.84134)=\mathbf{0.4213} \). A single Gaussian with the same mean and variance (\( \sigma^2=25+100=125,\ \sigma\approx11.18 \)) would price it at \( \Phi(\tfrac{70-75}{11.18})=\Phi(-0.447)=\mathbf{0.327} \). The mixture knows a whole camp sits above 75, so it rates “clears 75” far higher than the moment-matched bump — exactly the structure v1 had to throw away.

5Price sensitivity ∂P/∂μ#

How fast does a contract’s price move when the belief mean shifts by one unit? This derivative \( \partial \text{Price}/\partial\mu \) is the contract’s “delta”, and it feeds the adverse-selection spread (§8). Closed forms again:

Type\( \partial\text{Price}/\partial\mu \)
LINEAR\( 1 \)
CALL\( \Phi\!\big(\tfrac{\mu-K}{\sigma}\big) \)
PUT\( -\Phi\!\big(\tfrac{K-\mu}{\sigma}\big) \)
BINARY_CALL\( \varphi\!\big(\tfrac{\mu-K}{\sigma}\big)/\sigma \)
BINARY_PUT\( -\varphi\!\big(\tfrac{K-\mu}{\sigma}\big)/\sigma \)
SPREAD\( \big(\varphi(\tfrac{a-\mu}{\sigma})-\varphi(\tfrac{b-\mu}{\sigma})\big)/\sigma \)
GAUSSIAN\( \text{Price}\cdot\dfrac{c-\mu}{w^2+\sigma^2} \)

Note a CALL’s delta is exactly \( P(\theta\ge K) \): deep in-the-money it tracks μ one-for-one (delta→1), far out it barely reacts (delta→0). That’s the intuition the spread uses to charge more on trades that could move the price a lot.

Worked example At the §4 belief \( \mathcal N(100,10^2) \), a CALL struck at \(K=105\) has \( d=\tfrac{100-105}{10}=-0.5 \), so its delta is \( \Phi(-0.5)=\mathbf{0.3085} \): a one-unit rise in the belief mean μ lifts that call’s fair price by about \( 0.31 \). A BINARY_CALL at the same strike has delta \( \varphi(-0.5)/10=0.3521/10=\mathbf{0.0352} \) — much flatter, because a binary’s payoff is capped at 1.
Why a CALL’s delta collapses to just \( \Phi(d) \) (the edge terms cancel)

Differentiate \( \text{CALL}=(\mu-K)\Phi(d)+\sigma\varphi(d) \) with respect to μ, with \( d=\tfrac{\mu-K}{\sigma} \) so \( \tfrac{\partial d}{\partial\mu}=\tfrac1\sigma \). The product rule gives three terms: \( \Phi(d)+(\mu-K)\varphi(d)\tfrac1\sigma+\sigma\,\varphi'(d)\tfrac1\sigma \). Because \( \varphi'(d)=-d\,\varphi(d)=-\tfrac{\mu-K}{\sigma}\varphi(d) \), the third term is \( -(\mu-K)\varphi(d)\tfrac1\sigma \), which exactly cancels the second. Only \( \Phi(d) \) survives, so \( \partial\text{Price}/\partial\mu=\Phi(d)=P(\theta\ge K) \). This same cancellation — the density-weighted boundary terms always vanish — is why every delta in the table above is so clean.

6Signal extraction#

When someone trades, they reveal information. The engine decodes a trade \( (C, q) \) into a signal \( s \) — the trader’s implied estimate of θ — and a weight \( w \ge 0 \) — how much to trust it. Define direction \( \text{dir}=\operatorname{sign}(q) \) and a clamped signal intensity \( \iota=\min\!\big(1,\ |q|/Q_\text{max}\big)\in[0,1] \). (The spread’s adverse-selection term in §8 uses the un-clamped \( |q|/Q_\text{max} \) instead — solvency-gated fills can exceed \( Q_\text{max} \), and there a larger order should keep widening the quote, whereas the belief must not be moved by more than one full-precision signal.)

$$ s=\begin{cases} \mu+\text{dir}\cdot\beta\,\sigma\,\iota & \text{LINEAR}\\[4pt] K+\text{dir}\cdot\alpha\,\sigma\,(1+\iota) & \text{CALL / BINARY\_CALL}\\[4pt] K-\text{dir}\cdot\alpha\,\sigma\,(1+\iota) & \text{PUT / BINARY\_PUT}\\[4pt] \text{toward target } c \text{ (buy) / away (sell)} & \text{GAUSSIAN / SPREAD} \end{cases} $$signal.ts

The intuition: buying a CALL says “θ is above the strike”; selling it says the opposite. Buying a LINEAR pushes the signal above the current mean; selling pushes below. A range/point bet sets the signal at the target when bought, and pushes it away — along \( \operatorname{sign}(\mu-\text{target}) \) — when sold.

The weight rises with size but treats tiny trades as noise — below the threshold \( q_\text{th} \) it’s near zero:

$$ w=\iota\,\Big(1-e^{-|q|/q_\text{th}}\Big)=\frac{|q|}{Q_\text{max}}\Big(1-e^{-|q|/q_\text{th}}\Big) $$

Because \( \iota \) is clamped to \([0,1]\) and the soft gate \( 1-e^{-|q|/q_\text{th}}\in[0,1) \), the weight is always \( w\in[0,1) \) — an oversized fill (one the reserve gate of §10 let through above \( Q_\text{max} \)) saturates at full intensity rather than over-trusting the trade. The signal magnitude \( (1+\iota) \) saturates at \( 2 \) for the same reason.

Worked example Prior \( \mathcal N(100,12^2) \), a buy of 120 units of a CALL at \(K=100\) (defaults \( \alpha=1,\ \beta=1,\ Q_\text{max}=500,\ q_\text{th}=10 \)). Intensity \( \iota=120/500=0.24 \). Signal \( s=100+(+1)(1)(12)(1+0.24)=\mathbf{114.88} \). Weight \( w=0.24\,(1-e^{-120/10})=0.24(1-e^{-12})\approx\mathbf{0.2400} \). The trade reads as “θ is around 115, trust it ~24%”.
Why this shape Larger orders are more likely to be informed and carry more conviction, so both the signal magnitude \( (1+\iota) \) and the weight grow with \(|q|\). The \( 1-e^{-|q|/q_\text{th}} \) factor is a soft gate: a 1-unit “tyre-kick” barely nudges the belief; a 200-unit order moves it.
Signal rules for the extension contracts (Phase G5)

The four bounded shapes and two unbounded contracts reuse the same three primitives — point-bet, directional-from-strike, or location-neutral — so no new signal machinery was needed (signal.ts):

  • SKEW_GAUSSIAN, TENT → a point-bet on the centre \(c\), exactly like GAUSSIAN (buy pulls the belief toward \(c\), sell pushes away along \(\operatorname{sign}(\mu-c)\)).
  • TRAPEZOID → a range-bet on the midpoint \(\tfrac{a+b}{2}\), like SPREAD.
  • SIGMOID → bullish, treated as a CALL with strike \(c\): \( s=c+\text{dir}\cdot\alpha\,\sigma\,(1+\iota) \).
  • EXPONENTIAL → directional by the rate’s sign: \(a>0\) is bullish, \(a<0\) bearish, so \( s=\mu+\text{dir}\cdot\operatorname{sign}(a)\,\alpha\,\sigma\,(1+\iota) \).
  • POLYNOMIAL → a general \( \sum_k a_k\theta^k \) is a shape bet (e.g. \((\theta-c)^2\) rewards both tails), not a directional location bet, so it carries no well-defined μ-signal: the location is left neutral (\( s=\mu \)) and the trade still prices and reserves normally — it just doesn’t move the mean.

7The Bayesian update#

With a signal \( s \) and weight \( w \), the belief updates by the conjugate Normal–Normal rule — a precision-weighted average of the prior and the signal. Precision is inverse variance; the signal’s precision is its weight over the signal-noise variance \( \sigma_\varepsilon^2 \):

$$ \tau_0=\frac{1}{\sigma^2},\quad \tau_s=\frac{w}{\sigma_\varepsilon^2},\qquad \mu'=\frac{\tau_0\,\mu+\tau_s\,s}{\tau_0+\tau_s},\qquad \sigma'^2=\frac{1}{\tau_0+\tau_s} $$bayes.ts

The mean slides toward the signal, by an amount set by the relative precisions; the variance can only shrink (more data → more confidence). A floor \( \sigma'^2\ge\sigma_\text{min}^2 \) prevents runaway overconfidence. With the demo default \( \sigma_\varepsilon=\sigma_0 \), a full-weight signal moves μ about halfway to \(s\).

Why these exact formulas? The Normal–Normal conjugate derivation

Bayes’ rule says posterior \( \propto \) prior \( \times \) likelihood. The prior is \( \mathcal N(\mu,\sigma^2) \); we treat the trade as a noisy observation of the truth, \( s\sim\mathcal N(\theta,\ \sigma_\varepsilon^2/w) \) — a reading centred on θ whose precision scales with the trust weight \(w\). Both factors are Gaussian, so their product is Gaussian too (a conjugate pair: the posterior stays in the same family):

$$ p(\theta\mid s)\ \propto\ \exp\!\Big(-\tfrac{(\theta-\mu)^2}{2\sigma^2}\Big)\,\exp\!\Big(-\tfrac{w\,(\theta-s)^2}{2\sigma_\varepsilon^2}\Big) $$

Add the two exponents and complete the square in θ. Writing \( \tau_0=\tfrac1{\sigma^2} \) and \( \tau_s=\tfrac{w}{\sigma_\varepsilon^2} \), expand the squares and regroup by powers of θ:

$$ -\tfrac12\big[\tau_0(\theta-\mu)^2+\tau_s(\theta-s)^2\big]=-\tfrac12(\tau_0+\tau_s)\,\theta^2+(\tau_0\mu+\tau_s s)\,\theta+\text{const}. $$

Any Gaussian \( \exp\!\big(-\tfrac12\tau'(\theta-\mu')^2\big) \) has \( \theta^2 \)-coefficient \( -\tfrac12\tau' \) and θ-coefficient \( +\tau'\mu' \). Matching those two coefficients to the line above reads the posterior off directly — the quadratic term fixes the precision, the linear term fixes the centre:

$$ \tau'=\underbrace{\tfrac{1}{\sigma^2}}_{\tau_0}+\underbrace{\tfrac{w}{\sigma_\varepsilon^2}}_{\tau_s},\qquad \mu'=\frac{\tau_0\,\mu+\tau_s\,s}{\tau'},\qquad \sigma'^2=\frac{1}{\tau'} $$

Notice the constant term we dropped carries no θ, so it only renormalizes the density and never touches \( \mu' \) or \( \sigma' \) — which is why the posterior depends on the prior and the signal but not on how “surprising” the signal was. The mean \( \mu' \) is a genuine weighted average: rewrite it as \( \mu'=\mu+\tfrac{\tau_s}{\tau_0+\tau_s}(s-\mu) \), a step from the prior toward the signal whose fractional size \( \tfrac{\tau_s}{\tau_0+\tau_s}\in[0,1) \) is exactly the signal’s share of the total precision. With the demo default \( \sigma_\varepsilon=\sigma_0 \) and full weight \( w=1 \), that share is \( \tfrac{1}{1+1}=\tfrac12 \) — μ moves halfway to \(s\), as §7 claims.

That is the whole rule: precisions add — each independent source of information contributes \( 1/\text{variance} \) — and the new mean is the precision-weighted average of prior and signal, leaning toward whichever is more certain. Since \( \tau_s\ge0 \), the posterior precision only grows, so \( \sigma'^2 \) can only shrink: more data, more confidence. The \( \sigma_\text{min} \) floor merely stops this from collapsing into absurd over-confidence.

Worked example (continuing §6) Feed \( s=114.88,\ w=0.24 \) into prior \( \mathcal N(100,12^2) \) with \( \sigma_\varepsilon=\sigma_0=12 \). Precisions \( \tau_0=\tfrac1{144},\ \tau_s=\tfrac{0.24}{144} \), so \( \mu'=\tfrac{100+0.24(114.88)}{1.24}=\mathbf{102.88} \) and \( \sigma'=\sqrt{144/1.24}=\mathbf{10.78} \). One 120-lot call buy moved the mean up \( 2.88 \) and tightened σ from 12 to 10.78. Commit & repeat below to watch this compound.

This widget runs the real loop: choose a trade, see the prior (grey) become the posterior (blue). Hit Commit & repeat to feed the posterior back in and watch the belief converge and tighten — the market learning:

One trade → one belief updatelive
commit repeatedly to see convergence
priorposteriorsignal s
Simplified learning-rate variant (behind a flag)

When useSimplifiedUpdate is set, the engine instead uses \( \mu'=\mu+\text{lr}\,(s-\mu)\,w \) and \( \sigma'^2=\sigma^2(1-\text{decay}\cdot w) \). Cheaper, less principled; off by default.

V2 — updating a mixture belief

The same scalar \((s,w)\) drives a mixture, but two things happen at once. First a membership update treats the component label as latent with prior \(\pi_k\): the signal’s evidence under each component’s predictive \( \mathcal N(s;\mu_k,\sigma_k^2+\sigma_\varepsilon^2) \) re-weights the mixture (tempered by the reliability \(w\), then renormalized into responsibilities \(r_k\)):

$$ r_k\ \propto\ \pi_k\,\Big[\mathcal N\!\big(s;\mu_k,\sigma_k^2+\sigma_\varepsilon^2\big)\Big]^{w}, \qquad \sum_k r_k=1,\qquad \pi_k'\leftarrow r_k $$ bayes.ts

This is what makes a consistent stream of signals concentrate weight on the nearest mode instead of averaging the modes together. Second, each component runs the ordinary Normal–Normal update above, but with its signal precision scaled by its own responsibility, so the component that owns the signal absorbs it while the others barely move:

$$ \tau_{s,k}=\frac{w\,r_k}{\sigma_\varepsilon^2},\qquad \mu_k'=\frac{\tau_{0,k}\mu_k+\tau_{s,k}\,s}{\tau_{0,k}+\tau_{s,k}},\qquad \sigma_k'^2=\frac{1}{\tau_{0,k}+\tau_{s,k}} $$

Finally the mixture is kept tractable by component management (mixture_ops.ts): prune components with \(\pi_k<\pi_\text{min}\), merge near-duplicates \(|\mu_i-\mu_j|<\tau(\sigma_i+\sigma_j)\) by moment-matching (which preserves the pair’s combined weight, mean and variance exactly), and cap the count \(K\). The whole step is computed in log-space so well-separated modes don’t underflow.

Gen·basis — adaptive mode-spawning. The default general model (§21) runs this exact update with one addition: before the membership step, a confident signal that lands farther than \(\tau_\text{spawn}\,\sigma_k\) from every existing mode (and carries weight \(w\ge w_\text{min}\)) seeds a fresh narrow component \(\{\pi=w_\text{seed},\,\mu=s,\,\sigma^2=\sigma_\varepsilon^2\}\). The responsibility step then grows a mode there instead of dragging an existing one across the gap; component management merges it straight back if it turns out redundant, or keeps it once it earns responsibility — so the parameter count is data-driven (it rises on disagreement, falls on consensus) yet stays under the \(K\)-cap. Spawning is off for the plain mixture kind, so that path is byte-identical to the above; it is on only when the market’s model tag is gen_basis.

Gen·basis — placement ("paint the curve"). On a Gen·basis market the bell (GAUSSIAN) contract is a placement primitive (the trade engine routes it to a weight-only update, placeBasisBump, instead of the responsibility step above). A bell buy at center \(c\) with intensity \(w\) adds mass there — it spawns a fresh bump \(\{\pi\propto w,\ \mu=c,\ \sigma^2=\sigma_\varepsilon^2\}\) if \(c\) is farther than \(\tau_\text{spawn}\,\sigma_k\) from every mode, else it accumulates \(w\) onto the nearest bump and pulls its mean toward \(c\); a bell sell near a bump removes mass from it (managed-out once it falls below \(\pi_\text{min}\)). Weights are renormalised to \(\sum_k\pi_k=1\) and managed (prune / merge / cap) as usual. The key difference from the responsibility update is that placement is additive: it never multiplicatively reallocates the other bumps' weights or drifts their means, so repeated bells at distinct prices accumulate into distinct, persistent camps — a user literally paints the belief through trades. (The responsibility update is consensus-forming and would collapse weight onto the most-recent camp; see parametric-belief-families.md §4.) Pricing, reserve and persistence are unchanged — the result is an ordinary mixture.

V2 — updating a Student-t belief

The Student-t is not self-conjugate, so a t prior under the trade likelihood has no exact t posterior. We keep \(\nu\) — the structural tail weight, fixed at creation — and move only the location and scale, running the very same precision-weighted step as the Gaussian but carried out in the variance domain. Moment-match the prior to a Gaussian of equal variance \(V=s_t^2\,\tfrac{\nu}{\nu-2}\) (here \(s_t\) is the t scale written \(s\) in §6, renamed so \(s\) stays the signal), run the Normal–Normal update, then map the posterior variance back to a t scale:

$$ \tau_0=\frac1V,\quad \tau_s=\frac{w}{\sigma_\varepsilon^2},\qquad \mu'=\frac{\tau_0\,\mu+\tau_s\,s}{\tau_0+\tau_s},\qquad V'=\frac{1}{\tau_0+\tau_s},\qquad s_t'^2=V'\,\frac{\nu-2}{\nu} $$ bayes.ts

This is byte-for-byte the §7 arithmetic (the floor \(V'\ge\sigma_\text{min}^2\) still applies), so a Student-t market learns at the Gaussian rate while keeping its fat tails, and degrades smoothly to the exact Normal–Normal update as \(\nu\to\infty\) (where \(V\to s_t^2\)). Because \(\nu\) never changes, the tails stay as heavy as the market was created with; only the centre and width move. The useSimplifiedUpdate variant applies the same \(\mu'=\mu+\text{lr}\,(s-\mu)\,w\) and \(V'=V\,(1-\text{decay}\cdot w)\) in the variance domain.

V2 — the Gen·exact belief and its update

Gen·exact ★ is the max-entropy exp(−poly) belief: with \(u=(\theta-\mu)/\sigma\),

$$ p(\theta)=\frac{1}{\sigma\,Z}\,e^{-E(u)},\qquad E(u)=\tfrac12\lambda_2 u^2+\lambda_3 u^3+\lambda_4 u^4+\lambda_6 u^6 $$ gen_exact.ts

Each coefficient is one shape knob — \(\lambda_2\) Gaussian, \(+\lambda_3\) skew, \(+\lambda_4>0\) flat-top / thinner tails, \(\lambda_2<0\) a double-well (bimodal). The confining sixth-order term is auto-set, \(\lambda_6=0.004+0.06\max(0,-\lambda_4)+0.03|\lambda_3|\), so the density is always integrable and skew can never drag the mean to the tail — any in-range \((\lambda_2,\lambda_3,\lambda_4)\) is a valid belief. There is no closed-form normaliser, so \(Z\), the mean, and the variance are cached by fixed composite-Simpson over \(u\in[-7,7]\) with a min-energy shift (subtract \(E_{\min}\) before exponentiating) for stability; the CDF/quantile/sampler share a cumulative grid over the same window. Pricing has no closed form either, so it routes through the §4 quadrature (jump payoffs use the cached CDF, exactly as Student-t does, to avoid the discontinuity-in-a-Simpson-cell error). This is the Gen·exact ★ option in the §21 sandbox, ported faithfully.

v1 update — fixed shape. The exponent \(\lambda\) (the shape) is held constant; only the location \(\mu\) and scale \(\sigma\) move, by the very same variance-domain step as the Student-t. With prior variance \(V\) (here \(V=\sigma^2\,\mathrm{Var}[u]\), the cached variance):

$$ \tau_0=\frac1V,\ \ \tau_s=\frac{w}{\sigma_\varepsilon^2},\qquad \mu'=\frac{\tau_0\,\mu+\tau_s\,s}{\tau_0+\tau_s},\qquad V'=\max\!\Big(\tfrac{1}{\tau_0+\tau_s},\,\sigma_\text{min}^2\Big),\qquad \sigma'=\sigma\sqrt{V'/V} $$ bayes.ts

Variance \(\propto\sigma^2\) at fixed shape, so rescaling \(\sigma\) by \(\sqrt{V'/V}\) makes the belief variance land exactly on \(V'\). The skew/peak the creator authored is preserved; only the centre and width learn.

v2 update — moment-projection (shape adapts). The default for Gen·exact (genExactShapeAdapt). This is assumed-density filtering: form the exact Bayesian posterior — prior times the Gaussian likelihood of the trade signal —

$$ \tilde p(\theta)\ \propto\ p(\theta)\;\exp\!\Big(-\tfrac{(\theta-s)^2}{2\,\sigma_\varepsilon^2/w}\Big), $$ bayes.ts

take its first four moments \((m,\,V,\,\gamma_1,\,\gamma_2)\) by quadrature, then project back onto the family: hold \(\lambda_2\) (the creator's unimodal/bimodal choice), re-fit \((\lambda_3,\lambda_4)\) so the standardised shape's skewness/excess-kurtosis match \((\gamma_1,\gamma_2)\) — a damped Gauss-Newton solve clamped to the sandbox-safe ranges — and set \(\sigma=\sqrt{V/\mathrm{Var}[u]}\), \(\mu=m-\sigma\,\mathbb{E}[u]\) to match the posterior mean/variance. So order flow sculpts the curve (skew, tails), not just slides it. A symmetric update leaves \(\lambda_3\approx0\) (it reduces to the v1 step), and the simplified / zero-weight / degenerate paths fall back to v1. MC-verified against an importance-sampled posterior.

Caching (I3). The standardised moments \(\{E_{\min},Z,\mathbb{E}[u],\mathbb{E}[u^2]\}\) depend only on \(\lambda\) (not \(\mu,\sigma\)), so a fixed-shape v1 update reuses them and they are persisted in belief_state — a hot read reconstructs the belief without re-running the normalisation quadrature. The cumulative grid (CDF/quantile/sampler) is built lazily, only when a jump payoff or the reserve Monte-Carlo actually needs it.

Then → Now — how a trade updates a Gen·exact belief
Then · v1, fixed shape

A trade moved only \(\mu\) and \(\sigma\); the exponent \(\lambda\) — the authored skew / peak / bimodality — was frozen. The belief slid and breathed but never changed shape: the same variance-domain step as the Student-t, with \(\sigma\) rescaled by \(\sqrt{V'/V}\).

Now · v2, moment-projection (default)

Assumed-density filtering: form the exact posterior, read its four moments, then re-fit \((\lambda_3,\lambda_4)\) to the posterior skew/kurtosis (holding \(\lambda_2\), the unimodal/bimodal choice). Order flow now sculpts the curve, not just slides it.

Why · and what changed in the market

Statistics: v1 is a projection onto a 2-parameter \((\mu,\sigma)\) slice of the family — it discards the higher-moment information a trade carries. v2 projects onto the full \((\mu,\sigma,\lambda_3,\lambda_4)\) family (the standard moment-matched ADF step), so a stream that is genuinely asymmetric bends the density’s skew toward it. Economics & market behaviour: under v1 a creator’s authored shape was a permanent prior the crowd could never correct; a run of one-sided flow left the tails mispriced because only the centre could move. Under v2 the crowd can re-shape the tails and skew it’s actually trading — the far-strike and asymmetric contracts re-price as conviction builds. It reduces exactly to v1 on a symmetric update (\(\lambda_3\!\to\!0\)), and the simplified / zero-weight / numerically-degenerate paths still fall back to v1, so nothing regresses; the cost is the per-update posterior quadrature (v1 was arithmetic only). Toggle it off with genExactShapeAdapt=false to recover the frozen-shape behaviour.

Sandbox — watch a trade reshape a Gen·exact belief (v1 vs v2)

Author a shape with the \(\lambda\) knobs (push \(\lambda_2\) negative for a bimodal prior), pick a trade, and the widget runs both updates from the same prior: v1 (fixed shape) only slides and rescales the curve, while v2 (moment-projection) re-fits \((\lambda_3,\lambda_4)\) to the posterior skew/kurtosis. Commit & repeat feeds the v2 posterior back so you can watch order flow sculpt the shape over a stream. Every number is the faithfully-ported engine update — a parity self-check against packages/core logs to the browser console on load.

Gen·exact update — fixed shape (v1) vs moment-projection (v2)live
author a shape · trade · compare the two posteriors
priorv1 fixed shapev2 shape adaptssignal s
What to look for. With a symmetric prior (\(\lambda_3=\lambda_4=0\)) and an on-centre trade, v1 and v2 land on top of each other — v2 reduces to v1 when the posterior carries no skew. Push the trade off-centre (a far strike, a big size) and the exact posterior becomes asymmetric: v2 bends \(\lambda_3\) so its curve leans the way the flow points, while v1 can only shift a rigid bell. Best on a bimodal prior (\(\lambda_2<0\)): a one-sided buy reweights the two camps, so the true posterior is strongly skewed — watch v2’s \(\lambda_3\) saturate toward the camp the order favours while v1 just drifts the whole twin-peak. The readout shows the posterior skew/kurtosis v2 is matching and the \((\lambda_3,\lambda_4)\) it fitted.

8The bid–ask spread#

The MM never trades at fair — it adds a half-spread on the way in and subtracts on the way out. The spread is the sum of four risk charges:

$$ \begin{aligned} \text{Spread} &= \underbrace{s_0\,|\text{Fair}|}_{\text{base}} + \underbrace{\gamma\,|\,\text{mmShort}+q\,|\,|\text{Fair}|}_{\text{inventory}} \\[2pt] &\quad + \underbrace{\lambda\,\iota\,\big|\tfrac{\partial \text{Price}}{\partial\mu}\big|\,\sigma}_{\text{adverse selection}} + \underbrace{\eta\,\tfrac{\sigma}{\max(|\mu|,\sigma)}\,|\text{Fair}|}_{\text{volatility}} \end{aligned} $$spread.ts
  • Base — a flat \( s_0 \) (1%) of fair, the minimum edge.
  • Inventory — grows with the MM’s post-trade net position \( |\text{mmShort}+q| \); the more lopsided its book, the more it charges to lean further.
  • Adverse selection — \( \lambda \) times the trade intensity times the per-σ price move \( \big|\partial P/\partial\mu\big|\,\sigma \); big, price-moving orders pay more (they’re likely informed).
  • Volatility — scales with relative uncertainty \( \sigma/\max(|\mu|,\sigma) \); a jittery market quotes wider.
$$ \text{Ask (buy)}=\text{Fair}+\text{Spread},\qquad \text{Bid (sell)}=\max\!\big(0,\ \text{Fair}-\text{Spread}\big) $$
Worked example Belief \( \mathcal N(100,10^2) \), a CALL at \(K=100\) (fair \(=3.9894\)), a buy of 120 over an existing \( \text{mmShort}=200 \). The four charges are base \( 0.01(3.9894)=0.040 \), inventory \( 0.0005|320|(3.9894)=0.638 \), adverse \( 0.5(0.24)(0.5)(10)=0.600 \), volatility \( 0.05(0.1)(3.9894)=0.020 \) → spread \( \mathbf{1.298} \). You pay \( 3.989+1.298=\mathbf{5.288} \). Here inventory and adverse selection dominate — exactly the price-impact-sensitive terms.
Background — why a maker needs a spread (inventory & adverse selection)

An automated maker quotes both sides of every contract without knowing the outcome. It is not greedy for the spread — it needs it to survive two structural risks, and the four terms above are exactly those risks turned into numbers.

Adverse selection — the informed-trader problem. Some traders know more than the maker, and they only trade when the maker’s price is wrong in their favour: they buy what is cheap and sell what is dear. So averaged over its counterparties the maker systematically loses — it gets “picked off”. The classic market-microstructure result (Glosten–Milgrom) is that the only defence is to pre-charge every trade for the information it might carry, because you cannot tell the informed from the noise traders in advance. How large is that expected loss? A trade can move the belief, and the resulting price change is the price’s sensitivity to the mean times the mean’s own uncertainty, \( |\partial P/\partial\mu|\cdot\sigma \); a bigger order (intensity \( \iota \)) is both more likely to be informed and more costly when it is. That is the term \( \lambda\,\iota\,|\partial P/\partial\mu|\,\sigma \) — note it uses the very delta of §5, so contracts whose price barely reacts to μ are cheap to quote, and price-sensitive ones are dear.

Inventory risk — the unwanted-position problem. Every fill leaves the maker holding the opposite side, a book \( \text{mmShort}+q \) it never chose and cannot lay off on anyone else (it is the market). The more lopsided that book, the more a bad settlement hurts, so the maker skews its quotes to discourage growing it — the Avellaneda–Stoikov idea of inventory control. Charging in proportion to the post-trade position, \( \gamma\,|\text{mmShort}+q|\,|\text{Fair}| \), makes the side that flattens the book cheaper to trade and the side that worsens it more expensive, gently steering flow back toward neutral while paying the maker for the risk it carries in the meantime.

Volatility and base. A wider belief means any quote could be stale a moment later, so the spread scales with relative uncertainty \( \sigma/\max(|\mu|,\sigma) \). The \( \max \) in the denominator keeps this well-defined as \( \mu\to0 \) and caps the ratio at 1 (since the numerator can never exceed the denominator), so the volatility charge is always between 0 and \( \eta\,|\text{Fair}| \). The flat base \( s_0\,|\text{Fair}| \) is the floor — the minimum edge that keeps the maker profitable on benign, uninformed flow. Three of the four terms are expressed as a fraction of \( |\text{Fair}| \) so they scale with the contract’s magnitude; adverse selection is already in price units because \( |\partial P/\partial\mu|\cdot\sigma \) is itself a price move.

The bid is floored at 0 — the MM can charge to take a contract back but never pays you to hand it over. Drag size and inventory to see which component dominates:

Spread breakdownlive
μ=100, σ=12 fixed here

9Execution & position math#

Cost and cash, with signs

Total cost is signed by \( q \). A buy is a positive cost (you pay); a sell is negative (you’re paid). The MM’s cash moves opposite the trader’s wallet:

$$ \text{totalCost}=\text{execPrice}\cdot q,\qquad \text{cash} \mathrel{+}= \text{totalCost},\qquad \text{balance} \mathrel{-}= \text{totalCost} $$

Average-entry accounting

Positions are long-only in v1 and tracked by average entry. A buy blends the average; a sell realizes P&L against it and leaves the average unchanged until a full close resets it:

$$ \text{buy: } \overline{P}'=\frac{Q\,\overline{P}+q\,\text{exec}}{Q+q};\qquad \text{sell: } \Delta\text{realized}=|q|\,(\text{exec}-\overline{P}) $$tradeMath.ts · applyFill
Worked example You hold 100 units at average \( \overline{P}=4.00 \). Buy 50 more at \( 4.60 \): \( \overline{P}'=\tfrac{100(4)+50(4.6)}{150}=4.20 \). Later sell 60 at \( 5.20 \): realized \( =60(5.20-4.20)=\mathbf{60.00} \), quantity falls to 90, and the average stays \( 4.20 \) until you flatten completely.

Partial fills — solving the feasible size

If the full size would breach solvency (or the buyer’s balance), the engine fills the largest feasible amount. Both constraints tighten monotonically with size, so a 50-step binary search finds the frontier:

// tradeMath.ts · solveFill — feasibility test for a candidate size
const effectiveCash = cash + Math.min(0, totalCost);   // a buy's premium can't back its own risk
const reserveAfter  = requiredReserve(nextBook, belief);
const margin       = reserveAfter > reserveBefore ? openMargin : 1; // 1.2 to open, 1.0 to close
const solvent      = effectiveCash + EPS >= margin * reserveAfter;
const affordable   = side === 'sell' || isInfinite || balance + EPS >= totalCost;
Subtle but important effectiveCash = cash + min(0, totalCost) means a buy’s incoming premium is not counted as backing for the new risk it creates (only past cash backs risk), while a sell’s outgoing payout is subtracted. This stops a trade from self-funding unbounded exposure through its own spread.

10Reserve & solvency#

The MM is the counterparty to every trade, so it must hold enough cash to pay out in bad outcomes. The liability at a hypothetical outcome θ is what it would owe across its whole book:

$$ L(\theta)=\sum_{C}\text{mmShort}[C]\cdot f_C(\theta),\qquad \mathbb E[L]=\sum_C \text{mmShort}[C]\cdot\text{Price}(C) $$

The required reserve is the high-quantile (99th-percentile) liability under the belief — a Value-at-Risk. There’s no closed form across a mixed book, so it’s estimated by seeded Monte-Carlo:

$$ \text{Reserve}=\operatorname{quantile}_{\alpha}\big(\,L(\theta)\ \big|\ \theta\sim\mathcal N(\mu,\sigma^2)\big),\quad \alpha=0.99 $$solvency.ts
// draw 50k θ's from the belief, evaluate L, take the 99th-percentile loss
const draws = belief.sample(n, rng);            // seeded → reproducible
for (...) losses[i] = liability(book, draws[i]);
losses.sort();
return Math.max(0, losses[Math.floor(alpha * (n - 1))]);
Background — Value-at-Risk, Monte-Carlo, and the empirical quantile

Value-at-Risk (VaR). The α-VaR is the loss you will not exceed with probability α — i.e. the α-quantile of the loss distribution. At \( \alpha=0.99 \) it answers “how much could the book owe on all but the worst 1% of outcomes?”. Holding that much cash means only a 1-in-100 tail can breach the reserve.

Why Monte-Carlo. Across a mixed book (calls, binaries, spreads…) the total liability \( L(\theta) \) is a kinked, non-Gaussian function with no closed-form quantile. The Monte-Carlo method estimates it by brute force: draw many θ from the belief, evaluate \( L \) at each, and read the quantile off the empirical sample. The sampling error shrinks like \( 1/\sqrt n \), so 50k draws pin the 99th percentile to well under a cent.

Empirical quantile. Sort the \(n\) sampled losses ascending; the α-quantile is the value at rank \( \lfloor\alpha(n-1)\rfloor \) (the zero-indexed nearest-rank estimator). For \( \alpha=0.99,\ n=50{,}000 \) that is index 49499 — the 501st-largest loss (the 501 values at indices 49499…49999 sit at or above it). The \( \max(0,\cdot) \) clamps a net-credit book (negative liability) up to a non-negative reserve.

Worked example Book = 300 units of a BINARY_CALL at \(K=100\), belief \( \mathcal N(100,12^2) \). Each unit pays at most 1, so liability is capped at 300. The expected liability is \( 300\cdot\Phi(0)=\mathbf{150} \), but the 99th-percentile draw lands well into \( \theta\ge100 \) where every unit pays in full, so the required reserve is \( \approx\mathbf{300} \) — the MM must hold double its expected loss to survive the tail.

A trade is admitted only if cash covers a margin multiple of the post-trade reserve (1.2× when opening risk, 1.0× when closing). The plot shows the liability curve \( L(\theta) \) (red) against the belief; the dashed lines are the reserve and the expected liability — toggle inventory, μ, σ, and cash to trip the gate:

Liability, reserve & the solvency gatelive · MC
6k samples for snappiness
L(θ)reserve 99%E[L]belief

Production uses 50,000 samples seeded with 0x626d6d (“bmm”); this page uses 6,000 for responsiveness — the quantile is within a rounding cent.

11Settlement#

When an admin resolves the market with the true outcome \( \theta^\star \), each position pays its payoff. Full lifecycle (MarketStatus): CREATED → OPEN → RESOLVED(θ*) → SETTLED → CLOSED — a market is born CREATED (configured but not yet trading) and only accepts trades once OPEN. An admin may suspend a live market (OPEN ⇄ SUSPENDED), which halts trading without resolving it — the rapid-price-move circuit breaker of §14 recommends exactly this — or cancel it (→ CANCELLED), which unwinds every open position at cost rather than at payoff (below).

$$ \text{Payout}(C, q)=q\cdot f_C(\theta^\star),\qquad \text{Realized P\&L}=\text{Payout}-\text{costBasis} $$

Trader payouts are paid from the pool, but — by design — they do not mutate markets.cash directly; the residual is computed logically so the cash-flow ledger reconciles exactly (see the admin market ledger). The leftover pool after all trader payouts is the LPs’ to claim:

$$ \text{cashFinal}=\text{cash}-\sum_{\text{traders}}\text{Payout},\qquad \text{LP claim}=\frac{\text{shares}}{S_\text{total}}\cdot\text{cashFinal} $$lpMath.ts · settleMath.ts

Cancellation refunds

If a market is cancelled instead of resolved, no payoff is computed: every open position is refunded the capital still locked in it — its cost basis — and the MM hands back the premium it had collected. Only non-infinite (real) traders are credited:

$$ \text{Refund}(C,q)=q\cdot\overline{P}\qquad(\text{quantity}\times\text{average entry}) $$settleMath.ts · positionRefund

Proximity settlement

The GAUSSIAN contract is the proximity-reward mechanism: a point bet at \( c \) with tolerance \( w \) pays \( \exp(-(\theta^\star-c)^2/2w^2) \) — e.g. with \( w=1000 \), being 500 off pays \( \exp(-0.125)\approx 0.8825 \), being 2000 off pays \( \exp(-2)\approx 0.1353 \).

12Liquidity providers#

LPs fund the MM’s cash and own pro-rata shares of its net asset value. The NAV is liquid cash minus the mark-to-model liability; the share price is NAV per share:

$$ \text{NAV}=\text{cash}-\mathbb E_p[L(\theta)],\qquad \text{sharePrice}=\frac{\text{NAV}}{S_\text{total}}\ \ (\!=1\text{ at genesis}) $$

Deposits mint shares at the pre-deposit price (so you don’t dilute yourself); withdrawals burn shares for the current value (gated by the same 1.2× reserve rule):

$$ \Delta S=\text{amount}\cdot\frac{S_\text{total}}{\text{NAV}_\text{before}},\qquad \text{cashOut}=\frac{\text{shares}}{S_\text{total}}\cdot\text{NAV} $$lpMath.ts

At genesis the creator deposits the reserve \( R_0 \) and is minted \( S_0=R_0 \) shares (price 1). If the MM earns spread income, NAV rises above \( R_0 \) and the LP’s claim grows.

Worked example Pool NAV 1000 over 1000 shares → share price \( \mathbf{1.000} \); a deposit of 200 mints \( 200\cdot\tfrac{1000}{1000}=\mathbf{200} \) shares. Now the MM earns 100 of spread, lifting NAV to 1100 (price \( \mathbf{1.100} \)). The same 200 deposit then mints only \( 200\cdot\tfrac{1000}{1100}=\mathbf{181.82} \) shares — later entrants pay the appreciated price, so early LPs aren’t diluted by the profit they earned.
LP share accountinglive
deposit mints at the current price
Shares are minted at NAV/S so a deposit buys exactly its fair fraction of the pool — no dilution either way.

13Stats & metrics#

The “know your trade” analytics and market stats are all expectations under the belief. With fair price \( P=\mathbb E[f] \) and second moment \( m_2=\mathbb E[f^2] \):

$$ \text{E[payout]}=qP,\quad \operatorname{Var}[qf]=q^2\,(m_2-P^2),\quad \text{Std}=|q|\sqrt{m_2-P^2} $$

The second moment has closed forms too: for indicators \( f\in\{0,1\} \) so \( m_2=P \); for LINEAR \( m_2=\mu^2+\sigma^2 \); for GAUSSIAN it’s a proximity price with width \( w/\sqrt2 \). The kinked CALL/PUT payoffs have a closed \( m_2 \) only under a Student-t belief (an exact truncated-moment form, studentTKinkSecondMoment); under the Gaussian and the other kinds they fall back to the Simpson integrator of §4. Tail risk and win-probability come from the same Monte-Carlo draws:

MetricDefinition
P(profit)fraction of sampled outcomes with \( q f(\theta)-\text{costBasis}>0 \)
VaR₉₅5th-percentile P&L (the loss exceeded 5% of the time)
CVaR₉₅mean P&L within that worst 5% tail
Breakeven θsolve \( f(\theta)=\text{costBasis}/q \) — only for the monotone payoffs LINEAR / CALL / PUT (e.g. CALL: \( \theta=K+\text{costBasis}/q \)); null for bounded/indicator payoffs, where “breakeven” isn’t a single clean threshold
Calibration (inCi80)did \( \theta^\star \) land inside the final 80% CI? Should hold ≈80% of the time
Worked example A BINARY_CALL with fair price \( P=0.3085 \). Since an indicator squares to itself, \( m_2=P \), so the per-unit variance is \( P-P^2=0.3085(1-0.3085)=\mathbf{0.2133} \) and the per-unit standard deviation is \( \sqrt{0.2133}=\mathbf{0.4619} \). A 100-lot position therefore has \( \text{Std}=100\cdot0.4619=\mathbf{46.19} \) — the spread of its settlement value around the \( 100\cdot0.3085=30.85 \) expected payout.
Background — the variance identity, VaR vs CVaR

Variance from two moments. For any payoff, \( \operatorname{Var}[f]=\mathbb E[f^2]-(\mathbb E[f])^2=m_2-P^2 \) (§2). Scaling by \(q\) units multiplies the variance by \( q^2 \) (and the standard deviation by \( |q| \)), giving \( \operatorname{Var}[qf]=q^2(m_2-P^2) \). For an indicator \( f\in\{0,1\} \), \( f^2=f \) so \( m_2=P \) and the variance is the Bernoulli \( P(1-P) \).

VaR vs CVaR. Both summarise the loss tail. VaR₉₅ is a single threshold — the 5th-percentile P&L, the loss you beat 95% of the time. CVaR₉₅ (a.k.a. expected shortfall) is the average loss given you are already in that worst 5% — it sees how deep the tail goes beyond the threshold, which VaR alone hides. Necessarily \( \text{CVaR}\le\text{VaR} \) (it averages strictly worse outcomes). Both are read from the same sorted Monte-Carlo P&L sample.

14Circuit breakers#

After each trade the engine evaluates safety conditions and emits alerts (v1 alerts the admin; it does not auto-suspend). Thresholds compare against the genesis \( \sigma_0 \) and the reserve:

BreakerConditionSeverity → action
Belief divergence\( \sigma > 3\,\sigma_0 \)warning → alert
Rapid price move\( |\Delta\text{Price}|/\text{Price} > 10\% \)critical → suspend (recommended)
Insolvency risk\( \text{cash} < 1.2\,\text{reserve} \)critical → reject new risk
Insolvency warning\( \text{cash} < 1.5\,\text{reserve} \)warning → alert

15Capacity & the gate#

The reserve of §10 is not just a number the MM watches — it is a hard ceiling on how much risk a market can take on. A market's capacity is, in one line,

$$ \text{capacity}\ \sim\ \frac{\text{cash}}{\text{risk}},\qquad\text{enforced as}\quad \text{cash}\ \ge\ m\cdot\text{Reserve}. $$

Because the MM is the counterparty (§10), a buy is not pure cash-in — it creates a liability. The premium collected is small; the worst-case payout owed can be large, and it raises the reserve. So every risk-opening trade is checked against the cash that backs it.

The admission gate

Both the quote (preview) and the execute (commit) paths run the identical test inside solveFill, so they can never disagree. A candidate fill of signed size \( q \) is admitted iff:

$$ \underbrace{\big(\text{cash}+\min(0,\ \text{totalCost})\big)}_{\text{effectiveCash}}+\varepsilon\ \ge\ m\cdot\text{Reserve}_{\text{after}}(q),\qquad m=\begin{cases}1.2 & \text{Reserve}_{\text{after}}\gt\text{Reserve}_{\text{before}}\ \ (\text{opening risk})\\[2pt] 1 & \text{otherwise}\ \ (\text{reducing risk})\end{cases} $$tradeMath.ts · tradeSvc.ts

plus a buyer-side affordability constraint \( \text{balance}\ge\text{totalCost} \) (sells credit the trader, so they skip it). Two subtleties carry all the weight:

  • The opening margin \( m=1.2 \). A trade that raises the reserve must be backed by a 20% buffer of cash above the new reserve; a trade that lowers it needs only \( m=1 \). The buffer absorbs the Monte-Carlo sampling error in the reserve and any belief drift between sizing and settlement. (It also surfaces in §14 as the insolvency-risk breaker.)
  • effectiveCash excludes a buy's own premium. For a buy \( \text{totalCost}\gt 0 \), so \( \min(0,\text{totalCost})=0 \): the incoming premium is not counted as backing for the risk that same trade creates — otherwise a trade could self-fund unbounded exposure out of its own spread. The premium is still banked into cash (it becomes LP profit); it simply cannot collateralize itself. A sell has \( \text{totalCost}\lt 0 \), so its payout outflow is subtracted.

Partial fills

When the requested size doesn't fit, the engine doesn't reject outright — it sizes down. Both constraints are monotone in \( |q| \) (a bigger buy raises both the reserve and the cost), so solveFill binary-searches the largest feasible fill:

$$ q^\star=\max\{\,s\in[0,\,|q|]\ :\ \text{admit}(s)\,\},\qquad\text{reject only if } q^\star\approx 0. $$

The “no-buy band”

This explains the surprising symptom — an open, solvent market that still refuses every buy, even a tiny one. Let \( \rho=\text{Reserve}/\text{cash} \). The pool is solvent while \( \rho\le 1 \), but it opens new risk only while \( \rho\le 1/1.2\approx 0.83 \) (any buy makes \( \text{Reserve}_{\text{after}}\gt\text{Reserve}_{\text{before}} \), forcing \( m=1.2 \)). So in the band

$$ \tfrac{1}{1.2}\ \lt\ \rho\ \le\ 1\qquad\Longleftrightarrow\qquad \text{cash}\in[\,1.0,\ 1.2\,)\times\text{Reserve} $$

the market is solvent yet frozen for buys at any size; only trades that shrink the reserve — sells or offsetting bets — can pull \( \rho \) back down and thaw it.

Worked example Pool cash \( =110 \), required reserve \( =100 \) (\( \rho=0.91 \)). The market is solvent (cash > reserve), but a buy needs \( \text{cash}\ge 1.2\times\text{Reserve}_{\text{after}}\ge 120 \) — it has only 110, so every buy is rejected regardless of size. A holder then sells, cutting the reserve to 90; now \( 1.2\times 90=108\le 110 \) and buying reopens. Nothing was broken — the market was momentarily full.

Relaxing the limit — the developer's options

Capacity is \( \text{cash}/\text{risk} \), so there is no free lunch: every way to “bypass” the gate either adds capital, sheds risk, or hands the shortfall to someone. The full treatment with consequences is in docs/capacity/expanding-capacity.md; in brief:

ApproachIdeaCost / who bears the tail
Lower the buffer \( m \)1.2 → ~1.05thinner cushion; more exposed to MC / belief error
Lower \( \alpha_R \)reserve at 95% not 99%more capacity, more ruin events
Soft capprice the crowded side up steeply so demand chokes off — cliff → rampnone (the price bears it); preserves every guarantee
Solvency-factor haircutlet exposure exceed cash; scale payouts by \( s=\min(1,\,\text{cash}/\text{owed}) \)winning traders (payouts become contingent)
Insurance fund (V2-H)shared pre-funded backstop tops up shortfallsthe fund (mutualized across markets)
Hedging / reinsurance (V3-D)offload tail risk → lower reserve per unit of exposureexternal counterparty; basis risk
Auto-deleverageforce-close opposing positions to reclaim capacityopposing traders (forced)
Bounded contractscap payoff types so tails are known / smallernarrower product menu
Parimutuelwinners split losers' stakes (endogenous payouts)redefines the product entirely

The chosen direction is the soft cap: add a capacity-aware congestion term to the spread (§8) that is ≈ 0 with headroom and rises toward infinity as \( \rho\to 1 \), so the price ramps instead of hitting a wall — while the hard gate above stays as a backstop, so solvency and fixed payouts are untouched. Reference design:

$$ \text{congestion}(q)=\kappa\,|\text{Fair}|\cdot\frac{u^{a}}{1-\min(u,\ 1-\varepsilon)},\qquad u=\frac{m\cdot\text{Reserve}_{\text{after}}(q)}{\text{cash}}\quad\big(0\ \text{if }\text{Reserve}_{\text{after}}\le\text{Reserve}_{\text{before}}\big). $$

Status: the gate above ships today; the soft cap and the other options are design choices. The soft cap is planned but not yet implemented — see docs/capacity/soft-cap.md and its build plan. Code: tradeMath.ts (solveFill), tradeSvc.ts (acceptance gate), solvency.ts (reserve).

Interactive sandbox — a market at the gate, and every fix applied live

A faithful miniature of the trade engine, booted into the no-buy band: a default market that took so many one-sided buys it hit the gate, so every further buy is rejected. Pick any fix below and the market re-initialises in “after-fix” mode — it replays the same buy-demand under the new rules so you can see where it lands, then you can keep buying and watch the stats, the price ramp, the payouts, and the positions react. Every number is the real engine (window.BMM), not a mock-up.

Market: “Will the outcome θ exceed 100 by resolution?” · the crowd is long CALL(K=100) (pays \( \max(0,\theta-100) \)) · genesis \( \mu_0{=}100,\ \sigma_0{=}12 \)
Belief & the MM’s liability tail
Price to buy more — cliff vs ramp
Choose a fix — the market re-initialises under its rules:
Contract

Tip: in the blocked market, buying more CALL 100 is rejected (it opens risk) — but buying a PUT (the opposite side) is risk-reducing, drops to margin 1, and still fills. That’s the gate working as designed — it only blocks new risk, never offsets.

Payouts — what winners are owed, and the solvency factor

Positions — the crowd that filled the book, plus “You”

Computed client-side by capacity-engine.js (the DOM-free math, unit-tested against the server) and rendered by capacity-sandbox.js, both on window.BMM (the same port of packages/core that draws every figure on this page). Reserve is the closed-form 99%/α VaR of this single-CALL book — exactly the value the live engine estimates by Monte-Carlo. A–E and G run the real fix logic; F is a simplified hedge approximation; H & I are explained, not simulated (they redefine the product menu / payout rule, so they can’t mutate this one book). Design notes: expanding-capacity.md, soft-cap.md.

16Config parameters#

Every knob, its symbol, default, and what it tunes. Absolute σ-parameters are derived from the market’s genesis \( \sigma_0 \).

SymbolParamDefaultControls
s₀s00.01base spread (% of fair)
γgamma0.0005inventory spread per unit
λlambda0.5adverse-selection strength
ηeta0.05volatility spread
αalpha1.0call/put signal strength
βbeta1.0linear signal strength
Q_maxqMax500size that normalizes intensity ι
q_thqThreshold10noise gate on signal weight
σ_minsigmaMin0.1·σ₀floor on posterior σ (anti-overconfidence)
σ_εsigmaEps1.0·σ₀signal-noise σ (lower = trades move μ more)
α_RreserveAlpha0.99reserve VaR confidence
lr, decay0.01, 0.05simplified-update rate / σ-decay (flagged off)
useSimplifiedUpdatefalseswitch to the learning-rate update
genExactShapeAdapttrueGen·exact: true → v2 moment-projection (order flow sculpts skew/tails), false → v1 fixed shape (§7)

The Gen·basis spawn / mixture-management constants (\(\tau_\text{spawn}\), \(w_\text{min}\), \(w_\text{seed}\), \(\pi_\text{min}\), merge tolerance, the \(K\)-cap) live in mixture_ops.ts (and are consumed by placement.ts), while the Gen·exact shape-fit clamps (\(\lambda_3,\lambda_4\) ranges) live in bayes.ts — rather than in EngineConfig, since they are structural to the general model rather than per-market knobs — see §7.

17Numerics & rounding#

The special functions everything rests on (accurate to ~1e-12, validated against known values):

$$ \varphi(x)=\tfrac{1}{\sqrt{2\pi}}e^{-x^2/2},\qquad \Phi(x)=\tfrac12\,\operatorname{erfc}\!\big(-x/\sqrt2\big),\qquad \Phi^{-1}=\text{Acklam + 1 Halley step} $$

erf uses a Maclaurin series for \( |z|<2 \) and a Lentz continued fraction in the tails. Randomness flows through a seeded mulberry32 PRNG — never Math.random — so Monte-Carlo reserve and tests are reproducible.

Background — what each numerical method is (and why it’s here)
  • \( \operatorname{erf} \) and the link to \( \Phi \). The error function \( \operatorname{erf}(x)=\tfrac{2}{\sqrt\pi}\int_0^x e^{-t^2}\,dt \) is the Gaussian integral written in its standard form, and \( \operatorname{erfc}(x)=1-\operatorname{erf}(x) \) is its right tail. The normal CDF is just a shifted, rescaled copy of it: substituting \( t=u/\sqrt2 \) in \( \Phi \)’s integral turns it into an \( \operatorname{erf} \), giving \( \Phi(x)=\tfrac12\big(1+\operatorname{erf}(x/\sqrt2)\big)=\tfrac12\operatorname{erfc}(-x/\sqrt2) \). So implementing one well-conditioned \( \operatorname{erf} \) hands you every Gaussian probability on this page — pdf, cdf, prices, reserve — for free.
  • Why two methods for \( \operatorname{erf} \) (series vs continued fraction). Near zero the Maclaurin (Taylor-at-0) series \( \operatorname{erf}(z)=\tfrac{2}{\sqrt\pi}\sum_{n\ge0}\tfrac{(-1)^n z^{2n+1}}{n!\,(2n+1)} \) converges in a handful of terms. Out in the tails its terms first grow enormous and then nearly cancel, so finite precision is destroyed — there the code instead evaluates \( \operatorname{erfc} \) as a continued fraction, a nested ratio \( \cfrac{a_1}{b_1+\cfrac{a_2}{b_2+\cdots}} \) that converges fastest exactly where the series is worst. It is summed by Lentz’s algorithm, which accumulates the fraction from the top using running ratios, so nothing overflows and you needn’t fix the depth in advance. Handing over at \( |z|=2 \) keeps the whole real line at ~1e-12.
  • \( \Phi^{-1} \): Acklam’s approximation + one Halley step. No elementary function inverts \( \Phi \). Acklam’s algorithm splits \( (0,1) \) into a central region and two tails and fits each with a ratio of low-order polynomials (a rational approximation), reaching ~1.15e-9 relative error. One step of Halley’s method then polishes that to full double precision. Halley refines a root of \( g(x)=\Phi(x)-p \) using the value, the slope \( g'=\varphi \), and the curvature \( g''=-x\varphi \): \( x\leftarrow x-\dfrac{2\,g\,g'}{2\,g'^2-g\,g''} \). It is the cubically-convergent cousin of Newton’s method, so each pass roughly triples the number of correct digits (Newton only doubles) — taking ~9 good digits to the full ~16 in a single shot.
  • Inverse-transform sampling. If \( U \) is uniform on \( (0,1) \) then \( F^{-1}(U) \) has exactly the distribution whose CDF is \( F \) — because \( P\big(F^{-1}(U)\le\theta\big)=P\big(U\le F(\theta)\big)=F(\theta) \). For a normal that reads \( \theta=\mu+\sigma\,\Phi^{-1}(u) \): push a uniform through the inverse-CDF and a Gaussian draw comes out. That is precisely how the reserve’s Monte-Carlo θ’s (§10) are generated — one cheap \( \Phi^{-1} \) evaluation per sample, all sharing the same seeded uniform stream.
  • mulberry32. A tiny 32-bit seeded generator: from a single integer of state it emits a fast, well-spread sequence of uniforms. Because it is seeded, every uniform — and therefore every reserve estimate, every sampled draw, every test — is bit-for-bit reproducible across runs and machines, the one guarantee an unseeded Math.random cannot give.

Money & rounding

All money is numeric(20,8). Every arithmetic result goes through round8round-half-even (banker’s rounding) to 8 decimals — so repeated operations don’t accumulate floating-point drift and ledgers reconcile to the cent:

// shared/src/money.ts
if (diff > 0.5) rounded = floor + 1;
else if (diff < 0.5) rounded = floor;
else rounded = floor % 2 === 0 ? floor : floor + 1; // tie → even

18Model validation — the Monte-Carlo harness#

Beyond per-function unit tests, sim.ts runs the entire loop end-to-end against synthetic informed traders to confirm the engine actually learns, stays calibrated, and earns its spread. A single run draws a hidden truth, lets noisy-but-informed traders arrive one by one, and settles:

$$ \begin{aligned} \theta_\text{true} &\sim \mathcal N(\mu_0,\sigma_0^2)\\[2pt] \text{trader sees } y &= \theta_\text{true}+\sigma_\text{obs}\,\varepsilon,\quad \text{edge}=y-\mu,\ \ \text{buys a CALL/PUT struck at } K{=}\mu\\[2pt] \text{size } q &= \max\!\Big(1,\ \min\big(1,\tfrac{|\text{edge}|}{2\sigma}\big)\cdot Q_\text{max}\Big) \quad(\text{conviction in } \sigma\text{-units})\\[2pt] \text{MM} &: \text{quote} \to \text{spread} \to \text{collect premium} \to \text{Bayes-update belief}\\[2pt] \text{settle} &: \text{pay } q\,f(\theta_\text{true}),\ \ \text{tally MM and trader P\&L} \end{aligned} $$sim.ts · simulateRun

Aggregated over many runs — one seeded RNG threads the whole experiment, so the report is fully reproducible — the summary answers two questions: did the posterior beat the no-learning prior, and is the credible interval honest?

MetricMeaningHealthy
meanBeliefError\( \overline{|\mu_\text{final}-\theta_\text{true}|} \)< meanPriorError (it learned)
meanPriorError\( \overline{|\mu_0-\theta_\text{true}|} \) — the baseline to beatreference
rmseBeliefErrorroot-mean-square posterior errorlow
calibration80fraction with \( \theta_\text{true}\in[\mu_f\pm1.2816\,\sigma_f] \)\( \approx 0.80 \)
meanMmPnlmean MM profit (premium income − settlement payouts)\( \gtrsim 0 \)
meanUserWelfaremean per-trader realized profitinformed traders \( \gtrsim 0 \)
What it does and doesn’t cover The harness models spread income vs settlement payouts — the P&L that drives calibration and profitability — but deliberately does not run the reserve / solvency gate (§10); that path is exercised by the API integration tests. So it’s a diagnostic and a parameter-tuning tool: vary cfg, compare summaries, and pick the engine defaults of §16.

19Price impact — how trades & liquidity move price#

This section ties the earlier pieces together into the one thing a trader actually feels: how much the price moves. There are two distinct effects. A trade leaves a lasting mark — it shifts the belief, so the fair price stays moved after the trade (this is price impact). It also pays a transient round-trip cost — the spread (§8) — that is not a price move at all, just a fee around the mid. We focus on the lasting part.

The lasting move is just the fair price (§4) re-evaluated on the post-trade belief (§7). To first order it factors cleanly into a delta (§5, how much price reacts to the mean) times a mean shift (§7, how far the trade drags the mean):

$$ \Delta\text{Fair}=\text{Fair}(\text{belief}')-\text{Fair}(\text{belief}) \ \approx\ \underbrace{\frac{\partial\text{Price}}{\partial\mu}}_{\text{delta}}\cdot \underbrace{\Delta\mu}_{\text{mean shift}},\qquad \Delta\mu=\kappa\,(s-\mu),\quad \kappa=\frac{\tau_s}{\tau_0+\tau_s}=\frac{w}{w+\sigma_\varepsilon^2/\sigma^2} $$§5 × §7

Both halves are driven by the trade intensity \( \iota=|q|/Q_\text{max} \): the signal reach \( s-\mu \) grows with \( \iota \) (§6) and so does the trust weight \( w \) (§6), which lifts the blend factor \( \kappa \). That single ratio is the lever behind all three charts below — and notice \( Q_\text{max} \) (market depth) sits in the denominator, so more liquidity shrinks every trade’s intensity, hence its impact.

All three plots price the same probe contract — a BINARY_CALL struck at the mean, so its fair price reads directly as the crowd’s probability \( P(\theta\ge K)\in[0,1] \), starting at 0.50. Flip the one switch to see a buy push it up vs a sell push it down:

Price impact — three viewslive
market N(100, 12²) · probe = Bin·Call @100

① Trade size

One trade of growing size. Bigger order ⇒ higher intensity ⇒ bigger lasting move.

post-trade pricestart 0.50

② Trade count

Many same-size, same-side trades in a row. Each adds a move; σ shrinks, so they fade.

price after n tradesstart 0.50

③ Liquidity (LP)

Same fixed trade into a deeper pool. More LP ⇒ depth Q≈0.5·LP ⇒ less impact.

impact vs liquiditystart 0.50
How each chart reads off the formula ① Size sweeps \( |q| \) (so \( \iota \) and \( w \)) with depth fixed: the move grows with size but bends over as the weight saturates. ② Count applies the same trade repeatedly — every fill nudges \( \mu \) again, but each Bayesian update also shrinks \( \sigma \) (§7), so later trades carry less reach and the binary saturates toward 1 (or 0). ③ Liquidity holds the trade fixed and grows the pool: deeper liquidity raises \( Q_\text{max} \), which divides down the intensity \( \iota=|q|/Q_\text{max} \), so the very same order barely moves a deep market. This is the precise sense in which liquidity does not set the price — it sets how hard the price is to move.
Why liquidity touches impact, not the level The fair price is \( \mathbb E_\text{belief}[f(\theta)] \) — a pure function of the belief, with no cash term. So an LP deposit never changes today’s price; it changes the market’s depth (modelled here as \( Q_\text{max}\propto \) pool liquidity), which is what every trade’s intensity is measured against. Chart ③ is therefore flat-ish on the right: past a point, more capital just makes the line harder to bend, exactly as a deep order book resists slippage. (Liquidity’s other job — backing the reserve gate of §10 so large trades are admitted at all — is covered there.)

20Slippage & price impact vs standard prediction markets#

Is a continuous belief market economically competitive with the well-known discrete platforms — Polymarket, Kalshi, Augur (central limit order books), and Manifold, Gnosis/Omen, Futuur (automated market makers)? The two costs a trader actually pays are:

  • Price impact — how far the trade leaves the price moved (where the marginal price ends up). This is a lasting effect and a cost to the next trader / to you on the round trip.
  • Slippage — the premium you pay over the pre-trade price to fill your order (your average fill minus the mid).

Every automated mechanism shares one law: impact and slippage scale like \( 1/\text{liquidity} \). What differs is the shape of the curve and, crucially, how the cost is packaged. The four families:

Family · platformsMechanismMarginal price after buying \(x\)Liquidity knob
Order book (CLOB)
Polymarket · Kalshi · Augur
match against resting limit orders\( 0.5+x/\rho \) (linear, then a gap when the book runs out)posted depth \( \rho \) — only what makers choose to post
LMSR
Manifold · Gnosis · Hanson
logarithmic scoring rule\( \sigma(x/b) \) (logistic; smooth saturation)\( b \) — set by the LP subsidy \( b\ln 2 \)
CPMM
Omen · Futuur · Uniswap-style
constant product \( R_yR_n{=}k \)\( (R{+}t)^2/\!\big(R^2{+}(R{+}t)^2\big) \) (convex)reserves \( R \) — the LP collateral
BMM (ours)
continuous belief MM
re-price on the updated belief\( \Phi\big((\mu'{-}K)/\sigma'\big) \) — Bayesian move (§19)depth \( Q_\text{max}\propto \) LP capital

Drag liquidity — now log-scaled all the way to the billions, so you can watch every curve collapse toward dead-flat as LP capital grows — and drag trade size to read each mechanism at a chosen order. Flip the soft-cap fix switch (off by default) to overlay the capacity-congestion premium from the soft-cap design onto the BMM line: the faint line is BMM today, the bold line is BMM with the fix, and the dashed marker is the pool's capacity. All start at the same mid (\(0.5\)) on a binary, matched to a common liquidity budget so the comparison is apples-to-apples (calibration is approximate — the shapes and the \(1/\text{liquidity}\) scaling are the real lesson):

Continuous vs discrete — impact & slippagelive
binary @ mid 0.50 · matched liquidity
BMM (ours) LMSR CPMM Order book

Price impact

Marginal price left behind after buying \(x\) shares. Higher ⇒ the trade moved the market more.

Slippage — cost to fill

Average premium over the mid to acquire \(x\) shares. Ours is a single-price spread; AMMs/books make you walk the curve.

What the two charts show Impact (left). All four obey \(\text{impact}\propto x/\text{liquidity}\), so they overlap for small orders near the mid — a continuous belief market is in the same economic regime as the standard AMMs, not worse. They part company on big orders: LMSR/CPMM saturate smoothly toward 1, the order book is dead-linear until it runs out of resting orders (then a price gap), and BMM bends up a little faster because each fill also sharpens the belief (σ shrinks, §19). Slippage (right). Here the packaging differs. On an AMM or order book you eat your own impact: you walk up the curve and your average fill is the integral under it, so slippage grows with size and there is no separate fee. On BMM you fill the whole order at one quoted price (mid + spread) and the price moves afterwards — so there is no intra-order slippage; the cost is an explicit, tunable maker spread that becomes LP revenue, and the impact is deferred to the next trade.
The economic verdict — and the real risk difference Cost regime: comparable. Impact scales as \(1/\text{liquidity}\) in every system, so with matched LP capital our continuous market is neither cheaper nor dearer in kind — it sits in the same band as LMSR and CPMM. (Its explicit spread is a tunable config; the engine defaults here are conservative.) The decisive advantage is always-on, continuous liquidity. Order-book venues (Polymarket, Kalshi) only have the depth makers choose to post — a thin or one-sided book means a huge gap or simply no fill, and a trader can be stuck unable to exit. AMMs and BMM always quote a two-sided price, so liquidity never disappears. And BMM prices a continuum: one belief prices any strike or range (§3–§4), where a discrete market only offers fixed buckets — so a “between 100 and 105” bet that has no contract elsewhere is just another query here. How much risk does a user take? The same \(1/\text{liquidity}\) slippage risk as a standard AMM, minus the order-book’s thin-market / no-exit risk, plus a known spread fee instead of unknown curve-walking. Initial liquidity is the single dial that makes all of it acceptable — exactly as the left chart shows every curve collapsing toward flat as LP capital grows.
The soft-cap switch — cliff → ramp at the capacity frontier Today, BMM's cost drifts gently and then a separate, binary gate (\(\text{cash}\ge 1.2\times\) reserve) slams shut: a market can look open and still refuse every buy. Turn the switch on and the BMM line picks up a fifth spread term — the capacity-congestion premium \( \kappa\,|\text{fair}|\;u^{a}/(1-u) \), where utilisation \( u=mR_1/C \) rises toward \(1\) as the pool nears its solvency frontier (capacity \(\propto\) liquidity). Watch what changes: with headroom the bold line sits right on top of the faint "today" baseline (healthy markets are untouched), but as the order approaches the dashed capacity marker the price ramps up convexly instead of hitting a wall — demand prices itself out smoothly and the market never has to issue a hard rejection. Drag liquidity up and the capacity marker shoots off the chart: with deep books the premium is invisible. This is a UX-at-the-limit fix, not a bigger ceiling — it converts the cliff into a ramp without touching the solvency guarantee or any payout (see soft-cap.md).

21Flexible parametric beliefs — a design study#

Status: the design study that shipped two models Some markets let traders paint almost any curve by clicking to place predictions — a non-parametric, per-bucket design (LMSR / pari-mutuel over bins). This section asks the opposite question: how much shape flexibility can we get while staying parametric — continuous \(\theta\), contract-composition, Bayesian-from-signal updates, closed-form-or-quadrature pricing — without going bin-based? It is kept as the reasoning that produced two now-shipped models: the recommended fixed-basis sweet-spot became Gen·basis ⛓ (a mixture belief with mode-spawning + the placement primitive) and the max-entropy form became Gen·exact ★ — both selectable at creation today (see §2, updates in §7). The other rows (skew-normal, generalized-normal, Beta) remain un-built alternatives, kept for the trade-off they illustrate. Full write-up in docs/multi model/.

The key reframe: in a parametric world flexibility = the number of shape degrees of freedom (DOF), and there is a continuous spectrum between a Gaussian and a histogram:

$$ \underbrace{\text{Gaussian}}_{2}\ \to\ \underbrace{\text{skew / peaked}}_{3}\ \to\ \underbrace{\text{skew-}t\,/\,\text{gen-hyperbolic}}_{4}\ \to\ \underbrace{\text{mixture}}_{3K}\ \to\ \underbrace{\text{fixed-basis}}_{N\ \text{weights}}\ \to\ \cdots\ \to\ \underbrace{\text{bins}}_{\infty} $$

Every family below already fits the engine: BeliefModel needs only pdf/cdf/mean/variance/sample, and expectF prices any pdf by quadrature when no closed form exists. So the question is which family, traded off along four axes — shape power, pricing cost, update difficulty, and on-chain feasibility. Pick a family and morph it; the scorecard reads its tradeoff live (drawn against the equal-\((\mu,\sigma)\) Gaussian, so any gap is pure shape):

Flexible-beliefs scorecardlive
morph each family · read flexibility vs implementation cost
candidate p(θ)Gaussian(μ,σ)tail strike K
The menu — flexibility vs implementation cost
FamilyDOFAddsPricingUpdateOff-chainOn-chain
Gaussian2baseline bumpclosed-formconjugate (exact)shippedmoderate — Φ approx
Skew-normal3asymmetryquadraturemoment-matchlowhard — Φ inside ∫
Generalized-normal3peak ↔ flatquadrature + Γmoment-matchlowhard — Γ, frac. powers
Student-t3fat tailsquadrature + Γvariance-domain (shipped)shippedhard — Γ, pow
Beta (bounded)2–3U / J / skew / flatincomplete-βnear-conjugatelow–medhard — incomplete-β
Mixture3KK distinct campsclosed-form Σresponsibility (shipped)shippedmoderate — K·Φ
Gen·basis ⛓ (on-chain)N weightsANY # bumps · skew · flatclosed-form Σ·Φ · fastweight-onlylowfeasible — N·Φ, linear
Gen·exact ★ (off-chain)2 + Mskew·peak·flat·bimodalquadraturemoment-projectionmedvery hard — numeric ∫

Two threads run through that table. First, pricing path decides on-chain feasibility. Fixed-point chains can afford a polynomial approximation of \(\Phi\) but not \(\Gamma\), the incomplete beta, or a 4000-node quadrature run deterministically across nodes. So the only families whose price is a finite \(\sum_k w_k\,\Phi(\cdot)\) — Gaussian, mixture, and fixed-basis — are realistic on-chain; the special-function families are off-chain-only without heroic effort.

Second, the recommended sweet spot is the fixed-basis Gaussian density: \(N\) narrow bumps on a fixed grid with learnable weights,

$$ p(\theta)=\frac{1}{\sum_k w_k}\sum_{k=1}^{N} w_k\,\mathcal N(\theta;\,c_k,\,\sigma^2) $$design study

It gives near-arbitrary smooth shapes (universal approximation as the grid densifies), prices in closed form as \(\sum_k w_k\cdot\text{price}_k\) — so no quadrature lag and the most on-chain-feasible flexible option — and updates by a trivial weight-only step. Best of all, a “click near \(\theta\)” maps directly onto “raise the weights of nearby bumps”, which is the parametric way to get the paint-the-curve feel without bins.

One general form for every shape — the creator picks nothing

A creator can’t know whether the crowd will converge, split into camps, or fear a fat-tailed surprise. The ideal is a single general belief that supports (almost) any shape with as many parameters as the data demands. Two forms qualify (full write-up: docs/multi model/general-belief-form.md):

(A) Adaptive Gaussian mixture / basis ⛓ — the on-chain-feasible general mode. Finite Gaussian mixtures are dense in the space of distributions, so the mixture is the general model and every family above is a special case (one component → Gaussian; offset pair → skew; a scale-mixture → Student-t tails; \(K\) separated bumps → any number of modes). With component split/merge the parameter count is data-driven — it grows modes on disagreement, collapses on consensus. Crucially it drops the one thing that makes a general form expensive — numerical normalisation — because pricing stays closed-form (`Σ πₖ·Φ`, linear in the weights). That is the less-complex mode for on-chain: a finite sum of one well-approximated function (Φ), no quadrature, no Γ. It is the Gen·basis ⛓ option in the sandbox — and the form to ship as a general kind the creator selects without choosing a shape.

(B) Max-entropy / moment expansion — the literal “\(N\) parameters describe the belief” dial:

$$ p(\theta)\ \propto\ \exp\!\Big(-\big[\tfrac12\lambda_2 u^2+\lambda_3 u^3+\lambda_4 u^4+\cdots\big]\Big),\quad u=\tfrac{\theta-\mu}{\sigma} $$General ★

Each polynomial term is one more shape knob: \(\lambda_2\) → Gaussian, \(+\lambda_3\) → skew, \(+\lambda_4>0\) → fat tails / flat-top, and \(\lambda_2<0\) → a double-well → bimodal. The mode count is bounded by the polynomial degree (degree \(2m\) → up to \(m\) modes), and 3+ visible modes need a higher-degree poly with finely-balanced wells — so for arbitrary multi-bump the basis form (A) is the practical tool. One formula, a continuum of shapes — always a valid (max-entropy) density, but normalised by numerical integration, so it prices by quadrature and is an off-chain tool. It is the Gen·exact ★ option in the sandbox above — sweep \(\lambda_2\) negative to split one bump into two from a single exponential family.

The half nobody mentions — the interaction A flexible family is inert unless trades can excite its DOF. A plain trade collapses to one scalar signal (§6), which can only slide or reweight — it cannot grow a bump where a user is betting. This is the half that had to be built alongside the representation, and it now is: Gen·basis enables mixture component spawn (a confident off-mode signal seeds a fresh camp) and a placement primitive — the bell contract routes to placeBasisBump, additively painting mass at \(c\) (§7); Gen·exact’s moment-projection update lets flow bend its skew and tails. The representation and the interaction are two halves of the same lever — and both halves shipped together.
Then → Now — exciting the shape with trades
Then · one scalar, slide-or-reweight

A trade decoded to a single \((s,w)\). It could shift \(\mu\), tighten \(\sigma\), or reallocate existing mixture weight — but it could never add a mode or bend a shape. The density’s degrees of freedom beyond location/scale were inert: authored once, frozen.

Now · flow grows & sculpts the curve

Three channels excite the extra DOF: spawn (off-mode conviction seeds a new Gen·basis camp), placement (a bell trade additively paints a persistent bump), and moment-projection (Gen·exact re-fits skew/kurtosis from the posterior).

Why · and what changed in the market

Economics: a representation the order flow can’t reach is just a fancier prior — the crowd’s disagreement would still be averaged into a single quoted shape. Market behaviour: placement is deliberately additive, not consensus-forming, so two camps trading at distinct prices accumulate into two persistent peaks instead of collapsing onto the most-recent one (the responsibility update would have merged them) — the order book literally paints the belief. The cost is that richer input needs richer guards: spawn is rate-limited by \(w_\text{min}\) and the \(K\)-cap, and component-management prunes/merges redundant bumps so the parameter count stays data-driven rather than runaway.