How to build a cross-platform arbitrage scanner for Kalshi + Polymarket in 2026
Operator-honest build guide. The scanner is one of the four buildable projects in prediction-market quant work (per the backtest read). Here's the actual stack, the data sources, and the structural barriers.
TL;DR
An arb scanner watches the same underlying event on Kalshi + Polymarket + UD Predict + PrizePicks for implied-probability divergence.
3-5 percentage points after fees + slippage is the minimum profitable threshold; 8-15pp shows up after news shocks.
The hard part isn't the API code — it's fuzzy event matching across platforms + execution latency.
Resolution criteria can differ across platforms — "the arb" is not always risk-free.
The architecture (operator-honest reference)
┌─────────────────────────────────────────────────────────────┐│ EVENT LISTING SCRAPER LAYER│ Kalshi API → /events, /markets endpoints│ Polymarket → CLOB API + on-chain reads│ Underdog → HTML scrape (no public API)│ PrizePicks → HTML scrape (no public API)└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐│ EVENT MATCHER (the hard part)│ Fuzzy name matching + manual override table│ Resolution-criteria reconciliation flags│ Stores matched events to SQLite/Postgres└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐│ DIVERGENCE CALCULATOR│ Implied probability per platform│ Net of fees + slippage│ Alert when |Δ| > threshold (operator-set)└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐│ HUMAN OPERATOR (decides + executes)│ Reviews the alert│ Checks resolution-criteria alignment│ Executes both sides if real arb (or one side as carve)└─────────────────────────────────────────────────────────────┘
Note the last layer: operator-in-the-loop. Fully automated execution is technically possible but operator-honest practice keeps a human approving each cross-platform position. Resolution-criteria mismatches catch you when you trust the scanner blindly.
The divergence threshold math
Cost basis to clear before an arb is profitable:
Cost
Kalshi side
Polymarket side
Per-contract fee
~$0.01-0.03
Variable (Polygon gas typically $0.01-2)
Slippage on entry
0.5-2% in thin markets
1-3% in thin markets
Resolution timing
Usually same day
Same day to days
Effective cost per side
~1-3%
~2-4%
Combined cost ~3-5%. So divergence below 5 percentage points usually isn't profitable. Above 8 points it's interesting. Above 15 points (rare, news-shock-driven) it's the moment.
The hard problem: fuzzy event matching
Same event has different identifiers across platforms:
KALSHI: "PRES-2026" — "Will Trump win 2026 election?"
POLYMARKET: "trump-wins-2024-3" — "Donald Trump wins 2024 US election"
UD PREDICT: "Election 2024" — outcome-coded as Trump/Harris/Other
Match attempt by name string: FUZZY
Match attempt by resolution date: USEFUL
Match attempt by resolution criteria: MANUAL OVERRIDE NEEDED
The operator move: build an event-matching table that maps platform IDs to a canonical event-id. Maintain it manually for high-value events (elections, major sports finals). Use fuzzy name matching as a first-pass filter, manual confirmation for actual position-taking.
The Python skeleton (minimum viable)
import asyncio, httpx, pandas as pd
async def fetch_kalshi_markets(client):
r = await client.get("https://api.elections.kalshi.com/trade-api/v2/markets")
return r.json()["markets"]
async def fetch_polymarket_markets(client):
r = await client.get("https://clob.polymarket.com/markets")
return r.json()
async def scan_arbs(threshold_pp=8):
async with httpx.AsyncClient(timeout=10) as c:
k = await fetch_kalshi_markets(c)
p = await fetch_polymarket_markets(c)
# Match events (your matching table here)
matched = match_events(k, p)
for event in matched:
k_prob = event.kalshi_yes_price / 100
p_prob = event.poly_yes_price
diff_pp = abs(k_prob - p_prob) * 100
if diff_pp > threshold_pp:
print(f"⚡ ARB: {event.name} · Kalshi {k_prob:.2%} vs Poly {p_prob:.2%} · Δ {diff_pp:.1f}pp")
asyncio.run(scan_arbs())
~50 lines to a working v1. The remaining ~95% of effort is the matching table, alert routing (Slack/SMS), and execution-side automation.
Structural barriers (the honest 4)
1. Event-matching is fuzzy
Same event, different identifiers. Fuzzy name matching catches 70-80%; the rest needs human curation. Maintain a manual mapping table.
2. Latency kills arbs
By the time you see the divergence on screen, the other side may have moved. Sub-second polling on both APIs is the table stakes. WebSocket subscriptions where available (Kalshi).
3. Capital fragmentation
You need funded balance on each platform. KYC on each. Account history on each. The operator capital lock per platform is real — you can't run the scanner from a single bank account.
4. Resolution criteria mismatch
Kalshi may settle "Trump wins" on Electoral College; Polymarket may settle on Popular Vote. If the scanner doesn't know this, the "arb" can resolve as a loss on both sides. This is why operator-in-the-loop is mandatory.
⚠️ Not financial advice. Prediction-market arbitrage is high-execution-skill work. Most scanners surface signals that aren't actually risk-free arbs once resolution criteria are reconciled. Start with paper-trading + small live positions before scaling capital.
How this connects to the SideGuy framework
The arb scanner is the scanner layer · once a divergence is real, the position taken IS a Betting Lab release-carve operation. You release on one platform (Kalshi or Polymarket), and the opposite-side position on the other platform IS the carve.
Per the release-carve operating system doctrine, the divergence isn't a "perfect trade" · it's the substrate that creates the day's carving opportunity. The scanner surfaces the release moment; the operator carves through the resolution window.
Want the full framework?
The SideGuy Betting Lab is the public doctrine — 12 layers of structure from bracket / middle / gap through release-carve.