Skip to content

WebSocket Integration

Learn how to integrate real-time market data into TradeX Chart using WebSocket connections for live price updates.

Overview

WebSockets provide bi-directional, real-time communication ideal for:

  • Live price updates - Streaming tick data
  • Real-time candles - Updating candles as they form
  • Low latency - Faster than polling REST APIs
  • Efficient - Single persistent connection
  • Event-driven - Push-based updates

Basic WebSocket Setup

1. Simple Connection

import { Chart } from 'tradex-chart'

class ChartWebSocket {
  constructor(chart, url) {
    this.chart = chart
    this.url = url
    this.ws = null
    this.reconnectAttempts = 0
    this.maxReconnectAttempts = 5
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = this.handleOpen.bind(this)
    this.ws.onmessage = this.handleMessage.bind(this)
    this.ws.onerror = this.handleError.bind(this)
    this.ws.onclose = this.handleClose.bind(this)
  }
  
  handleOpen(event) {
    console.log('WebSocket connected')
    this.reconnectAttempts = 0
    
    // Subscribe to symbol
    this.subscribe('BTCUSDT')
  }
  
  handleMessage(event) {
    try {
      const data = JSON.parse(event.data)
      
      if (data.type === 'candle') {
        this.updateChart(data)
      }
    } catch (error) {
      console.error('Failed to parse message:', error)
    }
  }
  
  handleError(error) {
    console.error('WebSocket error:', error)
  }
  
  handleClose(event) {
    console.log('WebSocket closed:', event.code, event.reason)
    
    // Attempt reconnection
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++
      const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
      
      console.log(`Reconnecting in ${delay}ms...`)
      setTimeout(() => this.connect(), delay)
    }
  }
  
  subscribe(symbol) {
    const message = JSON.stringify({
      type: 'subscribe',
      symbol: symbol,
      channel: 'candles'
    })
    
    this.ws.send(message)
  }
  
  updateChart(data) {
    const candle = [
      data.timestamp,
      data.open,
      data.high,
      data.low,
      data.close,
      data.volume
    ]
    
    if (data.isClosed) {
      this.chart.addCandle(candle)
    } else {
      this.chart.updateStreamingCandle(candle)
    }
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close()
      this.ws = null
    }
  }
}

// Usage
const chart = document.querySelector('tradex-chart')
const wsClient = new ChartWebSocket(chart, 'wss://api.example.com/ws')
wsClient.connect()

2. Streaming Price Updates

class StreamingChart {
  constructor(chart, candleInterval = 60000) {
    this.chart = chart
    this.currentCandle = null
    this.candleInterval = candleInterval // 1 minute default
  }
  
  handleTick(tick) {
    const timestamp = Math.floor(tick.timestamp / this.candleInterval) * this.candleInterval
    
    if (!this.currentCandle || this.currentCandle[0] !== timestamp) {
      // New candle period
      if (this.currentCandle) {
        // Finalize previous candle
        this.chart.addCandle(this.currentCandle)
      }
      
      // Start new candle
      this.currentCandle = [
        timestamp,
        tick.price,  // open
        tick.price,  // high
        tick.price,  // low
        tick.price,  // close
        tick.volume  // volume
      ]
    } else {
      // Update current candle
      this.currentCandle[2] = Math.max(this.currentCandle[2], tick.price) // high
      this.currentCandle[3] = Math.min(this.currentCandle[3], tick.price) // low
      this.currentCandle[4] = tick.price // close
      this.currentCandle[5] += tick.volume // volume
    }
    
    // Update chart with streaming candle
    this.chart.updateStreamingCandle(this.currentCandle)
  }
}

Advanced Patterns

3. Robust Reconnection Strategy

class RobustWebSocket {
  constructor(url, options = {}) {
    this.url = url
    this.options = {
      reconnectInterval: 1000,
      maxReconnectInterval: 30000,
      reconnectDecay: 1.5,
      maxReconnectAttempts: null,
      ...options
    }
    
    this.ws = null
    this.reconnectAttempts = 0
    this.reconnectTimer = null
    this.forcedClose = false
    this.messageQueue = []
  }
  
  connect() {
    this.forcedClose = false
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = (event) => {
      console.log('WebSocket connected')
      this.reconnectAttempts = 0
      
      // Flush message queue
      while (this.messageQueue.length > 0) {
        const message = this.messageQueue.shift()
        this.ws.send(message)
      }
      
      if (this.options.onopen) {
        this.options.onopen(event)
      }
    }
    
    this.ws.onmessage = (event) => {
      if (this.options.onmessage) {
        this.options.onmessage(event)
      }
    }
    
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error)
      
