Polymarket Trading Bot Examples: 4 Patterns with Python Code
If you're thinking about building a Polymarket trading bot, you're in a small but growing club. Polymarket's CLOB (central limit order book) is fully accessible via REST and WebSocket APIs, the markets are deep enough to absorb meaningful size, and the inefficiencies relative to mature financial markets are still significant.
Below are four patterns we've seen work in production, with minimal Python code. Each one has a real edge — and each one has a hidden failure mode that takes down most beginner bots: dispute risk. We'll cover that explicitly in pattern #4.
Setup: The Polymarket CLOB Client
All examples use the official py-clob-client package and assume you've funded a Polygon wallet with USDC.
pip install py-clob-client requests
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON
HOST = "https://clob.polymarket.com"
KEY = "0x..." # private key (use env var in production)
CHAIN_ID = POLYGON
client = ClobClient(HOST, key=KEY, chain_id=CHAIN_ID)
client.set_api_creds(client.create_or_derive_api_creds())
Pattern 1: Same-Event Arbitrage
Polymarket sometimes lists multiple markets on the same underlying event with slightly different framing. If their implied probabilities sum to more than 1.0 (or the YES/NO mirror differs from 1.0), there's an arb.
def find_yes_no_arb(market_id, threshold=0.02):
"""Buy YES and NO on the same market when sum < 1 - threshold."""
book = client.get_order_book(market_id)
best_yes_ask = float(book.asks[0].price)
best_no_ask = float(book.asks_no[0].price)
total_cost = best_yes_ask + best_no_ask
if total_cost < 1 - threshold:
return {"profit_per_dollar": 1 - total_cost, "yes": best_yes_ask, "no": best_no_ask}
return None
Edge: Polymarket's market makers occasionally let YES + NO drift below 1.00 during high-volatility moments. Bots that fire fast capture the spread.
Failure mode: Slippage on the second leg. By the time you've filled YES, the NO ask may have moved.
Pattern 2: Momentum / Order Book Imbalance
Track the bid/ask imbalance over a rolling window. Sustained one-sided pressure often precedes price moves.
from collections import deque
import time
window = deque(maxlen=60) # 60-second rolling
def compute_imbalance(market_id):
book = client.get_order_book(market_id)
bid_size = sum(float(b.size) for b in book.bids[:5])
ask_size = sum(float(a.size) for a in book.asks[:5])
return (bid_size - ask_size) / (bid_size + ask_size + 1e-9)
while True:
imb = compute_imbalance("0xMARKET_ID")
window.append(imb)
avg_imb = sum(window) / len(window)
if avg_imb > 0.4:
print("Strong bid pressure — consider long")
elif avg_imb < -0.4:
print("Strong ask pressure — consider short")
time.sleep(1)
Edge: Polymarket's retail flow creates persistent imbalances on news-driven markets.
Failure mode: Whale orders that get pulled. The imbalance disappears, your fill price worsens.
Pattern 3: Liquidity Provision (Market Making)
Place limit orders on both sides of the spread, capture the spread, manage inventory.
SPREAD_BPS = 200 # 2% spread
MAX_INVENTORY = 100 # max contracts per side
def make_market(market_id):
book = client.get_order_book(market_id)
mid = (float(book.bids[0].price) + float(book.asks[0].price)) / 2
half_spread = SPREAD_BPS / 20000 # convert bps to price units (1.00 = full)
bid_price = round(mid - half_spread, 3)
ask_price = round(mid + half_spread, 3)
# Cancel stale orders
client.cancel_all(market_id)
# Place new pair
client.create_order(market_id, side="BUY", price=bid_price, size=10)
client.create_order(market_id, side="SELL", price=ask_price, size=10)
Edge: On low-volume markets without dedicated MMs, you can capture 2–5% spreads consistently.
Failure mode: Adverse selection. Informed traders hit your stale quotes during news events. Always cancel-replace on new info.
Pattern 4: Dispute-Risk-Aware Position Sizing
This is the one most beginner bots miss. Every position you hold has a non-zero probability of being frozen for days when the market gets disputed on UMA. The frozen capital has an opportunity cost — and on dispute-prone markets, that cost can wipe out your edge.
The fix: scale your position size by (1 − dispute_risk) using a dispute risk score.
import requests
def get_dispute_risk(market_id):
"""Returns 0.0 to 1.0 dispute risk score from OracleMangle."""
r = requests.get(f"https://api.oraclemangle.com/v1/markets/{market_id}")
if r.status_code != 200:
return 0.5 # default to caution if unavailable
return r.json().get("dispute_risk", 0.5)
def sized_position(market_id, base_size_usd):
"""Scale position by inverse dispute risk."""
risk = get_dispute_risk(market_id)
if risk > 0.5:
return 0 # skip extreme-risk markets entirely
# Linear scaling: 0% risk = full size, 50% risk = 0
return base_size_usd * (1 - 2 * risk)
# In your main loop:
size = sized_position(market_id, base_size_usd=500)
if size > 0:
client.create_order(market_id, side="BUY", price=ask, size=size / ask)
Edge: You stop entering the markets where 8–10% of your capital ends up frozen for 2+ weeks. Across our dataset, the highest-risk bucket gets disputed at 9.4% — vs the 1.14% baseline. That's a meaningful drag on bot Sharpe ratios.
Failure mode: Skipping legitimately good trades because the score is conservative. Tune the threshold to your risk tolerance.
What Most Bot Authors Get Wrong
Across the bots we've seen ship to production, three mistakes recur:
- No dispute risk filter. Bots track price, volume, expiry, slippage — and ignore the one risk that can lock capital for weeks.
- Polling instead of WebSockets. The CLOB has a WebSocket feed (
wss://ws-subscriptions-clob.polymarket.com). Polling REST every second is wasteful and slow. - Ignoring USDC bridge fees. Moving USDC on/off Polygon costs gas + bridge fees. Small positions get eaten by fees.
Putting It Together
A production-grade Polymarket bot combines:
- WebSocket order book subscription (low latency)
- One or more of the four patterns above (your edge)
- Dispute risk filter on every entry (capital protection)
- Position sizing as a function of edge × confidence × (1 − dispute_risk)
- Inventory management and stop-out rules
The first three are straightforward engineering. The fourth — dispute risk — is the differentiator. Most bots ship without it, get caught in a few disputes, and quietly underperform.
The Bottom Line
Polymarket bots are profitable when they combine a real edge (arb, momentum, market making) with disciplined risk management. The risk most beginners miss is dispute risk — capital frozen for days while UMA token holders vote. Add a single API call to your entry logic and you've removed 80%+ of the freeze risk for free.
OracleMangle's REST API returns dispute risk for any Polymarket market. See the API docs.