← Back to blog
·5 min read·SettleRisk Team

The Pricing Engine: From Risk Score to Adjusted Fair Price

Deep Dive

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_ratio is the expected loss conditional on a dispute, calibrated per platform (Polymarket=0.42, Kalshi=0.28)
  • d_quantile is the chosen delay quantile (defaults to p90)
  • cof_annual is your annualized cost-of-funds
  • base_mm_margin is your floor spread (e.g. 25 bps for a tight book)
  • spread_ceiling is 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_quote territory 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_price or p_event
  • [ ] Persist the full explain block
  • [ ] 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_changed to 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.