website/content/blog/realtime-market-data-with-websockets.mdx
The first version of every bot polls: call fetchTicker in a loop, sleep, repeat. It works —
until you want more than one symbol, or faster reactions, or you hit the exchange's rate
limit. Polling gives you a snapshot per request; markets move between your requests.
Exchanges solve this with WebSocket streams: you subscribe once and the exchange pushes every
update to you. CCXT ships WebSocket support in the same package you already have, in all six
languages, behind an API that mirrors the REST one: where REST has fetchOrderBook,
WebSockets have watchOrderBook.
A watch* method resolves every time a new update arrives, so real-time code is just a loop:
import ccxt from 'ccxt';
const exchange = new ccxt.pro.binance();
while (true) {
const orderbook = await exchange.watchOrderBook('BTC/USDT');
console.log(orderbook.bids[0], orderbook.asks[0]);
}
import asyncio
import ccxt.pro
async def main():
exchange = ccxt.pro.binance()
while True:
orderbook = await exchange.watch_order_book('BTC/USDT')
print(orderbook['bids'][0], orderbook['asks'][0])
asyncio.run(main())
<?php
use function React\Async\await;
$exchange = new \ccxt\pro\binance();
while (true) {
$orderbook = await($exchange->watch_order_book('BTC/USDT'));
echo json_encode($orderbook['bids'][0]), ' ', json_encode($orderbook['asks'][0]), "\n";
}
using ccxt;
var exchange = new ccxt.pro.binance(new Dictionary<string, object>() {});
while (true)
{
var orderbook = await exchange.WatchOrderBook("BTC/USDT");
Console.WriteLine($"{orderbook.bids[0][0]} {orderbook.asks[0][0]}");
}
package main
import (
"fmt"
"log"
ccxtpro "github.com/ccxt/ccxt/go/v4/pro"
)
func main() {
exchange := ccxtpro.NewBinance(nil)
for {
orderbook, err := exchange.WatchOrderBook("BTC/USDT")
if err != nil {
log.Println(err)
break
}
fmt.Println(orderbook.Bids[0], orderbook.Asks[0])
}
}
import io.github.ccxt.exchanges.pro.Binance;
var exchange = new Binance();
while (true) {
var orderbook = exchange.watchOrderBook("BTC/USDT");
System.out.println(orderbook.bids.get(0) + " " + orderbook.asks.get(0));
}
The first call opens the connection and subscribes; every following call just waits for the
next update. The same pattern works for watchTicker, watchTrades, watchOHLCV, and — with
API keys — watchBalance, watchOrders, and watchMyTrades for your own fills.
Raw exchange WebSocket APIs are messy, and this is where most hand-rolled integrations go
wrong. When you call watchOrderBook, CCXT:
For dashboards, scanners, and market making you rarely want a single stream. watchTickers
subscribes to a list of symbols in one shot and returns a structure keyed by symbol, updated
as events arrive:
const symbols = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT'];
while (true) {
const tickers = await exchange.watchTickers(symbols);
console.log(tickers['BTC/USDT'].last);
}
symbols = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT']
while True:
tickers = await exchange.watch_tickers(symbols)
print(tickers['BTC/USDT']['last'])
$symbols = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT'];
while (true) {
$tickers = await($exchange->watch_tickers($symbols));
echo $tickers['BTC/USDT']['last'], "\n";
}
var symbols = new List<string>() { "BTC/USDT", "ETH/USDT", "SOL/USDT" };
while (true)
{
var tickers = await exchange.WatchTickers(symbols);
Console.WriteLine(tickers["BTC/USDT"].last);
}
symbols := []string{"BTC/USDT", "ETH/USDT", "SOL/USDT"}
for {
tickers, err := exchange.WatchTickers(ccxt.WithWatchTickersSymbols(symbols))
if err != nil {
log.Println(err)
break
}
fmt.Println(*tickers.Tickers["BTC/USDT"].Last)
}
var symbols = java.util.List.of("BTC/USDT", "ETH/USDT", "SOL/USDT");
while (true) {
var tickers = exchange.watchTickers(symbols, null);
System.out.println(tickers.tickers.get("BTC/USDT").last);
}
There are ...ForSymbols variants (watchTradesForSymbols, watchOrderBookForSymbols,
watchOHLCVForSymbols) when you need one loop over many markets, and running several
exchanges concurrently is just multiple loops — one async task per exchange in
JavaScript/Python/PHP/C#/Java, one goroutine per exchange in Go.
The same mechanism covers private data, which is how bots track fills without polling
fetchOrder:
const exchange = new ccxt.pro.binance({ apiKey: KEY, secret: SECRET });
while (true) {
const orders = await exchange.watchOrders();
for (const order of orders) {
console.log(order.id, order.status, order.filled);
}
}
exchange = ccxt.pro.binance({'apiKey': KEY, 'secret': SECRET})
while True:
orders = await exchange.watch_orders()
for order in orders:
print(order['id'], order['status'], order['filled'])
$exchange = new \ccxt\pro\binance(['apiKey' => $key, 'secret' => $secret]);
while (true) {
$orders = await($exchange->watch_orders());
foreach ($orders as $order) {
echo $order['id'], ' ', $order['status'], ' ', $order['filled'], "\n";
}
}
var exchange = new ccxt.pro.binance(new Dictionary<string, object>() {
{ "apiKey", key }, { "secret", secret },
});
while (true)
{
var orders = await exchange.WatchOrders();
foreach (var order in orders)
{
Console.WriteLine($"{order.id} {order.status} {order.filled}");
}
}
exchange := ccxtpro.NewBinance(nil)
exchange.ApiKey = key
exchange.Secret = secret
for {
orders, err := exchange.WatchOrders()
if err != nil {
log.Println(err)
break
}
for _, order := range orders {
fmt.Println(*order.Id, *order.Status, *order.Filled)
}
}
var exchange = new Binance();
exchange.apiKey = key;
exchange.secret = secret;
while (true) {
var orders = exchange.watchOrders();
for (var order : orders) {
System.out.println(order.id + " " + order.status + " " + order.filled);
}
}
exchange.has['watchOrderBook'], exchange.has['watchOrders'], and so on.close() on shutdown. WebSocket connections hold sockets and background tasks;
call await exchange.close() when your program exits.If you're building toward strategies that need this — scanners, market making — the follow-up posts pick up exactly here: arbitrage strategies and market making.