Build a Polymarket Bot with Claude Code (Paper Mode + Guardrails) A developer published a step-by-step recipe on BotCookery for using Claude Code to scaffold a Polymarket US prediction-market trading bot that defaults to paper mode, simulating fills locally against the live public order book and validating orders via POST /v1/order/preview without placing them. The guide specifies a modular Python 3.11+ architecture (config, data, strategy, risk) with hard position limits, a kill switch, bounded exponential backoff on 429/5xx responses, and a live mode locked behind a flag and typed confirmation. It explicitly offers no profitable strategy, warning that "nobody honest can promise one. Originally published on BotCookery https://botcookery.com/recipes/polymarket-bot-claude-code-paper-mode/ , a free site of build recipes for AI-built trading and prediction-market bots. Written with AI assistance and checked against the Polymarket US docs linked below. Nothing here is financial advice, and there's no strategy in it that makes money. You can build a Polymarket US bot with Claude Code by having it write a bot that runs in paper mode by default: it simulates fills locally against the live public order book and can validate orders with POST /v1/order/preview without placing them. Polymarket US has no public demo exchange, so a local paper ledger, hard position limits, a kill switch and a locked live mode are the safe way to start. Goal: A Polymarket US bot skeleton that watches markets you choose, runs your rule, and records simulated fills in a local paper ledger. A live mode exists but is locked behind a flag, a typed confirmation and hard limits. Who it's for: Builders who want a real bot structure data → rule → risk check → order without risking money on day one. Not for: Anyone looking for a strategy that makes money. We don't provide one, and nobody honest can promise one. POST /v1/order/preview body wrapped as {"request": order} to validate an order without placing it. https://gateway.polymarket.us with no key. Live orders go to POST https://api.polymarket.us/v1/orders . marketSlug , intent ORDER INTENT BUY LONG , ORDER INTENT SELL LONG , ORDER INTENT BUY SHORT , ORDER INTENT SELL SHORT , type ORDER TYPE LIMIT / ORDER TYPE MARKET , price: {value, currency: "USD"} , quantity , tif e.g. TIME IN FORCE IMMEDIATE OR CANCEL , and manualOrderIndicator , which the docs say is required MANUAL ORDER INDICATOR AUTOMATIC for a bot . POST /v1/orders after a timeout. price.value always refers to the YES long side, even when buying NO, and must be between 0.01 and 0.99. An out-of-range order still gets an order ID but never fills. orderPriceMinTickSize and minimumTradeQty before ordering. Don't assume them. POST /v1/orders/open/cancel . Paste this into Claude Code or Cursor, or Codex in an empty folder: Build a Polymarket US trading bot in Python 3.11+ requests, cryptography, pytest only that runs in PAPER MODE by default. Use only endpoints documented at https://docs.polymarket.us. If you are unsure an endpoint or field exists, ask me instead of guessing. Read CLAUDE.md the BotCookery safety rules first and follow it. If you are not allowed to write under .claude/, tell me and I will approve it; do not work around it. Architecture one file per module : - config.py : loads config.toml markets = list of slugs, poll seconds, target price and limits.json max contracts per order, max open exposure usd, max daily loss usd, max orders per minute . Fail closed if a limit is missing. - data.py : public market data from https://gateway.polymarket.us, no keys: GET /v1/markets?active=true&closed=false&limit=20 listing , GET /v1/market/slug/{slug} response {"market": {...}}; read orderPriceMinTickSize and minimumTradeQty; cache 5 min , GET /v1/markets/{slug}/book response {"marketData": {"bids": {"px":{"value"},"qty"} ,"offers": ... , "state"}}; px.value and qty are strings; prices are YES-side , GET /v1/markets/{slug}/bbo. Bounded exponential backoff on 429/5xx. - strategy.py : ONE placeholder rule I will replace: "if best YES offer <= target price from config, propose buying the minimum quantity of YES". Return an order body dict or None, plus a one-line reason. No ML, no "AI picks". - risk.py : check order enforcing every limit in limits.json plus: price within 0.01-0.99 AND on the market's tick, quantity a multiple of minimumTradeQty, no YES+NO buys on the same market self-match , and a kill switch file KILL exists or BOT KILL=1 = no orders . Unit-test every rule. - paper.py : local ledger paper ledger.json . Fill conservatively against the current book: buys lift offers at or below my limit, sells hit bids at or above it, at the displayed price, never more than displayed size, walking levels. NO intents trade the inverse BUY SHORT fills against YES bids at cost 1 - bid . Taker fee = theta qty p 1-p , theta = the market's feeCoefficient 0.0695 standard; Table Tennis 0.10, see https://docs.polymarket.us/fees , rounded to $0.01 per fill with banker's rounding round half to even , total for one order capped at the rounded cumulative fee. Test against the fee page's worked examples. Track cash, positions, realized and unrealized PnL mark at the bid . - broker.py : LIVE adapter for https://api.polymarket.us. Ed25519: the secret key from env is base64; the first 32 decoded bytes are the seed. Headers X-PM-Access-Key, X-PM-Timestamp ms, within 30 s of server time , X-PM-Signature = base64 sign timestamp + METHOD + path , e.g. path "/v1/portfolio/positions"; the body is not signed. Methods: preview order POST /v1/order/preview, body wrapped as {"request": order} , create order POST /v1/orders , cancel all POST /v1/orders/open/cancel, body {"slugs": } , positions GET /v1/portfolio/positions , balances GET /v1/account/balances . Order body: marketSlug, type ORDER TYPE LIMIT, price {value, currency "USD"} ALWAYS the YES price: buying NO at 0.83 sends 0.17 , quantity number , tif TIME IN FORCE IMMEDIATE OR CANCEL, intent, and manualOrderIndicator MANUAL ORDER INDICATOR AUTOMATIC. - bot.py : main loop. Default --paper. --preview calls the preview endpoint with my key but never places orders exit with a clear message if keys are missing . --live requires the flag, keys, AND typing "I accept the risk"; refuse if stdin is not a terminal. Every loop: kill-switch check - data - strategy - risk - paper/preview/live - log to logs/decisions.jsonl. - cancel all.py : one command that calls cancel-all in live mode. Rules: - Keys only from env vars POLYMARKET KEY ID, POLYMARKET SECRET KEY . .env.example with blanks; .env in .gitignore; never print keys. - Bounded exponential backoff on 429/5xx for reads and cancels. A "Global Rate Limit Exceeded" reject on an order is a latency stopgap, not a rate limit: log it, don't retry. - The order API has no client order id, so do idempotency locally: record a key hash of order + nonce in sent orders.json BEFORE sending, never re-send a recorded key, and never auto-retry POST /v1/orders after a timeout or 5xx outcome unknown; check open orders instead . - pytest tests for risk.py each rule , paper.py fills partial, NO side, sells, fee examples , the YES-price inversion, the live-mode gate, and broker signing verify with the public key using a fake HTTP session. Show me the file plan first. Then build it. Then give me the exact commands to run it in paper mode. git init , then add the CLAUDE.md , add the .gitignore , and add .claude/settings.json with the deny rules so Claude Code can't read your .env or key files. Claude Code will ask permission before writing under .claude/ ; approve it. config.toml . GET https://gateway.polymarket.us/v1/markets?active=true&closed=false&limit=20 . Without closed=false , the list can include markets that have already closed. Set a target price for the placeholder rule. limits.json , for example 1 contract per order, $5 max exposure, $2 max daily loss, 3 orders a minute. python bot.py --paper . Watch logs/decisions.jsonl . Every line should show the data it saw, what the rule proposed, and whether risk said yes or no. Some markets quote below $0.01 their tick can be 0.001 ; the risk check refuses those because the exchange only accepts 0.01–0.99, so a "price outside 0.01-0.99" line is the guardrail working, not a bug. touch KILL . The next loop should log "kill switch on" and place nothing. Delete the file to resume. python bot.py --preview . It calls --preview should exit with a clear message. --live , with limits you're fine losing entirely. The typed confirmation is there to make you stop and think. Level 1: local paper ledger no account needed . paper.py simulates fills against the real public book. Be conservative: fill at the ask for buys and the bid for sells, never better, and never more than the displayed size. Mark open positions at the bid, so a fresh position shows a loss equal to the spread. Real fills are often worse than paper fills because of latency, queue position and fees, so treat paper profits with suspicion. Fees belong in the simulation. The taker fee is Θ × contracts × p × 1 − p with Θ = 0.0695 on standard markets, rounded to the cent with banker's rounding round half to even . Have the AI write tests against the worked examples on the fee schedule https://docs.polymarket.us/fees ; binary floating point can round half-cent cases the wrong way, so round with Decimal and ROUND HALF EVEN . Level 2: order preview needs a key, places nothing . POST /v1/order/preview checks your parameters against the exchange tick size, minimum quantity, price validity and returns what it expects would happen. It's the best way to catch "valid in my head, rejected by the exchange" bugs before going live. What to review before you go further: decisions.jsonl with no surprise orders. 0.17 . Write a unit test for this; it's the most common way these bots buy the wrong thing. manualOrderIndicator: MANUAL ORDER INDICATOR AUTOMATIC on every bot order. The docs mark the field as required. Yes. Claude Code can write the whole structure data, strategy, risk checks, broker adapter from a good prompt. It can't make the strategy profitable, and it will make mistakes, so run it in paper mode and read the code, especially the parts that touch keys and orders. There's no public retail demo exchange documented. The practical approach is a local paper ledger that simulates fills against the live public order book, plus the POST /v1/order/preview endpoint, which validates an order and shows expected fills without placing it. US residents should use Polymarket US, the CFTC-regulated exchange, which requires identity verification before you can create API keys. International Polymarket is close-only for the US, and using a VPN to get around that breaks its terms. Be careful. Many "bots for sale" are scams or repackaged public GitHub code, and any repo you run can hide code that steals keys. If you use someone else's code, audit it first. See our GitHub audit recipe https://botcookery.com/recipes/audit-kalshi-polymarket-bot-github-repo/ .