      if (this.options.onerror) {
        this.options.onerror(error)
      }
    }
    
    this.ws.onclose = (event) => {
      console.log('WebSocket closed')
      
      if (this.options.onclose) {
        this.options.onclose(event)
      }
      
      if (!this.forcedClose) {
        this.reconnect()
      }
    }
  }
  
  reconnect() {
    if (
      this.options.maxReconnectAttempts &&
      this.reconnectAttempts >= this.options.maxReconnectAttempts
    ) {
      console.error('Max reconnection attempts reached')
      return
    }
    
    this.reconnectAttempts++
    
    const timeout = Math.min(
      this.options.reconnectInterval * Math.pow(this.options.reconnectDecay, this.reconnectAttempts),
      this.options.maxReconnectInterval
    )
    
    console.log(`Reconnecting in ${timeout}ms (attempt ${this.reconnectAttempts})`)
    
    this.reconnectTimer = setTimeout(() => {
      console.log('Reconnecting...')
      this.connect()
    }, timeout)
  }
  
  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data)
    } else {
      // Queue message for later
      this.messageQueue.push(data)
    }
  }
  
  close() {
    this.forcedClose = true
    
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
    }
    
    if (this.ws) {
      this.ws.close()
    }
  }
}

// Usage
const ws = new RobustWebSocket('wss://api.example.com/ws', {
  onopen: () => console.log('Connected!'),
  onmessage: (event) => handleMessage(event),
  reconnectInterval: 1000,
  maxReconnectAttempts: 10
})

ws.connect()

4. Multi-Symbol Management

class MultiSymbolWebSocket {
  constructor(url) {
    this.url = url
    this.ws = null
    this.subscriptions = new Map()
    this.charts = new Map()
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = () => {
      console.log('Connected')
      // Resubscribe to all symbols
      this.subscriptions.forEach((_, symbol) => {
        this.subscribe(symbol)
      })
    }
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      const chart = this.charts.get(data.symbol)
      
      if (chart) {
        this.updateChart(chart, data)
      }
    }
  }
  
  addChart(symbol, chart) {
    this.charts.set(symbol, chart)
    this.subscribe(symbol)
  }
  
  removeChart(symbol) {
    this.charts.delete(symbol)
    this.unsubscribe(symbol)
  }
  
  subscribe(symbol) {
    if (!this.subscriptions.has(symbol)) {
      this.subscriptions.set(symbol, true)
      
      this.ws.send(JSON.stringify({
        type: 'subscribe',
        symbol: symbol
      }))
    }
  }
  
  unsubscribe(symbol) {
    if (this.subscriptions.has(symbol)) {
      this.subscriptions.delete(symbol)
      
      this.ws.send(JSON.stringify({
        type: 'unsubscribe',
        symbol: symbol
      }))
    }
  }
  
  updateChart(chart, data) {
    const candle = [
      data.timestamp,
      data.open,
      data.high,
      data.low,
      data.close,
      data.volume
    ]
    
    if (data.isClosed) {
      chart.addCandle(candle)
    } else {
      chart.updateStreamingCandle(candle)
    }
  }
}

5. Heartbeat/Ping-Pong

class WebSocketWithHeartbeat {
  constructor(url, heartbeatInterval = 30000) {
    this.url = url
    this.heartbeatInterval = heartbeatInterval
    this.heartbeatTimer = null
    this.pongTimeout = null
    this.ws = null
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = () => {
      console.log('Connected')
      this.startHeartbeat()
    }
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      
      if (data.type === 'pong') {
        // Heartbeat acknowledged
        clearTimeout(this.pongTimeout)
        return
      }
      
      // Handle other messages
      this.handleMessage(data)
    }
    
    this.ws.onclose = () => {
      this.stopHeartbeat()
    }
  }
  
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }))
        
        // Expect pong within 5 seconds
        this.pongTimeout = setTimeout(() => {
          console.error('Heartbeat timeout, reconnecting...')
          this.ws.close()
        }, 5000)
      }
    }, this.heartbeatInterval)
  }
  
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer)
      this.heartbeatTimer = null
    }
    
    if (this.pongTimeout) {
      clearTimeout(this.pongTimeout)
      this.pongTimeout = null
    }
  }
  
  handleMessage(data) {
    // Handle regular messages
  }
}

Exchange-Specific Examples

Binance WebSocket

class BinanceWebSocket {
  constructor(chart, symbol) {
    this.chart = chart
    this.symbol = symbol.toLowerCase()
    this.ws = null
  }
  
