Real-Time Odds Calculation: Stream Processing for Betting and Prediction Markets (2026)
Real-time odds calculation is the process of continuously recomputing betting probabilities as new information arrives — trades, game events, market data, or external signals. In 2026, with prediction markets processing $6 billion in weekly volume and sports betting exceeding $300 billion globally, the infrastructure behind live odds is one of the most demanding real-time data workloads. A streaming database like RisingWave can implement odds engines, line movement tracking, and cross-market analytics using pure SQL.
This guide covers how real-time odds calculation works, the data patterns behind it, and how to build it with stream processing.
How Odds Work
Odds represent the implied probability of an outcome and determine the payout ratio. There are three common formats:
| Format | Example | Implied Probability | Payout on $100 bet |
| Decimal | 2.50 | 1/2.50 = 40% | $250 (profit: $150) |
| American | +150 | 100/(100+150) = 40% | $250 (profit: $150) |
| Prediction Market | $0.40 | 40% | $100 (profit: $60) |
In prediction markets, the price IS the probability. A YES contract at $0.40 = 40% implied probability. In sports betting, odds are set by bookmakers and adjusted based on betting volume and external information.
Real-Time Odds Calculation Patterns
Pattern 1: Order-Book Based Odds (Prediction Markets)
In CLOB-based prediction markets (Polymarket, Kalshi), odds are determined by the best available prices in the order book:
-- Real-time odds from order book
CREATE MATERIALIZED VIEW live_odds AS
SELECT
market_id,
-- Best available prices
MAX(price) FILTER (WHERE side = 'BID' AND side_outcome = 'YES') as best_yes_bid,
MIN(price) FILTER (WHERE side = 'ASK' AND side_outcome = 'YES') as best_yes_ask,
-- Midpoint = best estimate of true probability
(MAX(price) FILTER (WHERE side = 'BID' AND side_outcome = 'YES') +
MIN(price) FILTER (WHERE side = 'ASK' AND side_outcome = 'YES')) / 2.0 as midpoint_probability,
-- Spread = market uncertainty/liquidity indicator
MIN(price) FILTER (WHERE side = 'ASK' AND side_outcome = 'YES') -
MAX(price) FILTER (WHERE side = 'BID' AND side_outcome = 'YES') as spread,
-- Depth at best prices
SUM(quantity) FILTER (WHERE side = 'BID' AND price >= best_bid_threshold) as bid_depth,
SUM(quantity) FILTER (WHERE side = 'ASK' AND price <= best_ask_threshold) as ask_depth
FROM order_book_events
GROUP BY market_id;
Pattern 2: Volume-Weighted Odds
When odds need to reflect where actual money is flowing, not just the last trade price:
-- VWAP (Volume Weighted Average Price) as probability estimate
CREATE MATERIALIZED VIEW vwap_odds AS
SELECT
market_id,
-- Short-term VWAP (recent sentiment)
SUM(price * quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '5 minutes')
/ NULLIF(SUM(quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '5 minutes'), 0)
as vwap_5min,
-- Medium-term VWAP (trend)
SUM(price * quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '1 hour')
/ NULLIF(SUM(quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '1 hour'), 0)
as vwap_1h,
-- Long-term VWAP (baseline)
SUM(price * quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '24 hours')
/ NULLIF(SUM(quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '24 hours'), 0)
as vwap_24h,
-- Volume
SUM(quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '5 minutes') as volume_5min,
SUM(quantity) FILTER (WHERE trade_time > NOW() - INTERVAL '1 hour') as volume_1h
FROM trades
GROUP BY market_id;
Pattern 3: Line Movement Tracking
Detecting when odds shift significantly — critical for arbitrage, sharp bettor identification, and market manipulation detection:
-- Track odds movement over time windows
CREATE MATERIALIZED VIEW line_movement AS
SELECT
market_id,
-- Current vs historical prices
last_value(price ORDER BY trade_time) as current_price,
-- Price 5 minutes ago
last_value(price ORDER BY trade_time)
FILTER (WHERE trade_time <= NOW() - INTERVAL '5 minutes') as price_5min_ago,
-- Price 1 hour ago
last_value(price ORDER BY trade_time)
FILTER (WHERE trade_time <= NOW() - INTERVAL '1 hour') as price_1h_ago,
-- Movement
last_value(price ORDER BY trade_time) -
last_value(price ORDER BY trade_time)
FILTER (WHERE trade_time <= NOW() - INTERVAL '5 minutes') as movement_5min,
last_value(price ORDER BY trade_time) -
last_value(price ORDER BY trade_time)
FILTER (WHERE trade_time <= NOW() - INTERVAL '1 hour') as movement_1h,
-- Volatility (standard deviation of recent prices)
STDDEV(price) FILTER (WHERE trade_time > NOW() - INTERVAL '1 hour') as volatility_1h
FROM trades
WHERE side = 'YES'
GROUP BY market_id;
Pattern 4: Cross-Market Odds Comparison
Compare the same event across multiple platforms to detect arbitrage and consensus:
-- Aggregate odds from multiple sources
CREATE MATERIALIZED VIEW cross_market_odds AS
SELECT
event_id,
event_name,
-- Odds from each platform
MAX(price) FILTER (WHERE platform = 'polymarket') as polymarket_odds,
MAX(price) FILTER (WHERE platform = 'kalshi') as kalshi_odds,
MAX(price) FILTER (WHERE platform = 'betfair') as betfair_odds,
-- Consensus (average)
AVG(price) as consensus_probability,
-- Disagreement (max spread between platforms)
MAX(price) - MIN(price) as max_spread,
-- Arbitrage opportunity flag
CASE
WHEN MAX(price) - MIN(price) > 0.05 THEN true
ELSE false
END as arbitrage_flag
FROM aggregated_odds
GROUP BY event_id, event_name;
In-Play Sports Odds
The most demanding odds calculation scenario is live, in-play sports betting. Odds must update within milliseconds of every game event — a goal, a turnover, a penalty.
Live Game State Tracking
-- Track live game state from event feed
CREATE MATERIALIZED VIEW live_game_state AS
SELECT
game_id,
home_team,
away_team,
-- Current score
SUM(CASE WHEN event_type = 'goal' AND team = home_team THEN 1 ELSE 0 END) as home_score,
SUM(CASE WHEN event_type = 'goal' AND team = away_team THEN 1 ELSE 0 END) as away_score,
-- Game clock
MAX(game_clock) as current_time,
-- Recent events
last_value(event_type ORDER BY event_time) as last_event,
last_value(team ORDER BY event_time) as last_event_team,
-- Momentum indicators
COUNT(*) FILTER (WHERE team = home_team AND event_time > NOW() - INTERVAL '5 minutes'
AND event_type IN ('shot', 'corner', 'possession')) as home_pressure_5min,
COUNT(*) FILTER (WHERE team = away_team AND event_time > NOW() - INTERVAL '5 minutes'
AND event_type IN ('shot', 'corner', 'possession')) as away_pressure_5min
FROM game_events
GROUP BY game_id, home_team, away_team;
Real-Time Win Probability
-- Combine game state with historical model
CREATE MATERIALIZED VIEW win_probability AS
SELECT
gs.game_id,
gs.home_team,
gs.away_team,
gs.home_score,
gs.away_score,
gs.current_time,
-- Look up historical win probability from pre-computed model
m.home_win_prob,
m.draw_prob,
m.away_win_prob,
-- Adjust for momentum
CASE
WHEN gs.home_pressure_5min > gs.away_pressure_5min * 2
THEN m.home_win_prob * 1.05 -- 5% boost for sustained pressure
ELSE m.home_win_prob
END as adjusted_home_win_prob
FROM live_game_state gs
JOIN win_probability_model m
ON gs.home_score = m.home_score
AND gs.away_score = m.away_score
AND gs.current_time BETWEEN m.time_range_start AND m.time_range_end;
Architecture for Real-Time Odds
External Feeds ──→ ┌──────────────────────────────────────┐
(Sports API, │ RisingWave │
Exchange data, │ │
Social signals) │ ┌─ Live Odds MV │ ──→ Trading API
│ ├─ Line Movement MV │ ──→ Dashboard
Game Events ────→ │ ├─ Cross-Market Comparison MV │ ──→ Risk Alerts
│ ├─ Win Probability MV │ ──→ Mobile App
Order Book ─────→ │ ├─ Arbitrage Detection MV │ ──→ Trader Tools
│ └─ Settlement MV │
└──────────────────────────────────────┘
PostgreSQL Protocol
Why a streaming database for odds calculation:
- Sub-100ms updates — Materialized views refresh within milliseconds of new data
- SQL-only — Odds logic expressed in standard SQL, not custom C++ or Java
- Built-in serving — APIs query odds directly via PostgreSQL protocol, no cache layer
- Multi-source joins — Combine order book, game events, and external feeds in SQL
- Consistent snapshots — All queries return a consistent point-in-time view of odds
Real-Time Odds at Scale
The scale of modern betting and prediction markets is massive:
| Metric | Value (2026) |
| Global sports betting market | $300B+ annually |
| Prediction market weekly volume | ~$6B (Kalshi + Polymarket) |
| Peak events per second (Super Bowl) | 100,000+ bets/sec |
| In-play bet share | 70%+ of all sports bets |
| Markets per platform | 10,000+ concurrent |
| Odds update frequency | Every trade / every game event |
A streaming database handles this by:
- Distributing computation across multiple compute nodes
- Storing state on S3 (no local disk bottleneck)
- Parallel materialized view maintenance across partitions
- Elastic scaling — add compute during peak events (Super Bowl, elections)
Common Odds Calculation Challenges
Challenge 1: Stale Odds
Problem: Batch-refreshed odds (every minute or every 5 minutes) create arbitrage windows where sharp bettors exploit outdated lines.
Solution: Streaming materialized views update with every trade — no stale window.
Challenge 2: Multi-Outcome Markets
Problem: Markets with 20+ outcomes (e.g., "Who will win the 2026 World Cup?") require normalized probabilities that sum to 100%.
Solution:
CREATE MATERIALIZED VIEW normalized_odds AS
SELECT
market_id,
outcome,
raw_price,
raw_price / SUM(raw_price) OVER (PARTITION BY market_id) as normalized_probability
FROM outcome_prices;
Challenge 3: Correlated Markets
Problem: Related markets (e.g., "Team A wins Game 1" and "Team A wins the series") must have consistent odds.
Solution: Streaming joins across related markets detect inconsistencies in real time:
CREATE MATERIALIZED VIEW correlation_alerts AS
SELECT *
FROM game_odds g
JOIN series_odds s ON g.team = s.team
WHERE g.win_prob > s.win_series_prob; -- Logically impossible
Frequently Asked Questions
How are prediction market odds calculated?
In CLOB-based prediction markets (Polymarket, Kalshi), odds are determined by the best available bid and ask prices in the order book. The midpoint between best bid and best ask is the market's implied probability. In AMM-based markets, a mathematical formula (LMSR) calculates the price based on the current state of the liquidity pool. Both approaches produce prices between $0 and $1, where the price directly represents the probability.
What latency is required for live odds?
For pre-game betting, odds can update every few seconds. For in-play/live betting (which represents 70%+ of all sports bets), odds must update within milliseconds of game events — a goal, a penalty, a turnover. Prediction market odds should update with every trade, ideally within sub-100ms. A streaming database achieves this by maintaining odds as continuously updated materialized views.
Can SQL handle real-time odds calculation?
Yes. Modern streaming databases like RisingWave execute SQL materialized views that update in real time as data arrives. VWAP calculations, line movement tracking, cross-market comparisons, and win probability models are all expressible in standard SQL. The streaming engine handles the continuous computation; you write the logic in SQL.
How do you detect arbitrage across betting platforms?
Arbitrage occurs when the same event has different odds on different platforms, allowing a risk-free profit. A streaming database can ingest odds from multiple platforms into a single materialized view that continuously computes the spread. When the spread exceeds the platform fees (typically 2-5%), an arbitrage opportunity exists. This detection happens in real time, within milliseconds of odds changing on any platform.
What infrastructure do prediction markets use for odds?
Traditional betting platforms use custom C++ matching engines with Redis caching and PostgreSQL for persistence. Modern prediction markets increasingly use stream processing: Kafka for trade ingestion, a streaming engine (Flink or RisingWave) for odds calculation, and a serving layer for API access. RisingWave simplifies this to a single system that handles ingestion, computation, and serving via PostgreSQL protocol.
