Skip to content

Get Trades (WebSocket)

Retrieve trade execution history (fills) for the authenticated subaccount through the WebSocket connection. Each trade represents a filled order or partial fill that has been executed.

Request-Response vs Subscriptions

This method provides on-demand snapshots of trade history. For real-time updates when new trades execute, use SubAccount Updates Subscription.

MethodPurposeWhen to Use
getTradesHistorical trades snapshotPerformance analysis, reconciliation, trade history
Trade Updates SubscriptionReal-time notificationsLive UI updates, trade event handling

Endpoint

ws.send() wss://api.synthetix.io/v1/ws/trade

Request

Request Format

{
  "id": "trades-1",
  "method": "post",
  "params": {
    "action": "getTrades",
    "subAccountId": "1867542890123456789",
    "symbol": "BTC-USDT",
    "limit": 100,
    "offset": 0,
    "startTime": 1730678400000,
    "endTime": 1730764800000,
    "expiresAfter": 1704067500,
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}

Parameters

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "post"
paramsobjectYesContains all parameters for the request

Params Object

ParameterTypeRequiredDescription
actionstringYesMust be "getTrades"
subAccountIdstringYesSubaccount identifier
symbolstringNoFilter trades by market symbol (e.g., "BTC-USDT")
limitintegerNoMaximum number of trades to return (default: 100, max: 1000)
offsetintegerNoNumber of trades to skip for pagination (default: 0)
startTimeintegerNoStart timestamp in milliseconds (inclusive)
endTimeintegerNoEnd timestamp in milliseconds (inclusive)
expiresAfterintegerNoOptional expiration timestamp in Unix seconds (use 0 for no expiration)
signatureobjectYesEIP-712 signature using SubAccountAction type
Important Notes:
  • This endpoint uses SubAccountAction EIP-712 type (no nonce required, only expiresAfter is optional)
  • Time range cannot exceed 30 days (2,592,000,000 milliseconds)
  • Trades are returned in descending order by timestamp (newest first)

EIP-712 Signature

const domain = {
  name: "Synthetix",
  version: "1",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000000"
};
 
const types = {
  SubAccountAction: [
    { name: "subAccountId", type: "uint256" },
    { name: "action", type: "string" },
    { name: "expiresAfter", type: "uint256" }
  ]
};
 
const message = {
  subAccountId: "1867542890123456789",
  action: "getTrades",
  expiresAfter: 0
};
 
const signature = await signer._signTypedData(domain, types, message);

Response Format

Success Response

{
  "id": "trades-1",
  "status": 200,
  "result": {
    "status": "success",
    "response": {
      "trades": [
        {
          "tradeId": "123456789",
          "order": {
            "venueId": "1948058938469519360",
            "clientId": "cli-1948058938469519360"
          },
          "orderId": "1948058938469519360",
          "symbol": "BTC-USDT",
          "side": "buy",
          "direction": "open long",
          "price": "50000.50",
          "quantity": "0.1",
          "realizedPnl": "125.50",
          "fee": "5.00",
          "feeRate": "0.001",
          "markPrice": "50025.00",
          "entryPrice": "49500.00",
          "timestamp": 1704067200500,
          "maker": false,
          "reduceOnly": false,
          "triggeredByLiquidation": false,
          "postOnly": false
        },
        {
          "tradeId": "123456790",
          "order": {
            "venueId": "1948058938469519361",
            "clientId": "cli-1948058938469519361"
          },
          "orderId": "1948058938469519361",
          "symbol": "ETH-USDT",
          "side": "sell",
          "direction": "close long",
          "price": "2999.25",
          "quantity": "1.5",
          "realizedPnl": "-75.25",
          "fee": "4.50",
          "feeRate": "0.001",
          "markPrice": "3000.00",
          "entryPrice": "2950.00",
          "timestamp": 1704067201000,
          "maker": true,
          "reduceOnly": true,
          "triggeredByLiquidation": false,
          "postOnly": false
        },
        {
          "tradeId": "123456791",
          "order": {
            "venueId": "1948058938469519362",
            "clientId": "cli-1948058938469519362"
          },
          "orderId": "1948058938469519362",
          "symbol": "BTC-USDT",
          "side": "sell",
          "direction": "open short",
          "price": "49500.00",
          "quantity": "0.05",
          "realizedPnl": "0.00",
          "fee": "2.48",
          "feeRate": "0.001",
          "markPrice": "49450.00",
          "entryPrice": "50000.00",
          "timestamp": 1704067201500,
          "maker": false,
          "reduceOnly": false,
          "triggeredByLiquidation": true,
          "postOnly": false
        }
      ],
      "hasMore": false,
      "total": 3
    }
  }
}

Empty Result

{
  "id": "trades-1",
  "status": 200,
  "result": {
    "status": "success",
    "response": {
      "trades": [],
      "hasMore": false,
      "total": 0
    }
  }
}

Error Response

{
  "id": "trades-1",
  "status": 400,
  "result": null,
  "error": {
    "code": 400,
    "message": "Invalid time range: startTime must be less than or equal to endTime"
  }
}

Response Fields

FieldTypeDescription
idstringEchoed request identifier
statusintegerHTTP-style status code
result.statusstring"success" on success
result.response.tradesarrayArray of trade objects
result.response.hasMorebooleanWhether more trades exist beyond current result
result.response.totalintegerTotal number of trades matching the query

Trade Object Fields

FieldTypeDescription
tradeIdstringUnique trade identifier
order.venueIdstringCanonical venue-generated order ID
order.clientIdstringOptional client-provided order ID
orderIdstringDeprecated legacy order ID
symbolstringTrading pair symbol (e.g., "BTC-USDT")
sidestringTrade side: "buy" or "sell"
directionstringPosition effect (e.g., "open long", "close long", "open short", "close short")
pricestringExecution price
quantitystringFilled quantity for this trade
realizedPnlstringProfit/loss realized from this trade
feestringTrading fee charged
feeRatestringFee rate applied (maker/taker rate)
markPricestringMark price at time of trade execution
entryPricestringPosition's average entry price at time of trade
timestampintegerTrade execution timestamp (Unix milliseconds)
makerbooleanWhether this trade provided liquidity (maker) or took liquidity (taker)
reduceOnlybooleanWhether this trade reduced an existing position
triggeredByLiquidationbooleanWhether this trade was part of a liquidation
postOnlybooleanWhether this order was post-only (maker-only)

Migration Note: Use order.venueId as canonical order reference. orderId remains for backward compatibility.

Code Examples

Get Recent Trades

{
  "id": "trades-recent",
  "method": "post",
  "params": {
    "action": "getTrades",
    "subAccountId": "1867542890123456789",
    "limit": 50,
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Filter by Symbol

{
  "id": "trades-btc",
  "method": "post",
  "params": {
    "action": "getTrades",
    "subAccountId": "1867542890123456789",
    "symbol": "BTC-USDT",
    "limit": 100,
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Filter by Time Range

{
  "id": "trades-timerange",
  "method": "post",
  "params": {
    "action": "getTrades",
    "subAccountId": "1867542890123456789",
    "startTime": 1730678400000,
    "endTime": 1730764800000,
    "limit": 100,
    "offset": 0,
    "expiresAfter": 0,
    "signature": { "v": 28, "r": "0x...", "s": "0x..." }
  }
}

Implementation Example

class TradeHistoryQuery {
  constructor(ws) {
    this.ws = ws;
    this.pendingRequests = new Map();
  }
 
  async getTrades(options = {}) {
    const {
      subAccountId,
      symbol = null,
      limit = 100,
      offset = 0,
      startTime = null,
      endTime = null,
      expiresAfter = 0,
      signature
    } = options;
 
    const request = {
      id: `trades-${Date.now()}`,
      method: "post",
      params: {
        action: "getTrades",
        subAccountId: subAccountId.toString(),
        expiresAfter,
        signature
      }
    };
 
    // Add optional filters
    if (symbol) request.params.symbol = symbol;
    if (limit) request.params.limit = limit;
    if (offset) request.params.offset = offset;
    if (startTime) request.params.startTime = startTime;
    if (endTime) request.params.endTime = endTime;
 
    return this.sendRequest(request);
  }
 
  async getAllTrades(options) {
    const allTrades = [];
    let offset = 0;
    const limit = options.limit || 100;
 
    while (true) {
      const response = await this.getTrades({
        ...options,
        limit,
        offset
      });
 
      if (response.trades) {
        allTrades.push(...response.trades);
      }
 
      if (!response.hasMore || response.trades.length === 0) {
        break;
      }
 
      offset += limit;
    }
 
    return allTrades;
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(request.id, { resolve, reject });
      this.ws.send(JSON.stringify(request));
 
      setTimeout(() => {
        if (this.pendingRequests.has(request.id)) {
          this.pendingRequests.delete(request.id);
          reject(new Error('Request timeout'));
        }
      }, 30000);
    });
  }
 
  handleResponse(response) {
    const pending = this.pendingRequests.get(response.id);
    if (pending) {
      this.pendingRequests.delete(response.id);
      if (response.error) {
        pending.reject(new Error(response.error.message));
      } else {
        pending.resolve(response.result.response);
      }
    }
  }
}
 
// Usage
const query = new TradeHistoryQuery(ws);
 
// Get last 7 days of BTC trades
const endTime = Date.now();
const startTime = endTime - (7 * 24 * 60 * 60 * 1000);
 
const trades = await query.getTrades({
  subAccountId: "1867542890123456789",
  symbol: "BTC-USDT",
  startTime,
  endTime,
  signature: { v: 28, r: "0x...", s: "0x..." }
});
 
console.log(`Found ${trades.total} trades`);
trades.trades.forEach(trade => {
  console.log(`${trade.side} ${trade.quantity} @ ${trade.price} - PnL: ${trade.realizedPnl}`);
});

Trade Analysis Example

function analyzeTradeHistory(trades) {
  const stats = {
    totalTrades: trades.length,
    totalVolume: 0,
    totalFees: 0,
    totalPnl: 0,
    makerTrades: 0,
    takerTrades: 0,
    bySymbol: {}
  };
 
  for (const trade of trades) {
    const volume = parseFloat(trade.quantity) * parseFloat(trade.price);
    const fee = parseFloat(trade.fee);
    const pnl = parseFloat(trade.realizedPnl);
 
    stats.totalVolume += volume;
    stats.totalFees += fee;
    stats.totalPnl += pnl;
 
    if (trade.maker) {
      stats.makerTrades++;
    } else {
      stats.takerTrades++;
    }
 
    // Group by symbol
    if (!stats.bySymbol[trade.symbol]) {
      stats.bySymbol[trade.symbol] = {
        trades: 0,
        volume: 0,
        fees: 0,
        pnl: 0
      };
    }
    stats.bySymbol[trade.symbol].trades++;
    stats.bySymbol[trade.symbol].volume += volume;
    stats.bySymbol[trade.symbol].fees += fee;
    stats.bySymbol[trade.symbol].pnl += pnl;
  }
 
  return stats;
}
 
// Usage
const allTrades = await query.getAllTrades({
  subAccountId: "1867542890123456789",
  startTime: Date.now() - (30 * 24 * 60 * 60 * 1000),
  endTime: Date.now(),
  signature: { /* ... */ }
});
 
const stats = analyzeTradeHistory(allTrades);
console.log(`Total Volume: ${stats.totalVolume.toFixed(2)}`);
console.log(`Total Fees: ${stats.totalFees.toFixed(2)}`);
console.log(`Total PnL: ${stats.totalPnl.toFixed(2)}`);
console.log(`Maker/Taker: ${stats.makerTrades}/${stats.takerTrades}`);

Trade Types

Regular Trades

Standard trades from limit or market orders:

  • Contains standard execution information
  • May be partial fills (group by order.venueId to see all fills for the same order)

Liquidation Trades

Forced trades due to insufficient margin:

  • Identified by triggeredByLiquidation: true
  • Otherwise contains the same fields as regular trades

Maker vs Taker

  • Maker: Order added liquidity to the orderbook (maker: true)
    • Typically receives fee rebate or reduced fees
  • Taker: Order removed liquidity from the orderbook (maker: false)
    • Typically pays higher fees

Validation Rules

  • Subaccount ID must be valid and accessible
  • Limit must be between 1 and 1000 (default: 100)
  • Offset must be non-negative (default: 0)
  • Symbol must be a valid trading pair (if specified)
  • Time range cannot exceed 30 days
  • startTime must be less than or equal to endTime

Common Errors

ErrorDescriptionSolution
Invalid time rangeStart time is after end time, or time range exceeds 30 daysEnsure startTime < endTime and range ≤ 30 days
Invalid limitLimit exceeds maximumUse limit ≤ 1000
Invalid offsetNegative offset valueUse offset ≥ 0
Invalid symbolMarket symbol not recognizedCheck available markets
Authentication failedInvalid signatureVerify signature generation
Error CodeDescriptionRetryable
UNAUTHORIZEDEIP-712 signature validation failedNo
VALIDATION_ERRORRequest validation failedNo
MISSING_REQUIRED_FIELDRequired field is missingNo
INVALID_FORMATField format is invalidNo
INVALID_VALUEInvalid parameter valueNo
RATE_LIMIT_EXCEEDEDToo many requests in time windowYes
INSUFFICIENT_MARGINNot enough margin for tradeNo
ORDER_NOT_FOUNDOrder does not existNo
OPERATION_TIMEOUTOperation timed outYes

Next Steps