# WebSocket Testing Guide - OrbiQuant Trading Platform

## Overview

The OrbiQuant 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:

```json
{
  "type": "subscribe",
  "channels": ["price:BTCUSDT"]
}
```

### Server to Client

Server responses include a `type`, optional `channel`, and `timestamp`:

```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["price:BTCUSDT"]
}
```

**Data received:**
```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["ticker:ETHUSDT"]
}
```

**Data received:**
```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["klines:BTCUSDT:1m"]
}
```

**Data received:**
```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["trades:BTCUSDT"]
}
```

**Data received:**
```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["depth:BTCUSDT"]
}
```

**Data received:**
```json
{
  "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:**
```json
{
  "type": "subscribe",
  "channels": ["signals:BTCUSDT"]
}
```

## Client Messages

### Subscribe

```json
{
  "type": "subscribe",
  "channels": ["price:BTCUSDT", "ticker:ETHUSDT", "klines:SOLUSDT:5m"]
}
```

### Unsubscribe

```json
{
  "type": "unsubscribe",
  "channels": ["price:BTCUSDT"]
}
```

### Ping (Keepalive)

```json
{
  "type": "ping"
}
```

**Response:**
```json
{
  "type": "pong",
  "timestamp": "2024-12-06T12:00:00.000Z"
}
```

## Testing Tools

### 1. Using `wscat` (Node.js)

Install:
```bash
npm install -g wscat
```

Connect:
```bash
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:
```bash
# macOS
brew install websocat

# Linux
cargo install websocat
```

Connect:
```bash
websocat ws://localhost:3000/ws
```

### 3. Using Browser JavaScript

```html
<!DOCTYPE html>
<html>
<head>
  <title>WebSocket Test</title>
</head>
<body>
  <h1>OrbiQuant 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

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

```bash
curl http://localhost:3000/api/v1/ws/stats
```

**Response:**
```json
{
  "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

```json
{
  "type": "error",
  "channel": "price:INVALID",
  "data": {
    "message": "Failed to fetch quote for INVALID"
  }
}
```

### Authentication Errors

For private channels without valid token:
```json
{
  "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

```javascript
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
