DEV Community

Cover image for Polymarket Pair Trading Bot: Statistical Arbitrage & Correlated Markets
Bo$onaX
Bo$onaX

Posted on

Polymarket Pair Trading Bot: Statistical Arbitrage & Correlated Markets

Learn how to build a Polymarket pair trading bot using correlated markets, spread analysis, z-scores, real-time order books, Rust, and two-leg execution risk management.

Two Polymarket markets can ask different questions while being driven by almost the same underlying event. When their prices temporarily separate, the interesting signal is not simply that one market looks “cheap.” The signal is that the relationship between the two markets has moved outside its normal range.

That is the basic idea behind a Polymarket pair trading bot.

By Bo$onaX

Polymarket trading bots • Quantitative trading • Rust • Web3 infrastructure

GitHub: https://github.com/n9xdev/poly-alpha-lab
Telegram: https://t.me/bosonax
YouTube: https://youtube.com/@bosonax
X: https://x.com/xxniiinxx
Polymarket: https://polymarket.com/@bosona

Pair trading is about the spread, not the price

Suppose two markets historically react to the same information:

  • Market A: 0.62
  • Market B: 0.48

The absolute prices tell us very little.

A pair strategy instead constructs a relationship such as:

$$
S_t = P_A - \beta P_B
$$

where β represents the estimated relationship between the two price series.

The bot records the historical distribution of S. If the current spread moves far enough from its mean, the system can flag a potential relative-value trade.

A common normalization is the z-score:

$$
Z_t = \frac{S_t-\mu_S}{\sigma_S}
$$

For example, a hypothetical Z = +2.5 means the current spread is substantially above its recent average. That does not mean the trade will converge. It only means the observed relationship is statistically unusual under the selected model.

That distinction matters.

Finding correlated Polymarket markets

The first difficult problem is not execution. It is selecting pairs that actually have an economic relationship.

Polymarket's current data model separates events, markets, and outcome token IDs. Each outcome has its own token ID, which is the identifier used when reading market prices and order books. ([Polymarket Documentation][2])

A pair scanner can therefore build candidates from:

  1. Similar event subjects
  2. Related political or economic questions
  3. Markets sharing an underlying reference variable
  4. Different time horizons for the same theme
  5. Conditional outcomes with overlapping information

Correlation alone is insufficient.

Two markets can have a 0.95 historical correlation and still be a terrible pair if the relationship is caused by a temporary regime, one market has thin liquidity, or their resolution rules differ.

For serious Polymarket statistical arbitrage, I would store the market metadata and resolution conditions alongside every statistical observation.

The bot architecture

A practical implementation can be split into five components:

Market Discovery
      ↓
Pair Selection
      ↓
Real-Time Price Engine
      ↓
Spread / Z-Score Engine
      ↓
Execution + Risk Manager
Enter fullscreen mode Exit fullscreen mode

Polymarket currently provides real-time market streams containing order-book, price-change, last-trade, best-bid/ask and market-lifecycle events. That makes a streaming architecture preferable to repeatedly polling every pair. ([Polymarket Documentation][1])

The state layer should maintain the latest book for both legs:

PairState
 ├── token_a
 ├── token_b
 ├── bid_a / ask_a
 ├── bid_b / ask_b
 ├── spread
 ├── rolling_mean
 ├── rolling_std
 └── z_score
Enter fullscreen mode Exit fullscreen mode

The strategy layer should consume normalized state rather than raw WebSocket messages. This keeps market-data handling independent from trading logic.

Don't trade the midpoint blindly

A common mistake is calculating:

spread = midpoint_A - beta * midpoint_B
Enter fullscreen mode Exit fullscreen mode

and immediately sending two orders.

Execution happens against actual liquidity.

A better signal incorporates executable prices:

long A / short B:
buy_price_A = ask_A
sell_price_B = bid_B
Enter fullscreen mode Exit fullscreen mode

The expected edge must then survive:

  • bid/ask spread
  • slippage
  • taker fees
  • partial fills
  • latency
  • position imbalance
  • model error

Polymarket currently applies taker fees to certain markets while makers are not charged fees; the fee calculation depends on share count, price, and the market's fee rate. ([Polymarket Documentation][3])

For a pair strategy, this means the threshold should be based on net executable edge, not simply a z-score threshold.

Why two-leg execution is the real problem

Statistical convergence is irrelevant if only one side fills.

Imagine the bot detects:

Z = +2.7
Enter fullscreen mode Exit fullscreen mode

It buys one leg, but liquidity disappears before the hedge executes.

The strategy is now directional.

That turns a market-neutral idea into ordinary prediction-market exposure.

A production Polymarket pair trading bot therefore needs an explicit execution state machine:

SIGNAL
  ↓
CHECK LIQUIDITY
  ↓
PLACE LEG A
  ↓
CONFIRM FILL
  ↓
HEDGE LEG B
  ↓
VERIFY POSITION
  ↓
MONITOR SPREAD
  ↓
EXIT / TIMEOUT / STOP
Enter fullscreen mode Exit fullscreen mode

Partial-fill handling belongs inside this state machine, not as an afterthought.

Rust implementation choices

Rust fits this architecture well because market-data ingestion, rolling statistics, and execution coordination can run as separate asynchronous tasks.

Polymarket's official Rust CLOB client currently provides CLOB functionality plus optional WebSocket, Data API, Gamma API and other modules. Its WebSocket support includes order-book, price, midpoint and authenticated user-event streams. ([GitHub][4])

A simplified strategy interface could look like:

struct PairSignal {
    spread: f64,
    z_score: f64,
    executable_edge: f64,
}

fn should_trade(signal: &PairSignal, threshold: f64) -> bool {
    signal.z_score.abs() >= threshold
        && signal.executable_edge > 0.0
}
Enter fullscreen mode Exit fullscreen mode

The production implementation should use decimal-safe price representations rather than relying on floating-point arithmetic for order construction.

The failure mode most pair bots underestimate

Correlation decay.

A pair can work for months and then stop behaving like a pair.

The correct response is not simply lowering the entry threshold.

Monitor:

  • rolling correlation
  • spread volatility
  • hedge-ratio stability
  • mean-reversion half-life
  • fill quality
  • leg imbalance
  • time-to-resolution
  • resolution-rule changes

If the statistical relationship deteriorates, disable the pair.

A pair trading system should be capable of saying “no trade” much more often than it says “buy.”

Risk note

Pair trading and statistical arbitrage are not risk-free arbitrage. Historical correlation does not guarantee future convergence. Liquidity, fees, slippage, execution asymmetry, model error, and market-resolution differences can produce losses. All numerical examples above are hypothetical, not measured trading results.

Final thought

The interesting part of a Polymarket pair trading bot is not the z-score formula.

It is the machinery surrounding it.

Market selection determines whether the relationship is meaningful. The spread model determines whether the divergence is unusual. The execution engine determines whether the theoretical edge survives contact with the order book.

A useful pair-trading system therefore looks less like a simple arbitrage script and more like a small quantitative execution platform: discover → model → price → hedge → monitor → invalidate.

Top comments (0)