← Back to blog

How to Build a Polymarket Bot in Python: Architecture, Components, and What Actually Matters

updated 2026-10-08 polymarket trading python infrastructure tutorial

By ZenHodl. Dataset documentation, research and model evaluations are linked in the article. A separate filtered ledger of bot-attributed trades, including losses and its admission rules, is public at /results.

A working Polymarket trading bot in 2026 has eight components. Most beginner tutorials show you one or two (usually authentication + a market data fetch) and stop there. The result is people copy-pasting code into a script that calls the API but is missing the persistence, the risk layer, the settlement handling, and the observability — all the parts that determine whether the bot is profitable or just an expensive way to lose USDC.

This post is the architectural walkthrough. We are not going to paste copy-pasteable code for every component — for that, see our Polymarket API Python tutorial. We are going to walk through what each component does, why it matters, and how to think about the design choices.

If you want the conceptual one-pager on what a bot does at all, our how prediction market bots work post covers that. This post assumes you understand the concept and want the production architecture.

Source check: October 8, 2026. Polymarket recommends the unified polymarket-client SDK for new projects. The token-based CLOB examples below target py-clob-client-v2 1.2.0. That package also distinguishes position_id orders, which use a different signing route; do not substitute identifiers or apply one Exchange version to every market. Public GET response shapes were checked separately; authenticated orders, cancellation and redemption were not executed for this article.

The Eight Components

A working Polymarket bot consists of:

  1. Market discovery — which markets are active right now and worth scanning?
  2. Price feed — real-time bid/ask for those markets.
  3. Fair value model — your model’s probability estimate, with its feature provenance and dated evaluation.
  4. Signal generation — when does edge cross your threshold?
  5. Risk & sizing — how big should each position be?
  6. Order placement — submit to the CLOB and confirm fills.
  7. Position tracking — what do you currently own, at what cost?
  8. Settlement & redemption — collect winnings when markets resolve.

Plus two cross-cutting concerns: logging/observability (which of these is breaking right now?) and persistence (state survives restarts).

You can skip none of these and end up with a working bot. We will walk through each in order of build priority.

1. Market Discovery

Your bot needs to know which markets are tradeable today. Use the Gamma API — if you're still choosing a data source, our guide to prediction market APIs compares Polymarket's endpoints with Kalshi, ESPN, and The Odds API:

import requests

def list_one_sports_market_page():
    tag_response = requests.get(
        "https://gamma-api.polymarket.com/tags/slug/sports", timeout=10
    )
    tag_response.raise_for_status()
    tag_id = tag_response.json()["id"]
    response = requests.get(
        "https://gamma-api.polymarket.com/markets/keyset",
        params={"tag_id": tag_id, "closed": "false", "limit": 50},
        timeout=10,
    )
    response.raise_for_status()
    return response.json()  # markets plus next_cursor; continue after_cursor

For token-backed markets inspect raw conditionId, encoded clobTokenIds and outcomes. Retain the market version as well: position-backed markets require their own identifier routing. Refresh cached metadata and handle newly listed, closed or suspended markets.

A persistent lookup can map game slugs to verified market and outcome identifiers. Keep discovery outside the hot signal path, but store its observation time and invalidate stale entries; this article does not report a current production cache count.

2. Price Feed

For update-driven book data connect to the public market channel at wss://ws-subscriptions-clob.polymarket.com/ws/market. This is transport pseudocode; connect and update_orderbook are application placeholders:

# Transport pseudocode — consult the current official realtime contract
ws = await connect(WS_URL)
await ws.send_json({"type": "market", "assets_ids": token_ids})
async for msg in ws:
    update_orderbook(msg)

You will hit operational reality fast: - The WS will drop connections silently. Build a heartbeat-based reconnection layer with re-subscription on reconnect. - Subscribing to too many tokens at once degrades message rate. Batch wisely. - The initial snapshot from a subscription is large; treat incremental updates differently from the snapshot.

The official market channel requires text PING every 10 seconds and replies PONG. Track heartbeat responses separately from market updates, reconnect with backoff, resubscribe and reconcile a fresh snapshot. A quiet market does not by itself mean a dead connection.

3. Fair Value Model

Probability estimates and execution both affect the outcome; operational reliability cannot substitute for a tested strategy.

Your fair value model takes whatever inputs you have (game state, team Elo, player stats, market microstructure) and produces a probability estimate. If you claim an independent model view, identify all market-derived features and keep their timestamps available at the decision time. Market information can be a legitimate feature, but then independence is a claim to test, not assume. Evaluate probability forecasts against held-out outcomes and price-relative strategies after costs.

