Websocket (Python SDK)

The TurbineWSClient provides async WebSocket streaming for real-time orderbook, trade, and quick market updates.

Connection

Create a TurbineWSClient and connect using the async context manager:

import asyncio
from turbine_client.ws import TurbineWSClient

async def main():
    ws = TurbineWSClient(host="https://api.turbinefi.com")

    async with ws.connect() as stream:
        await stream.subscribe("0xMarketId...")

        async for msg in stream:
            print(f"Type: {msg.type}, Market: {msg.market_id}")

asyncio.run(main())

The WebSocket endpoint is wss://<host>/api/v1/stream. HTTP/HTTPS URLs are converted to WS/WSS automatically.

Constructor Parameters

ParameterTypeRequiredDescription
hoststrYesAPI host URL (HTTP or WebSocket scheme)
reconnectboolNoAuto-reconnect on disconnect (default: True)
reconnect_delayfloatNoInitial reconnect delay in seconds (default: 1.0)
max_reconnect_delayfloatNoMax reconnect delay in seconds (default: 60.0)

Subscriptions

Subscribe to a market to receive all updates (orderbook, trades, cancellations) for that market. Subscriptions are market-level — you subscribe to a market ID, not to individual channels.

# Subscribe
await stream.subscribe("0xMarketId...")

# Unsubscribe
await stream.unsubscribe("0xMarketId...")

You can subscribe to multiple markets simultaneously:

market_ids = ["0xMarket1...", "0xMarket2...", "0xMarket3..."]

for market_id in market_ids:
    await stream.subscribe(market_id)

Convenience Aliases

These are aliases for subscribe() and exist for backwards compatibility:

await stream.subscribe_orderbook(market_id)  # same as subscribe()
await stream.subscribe_trades(market_id)      # same as subscribe()

Receiving Messages

Async Iteration

The primary way to receive messages. Iterates indefinitely until the connection closes:

async with ws.connect() as stream:
    await stream.subscribe("0x...")

    async for msg in stream:
        if msg.type == "orderbook":
            ob = msg.orderbook
            if ob and ob.bids:
                print(f"Best bid: {ob.bids[0].price} (${ob.bids[0].price / 1e6:.4f})")

        elif msg.type == "trade":
            trade = msg.trade
            if trade:
                print(f"Trade: {trade.size / 1e6:.2f} @ {trade.price} (${trade.price / 1e6:.4f})")

        elif msg.type == "order_cancelled":
            print(f"Order cancelled in market {msg.market_id}")

        elif msg.type == "quick_market":
            qm = msg.quick_market
            if qm:
                print(f"Quick market update: {qm.market_id}")

Single Receive

Receive one frame of messages (the server may batch multiple messages per frame):

messages = await stream.recv()

for msg in messages:
    print(f"{msg.type}: {msg.data}")

Returns: list[WSMessage] — one or more parsed messages from a single WebSocket frame.

Message Types

All messages are instances of WSMessage or its subclasses. The type field determines the message kind.

typeClassPropertyDescription
"orderbook"OrderBookUpdate.orderbook → OrderBookSnapshotFull orderbook snapshot
"trade"TradeUpdate.trade → TradeTrade execution
"quick_market"QuickMarketUpdate.quick_market → QuickMarketQuick market state change
"order_cancelled"WSMessage.data → dictOrder cancellation

Orderbook Updates

Sent whenever the orderbook changes for a subscribed market. Contains a full snapshot (not a diff).

if msg.type == "orderbook":
    ob = msg.orderbook  # OrderBookSnapshot

    # Bids sorted best (highest) first
    for level in ob.bids[:3]:
        print(f"  BID {level.price} (${level.price / 1e6:.4f}) x {level.size / 1e6:.2f}")

    # Asks sorted best (lowest) first
    for level in ob.asks[:3]:
        print(f"  ASK {level.price} (${level.price / 1e6:.4f}) x {level.size / 1e6:.2f}")

    # Mid price
    if ob.bids and ob.asks:
        mid = (ob.bids[0].price + ob.asks[0].price) / 2
        print(f"  Mid: {mid:.0f} (${mid / 1e6:.4f})")

