Prediction-market bots: build a dry-run workflow

Build a prediction-market bot that reads public data and simulates fills without keys, wallets or real orders. Steps, a worked example and stopping rules.

In this guide

What a prediction-market dry run actually is

A prediction-market bot is a program that reads market data, forms a signal and either sends orders or pretends to. A dry run is the second mode: it polls public metadata, records what it would have done, and simulates the fill, with no API keys, no wallet and no order call. That last part matters because on Polymarket the public discovery endpoints return events and markets without authentication, while placing trades uses a different authenticated path. Read-only API examples are limited snapshots, not trading permission.

The workflow below is a protocol, not a tested strategy. It has 5 checks in order: poll metadata with a timestamp, confirm the market is active and open, confirm quote freshness and depth, record one hypothetical signal, then simulate a fill under assumptions you write down. When data is missing, stale or the market is closed, you stop. A timeout is a data gap, never a price of zero.

Read the discovery feed correctly

Discovery returns a page of events plus a cursor. You paginate until the cursor is exhausted, otherwise you silently miss markets and your bot looks selective when it is only short-sighted. Events also carry nested markets, and each market has outcome token IDs that are not the same as event or condition IDs. Keep them apart from the start: deduplication later depends on it.

Market details give the question, outcomes, outcome prices, token IDs, activity, closure status, order acceptance, liquidity and volume. Several of those arrive as serialized arrays, so parse before you compare. Store the observation date with every price you keep. Volume and liquidity measure different things, and a missing or stale field is not a zero you can safely compare against.

Check the quote you would actually cross

The price shown on a market page is usually the midpoint between the best bid and the best ask. When the spread is wider than 0.10, the venue may display the last trade instead, which tells you even less about what you could pay. A midpoint is not executable. A market order walks through price levels and pays slippage, so depth at and around the quote is part of the data, not decoration. Record the quote time and the depth you assume, or your simulated fill is fiction.

Worked dry-run example: 1 signal, 2 polls, 1 decision

This example is illustrative, not a study. Picture a discovery poll at 2026-10-10T12:00:00Z that returns an open market. Parsed outcome prices are [0.55, 0.45], the spread is 0.03 and there are 500 units of depth within 0.02 of the midpoint. Your bot checks active=true and closed=false, sees a quote 45 seconds old, and records a hypothetical buy on the first outcome. That record gets a decision ID built from event, market and a stable signal identifier.

At 12:05:00Z the same signal appears again. Because event ID, market ID and decision ID match, the repeat is reconciled as the same decision, not a second profitable fill. For the simulated entry you declare assumptions: you cross the spread, you take the ask rather than the 0.55 midpoint, you cap the fill at 50 percent of displayed depth, and you charge 0.01 of slippage per 100 units. Then the next poll times out. The bot logs a data gap, keeps the last valid observation as state, and records no fill. If the market later reports active=false or closed=true, it stops recording signals entirely. The losing case is the common one here: a bot that counts every repeat as a trade will show a long, winner-heavy dry-run log that describes one position, not many.

Illustrative dry-run case: one signal across 2 polls is one decision
StepIllustrative inputRecorded outcome
Poll 12026-10-10T12:00:00Z, quote age 45 s, spread 0.03, depth 500 units within 0.02, outcome prices [0.55, 0.45]Active and open; hypothetical buy on first outcome gets one decision ID
Poll 22026-10-10T12:05:00Z, same event, market and decision IDReconciled as the same decision, no second fill
Fill simulationCross the spread to the ask, cap at 50 percent of depth, 0.01 slippage per 100 unitsOne simulated entry recorded, assumptions written in the log
Poll 3API times outData gap logged; no fill; last valid observation kept as state
Later pollactive=false or closed=true, or order acceptance offBot stops recording signals

Declare fill assumptions before you score anything

A fill simulation is only as honest as its stated inputs: which side of the book you crossed, how much depth you consumed, how slippage is charged and whether partial fills are allowed. If the spread is 0.03 and you assume a market order, your entry is the ask, not the midpoint. If depth is 200 units and you simulate 1,000, you are trading size that was not there. Write each assumption in the log so a later reader can argue with it instead of trusting it. Note the limits: queue position at a limit price is unknown from public data, so any limit-fill simulation is a guess, and a real book can move between your poll and any hypothetical order.

Reconcile repeats, then set stop conditions

Reconciliation is one rule: collapse records that share an event ID, market ID and decision ID into a single decision outcome with one entry. Without it, a 5-minute polling loop turns a signal that persisted for an hour into 12 separate trades and inflates every count in your log.

Stop conditions belong in code, not in your head. Stop when the market disappears from discovery, when the quote timestamp passes your freshness threshold, when active=false or closed=true, when order acceptance is off, or when the response is malformed. Log the reason for each stop. This is what prevents an outage or a closed market from being read as a quiet non-event, and it keeps stale prices out of your fill math.

Where to go next

2 things usually break dry runs. The first is signal quality: with a handful of reconciled decisions you can easily tune rules until the past looks perfect, so use a documented backtest methodology for signal evaluation before you trust any dry-run score. The second is order execution, which requires its own risk controls before any move beyond simulation: position limits, a kill switch and a reconciliation between simulated and actual fills. The dry run stays keyless on purpose; it exists to test your data assumptions, not to grant trading authority.

For endpoint specifics, pagination behaviour and the difference between public reads and authenticated trading calls, see the read-only endpoint behavior when discovering prediction markets. Keep the bot on public GETs and nothing else.

Sources & verification

Polymarket: Discover markets ↗

Sources checked

Polymarket: Market details ↗

Sources checked

Polymarket: Prices and order book ↗

Sources checked

PolyZeno. Automated review with DeepSeek V4.1 Flash.