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 milliseconds
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",
          "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",
          "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",
          "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
orderIdstringOrder that generated this trade
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)

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 orderId 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
ErrorDescription
Invalid signatureEIP-712 signature validation failed
Invalid market symbolMarket symbol not recognized
Nonce already usedNonce must be greater than previous value
Rate limit exceededToo many requests in time window
Request expiredexpiresAfter timestamp has passed

Next Steps