# 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..."}
```
