Moonbase Docs

Ticker Channel

Best bid/ask price updates

Overview

The ticker channel provides real-time updates of the best bid and ask prices for a specific trading product. This is a lightweight alternative to the orderbook channel when you only need top-of-book data.

Channel: ticker Authentication: Not required (Public) Products: BTC-VND, ETH-VND, SOL-VND, XRP-VND

Subscribe

Request:

{
  "op": "sub",
  "channel": "ticker",
  "product": "BTC-VND"
}

Friendly format:

sub ticker BTC-VND

Parameters:

  • op (string, required) - Must be "sub"
  • channel (string, required) - Must be "ticker"
  • product (string, required) - Trading pair (e.g., "BTC-VND")

Responses

Subscription Confirmation

Upon successful subscription:

{
  "channel": "ticker",
  "product": "BTC-VND",
  "type": "subscribed"
}

Snapshot Message

Immediately after subscription, you'll receive the current best bid and ask:

{
  "channel": "ticker",
  "product": "BTC-VND",
  "type": "snapshot",
  "data": {
    "bid": {
      "price": "3123760000",
      "size": "0.033"
    },
    "ask": {
      "price": "3124680000",
      "size": "0.034"
    }
  },
  "timestamp": "1755243387119627000"
}

Update Messages

After the snapshot, you'll receive updates whenever the best bid or ask changes:

{
  "channel": "ticker",
  "product": "BTC-VND",
  "type": "update",
  "data": {
    "bid": {
      "price": "3123470000",
      "size": "0.101"
    },
    "ask": {
      "price": "3124680000",
      "size": "0.034"
    }
  },
  "timestamp": "1755243388119627000"
}

Data Structure

interface TickerData {
  bid: {
    price: string;  // Best bid price
    size: string;   // Size at best bid
  };
  ask: {
    price: string;  // Best ask price
    size: string;   // Size at best ask
  };
}

interface TickerMessage {
  channel: "ticker";
  product: string;
  type: "snapshot" | "update";
  data: TickerData;
  timestamp: string;  // Nanoseconds since epoch
}

Field Descriptions:

  • bid.price - Highest buy order price (in quote currency, VND)
  • bid.size - Total quantity available at the best bid
  • ask.price - Lowest sell order price (in quote currency, VND)
  • ask.size - Total quantity available at the best ask
  • timestamp - Time of the update (nanoseconds since Unix epoch)

Example: Processing Ticker Updates

const WebSocket = require('ws');

const ws = new WebSocket('wss://ws.dev.mbhq.net/ws');

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

  // Subscribe to BTC-VND ticker
  ws.send(JSON.stringify({
    op: 'sub',
    channel: 'ticker',
    product: 'BTC-VND'
  }));
});

ws.on('message', (data) => {
  const message = JSON.parse(data);

  if (message.channel === 'ticker') {
    if (message.type === 'subscribed') {
      console.log('Subscribed to ticker');
    } else if (message.type === 'snapshot' || message.type === 'update') {
      const { bid, ask } = message.data;

      const bidPrice = parseFloat(bid.price);
      const askPrice = parseFloat(ask.price);
      const spread = askPrice - bidPrice;
      const spreadPercent = (spread / bidPrice) * 100;
      const midPrice = (bidPrice + askPrice) / 2;

      console.log('=== BTC-VND Ticker ===');
      console.log(`Bid: ${bid.price} VND (${bid.size} BTC)`);
      console.log(`Ask: ${ask.price} VND (${ask.size} BTC)`);
      console.log(`Spread: ${spread.toFixed(0)} VND (${spreadPercent.toFixed(3)}%)`);
      console.log(`Mid Price: ${midPrice.toFixed(0)} VND`);

      // Convert timestamp
      const time = new Date(parseInt(message.timestamp) / 1000000);
      console.log(`Time: ${time.toISOString()}`);
    }
  }
});

Example: Price Alert System

class PriceAlert {
  constructor(product, targetPrice, condition) {
    this.product = product;
    this.targetPrice = targetPrice;
    this.condition = condition; // 'above' or 'below'
    this.triggered = false;
  }

  check(tickerData) {
    const midPrice = (parseFloat(tickerData.bid.price) +
                     parseFloat(tickerData.ask.price)) / 2;

    if (this.triggered) return false;

    if (this.condition === 'above' && midPrice >= this.targetPrice) {
      this.triggered = true;
      return true;
    }

    if (this.condition === 'below' && midPrice <= this.targetPrice) {
      this.triggered = true;
      return true;
    }

    return false;
  }
}

