Sourcing the Data Layer for a Polymarket / Kalshi Arbitrage Dashboard
Key Takeaways
What makes Polymarket and Kalshi hard to compare directly? Polymarket is a decentralized, crypto-settled prediction market with outcome shares priced 0–1. Kalshi is a CFTC-regulated exchange with a traditional bid/ask order book denominated in cents (0–100). The same real-world event can be listed with different question wording, different resolution criteria, and different closing times on each — you're matching semantically equivalent markets, not identical tickers.
What data do you actually need to spot a mispriced pair? At minimum: the implied probability (price) on both platforms, the bid-ask spread on each (a wide spread erases small mispricings after execution cost), and liquidity/volume (a mispricing with no depth behind it isn't tradeable).
Is this actually arbitrage, or directional betting on convergence? True riskless arbitrage across two separate platforms is rare once you account for withdrawal friction, KYC differences, and settlement timing — most builders in this space are really building a divergence monitor, flagging when two platforms disagree on the same event, not executing zero-risk trades.
Two Different Market Structures, One Question
Before any dashboard logic, understand what you're actually comparing. A Polymarket market returns outcome shares and a price implied directly from the order book:
{
"question": "Will JD Vance win the 2028 US Presidential Election?",
"outcomes": ["Yes", "No"],
"outcomePrices": [0.32, 0.68],
"volume": 15000000.50,
"liquidity": 500000.00,
"bestBid": 0.31,
"bestAsk": 0.33,
"spread": 0.02,
"endDate": "2028-11-05T00:00:00Z"
}
A Kalshi market nests individual contracts inside an event, priced in cents on a 0–100 scale, and needs one extra step to normalize before it's comparable:
{
"series_title": "New York Governor",
"event_title": "New York Governor winner?",
"category": "Elections",
"total_volume": 295509,
"markets": [
{ "ticker": "GOVPARTYNY-26-D", "yes_subtitle": "Democratic party",
"yes_bid": 89, "yes_ask": 90, "last_price": 89, "volume": 223875 }
]
}
Pull both feeds with Polymarket Market Scraper and Kalshi Scraper — both expose category/searchQueries filters, which is how you narrow to the event categories you're actually tracking (elections, macro, crypto) instead of pulling every open market on both platforms every run.
Normalizing to One Probability Scale
The core normalization is trivial once you see it: Polymarket's outcomePrices are already a 0–1 probability. Kalshi's yes_bid/yes_ask are cents on a 0–100 scale — divide by 100 and you have the same unit.
function normalizeMarket(record, source) {
if (source === "polymarket") {
return {
question: record.question,
impliedYesProb: record.outcomePrices[0],
bid: record.bestBid,
ask: record.bestAsk,
spread: record.spread,
volume: record.volume,
liquidity: record.liquidity,
};
}
if (source === "kalshi") {
const m = record.markets[0]; // one contract per event, for a binary market
return {
question: record.event_title,
impliedYesProb: m.last_price / 100,
bid: m.yes_bid / 100,
ask: m.yes_ask / 100,
spread: (m.yes_ask - m.yes_bid) / 100,
volume: m.volume,
liquidity: null, // Kalshi doesn't expose a direct liquidity field like Polymarket
};
}
}
Matching Equivalent Markets Across Platforms
This is the part that can't be fully automated: "Will JD Vance win the 2028 election" and "2028 Presidential Election Winner — JD Vance" are the same underlying bet with different question text. Two practical approaches, in order of effort:
- Manual mapping table for your tracked events — a small JSON file pairing a Polymarket market ID to its Kalshi event ticker. Tedious to build, trivially reliable once built. This is the right call if you're tracking a fixed watchlist (majors elections, Fed rate decisions) rather than every market on both platforms.
- Fuzzy match on question text (normalized, stopwords stripped, key entities extracted) as a first pass, with the pairs above a similarity threshold flagged for manual confirmation before they enter your live feed. Necessary if you want broad category coverage instead of a fixed watchlist, but it will produce false pairs you need to catch.
Computing the Divergence Signal
Once two markets are paired, the metric worth watching isn't the raw probability difference — it's the difference net of both spreads, since that's the part that actually survives execution cost:
function divergence(polyMarket, kalshiMarket) {
const rawDiff = Math.abs(polyMarket.impliedYesProb - kalshiMarket.impliedYesProb);
const combinedSpreadCost = polyMarket.spread + kalshiMarket.spread;
const netEdge = rawDiff - combinedSpreadCost;
return { rawDiff, combinedSpreadCost, netEdge, tradeable: netEdge > 0.02 };
}
A 5-point raw disagreement between platforms with a combined 4 points of spread on both sides leaves 1 point of real edge — usually not worth the platform-switching friction. This is why liquidity and spread matter as much as the headline price gap: most of what looks like arbitrage on a raw price comparison disappears once you price in execution.
The Rewards Layer, If You're Market-Making Instead
If your goal is providing liquidity rather than capturing divergence, Polymarket separately tracks which markets pay active liquidity-provider rewards — a different, lower-risk way to earn from the same underlying data. The Polymarket Rewards Scraper returns the daily reward rate and the spread/size requirements to qualify for it:
{
"question": "Next PM of Hungary - Péter Magyar?",
"rewardsDailyRate": 2000, "rewardsMaxSpread": 3.5, "rewardsMinSize": 200,
"competitive": 0.91, "volume": 5200000.00
}
Filter on minRewardRate and sort by competitive (lower means less crowded) to find markets where you can post inside the required spread without competing against a dozen other market makers for the same reward pool.
FAQ
Do you need a wallet or exchange account just to pull this data? No — both actors scrape public market data, no API key or account required on either platform. You only need credentials once you move from monitoring to actually placing trades.
How often should this run? Prediction market prices move continuously, but a 5–15 minute polling interval is enough for a divergence monitor — sub-minute polling mostly adds cost without adding a usable edge unless you're also automating execution.
If you're building signal-tracking dashboards more broadly, see Tracking Congressional Stock Trades and Insider Form 4 Filings, or browse the full News and finance actor list on ParseBird.