The first version of this I ever built was embarrassingly naive. I pulled a price from Polymarket and a price from Kalshi for what I thought was the same event, saw a wide gap, and got briefly excited. Then I read the two questions carefully. One resolved on a slightly different date. One counted a runoff as a separate outcome. The gap was real on the screen and completely fake in reality. So most of the work in a cross-platform spread monitor is not the plumbing. It is deciding whether two prices are even talking about the same thing, and then making them comparable before you let yourself get excited again.
Five venues is a good stress test because they disagree in almost every way a data source can disagree. Polymarket quotes in a stablecoin and settles onchain. Kalshi is a regulated exchange quoting in dollars with a real fee schedule. Manifold runs on play money, so its prices carry information but no capital. PredictIt has fees on both trades and withdrawals plus position caps. Metaculus is not a market at all, it is a forecasting platform, so its number is a community estimate rather than a price you can hit. If you treat all five as interchangeable probabilities, you will find spreads that are pure artifact.
Get the outcomes to line up first
Before any number gets normalized, you need a stable identity for the outcome itself. I keep a canonical outcome record that every venue's contract maps into, and the mapping is the part I never let an algorithm do alone. Two markets belong to the same canonical outcome only if they agree on all of the following, and it is worth being annoyingly strict here.
- The exact resolution question, including any threshold. "Above 3 percent" and "at or above 3 percent" are different contracts and I have been burned assuming otherwise.
- The resolution date or window. A market resolving end of quarter is not the same as one resolving on a specific announcement day.
- The resolution source. If one venue settles on an official release and another settles on a media call, they can diverge for legitimate reasons and never converge.
- The handling of edge cases. Runoffs, ties, cancellations, and "none of the above" branches change what the yes share actually pays.
When two contracts match on the question but differ on any of these, I still store them, I just flag them as related rather than identical. A related-but-not-identical pair can still be interesting, but it goes in a separate bucket so it never contaminates the tight same-outcome spreads I actually want to trade.
Normalize to one comparable number
Once outcomes are linked, every venue's raw quote gets pushed through the same pipeline so that what comes out the other side means the same thing everywhere. The order matters because some steps depend on earlier ones.
Start with the probability convention. Most venues quote a yes price you can read as an implied probability between zero and one, but the sign and the reference outcome are not always what you assume. Pin each quote to the same yes leg of the canonical outcome, and flip anything that is quoting the no side. Metaculus needs its own handling here because its community forecast is a distribution summary, not a tradeable price, so I carry it as context and never let it generate an executable spread on its own.
Then convert currency to a single unit. Polymarket in stablecoin and Kalshi in dollars are close enough that people skip this, but if you ever add a venue quoting in another currency, an unconverted number is a silent landmine. Convert everything to one accounting unit at ingest so no downstream code has to remember.
Next, and this is the step that changes conclusions, subtract the cost of actually getting in and out. A headline price is the mid or the last trade. What you can execute is the price after the spread you cross, plus trading fees, plus withdrawal fees if the capital is trapped afterward. On a venue with per-trade and withdrawal fees, a two-point headline gap can be gone entirely once you model both legs round trip. I compute an effective entry price and an effective exit price per venue and store both, because the round-trip cost is what turns a headline spread into an executable one.
Last, attach liquidity. A price with no size behind it is a rumor. For each quote I record the depth available within a small band of the top of book, because a five-point spread that only clears fifty dollars is not the same opportunity as a two-point spread that clears real capital. The monitor should know the difference.
Refresh cadence without lying to yourself
You do not need every venue on the same clock, and pretending you do will cost you either money on rate limits or accuracy on staleness. I run tiered polling. The liquid onchain and exchange venues get frequent refreshes, on the order of every few seconds, because their prices move and you can act on them. The slower or thinner venues get refreshed less often because a fresh poll mostly returns the same number. Every stored quote carries its own timestamp, and the monitor treats any quote past a freshness threshold as unusable rather than quietly comparing a live price against one that is minutes old. A stale quote on one leg is the single most common way a spread monitor invents an edge that was never there.
Where you can, prefer a push feed over polling. A websocket that streams book updates beats hammering a REST endpoint, both for freshness and for staying inside rate limits. Not every venue offers one, so the practical answer is usually a mix, and the data model should not care which transport a given quote arrived on as long as it carries a trustworthy timestamp.
Rank by edge you can take, not by headline gap
The whole point is the ranking, and sorting by raw price difference is the trap. The number I sort on is the executable edge, which is the gap between the effective buy on the cheaper venue and the effective sell on the richer one, after both venues' round-trip costs, capped at the size the shallower side can actually fill. A spread only earns a spot near the top if all of these hold.
- Both legs are on the same canonical outcome, not a related-but-different one.
- Both quotes are inside the freshness window right now.
- The edge survives round-trip fees on both venues.
- There is enough depth to fill a size worth the operational effort.
- Capital is not trapped by a settlement or withdrawal delay that outlives the edge.
The reason I care about that last point is that many cross-venue spreads are not simultaneous arbitrage at all. You are long on one venue and short on another until resolution, and if one side locks your capital for weeks while the other needs margin, the annualized return on that trapped capital is the real yardstick, not the headline gap. A four-point spread that ties up money until an election resolves can be a worse use of capital than a two-point spread that clears in a day.
When I fold this kind of normalization into the wider cross-venue tooling at Blockcircle, the pattern is always the same. The clean price feed is table stakes. The value is in the metadata riding alongside each number, the fee model, the depth, the timestamp, the resolution details, because that is what decides whether a gap is money or a mirage. Build the monitor so it is honest about all four, and it will spend most of its time telling you there is nothing to do, which is exactly what a good one should do.