Moonbase Docs

Trades Channel

Real-time trade execution data

Overview

The trades channel provides real-time updates of trade executions for a specific trading product. Each message contains information about a completed trade, including price, size, side, and timestamp.

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

Subscribe

Request:

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

Friendly format:

sub trades BTC-VND

Parameters:

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

Responses

Subscription Confirmation

Upon successful subscription:

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

Trade Updates

Each trade execution generates an update message:

{
  "channel": "trades",
  "product": "BTC-VND",
  "type": "update",
  "data": {
    "price": "3124660000",
    "size": "0.003",
    "maker_side": "buy",
    "id": "0x0198aca869...",
    "time": "1755243387119627000"
  }
}

Data Structure

interface TradeData {
  price: string;       // Execution price
  size: string;        // Trade size (in base currency)
  maker_side: "buy" | "sell";  // Maker order side
  id: string;          // Unique trade identifier
  time: string;        // Execution time (nanoseconds since epoch)
}

Field Descriptions:

  • price - The price at which the trade was executed (in quote currency, VND)
  • size - The quantity traded (in base currency, e.g., BTC)
  • maker_side - The side of the maker order
    • "buy" - Maker was buying (taker was selling)
    • "sell" - Maker was selling (taker was buying)
  • id - Unique identifier for this trade (match ID)
  • time - Unix timestamp in nanoseconds when the trade occurred

Understanding Maker Side

The maker_side field indicates which side placed the resting order (maker):

maker_sideMaker ActionTaker ActionMarket Direction
buyPlaced buy orderPlaced sell orderPrice supported at bid
sellPlaced sell orderPlaced buy orderPrice pushed at ask

Example: Processing Trades

const WebSocket = require('ws');

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

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

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

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

  if (message.channel === 'trades') {
    if (message.type === 'subscribed') {
      console.log('Subscribed to trades');
    } else if (message.type === 'update') {
      const trade = message.data;

      // Convert nanosecond timestamp to Date
      const timestamp = new Date(parseInt(trade.time) / 1000000);

      console.log(`Trade: ${trade.size} BTC @ ${trade.price} VND`);
      console.log(`  Maker Side: ${trade.maker_side}`);
      console.log(`  Time: ${timestamp.toISOString()}`);
      console.log(`  ID: ${trade.id}`);

      // Store trade for analysis
      trades.push({
        ...trade,
        timestamp: timestamp
      });

      // Calculate volume metrics
      if (trades.length >= 10) {
        const recentVolume = trades.slice(-10).reduce(
          (sum, t) => sum + parseFloat(t.size),
          0
        );
        console.log(`Last 10 trades volume: ${recentVolume.toFixed(4)} BTC`);
      }
    }
  }
});

Example: Building a Trade Ticker

class TradeTicker {
  constructor(windowMs = 60000) { // 1 minute window
    this.trades = [];
    this.windowMs = windowMs;
  }

  addTrade(trade) {
    const timestamp = parseInt(trade.time) / 1000000; // Convert to ms
    this.trades.push({
      price: parseFloat(trade.price),
      size: parseFloat(trade.size),
      timestamp: timestamp,
      side: trade.maker_side
    });

    // Remove trades outside the time window
    const cutoff = Date.now() - this.windowMs;
    this.trades = this.trades.filter(t => t.timestamp >= cutoff);
  }

  getStats() {
    if (this.trades.length === 0) return null;

    const prices = this.trades.map(t => t.price);
    const volumes = this.trades.map(t => t.size);

    return {
      count: this.trades.length,
      volume: volumes.reduce((a, b) => a + b, 0),
      high: Math.max(...prices),
      low: Math.min(...prices),
      last: prices[prices.length - 1],
      vwap: this.trades.reduce((sum, t) => sum + t.price * t.size, 0) /
            volumes.reduce((a, b) => a + b, 0),
      buyVolume: this.trades
        .filter(t => t.side === 'sell') // Taker bought
        .reduce((sum, t) => sum + t.size, 0),
      sellVolume: this.trades
        .filter(t => t.side === 'buy') // Taker sold
        .reduce((sum, t) => sum + t.size, 0)
    };
  }
}

// Usage
const ticker = new TradeTicker(60000); // 1 minute window

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

  if (message.channel === 'trades' && message.type === 'update') {
    ticker.addTrade(message.data);

    const stats = ticker.getStats();
    if (stats) {
      console.log('1-Minute Stats:');
      console.log(`  Trades: ${stats.count}`);
      console.log(`  Volume: ${stats.volume.toFixed(4)} BTC`);
      console.log(`  High: ${stats.high.toFixed(0)} VND`);
      console.log(`  Low: ${stats.low.toFixed(0)} VND`);
      console.log(`  VWAP: ${stats.vwap.toFixed(0)} VND`);
      console.log(`  Buy/Sell Ratio: ${(stats.buyVolume / stats.sellVolume).toFixed(2)}`);
    }
  }
});

Unsubscribe

To stop receiving trade updates:

Request:

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

Friendly format:

unsub trades BTC-VND

Response:

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

Use Cases

  1. Price Feeds - Display real-time trade prices in your application
  2. Volume Analysis - Monitor trading volume and market activity
  3. Trade History - Build a recent trades list
  4. Market Sentiment - Analyze buy/sell pressure using maker_side
  5. VWAP Calculation - Calculate volume-weighted average price
  6. Alerts - Trigger notifications on large trades or price movements

Best Practices

  1. Use Timestamps - Trade timestamps are in nanoseconds; convert appropriately
  2. Handle High Frequency - During volatile periods, trades can arrive rapidly
  3. Buffer Messages - Consider buffering and batching UI updates
  4. Track Volume - Monitor both price and volume for complete market picture
  5. Combine Channels - Use with orderbook channel for comprehensive market data

Time Conversion

Trade timestamps are in nanoseconds since Unix epoch:

// Convert nanosecond timestamp to milliseconds
const timestampMs = parseInt(trade.time) / 1000000;

// Create Date object
const date = new Date(timestampMs);

// Format for display
const formatted = date.toISOString(); // "2023-06-15T14:30:45.123Z"