The Real Bottleneck in Polymarket Arbitrage Isn't Finding the Edge - It's Keeping It
Risk-free arbitrage windows on Polymarket close faster than most bots can react. Here's why detection was never the hard part.
The part everyone gets wrong about arbitrage bots
Ask someone to explain a Polymarket arbitrage bot and they’ll describe the detection logic: YES and NO prices should sum to $1.00, when they don’t, buy both, redeem the gap. That explanation is correct and also, on its own, almost useless - because detecting the mispricing was never the hard problem. Every arbitrage bot running against Polymarket’s public order books can see the same gap at roughly the same moment. The problem worth solving is what happens in the three or four seconds after that.
Why the gap closes before you can act on it
A pricing inefficiency on Polymarket doesn’t sit still waiting to be captured. The moment it appears, it’s visible to every bot polling that market, and the first execution to hit the order book starts consuming the very liquidity the gap depended on. A $0.15 gap on $5,000 of depth might only support $1,200 of size before the remaining liquidity re-prices back toward $1.00. Miss that window and you’re not capturing arbitrage anymore - you’re providing exit liquidity for whoever got there first.
This reframes what actually matters in a well-built system. Detection is table stakes. Execution latency, order sizing against live depth, and how fast the bot can act on a signal it just generated are where real edge lives.
The signal-to-execution gap, concretely
Most naive implementations separate detection and execution into sequential steps: poll the book, compute the gap, decide to trade, submit the order. Each step adds latency, and on a fast-closing arbitrage window, that sequential structure is the actual bottleneck - not the math.
def evaluate_and_execute(book, min_gap=0.02):
yes_price = float(book.asks[0].price)
no_price = float(book.bids[0].price) # opposing side, same market
gap = 1.0 - (yes_price + no_price)
if gap < min_gap:
return None
# depth check happens inline, not as a separate downstream step
fillable = min(
sum(float(lvl.size) for lvl in book.asks if float(lvl.price) <= yes_price + 0.005),
sum(float(lvl.size) for lvl in book.bids if float(lvl.price) >= no_price - 0.005),
)
if fillable < MIN_VIABLE_SIZE:
return None
return submit_paired_order(yes_price, no_price, size=fillable)The point isn't this specific snippet - it's the principle: by the time you've separately validated a signal and then gone to check whether it's actually fillable, a meaningful fraction of that liquidity may already be gone.
Why this matters more as more bots enter the space
Polymarket’s arbitrage opportunities aren’t a fixed resource - they’re a byproduct of market inefficiency, and more capital chasing the same gaps compresses both the size and duration of each window. Two years ago, a slower bot could still capture a wide gap simply because fewer people were looking. That’s less true now. As more sophisticated capital enters prediction markets, the strategies that were “close to risk-free” in a quieter market increasingly reward execution speed over strategy novelty - which is a different competitive dynamic than most writeups about Polymarket arbitrage account for.
What this means practically
If you’re building or evaluating a Polymarket arbitrage bot, the right question isn’t “does it detect YES/NO mispricing.” Every implementation does that; it’s not differentiated. The right questions are: how fast does it go from seeing the gap to submitting the order, how does it size against real-time depth rather than a stale snapshot, and what does it do when a partial fill leaves one leg exposed. Those answers are where a genuinely well-engineered system separates itself from a script that happens to work in a backtest.
Full implementation, including the signal-scoring and execution logic across all five arbitrage and momentum strategies: github.com/casatrick/polymarket-arbitrage-bot-python

