Skip to main content

Real-time betting odds SDK — multi-bookmaker, sport-discriminated, asyncio.

Project description

realtimeodds

Real-time betting odds SDK for Python — multi-bookmaker, sport-discriminated, asyncio-first.

The SDK is a strict replica of the gateway's internal stores: same shapes, same fields, same getters and read-side methods (transposed to Pythonic snake_case). Only the mutation surface (_with_*) is intentionally underscore-prefixed.

Status: 0.3.0 — alpha. The shapes documented here are intended to remain stable through the 0.x line.

Install

pip install realtimeodds

Requires Python 3.10+.

Quickstart

import asyncio
from realtimeodds import create_client


async def main() -> None:
    client = create_client(
        url="wss://api.realtimeodds.xyz",
        api_key="your-api-key",
    )

    def on_odds(ev):
        ctx = client.odds.find_context(ev.selection_id)
        if ctx is None or ctx.sport_event.kind != "se:basketball_match":
            return
        print(
            f"[{ev.bookmaker}] {ctx.sport_event.name} · "
            f"{ctx.market.kind} · {ctx.selection.result}{ev.quote.price}"
        )

    client.on("odds:changed", on_odds)

    await client.connect()
    try:
        await asyncio.sleep(60)
    finally:
        await client.disconnect()


asyncio.run(main())

API

API Behaviour
create_client(url, api_key, reconnect=None) Construct a client.
await client.connect() Open the WebSocket. Resolves on first successful connection; raises on invalid api_key, incompatible protocol, or exhausted reconnect attempts. Concurrent calls return the same future.
await client.disconnect() Close and stop reconnecting. Idempotent. Raises any in-flight connect(). The live OddsBook is emptied immediately.
client.odds Live OddsBook — same instance every read, mutated in place as wire messages arrive.
client.snapshot() Frozen clone of the live book taken at the moment of the call. Use when you need a stable view across multiple reads.
client.get_sport_event(id) O(1) single lookup. Returns None if unknown. Convenience shortcut for client.odds.get_sport_event(id).
client.on(event, cb) / client.off(event, cb) Subscribe / unsubscribe. Synchronous callbacks.
client.connection_state ConnectionState(status, last_error). Use lifecycle events for reactive flows.

OddsBook

Read-only view of every sport event the SDK knows about, across every bookmaker. All lookups are O(1) thanks to maintained inverse indexes.

client.odds.size                              # number of sport events
client.odds.get_sport_event(sport_event_id)   # SportEvent | None
client.odds.get_market(market_id)             # Market | None
client.odds.get_selection(selection_id)       # Selection | None
client.odds.find_context(selection_id)        # OddsContext | None
list(client.odds)                             # iterate every event

find_context returns an OddsContext(sport_event, market, selection) in one O(1) lookup — recommended inside an odds:changed handler when you need more than just the price.

Events

Synchronous callbacks. Each event payload is a frozen dataclass.

Event Payload
connected None
disconnected DisconnectedEvent(will_reconnect, code, reason) — the live OddsBook is emptied before this fires.
reconnecting ReconnectingEvent(attempt, delay_ms)
error ErrorEvent(message, fatal)
sportEvent:added SportEventAddedEvent(sport_event, received_at)
sportEvent:updated SportEventUpdatedEvent(sport_event, received_at) — fires on metadata change OR on any odds change
sportEvent:removed SportEventRemovedEvent(bookmaker, sport_event_id, received_at)
odds:changed OddsChangedEvent(bookmaker, sport_event_id, market_id, selection_id, quote, received_at)
source:cleared SourceClearedEvent(bookmaker, received_at) — an entire bookmaker source went away. The SDK has already purged its events from the live book before this fires.
resync ResyncEvent(bookmaker, reason, sport_events, received_at) — atomic full-state replacement for one bookmaker. The live book has already swapped the affected slice before this fires.

Close codes 4001/4002/4003 are fatal auth close codes; fatal=True errors stop the client.

Entities

