Moonbase Docs

Orders Channel

User-specific order updates and trade matches

Overview

The orders channel provides real-time updates about your orders and trade executions. You'll receive notifications when orders are created, filled, partially filled, cancelled, or when any state changes occur.

Channel: orders Authentication: Required Product: Not applicable (all products included)

Subscribe

You must authenticate before subscribing to the orders channel. See Authentication for details.

Request:

{
  "op": "sub",
  "channel": "orders"
}

Friendly format:

sub orders

Parameters:

  • op (string, required) - Must be "sub"
  • channel (string, required) - Must be "orders"

Note: No product parameter needed. You'll receive updates for all your orders across all products.

Responses

Subscription Confirmation

Upon successful subscription:

{
  "channel": "orders",
  "type": "subscribed"
}

Snapshot Message

Immediately after subscription, you'll receive a snapshot of all your non-done orders:

{
  "channel": "orders",
  "type": "snapshot",
  "data": [
    {
      "id": "0x01234...",
      "client_order_id": "my-order-123",
      "user_id": "1000004",
      "product_id": "BTC-VND",
      "side": "buy",
      "type": "limit",
      "time_in_force": "GTC",
      "post_only": false,
      "price": "3100000000",
      "size": "0.1",
      "filled_size": "0.05",
      "status": "open",
      "created_at": "1698765432000000000",
      "updated_at": "1698765433000000000",
      "trades": []
    }
  ],
  "timestamp": "1698765434000000000",
  "gsn": 12345678
}

Update Messages

After the snapshot, you'll receive updates when your orders change:

{
  "channel": "orders",
  "type": "update",
  "data": [
    {
      "id": "0x01234...",
      "client_order_id": "my-order-123",
      "user_id": "1000004",
      "product_id": "BTC-VND",
      "side": "buy",
      "type": "limit",
      "time_in_force": "GTC",
      "post_only": false,
      "price": "3100000000",
      "size": "0.1",
      "filled_size": "0.075",
      "status": "open",
      "created_at": "1698765432000000000",
      "updated_at": "1698765435000000000",
      "trades": [
        {
          "id": "0xabc...",
          "price": "3100000000",
          "size": "0.025",
          "liquidity_indicator": "MAKER",
          "time": "1698765435000000000",
          "fee_asset": "VND",
          "fee_amount": "7750"
        }
      ]
    }
  ],
  "timestamp": "1698765435000000000",
  "gsn": 12345679
}

Data Structure

Order Object

interface Order {
  id: string;                    // Unique order identifier
  client_order_id?: string;      // Your custom order ID
  user_id: string;               // Your user ID
  product_id: string;            // Trading pair
  side: "buy" | "sell";          // Order side
  type: "limit" | "market";      // Order type
  time_in_force: "GTC" | "IOC" | "FOK";  // Time in force
  post_only: boolean;            // Post-only flag
  price: string;                 // Order price
  size: string;                  // Order size
  filled_size: string;           // Amount filled
  status: OrderStatus;           // Current status
  created_at: string;            // Creation timestamp (nanoseconds)
  updated_at: string;            // Last update timestamp (nanoseconds)
  cancel_requested_at?: string;  // Cancel request timestamp
  trades: Trade[];               // Associated trades
}

type OrderStatus =
  | "pending"     // Order received, not yet processed
  | "accepted"    // Order accepted by matching engine
  | "open"        // Active order on orderbook
  | "filled"      // Completely filled
  | "cancelled"   // Cancelled
  | "rejected";   // Rejected

interface Trade {
  id: string;                           // Trade (match) ID
  price: string;                        // Execution price
  size: string;                         // Execution size
  liquidity_indicator: "MAKER" | "TAKER";  // Your role
  time: string;                         // Execution time (nanoseconds)
  fee_asset: string;                    // Fee currency
  fee_amount: string;                   // Fee charged
}

Order Lifecycle Events

1. Order Created

When you create a new order:

{
  "channel": "orders",
  "type": "update",
  "data": [{
    "id": "0x01234...",
    "status": "pending",
    "filled_size": "0",
    "trades": []
  }],
  "gsn": 12345680
}

2. Order Accepted

When the matching engine accepts your order:

{
  "channel": "orders",
  "type": "update",
  "data": [{
    "id": "0x01234...",
    "status": "open",
    "filled_size": "0",
    "trades": []
  }],
  "gsn": 12345681
}

3. Partial Fill

When your order is partially filled:

{
  "channel": "orders",
  "type": "update",
  "data": [{
    "id": "0x01234...",
    "status": "open",
    "size": "0.1",
    "filled_size": "0.03",
    "trades": [
      {
        "id": "0xabc...",
        "price": "3100000000",
        "size": "0.03",
        "liquidity_indicator": "MAKER",
        "time": "1698765435000000000",
        "fee_asset": "VND",
        "fee_amount": "9300"
      }
    ]
  }],
  "gsn": 12345682
}

