Skip to main content

Overview

The ticker channel provides comprehensive real-time market statistics including prices, funding rates, and open interest for all supported trading pairs. This is the most complete market data stream, combining pricing data with market activity metrics.

Channel Information

PropertyValue
Channel Nameticker
AuthenticationRequired
Update FrequencyReal-time
Data TypeComprehensive market statistics

Subscribe to Ticker Data

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

Response Format

{
  "topic": "ticker.US500-BTC",
  "type": "snapshot",
  "createdTime": 1640995200000000000,
  "data": {
    "symbol": "US500-BTC",
    "lastPrice": "0.00432500",
    "indexPrice": "0.00432450",
    "markPrice": "0.00432475",
    "bestBid": "0.00432450",
    "bestAsk": "0.00432500",
    "bestBidQty": "125.50",
    "bestAskQty": "156.75",
    "openInterest": "500.00",
    "openInterestValue": "2.16250000",
    "fundingRate": "0.0001",
    "timestamp": 1640995200000000000,
    "unit": "btc"
  }
}
data
object
Comprehensive ticker data

Implementation Examples

JavaScript Ticker Monitor

class TickerMonitor {
  constructor() {
    this.tickers = {};
    this.listeners = [];
  }

  connect() {
    this.ws = new WebSocket('wss://ws.roxom.com/ws');
    
    this.ws.onopen = () => {
      console.log('Connected to Ticker 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('ticker.')) {
        this.handleTickerUpdate(data);
      }
    };
  }

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

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

  handleTickerUpdate(message) {
    const symbol = message.data.symbol;
    const ticker = message.data;

    // Update local ticker cache
    this.tickers[symbol] = {
      symbol: symbol,
      lastPrice: parseFloat(ticker.lastPrice || 0),
      indexPrice: parseFloat(ticker.indexPrice || 0),
      markPrice: parseFloat(ticker.markPrice || 0),
      bestBid: parseFloat(ticker.bestBid || 0),
      bestAsk: parseFloat(ticker.bestAsk || 0),
      bestBidQty: parseFloat(ticker.bestBidQty || 0),
      bestAskQty: parseFloat(ticker.bestAskQty || 0),
      openInterest: parseFloat(ticker.openInterest || 0),
      openInterestValue: parseFloat(ticker.openInterestValue || 0),
      fundingRate: parseFloat(ticker.fundingRate || 0),
      timestamp: ticker.timestamp,
      lastUpdated: Date.now()
    };

    // Calculate spread
    if (ticker.bestBid && ticker.bestAsk) {
      const spread = this.tickers[symbol].bestAsk - this.tickers[symbol].bestBid;
      const midPrice = (this.tickers[symbol].bestBid + this.tickers[symbol].bestAsk) / 2;
      this.tickers[symbol].spread = spread;
      this.tickers[symbol].spreadPercent = (spread / midPrice) * 100;
    }

    console.log(`${symbol} Ticker Update:`);
    console.log(`  Last: ${ticker.lastPrice} | Mark: ${ticker.markPrice} | Index: ${ticker.indexPrice}`);
    console.log(`  Bid: ${ticker.bestBid} (${ticker.bestBidQty}) | Ask: ${ticker.bestAsk} (${ticker.bestAskQty})`);
    console.log(`  OI: ${ticker.openInterest} contracts (${ticker.openInterestValue} BTC)`);
    console.log(`  Funding Rate: ${(parseFloat(ticker.fundingRate || 0) * 100).toFixed(4)}%`);

    // Notify listeners
    this.notifyListeners(symbol, this.tickers[symbol]);
  }

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

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

  getTicker(symbol) {
    return this.tickers[symbol] || null;
  }

  getAllTickers() {
    return { ...this.tickers };
  }
}

// Usage
const tickerMonitor = new TickerMonitor();