Common approaches: - Win probability models trained on historical games. Inputs: score differential, time remaining, possession, Elo ratings, team stats. Output: P(home_wins). - Hierarchical models for sports with structured progression (tennis points → games → sets, soccer score over elapsed minutes). - Series models for multi-game/multi-map formats (BO3 esports) — our CS2 bot's 4-tier architecture is a worked example of degrading gracefully as data quality drops.

Evaluate calibration on a separate dated cohort with stated bins and sample counts. A 70% estimate is not a promise about a new event or sample. We cover the calculations in calibration beats accuracy; calibrated forecasts still require cost and risk checks.

4. Signal Generation

A signal fires when your fair probability and the market price disagree by enough to be tradeable after costs. The basic check:

def should_trade(fair_prob: float, market_ask_c: int, min_edge_c: int = 5) -> bool:
    market_implied_c = market_ask_c
    fair_implied_c = int(fair_prob * 100)
    edge_c = fair_implied_c - market_implied_c
    return edge_c >= min_edge_c

The real version is messier. You filter for: - Maximum edge (extreme edges are usually stale data or model bugs, not real opportunities — set an explicit model/version-specific cap and validate it; this article does not verify current per-sport settings) - Minimum and maximum entry price (avoid buying at 5c or 90c on most strategies) - Cooldowns (do not trade the same market repeatedly within X seconds) - Per-sport circuit breakers (if a sport is bleeding the last 30 days, do not trade it) - Data freshness gates (if the price feed is stale, do not trade)

Most of these are about avoiding low-quality signals, not finding good ones. The displayed model-minus-price difference is only a candidate signal; freshness, cost, size and risk rules determine whether it is eligible. We cover this layer in depth — freshness gates, the edge band, and the scan loop itself — in our real-time edge signals scanner walkthrough.

5. Risk & Sizing

Once a signal fires, how much do you bet? The honest answers in increasing sophistication:

Possible controls include fractional sizing, per-sport concentration limits and drawdown reductions. Their effectiveness depends on the probability errors, correlations and execution assumptions used in stress tests; neither Kelly nor flat sizing has a guaranteed outcome.

For the math, see our Kelly Criterion for Prediction Markets post.

6. Order Placement

Use a supported SDK matched to your market’s position system. The retained token-based example below uses py-clob-client-v2 1.2.0 and assumes client is authenticated as described in the Python tutorial. It submits an order when run; it was not run during this review. Resolve the actual token, tick, minimum size and market state first.

from py_clob_client_v2 import OrderArgs, OrderType, Side

order_args = OrderArgs(
    price=0.55,         # illustrative pUSD/share; must fit the market tick
    size=100,           # illustrative shares; check minimum and balance
    side=Side.BUY,
    token_id="TOKEN_ID_FROM_SELECTED_CTF_MARKET",
)
signed_order = client.create_order(order_args)
resp = client.post_order(signed_order, OrderType.FOK)
# Check acceptance and actual execution; submission is not a confirmed fill.

Order types matter. For a strategy chasing transient edges, OrderType.FOK (fill-or-kill) makes sense — either the full size fills now or the order cancels. For a strategy posting limit orders, OrderType.GTC (good-till-canceled) is right. Match the order type to the use case.

FOK must fill all or none. A FAK order can fill 47 of 100 shares and cancel the remaining 53; a resting GTC can remain partially filled. Reconcile each order’s actual status and fills rather than applying the same remainder rule to every order type.

7. Position Tracking

Your bot needs to know what it owns, at what entry price, and whether each position is still open. The naive approach (in-memory dict) breaks on restart. The right approach is an append-only log on disk.

A local event log can record order IDs, fill IDs, prices, sizes, fees and settlement state. It is your application record, not proof of exchange holdings. On restart, reconcile open orders and balances with the exchange/wallet before enabling new orders; a truncated recent log may omit older positions.

This matters more than people think. Without persistent position state: - Restarts wipe knowledge of open positions, so the bot cannot manage them. - The bot might re-buy the same position because it does not know it already owns it. - You cannot reconcile against actual on-chain holdings at startup.

Build the persistence layer second (after market discovery). Building it after the bot is "working in memory" means rewriting everything else around it.

8. Settlement & Redemption

When a market resolves, you need to: 1. Detect that resolution happened (poll market metadata, or watch settlement events). 2. Update your position records (resolved=True, won=True/False). 3. Redeem resolved winning positions to receive the collateral appropriate to that market flow (currently pUSD).

