Skip to main content
Back to Learn
intermediatewebsocketmarket-datareal-timestreaming

Real-Time Market Data with WebSockets

Connect to eToro's WebSocket API for live price streaming, handle reconnections, and process real-time market data efficiently.

3 min readBy eToro Developer Relations

Overview

The eToro WebSocket API provides real-time streaming of market data including price quotes, order book updates, and trade notifications. This guide walks through connecting, subscribing to channels, and handling data efficiently.

Connection Setup

Establishing a WebSocket Connection

const WebSocket = require("ws");

const WS_URL = "wss://api.etoro.com/ws/v2";

function connect(token) {
  const ws = new WebSocket(WS_URL, {
    headers: { Authorization: `Bearer ${token}` },
  });

  ws.on("open", () => {
    console.log("Connected to eToro WebSocket");
  });

  ws.on("message", (data) => {
    const message = JSON.parse(data);
    handleMessage(message);
  });

  ws.on("close", (code, reason) => {
    console.log(`Disconnected: ${code} - ${reason}`);
    if (code !== 1000) {
      setTimeout(() => connect(token), 5000);
    }
  });

  ws.on("error", (error) => {
    console.error("WebSocket error:", error.message);
  });

  return ws;
}

Subscribing to Channels

Once connected, subscribe to specific instrument channels:

function subscribe(ws, instruments) {
  ws.send(
    JSON.stringify({
      action: "subscribe",
      channels: ["quotes"],
      instruments: instruments,
    })
  );
}

// Subscribe to Apple, Tesla, and Bitcoin
subscribe(ws, ["AAPL", "TSLA", "BTC"]);

Available Channels

Channel Description Update Frequency
quotes Bid/ask prices Every tick
candles OHLCV candles Per interval
orderbook Level 2 depth Every change
trades Executed trades Per trade

Processing Messages

function handleMessage(message) {
  switch (message.type) {
    case "quote":
      console.log(
        `${message.instrument}: Bid ${message.bid} / Ask ${message.ask}`
      );
      break;

    case "candle":
      console.log(
        `${message.instrument} ${message.interval}: O${message.open} H${message.high} L${message.low} C${message.close}`
      );
      break;

    case "heartbeat":
      // Connection keepalive — no action needed
      break;

    default:
      console.log("Unknown message type:", message.type);
  }
}

Reconnection Strategy

Production applications need robust reconnection logic with exponential backoff:

class ReconnectingSocket {
  constructor(url, token) {
    this.url = url;
    this.token = token;
    this.attempt = 0;
    this.maxDelay = 30000;
    this.subscriptions = [];
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url, {
      headers: { Authorization: `Bearer ${this.token}` },
    });

    this.ws.on("open", () => {
      this.attempt = 0;
      this.resubscribe();
    });

    this.ws.on("close", (code) => {
      if (code !== 1000) {
        const delay = Math.min(
          1000 * Math.pow(2, this.attempt),
          this.maxDelay
        );
        this.attempt++;
        console.log(`Reconnecting in ${delay}ms (attempt ${this.attempt})`);
        setTimeout(() => this.connect(), delay);
      }
    });
  }

  subscribe(channels, instruments) {
    this.subscriptions.push({ channels, instruments });
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(
        JSON.stringify({ action: "subscribe", channels, instruments })
      );
    }
  }

  resubscribe() {
    for (const sub of this.subscriptions) {
      this.ws.send(
        JSON.stringify({
          action: "subscribe",
          channels: sub.channels,
          instruments: sub.instruments,
        })
      );
    }
  }
}

Performance Tips

  1. Batch subscriptions — Subscribe to multiple instruments in a single message
  2. Throttle UI updates — Use requestAnimationFrame or debounce for rendering
  3. Use binary frames — Enable MessagePack encoding for lower bandwidth
  4. Unsubscribe when you no longer need a channel to reduce server load

Next Steps

We use cookies to improve your experience. By using this site, you agree to our use of cookies. Privacy Policy (opens in new tab)