4. Complete Fill

When your order is completely filled:

{
  "channel": "orders",
  "type": "update",
  "data": [{
    "id": "0x01234...",
    "status": "filled",
    "size": "0.1",
    "filled_size": "0.1",
    "trades": [...]
  }],
  "gsn": 12345683
}

5. Order Cancelled

When your order is cancelled:

{
  "channel": "orders",
  "type": "update",
  "data": [{
    "id": "0x01234...",
    "status": "cancelled",
    "cancel_requested_at": "1698765440000000000"
  }],
  "gsn": 12345684
}

Example: Order Tracker

const WebSocket = require('ws');

class OrderTracker {
  constructor() {
    this.orders = new Map();
  }

  handleMessage(message) {
    if (message.type === 'snapshot') {
      // Initialize with snapshot
      message.data.forEach(order => {
        this.orders.set(order.id, order);
      });
      console.log(`Loaded ${this.orders.size} active orders`);
    } else if (message.type === 'update') {
      // Process updates
      message.data.forEach(order => {
        const existing = this.orders.get(order.id);

        if (order.status === 'filled' || order.status === 'cancelled') {
          // Remove done orders
          this.orders.delete(order.id);
          this.logOrderComplete(order);
        } else {
          // Update or add order
          this.orders.set(order.id, order);
          if (existing) {
            this.logOrderUpdate(existing, order);
          } else {
            this.logOrderCreated(order);
          }
        }
      });
    }
  }

  logOrderCreated(order) {
    console.log(`📝 New Order: ${order.side.toUpperCase()} ${order.size} ${order.product_id} @ ${order.price}`);
    console.log(`   ID: ${order.id}`);
  }

  logOrderUpdate(oldOrder, newOrder) {
    if (newOrder.filled_size !== oldOrder.filled_size) {
      const fillSize = parseFloat(newOrder.filled_size) - parseFloat(oldOrder.filled_size);
      console.log(`✅ Fill: ${fillSize} ${newOrder.product_id} @ ${newOrder.trades[newOrder.trades.length - 1].price}`);
      console.log(`   Order ID: ${newOrder.id}`);
      console.log(`   Progress: ${newOrder.filled_size}/${newOrder.size}`);
    }
  }

  logOrderComplete(order) {
    if (order.status === 'filled') {
      console.log(`✅ Order Filled: ${order.id}`);
      console.log(`   ${order.side.toUpperCase()} ${order.filled_size} ${order.product_id}`);

      // Calculate total fees
      const totalFees = order.trades.reduce((sum, trade) => {
        return sum + parseFloat(trade.fee_amount);
      }, 0);
      console.log(`   Total Fees: ${totalFees} ${order.trades[0]?.fee_asset || 'VND'}`);
    } else if (order.status === 'cancelled') {
      console.log(`❌ Order Cancelled: ${order.id}`);
      console.log(`   Filled: ${order.filled_size}/${order.size}`);
    }
  }

  getActiveOrders() {
    return Array.from(this.orders.values());
  }

  getOrdersByProduct(productId) {
    return this.getActiveOrders().filter(o => o.product_id === productId);
  }
}

// Usage
const tracker = new OrderTracker();

ws.on('open', async () => {
  // Authenticate first
  await authenticate(ws);

  // Subscribe to orders
  ws.send(JSON.stringify({
    op: 'sub',
    channel: 'orders'
  }));
});

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

  if (message.channel === 'orders') {
    tracker.handleMessage(message);

    // Display active orders
    const active = tracker.getActiveOrders();
    console.log(`\nActive Orders: ${active.length}`);
  }
});

Unsubscribe

To stop receiving order updates:

Request:

{
  "op": "unsub",
  "channel": "orders"
}

Response:

{
  "type": "unsubscribed",
  "channel": "orders"
}

Important Notes

  1. Authentication Required - You must authenticate before subscribing
  2. All Products - The channel includes orders from all trading pairs
  3. Done Orders Excluded - Snapshot only includes non-done orders (pending, accepted, open)
  4. GSN Tracking - Use Global Sequence Number (GSN) to ensure message ordering
  5. Trades Array - Only includes trades from the current update, not all historical trades
  6. Nanosecond Timestamps - All timestamps are in nanoseconds since Unix epoch

Use Cases

  1. Order Management UI - Display user's active orders
  2. Trade Notifications - Alert users when orders are filled
  3. Position Tracking - Monitor order fills to track positions
  4. Execution Analytics - Analyze fill prices and fees
  5. Automated Trading - React to order state changes programmatically

Best Practices

  1. Maintain State - Keep a local map of active orders
  2. Handle Reconnections - Re-subscribe and process snapshot after reconnecting
  3. Use GSN - Track GSN to detect missed messages
  4. Filter by Product - Filter orders client-side if you only care about specific products
  5. Calculate PnL - Use trade data to calculate profit/loss and fees