> For the complete documentation index, see [llms.txt](https://docs.ojolabs.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ojolabs.xyz/markets/api/python-sdk/websocket.md).

# Websocket

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:

{% code title="connect\_example.py" %}

```python
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())
```

{% endcode %}

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

### Constructor Parameters

| Parameter             | Type    | Required | Description                                         |
| --------------------- | ------- | -------- | --------------------------------------------------- |
| `host`                | `str`   | Yes      | API host URL (HTTP or WebSocket scheme)             |
| `reconnect`           | `bool`  | No       | Auto-reconnect on disconnect (default: `True`)      |
| `reconnect_delay`     | `float` | No       | Initial reconnect delay in seconds (default: `1.0`) |
| `max_reconnect_delay` | `float` | No       | Max 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.

{% code title="subscribe\_unsubscribe.py" %}

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

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

{% endcode %}

You can subscribe to multiple markets simultaneously:

{% code title="subscribe\_multiple.py" %}

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

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

{% endcode %}

### Convenience Aliases

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

```python
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:

{% code title="recv\_iter.py" %}

```python
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}")
```

{% endcode %}

### Single Receive

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

{% code title="recv\_single.py" %}

```python
messages = await stream.recv()

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

{% endcode %}

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.

| Type                | Class               | Property                           | Description               |
| ------------------- | ------------------- | ---------------------------------- | ------------------------- |
| `"orderbook"`       | `OrderBookUpdate`   | `.orderbook` → `OrderBookSnapshot` | Full orderbook snapshot   |
| `"trade"`           | `TradeUpdate`       | `.trade` → `Trade`                 | Trade execution           |
| `"quick_market"`    | `QuickMarketUpdate` | `.quick_market` → `QuickMarket`    | Quick market state change |
| `"order_cancelled"` | `WSMessage`         | `.data` → `dict`                   | Order cancellation        |

### Orderbook Updates

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

{% code title="orderbook\_example.py" %}

```python
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})")
```

{% endcode %}

### Trade Updates

Sent when a trade is executed in a subscribed market.

{% code title="trade\_example.py" %}

```python
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}")
```

{% endcode %}

### Quick Market Updates

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

{% code title="quick\_market\_example.py" %}

```python
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}")
```

{% endcode %}

## Connection with Retry

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

{% code title="connect\_with\_retry.py" %}

```python
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
```

{% endcode %}

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:

{% code title="closing.py" %}

```python
# Close the stream
await stream.close()

# Close the client
await ws.close()
```

{% endcode %}

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:

{% code title="complete\_bot.py" %}

```python
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())
```

{% endcode %}

## 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:

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

Unsubscribe message format:

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ojolabs.xyz/markets/api/python-sdk/websocket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
