The Pricing Engine: From Risk Score to Adjusted Fair Price
Executive Summary
A risk score is interesting. A risk-adjusted fair price is tradeable. SettleRisk's pricing engine takes the score, the dispute probability, and the settlement delay distribution and returns four numbers that a market maker can plug directly into a quoting system: adjusted fair price, risk premium in bps, capital lockup cost in bps, and fair spread in bps. This post derives each formula, walks through a worked Kalshi example, and explains the do_not_quote flag.
Core Concept
The pricing engine is closed-form. Given a market m with score s, dispute probability p, and expected delay d:
adjusted_fair_price = mid_price * (1 - p * loss_ratio)
risk_premium_bps = p * loss_ratio * 1e4
capital_lockup_bps = d_quantile * (cof_annual / hrs_per_year) * 1e4
fair_spread_bps = 2 * (risk_premium_bps + capital_lockup_bps) + base_mm_margin
do_not_quote = (fair_spread_bps > spread_ceiling) OR (s.tier == "CRITICAL")
Where:
loss_ratiois the expected loss conditional on a dispute, calibrated per platform (Polymarket=0.42, Kalshi=0.28)d_quantileis the chosen delay quantile (defaults to p90)cof_annualis your annualized cost-of-fundsbase_mm_marginis your floor spread (e.g. 25 bps for a tight book)spread_ceilingis the absolute cap above which you refuse to quote
The flag do_not_quote=true means the math says you shouldn't quote — either spread is too wide to be competitive, or the market is CRITICAL and the loss distribution has too long a tail to size around.
Worked Example
A Kalshi market on "Will the US enter formal recession by Q3 2026?" is quoted at 22/24 cents.
from settlerisk import SettleRiskClient
client = SettleRiskClient(api_key="sk-...")
price = client.price(
market_id="kalshi:recession-q3-2026",
mid_price=0.23,
cost_of_funds_annual=0.15,
delay_quantile="p90",
spread_ceiling_bps=500,
base_mm_margin_bps=25,
)
print(price.model_dump_json(indent=2))
Output:
{
"adjusted_fair_price": 0.2224,
"risk_premium_bps": 76.00,
"capital_lockup_cost_bps": 11.42,
"fair_spread_bps": 199.84,
"do_not_quote": false,
"explain": {
"tier": "MEDIUM",
"p_dispute": 0.183,
"delay_p90_hours": 66.7,
"loss_ratio": 0.28,
"platform": "kalshi"
}
}
Interpretation:
- The unbiased mid is 23 cents; risk-adjusted fair is 22.24 cents (you discount 0.76 cents to compensate for dispute risk)
- Quote 21.74 / 22.74 around the adjusted fair to capture the fair spread
- You're not in
do_not_quoteterritory but you're at 200 bps — wider than most casual MM books would quote
TypeScript:
import { SettleRiskClient } from "settlerisk";
const client = new SettleRiskClient({ apiKey: "sk-..." });
const price = await client.price({
marketId: "kalshi:recession-q3-2026",
midPrice: 0.23,
costOfFundsAnnual: 0.15,
delayQuantile: "p90",
spreadCeilingBps: 500,
baseMmMarginBps: 25,
});
console.log(`Adjusted fair: ${price.adjustedFairPrice}`);
console.log(`Spread: ${price.fairSpreadBps} bps`);
console.log(`DNQ: ${price.doNotQuote}`);
Implementation Notes
The pricing endpoint is XOR on inputs. Pass exactly one of mid_price (when you have a market quote) or p_event (when you have a model probability and want the engine to derive everything else). Passing both returns PRICING_MUTUALLY_EXCLUSIVE_INPUT; passing neither returns PRICING_MISSING_PRICE_INPUT.
The explain block is your audit trail. Every pricing call returns the underlying tier, p_dispute, delay quantile, loss ratio, and platform. Persist these alongside the priced output for compliance and TCA.
Loss ratio is platform-specific. Polymarket disputes recover less for the losing side because UMA's bond-and-challenge keeps part of the bond. Kalshi disputes recover more cleanly because the venue refunds the position notional. Don't unify the constants.
| Platform | loss_ratio | Reasoning |
|----------|-------------|-----------|
| Polymarket | 0.42 | UMA bond economics, longer disputes |
| Kalshi | 0.28 | CFTC supervised refund flow |
| Future (e.g. Drift, OptionBlitz) | 0.35 default | Calibrate as data arrives |
Batch pricing scales well. The /v1/pricing:batch endpoint accepts up to 1,000 markets per call with partial-success semantics. Each item in the response carries either a result or an item-level error. Use this for nightly book repricing.
Failure Modes
1. Treating adjusted_fair_price as a directional signal. It is a risk-adjusted reference price, not a forecast. It tells you what to quote against, not which side to take.
2. Skipping do_not_quote. If the engine says don't quote, don't quote. The flag fires when spread economics break, not when the market looks risky in your gut.
3. Mixing quantile assumptions. Capital lockup at p90 with risk premium at p50 produces incoherent pricing. Pick one cohort assumption.
4. Caching pricing output. Don't cache the pricing block — it is a function of inputs that change every refresh. Cache the score and delay block separately; recompute pricing on every quote.
5. Ignoring base_mm_margin. The engine's fair_spread_bps includes your base margin. If you add your margin again downstream, you double-charge.
Checklist
- [ ] Pick exactly one of
mid_priceorp_event - [ ] Persist the full
explainblock - [ ] Respect
do_not_quote - [ ] Pick a single delay quantile and stick with it
- [ ] Batch reprice your book on a regular schedule
- [ ] Subscribe to
score.tier_changedto force immediate reprice
Sources + Further Reading
- SettleRisk pricing engine — full formula reference
- Methodology — derivation of loss ratios
- Capital lockup post — the carrying-cost component
- Theory of the Bid-Ask Spread (Stoll 1989) — base MM margin foundations
- UMA Optimistic Oracle docs — bond economics
Get a free key at /signup and price any market in under 200ms.
Get weekly risk analysis in your inbox
Market risk scores, emerging dispute patterns, and settlement delay trends — delivered every Monday.