How to build a Polymarket trading bot
A Polymarket bot is four parts: a market to trade, a data feed, a strategy you have tested against real order books, and an execution layer on Polymarket's SDK. Most bots that fail do so at the testing step: they are tested on prices that could never have been traded, then lose money to spreads, depth and fees.
Last updated:
1. Pick a market type
Most bot activity is in the crypto up/down markets: BTC, ETH, SOL and others settling every 5 minutes, 15 minutes, hour, 4 hours or day. They repeat constantly, so a strategy gets hundreds of trades to prove itself. Sports, weather and economics markets settle less often and have their own quirks, such as weather books that sit at a 0.01/0.99 placeholder for the first hours after listing.
Since 2026, crypto markets charge takers a fee, which rules out many strategies that worked before. Decide early whether your bot takes liquidity (pays the fee) or provides it (pays nothing and earns a rebate).
2. Get live and historical data
- Live: Polymarket's CLOB WebSocket streams the book for free. See the Polymarket API guide for endpoints and limits.
- History: Polymarket does not publish past order books, only a price series. Resolved Markets has recorded full-depth books since March 2026, which is what you need to know what an order would have filled at.
3. Backtest against the book, not the mid
A backtest that buys at the mid price assumes a fill that did not exist. The script below takes one settled BTC 15-minute market and asks what a $500 market buy would actually have paid at several points in the window, by walking the ask ladder level by level and adding Polymarket's taker fee. It runs on a free API key.
import os, requests
API = "https://api.resolvedmarkets.com"
HEADERS = {"X-API-Key": os.environ["RESOLVED_MARKETS_API_KEY"]}
# 1. The most recently settled BTC 15-minute market
market = requests.get(f"{API}/v1/markets/history/recent", headers=HEADERS,
params={"crypto": "BTC", "timeframe": "15m", "status": "closed", "limit": 1}).json()["markets"][0]
# 2. Its UP-token order book over the whole window, full depth
rows = requests.get(f"{API}/v1/markets/{market['market_id']}/snapshots", headers=HEADERS,
params={"side": "UP", "includebook": "true", "limit": 500, "order": "asc", "count": "false"}).json()["data"]
def buy_fill(asks, usd):
"""Walk the ask ladder: average price paid for `usd` worth of shares, or None if the book is too thin."""
shares = cost = 0.0
for level in asks: # lowest ask first
take = min(level["size"], (usd - cost) / level["price"])
shares += take
cost += take * level["price"]
if cost >= usd - 1e-9:
return cost / shares, shares
return None
def taker_fee(shares, price, rate=0.07):
"""Polymarket's crypto taker fee (docs.polymarket.com/trading/fees, Sept 2026)."""
return shares * rate * price * (1 - price)
# 3. What would a $500 market buy have actually paid, vs the mid a naive backtest uses?
for row in rows[::100]:
fill = buy_fill(row["asks"], 500)
if fill is None:
print(row["timestamp"], "book too thin for $500")
continue
price, shares = fill
print(f"{row['timestamp']} mid {row['mid_price']:.3f} fill {price:.3f} fee ${taker_fee(shares, price):.2f}")Output for one window on 2026-09-27:
2026-09-27 06:15:06.172 mid 0.485 fill 0.511 fee $17.11
2026-09-27 06:24:01.408 mid 0.465 fill 0.497 fee $17.61
2026-09-27 06:29:21.215 mid 0.435 fill 0.457 fee $19.01
2026-09-27 06:30:09.608 mid 0.395 fill 0.430 fee $19.93Each $500 buy filled 2 to 4 cents above the mid, 5% to 9% worse, and then paid a fee of 3.4% to 4% of notional. A strategy needs an edge larger than both just to break even. Three more things a realistic backtest has to model:
- Latency. The book moves between your signal and your order landing. Enter a few hundred milliseconds after the signal, and skip the trade if the price has moved past your limit.
- Thin books. Near settlement, one side of the book is often empty. A mid of 0.995 there is a bound, not a price you can sell at.
- Exits. Selling walks the bid ladder the same way. Stops placed where the book is thin fill well below the trigger.
The Resolved Markets backtester (Pro and above) does this for you: it walks the real ladder on entry and exit, applies an entry delay and slippage guard, charges a configurable taker fee, and flags trades the book could not absorb.
4. Paper trade on the live feed
Run the strategy on the live WebSocket without placing orders, logging the fill you would have got from the book at that moment. A week of paper results that match the backtest is the best evidence you have that the backtest was honest.
5. Go live with the official SDK
- Use py-sdk (Python) or ts-sdk (TypeScript). The older py-clob-client and clob-client are archived and stopped working with CLOB V2.
- Derive API credentials with a wallet signature (L1); orders are signed with HMAC headers (L2).
- Check
GET https://polymarket.com/api/geoblockfrom your server: the US and several other countries are close-only. - Order placement has per-wallet rate limits tiered by volume; back off rather than retry in a loop.
- Start small, and compare every live fill with what the backtest predicted for the same moment.
Frequently asked questions
Are Polymarket trading bots profitable?
Some are, most are not after costs. On crypto up/down markets a taker pays a fee of 7% × (1 − price) of notional, 3.5% at a 50¢ price and more below it, and a market order also pays the spread and walks the book. A strategy that looks profitable on mid prices often loses once fills, fees and latency are modelled, so test against real order-book depth before risking money.
What data do I need to backtest a Polymarket bot?
Historical order books, not just prices. Polymarket's official APIs return the live book and a price series, but not past bids and asks, so you cannot see what a past order would have filled at. An archive of full-depth snapshots, such as Resolved Markets, lets you walk the book at the moment of each simulated trade.
Which library should I use to trade on Polymarket?
Polymarket's current official SDKs are py-sdk (Python) and ts-sdk (TypeScript). The older py-clob-client and clob-client are archived since the CLOB V2 upgrade in April 2026, so many tutorials and open-source bots that import them no longer work unmodified.
What are Polymarket fees for bots?
Only takers pay. The fee is shares × rate × p × (1 − p), where p is the price. For crypto markets the rate is 0.07, so the dollar fee peaks at 50¢: $1.75 per 100 shares, or 3.5% of the $50 notional. As a share of notional it is 7% × (1 − p), so it is larger on cheap outcomes. Makers pay nothing, and 20% of crypto taker fees are paid back to makers as rebates. Rates differ by category; check docs.polymarket.com/trading/fees.
Can I run a Polymarket bot from the US?
Polymarket's geoblock page lists the US as close-only for both the website and the API. Call GET https://polymarket.com/api/geoblock from the server your bot runs on before placing orders.

