How to Build a Polymarket Trading Bot
Most Polymarket bot tutorials are built on SDKs that no longer exist. This is the current path end to end — what to install in 2026, how auth actually works, where the data gaps are, and the failure modes that kill bots in week one.
A Polymarket bot is six pieces: discover markets, read prices, decide, place orders, manage positions, and stay alive. The first two are easy, the fourth is fiddly, and the sixth is where most bots actually die. This guide walks the whole arc with verified code, and is honest about the parts that are genuinely hard.
Step 0: Decide what your bot trades
Worth doing before you write a line, because it determines everything downstream. Polymarket markets fall into two families with almost nothing in common:
| Event markets | Crypto Up/Down markets | |
|---|---|---|
| Examples | Elections, sports, awards | btc-updown-5m, eth-updown-15m |
| Lifespan | Weeks to months | 5 minutes to 24 hours |
| What moves price | News, information, sentiment | Microstructure and order flow |
| Edge comes from | Being better informed | Being faster or better priced |
| Trades per day | A handful | Hundreds |
| Latency sensitivity | Low | High |
If you are automating, you are almost certainly interested in the second family — high frequency, continuous, and mechanical enough to encode. Those are covered in depth in Up/Down markets explained.
Step 1: Install the current SDK
Both SDKs give you one client interface over all of Polymarket's APIs, typed models, consistent pagination and built-in signing — which is a large amount of fiddly work you do not want to reimplement.
pip install polymarket-client
# or: uv add polymarket-client / poetry add polymarket-clientThere are four Python clients, and picking the right one matters more than it looks:
| Client | Public data | Trading | Realtime |
|---|---|---|---|
AsyncPublicClient | Yes | No | Yes |
AsyncSecureClient | Yes | Yes | Yes |
PublicClient | Yes | No | No |
SecureClient | Yes | Yes | No |
subscribe(). Bots want the async variants; the sync ones are for scripts and notebooks.Step 2: Find markets
Public market discovery needs no credentials at all. The SDK's paginator is consistent across every list method, so this pattern generalises.
import asyncio
from polymarket import AsyncPublicClient
async def main() -> None:
async with AsyncPublicClient() as client:
pages = client.list_markets(closed=False, page_size=50)
# Iterate items directly when page boundaries do not matter.
async for market in pages.iter_items():
if "updown" in (market.slug or ""):
print(market.slug, market.condition_id)
asyncio.run(main())Two things to internalise here. First, page.next_cursor is an opaque cursor — store it verbatim if you want to resume a scan later, and do not try to parse or construct one. Second, /markets carries the tightest rate limit on the whole platform at 300 req / 10s, so cache market metadata rather than re-listing it in a hot loop. Full numbers in the rate limits reference.
Step 3: Authenticate
Trading needs a wallet. The Python SDK builds the signer from a local private key:
import os
from polymarket import AsyncSecureClient
client = await AsyncSecureClient.create(
private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
)Underneath, Polymarket uses two auth layers: L1 is an EIP-712 wallet signature that proves you control the address and lets you create or derive API credentials; L2 signs each private request with those credentials using HMAC-SHA256. The SDK handles both, which is the main reason to use it.
Step 4: Get real-time prices
The most common architectural mistake in a first bot is polling /book on a timer. It burns your rate limit budget, adds latency, and scales badly. Subscribe instead — the SDK merges multiple feeds into one event stream.
from polymarket.streams import MarketSpec
async with await client.subscribe([MarketSpec(token_ids=[token_id])]) as stream:
async for event in stream:
# MarketBookEvent | MarketPriceChangeEvent | MarketLastTradePriceEvent
# | MarketTickSizeChangeEvent | MarketBestBidAskEvent
# | NewMarketEvent | MarketResolvedEvent
handle(event)If you connect to the raw WebSocket instead of using the SDK, you must send a PING every 10 seconds or the connection is dropped, and you need custom_feature_enabled: true to receive best_bid_ask, new_market and market_resolved events at all. Those and the reconnect patterns are covered in the WebSocket guide.
Step 5: Place an order
A market order is the simplest thing that works, and amount is denominated in pUSD you are willing to spend rather than in shares:
market = await client.get_market(slug="<market-slug>")
token_id = market.outcomes.yes.token_id
assert token_id is not None
response = await client.place_market_order(
token_id=token_id,
side="BUY",
amount="10", # spend up to 10 pUSD
)
if not response.ok:
raise RuntimeError(response.message)
# Settlement is on-chain and asynchronous — this waits for it.
hashes = await client.wait_for_order_fill_settlement(response)For resting limit orders, two rules will bite you before anything else does. Prices must sit on the market's tick grid — call GET /tick-size per market rather than assuming 0.01, because tick size varies and can tighten as a market matures. And orders below the market's minimum size are rejected outright. Both come back as 400s with specific messages, listed in the error reference.
Step 6: Know whether the strategy works
Everything above is plumbing, and plumbing is the easy half. The hard question is whether your strategy makes money — and this is where Polymarket bot building diverges sharply from crypto spot bot building.
There is no testnet. No sandbox, no demo API, no paper trading endpoint. The words do not appear anywhere in Polymarket's documentation. Your first real order is real money against real counterparties. See does Polymarket have a testnet for the four workarounds.
And there is no historical order book. Polymarket's API serves current state only. Once a market resolves, its book returns 404 and its price history returns 200 with an empty array — a successful response containing nothing:
TOK=<token_id_of_a_resolved_market>
curl -s "https://clob.polymarket.com/book?token_id=$TOK"
# {"error":"No orderbook exists for the requested token id"} HTTP 404
curl -s "https://clob.polymarket.com/prices-history?market=$TOK&interval=max&fidelity=1"
# {"history":[]} HTTP 200So you cannot backtest from Polymarket. You need point-in-time snapshots that somebody recorded while the markets ran. That is what PolyTest provides for crypto Up/Down markets — 90M+ snapshots, 8 levels of book depth per side, sub-second timestamps, resolved markets retained:
import os, requests
BASE = "https://api.polytest.io/api/v1"
H = {"X-API-Key": os.environ["POLYTEST_KEY"]}
markets = requests.get(f"{BASE}/markets",
params={"coin": "btc", "market_type": "5m", "limit": 50},
headers=H).json()["markets"]
snaps = requests.get(f"{BASE}/markets/{markets[0]['id']}/snapshots",
params={"include_orderbook": "true"},
headers=H).json()["snapshots"]
def simulate_fill(levels, qty):
"""Walk the ladder. Mid-price is not a price you can trade at."""
filled = cost = 0.0
for lvl in levels:
take = min(lvl["size"], qty - filled)
cost += take * lvl["price"]
filled += take
if filled >= qty:
break
return cost / filled if filled >= qty else None
print(simulate_fill(snaps[0]["orderbook"]["asks"], 500))Step 7: Survive contact with production
A bot that trades correctly for an hour and then wedges is worse than no bot. These are the failure modes that actually occur, in rough order of how often they bite:
| Failure | What you see | Handling |
|---|---|---|
| Rate limited | Latency climbing, not errors | Cloudflare throttles rather than rejecting — watch p99, not error rate |
| Matching engine restart | 425 Too Early | Retry with backoff; do not alert on singles |
| Exchange paused | 503 | Stop opening. Cancel if permitted |
| Cancel-only mode | 503 on new orders | Cancel path must work independently of the entry path |
| Post-only mode | 503 + retry_after_seconds | Sleep exactly that long, then retry |
| Signer mismatch | 400 order signer error | Stale credentials from another wallet — re-derive |
| WebSocket drop | Silence | No data is not the same as no change — track last-message age |
| Clock drift | 401 on a bot that worked yesterday | Run NTP; this one wastes the most time |
- Test the cancel path before the entry path. Being unable to exit is far worse than being unable to enter.
- Cap exposure in code, not in your head. A hard position limit turns a logic bug into a small loss instead of an unbounded one.
- Build a kill switch and actually test it. An untested kill switch is a comment.
- Add jitter to scheduled work. Workers waking on the exact second create a synchronised spike far above your average rate.
- Log every request and response for the first week. You will need them, and you cannot reconstruct them later.
- Alert on categories, not messages. Polymarket embeds order IDs and prices in error strings, so raw-string alerting is pure noise.
A realistic build order
The sequence that wastes the least time and money:
- Read-only integration, public client, no keys. Prove discovery, parsing, pagination and backoff. One day, free.
- Backtest the strategy on historical books with honest fill simulation. Most ideas die here. That is the point.
- Paper trade live for at least a week — log intended orders instead of sending them. Proves uptime, reconnects and timing.
- Go live at minimum size with a hard exposure cap and a tested kill switch.
- Scale only when live numbers match paper numbers. If they diverge, your fill model was wrong — return to step 2.
Steps 1, 3 and 4 you can build in a few days. Step 2 needs data Polymarket does not keep, and it is also the step that most reliably saves money. Start free on PolyTest — no card, and enough to find out whether your idea survives the spread.
Frequently asked questions
- How do I build a Polymarket trading bot?
- Install the current unified SDK — polymarket-client for Python or @polymarket/client for TypeScript. Use AsyncPublicClient to discover markets and read prices without credentials, then AsyncSecureClient with a private key to trade. Subscribe to the WebSocket market channel rather than polling for prices, place orders with place_market_order, and handle 425, 429 and 503 responses with backoff. Before going live, backtest against historical order book snapshots with realistic fill simulation, since Polymarket has no testnet and keeps no order book history.
- What SDK should I use for a Polymarket bot in 2026?
- Use the unified SDKs: polymarket-client for Python or @polymarket/client for TypeScript. These replace the older py-clob-client, @polymarket/clob-client-v2, @polymarket/builder-relayer-client and @polymarket/builder-signing-sdk packages. A unified Rust SDK is in development. Most tutorials and AI-generated code still reference the deprecated packages, so check the imports before trusting a snippet.
- Can I paper trade on Polymarket before risking real money?
- Not through Polymarket — there is no testnet, sandbox or demo API. You can build a paper-trading shim yourself by running your live strategy loop against production market data and logging intended orders with the book state at that instant instead of submitting them. That proves your bot is operationally sound, but it assumes fills, so it will not teach you about queue position or market impact.
- How do I backtest a Polymarket trading bot?
- You need historical order book snapshots from a third party, because Polymarket's API returns 404 for a resolved market's book and an empty array for its price history. Pull point-in-time snapshots with full depth, replay them chronologically without peeking ahead, and simulate fills by walking the book levels rather than assuming mid-price. Up/Down spreads of 2-5 cents on a $1 contract are usually larger than the claimed edge, so mid-price backtests are misleading.
- Why does my Polymarket bot get Unauthorized/Invalid api key?
- Most often one of two things. Either your credential set is incomplete — L2 authentication needs the API key, secret and passphrase together — or your system clock has drifted, since the request signature embeds a timestamp and a few seconds of drift invalidates it. Less commonly, you derived credentials against a different wallet than the one you are trading from.
- Do I need a WebSocket for a Polymarket bot or can I poll?
- Use the WebSocket. Polling /book on a timer burns rate limit budget, adds latency and scales poorly. Subscribe to the CLOB market channel for public book and price updates, and the user channel for your own order and trade updates. If you connect to the raw WebSocket rather than through an SDK, send a PING every 10 seconds to keep the connection alive.
Get the historical data Polymarket does not keep
PolyTest records Polymarket crypto Up/Down markets as they run — 90M+ snapshots with 8 levels of order book depth, sub-second timestamps, and resolved markets preserved. Free tier, no card.
Keep reading
Polymarket WebSockets: Real-Time Data That Does Not Drop
Polling the order book is the most common way to burn a rate limit budget for no benefit. Here are the four streams, the subscription options nobody documents, and the failure mode where your bot receives silence and calls it calm.
The Polymarket Python SDK in 2026 (and Migrating off py-clob-client)
If your code still imports ClobClient from py_clob_client, it is built on a superseded package — and so is most of the Polymarket code an AI assistant will write for you. Here is the current SDK and how to move.
Does Polymarket Have a Testnet?
Short answer: no. Polymarket has never shipped a public testnet, and there is no sandbox environment or demo API. Your first real order is a real order, with real money, against real counterparties. Here is how experienced builders work around that.