A calibrated win-probability model is the entry ticket to prediction-market trading. Turning it into an actual edge signal that fires real orders against a real market is a separate engineering problem with its own gotchas. This post walks through the live pipeline we run on Polymarket — score polling, fair-probability computation, edge math, freshness gating, and order placement — at the level of detail you would need to build one yourself.
The five-stage pipeline
Every signal that reaches our order placement layer has passed through five stages, in this order:
- Live game state ingestion from ESPN, official league APIs, or sport-specific feeds (lolesports, MLB Stats API, NHL API).
- Fair probability computation by feeding the game state into the per-sport model.
- Live market price ingestion via Polymarket's CLOB WebSocket (orderbook updates push, no polling).
- Edge math + freshness gating — compute the gap between fair and ask, reject anything stale or outside the edge band.
- Order placement via the Polymarket CLOB REST API with EIP-712 signatures.
Stages 1-3 run continuously as background tasks. Stage 4 runs on a fast inner loop (every 500ms). Stage 5 fires only when stage 4 emits a signal. Total end-to-end latency, from game-state event to order placement, is 800-1500ms in normal operation.
Stage 1: Game state polling cadence
The single most consequential design decision is polling cadence. Too slow and the market beats you to every move. Too fast and you burn rate limit on idle games.
Our cadence varies by sport based on event density:
- NBA, NCAAMB: every 5 seconds (high event density, fast clock)
- NHL: every 5 seconds (faster than NBA when scoring happens)
- MLB: every 10 seconds (slow clock, but pitch-by-pitch)
- Soccer: every 15 seconds (events less frequent, clock is just minutes)
- Tennis: every 10 seconds (point-by-point)
- CS2, LoL: every 10 seconds (round/objective-based)
Within each cadence, we de-duplicate state. If the game state has not changed since the last poll, we do not re-compute the fair probability. This cuts compute cost by roughly 60% on slow-moving games.
Stage 2: Fair probability computation
The fair probability comes from a per-sport pickled model — XGBoost with isotonic calibration for most sports, a Poisson process for soccer (which needs the draw outcome), and a hierarchical point-game-set model for tennis. Each model takes the current game state as input and emits a calibrated win probability for the home side (or, for soccer, three outcomes summing to 1.0).
The compute cost per prediction is small — single-digit milliseconds for XGBoost, slightly more for the soccer Poisson because of the score grid summation. With per-sport caching of the model file in memory, we can produce predictions at well over 100/second per worker.
Stage 3: Polymarket WebSocket subscription
The CLOB WebSocket pushes orderbook updates as they happen. We subscribe to the YES tokens for every active game contract and maintain an in-memory order book. The best ask updates within milliseconds of any market participant's action. This is dramatically lower latency than polling the REST endpoint, which we use only as a fallback when the WebSocket disconnects.
Polymarket's WebSocket is generally reliable but disconnects every few hours during maintenance windows. The handler reconnects automatically and replays any missed orderbook snapshots from REST to bring the in-memory state current. Without this reconnect logic, the bot silently goes blind for hours at a time and you do not notice until you check trade volumes the next morning.
Stage 4: Edge math and freshness gating
The inner loop runs every 500ms. For each watched contract, it checks:
if (now - quote.ts) > MAX_QUOTE_AGE_S: skip
if (now - game_state.ts) > MAX_GAME_AGE_S: skip
fair_prob = predict(game_state)
edge_c = fair_prob * 100 - quote.ask_c - taker_fee_c
if MIN_EDGE_C <= edge_c <= MAX_EDGE_C: emit_signal
The MIN_EDGE_C and MAX_EDGE_C thresholds are sport-specific. The minimum is calibrated to clear costs (typically 5-8 cents). The maximum is the lesson we paid for in tennis — see our edge band post. Without the maximum, the bot fires on its own model errors.
The freshness gates (MAX_QUOTE_AGE_S, MAX_GAME_AGE_S) are 5 and 8 seconds respectively. If either side is stale beyond that, the trade is skipped. Stale data is the worst kind of trade decision because the apparent edge has usually already disappeared.
Stage 5: Order placement
The order is placed via Polymarket's CLOB REST API. The request needs an EIP-712 signature from the trading wallet. We use the proxy-wallet pattern (Gnosis Safe v1.3.0 wrapper) for security — the EOA private key signs, but funds live in the proxy.
Order size is set by quarter-Kelly sizing (see our Kelly post). Order type is FOK (fill-or-kill) at the best ask price. If the order does not fill instantly, we do not chase — the price has already moved and the edge math no longer applies.
Latency from signal emission to order placement is typically 200-400ms. The EIP-712 signing is the slowest step at around 50-100ms; the network round-trip to the CLOB is 100-200ms.
What can go wrong
Every production bot has been bitten by these in some order:
- Stale game state. ESPN's feed lags major scoring plays by 5-30 seconds depending on sport. The model thinks the team is ahead when they have just gone behind. Edge looks huge. Trade is wrong. Mitigation: cap maximum edge.
- Stale orderbook. WebSocket disconnects unnoticed. The "best ask" you see is from minutes ago. Mitigation: heartbeat the connection and reset the in-memory book on reconnect.
- Position duplication. Same signal fires every poll cycle until the position fills. Without idempotent emission, you place 50 orders for the same contract in 25 seconds. Mitigation: TTL cache keyed by token + bucket.
- Settlement-time double-count. If the bot restarts mid-game without restoring open positions from the trade log, it loses track of its exposure and may re-buy a contract it already holds. Mitigation: persistent trade log with position reconstruction on startup.
What gets monitored
Three metrics matter more than P&L itself, because they are leading indicators rather than lagging ones:
- Closing Line Value per sport, per edge bucket. The full deep-dive is in our CLV post. Positive CLV means your entries beat the closing market. Negative CLV means the market knew something you did not.
- Signal latency — the time between game-state event and order placement. When this drifts above 2 seconds, you are losing edge to slower data pipelines than the competition.
- Reject rate — the fraction of computed signals that get filtered out. A sudden change in reject rate (from 90% to 60% or vice versa) usually indicates a feed problem, not a market change.
P&L is the lagging confirmation. CLV is the leading edge signal. Watch both. Trust CLV first.
The bottom line
Building a real-time edge signal pipeline is mostly engineering, not modeling. The model produces the probability. The pipeline gets the probability into a market fast enough, with enough freshness checks, and with enough idempotency to actually be tradable. Most amateur attempts fail at the engineering layer, not the modeling layer.
If you are building one, build the freshness checks and the WebSocket reconnect first. Add the order placement last. The longest-running production bots are the ones with the most paranoid plumbing.
Real-time edge signals across 11 sports
ZenHodl publishes calibrated win probabilities and edge signals for NBA, NHL, MLB, NCAAMB, NCAAWB, CFB, NFL, soccer, tennis, CS2, and LoL. Seven-day free trial.
Try ZenHodl free