Learn how to build a Polymarket statistical arbitrage bot using relative-value signals, z-scores, live order books, execution costs, risk controls, and historical replay.
A market can look mispriced without being arbitrageable.
That distinction matters when building a Polymarket statistical arbitrage bot. A raw price difference between two contracts is not enough. The system needs to determine whether the relationship between those contracts has historically been stable, whether the current deviation is statistically unusual, and whether execution costs leave enough edge after the trade is opened.
By Bo$onaX
Polymarket trading bots • Quantitative trading • Rust • Web3 infrastructure
GitHub: poly-alpha-lab
Telegram: Telegram
YouTube: YouTube
X: X
Polymarket: Polymarket profile
Statistical arbitrage is a relative-value problem
Traditional arbitrage asks whether two instruments create a deterministic payoff mismatch.
Statistical arbitrage asks a different question:
Is the current relationship unusually far from its normal behavior?
For Polymarket, useful relationships can exist between related contracts, complementary outcomes, or markets exposed to the same underlying event. Instead of treating every market independently, a quantitative system can construct a spread such as:
spread = price_A - β × price_B
where β is estimated from historical observations.
The bot then measures how unusual the spread is:
z = (spread - mean) / standard_deviation
A large positive or negative z-score can become a candidate signal.
It is not automatically a trade.
The statistical relationship itself can break.
Where the edge actually comes from
Suppose two related Polymarket contracts normally move together. A sudden information shock moves one contract faster than the other.
A naive bot buys the cheaper side.
A statistical-arbitrage system does more work:
- Verify that the markets are genuinely related.
- Estimate the historical relationship.
- Measure the current deviation.
- Check available liquidity.
- Estimate execution cost.
- Enter only when expected convergence compensates for those costs.
- Exit when the relationship normalizes—or when the model invalidates the trade.
That makes the strategy closer to Polymarket quantitative trading than simple price scraping.
Polymarket's current market-data infrastructure provides real-time market streams containing order-book, price-change, last-trade, tick-size and other market events, which is much more suitable for this type of system than repeatedly polling prices. ([Polymarket Documentation][2])
The data pipeline I would build
A production Polymarket arbitrage strategy should separate research data from execution state.
Market discovery
↓
Relationship builder
↓
Historical feature store
↓
Signal engine
↓
Cost / liquidity filter
↓
Risk engine
↓
Order executor
↓
Position + PnL monitor
The relationship builder is especially important.
Don't blindly correlate every market with every other market. Correlation can be caused by a common short-lived event and disappear immediately afterward.
Better features include:
- rolling correlation
- rolling volatility
- spread mean and variance
- hedge ratio
- half-life of mean reversion
- order-book imbalance
- bid/ask spread
- recent trade intensity
- market time-to-resolution
The final signal should be based on several conditions rather than a single z-score.
Rust is a good fit for the signal engine
The numerical calculation itself is straightforward:
fn z_score(value: f64, mean: f64, stddev: f64) -> Option<f64> {
if stddev <= f64::EPSILON {
return None;
}
Some((value - mean) / stddev)
}
fn should_trade(z: f64, threshold: f64) -> bool {
z.abs() >= threshold
}
The difficult part is not the formula.
It is keeping the statistical state synchronized with live market state.
For example, the model might calculate a strong signal from an old book snapshot while the execution engine sees a completely different ask price. A profitable-looking spread can disappear before the order reaches the book.
That is why I would keep signal generation and execution as separate components, connected through timestamped events.
Fees change the meaning of “cheap”
A statistical edge must survive transaction costs.
Polymarket currently charges taker fees on certain market categories, while makers are not charged trading fees under the published fee structure; the exact fee treatment depends on the market. ([Polymarket Help Center][3])
Therefore:
net_edge =
expected_convergence
- spread_cost
- taker_fee
- slippage
- adverse_selection
If net_edge is negative, the z-score is irrelevant.
This is one reason a backtest using midpoint prices can be dangerously optimistic. A strategy that appears profitable at mid-price may disappear when simulated using executable bid/ask prices.
The hidden risk: the relationship can be wrong
Statistical arbitrage has a failure mode that ordinary arbitrage does not:
the model can be statistically correct historically and still be wrong now.
A relationship can break because:
- the market definitions differ subtly;
- one contract receives new information first;
- liquidity changes;
- the remaining time to resolution changes the dynamics;
- participants discover the same relationship;
- the underlying regime changes.
Market resolution is another engineering consideration. Polymarket documents resolution through its market rules and oracle mechanisms; on Polymarket's international platform, markets are commonly resolved through UMA's Optimistic Oracle. ([Polymarket Help Center][4])
Your bot therefore needs a market-lifecycle state machine, not just OPEN and CLOSED.
Production controls
For a real Polymarket bot development project, I would add hard limits around the model:
maximum position per market
maximum combined correlated exposure
maximum spread age
maximum signal age
maximum slippage
maximum daily loss
maximum unresolved inventory
Every order should also carry enough metadata to reconstruct why it was submitted:
strategy_id
market_id
signal_timestamp
z_score
expected_edge
observed_bid
observed_ask
estimated_cost
position_before
That turns debugging from guesswork into event reconstruction.
How I would test it
Start with historical replay.
Feed the strategy timestamped market data and simulate execution against the available book rather than using theoretical midpoints.
Then introduce increasingly hostile conditions:
- delayed signals
- partial fills
- stale books
- missing events
- widened spreads
- sudden correlation breakdown
- rejected orders
- market resolution
- process restarts
Only after that would I move to paper trading.
The objective is not to prove that the strategy makes money. It is to discover which assumptions fail first.
Final observation
A Polymarket statistical arbitrage bot is fundamentally a model of relationships.
Top comments (0)