trading-platform-backend-v2/WEBSOCKET_TESTING.md
rckrdmrd e45591a0ef feat: Initial commit - Trading Platform Backend
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>
2026-01-18 04:28:47 -06:00

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/USDT
  • ETHUSDT - Ethereum/USDT
  • BNBUSDT - Binance Coin/USDT
  • SOLUSDT - Solana/USDT
  • XRPUSDT - Ripple/USDT
  • DOGEUSDT - Dogecoin/USDT
  • ADAUSDT - Cardano/USDT
  • AVAXUSDT - Avalanche/USDT

Error Handling

Connection Errors

If connection fails, check:

  1. Backend server is running on port 3000
  2. WebSocket path is /ws
  3. 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

  1. Heartbeat: Send ping every 30 seconds to keep connection alive
  2. Reconnection: Implement exponential backoff for reconnections
  3. Subscription Limit: Don't subscribe to too many symbols at once (max 50 per client)
  4. Clean Disconnect: Unsubscribe before closing connection
  5. Error Handling: Always handle error messages 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

  1. Check if Binance API is accessible from your server
  2. Verify symbol format (must be uppercase, e.g., BTCUSDT)
  3. Check backend logs for connection errors

High Latency

  1. Ensure server is geographically close to Binance servers
  2. Check network connection quality
  3. Reduce number of subscriptions

Disconnections

  1. Implement heartbeat/ping mechanism
  2. Check server logs for errors
  3. 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