This is a hands-on guide to a low-latency Polymarket trading bot in Python. We'll wire up py-clob-client for orders, the CLOB WebSocket feed for real-time book updates, and cover the infrastructure decision that beats every code optimization combined.
Architecture
WebSocket feed ──► strategy logic ──► REST place/cancel ──► Polygon settlement
(perceive, (your edge) (act, warm conn)
~1.2 ms info)
Two channels: WebSocket to perceive (push, no polling lag) and REST to act (place/cancel orders). Keep both warm.
Step 0: host near the order book (the biggest lever)
Polymarket's CLOB API answers from Amsterdam. I benchmarked it: ~1.2 ms from an AMS box vs ~88 ms from US-East. Before any code, deploy your bot in an Amsterdam-metro datacenter — that single choice is worth ~90 ms, more than anything below.
Sanity check from your server:
curl -s -o /dev/null -w "connect=%{time_connect} ttfb=%{time_starttransfer}\n" \
https://clob.polymarket.com/
I run mine on a modest AMS VPS (bots are network-bound, so a few cores + NVMe is plenty): the Amsterdam VPS I run the bot on
Disclosure: affiliate link, I earn a referral. It's the box producing the 1.2 ms.
Step 1: authenticate with py-clob-client
from py_clob_client.client import ClobClient
HOST = "https://clob.polymarket.com"
client = ClobClient(HOST, key=PRIVATE_KEY, chain_id=137) # Polygon
creds = client.create_or_derive_api_creds()
client.set_api_creds(creds)
Reuse this client. Don't reconstruct it per order — you'd pay a fresh TCP + TLS handshake every time. One warm client = keep-alive = you pay TLS once.
Step 2: place and cancel orders
from py_clob_client.clob_types import OrderArgs
from py_clob_client.order_builder.constants import BUY, SELL
def place(token_id, price, size, side=BUY):
order = client.create_order(OrderArgs(
token_id=token_id, price=price, size=size, side=side))
return client.post_order(order)
def cancel(order_id):
return client.cancel(order_id)
Step 3: perceive via WebSocket (no polling)
Polling GET /book makes your view as stale as your loop interval. Subscribe instead:
import asyncio, json, websockets
WS = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
async def stream(token_ids, on_update):
async with websockets.connect(WS, ping_interval=20) as ws:
await ws.send(json.dumps({"assets_ids": token_ids, "type": "market"}))
async for raw in ws:
on_update(json.loads(raw)) # 'book' snapshots + 'price_change' deltas
(Endpoints/params evolve — verify against current Polymarket docs.)
Step 4: the reactive loop
def on_update(msg):
book = maintain_local_book(msg) # apply snapshot/deltas
signal = strategy(book) # YOUR edge — keep it lean
if signal:
place(signal.token_id, signal.price, signal.size, signal.side)
asyncio.run(stream([TOKEN_ID], on_update))
Once the network is ~1.2 ms, you can become the bottleneck. Keep strategy() tight; push logging/analytics off the hot path; avoid per-tick allocations and JSON re-parsing.
Step 5: don't get throttled
The CLOB enforces rate limits (check current docs). Rules of thumb:
- Get data over WebSocket, not REST — save your REST budget for orders.
- Batch cancels when the market moves; pulling stale quotes is the priority.
- Handle
429with backoff.
Recap
- Host in Amsterdam (~90 ms win).
- Reuse a warm
py-clob-client(keep-alive/TLS). - Perceive over WebSocket, act over REST.
- Keep your decision path lean.
- Respect rate limits.
Get step 0 right and the rest is incremental. Skip it and no amount of clever code makes you competitive.
Latency figures from my own 2026 tests; code is illustrative — verify against current docs. Not financial advice.
Top comments (0)