Build a Polymarket arbitrage bot with real-time order books, fee-aware execution, risk controls, and production-ready Python architecture.
Introduction
A useful Polymarket arbitrage bot is not simply a script that checks whether two prices add up to less than $1.
The difficult part is turning a theoretical pricing discrepancy into an executable trade after accounting for fees, spread, available liquidity, partial fills, stale quotes, execution failure, and inventory risk.
Polymarket's current production trading stack uses CLOB V2. The official Python client is py-clob-client-v2, while real-time market data is available through the public WebSocket market channel. ([Polymarket Documentation][1])
This article presents a compact architecture for building an arbitrage engine around those primitives.
About the Author
Bo$onaX
I write about Polymarket trading bots, prediction-market infrastructure, algorithmic trading, Python automation, Web3 development, and quantitative strategies.
Contact:
Github: https://github.com/n9xdev/poly-alpha-lab
X: https://x.com/xxniiinxx
Telegram: https://t.me/bosonax
What You'll Learn
- How to structure an arbitrage engine
- How to consume Polymarket order-book updates
- How to calculate fee-aware opportunities
- How to separate detection from execution
- How to manage two-leg execution risk
- How to test and monitor the system
1. The Architecture
A production bot should separate market data, strategy, risk, execution, and observability.
flowchart LR
WS[Polymarket WebSocket] --> BOOK[Order Book Cache]
META[Market Metadata] --> BOOK
BOOK --> DETECT[Arbitrage Detector]
DETECT --> COST[Fee + Slippage Model]
COST --> RISK[Risk Engine]
RISK --> EXEC[Execution Engine]
EXEC --> CLOB[Polymarket CLOB V2]
CLOB --> FILLS[Fill Events]
FILLS --> POS[Position Manager]
POS --> MON[Metrics + Logs]
EXEC --> MON
The key design principle is simple: never let the strategy directly control the exchange client.
The strategy produces an opportunity. The risk layer decides whether it is allowed. The execution layer determines how it is actually traded.
2. Real-Time Market Data
Polymarket provides a public WebSocket market channel for order-book snapshots and incremental updates. The documented channel supports book updates, price changes, last-trade events, tick-size changes, and other market lifecycle events. Clients should also send the documented heartbeat. ([Polymarket Documentation][2])
For an arbitrage system, maintain an in-memory book:
from dataclasses import dataclass
@dataclass
class Quote:
bid: float
ask: float
bid_size: float
ask_size: float
books: dict[str, Quote] = {}
Do not repeatedly poll REST endpoints for every strategy decision when streaming data is available. The detector should react to book events and operate against the latest validated local state.
3. Detecting an Arbitrage Opportunity
For a binary market, a simple conceptual parity check is:
cost = yes_ask + no_ask
If the combined executable cost is below the eventual combined payout, there may be an opportunity.
But yes_ask + no_ask < 1 is not sufficient.
The actual decision should look more like:
net_edge =
guaranteed_or_expected_value
- execution_cost
- taker_fee
- slippage
- inventory_risk_buffer
Polymarket currently applies taker fees only to fee-enabled markets, with fees calculated at match time. The market's fee configuration can be queried through the CLOB market information API. Makers are not charged the trading fee, and eligible maker activity can receive rebates. ([Polymarket Documentation][3])
Therefore, hard-coding a universal fee percentage into your arbitrage bot is a mistake.
4. Strategy Layer
Keep the strategy deliberately small:
def find_opportunity(yes, no, min_edge):
combined = yes.ask + no.ask
if combined >= 1.0:
return None
edge = 1.0 - combined
if edge < min_edge:
return None
return {
"yes_price": yes.ask,
"no_price": no.ask,
"edge": edge,
}
In production, min_edge should represent the minimum executable edge after all known costs, not an arbitrary percentage.
The detector should also verify:
- sufficient size exists at both prices
- quotes are fresh
- both tokens belong to the intended market
- market is still tradable
- tick sizes are valid
- current fee configuration is known
- available capital supports both legs
5. Execution Is the Hard Part
The biggest architectural mistake is treating arbitrage as a detection problem.
It is primarily an execution problem.
Suppose the detector sees:
YES ask = 0.47
NO ask = 0.51
Combined = 0.98
That does not mean you can buy both at those prices.
The YES order might fill while the NO ask disappears. You now have directional exposure.
A safer execution engine therefore tracks every opportunity as a state machine:
DETECTED
↓
VALIDATED
↓
LEG_A_SUBMITTED
↓
LEG_A_FILLED
↓
LEG_B_SUBMITTED
↓
COMPLETE
Failure states should include:
EXPIRED
PARTIAL
CANCELLED
UNWOUND
ERROR
The execution layer should never assume that submission equals execution.
6. Python Project Structure
A practical project can remain compact:
polymarket_arb/
├── config.py
├── market_data.py
├── orderbook.py
├── strategy.py
├── fees.py
├── risk.py
├── execution.py
├── positions.py
├── monitoring.py
└── main.py
The official documentation currently recommends the V2 Python client, py-clob-client-v2, for CLOB integrations. Legacy V1 clients are no longer supported against production CLOB V2. ([Polymarket Documentation][1])
Keep credentials outside source code:
import os
API_KEY = os.environ["POLY_API_KEY"]
API_SECRET = os.environ["POLY_API_SECRET"]
API_PASSPHRASE = os.environ["POLY_API_PASSPHRASE"]
Private keys should be handled separately and never logged.
7. Risk Engine
Before submitting either leg, enforce hard limits:
def allowed(size, available_capital, max_position):
return (
size > 0
and size <= available_capital
and size <= max_position
)
Production systems should additionally enforce:
- maximum market exposure
- maximum simultaneous opportunities
- maximum one-leg exposure
- stale-book timeout
- daily loss limit
- emergency shutdown
- duplicate-order protection
A particularly important control is a one-leg timeout. If the hedge does not fill quickly enough, the bot must have a predefined response rather than waiting indefinitely.
8. Performance Considerations
Latency matters, but optimizing Python syntax before optimizing architecture is usually backwards.
Prioritize:
- WebSocket market data
- local order-book state
- event-driven strategy evaluation
- persistent connections
- asynchronous execution
- minimal network hops
- measured order submission latency
Polymarket's documentation currently identifies eu-west-2 as the primary CLOB server region and documents direct co-location availability for eligible users. ([Polymarket Documentation][4])
Measure your actual pipeline:
market event
→ detector
→ validation
→ signing
→ submission
→ acknowledgement
→ fill
Do not claim a latency target without measuring your own infrastructure.
9. Failure Modes
Common failures include:
Stale books: A cached opportunity is no longer executable.
Partial fills: One leg executes while the hedge disappears.
Fee blindness: Gross spread looks profitable while net execution is negative.
Liquidity blindness: The best displayed price exists only for a tiny quantity.
Duplicate execution: Multiple event handlers trigger the same opportunity.
Market lifecycle errors: The market changes state while orders are being prepared.
Credential leakage: API credentials or private keys enter logs, repositories, or telemetry.
The bot should treat every external response as untrusted input and every order as an asynchronous state transition.
10. Testing Strategy
Start with deterministic replay.
Store historical order-book events and feed them into the same detector used in production.
Test:
- normal book updates
- disappearing liquidity
- crossed quotes
- partial fills
- rejected orders
- WebSocket disconnects
- duplicate events
- stale timestamps
- fee changes
- market closure
Then run the exact strategy in simulation before enabling live execution.
The goal is not merely to prove that the detector finds arbitrage. It is to prove that the whole state machine behaves correctly when arbitrage disappears halfway through execution.
11. Monitoring
Track at minimum:
opportunities_detected
opportunities_rejected
orders_submitted
orders_filled
partial_fills
unwinds
gross_edge
realized_pnl
fees_paid
slippage
data_age
order_latency
Structured logs should include a unique opportunity ID so that detection, orders, fills, and position changes can be reconstructed later.
Polymarket also provides an authenticated user WebSocket channel for real-time order and trade events, making it useful for synchronizing execution state with actual account activity. ([Polymarket Documentation][5])
12. Practical Example
Consider a hypothetical opportunity:
YES ask: $0.46
NO ask: $0.51
Gross cost: $0.97
Gross edge: $0.03
A naive bot immediately buys both.
A production bot asks:
Is the book fresh?
Is enough size available?
Are fees enabled?
What is the effective fee?
What happens if only YES fills?
Can NO still be executed?
What is the maximum acceptable unwind loss?
Only after those checks does the risk engine authorize execution.
That difference is what separates an arbitrage detector from an arbitrage trading system.
13. Advanced Improvements
Once the core engine works, add:
- multi-market opportunity ranking
- fee-aware dynamic thresholds
- execution simulation
- inventory-aware sizing
- cross-market arbitrage models
- persistent event storage
- automatic strategy kill switches
- latency histograms
- replay-based regression tests
You can also separate alpha generation from execution completely, allowing multiple strategies to share one execution and risk framework.
Frequently Asked Questions
Is a Polymarket arbitrage bot risk-free?
No. A theoretical arbitrage can still suffer from partial fills, changing liquidity, execution failure, fees, and operational risk.
Does YES + NO below $1 always mean profit?
No. You must evaluate executable size, fees, slippage, and execution risk.
Should I use REST or WebSocket?
For real-time order-book-driven strategies, WebSocket is the natural primary market-data path. REST remains useful for queries and account operations. ([Polymarket Documentation][2])
Which Python SDK should new integrations use?
Polymarket's current documentation specifies py-clob-client-v2 for the CLOB V2 integration. ([Polymarket Documentation][1])
Can arbitrage be profitable?
It can be theoretically attractive, but profitability is strategy-, market-, liquidity-, fee-, and execution-dependent. No specific return should be assumed.
Conclusion
A serious Polymarket arbitrage bot is not a price-comparison script.
The durable architecture is:
stream → normalize → detect → price costs → validate risk → execute → reconcile → monitor.
Build those boundaries correctly and you can change the arbitrage strategy without rebuilding the entire trading infrastructure.
Trading and arbitrage involve substantial risk. Examples in this article are educational and do not imply profitability.
Related Articles
How to Build a Polymarket Trading Bot in Python
Anchor: build a Polymarket trading bot in Python
Why: foundational implementation.Polymarket API Explained for Developers
Anchor: Polymarket API architecture
Why: explains the API layer behind the bot.Build a Real-Time Polymarket Order Book Monitor
Anchor: real-time Polymarket order book monitor
Why: directly supports the market-data layer.Polymarket CLOB Architecture Explained
Anchor: Polymarket CLOB architecture
Why: explains the execution venue.Polymarket Trading Bot Risk Management
Anchor: Polymarket bot risk management
Why: expands the risk engine.Polymarket WebSocket Trading Data
Anchor: Polymarket WebSocket data
Why: supports event-driven architecture.
Useful Resources
- Official Polymarket — platform and market interface.
- Polymarket Developer Documentation — primary technical reference.
- Polymarket CLOB Trading Overview — CLOB architecture, authentication, and SDKs.
- CLOB V2 Migration Guide — essential for current integrations.
- Polymarket Fees Documentation — current fee mechanics.
- Polymarket Market WebSocket Documentation — real-time order-book data.
- Polymarket Developers on X — developer announcements.
- Polymarket arbitrage bot — Medium example — third-party implementation discussion; useful for comparison, not authoritative.
- Polymarket arbitrage bot — YouTube example — practical community demonstration; not an official technical source.
Top comments (0)