The SDK exposes the same shapes the gateway holds, transposed to Pythonic naming. All entities are frozen dataclasses with computed properties.

  • SportEvent (BasketballMatch | FootballMatch | TennisMatch): id, kind, bookmaker, sport, competition, sport_region, start_date (Python datetime), match_url, name, markets: Mapping, plus get_market(id), get_selection(id).
  • Market (6 variants discriminated by kind): id, kind, selection_kind, is_synthetic, bookmaker, market_name, sport_event_name, sport, category, is_available, is_fully_available, number_of_possible_results, selections: Mapping, plus get_selection(id), get_selection_by_result(result), get_fair_odd(result), calculate_margin(), etc.
  • Selection: id, kind, result, quote, order_book, bookmaker, is_available, price (raises if unavailable).
  • Quote: price, size, timestamp, implied_probability.
  • OrderBook: bids, asks, timestamp, best_bid, best_ask, spread, mid_price, available_size_up_to(max_price).

Sport-specific fields (home_team/away_team/competitor1/competitor2/period/handicap/scope/cut/player_name/prop_type) live on the relevant subtypes. Branch on kind (or use isinstance) to access them with type narrowing.

Sport / kind narrowing

def on_added(ev):
    se = ev.sport_event
    if se.sport == "basketball":
        # mypy / IDE narrows to BasketballMatch
        print(se.home_team, se.away_team)
    elif se.sport == "tennis":
        print(se.competitor1, se.competitor2)

    for market in se.markets.values():
        if market.kind == "market:basketball_match.handicap":
            print(market.handicap)

Multi-bookmaker behaviour

Every SportEvent carries a bookmaker property (derived from its id). The same underlying match (e.g. Lakers vs Celtics) reported by two bookmakers is two distinct entries with different id and bookmaker. Filter to one bookmaker:

ps3838_events = [
    ev for ev in client.odds
    if ev.bookmaker == "ps3838"
]

Reconnect tuning

Default policy: exponential backoff 1s → 30s, factor 2, ±30% jitter, unbounded attempts. Override per client:

from realtimeodds import create_client, ReconnectPolicy

client = create_client(
    url="wss://api.realtimeodds.xyz",
    api_key="...",
    reconnect=ReconnectPolicy(
        initial_delay_ms=500,
        max_delay_ms=10_000,
        max_attempts=20,
    ),
)


def on_error(ev):
    if ev.fatal:
        print(f"giving up: {ev.message}")


client.on("error", on_error)

Time semantics

  • received_at (on every event payload) — local clock when the SDK received the message. Authoritative for SDK-side latency analysis.
  • quote.timestamp / order_book.timestamp — observation time set by whichever party constructed the object (gateway or SDK at hydration). Approximates freshness; not the bookmaker's authoritative emit time.

Stability

This is 0.3.0. The shapes documented above are intended to remain stable through the 0.x line. Breaking changes will require a 0.x → 0.(x+1) minor bump. Pre-1.0 means we may still iterate on edge-case behaviour and undocumented internals.

See realtimeodds-spec for the wire-format JSON Schemas (used by cross-language ports for protocol-level validation).

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

realtimeodds-0.3.1.tar.gz (27.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

realtimeodds-0.3.1-py3-none-any.whl (35.9 kB view details)

Uploaded Python 3

File details

Details for the file realtimeodds-0.3.1.tar.gz.

File metadata

  • Download URL: realtimeodds-0.3.1.tar.gz
  • Upload date:
  • Size: 27.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for realtimeodds-0.3.1.tar.gz
Algorithm Hash digest
SHA256 694ac41bb269ed1a740e303c1c9a976d865149026cea68ecd1e96919e38798eb
MD5 1d38da7e46de3f6ec4d28eed642de61c
BLAKE2b-256 f40804f9ce6b052884b72331ff3bd1d1e4a8ebcd519228b0b1462de7bcbf369e

See more details on using hashes here.

File details

Details for the file realtimeodds-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: realtimeodds-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 35.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for realtimeodds-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 27b970e441901f3aadb213eeed1996f9f959eb83f8fdb62e9c1887df1f8bfade
MD5 05fdd6dc466ab4dc209004b3d987644a
BLAKE2b-256 25581e6c743b5cb25f174036d2e1096d5944737bb9e479a5b9e0efc766cfbefa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page