  connect(interval = '1m') {
    // Binance WebSocket endpoint
    const url = `wss://stream.binance.com:9443/ws/${this.symbol}@kline_${interval}`
    
    this.ws = new WebSocket(url)
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      
      if (data.e === 'kline') {
        const kline = data.k
        const candle = [
          kline.t,                    // timestamp
          parseFloat(kline.o),        // open
          parseFloat(kline.h),        // high
          parseFloat(kline.l),        // low
          parseFloat(kline.c),        // close
          parseFloat(kline.v)         // volume
        ]
        
        if (kline.x) {
          // Candle is closed
          this.chart.addCandle(candle)
        } else {
          // Candle is still forming
          this.chart.updateStreamingCandle(candle)
        }
      }
    }
    
    this.ws.onerror = (error) => {
      console.error('Binance WebSocket error:', error)
    }
    
    this.ws.onclose = () => {
      console.log('Binance WebSocket closed')
      // Implement reconnection logic
    }
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close()
    }
  }
}

// Usage
const chart = document.querySelector('tradex-chart')
const binanceWS = new BinanceWebSocket(chart, 'BTCUSDT')
binanceWS.connect('1m')

Coinbase WebSocket

class CoinbaseWebSocket {
  constructor(chart, productId) {
    this.chart = chart
    this.productId = productId
    this.ws = null
    this.streamingChart = new StreamingChart(chart, 60000)
  }
  
  connect() {
    this.ws = new WebSocket('wss://ws-feed.exchange.coinbase.com')
    
    this.ws.onopen = () => {
      // Subscribe to ticker channel
      this.ws.send(JSON.stringify({
        type: 'subscribe',
        product_ids: [this.productId],
        channels: ['ticker']
      }))
    }
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      
      if (data.type === 'ticker') {
        const tick = {
          timestamp: new Date(data.time).getTime(),
          price: parseFloat(data.price),
          volume: parseFloat(data.last_size)
        }
        
        this.streamingChart.handleTick(tick)
      }
    }
    
    this.ws.onerror = (error) => {
      console.error('Coinbase WebSocket error:', error)
    }
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.send(JSON.stringify({
        type: 'unsubscribe',
        product_ids: [this.productId],
        channels: ['ticker']
      }))
      this.ws.close()
    }
  }
}

// Usage
const chart = document.querySelector('tradex-chart')
const coinbaseWS = new CoinbaseWebSocket(chart, 'BTC-USD')
coinbaseWS.connect()

Kraken WebSocket

class KrakenWebSocket {
  constructor(chart, pair) {
    this.chart = chart
    this.pair = pair
    this.ws = null
  }
  
  connect(interval = 1) {
    this.ws = new WebSocket('wss://ws.kraken.com')
    
    this.ws.onopen = () => {
      // Subscribe to OHLC channel
      this.ws.send(JSON.stringify({
        event: 'subscribe',
        pair: [this.pair],
        subscription: {
          name: 'ohlc',
          interval: interval
        }
      }))
    }
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      
      // Kraken sends array format for OHLC data
      if (Array.isArray(data) && data[2] === 'ohlc') {
        const ohlc = data[1]
        const candle = [
          parseFloat(ohlc[1]) * 1000,  // timestamp (convert to ms)
          parseFloat(ohlc[2]),          // open
          parseFloat(ohlc[3]),          // high
          parseFloat(ohlc[4]),          // low
          parseFloat(ohlc[5]),          // close
          parseFloat(ohlc[7])           // volume
        ]
        
        // Check if candle is closed (last value in array)
        if (data[1][8] === '1') {
          this.chart.addCandle(candle)
        } else {
          this.chart.updateStreamingCandle(candle)
        }
      }
    }
  }
  
  disconnect() {
    if (this.ws) {
      this.ws.close()
    }
  }
}

Best Practices

1. Connection Management

class ConnectionManager {
  constructor(url) {
    this.url = url
    this.ws = null
    this.isConnecting = false
    this.isConnected = false
  }
  
  async connect() {
    if (this.isConnecting || this.isConnected) {
      return
    }
    
    this.isConnecting = true
    
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.url)
      
      const timeout = setTimeout(() => {
        reject(new Error('Connection timeout'))
        this.ws.close()
      }, 10000)
      
      this.ws.onopen = () => {
        clearTimeout(timeout)
        this.isConnecting = false
        this.isConnected = true
        resolve()
      }
      