Trade Updates

Sent when a trade is executed in a subscribed market.

if msg.type == "trade":
    trade = msg.trade  # Trade

    outcome = "YES" if trade.outcome == 0 else "NO"
    print(f"Trade: {trade.size / 1e6:.2f} {outcome} @ {trade.price} (${trade.price / 1e6:.4f})")
    print(f"  Buyer: {trade.buyer}")
    print(f"  Seller: {trade.seller}")
    print(f"  TX: {trade.tx_hash}")

Quick Market Updates

Sent when a quick market's state changes (new market, resolution, etc.).

if msg.type == "quick_market":
    qm = msg.quick_market  # QuickMarket

    print(f"Quick market: {qm.asset} ({qm.market_id})")
    print(f"  Strike: ${qm.start_price / 1e8:,.2f}")
    print(f"  Resolved: {qm.resolved}")

Connection with Retry

For long-running bots, use connect_with_retry() to handle disconnections with exponential backoff:

ws = TurbineWSClient(
    host="https://api.turbinefi.com",
    reconnect=True,
    reconnect_delay=1.0,
    max_reconnect_delay=60.0,
)

stream = await ws.connect_with_retry()

await stream.subscribe("0x...")

async for msg in stream:
    # Process messages
    pass

If the connection fails, it retries with exponential backoff (1s, 2s, 4s, ..., up to 60s). The reconnect=False setting disables retries and raises WebSocketError immediately on failure.

Closing

Close the stream or client explicitly when done:

# Close the stream
await stream.close()

# Close the client
await ws.close()

The context manager (async with ws.connect()) handles cleanup automatically.

Complete Example

A bot that monitors a BTC quick market and logs orderbook + trade activity:

import asyncio
from turbine_client import TurbineClient
from turbine_client.ws import TurbineWSClient

async def main():
    # Get the active BTC quick market
    client = TurbineClient(host="https://api.turbinefi.com", chain_id=137)
    qm = client.get_quick_market("BTC")
    market_id = qm.market_id

    print(f"Monitoring: {market_id}")
    print(f"Strike: ${qm.start_price / 1e8:,.2f}")

    # Connect to WebSocket
    ws = TurbineWSClient(host="https://api.turbinefi.com")

    async with ws.connect() as stream:
        await stream.subscribe(market_id)

        async for msg in stream:
            if msg.type == "orderbook":
                ob = msg.orderbook
                if ob and ob.bids and ob.asks:
                    spread = ob.asks[0].price - ob.bids[0].price
                    print(
                        f"Book: {ob.bids[0].price} (${ob.bids[0].price / 1e6:.4f}) / "
                        f"{ob.asks[0].price} (${ob.asks[0].price / 1e6:.4f}) "
                        f"spread={spread}"
                    )

            elif msg.type == "trade":
                trade = msg.trade
                if trade:
                    outcome = "YES" if trade.outcome == 0 else "NO"
                    print(
                        f"Trade: {trade.size / 1e6:.2f} {outcome} "
                        f"@ {trade.price} (${trade.price / 1e6:.4f})"
                    )

            elif msg.type == "quick_market":
                qm_update = msg.quick_market
                if qm_update and qm_update.market_id != market_id:
                    # New market — switch subscription
                    await stream.unsubscribe(market_id)
                    market_id = qm_update.market_id
                    await stream.subscribe(market_id)
                    print(f"Switched to new market: {market_id}")

asyncio.run(main())

Wire Format

The WebSocket server sends newline-delimited JSON. Each frame may contain one or more JSON objects separated by \n. The client parses these automatically into individual WSMessage objects.

Subscribe message format:

{"type": "subscribe", "marketId": "0x..."}

Unsubscribe message format:

{"type": "unsubscribe", "marketId": "0x..."}