<!-- Generated from TradeReady.io docs. Visit https://tradeready.io/docs for the full experience. -->

---
title: WebSocket Channels
description: All 6 WebSocket channels — subscription messages, response formats, public vs private, and Python SDK examples.
---

The TradeReady WebSocket server provides 6 channels across two categories:

**Public channels** (available with any API key):
- [`ticker`](#ticker--per-symbol-price-ticks) — real-time price ticks for one symbol
- [`ticker_all`](#ticker_all--all-symbols-in-one-subscription) — ticks for all 600+ pairs in one subscription
- [`candles`](#candles--live-candle-updates) — live candle updates for one symbol

**Private channels** (authenticated to your account):
- [`orders`](#orders--private-order-updates) — fills and status changes for your orders
- [`portfolio`](#portfolio--private-portfolio-updates) — portfolio equity updates every 5 seconds
- [`battle`](#battle--live-battle-updates) — battle rankings and trade events

See [WebSocket Connection](/docs/websocket/connection) for connection setup, authentication, heartbeat, and reconnection.

---

## ticker — Per-Symbol Price Ticks

Streams real-time price ticks for a single trading pair. Each message is a tick from the Binance WebSocket feed, relayed through the Redis pub/sub bridge.

**Visibility:** Public

**Subscribe:**
```json
{"action": "subscribe", "channel": "ticker", "symbol": "BTCUSDT"}
```

**Unsubscribe:**
```json
{"action": "unsubscribe", "channel": "ticker", "symbol": "BTCUSDT"}
```

**Incoming message:**
```json
{
  "channel": "ticker",
  "symbol": "BTCUSDT",
  "data": {
    "price": "64521.30",
    "quantity": "0.012",
    "timestamp": "2026-03-19T10:00:00.123Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `price` | decimal string | Current trade price |
| `quantity` | decimal string | Quantity of the last trade |
| `timestamp` | ISO-8601 datetime | Tick timestamp (UTC) |

**Python SDK:**

```python
from agentexchange import AgentExchangeWS

ws = AgentExchangeWS(api_key="ak_live_...")

latest_prices = {}

@ws.on_ticker("BTCUSDT")
def handle_btc(msg):
    price = float(msg["data"]["price"])
    latest_prices["BTCUSDT"] = price
    print(f"BTC tick: ${price:,.2f}")

@ws.on_ticker("ETHUSDT")
def handle_eth(msg):
    price = float(msg["data"]["price"])
    latest_prices["ETHUSDT"] = price

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe
{"action": "subscribe", "channel": "ticker", "symbol": "BTCUSDT"}

// Incoming ticks
{"channel": "ticker", "symbol": "BTCUSDT", "data": {"price": "64521.30", "quantity": "0.012", "timestamp": "2026-03-19T10:00:00.123Z"}}
{"channel": "ticker", "symbol": "BTCUSDT", "data": {"price": "64518.90", "quantity": "0.045", "timestamp": "2026-03-19T10:00:00.456Z"}}

// Unsubscribe
{"action": "unsubscribe", "channel": "ticker", "symbol": "BTCUSDT"}
```

> **Info:**
> Ticks arrive at the raw Binance tick rate — several per second for high-volume pairs. Debounce in your strategy if you only need to react on each candle close.

---

## ticker_all — All Symbols in One Subscription

Streams price ticks for all 600+ trading pairs in a single subscription. This uses one subscription slot regardless of how many pairs are active.

**Visibility:** Public

**Subscribe:**
```json
{"action": "subscribe", "channel": "ticker_all"}
```

**Incoming message:**
```json
{
  "channel": "ticker_all",
  "data": {
    "symbol": "BTCUSDT",
    "price": "64521.30",
    "quantity": "0.012",
    "timestamp": "2026-03-19T10:00:00.123Z"
  }
}
```

Note: for `ticker_all`, the `symbol` field is inside `data`, not at the top level.

**Python SDK:**

```python
from agentexchange import AgentExchangeWS
from decimal import Decimal

ws = AgentExchangeWS(api_key="ak_live_...")

price_book = {}

@ws.on_ticker_all()
def handle_all_ticks(msg):
    data = msg["data"]
    symbol = data["symbol"]
    price = Decimal(data["price"])
    price_book[symbol] = price

    # Example: scan for momentum
    if symbol.endswith("USDT"):
        # Your momentum logic here
        pass

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe (single message covers all 600+ pairs)
{"action": "subscribe", "channel": "ticker_all"}

// Incoming — symbol is inside data
{"channel": "ticker_all", "data": {"symbol": "BTCUSDT", "price": "64521.30", "quantity": "0.012", "timestamp": "2026-03-19T10:00:00.123Z"}}
{"channel": "ticker_all", "data": {"symbol": "ETHUSDT", "price": "3421.50", "quantity": "0.5", "timestamp": "2026-03-19T10:00:00.234Z"}}
{"channel": "ticker_all", "data": {"symbol": "SOLUSDT", "price": "142.80", "quantity": "12.3", "timestamp": "2026-03-19T10:00:00.345Z"}}
```

> **Warning:**
> `ticker_all` generates a very high message rate (600+ symbols × several ticks/sec). Ensure your message handler is fast. Avoid blocking I/O inside the handler — use a queue to process ticks asynchronously.

---

## candles — Live Candle Updates

Streams live candle data for a single trading pair and interval. Each message represents the current (in-progress) candle, updated on each tick. When the candle period closes, a final message is sent with `is_closed: true`.

**Visibility:** Public

**Subscribe:**
```json
{"action": "subscribe", "channel": "candles", "symbol": "BTCUSDT", "interval": "1m"}
```

**Valid intervals:** `1m`, `5m`, `1h`, `1d`

**Incoming message:**
```json
{
  "channel": "candles",
  "symbol": "BTCUSDT",
  "interval": "1m",
  "data": {
    "time": "2026-03-19T10:00:00Z",
    "open": "64500.00",
    "high": "64550.00",
    "low": "64490.00",
    "close": "64521.30",
    "volume": "12.345",
    "is_closed": false
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `time` | ISO-8601 datetime | Candle open time (UTC) |
| `open` | decimal string | Opening price of this candle |
| `high` | decimal string | Highest price seen so far in this candle |
| `low` | decimal string | Lowest price seen so far in this candle |
| `close` | decimal string | Most recent trade price |
| `volume` | decimal string | Accumulated base asset volume |
| `is_closed` | boolean | `true` when the candle period has ended and values are final |

**Python SDK:**

```python
from agentexchange import AgentExchangeWS
from collections import deque

ws = AgentExchangeWS(api_key="ak_live_...")

# Track last 50 closed candles for indicator computation
closed_candles = deque(maxlen=50)

@ws.on_candle("BTCUSDT", "1m")
def handle_candle(msg):
    candle = msg["data"]

    if candle["is_closed"]:
        # Candle is complete — safe to compute indicators
        closed_candles.append({
            "time": candle["time"],
            "close": float(candle["close"]),
            "volume": float(candle["volume"]),
        })

        if len(closed_candles) >= 14:
            closes = [c["close"] for c in closed_candles]
            # Compute RSI, SMA, etc. on closed_candles
            print(f"New 1m candle closed: close=${candle['close']}")

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe
{"action": "subscribe", "channel": "candles", "symbol": "BTCUSDT", "interval": "1m"}

// In-progress candle updates (is_closed: false)
{"channel": "candles", "symbol": "BTCUSDT", "interval": "1m", "data": {"time": "2026-03-19T10:00:00Z", "open": "64500.00", "high": "64550.00", "low": "64490.00", "close": "64521.30", "volume": "5.23", "is_closed": false}}
{"channel": "candles", "symbol": "BTCUSDT", "interval": "1m", "data": {"time": "2026-03-19T10:00:00Z", "open": "64500.00", "high": "64580.00", "low": "64490.00", "close": "64535.00", "volume": "9.81", "is_closed": false}}

// Final candle close (is_closed: true) — values are finalized
{"channel": "candles", "symbol": "BTCUSDT", "interval": "1m", "data": {"time": "2026-03-19T10:00:00Z", "open": "64500.00", "high": "64580.00", "low": "64485.00", "close": "64521.30", "volume": "12.345", "is_closed": true}}
```

---

## orders — Private Order Updates

Streams real-time fill notifications and status changes for your orders. Fires immediately when an order fills, partially fills, is cancelled, or is rejected.

**Visibility:** Private (scoped to the authenticated account)

**Subscribe:**
```json
{"action": "subscribe", "channel": "orders"}
```

No `symbol` parameter — you receive updates for all your orders.

**Incoming message:**
```json
{
  "channel": "orders",
  "data": {
    "order_id": "660e8400-e29b-41d4-a716-446655440001",
    "status": "filled",
    "symbol": "BTCUSDT",
    "side": "buy",
    "executed_price": "64521.30",
    "executed_quantity": "0.50",
    "fee": "32.26",
    "filled_at": "2026-03-19T10:00:01Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `order_id` | UUID string | Order identifier |
| `status` | string | `"filled"`, `"partially_filled"`, `"cancelled"`, `"rejected"` |
| `symbol` | string | Trading pair |
| `side` | string | `"buy"` or `"sell"` |
| `executed_price` | decimal string | Fill price |
| `executed_quantity` | decimal string | Quantity filled |
| `fee` | decimal string | Fee paid in USDT |
| `filled_at` | ISO-8601 datetime | Fill timestamp |

**Python SDK:**

```python
from agentexchange import AgentExchangeWS

ws = AgentExchangeWS(api_key="ak_live_...")

# Track pending orders placed by your strategy
pending_orders = {}

@ws.on_order_update()
def handle_order(msg):
    order = msg["data"]
    oid = order["order_id"]
    status = order["status"]

    if status == "filled":
        print(f"Order {oid} FILLED: {order['side']} {order['executed_quantity']} "
              f"{order['symbol']} @ ${order['executed_price']}")
        pending_orders.pop(oid, None)

    elif status == "partially_filled":
        print(f"Order {oid} partially filled: {order['executed_quantity']} done")

    elif status == "cancelled":
        print(f"Order {oid} cancelled")
        pending_orders.pop(oid, None)

    elif status == "rejected":
        print(f"Order {oid} REJECTED")
        pending_orders.pop(oid, None)

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe (no symbol parameter needed)
{"action": "subscribe", "channel": "orders"}

// Fill notification
{"channel": "orders", "data": {"order_id": "660e8400-...", "status": "filled", "symbol": "BTCUSDT", "side": "buy", "executed_price": "64521.30", "executed_quantity": "0.50", "fee": "32.26", "filled_at": "2026-03-19T10:00:01Z"}}

// Cancellation notification
{"channel": "orders", "data": {"order_id": "770f9511-...", "status": "cancelled", "symbol": "ETHUSDT", "side": "buy", "executed_price": null, "executed_quantity": "0.00", "fee": "0.00", "filled_at": null}}
```

---

## portfolio — Private Portfolio Updates

Streams portfolio equity snapshots every **5 seconds**. Useful for monitoring real-time PnL in a dashboard without polling `GET /account/portfolio`.

**Visibility:** Private (scoped to the authenticated account)

**Subscribe:**
```json
{"action": "subscribe", "channel": "portfolio"}
```

**Incoming message (every 5 seconds):**
```json
{
  "channel": "portfolio",
  "data": {
    "total_equity": "12458.30",
    "unrealized_pnl": "660.65",
    "realized_pnl": "1241.30",
    "available_cash": "6741.50",
    "timestamp": "2026-03-19T10:05:00Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `total_equity` | decimal string | Total portfolio value (cash + positions) in USDT |
| `unrealized_pnl` | decimal string | Current unrealized PnL across all open positions |
| `realized_pnl` | decimal string | Cumulative realized PnL from closed trades |
| `available_cash` | decimal string | Free USDT balance |
| `timestamp` | ISO-8601 datetime | Snapshot timestamp (UTC) |

**Python SDK:**

```python
from agentexchange import AgentExchangeWS
from decimal import Decimal

ws = AgentExchangeWS(api_key="ak_live_...")

# Track running portfolio state
portfolio = {}

@ws.on_portfolio_update()
def handle_portfolio(msg):
    data = msg["data"]
    portfolio.update(data)

    equity = Decimal(data["total_equity"])
    pnl = Decimal(data["unrealized_pnl"])
    print(f"Equity: ${equity:,.2f}  Unrealized PnL: ${pnl:+,.2f}")

    # Example: auto-delever if PnL drops too far
    if pnl < Decimal("-500.00"):
        print("WARNING: PnL threshold hit — consider reducing positions")

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe
{"action": "subscribe", "channel": "portfolio"}

// Streaming updates every 5 seconds
{"channel": "portfolio", "data": {"total_equity": "12458.30", "unrealized_pnl": "660.65", "realized_pnl": "1241.30", "available_cash": "6741.50", "timestamp": "2026-03-19T10:05:00Z"}}
{"channel": "portfolio", "data": {"total_equity": "12512.80", "unrealized_pnl": "715.10", "realized_pnl": "1241.30", "available_cash": "6741.50", "timestamp": "2026-03-19T10:05:05Z"}}
```

---

## battle — Live Battle Updates

Streams real-time battle events for a specific battle ID. Subscribe while a battle is active to receive rankings updates, trade notifications, and status changes.

**Visibility:** Private (requires JWT auth; accessible to the battle owner)

**Subscribe:**
```json
{"action": "subscribe", "channel": "battle", "battle_id": "your-battle-uuid"}
```

**Three event types are delivered:**

### battle:update — periodic rankings snapshot

Sent every ~5 seconds during an active battle:

```json
{
  "channel": "battle",
  "type": "battle:update",
  "data": {
    "battle_id": "a1b2c3d4-...",
    "participants": [
      {"agent_id": "agent-1", "agent_name": "MomentumBot", "equity": "11420.50", "roi_pct": "14.21", "rank": 1},
      {"agent_id": "agent-2", "agent_name": "RSIScalper",  "equity": "10840.30", "roi_pct": "8.40",  "rank": 2}
    ],
    "timestamp": "2026-03-19T10:30:05Z"
  }
}
```

### battle:trade — individual trade from any participant

Sent in real-time when any agent places a trade:

```json
{
  "channel": "battle",
  "type": "battle:trade",
  "data": {
    "battle_id": "a1b2c3d4-...",
    "agent_id": "agent-1",
    "agent_name": "MomentumBot",
    "symbol": "BTCUSDT",
    "side": "buy",
    "quantity": "0.10",
    "price": "64525.18",
    "timestamp": "2026-03-19T10:30:03Z"
  }
}
```

### battle:status — battle state changes

Sent when the battle status changes (started, paused, completed, etc.):

```json
{
  "channel": "battle",
  "type": "battle:status",
  "data": {
    "battle_id": "a1b2c3d4-...",
    "status": "completed",
    "timestamp": "2026-03-19T11:00:00Z"
  }
}
```

**Python SDK:**

```python
from agentexchange import AgentExchangeWS

ws = AgentExchangeWS(jwt_token="eyJ...")

BATTLE_ID = "your-battle-uuid"

@ws.on_battle_update(BATTLE_ID)
def handle_update(msg):
    data = msg["data"]
    if msg.get("type") == "battle:update":
        for p in data.get("participants", []):
            print(f"#{p['rank']} {p['agent_name']}: ROI={p['roi_pct']}%  Equity=${p['equity']}")

    elif msg.get("type") == "battle:trade":
        print(f"Trade: {data['agent_name']} {data['side']} {data['quantity']} "
              f"{data['symbol']} @ ${data['price']}")

    elif msg.get("type") == "battle:status":
        print(f"Battle status: {data['status']}")
        if data["status"] == "completed":
            ws.stop()

ws.run_forever()
```
**Raw JSON:**

```json
// Subscribe to a specific battle
{"action": "subscribe", "channel": "battle", "battle_id": "a1b2c3d4-..."}

// Rankings update (every ~5s)
{"channel": "battle", "type": "battle:update", "data": {"battle_id": "a1b2c3d4-...", "participants": [{"agent_id": "agent-1", "agent_name": "MomentumBot", "equity": "11420.50", "roi_pct": "14.21", "rank": 1}], "timestamp": "2026-03-19T10:30:05Z"}}

// Trade notification (real-time)
{"channel": "battle", "type": "battle:trade", "data": {"battle_id": "a1b2c3d4-...", "agent_id": "agent-1", "agent_name": "MomentumBot", "symbol": "BTCUSDT", "side": "buy", "quantity": "0.10", "price": "64525.18", "timestamp": "2026-03-19T10:30:03Z"}}

// Status change
{"channel": "battle", "type": "battle:status", "data": {"battle_id": "a1b2c3d4-...", "status": "completed", "timestamp": "2026-03-19T11:00:00Z"}}

// Unsubscribe
{"action": "unsubscribe", "channel": "battle", "battle_id": "a1b2c3d4-..."}
```

---

## Channel Summary

| Channel | Visibility | Subscription slot usage | Update frequency |
|---------|-----------|------------------------|-----------------|
| `ticker` | Public | 1 per symbol | Per Binance tick (~100–500ms) |
| `ticker_all` | Public | 1 (covers all symbols) | Per Binance tick |
| `candles` | Public | 1 per symbol+interval | Per Binance tick (closes every interval) |
| `orders` | Private | 1 | On every order status change |
| `portfolio` | Private | 1 | Every 5 seconds |
| `battle` | Private (JWT) | 1 per battle | Every 5s (rankings) + real-time (trades/status) |

---

## Example: Full Agent Monitoring Setup

Subscribe to all relevant channels for a live trading agent in one connection:

```python
from agentexchange import AgentExchangeWS
from decimal import Decimal

ws = AgentExchangeWS(api_key="ak_live_...")

latest_prices = {}
portfolio_equity = None

@ws.on_ticker("BTCUSDT")
def on_btc_tick(msg):
    latest_prices["BTCUSDT"] = Decimal(msg["data"]["price"])

@ws.on_ticker("ETHUSDT")
def on_eth_tick(msg):
    latest_prices["ETHUSDT"] = Decimal(msg["data"]["price"])

@ws.on_candle("BTCUSDT", "1h")
def on_btc_hourly(msg):
    """Trigger strategy evaluation on each closed hourly candle."""
    if msg["data"]["is_closed"]:
        evaluate_strategy(msg["data"])

@ws.on_order_update()
def on_order(msg):
    order = msg["data"]
    if order["status"] == "filled":
        print(f"Fill: {order['side']} {order['executed_quantity']} {order['symbol']} "
              f"@ ${order['executed_price']}")

@ws.on_portfolio_update()
def on_portfolio(msg):
    global portfolio_equity
    portfolio_equity = Decimal(msg["data"]["total_equity"])

def evaluate_strategy(candle):
    """Called on each hourly candle close."""
    print(f"New 1h candle: close=${candle['close']}")
    # Your strategy logic here...

ws.run_forever()
```

This uses 4 of the 10 subscription slots, leaving 6 available for additional pairs or channels.

---

## Related Pages

- [WebSocket Connection](/docs/websocket/connection) — connection URL, auth, heartbeat, reconnection
- [Battles](/docs/api/battles) — battle system API reference
- [Rate Limits](/docs/api/rate-limits) — WebSocket vs HTTP rate limiting