tickerMonitor.addListener((symbol, ticker) => {
  // Check for high funding rates
  if (Math.abs(ticker.fundingRate) > 0.001) {
    console.log(`⚠️  High funding rate on ${symbol}: ${(ticker.fundingRate * 100).toFixed(4)}%`);
  }
  
  // Check for large open interest
  if (ticker.openInterestValue > 100) {
    console.log(`📊 Large OI on ${symbol}: ${ticker.openInterestValue.toFixed(2)} BTC`);
  }
});

tickerMonitor.connect();

Python Ticker Tracker

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

class TickerTracker:
    def __init__(self):
        self.tickers: 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 Ticker stream")
        self.subscribe_to_symbols(['US500-BTC', 'GOLD-BTC', 'OIL-BTC'])
    
    def subscribe_to_symbols(self, symbols):
        subscribe_msg = {
            "op": "subscribe",
            "args": [{"channel": "ticker", "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('ticker.'):
                self.handle_ticker_update(data)
        except json.JSONDecodeError as e:
            print(f"❌ JSON decode error: {e}")
    
    def handle_ticker_update(self, message):
        ticker_data = message['data']
        symbol = ticker_data['symbol']
        
        # Parse ticker data with null handling
        last_price = float(ticker_data['lastPrice']) if ticker_data.get('lastPrice') else None
        index_price = float(ticker_data['indexPrice']) if ticker_data.get('indexPrice') else None
        mark_price = float(ticker_data['markPrice']) if ticker_data.get('markPrice') else None
        best_bid = float(ticker_data['bestBid']) if ticker_data.get('bestBid') else None
        best_ask = float(ticker_data['bestAsk']) if ticker_data.get('bestAsk') else None
        best_bid_qty = float(ticker_data['bestBidQty']) if ticker_data.get('bestBidQty') else None
        best_ask_qty = float(ticker_data['bestAskQty']) if ticker_data.get('bestAskQty') else None
        open_interest = float(ticker_data['openInterest']) if ticker_data.get('openInterest') else None
        open_interest_value = float(ticker_data['openInterestValue']) if ticker_data.get('openInterestValue') else None
        funding_rate = float(ticker_data['fundingRate']) if ticker_data.get('fundingRate') else None
        
        # Calculate derived values
        spread = None
        spread_percent = None
        if best_bid and best_ask:
            spread = best_ask - best_bid
            mid_price = (best_bid + best_ask) / 2
            spread_percent = (spread / mid_price) * 100 if mid_price > 0 else 0
        
        # Update ticker cache
        self.tickers[symbol] = {
            'symbol': symbol,
            'last_price': last_price,
            'index_price': index_price,
            'mark_price': mark_price,
            'best_bid': best_bid,
            'best_ask': best_ask,
            'best_bid_qty': best_bid_qty,
            'best_ask_qty': best_ask_qty,
            'open_interest': open_interest,
            'open_interest_value': open_interest_value,
            'funding_rate': funding_rate,
            'spread': spread,
            'spread_percent': spread_percent,
            'timestamp': ticker_data['timestamp'],
            'last_updated': datetime.now()
        }
        
        # Display ticker update
        print(f"\n{symbol} Ticker Update:")
        print(f"  Last: {last_price:.8f} | Mark: {mark_price:.8f} | Index: {index_price:.8f}")
        print(f"  Bid: {best_bid:.8f} ({best_bid_qty}) | Ask: {best_ask:.8f} ({best_ask_qty})")
        print(f"  OI: {open_interest} contracts ({open_interest_value:.8f} BTC)")
        print(f"  Funding Rate: {(funding_rate * 100):.4f}%")
        
        # Notify listeners
        self.notify_listeners(symbol, self.tickers[symbol])
    
    def add_listener(self, callback):
        self.listeners.append(callback)
    
    def notify_listeners(self, symbol, ticker):
        for callback in self.listeners:
            try:
                callback(symbol, ticker)
            except Exception as e:
                print(f"❌ Listener error: {e}")
    
    def get_ticker(self, symbol) -> Optional[Dict]:
        return self.tickers.get(symbol)
    
    def get_all_tickers(self) -> Dict:
        return self.tickers.copy()
    
    def on_error(self, ws, error):
        print(f"🚨 WebSocket error: {error}")
    
    def on_close(self, ws, close_status_code, close_msg):
        print("❌ Ticker connection closed")

# Usage
tracker = TickerTracker()

# Add custom ticker handler
def ticker_handler(symbol, ticker):
    # Alert on high funding rates
    if ticker['funding_rate'] and abs(ticker['funding_rate']) > 0.001:
        print(f"⚠️  High funding rate on {symbol}: {(ticker['funding_rate'] * 100):.4f}%")
    
    # Alert on large open interest
    if ticker['open_interest_value'] and ticker['open_interest_value'] > 100:
        print(f"📊 Large OI on {symbol}: {ticker['open_interest_value']:.2f} BTC")

tracker.add_listener(ticker_handler)
tracker.connect()

Use Cases

Display comprehensive market statistics in trading dashboards with real-time updates for all key metrics.
function updateMarketDashboard(symbol, ticker) {
  // Update price displays
  document.getElementById(`${symbol}-last`).textContent = ticker.lastPrice.toFixed(8);
  document.getElementById(`${symbol}-mark`).textContent = ticker.markPrice.toFixed(8);
  document.getElementById(`${symbol}-index`).textContent = ticker.indexPrice.toFixed(8);
  
  // Update orderbook top
  document.getElementById(`${symbol}-bid`).textContent = 
    `${ticker.bestBid.toFixed(8)} (${ticker.bestBidQty})`;
  document.getElementById(`${symbol}-ask`).textContent = 
    `${ticker.bestAsk.toFixed(8)} (${ticker.bestAskQty})`;
  
  // Update market stats
  document.getElementById(`${symbol}-oi`).textContent = ticker.openInterest;
  document.getElementById(`${symbol}-oi-value`).textContent = 
    `${ticker.openInterestValue.toFixed(4)} BTC`;
  document.getElementById(`${symbol}-funding`).textContent = 
    `${(ticker.fundingRate * 100).toFixed(4)}%`;
}
Monitor funding rates across symbols to identify arbitrage opportunities or funding rate trading strategies.
function monitorFundingRates(symbol, ticker) {
  const fundingRatePercent = ticker.fundingRate * 100;
  
  if (Math.abs(fundingRatePercent) > 0.1) {
    console.log(`⚠️  High funding rate: ${symbol} - ${fundingRatePercent.toFixed(4)}%`);
    
    // Potential arbitrage opportunity
    if (fundingRatePercent > 0.1) {
      console.log(`💡 Consider shorting ${symbol} to earn funding`);
    } else if (fundingRatePercent < -0.1) {
      console.log(`💡 Consider longing ${symbol} to earn funding`);
    }
  }
}
Track open interest changes to gauge market sentiment and potential price movements.
function trackOpenInterest(symbol, ticker) {
  const previousOI = this.previousOI[symbol] || 0;
  const currentOI = ticker.openInterestValue;
  const oiChange = currentOI - previousOI;
  const oiChangePercent = (oiChange / previousOI) * 100;
  
  if (Math.abs(oiChangePercent) > 5) {
    console.log(`📊 Significant OI change on ${symbol}:`);
    console.log(`   Previous: ${previousOI.toFixed(2)} BTC`);
    console.log(`   Current: ${currentOI.toFixed(2)} BTC`);
    console.log(`   Change: ${oiChangePercent.toFixed(2)}%`);
    
    if (oiChange > 0) {
      console.log(`   💹 Increasing OI - New positions being opened`);
    } else {
      console.log(`   📉 Decreasing OI - Positions being closed`);
    }
  }
  
  this.previousOI[symbol] = currentOI;
}
Monitor differences between mark price, index price, and last price to identify market inefficiencies.
function checkPriceDeviations(symbol, ticker) {
  if (!ticker.markPrice || !ticker.indexPrice || !ticker.lastPrice) return;
  
  const markIndexDiff = Math.abs(ticker.markPrice - ticker.indexPrice);
  const markIndexDiffPercent = (markIndexDiff / ticker.indexPrice) * 100;
  
  const lastMarkDiff = Math.abs(ticker.lastPrice - ticker.markPrice);
  const lastMarkDiffPercent = (lastMarkDiff / ticker.markPrice) * 100;
  
  if (markIndexDiffPercent > 0.5) {
    console.log(`⚠️  Large mark-index deviation on ${symbol}: ${markIndexDiffPercent.toFixed(3)}%`);
    console.log(`   Mark: ${ticker.markPrice.toFixed(8)}`);
    console.log(`   Index: ${ticker.indexPrice.toFixed(8)}`);
  }
  
  if (lastMarkDiffPercent > 1.0) {
    console.log(`⚠️  Large last-mark deviation on ${symbol}: ${lastMarkDiffPercent.toFixed(3)}%`);
    console.log(`   Last: ${ticker.lastPrice.toFixed(8)}`);
    console.log(`   Mark: ${ticker.markPrice.toFixed(8)}`);
  }
}

Performance Considerations

Ticker updates can be frequent during volatile market conditions. Consider throttling UI updates:
class ThrottledTickerHandler {
  constructor(throttleMs = 200) {
    this.throttleMs = throttleMs;
    this.pendingUpdates = {};
    this.updateTimer = null;
  }
  
  handleTickerUpdate(message) {
    const symbol = message.data.symbol;
    this.pendingUpdates[symbol] = message;
    
    if (!this.updateTimer) {
      this.updateTimer = setTimeout(() => {
        this.processPendingUpdates();
        this.updateTimer = null;
      }, this.throttleMs);
    }
  }
  
  processPendingUpdates() {
    Object.values(this.pendingUpdates).forEach(message => {
      this.processUpdate(message);
    });
    this.pendingUpdates = {};
  }
}
Ticker data fields can be null if data is not yet available. Always handle null values:
function safeParseTickerValue(value, defaultValue = 0) {
  if (value === null || value === undefined) {
    return defaultValue;
  }
  return parseFloat(value);
}

function handleTicker(ticker) {
  const lastPrice = safeParseTickerValue(ticker.lastPrice);
  const markPrice = safeParseTickerValue(ticker.markPrice);
  const fundingRate = safeParseTickerValue(ticker.fundingRate);
  
  // Use safely parsed values
  console.log(`Last: ${lastPrice}, Mark: ${markPrice}, Funding: ${fundingRate}`);
}

Data Fields Availability

Initial Connection: When first subscribing to a ticker channel, some fields may be null until the relevant data becomes available. This is normal behavior:
  • lastPrice: Available after the first trade
  • indexPrice: Available from price feed
  • markPrice: Available from price feed
  • bestBid/bestAsk: Available when orderbook has quotes
  • openInterest: Available from position tracking
  • fundingRate: Available for perpetual contracts
The ticker will send updates as new data becomes available for each field.

Error Handling

Common ticker stream errors and solutions:
Error: Subscription rejected for unknown trading pairSolution: Verify symbol format matches exactly: US500-BTC, GOLD-BTC, OIL-BTC, US100-BTC
Error: Unexpected null values in ticker dataSolution: Always handle null values gracefully as they indicate data not yet available
function displayTicker(ticker) {
  const lastPrice = ticker.lastPrice || 'N/A';
  const fundingRate = ticker.fundingRate ? 
    (parseFloat(ticker.fundingRate) * 100).toFixed(4) + '%' : 'N/A';
  
  console.log(`Last: ${lastPrice}, Funding: ${fundingRate}`);
}
Error: Ticker data not updatingSolution: Check connection health and implement staleness detection:
function checkTickerStaleness(ticker, maxAgeMs = 10000) {
  const age = Date.now() - ticker.lastUpdated;
  if (age > maxAgeMs) {
    console.warn(`Stale ticker data for ${ticker.symbol}: ${age}ms old`);
    return true;
  }
  return false;
}

Next Steps