Prediction markets are one of the more interesting algorithmic trading frontiers right now. Unlike crypto spot markets, where every quant shop on the planet is fighting over microseconds, Polymarket is still a space where a well-built bot with good information — not speed — wins. Prices on events like election outcomes, central bank decisions, and geopolitical escalations are moved by news, and news is slow enough that a properly wired AI pipeline can genuinely extract edge.
I have been building on Polymarket's API for several months. This article is a technical walkthrough of the architecture I landed on: a webhook-triggered event loop that pulls live news, sends it through an AI model for signal generation, and executes trades via the CLOB API. The full stack is Python. I will cover the real sharp edges.
If you are new to prediction markets and want to understand what probability pricing actually looks like before reading further, spend some time exploring active markets on PolyMarket Predictions — it gives you a quick intuition for how crowd probability consensus forms and where the mispricing opportunities tend to cluster.
The Three-API Architecture
Polymarket exposes three distinct APIs and you need to understand which one does what before writing a single line of bot code.
The Gamma API (https://gamma-api.polymarket.com) is your market discovery layer. You query it to find active markets, get their descriptions, retrieve token IDs for each outcome, and understand the event structure. Think of it as a read-only catalog. No auth required for reads.
The CLOB API (https://clob.polymarket.com) is where trading happens. Order books, prices, bid/ask spreads, order placement, cancellations — all of it lives here. This one requires authentication via EIP-712 signed messages on Polygon (chain ID 137). The official Python client is py-clob-client, installable via pip.
The Data API (https://data-api.polymarket.com) covers your positions, trade history, and portfolio state. You use it for post-trade reconciliation and position tracking.
The mental model is: Gamma to find markets → CLOB to trade → Data to reconcile. A bot that skips this separation ends up with a mess of mixed concerns that is very hard to debug at 2am.
Setting Up the CLOB Client
Install the dependencies:
pip install py-clob-client python-dotenv anthropic httpx
Authentication uses your Polygon wallet's private key, and Polymarket proxies all trades through a proxy wallet contract. The signature type matters — for most setups you want POLY_PROXY (signature type 1):
import os
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import ApiCreds
from dotenv import load_dotenv
load_dotenv()
HOST = "https://clob.polymarket.com"
CHAIN_ID = 137 # Polygon mainnet
client = ClobClient(
host=HOST,
key=os.getenv("PRIVATE_KEY"),
chain_id=CHAIN_ID,
signature_type=1, # POLY_PROXY
funder=os.getenv("PROXY_WALLET_ADDRESS"),
)
# Derive API credentials — call this once, cache the result
creds: ApiCreds = client.create_or_derive_api_creds()
print(f"API key: {creds.api_key}")
One thing that trips people up: create_or_derive_api_creds() makes a write call to the blockchain to register your API key. It costs a tiny amount of MATIC. Do not call it in a hot loop. Call it once at startup, persist the credentials, and reuse them.
Real-Time Market Data via WebSocket
Polling the REST endpoints for price updates is a path to 429 errors and stale data. The right approach is the WebSocket market channel, which pushes orderbook snapshots and price changes as they happen.
import asyncio
import json
import websockets
MARKET_WS = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
async def stream_market(token_id: str, on_event):
async with websockets.connect(MARKET_WS) as ws:
# Subscribe to the specific outcome token
subscribe_msg = {
"type": "subscribe",
"channel": "market",
"assets_ids": [token_id]
}
await ws.send(json.dumps(subscribe_msg))
async def heartbeat():
while True:
await asyncio.sleep(10)
await ws.send("PING")
asyncio.create_task(heartbeat())
async for raw in ws:
if raw == "PONG":
continue
event = json.loads(raw)
await on_event(event)
The channel sends you book events (full orderbook snapshots), price_change events, and last_trade_price events. For a signal-driven bot, price_change is the one you care about most — it tells you when the market's probability estimate has moved, which is your trigger to re-evaluate whether you still want to be in a position.
The Webhook Listener for News Events
The price feed tells you what the market thinks. News webhooks tell you what the world is doing. My setup uses a lightweight FastAPI endpoint that receives POST requests from a news aggregation service whenever a story matching a set of keywords fires:
from fastapi import FastAPI, Request, HTTPException
import hmac, hashlib, os
app = FastAPI()
WEBHOOK_SECRET = os.getenv("NEWS_WEBHOOK_SECRET").encode()
@app.post("/webhook/news")
async def news_webhook(request: Request):
body = await request.body()
sig = request.headers.get("X-Webhook-Signature", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET, body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(sig, expected):
raise HTTPException(status_code=401, detail="Bad signature")
payload = await request.json()
await signal_pipeline(payload)
return {"status": "accepted"}
Always verify webhook signatures before processing. An unsigned endpoint is a liability — someone can flood your bot with fake news events and trigger bad trades. The hmac.compare_digest constant-time comparison is there to prevent timing attacks, not just a style choice.
The signal_pipeline function is where the interesting work happens. That is where the AI comes in.
AI Signal Generation with Claude
When a news event arrives, the question the bot needs to answer is: does this headline shift the true probability of this market's outcome, and if so, by how much relative to the current market price? That is a structured reasoning task that LLMs handle surprisingly well when given the right context.
I use Claude for this because its instruction-following is precise enough to reliably return a structured JSON signal rather than prose. Here is the core of the signal pipeline:
import anthropic
import json
claude = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
async def signal_pipeline(news_event: dict) -> dict | None:
market_context = await get_market_context(news_event["keywords"])
if not market_context:
return None
prompt = f"""You are a prediction market analyst. Given a news event and
a market's current probability, estimate whether the news shifts
the true probability meaningfully.
News headline: {news_event['headline']}
News summary: {news_event['summary']}
Market question: {market_context['question']}
Current market probability (YES): {market_context['yes_price']:.1%}
Market resolves: {market_context['end_date']}
Return ONLY valid JSON with these fields:
- direction: "YES" | "NO" | "SKIP"
- confidence: 0.0 to 1.0
- reasoning: one sentence
- suggested_size_usdc: integer (max 100)"""
resp = claude.messages.create(
model="claude-opus-4-6",
max_tokens=256,
messages=[{"role": "user", "content": prompt}]
)
try:
signal = json.loads(resp.content[0].text)
if signal["confidence"] >= 0.65 and signal["direction"] != "SKIP":
return signal
except (json.JSONDecodeError, KeyError):
pass
return None
The confidence threshold of 0.65 is one you tune empirically. Start higher — 0.75 — and lower it only once you have data on your model's calibration. An overconfident bot burning through USDC on low-conviction signals is the most common failure mode I see in first attempts.
Some teams pair two models here: Grok for real-time social sentiment parsing (it has live X/Twitter access), then Claude for structured reasoning over the combined news + sentiment context. This multi-model approach adds latency but improves signal quality on markets that are heavily driven by social momentum, like sports and political scandals.
Order Execution and Risk Management
The signal tells you what direction to trade. Risk management tells you whether to actually send the order. Before touching execution logic, internalize this: Polymarket uses a USDC stablecoin order book on Polygon. Every outcome token trades between 0 and 1 USDC, representing a probability. If you buy YES at 0.62 and the market resolves YES, you receive 1.00 USDC per share — a 38-cent profit. If it resolves NO, you lose your 0.62.
A minimal but non-naive execution function:
from py_clob_client.clob_types import MarketOrderArgs, OrderType
MAX_POSITION_USDC = 100
MAX_TOTAL_EXPOSURE_USDC = 500
def execute_signal(signal: dict, token_id: str, current_book: dict):
if signal["direction"] == "SKIP":
return
# Check total portfolio exposure before trading
current_exposure = get_total_exposure() # your tracking logic
if current_exposure >= MAX_TOTAL_EXPOSURE_USDC:
print("Exposure limit reached, skipping")
return
size = min(
signal["suggested_size_usdc"],
MAX_POSITION_USDC,
MAX_TOTAL_EXPOSURE_USDC - current_exposure
)
# Check slippage — don't cross if spread is too wide
best_ask = float(current_book.get("asks", [{}])[0].get("price", 1))
best_bid = float(current_book.get("bids", [{}])[0].get("price", 0))
spread = best_ask - best_bid
if spread > 0.04: # 4-cent spread maximum
print(f"Spread {spread:.3f} too wide, skipping")
return
order_args = MarketOrderArgs(
token_id=token_id,
amount=size, # USDC amount
)
signed_order = client.create_market_order(order_args)
resp = client.post_order(signed_order, OrderType.FOK) # Fill-or-Kill
print(f"Order response: {resp}")
A few decisions worth explaining here. Fill-or-Kill order type means if the full order cannot execute at or better than the current market price, it is cancelled entirely. This prevents partial fills at bad prices during thin book conditions. The spread check catches illiquid markets where your market order would eat through multiple price levels and give you terrible average fill. And the total exposure cap is non-negotiable — it is what keeps a malfunctioning signal generator from bankrupting the account before you notice.
Putting It All Together
The full bot runs three concurrent loops: the FastAPI webhook server (news inbound), the WebSocket market feed (price updates), and a position reconciliation loop (every 30 seconds, checks open positions against current prices and closes any that have exceeded a stop-loss threshold).
Understanding the market microstructure is as important as the code. Before running any real capital, I strongly recommend spending time on PolyMarket Predictions to observe how prices move around real news events — watch how quickly a market re-prices after a major announcement and how long the drift afterward tends to last. That pattern recognition is what your AI signal thresholds should be calibrated against.
A few things I learned the hard way that are worth calling out explicitly:
Rate limits are real. The CLOB API will 429 you if you hammer it. Use WebSocket for market data (no rate limit impact) and implement exponential backoff on any REST calls. Start with a 5-second floor between order submissions.
Market resolution is on-chain and final. Unlike a crypto exchange where you can sometimes dispute a bad fill, prediction market resolutions are determined by UMA's oracle system and are not reversible. Verify the market's resolution source and end date before every position you open.
Thin books will eat you. A market with 24-hour volume under $10k will have a book so thin that even a $50 market order moves the price meaningfully. Filter your target markets to those with at least $50k daily volume before deploying real capital.
News latency is your real edge. The bot that wins is not the fastest at order submission — Polymarket's own latency makes sub-millisecond execution irrelevant. The edge is in AI analysis quality: does your model correctly weight a breaking news headline against the existing probability, faster and more accurately than the humans and slower bots in the market? That is a prompt engineering and model selection problem, not an infrastructure problem.
The complete implementation with a backtest harness using historical Gamma API data is worth building before going live. If your signals do not show positive expected value in backtesting on markets that have already resolved, they will not magically improve on live capital. Run it on six months of resolved markets, accounting for spread costs, and see what you actually have before putting real money at risk.
Prediction markets are a genuinely fair game for well-built bots right now. The tooling — Polymarket's CLOB API, the py-clob-client library, the WebSocket feeds — is mature and developer-friendly. The AI models for news analysis are capable enough to generate real edge. What is still rare is the careful combination of all three with disciplined risk management. Build that stack, and you are competing against a field that is still mostly doing this manually. That gap closes over time, but it is open right now.
Start by exploring what live markets look like and how they price uncertainty on PolyMarket Predictions. Then build the pipeline described above incrementally — webhook listener first, AI signal second, execution last. Do not connect execution until the first two are producing results you trust. That sequencing will save you a lot of USDC.