In efficient financial markets, the sum of probabilities for a mutually exclusive set of event outcomes must strictly equal 1.00 (100%).
However, across prediction markets like Polymarket (Polygon CLOB), Kalshi (CFTC regulated), and Predicton (non-custodial / zero-KYC), liquidity fragmentation and geographic restrictions frequently cause pricing dislocations. When the combined ask price falls below parity, risk-free mathematical arbitrage (a Synthetic Dutch Book) is possible.
To monitor these discrepancies in real time, we built an open-source terminal: OmniPredict.
Interactive Links & Repositories
- 🐙 GitHub Repository: omnipredict-terminal
- 🚀 Run in Browser (No Install): Open in Google Colab
- 🌐 Full Mathematical Documentation: Prediction Markets Intelligence Hub
The Mathematical Formula: Synthetic Dutch Book
In binary event contracts, each share pays $1.00 if the outcome resolves affirmatively and $0.00 if it resolves negatively.
When the combined ask price across exchanges satisfies:
$$\sum_{i=1}^{n} \text{Price}_{\text{Ask}}(\text{Outcome}_i) < 1.00$$
A quantitative trader can buy 1 share of YES for each outcome across separate order books. Because exactly one outcome must resolve, the gross payout is guaranteed at $1.00:
$$\text{Net Profit} = 1.00 - \sum \text{Price}_{\text{Ask}}(\text{Outcome}_i) - \text{Taker Fees}$$
Asynchronous Python Scanner Architecture
Below is the core scanning logic that compares order books and flags mispriced baskets:
python
import asyncio
import aiohttp
from tabulate import tabulate
class PredictionMarketScanner:
def __init__(self, fee_buffer=0.015):
self.fee_buffer = fee_buffer
def fetch_quotes(self):
return [
{
"event": "Fed Rate Cut (Next FOMC 25bps)",
"quotes": {
"Polymarket": {"yes": 0.48, "no": 0.53},
"Kalshi": {"yes": 0.51, "no": 0.50},
"Predicton": {"yes": 0.46, "no": 0.52}
}
},
{
"event": "Bitcoin Exceeds $120k in 2026",
"quotes": {
"Polymarket": {"yes": 0.38, "no": 0.63},
"Kalshi": {"yes": 0.41, "no": 0.61},
"Predicton": {"yes": 0.36, "no": 0.62}
}
}
]
def scan_arbitrage(self):
markets = self.fetch_quotes()
for m in markets:
venues = list(m["quotes"].keys())
for i in range(len(venues)):
for j in range(len(venues)):
if i != j:
v1, v2 = venues[i], venues[j]
yes_price = m["quotes"][v1]["yes"]
no_price = m["quotes"][v2]["no"]
total_cost = yes_price + no_price
effective_cost = total_cost + (total_cost * self.fee_buffer)
if effective_cost < 1.00:
net_profit = 1.00 - effective_cost
roi = (net_profit / effective_cost) * 100
print(f"[SPREAD DETECTED] {m['event']}")
print(f" Buy YES @ {v1} (${yes_price:.2f}) + Buy NO @ {v2} (${no_price:.2f})")
print(f" Combined: ${total_cost:.2f} | Net ROI: +{roi:.2f}%\n")
if __name__ == "__main__":
scanner = PredictionMarketScanner()
scanner.scan_arbitrage()
Top comments (0)