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.
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.
Paginates Polymarket's /events endpoint, filters to active
NegRisk categoricals, normalizes into pydantic models, upserts idempotently
into SQLite, and marks dropped events inactive.
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.
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.
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.
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.
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.
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.
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.
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
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 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.
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.