TradeReady.io
WebSocket

WebSocket Channels

All 6 WebSocket channels — subscription messages, response formats, public vs private, and Python SDK examples.

Download .md

The TradeReady WebSocket server provides 6 channels across two categories:

Public channels (available with any API key):

  • ticker — real-time price ticks for one symbol
  • ticker_all — ticks for all 600+ pairs in one subscription
  • candles — live candle updates for one symbol

Private channels (authenticated to your account):

  • orders — fills and status changes for your orders
  • portfolio — portfolio equity updates every 5 seconds
  • battle — battle rankings and trade events

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

{"action": "subscribe", "channel": "ticker", "symbol": "BTCUSDT"}

Unsubscribe:

{"action": "unsubscribe", "channel": "ticker", "symbol": "BTCUSDT"}

Incoming message:

{
  "channel": "ticker",
  "symbol": "BTCUSDT",
  "data": {
    "price": "64521.30",
    "quantity": "0.012",
    "timestamp": "2026-03-19T10:00:00.123Z"
  }
}
FieldTypeDescription
pricedecimal stringCurrent trade price
quantitydecimal stringQuantity of the last trade
timestampISO-8601 datetimeTick timestamp (UTC)
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()
// 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"}

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:

{"action": "subscribe", "channel": "ticker_all"}

Incoming message:

{
  "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.

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()
// 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"}}

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:

{"action": "subscribe", "channel": "candles", "symbol": "BTCUSDT", "interval": "1m"}

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

Incoming message:

{
  "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
  }
}
FieldTypeDescription
timeISO-8601 datetimeCandle open time (UTC)
opendecimal stringOpening price of this candle
highdecimal stringHighest price seen so far in this candle
lowdecimal stringLowest price seen so far in this candle
closedecimal stringMost recent trade price
volumedecimal stringAccumulated base asset volume
is_closedbooleantrue when the candle period has ended and values are final
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()
// 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:

{"action": "subscribe", "channel": "orders"}

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

Incoming message:

{
  "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"
  }
}
FieldTypeDescription
order_idUUID stringOrder identifier
statusstring"filled", "partially_filled", "cancelled", "rejected"
symbolstringTrading pair
sidestring"buy" or "sell"
executed_pricedecimal stringFill price
executed_quantitydecimal stringQuantity filled
feedecimal stringFee paid in USDT
filled_atISO-8601 datetimeFill timestamp
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()
// 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:

{"action": "subscribe", "channel": "portfolio"}

Incoming message (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"
  }
}
FieldTypeDescription
total_equitydecimal stringTotal portfolio value (cash + positions) in USDT
unrealized_pnldecimal stringCurrent unrealized PnL across all open positions
realized_pnldecimal stringCumulative realized PnL from closed trades
available_cashdecimal stringFree USDT balance
timestampISO-8601 datetimeSnapshot timestamp (UTC)
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()
// 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:

{"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:

{
  "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:

{
  "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.):

{
  "channel": "battle",
  "type": "battle:status",
  "data": {
    "battle_id": "a1b2c3d4-...",
    "status": "completed",
    "timestamp": "2026-03-19T11:00:00Z"
  }
}
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()
// 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

ChannelVisibilitySubscription slot usageUpdate frequency
tickerPublic1 per symbolPer Binance tick (~100–500ms)
ticker_allPublic1 (covers all symbols)Per Binance tick
candlesPublic1 per symbol+intervalPer Binance tick (closes every interval)
ordersPrivate1On every order status change
portfolioPrivate1Every 5 seconds
battlePrivate (JWT)1 per battleEvery 5s (rankings) + real-time (trades/status)

Example: Full Agent Monitoring Setup

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

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.


On this page