← Back to blog

Stream Live Sports Data with WebSockets in Python

2026-03-27 websocket python data live tutorial

If you are polling ESPN every 5 seconds for live scores, you are doing it the hard way. WebSockets give you push-based updates the instant something changes — no wasted requests, no missed events, lower latency. We'll use the websockets Python library.

HTTP Polling: The Baseline

Most people start here. Hit the ESPN scoreboard API on a loop:

import requests
import time

def poll_scores(sport="basketball", league="nba", interval=5):
    url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard"
    while True:
        data = requests.get(url).json()
        for event in data["events"]:
            if event["status"]["type"]["state"] == "in":
                home = event["competitions"][0]["competitors"][0]
                away = event["competitions"][0]["competitors"][1]
                print(f"{away['team']['abbreviation']} {away['score']} @ "
                      f"{home['team']['abbreviation']} {home['score']}")
        time.sleep(interval)

Three problems: 5-second latency ceiling, wasted requests when nothing changes, and rate limits across multiple sports. For trading bots, that latency window is where money is made and lost.

WebSocket Approach: Push-Based Updates

A WebSocket connection stays open. The server pushes data when something changes:

import asyncio
import websockets
import json

async def stream_scores(uri, callback):
    async for ws in websockets.connect(uri):
        try:
            async for message in ws:
                await callback(json.loads(message))
        except websockets.ConnectionClosed:
            print("Reconnecting...")
            continue

The async for ws in websockets.connect(uri) pattern handles reconnection automatically — critical for multi-hour game sessions.

Building a WebSocket Bridge

If your data source only offers HTTP (like ESPN), build a bridge: one process polls, another broadcasts changes over a local WebSocket:

import asyncio
import websockets
import json
import aiohttp

CLIENTS = set()

async def espn_poller():
    prev_states = {}
    url = "https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard"
    async with aiohttp.ClientSession() as session:
        while True:
            async with session.get(url) as resp:
                data = await resp.json()
            for event in data.get("events", []):
                game_id = event["id"]
                state = {
                    "game_id": game_id,
                    "home": event["competitions"][0]["competitors"][0]["team"]["abbreviation"],
                    "away": event["competitions"][0]["competitors"][1]["team"]["abbreviation"],
                    "home_score": int(event["competitions"][0]["competitors"][0]["score"]),
                    "away_score": int(event["competitions"][0]["competitors"][1]["score"]),
                    "period": event["status"]["period"],
                    "clock": event["status"]["displayClock"],
                }
                if state != prev_states.get(game_id):
                    prev_states[game_id] = state
                    msg = json.dumps(state)
                    await asyncio.gather(*[c.send(msg) for c in CLIENTS])
            await asyncio.sleep(3)

async def handler(ws):
    CLIENTS.add(ws)
    try:
        await ws.wait_closed()
    finally:
        CLIENTS.discard(ws)

async def main():
    async with websockets.serve(handler, "localhost", 8765):
        await espn_poller()

asyncio.run(main())

Your trading bot connects to ws://localhost:8765 and receives only changed game states — no parsing, no polling logic, no rate limit management.

The Trading Bot Client

async def trading_bot():
    async for ws in websockets.connect("ws://localhost:8765"):
        try:
            async for message in ws:
                game = json.loads(message)
                fair_prob = model.predict(extract_features(game))
                market_price = get_market_price(game["game_id"])
                edge = fair_prob - market_price
                if edge > 0.08:
                    print(f"SIGNAL: {game['away']}@{game['home']} "
                          f"fair={fair_prob:.2f} market={market_price:.2f}")
        except websockets.ConnectionClosed:
            continue

Every score change triggers immediate model evaluation. No waiting for the next poll cycle.

Latency Comparison

Approach Median Latency P95 Latency
HTTP polling (5s) 2.8s 5.0s
HTTP polling (2s) 1.2s 2.0s
WebSocket bridge (3s poll) 1.6s 3.1s

The WebSocket bridge sits in the middle — better than 5-second polling, not as fast as a native feed. For most strategies, sub-2-second latency is sufficient because prediction market orderbooks take 5-30 seconds to fully reprice after a scoring event.

Handling Connection Failures

from datetime import datetime, timedelta

async def resilient_stream(uri, callback, max_gap=timedelta(seconds=30)):
    last_msg = datetime.now()
    async for ws in websockets.connect(uri, ping_interval=20, ping_timeout=10):
        try:
            async for message in ws:
                last_msg = datetime.now()
                await callback(json.loads(message))
        except websockets.ConnectionClosed:
            if datetime.now() - last_msg > max_gap:
                print("Data gap detected — backfilling via HTTP")
                await backfill_current_state(callback)
            continue

If your WebSocket was down for 30+ seconds during a live game, you may have missed scoring events. The HTTP backfill catches your model up so it does not trade on stale data.

When to Use Which

HTTP polling works for backtesting, historical scraping, and low-frequency monitoring. WebSocket streaming is better when you trade on live prediction markets, track many concurrent games, or need sub-second reaction times.

For production trading bots, the bridge architecture gives you the best balance: simple ESPN polling on the backend, instant delivery to your bot on the frontend.


Part of the ZenHodl blog. We write about sports analytics, prediction markets, and building trading bots with Python.

Get ZenHodl Weekly

One weekly email with live results, one model insight, and product updates.

Tuesday mornings. No spam.

Want to build this yourself?

The ZenHodl course teaches you to build a complete prediction market bot in 6 notebooks.

Join the community

Discuss strategies, share results, get help.

Join Discord