      this.ws.onerror = (error) => {
        clearTimeout(timeout)
        this.isConnecting = false
        reject(error)
      }
    })
  }
  
  getState() {
    if (!this.ws) return 'DISCONNECTED'
    
    switch (this.ws.readyState) {
      case WebSocket.CONNECTING: return 'CONNECTING'
      case WebSocket.OPEN: return 'CONNECTED'
      case WebSocket.CLOSING: return 'CLOSING'
      case WebSocket.CLOSED: return 'CLOSED'
      default: return 'UNKNOWN'
    }
  }
}

2. Message Buffering

class BufferedWebSocket {
  constructor(url, bufferSize = 100) {
    this.url = url
    this.ws = null
    this.buffer = []
    this.bufferSize = bufferSize
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onmessage = (event) => {
      this.buffer.push(JSON.parse(event.data))
      
      // Keep buffer size limited
      if (this.buffer.length > this.bufferSize) {
        this.buffer.shift()
      }
      
      this.processBuffer()
    }
  }
  
  processBuffer() {
    // Process messages in batches for better performance
    const batch = this.buffer.splice(0, 10)
    
    batch.forEach(message => {
      this.handleMessage(message)
    })
  }
  
  handleMessage(message) {
    // Handle individual message
  }
}

3. Error Recovery

class ResilientWebSocket {
  constructor(url) {
    this.url = url
    this.ws = null
    this.subscriptions = []
  }
  
  connect() {
    this.ws = new WebSocket(this.url)
    
    this.ws.onopen = () => {
      // Restore subscriptions after reconnect
      this.subscriptions.forEach(sub => {
        this.ws.send(JSON.stringify(sub))
      })
    }
    
    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error)
      // Log error for debugging
      this.logError(error)
    }
    
    this.ws.onclose = (event) => {
      if (event.code !== 1000) {
        // Abnormal closure, attempt recovery
        console.log('Abnormal closure, reconnecting...')
        setTimeout(() => this.connect(), 1000)
      }
    }
  }
  
  subscribe(subscription) {
    this.subscriptions.push(subscription)
    
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(subscription))
    }
  }
  
  logError(error) {
    // Send to error tracking service
    console.error('WebSocket error logged:', error)
  }
}

Complete Example

import { Chart } from 'tradex-chart'

class RealtimeChartApp {
  constructor(containerId) {
    this.container = document.getElementById(containerId)
    this.chart = null
    this.ws = null
  }
  
  async initialize(symbol, timeframe) {
    // Create chart
    this.chart = document.createElement('tradex-chart')
    this.container.appendChild(this.chart)
    
    // Load initial historical data
    const historicalData = await this.fetchHistoricalData(symbol, timeframe)
    
    this.chart.start({
      title: symbol,
      state: { ohlcv: historicalData }
    })
    
    // Connect WebSocket for real-time updates
    this.connectWebSocket(symbol, timeframe)
  }
  
  async fetchHistoricalData(symbol, timeframe) {
    const response = await fetch(
      `https://api.example.com/ohlcv?symbol=${symbol}&timeframe=${timeframe}&limit=500`
    )
    return response.json()
  }
  
  connectWebSocket(symbol, timeframe) {
    this.ws = new RobustWebSocket('wss://api.example.com/ws', {
      onopen: () => {
        console.log('WebSocket connected')
        this.ws.send(JSON.stringify({
          type: 'subscribe',
          symbol: symbol,
          timeframe: timeframe
        }))
      },
      onmessage: (event) => {
        const data = JSON.parse(event.data)
        this.handleUpdate(data)
      },
      onerror: (error) => {
        console.error('WebSocket error:', error)
        this.showError('Connection error')
      }
    })
    
    this.ws.connect()
  }
  
  handleUpdate(data) {
    const candle = [
      data.timestamp,
      data.open,
      data.high,
      data.low,
      data.close,
      data.volume
    ]
    
    if (data.isClosed) {
      this.chart.addCandle(candle)
    } else {
      this.chart.updateStreamingCandle(candle)
    }
  }
  
  showError(message) {
    console.error(message)
    // Display error to user
  }
  
  destroy() {
    if (this.ws) {
      this.ws.close()
    }
  }
}

// Usage
const app = new RealtimeChartApp('chart-container')
await app.initialize('BTCUSDT', '1m')

Troubleshooting

Connection Drops

  • Implement exponential backoff for reconnection
  • Use heartbeat/ping-pong to detect dead connections
  • Handle network changes gracefully

Message Loss

  • Buffer messages during reconnection
  • Request missed data after reconnect
  • Implement sequence numbers if supported

Performance Issues

  • Batch process messages
  • Throttle updates if too frequent
  • Use Web Workers for heavy processing

Authentication

  • Send auth token after connection
  • Refresh tokens before expiry
  • Handle auth failures gracefully