NestJS backend with: - Authentication (JWT) - WebSocket real-time support - ML integration services - Payments module - User management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
WebSocket Testing Guide - Trading Platform
Overview
The Trading Platform backend now supports real-time market data via WebSocket, powered by direct integration with Binance WebSocket streams. This guide explains how to test and use the WebSocket API.
WebSocket Endpoint
ws://localhost:3000/ws
For production:
wss://your-domain.com/ws
Authentication (Optional)
To access private channels (portfolio, orders, user-specific data), include a JWT token in the query string:
ws://localhost:3000/ws?token=YOUR_JWT_TOKEN
Public market data channels do not require authentication.
Message Format
Client to Server
All messages must be valid JSON with a type field:
{
"type": "subscribe",
"channels": ["price:BTCUSDT"]
}
Server to Client
Server responses include a type, optional channel, and timestamp:
{
"type": "price",
"channel": "price:BTCUSDT",
"data": {
"symbol": "BTCUSDT",
"price": 97523.45,
"change24h": 2345.67,
"changePercent24h": 2.47,
"high24h": 98500.00,
"low24h": 95000.00,
"volume24h": 12345.67,
"timestamp": 1701806400000
},
"timestamp": "2024-12-06T12:00:00.000Z"
}
Available Channels
1. Price Updates (price:<symbol>)
Real-time price updates for a specific symbol (via Binance ticker stream).
Subscribe:
{
"type": "subscribe",
"channels": ["price:BTCUSDT"]
}
Data received:
{
"type": "price",
"channel": "price:BTCUSDT",
"data": {
"symbol": "BTCUSDT",
"price": 97523.45,
"change24h": 2345.67,
"changePercent24h": 2.47,
"high24h": 98500.00,
"low24h": 95000.00,
"volume24h": 12345.67,
"timestamp": 1701806400000
}
}
2. Ticker Updates (ticker:<symbol>)
Full 24h ticker statistics (bid, ask, volume, etc.).
Subscribe:
{
"type": "subscribe",
"channels": ["ticker:ETHUSDT"]
}
Data received:
{
"type": "ticker",
"channel": "ticker:ETHUSDT",
"data": {
"symbol": "ETHUSDT",
"price": 3650.00,
"bid": 3649.50,
"ask": 3650.50,
"volume": 123456.78,
"change": 125.50,
"changePercent": 3.56,
"high": 3700.00,
"low": 3500.00,
"open": 3524.50,
"previousClose": 3524.50,
"timestamp": "2024-12-06T12:00:00.000Z"
}
}
3. Klines/Candlesticks (klines:<symbol>:<interval>)
Real-time candlestick data at specified intervals.
Intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d, 3d, 1w, 1M
Subscribe:
{
"type": "subscribe",
"channels": ["klines:BTCUSDT:1m"]
}
Data received:
{
"type": "kline",
"channel": "klines:BTCUSDT:1m",
"data": {
"symbol": "BTCUSDT",
"interval": "1m",
"time": 1701806400000,
"open": 97500.00,
"high": 97600.00,
"low": 97400.00,
"close": 97523.45,
"volume": 123.45,
"isFinal": false,
"timestamp": "2024-12-06T12:00:00.000Z"
}
}
Note: isFinal: true indicates the candle is closed and will not change.
4. Trades (trades:<symbol>)
Individual trade executions as they happen.
Subscribe:
{
"type": "subscribe",
"channels": ["trades:BTCUSDT"]
}
Data received:
{
"type": "trade",
"channel": "trades:BTCUSDT",
"data": {
"symbol": "BTCUSDT",
"price": 97523.45,
"quantity": 0.5,
"side": "buy",
"timestamp": "2024-12-06T12:00:00.000Z"
}
}
5. Depth/Order Book (depth:<symbol>)
Order book depth updates (top 10 levels by default).
Subscribe:
{
"type": "subscribe",
"channels": ["depth:BTCUSDT"]
}
Data received:
{
"type": "depth",
"channel": "depth:BTCUSDT",
"data": {
"symbol": "BTCUSDT",
"bids": [
[97520.00, 1.5],
[97519.00, 2.3]
],
"asks": [
[97521.00, 0.8],
[97522.00, 1.2]
],
"timestamp": "2024-12-06T12:00:00.000Z"
}
}
6. ML Signals (signals:<symbol>)
AI-powered trading signals (requires backend ML service).
Subscribe:
{
"type": "subscribe",
"channels": ["signals:BTCUSDT"]
}
Client Messages
Subscribe
{
"type": "subscribe",
"channels": ["price:BTCUSDT", "ticker:ETHUSDT", "klines:SOLUSDT:5m"]
}
Unsubscribe
{
"type": "unsubscribe",
"channels": ["price:BTCUSDT"]
}
Ping (Keepalive)
{
"type": "ping"
}
Response:
{
"type": "pong",
"timestamp": "2024-12-06T12:00:00.000Z"
}
Testing Tools
1. Using wscat (Node.js)
Install:
npm install -g wscat
Connect:
wscat -c ws://localhost:3000/ws
Send messages:
> {"type":"subscribe","channels":["price:BTCUSDT"]}
< {"type":"subscribed","channel":"price:BTCUSDT","timestamp":"..."}
< {"type":"price","channel":"price:BTCUSDT","data":{...}}
2. Using websocat (Rust)
Install:
# macOS
brew install websocat
# Linux
cargo install websocat
Connect:
websocat ws://localhost:3000/ws
3. Using Browser JavaScript
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Test</title>
</head>
<body>
<h1>Trading Platform WebSocket Test</h1>
<div id="output"></div>
<script>
const ws = new WebSocket('ws://localhost:3000/ws');
const output = document.getElementById('output');
ws.onopen = () => {
console.log('Connected to WebSocket');
output.innerHTML += '<p>Connected!</p>';
// Subscribe to price updates
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['price:BTCUSDT', 'klines:ETHUSDT:1m']
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Received:', msg);
output.innerHTML += `<p>${msg.type}: ${JSON.stringify(msg.data || {})}</p>`;
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
output.innerHTML += '<p>Error occurred</p>';
};
ws.onclose = () => {
console.log('Disconnected');
output.innerHTML += '<p>Disconnected</p>';
};
// Send ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
</script>
</body>
</html>
4. Using Python
import asyncio
import websockets
import json
async def test_websocket():
uri = "ws://localhost:3000/ws"
async with websockets.connect(uri) as websocket:
# Subscribe to channels
await websocket.send(json.dumps({
"type": "subscribe",
"channels": ["price:BTCUSDT", "klines:BTCUSDT:1m"]
}))
# Listen for messages
while True:
message = await websocket.recv()
data = json.loads(message)
print(f"Received: {data['type']}")
if 'data' in data:
print(f" Data: {data['data']}")
asyncio.run(test_websocket())
WebSocket Stats Endpoint
Check WebSocket server statistics:
curl http://localhost:3000/api/v1/ws/stats
Response:
{
"success": true,
"data": {
"connectedClients": 5,
"activeChannels": ["price:BTCUSDT", "klines:ETHUSDT:1m"],
"quoteStreams": 0,
"signalStreams": 2,
"binanceStreams": 3,
"binanceActiveStreams": ["btcusdt@ticker", "ethusdt@kline_1m"],
"priceCache": 2
}
}
Common Symbols
BTCUSDT- Bitcoin/USDTETHUSDT- Ethereum/USDTBNBUSDT- Binance Coin/USDTSOLUSDT- Solana/USDTXRPUSDT- Ripple/USDTDOGEUSDT- Dogecoin/USDTADAUSDT- Cardano/USDTAVAXUSDT- Avalanche/USDT
Error Handling
Connection Errors
If connection fails, check:
- Backend server is running on port 3000
- WebSocket path is
/ws - No firewall blocking the connection
Subscription Errors
{
"type": "error",
"channel": "price:INVALID",
"data": {
"message": "Failed to fetch quote for INVALID"
}
}
Authentication Errors
For private channels without valid token:
{
"type": "error",
"channel": "portfolio:user123",
"data": {
"message": "Authentication required for this channel"
}
}
Best Practices
- Heartbeat: Send ping every 30 seconds to keep connection alive
- Reconnection: Implement exponential backoff for reconnections
- Subscription Limit: Don't subscribe to too many symbols at once (max 50 per client)
- Clean Disconnect: Unsubscribe before closing connection
- Error Handling: Always handle
errormessages from server
Example: Complete Trading Dashboard
class TradingDashboard {
constructor() {
this.ws = null;
this.subscriptions = new Set();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
connect() {
this.ws = new WebSocket('ws://localhost:3000/ws');
this.ws.onopen = () => {
console.log('Connected to trading server');
this.reconnectAttempts = 0;
this.resubscribe();
};
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
this.handleMessage(msg);
};
this.ws.onclose = () => {
console.log('Disconnected from server');
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Heartbeat
setInterval(() => this.ping(), 30000);
}
subscribe(channel) {
this.subscriptions.add(channel);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'subscribe',
channels: [channel]
}));
}
}
unsubscribe(channel) {
this.subscriptions.delete(channel);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'unsubscribe',
channels: [channel]
}));
}
}
resubscribe() {
if (this.subscriptions.size > 0) {
this.ws.send(JSON.stringify({
type: 'subscribe',
channels: Array.from(this.subscriptions)
}));
}
}
ping() {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnect attempts reached');
return;
}
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
this.reconnectAttempts++;
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => this.connect(), delay);
}
handleMessage(msg) {
switch (msg.type) {
case 'connected':
console.log('Server says: connected', msg.data);
break;
case 'price':
this.updatePrice(msg.data);
break;
case 'kline':
this.updateChart(msg.data);
break;
case 'ticker':
this.updateTicker(msg.data);
break;
case 'trade':
this.addTrade(msg.data);
break;
case 'error':
console.error('Server error:', msg.data);
break;
case 'pong':
// Heartbeat acknowledged
break;
default:
console.log('Unknown message type:', msg.type);
}
}
updatePrice(data) {
console.log(`Price update: ${data.symbol} = $${data.price}`);
// Update UI
}
updateChart(data) {
console.log(`Kline update: ${data.symbol} ${data.interval}`);
// Update chart
}
updateTicker(data) {
console.log(`Ticker update: ${data.symbol}`);
// Update ticker display
}
addTrade(data) {
console.log(`New trade: ${data.symbol} ${data.side} ${data.quantity} @ ${data.price}`);
// Add to trades list
}
disconnect() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
}
// Usage
const dashboard = new TradingDashboard();
dashboard.connect();
dashboard.subscribe('price:BTCUSDT');
dashboard.subscribe('klines:ETHUSDT:1m');
dashboard.subscribe('trades:SOLUSDT');
Troubleshooting
No Data Received
- Check if Binance API is accessible from your server
- Verify symbol format (must be uppercase, e.g.,
BTCUSDT) - Check backend logs for connection errors
High Latency
- Ensure server is geographically close to Binance servers
- Check network connection quality
- Reduce number of subscriptions
Disconnections
- Implement heartbeat/ping mechanism
- Check server logs for errors
- Verify no rate limiting from Binance
Support
For issues or questions:
- Check backend logs:
npm run dev(shows real-time logs) - WebSocket stats endpoint:
GET /api/v1/ws/stats - Server health:
GET /health
Last Updated: December 6, 2024 Backend Version: 0.1.0 WebSocket Protocol: RFC 6455