WebSocket Channels
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— real-time price ticks for one symbolticker_all— ticks for all 600+ pairs in one subscriptioncandles— live candle updates for one symbol
Private channels (authenticated to your account):
orders— fills and status changes for your ordersportfolio— portfolio equity updates every 5 secondsbattle— 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"
}
}
| Field | Type | Description |
|---|---|---|
price | decimal string | Current trade price |
quantity | decimal string | Quantity of the last trade |
timestamp | ISO-8601 datetime | Tick 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
}
}
| 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 |
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"
}
}
| 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 |
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"
}
}
| 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) |
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
| 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:
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 — connection URL, auth, heartbeat, reconnection
- Battles — battle system API reference
- Rate Limits — WebSocket vs HTTP rate limiting