{
  "op": "subscribe",
  "args": [
    { "channel": "level1", "symbol": "US500-BTC" },
    { "channel": "level1", "symbol": "GOLD-BTC" }
  ]
}
{
  "topic": "level1.US500-BTC",
  "type": "snapshot",
  "createdTime": 1640995200000000000,
  "data": {
    "symbol": "US500-BTC",
    "bid": ["0.00432450", "0.5"],
    "ask": ["0.00432500", "0.3"],
    "timestamp": 1640995200000000000
  }
}

Overview

Level 1 quotes provide real-time best bid and ask prices with quantities for all supported trading pairs. This is the most essential market data stream for displaying current market prices and spreads.

Channel Information

PropertyValue
Channel Namelevel1
AuthenticationNo
Update FrequencyReal-time
Data TypeCurrent best bid/ask

Subscribe to Level 1 Data

{
  "op": "subscribe",
  "args": [
    { "channel": "level1", "symbol": "US500-BTC" },
    { "channel": "level1", "symbol": "GOLD-BTC" }
  ]
}

Response Format

{
  "topic": "level1.US500-BTC",
  "type": "snapshot",
  "createdTime": 1640995200000000000,
  "data": {
    "symbol": "US500-BTC",
    "bid": ["0.00432450", "0.5"],
    "ask": ["0.00432500", "0.3"],
    "timestamp": 1640995200000000000
  }
}
data
object
Level 1 order book data

Implementation Examples

JavaScript Level 1 Handler

class Level1Handler {
  constructor() {
    this.quotes = {};
    this.listeners = [];
  }

  connect() {
    this.ws = new WebSocket('wss://ws.roxom.com/ws');
    
    this.ws.onopen = () => {
      console.log('Connected to Level 1 stream');
      this.subscribeToSymbols(['US500-BTC', 'GOLD-BTC', 'OIL-BTC']);
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      if (data.topic && data.topic.startsWith('level1.')) {
        this.handleLevel1Update(data);
      }
    };
  }

  subscribeToSymbols(symbols) {
    const args = symbols.map(symbol => ({
      channel: 'level1',
      symbol: symbol
    }));

    this.ws.send(JSON.stringify({
      op: 'subscribe',
      args: args
    }));
  }

  handleLevel1Update(message) {
    const symbol = message.data.symbol;
    const bid = message.data.bid;
    const ask = message.data.ask;
    const timestamp = message.data.timestamp;

    // Update local quote cache
    this.quotes[symbol] = {
      bid: {
        price: parseFloat(bid[0]),
        size: parseFloat(bid[1])
      },
      ask: {
        price: parseFloat(ask[0]),
        size: parseFloat(ask[1])
      },
      spread: parseFloat(ask[0]) - parseFloat(bid[0]),
      timestamp: timestamp,
      lastUpdated: Date.now()
    };

    // Calculate additional metrics
    const quote = this.quotes[symbol];
    quote.midPrice = (quote.bid.price + quote.ask.price) / 2;
    quote.spreadPercent = (quote.spread / quote.midPrice) * 100;

    console.log(`${symbol} | Bid: ${quote.bid.price} (${quote.bid.size}) | Ask: ${quote.ask.price} (${quote.ask.size}) | Spread: ${quote.spread.toFixed(2)} (${quote.spreadPercent.toFixed(3)}%)`);

    // Notify listeners
    this.notifyListeners(symbol, quote);

    // Update UI if available
    this.updateUI(symbol, quote);
  }

  addListener(callback) {
    this.listeners.push(callback);
  }

  notifyListeners(symbol, quote) {
    this.listeners.forEach(callback => {
      try {
        callback(symbol, quote);
      } catch (error) {
        console.error('Listener error:', error);
      }
    });
  }

  updateUI(symbol, quote) {
    // Update DOM elements if available
    const bidElement = document.getElementById(`${symbol}-bid`);
    const askElement = document.getElementById(`${symbol}-ask`);
    const spreadElement = document.getElementById(`${symbol}-spread`);

    if (bidElement) {
      bidElement.textContent = `${quote.bid.price} (${quote.bid.size})`;
    }

    if (askElement) {
      askElement.textContent = `${quote.ask.price} (${quote.ask.size})`;
    }

    if (spreadElement) {
      spreadElement.textContent = `${quote.spread.toFixed(2)}`;
    }
  }

  getBestPrices(symbol) {
    return this.quotes[symbol] || null;
  }

  getAllQuotes() {
    return { ...this.quotes };
  }

  calculateSpread(symbol) {
    const quote = this.quotes[symbol];
    if (!quote) return null;

    return {
      absolute: quote.spread,
      percentage: quote.spreadPercent,
      basisPoints: quote.spreadPercent * 100
    };
  }
}

