Resolved Markets logoResolved Markets logo
Developer Guide

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.93

Each $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/geoblock from 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