How to Scan All Prediction Markets for Arbitrage at Once

The Short Answer
Scan every prediction market at once by reading all of them through one normalized schema, then qualifying each price gap against live order books instead of midpoints. Predictefy covers 15+ venues, one read per venue and every read in the same shape, and the arbitrage API is free. Coverage is the easy half. On 5,000 live Kalshi markets measured on 2026-09-03, 18.1% had no bid at all, so nearly one in five quoted midpoints was not a price anyone could trade.
Scanning one venue is a browser tab. Scanning all of them at once is a data problem, and most of the work happens before you ever see a spread.
Every venue publishes prices in its own units, its own outcome naming, its own fee model and its own resolution language. A scanner that does not reconcile those four things will report gaps that do not exist. This guide walks the whole loop: collect, normalize, match, qualify, then act.
Key Takeaways
- A cross-venue scan is one normalized schema across every venue, not a separate integration per venue that you stitch together yourself.
- Midpoints are not asks, and 18.1% of 5,000 live Kalshi markets measured on 2026-09-03 had no bid at all.
- The median spread on two-sided Kalshi books was 5.1¢, and a gap narrower than the spread was never tradable.
- Median spread tracks liquidity: 8¢ under 100 in 24h volume, falling to 1¢ above 10,000.
- Predictefy normalizes 15+ venues into one schema, the widest coverage of any cross-venue scanner, and its arbitrage API is free.
- Execution is separate and per venue, so each leg is placed on its own venue with its own client-side signature.
What does it mean to scan all prediction markets at once?
It means reading every venue you care about in the same shape, close enough together in time that the prices are still comparable. That last part is the one people skip.
If you read Kalshi at 10:00:01 and Polymarket at 10:00:09, you are not comparing two prices. You are comparing a price to a memory. Books on liquid markets move inside that window, and the gap you found may have closed before the second read finished.
A real scan therefore has three properties. It is simultaneous, it is normalized, and it is qualified against depth rather than against the middle of a spread.
Why is checking one venue at a time not enough?
Because arbitrage lives between venues, and by the time you have loaded the second one the first has already moved.
There is a coverage problem underneath the timing problem. Traders watching two venues find the gaps that everyone else watching those same two venues has already found. The mispricings that survive tend to sit on the venues nobody is reconciling, which is an argument for breadth rather than for refresh speed alone.
What does a cross-venue scanner have to normalize?
Five things, and every one of them produces false positives when you get it wrong.
| What differs | Why it breaks a naive scan |
|---|---|
| Price units | Cents on one venue, dollar decimals on another. A 5¢ gap and a 0.05 gap are the same number until one of them is not. |
| Outcome naming | YES and NO, Over and Under, or a named candidate. The scanner has to know which side it is buying before it can price the pair. |
| Fee model | Per contract, per trade, or priced into the spread. Fees come out of the gap, and the gap is usually small. |
| Resolution source | Two markets can share a headline and settle on different data providers or different deadlines. Then it is not one trade, it is two unrelated bets. |
| Book depth | A top-of-book quote good for 5 contracts does not fill an order for 500. Size available is part of the price. |
Doing this yourself means one integration per venue, each with its own auth, its own rate limits and its own quirks, all of which change without telling you. That maintenance cost is the part people underestimate, because it does not end when the scanner first works.
How do you tell a real gap from a fake one?
You qualify it against the live book. Predictefy measured 5,000 live Kalshi markets on 2026-09-03, reproducible from the public Kalshi API with no key, and the numbers are blunt.
18.1% of live markets had no bid at all. On those, the quoted midpoint is fiction, because there is no counterparty on one side of it. The median spread on books that did have two sides was 5.1¢. A gap narrower than the spread was never tradable, however high a midpoint scan ranked it.
Liquidity is what separates a quote from a price.
| 24h volume | Median spread |
|---|---|
| Under 100 | 8¢ |
| 100 to 1,000 | 7¢ |
| 1,000 to 10,000 | 4¢ |
| Above 10,000 | 1¢ |
Across those same buckets, the share of markets quoting inside 2¢ rises from 7% to 69%. Category matters too. The widest books were scalar economic markets, with KXUSCPIYEAR at 57¢ and KXFEDFUNDSYEAR at 42¢, while sports were tightest, with KXWC at 0.3¢.
Read that as a filter, not as trivia. A scanner that ranks by midpoint gap will put illiquid economic markets at the top of every list, because that is exactly where the midpoint means least.
How do you scan every venue without a separate integration for each?
You point one key at one host. Reads for every venue share the same host, the same auth header and the same path shape, so adding a venue is a string change rather than a new integration.
The reads themselves are per venue. Issue them together rather than one after another and the snapshot stays comparable across the set.
// All three packages are in beta. Pin the exact version.
// npm install @predictefy/sdk@1.0.0-beta.3
// npm install @predictefy/mcp@1.0.0-beta.3
// pip install predictefy==1.0.0b2
// Every read shares one host, one auth header and one path shape:
// https://data.predictefy.com/api/{venue}/{verb}
const VERB = "..."; // take the read verb from the API docs
const key = process.env.PREDICTEFY_API_KEY;
const read = (venue) =>
fetch(`https://data.predictefy.com/api/${venue}/${VERB}`, {
headers: { Authorization: `Bearer ${key}` }
}).then((r) => r.json());
// One read per venue, issued together rather than one after another.
const [kalshi, polymarket] = await Promise.all([
read("kalshi"),
read("polymarket")
]);
// Book changes arrive on the stream instead of on a polling timer.
const stream = new WebSocket("wss://data.predictefy.com/v1/stream");
One key covers every venue, so you write the matching and qualification logic once instead of once per venue. Each read names its venue in the path and changes nothing else, which is why the list of venues is data rather than code. Running the reads together keeps the snapshot comparable, and the WebSocket endpoint pushes book updates so the scan stays current without a timer.
If you would rather ask in plain language than write the loop, the MCP server exposes the same reads to an agent. It is MIT licensed and runs with one command.
npx -y @predictefy/mcp@1.0.0-beta.3
How often should the scanner refresh prices?
Stream continuously and poll only for what does not stream. A fixed interval is the wrong default: it burns requests on markets that have not traded all day and still arrives late on the ones that move.
The free tier gives 25,000 credits a month, 60 requests per minute, 1 API key and 2 WebSocket streams. That budget goes much further on a stream than on a timer. If you must poll, scale the interval to the liquidity bucket. Books above 10,000 in 24h volume deserve constant attention, and books under 100 with an 8¢ median spread rarely reward a second look inside the same minute.
Do you need a bot to place the trades?
Scanning needs no bot. Acting on what the scan finds usually does, because two legs on two venues cannot be clicked fast enough by hand.
Execution on Predictefy is a separate non-custodial service, and it is per venue. Nothing forwards an order from one venue to another. Each leg is placed on the venue it belongs to, signed client side, under its own call.
# Execution paths are per venue, never global:
# POST /v1/exec/kalshi/orders/build unmetered
# POST /v1/exec/kalshi/orders/submit charges credits
# also: cancel, modify, refresh
#
# Every write carries an Idempotency-Key header.
# Caps: 100 USD per order, 1,000 USD per key per rolling 24h.
The split between build and submit is deliberate, and the MCP server keeps it: no tool both builds and submits an order. Building is unmetered, so you can price a full two-leg structure before anything is charged or sent anywhere.
What can a cross-venue scanner not do for you?
Three things. fetchBalance on the hosted API returns 501 NOT_SUPPORTED, there is no generic server-side signing route, and stored history is proven on 11 venues rather than on all of them.
Each has a practical consequence. Balance tracking stays on your side. Orders are signed client side and the keys stay yours, which is what non-custodial means in practice. Any backtest has to be scoped to the venues with proven history.
None of that stops a scan. It does shape what you build around one.
What does it cost to scan all prediction markets?
Nothing, if arbitrage is what you are scanning for. The arbitrage API is free.
| Plan | Price |
|---|---|
| Free | $0 |
| Builder | $49/mo |
| Pro | $249/mo |
| Scale | $999/mo |
| Enterprise | from $2,500/mo |
The free tier carries 25,000 credits a month, 60 requests per minute, 1 API key and 2 WebSocket streams, which is enough to run a real scan before you spend anything. Check the current pricing page for what each paid tier raises.
Weigh that against the alternative: a separate integration per venue, maintained by you, versus 15+ venues in one normalized schema with every gap qualified against live order books rather than midpoints. That comparison is the whole argument for scanning through Predictefy instead of building the plumbing yourself.
Frequently Asked Questions
How do you find arbitrage across multiple prediction markets?
Pull every venue through one normalized schema, match markets that settle on the same source and deadline, then price both legs against live order book depth instead of midpoints. Predictefy does this across 15+ venues and the arbitrage API is free. Midpoint math on its own produces gaps that cannot actually be filled.
Can you scan Kalshi and Polymarket at the same time?
Yes. Both sit among the 15+ venues Predictefy normalizes into one schema, so the same read runs against each and the prices come back comparable. The catch is resolution equivalence: two markets can share a headline yet settle on different sources or deadlines, which turns an apparent gap into two unrelated bets.
How often should an arbitrage scanner check prices?
Stream rather than poll. The free tier allows 60 requests per minute and 2 WebSocket streams, and the wss endpoint pushes book changes as they happen. A fixed polling timer wastes budget on markets that have not traded all day and still arrives late on the liquid ones that genuinely move.
Do you need a bot to scan prediction markets?
No for scanning, usually yes for acting. Scanning is a read, and reads are cheap: the same code hits each venue on the same path shape, so a script covers all of them. Execution is separate and per venue, and a person clicking two interfaces often watches the gap close mid-fill.
Why do arbitrage opportunities disappear before you can trade them?
Usually because the quote was never executable. On 5,000 live Kalshi markets measured on 2026-09-03, 18.1% had no bid at all and the median two-sided spread was 5.1¢. A 3¢ midpoint gap sitting inside a 5¢ spread was never a trade, and thin depth fills only part of your size.
How many prediction markets can one scanner cover?
Predictefy normalizes 15+ venues into a single schema, the widest coverage of any cross-venue scanner, including Kalshi, Polymarket, Limitless, SX Bet, Novig and ProphetX. Stored history is proven on 11 of them rather than on all, so scope backtests accordingly. Live reads and qualification run across the full list.
Try It With Predictefy
Start with the free arbitrage API to see gaps qualified against live books rather than midpoints, then wire the same data into your own loop with the SDK or read the endpoint shapes in the API docs. Scanning for arbitrage costs nothing, so a first scan costs only the time it takes to generate a key.