Redemption depends on the position system and wallet. The current position-management documentation distinguishes CTF collateral adapters from the newer Router flow; the unified SDK resolves that route from the condition ID. The former two-argument CTF.redeemPositions(conditionId, amounts) sketch and hardcoded adapter address in this article were not valid current universal instructions and are removed. Record confirmation and reconcile collateral after the operation.

Deposit wallets are the current default; legacy Proxy, Safe and standalone EOA paths differ. Select the actual wallet path and required relayer authorization rather than assuming every proxy is a Gnosis Safe.

Cross-Cutting: Logging and Persistence

The two things that turn a script into a system:

Logging. Every signal, every order, every fill, every reject. We log with structured fields (sport, market, edge, price, decision, result) so we can query the log to answer "why did the bot do X yesterday?" Plain print() statements are not enough; use the logging module with file rotation and at least info-level by default.

Persistence. All state that matters across restarts goes to disk: trade log, position state, calibration buffer, cooldowns, circuit breaker state. We use JSONL files for append-only state and JSON files for replaceable state. SQLite is fine if you outgrow flat files; many production bots run for years on JSONL with periodic compaction.

A Sensible Build Order

Do not build all eight components in parallel. The order that worked for us:

  1. Market discovery (component 1) — confirm you can list markets and resolve tokens.
  2. Persistence (cross-cutting) — set up trades.jsonl and the trade logger before any trades happen.
  3. Price feed (component 2) — get real-time prices flowing into local state.
  4. A trivial fair value model (component 3) — even just "predict 50% for everything" so you can develop the rest of the loop. Replace with real model later.
  5. Signal generation (component 4) — wire fair value into edge calculation.
  6. Order placement (component 6) — submit one tiny test order, confirm fill.
  7. Position tracking (component 7) — load and reconcile positions on restart.
  8. Settlement & redemption (component 8) — collect winnings on the first resolved trade.
  9. Revisit risk & sizing (component 5) — install limits and a kill switch before the first order, then evaluate any later change against observed fills and forecast error.

Verify each component before connecting it to execution. Completing the checklist or calibrating a model does not establish a profitable bot; source contracts, account lifecycle and strategy outcomes require separate checks.

Common Mistakes That Sink Bots

Failure modes to test for:

  1. Claiming input independence without checking features. Record market-derived inputs, their clocks and leakage controls; compare against a market-only baseline before attributing gains to other features.
  2. No stale-signal checks. Large gaps can come from stale quotes, incorrect mapping or model failures. Inspect their cause rather than assuming every large gap is profitable or false.
  3. Ignoring slippage in the backtest. An illustrative 8c quoted gap can shrink or disappear after fees and fill-price differences; this is not a measured universal 3–5c execution cost. Build realistic slippage into both backtest and live cost accounting.
  4. No concentration or drawdown tests. Kelly includes the possibility of losses but relies on accurate probabilities and payoff assumptions; estimation error and correlated positions can still produce severe drawdowns.
  5. Skipping settlement handling. Bot accumulates winning positions but never redeems them. Winning balances remain as positions until redeemed into collateral; reconcile the wallet before treating them as available spend.

Production Considerations

Once your bot works, the deployment questions:

What to Verify Before Execution

A Polymarket bot is not hard to build. It is hard to build well, because "well" means correctly handling restart, settlement, partial fills, slippage, drawdowns, stale signals, and the dozen other edge cases that do not show up in tutorials.

The 80/20 advice: spend most of your engineering time on persistence, observability, and the fair value model. The other components are largely solved problems with well-known patterns. Model evaluation, execution costs and observed fills must be assessed together. Persistence and restart reconciliation keep operational state consistent; they do not create a proven edge.

If you want to see how we sequenced this in practice, our Polymarket trading bots P&L breakdown walks through actual results across the bots described above.

Related deeper reads: - Polymarket API Python Tutorial — the hands-on API code. - Polymarket API Documentation Guide — the docs orientation and operational gotchas. - How Prediction Market Bots Work — the conceptual one-pager. - Backtesting Polymarket Strategies — testing the bot before deploying. - Kelly Criterion for Prediction Markets — sizing math. - Calibration Beats Accuracy — why fair-value model calibration matters.

Related reading

Get ZenHodl Weekly

Dataset releases, research notes, and public results, including corrections.

Research and dataset updates from ZenHodl.

Want to build this yourself?

Work through ESPN data collection, Elo, probability modeling, calibration and backtesting in six Jupyter notebooks. Inspect the free first module and course requirements before buying.