// Usage
const alerts = [
  new PriceAlert('BTC-VND', 3200000000, 'above'),
  new PriceAlert('BTC-VND', 3000000000, 'below')
];

ws.on('message', (data) => {
  const message = JSON.parse(data);

  if (message.channel === 'ticker' && message.type === 'update') {
    alerts.forEach((alert, index) => {
      if (alert.check(message.data)) {
        console.log(`🚨 ALERT ${index + 1} TRIGGERED!`);
        console.log(`${alert.product} price ${alert.condition} ${alert.targetPrice}`);
        // Send notification, email, etc.
      }
    });
  }
});

Example: Multi-Product Ticker Dashboard

class TickerDashboard {
  constructor() {
    this.tickers = new Map();
  }

  update(product, data) {
    const bidPrice = parseFloat(data.bid.price);
    const askPrice = parseFloat(data.ask.price);

    this.tickers.set(product, {
      bid: bidPrice,
      ask: askPrice,
      spread: askPrice - bidPrice,
      spreadPercent: ((askPrice - bidPrice) / bidPrice) * 100,
      midPrice: (bidPrice + askPrice) / 2,
      bidSize: parseFloat(data.bid.size),
      askSize: parseFloat(data.ask.size)
    });
  }

  display() {
    console.clear();
    console.log('='.repeat(80));
    console.log('MOONBASE TICKER DASHBOARD');
    console.log('='.repeat(80));
    console.log('Product    | Bid           | Ask           | Spread      | Mid Price');
    console.log('-'.repeat(80));

    this.tickers.forEach((ticker, product) => {
      console.log(
        `${product.padEnd(10)} | ` +
        `${ticker.bid.toFixed(0).padStart(13)} | ` +
        `${ticker.ask.toFixed(0).padStart(13)} | ` +
        `${ticker.spreadPercent.toFixed(3)}%    | ` +
        `${ticker.midPrice.toFixed(0)}`
      );
    });
  }
}

// Connect to multiple products
const dashboard = new TickerDashboard();
const products = ['BTC-VND', 'ETH-VND', 'SOL-VND', 'XRP-VND'];

ws.on('open', () => {
  products.forEach(product => {
    ws.send(JSON.stringify({
      op: 'sub',
      channel: 'ticker',
      product: product
    }));
  });
});

ws.on('message', (data) => {
  const message = JSON.parse(data);

  if (message.channel === 'ticker' &&
      (message.type === 'snapshot' || message.type === 'update')) {
    dashboard.update(message.product, message.data);
    dashboard.display();
  }
});

Unsubscribe

To stop receiving ticker updates:

Request:

{
  "op": "unsub",
  "channel": "ticker",
  "product": "BTC-VND"
}

Friendly format:

unsub ticker BTC-VND

Response:

{
  "type": "unsubscribed",
  "channel": "ticker",
  "product": "BTC-VND"
}

Ticker vs. Orderbook

FeatureTicker ChannelOrderbook Channel
DataBest bid/ask onlyFull order depth
Message frequencyLowerHigher
BandwidthMinimalHigher
Use casePrice displayOrder execution, market making
LatencyVery lowLow

When to use Ticker:

  • Displaying current price in UI
  • Price alerts and notifications
  • Multi-product dashboards
  • Lower bandwidth requirements

When to use Orderbook:

  • Market making strategies
  • Analyzing market depth
  • Visualizing orderbook
  • Precise order placement

Use Cases

  1. Price Display - Show current market price in your application
  2. Spread Monitoring - Track bid-ask spread for trading opportunities
  3. Multi-Product Dashboard - Monitor multiple markets efficiently
  4. Price Alerts - Trigger alerts based on price thresholds
  5. Quick Quote - Get instant price quotes without full orderbook

Best Practices

  1. Lightweight - Use ticker instead of orderbook when you only need best prices
  2. Calculate Mid Price - Use (bid + ask) / 2 for a fair market price
  3. Monitor Spread - Large spreads may indicate low liquidity
  4. Combine with Trades - Use ticker for price, trades for actual executions
  5. Efficient Updates - Ticker updates only when top of book changes