DEV Community

BlueWhale-Quant-Lab
BlueWhale-Quant-Lab

Posted on

Building a Low-Latency Polymarket Trading Bot in Python (py-clob-client + WebSocket)

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)
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

(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))
Enter fullscreen mode Exit fullscreen mode

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 429 with backoff.

Recap

  1. Host in Amsterdam (~90 ms win).
  2. Reuse a warm py-clob-client (keep-alive/TLS).
  3. Perceive over WebSocket, act over REST.
  4. Keep your decision path lean.
  5. 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)