DEV Community

Blockchain Rust Engineer
Blockchain Rust Engineer

Posted on

Building a Polymarket Arbitrage Bot: 5 Strategies, One Signal-Ranking Problem

Most "Polymarket arbitrage bot" writeups stop at explaining what arbitrage is. The actual engineering problem starts after that - once you have five strategies running in parallel, all watching the same markets, and need to decide which signal to act on when two or three of them fire at once. That's the part worth documenting.

The five strategies, briefly

Intra-market arbitrage exploits the fact that YES + NO token prices should sum to $1.00. When they don't, buying both sides and redeeming the complete set locks a profit independent of outcome. Combinatorial arbitrage is the same mechanic extended to multi-outcome markets. Both of these are close to structurally risk-free - you're buying a guaranteed redemption, not predicting anything.

Cross-platform arbitrage compares Polymarket's implied probability against spot prices on exchanges like Binance and bets on convergence. Endgame arbitrage targets near-resolution markets where one side already trades above ~93%. Momentum/mean-reversion applies Z-score, RSI, and VWAP divergence to Polymarket's price series across multiple timeframes. These three are directional, not risk-free, and treating them the same as the first two is a modeling mistake I see a lot of amateur implementations make.

The actual hard problem: signal ranking

Running five detection strategies concurrently means a single market can trigger multiple, sometimes conflicting, signals in the same scan cycle. A naive implementation executes whichever signal fires first - which is a bug waiting to compound, because "first" has nothing to do with "best."

The fix is a weighted composite score per signal:

def score_signal(signal):
    return (
        signal.expected_profit_pct * WEIGHT_PROFIT +
        signal.confidence * WEIGHT_CONFIDENCE +
        STRATEGY_PRIORITY[signal.strategy_type] * WEIGHT_STRATEGY +
        signal.urgency * WEIGHT_URGENCY +
        signal.risk_reward_ratio * WEIGHT_RISK_REWARD
    )

ranked = sorted(signals, key=score_signal, reverse=True)
executable = [s for s in ranked if s.score > EXECUTION_THRESHOLD]
Enter fullscreen mode Exit fullscreen mode

TRATEGY_PRIORITY is where the risk-profile distinction actually gets encoded - intra-market and combinatorial get weighted up, cross-platform and momentum get weighted down, by design, not by accident. Getting this weighting wrong is the difference between a bot that behaves like an arbitrage system and one that quietly turns into a directional trading system without anyone deciding that on purpose.

Why this matters if you're building or evaluating one

A lot of "arbitrage bot" marketing glosses over the fact that these are five different risk categories bundled under one label. If you're building your own, the signal-ranking layer is where real engineering judgment lives - it's a genuinely harder problem than detecting any single strategy in isolation, and it's the part that separates a working system from a collection of independent scripts.

Full open-source implementation, including the strategy detection and scoring logic: github.com/casatrick/polymarket-arbitrage-bot-python

Longer technical breakdown of each strategy: casatrick.substack.com

Top comments (1)

Collapse
 
jbowz profile image
jbowz

Encoding the risk distinction in STRATEGY_PRIORITY on purpose is a sound decision. Many systems marketed as arbitrage end up trading directionally once the entry price is taken into account. I have a question about the composite score: The components use different units (profit is in percent, confidence is presumably between 0 and 1, and priority is an integer), so the weights determine the overall meaning. How did you choose these weights, and do they perform well out of sample? Additionally, does EXECUTION_THRESHOLD adjust to volatility, or has a static value been sufficient?