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.
| Step | Illustrative input | Recorded outcome |
|---|---|---|
| Poll 1 | 2026-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 2 | 2026-10-10T12:05:00Z, same event, market and decision ID | Reconciled as the same decision, no second fill |
| Fill simulation | Cross the spread to the ask, cap at 50 percent of depth, 0.01 slippage per 100 units | One simulated entry recorded, assumptions written in the log |
| Poll 3 | API times out | Data gap logged; no fill; last valid observation kept as state |
| Later poll | active=false or closed=true, or order acceptance off | Bot 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
Sources checked
Sources checked
Sources checked
PolyZeno. Automated review with DeepSeek V4.1 Flash.