Complementary-Outcome Arbitrage on Polymarket: How It Works
When YES + NO prices sum to less than $1 across a binary market, a risk-free spread exists. This guide explains how to detect, size, and execute complementary-outcome arbitrage on Polymarket using the CLOB API and UMA's CTF adapter.
Complementary-outcome arbitrage on Polymarket exists whenever the best ask on YES plus the best ask on NO sums to less than $1.00 — guaranteeing a $1.00 payout at resolution. It sounds clean in theory; in practice the edges are thin, the fill windows are narrow, and most surface-level implementations bleed fees. This guide is about building one that doesn't.
The Mechanics of the Edge
Every binary market on Polymarket issues two conditional tokens — YES and NO — through UMA's Conditional Token Framework (CTF) adapter. At resolution, exactly one token pays $1.00 and the other pays $0.00. They are complementary by construction.
A risk-free position exists when:
ask_YES + ask_NO < 1.00
If you buy YES at p and NO at q where p + q < 1.00, your guaranteed profit per dollar of notional is 1.00 - p - q, regardless of outcome. That's the edge — before fees.
The friction is real:
- Taker fee: Polymarket's CLOB charges ~2 bps per fill on the taker side. With two legs, you pay it twice.
- Slippage: order books on smaller markets are thin. The best ask may only be available for 20–50 USDC before the book steps up. If you size past that, your effective entry degrades.
- Execution risk: between your YES fill and your NO fill, the book can move. Markets where the YES/NO sum dips to 0.98 have often recovered to 1.00 by the time you submit the second leg.
Net threshold in practice: the raw spread needs to exceed roughly 0.5–1.0 cents (ask_YES + ask_NO < 0.990–0.995) to clear fees and yield something worth executing. Markets that occasionally print 0.97 or lower are genuinely tradeable; markets that flicker at 0.999 are not.
Scanning the CLOB for Mispricings
Polymarket's CLOB REST API exposes order books per token ID. You need to track both token IDs for every binary market and compute the sum continuously.
import requests
BASE = "https://clob.polymarket.com"
def get_best_ask(token_id: str) -> float | None:
r = requests.get(f"{BASE}/book", params={"token_id": token_id}, timeout=3)
r.raise_for_status()
book = r.json()
asks = book.get("asks", [])
if not asks:
return None
return float(min(asks, key=lambda x: float(x["price"]))["price"])
def check_arb(yes_token: str, no_token: str) -> float | None:
ask_yes = get_best_ask(yes_token)
ask_no = get_best_ask(no_token)
if ask_yes is None or ask_no is None:
return None
total = ask_yes + ask_no
return (1.0 - total) if total < 0.99 else None
At scale this polling approach hits rate limits. The correct production setup uses the WebSocket feed — subscribe to book channel messages for both token IDs — and maintains an in-memory order book, recomputing the sum on every delta. That keeps latency under 100 ms and avoids the per-request overhead entirely.
For a scanner running across 200+ active markets, a single event-driven loop handles all books in parallel with asyncio. The Polymarket arbitrage bot we ship at TierZero runs exactly this architecture, with fallback REST polling for markets that go quiet on the WebSocket.
Sizing the Trade Correctly
The available edge is capped by the depth at the best ask on each side. If YES best ask shows 50 USDC available and NO shows 80 USDC available, your maximum size is 50 USDC — the binding constraint.
Fee-adjusted sizing:
FEE_RATE = 0.002 # 20 bps round-trip across two taker legs
def max_size(spread: float, depth_yes: float, depth_no: float) -> float:
net_spread = spread - FEE_RATE
if net_spread <= 0:
return 0.0
# cap at the shallower side
raw_size = min(depth_yes, depth_no)
return raw_size
def expected_profit(size: float, spread: float) -> float:
return size * (spread - FEE_RATE)
Below some profit floor — say $0.50 per trade — the execution overhead (gas on Polygon, monitoring cost, nonce management) erodes the edge to noise. Set a minimum expected_profit threshold and skip anything below it.
One subtlety: the spread you observe includes one penny of slippage tolerance on each side. If you are buying through the spread at market and the book is illiquid, always check that the next resting level isn't 3 cents worse before committing.
Execution: Legs and Timing
The structural risk in any two-legged arb is leg-failure. If your YES order fills but your NO order doesn't, you hold a binary YES position with no hedge — you've taken on directional prediction-market risk.
Mitigation strategies in order of preference:
Both legs as limit orders near the ask. Post IOC (immediate-or-cancel) orders at the current best ask level. If either leg fails to fully fill, cancel the other. The CLOB supports IOC via
time_in_force: "IOC"in the order payload.YES leg first, then NO leg immediately. If YES fills, submit NO within the same event loop tick. Monitor fill events via WebSocket rather than polling — fill latency on Polymarket's WebSocket is typically under 200 ms, which is fast enough to catch a book move before submitting leg two.
Hard stop on stale fills. If more than 2 seconds have elapsed since leg one filled without leg two confirming, submit a market sell of the YES position. This costs you the spread plus slippage but bounds the directional exposure.
Authentication uses EIP-712 signatures from your Polygon wallet. Keep a nonce tracker in memory and never reuse — nonce collisions cause silent order rejection on the CLOB. Understanding how UMA's CTF adapter handles resolution is worth internalizing here too: the CTF tokens you accumulate settle automatically post-resolution, so you don't need to manually redeem positions if you hold through the event.
What Makes Markets Dislocate
Most arb opportunities on Polymarket originate from one of three sources:
- Stale quotes from passive market makers. A market-making bot that refreshes on a 5-second timer leaves windows when the book shifts. Your arb scanner runs continuously; theirs doesn't.
- News-driven one-sided flow. A breaking update hits. Retail traders buy YES aggressively, but NO bids don't get lifted. For a few seconds, the combined ask sum is below parity.
- Low-liquidity frontier markets. Markets with under $5k open interest have wide spreads and lazy order management. These print arb opportunities more frequently but with smaller depth — often 20–40 USDC per opportunity.
High-liquidity major markets (US election outcomes, top crypto price markets) rarely dislocate because dedicated market makers with tight loops watch them. Scanning across 100+ mid-tier markets consistently finds more edge per hour than focusing on the top 10.
The Real Constraint: Capital Efficiency
Complementary-outcome arb locks capital until resolution. A market that closes in 6 hours returns your profit in 6 hours; a market closing in 45 days ties up capital for 45 days. The annualized return on a 0.5-cent edge with 30-day capital lock-up is roughly 6% APY — not bad, but not the reason to build this infrastructure.
The actual return profile comes from running across many short-duration markets simultaneously. Polymarket's 5-minute crypto up/down markets and 1-hour sports markets settle fast enough that capital turns over dozens of times daily. A bot that captures a 0.5-cent edge on 10 fills per day at $100 size each earns $0.50/day with modest capital — or 18% annualized on $1,000 deployed. The math scales horizontally with the number of monitored markets, not with size per trade.
If you want a production scanner and execution engine built around your capital and risk tolerance, reach out to TierZero. We design, build, and operate Polymarket arbitrage systems end to end — CLOB integration, execution logic, and live monitoring included.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article