import asyncio, os, requests
from centrifuge import (
Client, SubscriptionEventHandler, SubscriptionOptions,
PublicationContext, SubscribedContext,
)
API = "https://api.sx.bet" # REST — the order book is public
WS = "wss://realtime.sx.bet/connection/websocket" # WS token fetched below
# The WebSocket needs a realtime token from /user/realtime-token-v3/api-key
# (the only place an API key is used — the REST calls below are public).
async def fetch_token(ctx=None):
res = await asyncio.to_thread(
requests.get, f"{API}/user/realtime-token-v3/api-key",
headers={"x-sx-api-key": os.environ["SX_API_KEY"]},
)
res.raise_for_status()
return res.json()["token"]
# The book arrives price-aggregated in the MAKER frame: `outcomeOne` levels are
# makers backing outcome one, i.e. prices a TAKER gets on outcome TWO.
# percentageOdds = maker_implied * 10^20; size = maker stake in USDC (6dp).
def taker_levels(levels):
rows = []
for level in levels:
taker_implied = 1 - int(level["percentageOdds"]) / 1e20
if not (0 < taker_implied < 1):
continue
decimal_odds = 1 / taker_implied
maker_usdc = int(level["size"]) / 1e6
rows.append((decimal_odds, maker_usdc / (decimal_odds - 1))) # taker stake that fills it
return sorted(rows, key=lambda r: -r[0]) # best price first
def print_book(book):
for side, levels in (("one", book["outcomeTwo"]), ("two", book["outcomeOne"])):
rows = taker_levels(levels)
if not rows:
print(f" {side}: —"); continue
print(f" {side}: best {rows[0][0]:.2f} · max bet {sum(r[1] for r in rows):,.0f} USDC")
async def stream(market_hash):
held = {"version": ""}
# Every payload is the WHOLE book. Replace it, but only with a strictly
# newer `version` (compare as strings) so a stale seed never wins.
def apply(book):
if held["version"] and book["version"] <= held["version"]:
return
held["version"] = book["version"]
print_book(book)
# REST snapshot — public endpoint, no API key needed.
def snapshot():
return requests.get(f"{API}/orderbook-v3/snapshot",
params={"marketHash": market_hash}).json()["data"]
async def on_subscribed(ctx: SubscribedContext):
# recovered=True: the channel replayed the latest book, no seed needed.
if not ctx.recovered:
apply(await asyncio.to_thread(snapshot))
async def on_publication(ctx: PublicationContext):
apply(ctx.data)
client = Client(WS, get_token=fetch_token)
handler = SubscriptionEventHandler(on_subscribed=on_subscribed,
on_publication=on_publication)
sub = client.new_subscription(f"orderbook_v3:{market_hash}", handler,
SubscriptionOptions(recoverable=True))
await client.connect()
await sub.subscribe()
await asyncio.Future() # run forever
# Pick a market (public REST — no API key needed) and stream it live.
markets = requests.get(f"{API}/markets/active",
params={"onlyMainLine": "true", "pageSize": 5}
).json()["data"]["markets"]
asyncio.run(stream(markets[0]["marketHash"]))