Cross-Platform Risk Arbitrage: Pricing Differentials Between Polymarket and Kalshi
Executive Summary
When Polymarket and Kalshi both list a market on the same underlying event — a presidential election, a Fed rate decision, a court ruling — the prices rarely match. Some of the spread is liquidity. Some is platform-specific cost. And some is a real difference in resolution risk between the two venues. This post walks through how to decompose the spread, with a worked example on an election market and the SDK calls to do the decomposition automatically.
Core Concept
The observed price differential between two venues for the same event has three components:
price_diff = liquidity_premium + cost_diff + risk_diff
Where:
liquidity_premiumis the venue-specific bid/ask compressioncost_diffcaptures fees, withdrawal costs, capital-cost differencesrisk_diffis the resolution-risk-adjusted difference in fair price
risk_diff is the part SettleRisk computes. If Polymarket has p_dispute of 0.18 and Kalshi has p_dispute of 0.09 on the same event, the venues' adjusted fair prices differ even if the underlying probability is the same.
| Component | Typical magnitude | Source | |-----------|------------------|--------| | Liquidity premium | 5-30 bps | Order book depth | | Cost diff | 10-50 bps | Fees, withdrawal, capital | | Risk diff | 10-150 bps | Resolution risk profile |
For a market trading at 50 cents, a 100 bps risk diff means a 0.5-cent difference in fair price purely from resolution-risk asymmetry.
Worked Example
A presidential election market — "Will Candidate X win the November 2028 election?" — listed on both venues.
Observed quotes:
- Polymarket: 41 / 43 cents
- Kalshi: 40 / 44 cents
Naive arb: buy Polymarket bid 41, sell Kalshi ask 44, capture 3 cents.
SettleRisk decomposition:
from settlerisk import SettleRiskClient
client = SettleRiskClient(api_key="sk-...")
poly_price = client.price(
market_id="polymarket:2028-election-candidate-x",
mid_price=0.42,
cost_of_funds_annual=0.10,
)
kalshi_price = client.price(
market_id="kalshi:2028-election-candidate-x",
mid_price=0.42,
cost_of_funds_annual=0.10,
)
print(f"Polymarket adjusted fair: {poly_price.adjusted_fair_price:.4f}")
print(f"Kalshi adjusted fair: {kalshi_price.adjusted_fair_price:.4f}")
print(f"Risk diff: {(poly_price.adjusted_fair_price - kalshi_price.adjusted_fair_price) * 1e4:.2f} bps")
Polymarket adjusted fair: 0.4034
Kalshi adjusted fair: 0.4156
Risk diff: -122.00 bps
The 3-cent spread you thought you were arbing is actually 1.22 cents of "fair compensation" for Polymarket's higher resolution risk, plus ~1.78 cents of real edge. After fees, withdrawals, and capital-lockup differences, you may have closer to 0.5 cents of true alpha — much less than the headline 3 cents suggested.
TypeScript:
import { SettleRiskClient } from "settlerisk";
const client = new SettleRiskClient({ apiKey: "sk-..." });
const [poly, kalshi] = await Promise.all([
client.price({ marketId: "polymarket:2028-election-x", midPrice: 0.42 }),
client.price({ marketId: "kalshi:2028-election-x", midPrice: 0.42 }),
]);
const riskDiffBps = (poly.adjustedFairPrice - kalshi.adjustedFairPrice) * 1e4;
console.log(`Risk diff: ${riskDiffBps.toFixed(2)} bps`);
Implementation Notes
Match markets carefully. Two markets on "the 2028 election" can differ in subtle ways: resolution date, candidate eligibility rules, withdrawal handling. Read both rule sets before treating them as the same event.
Risk diff is not constant over time. A Polymarket market scored MEDIUM yesterday can flip to HIGH today on a rule edit, breaking the prior risk-diff calculation. Re-pull both scores before sizing.
Adjust for venue-specific recovery rates. Polymarket disputes recover differently from Kalshi disputes. The pricing engine handles this via platform-specific loss_ratio constants.
| Cost component | Polymarket | Kalshi | |----------------|-----------|--------| | Trading fees | ~2% per side | ~0.7% per side | | Withdrawal fees | Gas costs (variable) | None | | Capital lockup | Stablecoin yield foregone | T-bill yield foregone | | Recovery on dispute | UMA-mediated | CFTC-supervised refund |
Use the batch endpoint for portfolio decomposition. Decomposing risk-diff across 100+ cross-listed markets is one call to /v1/pricing:batch with both venues' market IDs.
Failure Modes
1. Treating any price gap as alpha. Most of the gap is platform cost and risk diff. The residual after accounting for both is the real alpha.
2. Skipping rule-text comparison. Markets that look the same can resolve differently. Always read both rule sets.
3. Stale score caching. A score that updated since you priced the arb invalidates the analysis. Re-pull before executing.
4. Ignoring capital-cost asymmetry. Polymarket positions earn stablecoin yield; Kalshi positions earn T-bill yield. The yield differential is part of cost_diff.
5. Confusing risk-diff with directional view. A negative risk-diff doesn't mean "fade Polymarket." It means Polymarket's fair price is lower than Kalshi's given the same underlying probability. That gap is rational compensation for higher resolution risk.
Checklist
- [ ] Read both venue rule sets before declaring same-event
- [ ] Pull
/v1/pricingfor both venues with consistent assumptions - [ ] Compute risk diff = poly_adj_fair - kalshi_adj_fair
- [ ] Subtract platform cost diff to isolate alpha
- [ ] Re-pull scores before executing on any decomposition
- [ ] Use batch pricing for portfolio-level analysis
Sources + Further Reading
- SettleRisk pricing engine — adjusted fair price math
- Compare page — Polymarket vs. Kalshi structural differences
- Capital lockup post — venue-specific cost
- Cross-Market Arbitrage in Event Contracts — Hanson (2003)
- Polymarket fee schedule, Kalshi fee schedule (public docs)
Free key at /signup — price the same event on both venues in one call.
Get weekly risk analysis in your inbox
Market risk scores, emerging dispute patterns, and settlement delay trends — delivered every Monday.