The reference architecture
Every Polymarket bot worth running has the same four layers. Mixing them up is the most common reason a working hello-world becomes a money-losing production bot.
| Layer | Job | Tooling |
|---|---|---|
| Sense | Read prices, books, and news | py-clob-client + Reuters / X firehose |
| Decide | Decide whether to enter / exit | Plain Python rules, or an LLM if your edge is linguistic |
| Act | Sign and post orders | py-clob-client + a Polygon wallet |
| Audit | Persist signals, decisions, fills | SQLite to start, Postgres at scale |
Build them in this order. Skipping audit until "later" is the most common production failure mode in this niche.
Setup, in five commands
uv venv && source .venv/bin/activate
uv pip install py-clob-client python-dotenv
cat > .env <<'EOF'
PRIVATE_KEY=0x...your_polygon_wallet_private_key
RPC_URL=https://polygon-rpc.com
EOF
# Fund the wallet with USDC on Polygon and ~$5 of MATIC for gas
That is the whole setup. No accounts, no API approvals, no waiting list: Polymarket is permissionless at the SDK layer.
Hello-world: read a book, place a limit order
import os
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType
load_dotenv()
client = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key=os.environ["PRIVATE_KEY"],
)
client.set_api_creds(client.create_or_derive_api_creds())
token_id = "0x..." # YES token of your chosen market
book = client.get_order_book(token_id=token_id)
best_bid = float(book.bids[0].price)
my_price = round(best_bid - 0.01, 3)
order = client.create_and_post_order(OrderArgs(
price=my_price, size=10.0, side="BUY", token_id=token_id,
), OrderType.GTC)
print("Posted:", order["orderID"])
That is a working bot. It is also a useless one: there is no strategy. Every honest bot from here is a bigger version of "what should the price be", evaluated against what the price is.
A real strategy: news-reaction trade
Prediction markets price natural-language events. A bot that reads the news faster and judges it better than the median trader has structural edge: for as long as the median trader is a human refreshing X.
import asyncio, json
from anthropic import AsyncAnthropic
from x_firehose import stream_keywords # pseudo-package
claude = AsyncAnthropic()
PROMPT = """You are a prediction-market analyst. Given this news item:
{news}
And this market description:
{market}
Output JSON: {{ "side": "YES"|"NO"|"NONE", "confidence": 0.0..1.0,
"target_price": 0.0..1.0, "reasoning": "..." }}"""
async def react(news_item, market):
msg = await claude.messages.create(
model="claude-sonnet-4-5",
max_tokens=400,
messages=[{"role": "user", "content":
PROMPT.format(news=news_item, market=market["description"])
}],
)
return json.loads(msg.content[0].text)
async def loop():
async for news in stream_keywords(["fed", "rates", "powell"]):
for market in WATCHLIST:
decision = await react(news, market)
if decision["confidence"] > 0.75 and decision["side"] != "NONE":
place(market, decision)
That is the entire shape. The hard parts are not in the diagram: they are in the production hardening.
What "production-ready" actually means
- Risk caps. Per-market notional, per-day notional, per-news-item notional. Pick numbers before you go live, not after.
- Idempotent orders. Tag every order with a deterministic
client_order_idderived from(news_id, market_id, side)so a retry never doubles up. - Observability. Log every prompt, every model response, every signed order, every fill. When a bot misbehaves you will be reading logs, not stack traces.
- Drawdown circuit-breakers. Auto-halt on -3% day, -5% week. The strategy you wrote at 2am is not the strategy you want trading at -5%.
- Health check. A liveness endpoint your alerting can poll. Polymarket does not call you when an oracle is disputed; you find out from your bot's silence.
The single-LLM trap
The hello-world above uses one model. That is fine for tutorials and dangerous for production. In NickAI's Q1 2026 internal benchmark, a single frontier model misclassified roughly 45% of market-direction signals; seven models in parallel weighted by historical calibration brought that to roughly 10%, a 78% relative reduction. Multi-model consensus is not an optimisation: it is the difference between a casino and an edge.
Implementing consensus by hand is a few hundred lines plus the operational cost of running it. Implementing it as a node in an agent graph is one line. That is the trade-off NickAI exists to collapse.
When you have outgrown py-clob-client
The signal that you have:
- Your strategy code is 80% plumbing, news ingestion, model retries, audit trail, alerting, and 20% strategy.
- You are running on more than five markets and dread adding a sixth.
- Each new model you want to ensemble adds a week of integration work.
- You spend longer reviewing logs than refining the strategy.
At that point you graduate to an agent runtime. The whole point of the runtime is that the four reference layers above become primitives, not code you maintain.