// Usage
const level1Handler = new Level1Handler();

level1Handler.addListener((symbol, quote) => {
  // Custom logic for quote updates
  if (quote.spreadPercent < 0.01) {
    console.log(`🟢 Tight spread on ${symbol}: ${quote.spreadPercent.toFixed(4)}%`);
  }
});

level1Handler.connect();

Python Level 1 Monitor

import websocket
import json
from typing import Dict, Any, Callable
from datetime import datetime

class Level1Monitor:
    def __init__(self):
        self.quotes: Dict[str, Dict] = {}
        self.listeners: List[Callable] = []
        self.ws = None
        
    def connect(self):
        self.ws = websocket.WebSocketApp(
            "wss://ws.roxom.com/ws",
            on_open=self.on_open,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close
        )
        
        self.ws.run_forever()
    
    def on_open(self, ws):
        print("📡 Connected to Level 1 stream")
        self.subscribe_to_symbols(['US500-BTC', 'GOLD-BTC', 'OIL-BTC'])
    
    def subscribe_to_symbols(self, symbols):
        subscribe_msg = {
            "op": "subscribe",
            "args": [{"channel": "level1", "symbol": symbol} for symbol in symbols]
        }
        self.ws.send(json.dumps(subscribe_msg))
    
    def on_message(self, ws, message):
        try:
            data = json.loads(message)
            
            if data.get('topic', '').startswith('level1.'):
                self.handle_level1_update(data)
        except json.JSONDecodeError as e:
            print(f"❌ JSON decode error: {e}")
    
    def handle_level1_update(self, message):
        symbol = message['data']['symbol']
        bid = message['data']['bid']
        ask = message['data']['ask']
        timestamp = message['data']['timestamp']
        
        # Parse bid/ask data
        bid_price, bid_size = float(bid[0]), float(bid[1])
        ask_price, ask_size = float(ask[0]), float(ask[1])
        
        # Calculate derived values
        spread = ask_price - bid_price
        mid_price = (bid_price + ask_price) / 2
        spread_percent = (spread / mid_price) * 100 if mid_price > 0 else 0
        
        # Update quote cache
        self.quotes[symbol] = {
            'bid_price': bid_price,
            'bid_size': bid_size,
            'ask_price': ask_price,
            'ask_size': ask_size,
            'spread': spread,
            'mid_price': mid_price,
            'spread_percent': spread_percent,
            'timestamp': timestamp,
            'last_updated': datetime.now()
        }
        
        print(f"{symbol} | Bid: {bid_price:.6f} ({bid_size}) | Ask: {ask_price:.6f} ({ask_size}) | Spread: {spread:.6f} ({spread_percent:.3f}%)")
        
        # Notify listeners
        self.notify_listeners(symbol, self.quotes[symbol])
        
        # Check for alerts
        self.check_spread_alerts(symbol, spread_percent)
    
    def add_listener(self, callback):
        self.listeners.append(callback)
    
    def notify_listeners(self, symbol, quote):
        for callback in self.listeners:
            try:
                callback(symbol, quote)
            except Exception as e:
                print(f"❌ Listener error: {e}")
    
    def check_spread_alerts(self, symbol, spread_percent):
        if spread_percent < 0.01:
            print(f"🟢 Tight spread on {symbol}: {spread_percent:.4f}%")
        elif spread_percent > 1.0:
            print(f"🟡 Wide spread on {symbol}: {spread_percent:.3f}%")
    
    def get_quote(self, symbol):
        return self.quotes.get(symbol)
    
    def get_all_quotes(self):
        return self.quotes.copy()
    
    def get_best_prices(self):
        """Get best prices across all symbols"""
        if not self.quotes:
            return None
            
        best_bid = max(self.quotes.values(), key=lambda q: q['bid_price'])
        best_ask = min(self.quotes.values(), key=lambda q: q['ask_price'])
        
        return {
            'best_bid': best_bid,
            'best_ask': best_ask,
            'cross_spread': best_ask['ask_price'] - best_bid['bid_price']
        }
    
    def on_error(self, ws, error):
        print(f"🚨 WebSocket error: {error}")
    
    def on_close(self, ws, close_status_code, close_msg):
        print("❌ Level 1 connection closed")

# Usage
monitor = Level1Monitor()

# Add custom quote handler
def quote_handler(symbol, quote):
    # Custom trading logic
    if quote['spread_percent'] < 0.05:  # Very tight spread
        print(f"🚀 Potential arbitrage opportunity on {symbol}")

monitor.add_listener(quote_handler)
monitor.connect()

Use Cases

Performance Considerations

Error Handling

Common Level 1 stream errors and solutions:

Next Steps