DEV Community

xTRFzx
xTRFzx

Posted on

Building a Multi-Exchange Prediction Market Arbitrage Scanner in Python (Polymarket, Kalshi, Predicton)

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


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()
Enter fullscreen mode Exit fullscreen mode

Top comments (0)