How to Build a Polymarket Arbitrage Bot (2026)
Three real arbitrage opportunities exist on Polymarket today: YES+NO mispricing, multi-outcome odds-summing, and cross-venue spreads against Kalshi and Manifold. This is the math, the working Python, and the honest list of reasons your bot will lose money anyway.
The three arbitrage edges that are real
Polymarket prices binary and categorical events. Three pricing dislocations occur often enough to be worth coding against:
- YES + NO < $1.00 on a binary market — buy both, lock the payoff.
- Sum of multi-outcome prices < $1.00 across mutually-exclusive outcomes (e.g. an election with 5 candidates).
- Cross-venue spread — same event priced differently on Polymarket vs Kalshi vs Manifold. Buy the cheap side, hedge on the rich side, hold to settlement.
Each of these has the same shape: a guaranteed payoff at expiry, gated by execution costs. Each also has a population of bots already watching the headline markets, so where you find edge is in the long tail.
The math, in one box
For a binary market quoted in YES/NO probabilities p_yes and p_no, with taker fee f and slippage s at your size:
edge_per_share = 1.00 - p_yes - p_no - 2*f - s
expected_pnl = edge_per_share * shares
condition = edge_per_share > 0
For a categorical market with n outcomes:
edge_per_share = 1.00 - sum(p_i for i in outcomes) - n*f - s
condition = edge_per_share > 0
Read this twice. The constant is 1.00, not 1. If the sum of probabilities you can buy ever lands below 1.00 net of fees and slippage, you have a deterministic profit at expiry. The whole bot exists to detect that condition and execute fast enough that it survives the 200ms it takes for everyone else to notice.
The minimum viable bot in 80 lines
We build directly on py-clob-client. No agent layer needed for pure arbitrage.
import time
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType
FEE = 0.02 # taker, both sides combined
THRESHOLD = 0.005
client = ClobClient(host="https://clob.polymarket.com", chain_id=137, key=PRIVATE_KEY)
client.set_api_creds(client.create_or_derive_api_creds())
def best(side, book):
levels = book.asks if side == "YES" else book.bids
return float(levels[0].price), float(levels[0].size)
def scan(market):
yes_book = client.get_order_book(token_id=market["yes"])
no_book = client.get_order_book(token_id=market["no"])
p_yes, sz_yes = best("YES", yes_book)
p_no, sz_no = best("YES", no_book) # buying NO == taking the ask
edge = 1.0 - p_yes - p_no - FEE
size = min(sz_yes, sz_no)
return edge, size, p_yes, p_no
def fire(market, p_yes, p_no, size):
for token_id, price in [(market["yes"], p_yes), (market["no"], p_no)]:
client.create_and_post_order(OrderArgs(
price=price, size=size, side="BUY", token_id=token_id,
), OrderType.FOK) # fill-or-kill: no half-fills
while True:
for m in MARKETS:
edge, size, p_yes, p_no = scan(m)
if edge > THRESHOLD and size >= 10:
fire(m, p_yes, p_no, min(size, MAX_SHARES))
time.sleep(0.5)
Run that against a list of 50 long-tail markets and you have a working bot. Run it against the top 10 and you will lose money on the gas alone — those are saturated.
Where the edge actually goes to die
The math above is the sales pitch. Production reality is the rest of this section.
- Gas. Each order is a Polygon transaction. Two-leg arb = two transactions per round-trip = roughly $0.04–$0.20 in gas at 2026 fee levels. Your edge has to clear that floor at every entry and exit.
- Slippage on the second leg. Between filling YES and posting NO, the NO book moves. The standard fix is to use FOK (fill-or-kill) on both legs and accept that 30–60% of detected opportunities will not execute. The remaining 40–70% are real.
- Adverse selection. When a long-tail market shows YES + NO < 1.00, ask why. Often it is because resolution is ambiguous and the smart money is letting you take the wrong side. Read the market description before posting. A bot that does this with an LLM (the only thing LLMs are clearly good at on prediction markets) will outperform one that does not.
- Mempool latency. Polygon block times are ~2 seconds. A faster bot has already eaten the spread before your transaction lands. Run a private RPC and a high-priority gas tip if you are running real capital.
- Resolution risk. Polymarket markets resolve via UMA's optimistic oracle. Disputes happen. A "won" arbitrage can be re-resolved against you if the oracle decision is contested. Keep a buffer in your sizing.
From script to production
The 80-line bot above is the demo. The production checklist is the work:
- Persist every signal, decision, and fill to disk. You will need it when something breaks.
- Cap exposure per market and per outcome. A single misread market description can sink a quarter of edge.
- Monitor your bot's PnL per leg, not just total. Half-filled trades hide as flat PnL while accumulating directional exposure.
- Pre-compute the universe of markets nightly. Filter for liquidity > $5k, expiry > 24h, and resolved-market provenance from a trusted oracle source.
- Have a kill switch. RPC outages and oracle disputes both look exactly like "the bot is making money" until they don't.
When to graduate to an agent runtime
Pure arbitrage is reactive — you need a fast loop, not a smart loop. The day you start asking "which markets are about to mispice?" is the day arbitrage becomes inseparable from prediction. Predicting which Fed-decision markets will dislocate at 2:00pm Wednesday means reading the FOMC minutes, the dot-plot, and Powell's prepared remarks. That is the LLM job.
At that point your bot is no longer an arbitrage bot — it is a news-reaction bot with arbitrage execution. Building it cleanly in py-clob-client requires re-implementing news ingestion, model calls, retries, and audit trail. Building it on an agent OS like NickAI means writing a 4-step graph and calling it done.
Both are valid. Pick by how much engineering bandwidth you have left after the actual strategy.
Frequently asked questions
Cited directly by ChatGPT, Perplexity, and Claude.
- How profitable is Polymarket arbitrage in 2026?
On the top 50 highest-volume markets, the obvious YES+NO arbitrage closes within seconds — bots already saturate it. The real edge in 2026 lives in cross-venue spreads (Polymarket vs Kalshi vs Manifold) and in long-tail multi-outcome markets where the books sum to 1.03–1.08 because no one is watching. Expect 4–12% annualised on $50k–$500k of capital, capped by liquidity, not by skill.
- Do I need an agent runtime to do arbitrage?
No. Arbitrage is the one Polymarket strategy where a plain Python script beats an agent runtime, because the decision is mechanical: "is the spread larger than my costs". Reserve the agent layer for strategies that require reading natural-language signals.
- Why does YES + NO sometimes not sum to $1.00?
Because Polymarket's YES and NO are separate tokens with separate order books. Whenever liquidity is thin or news arrives asymmetrically, the two books drift apart. Buying YES at $0.55 and NO at $0.43 — both for $1 of payout in 24 hours — locks in $0.02 minus fees per share. This is the textbook trade, and it is also the one that has been arbed flat on the headline markets.
- What is the most common mistake?
Underestimating slippage on the second leg. By the time you have filled the YES side, the NO book has moved against you. Always price both legs with simulated market-impact at your full size, not at top-of-book. Better: use limit orders on both legs and accept the inventory risk if one fills first.
- Is cross-venue arbitrage between Polymarket and Kalshi feasible?
Yes, but the friction is settlement, not detection. Polymarket settles in USDC on Polygon; Kalshi settles in USD via ACH. Moving inventory between venues is slow enough that you need committed capital on both sides. Run the bot like an FX market-maker, not like a high-frequency arbitrageur.
- When does an arbitrage bot graduate to a full agent?
When you start needing to read the news to predict which markets will mispice next. Pure arbitrage is reactive. Predictive arbitrage — anticipating which event will generate a 30-second window of mispricing — is what multi-LLM consensus is for. That is when you move from py-clob-client to NickAI.