Polymarket Arbitrage Scanner

Production-grade Python asyncio system that detects NegRisk multi-outcome arbitrage on Polymarket in real time, simulates fills against the live order book with a realistic latency penalty, and can sign and submit live orders behind hard risk caps.

60 tests passing 94% core-math coverage python 3.12 asyncio fastapi websockets web3.py sqlite (wal) docker MIT

The arbitrage, in one paragraph

Polymarket hosts categorical events — "Who wins the 2028 US Election?" — where every outcome trades as its own YES token on a public CLOB. Because exactly one outcome must win, the fair prices across all outcomes are bound to sum to exactly $1. When the sum of best-asks drops below $1, buying one share of every outcome locks in a guaranteed $1 payout on resolution. The challenge isn't detecting the mispricing — it's racing faster bots, walking order-book depth honestly so edge isn't a fantasy at top-of-book, paying realistic Polygon gas, and surviving UMA resolution disputes that can void a "risk-free" basket. This project builds the full pipeline for that strategy, end to end, with paper mode as the primary validation tool.

~2klines of python
60unit tests
94%coverage (core)
~5stest suite time
2modes (paper / live)
7risk caps enforced

What's in the box

Gamma REST discovery

Paginates Polymarket's /events endpoint, filters to active NegRisk categoricals, normalizes into pydantic models, upserts idempotently into SQLite, and marks dropped events inactive.

WebSocket L2 maintainer

Subscribes to the CLOB market channel. Parses book snapshots and price_change deltas against a per-token sorted price ladder. Shards across sockets, keeps connections alive with PING/PONG, reconnects with exponential backoff.

Opportunity engine

On every book tick, walks depth across all outcomes of the affected event, computes the basket size that maximizes net expected profit after fees and amortised gas, emits a typed Opportunity record only when the edge clears a configured threshold.

Paper executor

Simulates IOC fills at detection time + latency penalty against the live book, so levels that vanished during the simulated latency window model "being beaten by a faster bot." Writes baskets + per-leg fills to SQLite; closes out PnL when the underlying event resolves.

Live executor (gated)

Signs EIP-712 orders through py-clob-client, submits FAK across all legs in parallel, attempts to unwind any partial fill, calls NegRiskAdapter.redeemPositions on the winning leg. Default dry_run=True; real broadcast requires an explicit operator flip.

Risk gate

Hard caps before any order touches the network: max basket USD, max open baskets (global and per-event), daily loss stop, kill-switch file, proximity-to-resolution skip. Every deny is logged with its reason.

FastAPI + HTMX dashboard

Single-page viewer that polls SQLite every few seconds for live opportunities, open and historical baskets, realized paper PnL, and mode indicator. One-click kill-switch toggle. No SPA build step.

Docker + CLI

Single-binary CLI (arb init | discover | scan | web | resolve) plus Dockerfile + compose file for a one-command local run. Runs on a $5/month GCP VM or on a laptop.

Architecture

        Gamma REST                 CLOB WebSocket
            │                            │
            ▼                            ▼
     Event discovery              L2 Book Maintainer
    (active negRisk)           (per token, in-memory)
            │                            │
            └────────────┬───────────────┘
                         ▼
                Opportunity Engine
      (depth-walk, fee-net, gas-amortized threshold)
                         │
                         ▼
                    Risk Gate
       (basket caps, daily loss, kill switch)
                         │
            ┌────────────┴───────────────┐
            ▼                            ▼
      Paper Executor              Live Executor
   (latency-penalized          (sign + FAK + redeem)
    sim fills + PnL)                     │
            │                            │
            └────────────┬───────────────┘
                         ▼
                    SQLite (WAL)
                         │
                         ▼
             FastAPI + HTMX dashboard

Run it yourself

Local (Python 3.12+)

git clone https://github.com/matthewnyc2/arbitrage
cd arbitrage
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env

arb init
arb discover
arb scan &
arb web
# http://127.0.0.1:8000

Docker

docker compose up
# http://127.0.0.1:8000

Stays in paper mode — no keys, no capital at risk. Kill with Ctrl+C or by touching ./KILL.

What this is (and isn't)

This is a working engineering project — a reference for how to structure a latency-sensitive async trading bot with disciplined risk gates, honest simulated fills, and a testable core. It is not a get-rich tool. The retail edge in public prediction-market arbitrage has mostly been competed away by professional market makers with colocation, custom hardware, and seven-figure working capital. Paper mode here exists specifically to answer the question is there any edge left for a solo Python bot, before a single dollar is risked.

The architecture transfers cleanly to any order-book venue (Kalshi, Manifold, CEX spot markets) — swap the REST + WS adapters and the engine keeps working.