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.
| Method | Purpose | When to Use |
|---|---|---|
getTrades | Historical trades snapshot | Performance analysis, reconciliation, trade history |
| Trade Updates Subscription | Real-time notifications | Live UI updates, trade event handling |
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
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
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "getTrades" |
subAccountId | string | Yes | Subaccount identifier |
symbol | string | No | Filter trades by market symbol (e.g., "BTC-USDT") |
limit | integer | No | Maximum number of trades to return (default: 100, max: 1000) |
offset | integer | No | Number of trades to skip for pagination (default: 0) |
startTime | integer | No | Start timestamp in milliseconds (inclusive) |
endTime | integer | No | End timestamp in milliseconds (inclusive) |
expiresAfter | integer | No | Optional expiration timestamp in milliseconds |
signature | object | Yes | EIP-712 signature using SubAccountAction type |
- This endpoint uses
SubAccountActionEIP-712 type (no nonce required, onlyexpiresAfteris 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
| Field | Type | Description |
|---|---|---|
id | string | Echoed request identifier |
status | integer | HTTP-style status code |
result.status | string | "success" on success |
result.response.trades | array | Array of trade objects |
result.response.hasMore | boolean | Whether more trades exist beyond current result |
result.response.total | integer | Total number of trades matching the query |
Trade Object Fields
| Field | Type | Description |
|---|---|---|
tradeId | string | Unique trade identifier |
orderId | string | Order that generated this trade |
symbol | string | Trading pair symbol (e.g., "BTC-USDT") |
side | string | Trade side: "buy" or "sell" |
direction | string | Position effect (e.g., "open long", "close long", "open short", "close short") |
price | string | Execution price |
quantity | string | Filled quantity for this trade |
realizedPnl | string | Profit/loss realized from this trade |
fee | string | Trading fee charged |
feeRate | string | Fee rate applied (maker/taker rate) |
markPrice | string | Mark price at time of trade execution |
entryPrice | string | Position's average entry price at time of trade |
timestamp | integer | Trade execution timestamp (Unix milliseconds) |
maker | boolean | Whether this trade provided liquidity (maker) or took liquidity (taker) |
reduceOnly | boolean | Whether this trade reduced an existing position |
triggeredByLiquidation | boolean | Whether this trade was part of a liquidation |
postOnly | boolean | Whether 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
orderIdto 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
startTimemust be less than or equal toendTime
Common Errors
| Error | Description | Solution |
|---|---|---|
Invalid time range | Start time is after end time, or time range exceeds 30 days | Ensure startTime < endTime and range ≤ 30 days |
Invalid limit | Limit exceeds maximum | Use limit ≤ 1000 |
Invalid offset | Negative offset value | Use offset ≥ 0 |
Invalid symbol | Market symbol not recognized | Check available markets |
Authentication failed | Invalid signature | Verify signature generation |
| Error | Description |
|---|---|
| Invalid signature | EIP-712 signature validation failed |
| Invalid market symbol | Market symbol not recognized |
| Nonce already used | Nonce must be greater than previous value |
| Rate limit exceeded | Too many requests in time window |
| Request expired | expiresAfter timestamp has passed |
Next Steps
- SubAccount Updates Subscription - Real-time trade notifications
- Get Open Orders - Query active orders
- Get Order History - Query historical orders
- Get Positions - Query current positions
- REST Alternative - REST API comparison