DEV Community

Blockchain Rust Engineer
Blockchain Rust Engineer

Posted on • Originally published at casatrick.substack.com

Why Execution Speed Beats Detection Logic in Polymarket Arbitrage Bot

Every writeup on Polymarket arbitrage explains the same thing: YES and NO token prices should sum to $1.00, and when they don't, buying both sides and redeeming the pair locks in the gap. Correct, and also not the hard part. Every bot scanning the public order book sees the same gap at roughly the same moment. Detection was never the differentiator.

The actual problem is what happens in the 2-3 seconds after the gap appears.

Why the window closes faster than you'd expect

A pricing gap on Polymarket doesn't sit there waiting. The moment it's visible, it's visible to everyone polling that market, and the first execution to hit the book starts consuming the liquidity the gap depended on. A $0.15 gap on $5,000 of depth might only support $1,200 of size before the rest re-prices back toward $1.00. Miss the window and you're not doing arbitrage - you're exit liquidity for whoever got there first.

Where naive implementations lose the race

Most bots handle this as separate sequential steps:

book = get_order_book(token_id)          # step 1
gap = calculate_gap(book)                 # step 2
if gap > threshold:                       # step 3
    submit_order(...)                     # step 4
Enter fullscreen mode Exit fullscreen mode

Each step adds latency. On a window that closes in seconds, that sequence is the bottleneck - not the underlying math. By the time step 4 fires, the depth you validated in step 2 may already be gone.

Fold the depth check into the decision itself

def evaluate_and_execute(book, min_gap=0.02):
    yes_price = float(book.asks[0].price)
    no_price = float(book.bids[0].price)
    gap = 1.0 - (yes_price + no_price)

    if gap < min_gap:
        return None

    fillable = min(
        sum(float(l.size) for l in book.asks if float(l.price) <= yes_price + 0.005),
        sum(float(l.size) for l in book.bids if float(l.price) >= no_price - 0.005),
    )

    if fillable < MIN_VIABLE_SIZE:
        return None

    return submit_paired_order(yes_price, no_price, size=fillable)
Enter fullscreen mode Exit fullscreen mode

The point isn't this exact snippet - it's that depth-checking has to happen inline with the trade decision, not as a downstream validation step run against data that's already stale.

Why this gets harder, not easier, over time

Arbitrage windows 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 one. Execution speed increasingly matters more than strategy novelty - a different competitive dynamic than most Polymarket arbitrage content accounts for.

What to actually evaluate

If you're building or reviewing a Polymarket arbitrage bot, "does it detect mispricing" isn't a useful question - every implementation does. Better questions: how fast does it go from signal to submitted order, does it size against live depth or a stale snapshot, and what happens when a partial fill leaves one leg exposed?

Full implementation, including signal-scoring and execution logic across all five strategies: github.com/casatrick/polymarket-arbitrage-bot-python

Top comments (0)