DEV Community

FatherSon
FatherSon

Posted on

Building a Real-Time World Cup Prediction Dashboard with Polymarket APIs, Python, and Streamlit

The 2026 FIFA World Cup has turned Polymarket's tournament winner market into the largest prediction market ever, with nearly $2 billion in volume across 48 team outcomes. Prices update constantly (Spain at ~16.95¢, France ~16.05¢), but the web UI shows one market at a time. This guide shows how to build a full Python dashboard for live odds, fair probabilities, order book depth, historical charts, and a smart arbitrage scanner.

Polymarket Trading Bot

You need intermediate Python knowledge. No account, API key, or wallet required for read-only data.

The Two Key Polymarket APIs

  • Gamma API (https://gamma-api.polymarket.com): For discovery — events, markets, current prices, volumes, and token IDs.
  • CLOB API (https://clob.polymarket.com): For trading details — full order books, historical prices, and executable fills.

Token IDs link them: Gamma provides clobTokenIds; feed those to CLOB endpoints. Prices are in [0, 1] and directly equal implied probabilities.

Project Setup

mkdir polymarket-worldcup-dashboard && cd polymarket-worldcup-dashboard
pip install requests pandas plotly streamlit websockets
Enter fullscreen mode Exit fullscreen mode

Step 1: Fetch All 48 Markets (Gamma API)

Hardcode the World Cup event ID (30615) and pull markets:

import requests
import json

def get_worldcup_markets():
    url = "https://gamma-api.polymarket.com/markets"
    params = {"event_id": 30615, "active": True, "limit": 100}
    resp = requests.get(url, params=params).json()

    markets = []
    for m in resp.get("data", []):
        try:
            prices = json.loads(m["outcomePrices"])
            token_ids = json.loads(m["clobTokenIds"])
            markets.append({
                "team": m["groupItemTitle"],  # Clean name like "Spain"
                "price": float(prices[0]),    # YES price
                "volume": m["volume"],
                "token_id": token_ids[0]      # YES token
            })
        except:
            continue
    return sorted(markets, key=lambda x: x["price"], reverse=True)

# Example output: top teams with real live prices
Enter fullscreen mode Exit fullscreen mode

One call returns everything in <1s.

Step 2: Normalize Probabilities (Remove Overround)

Raw prices sum >1 due to liquidity premium (~4.3% here). Normalize for fair probs:

def normalize_probabilities(markets):
    total = sum(m["price"] for m in markets)
    for m in markets:
        m["raw_price"] = m["price"]
        m["fair_prob"] = m["price"] / total
    return markets
Enter fullscreen mode Exit fullscreen mode

Display both: raw for trading, fair for modeling/comparisons.

Step 3: Order Book Depth & Executable Prices (CLOB API)

Screen price ≠ fill price for size. Compute VWAP slippage:

def get_order_book(token_id):
    url = f"https://clob.polymarket.com/book?token_id={token_id}&side=sell"  # or buy
    return requests.get(url).json()

def calculate_executable_price(book, usd_size):
    # Walk asks/bids, accumulate volume-weighted average
    remaining = usd_size
    total_cost = 0
    for level in book["asks"]:  # example for buy
        price = float(level["price"])
        size = float(level["size"]) * price  # USD depth
        if remaining <= size:
            total_cost += remaining * price
            break
        total_cost += size
        remaining -= size
    return total_cost / usd_size if total_cost else None
Enter fullscreen mode Exit fullscreen mode

For Spain, $100k often fills near top-of-book with minimal slippage; $250k jumps significantly. Add this to your dashboard for real trading insight.

Step 4: Historical Probability Charts (CLOB)

Pull time series and visualize rotations with Plotly:

def get_price_history(token_id, interval="1m", fidelity=360):
    url = f"https://clob.polymarket.com/prices-history?token_id={token_id}&interval={interval}&fidelity={fidelity}"
    data = requests.get(url).json()
    # Convert to pandas DF and plot
    import plotly.express as px
    # fig = px.line(...) with % scaling and unified hover
Enter fullscreen mode Exit fullscreen mode

Overlay top contenders to spot shifts (e.g., Spain overtaking France).

Step 5: Arbitrage Scanner (With Depth Reality Check)

For mutually exclusive outcomes:

  • Long arb: Sum best asks < 1.00 → guaranteed profit.
  • Short arb: Sum best bids > 1.00 → collect premium.
def check_arb(markets):
    best_asks = sum(m["best_ask"] for m in markets)  # from books
    best_bids = sum(m["best_bid"] for m in markets)
    return {"long_arb": best_asks < 1.0, "short_arb": best_bids > 1.0, "profit": best_bids - 1.0}
Enter fullscreen mode Exit fullscreen mode

Critical: Always validate with executable depth. Many "arbs" vanish on thin longshots (no bids at all for teams like Curaçao). Show capacity per leg.

Step 6: Real-Time WebSocket Feed

For sub-second updates (no polling):

import websocket  # or websockets lib

ws_url = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
# Subscribe with {"assets_ids": [token_ids...]}
# Handle 'book' snapshot + 'price_change' patches
Enter fullscreen mode Exit fullscreen mode

Re-run scanner on every patch.

Step 7: Streamlit Dashboard

Wrap in app.py:

import streamlit as st
@st.cache_data(ttl=30)
def load_data(): ...

st.title("Polymarket World Cup 2026 Dashboard")
# Table with prices + fair probs
# Multi-select for charts
# Arb status with warnings
# Book depth viewer
Enter fullscreen mode Exit fullscreen mode

Refresh every 30s; swap to WebSocket + session_state for live.

Extensions

  • Cross-compare with Kalshi.
  • Telegram alerts on big moves or depth drops.
  • Add group/fixture markets (same code, different event IDs).

This dashboard gives superior visibility: probabilities, depth, history, and honest signals in one place — all from public endpoints in a few hundred lines of Python.

The $2B market is a goldmine of crowd wisdom. A solid dashboard beats the official UI for serious analysis or trading prep. Tournament runs through July 2026 — plenty of time to build and iterate.

If you have more questions, please feel free to contact me at any time: https://t.me/FatherSon97

Top comments (0)