交易历史 (Trades)
查询用户的历史成交记录,包括订单撮合产生的所有交易明细。
接口信息
- Method:
GET - Path:
/api/v1/account/trades - Authentication: 需要 JWT Token
Query 参数
| 参数 | 类型 | 必须 | 描述 |
|---|---|---|---|
| symbol | string | 否 | 过滤特定交易对(如 BTCUSDT) |
| start_time | number | 否 | 开始时间(毫秒级时间戳) |
| end_time | number | 否 | 结束时间(毫秒级时间戳) |
| limit | number | 否 | 返回数量(默认 50,最大 1000) |
| offset | number | 否 | 偏移量(分页) |
请求示例
GET /api/v1/account/trades?symbol=BTCUSDT&limit=10
响应示例
{
"trades": [
{
"id": "990e8400-e29b-41d4-a716-446655440000",
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"symbol": "BTCUSDT",
"side": "buy",
"price": "65000.00",
"amount": "0.05",
"quote_amount": "3250.00",
"fee": "3.25",
"fee_currency": "USDT",
"role": "taker",
"realized_pnl": "0.00",
"position_side": "long",
"is_opening": true,
"created_at": 1704067200000
},
{
"id": "990e8400-e29b-41d4-a716-446655440001",
"order_id": "550e8400-e29b-41d4-a716-446655440001",
"symbol": "BTCUSDT",
"side": "sell",
"price": "65500.00",
"amount": "0.05",
"quote_amount": "3275.00",
"fee": "3.28",
"fee_currency": "USDT",
"role": "maker",
"realized_pnl": "25.00",
"position_side": "long",
"is_opening": false,
"created_at": 1704070800000
}
],
"total": 2,
"summary": {
"total_volume": "6525.00",
"total_fee": "6.53",
"total_pnl": "25.00",
"winning_trades": 1,
"losing_trades": 0,
"win_rate": "100.00"
}
}
响应字段说明
Trade 对象
| 字段 | 类型 | 描述 |
|---|---|---|
| id | string | 成交记录 UUID |
| order_id | string | 关联的订单 UUID |
| symbol | string | 交易对 |
| side | string | 交易方向(buy/sell) |
| price | string | 成交价格(Decimal 字符串) |
| amount | string | 成交数量(Decimal 字符串) |
| quote_amount | string | 成交金额(price × amount) |
| fee | string | 手续费(Decimal 字符串) |
| fee_currency | string | 手续费币种(通常是 USDT) |
| role | string | 角色(maker/taker) |
| realized_pnl | string | 已实现盈亏(平仓交易才有) |
| position_side | string | 仓位方向(long/short) |
| is_opening | boolean | 是否是开仓交 易 |
| created_at | number | 成交时间(毫秒级时间戳) |
Summary 对象
| 字段 | 类型 | 描述 |
|---|---|---|
| total_volume | string | 总成交额(USDT) |
| total_fee | string | 总手续费 |
| total_pnl | string | 总盈亏 |
| winning_trades | number | 盈利交易数 |
| losing_trades | number | 亏损交易数 |
| win_rate | string | 胜率百分比 |
手续费说明
手续费率
| 角色 | 费率 | 说明 |
|---|---|---|
| Maker | 0.02% | 提供流动性(挂单) |
| Taker | 0.05% | 消耗流动性(吃单) |
手续费计算
手续费 = 成交金额 × 费率
示例:
成交金额: 3250 USDT
角色: Taker (0.05%)
手续费 = 3250 × 0.0005 = 1.625 USDT
VIP 等级优惠
| VIP 等级 | 条件 | Maker | Taker |
|---|---|---|---|
| VIP 0 | 默认 | 0.02% | 0.05% |
| VIP 1 | 30天成交量 > 1M | 0.015% | 0.045% |
| VIP 2 | 30天成交量 > 10M | 0.01% | 0.04% |
| VIP 3 | 30天成交量 > 50M | 0.005% | 0.035% |
| VIP 4 | 30天成交量 > 100M | 0.00% | 0.03% |
使用示例
JavaScript
// 查询最近10笔交易
async function getRecentTrades() {
const response = await fetch('/api/v1/account/trades?limit=10', {
headers: {
'Authorization': `Bearer ${jwtToken}`
}
});
const { trades, summary } = await response.json();
console.log('最近交易:');
trades.forEach(trade => {
const pnlText = trade.realized_pnl !== '0.00'
? `盈亏: ${trade.realized_pnl} USDT`
: '';
console.log(`
${trade.symbol} ${trade.side} ${trade.amount}
@ ${trade.price} (${trade.role})
手续费: ${trade.fee} USDT ${pnlText}
`);
});
console.log(`\n总盈亏: ${summary.total_pnl} USDT`);
console.log(`胜率: ${summary.win_rate}%`);
}
Python
import requests
def get_trades(symbol=None, limit=50):
params = {'limit': limit}
if symbol:
params['symbol'] = symbol
response = requests.get(
'/api/v1/account/trades',
params=params,
headers={'Authorization': f'Bearer {jwt_token}'}
)
data = response.json()
return data['trades'], data['summary']
# 使用
trades, summary = get_trades('BTCUSDT', limit=10)
for trade in trades:
print(f"{trade['symbol']} {trade['side']} {trade['amount']}")
print(f"价格: {trade['price']}, 手续费: {trade['fee']}")
if trade['realized_pnl'] != '0.00':
print(f"盈亏: {trade['realized_pnl']}")
print()
print(f"总盈亏: {summary['total_pnl']}")
print(f"胜率: {summary['win_rate']}%")
交易分析
计算平均成本
function calculateAverageCost(trades) {
const openingTrades = trades.filter(t => t.is_opening);
const totalCost = openingTrades.reduce((sum, trade) => {
return sum + parseFloat(trade.quote_amount) + parseFloat(trade.fee);
}, 0);
const totalAmount = openingTrades.reduce((sum, trade) => {
return sum + parseFloat(trade.amount);
}, 0);
return totalCost / totalAmount;
}
// 使用
const avgCost = calculateAverageCost(trades);
console.log(`平均开仓成本: ${avgCost.toFixed(2)} USDT`);
计算盈亏比
function calculateProfitMetrics(trades) {
const closingTrades = trades.filter(t => !t.is_opening);
const profits = closingTrades
.filter(t => parseFloat(t.realized_pnl) > 0)
.map(t => parseFloat(t.realized_pnl));
const losses = closingTrades
.filter(t => parseFloat(t.realized_pnl) < 0)
.map(t => Math.abs(parseFloat(t.realized_pnl)));
const avgProfit = profits.reduce((a, b) => a + b, 0) / profits.length || 0;
const avgLoss = losses.reduce((a, b) => a + b, 0) / losses.length || 0;
return {
avgProfit,
avgLoss,
profitFactor: avgProfit / avgLoss || 0,
winRate: (profits.length / closingTrades.length * 100).toFixed(2)
};
}
// 使用
const metrics = calculateProfitMetrics(trades);
console.log(`
平均盈利: ${metrics.avgProfit.toFixed(2)} USDT
平均亏损: ${metrics.avgLoss.toFixed(2)} USDT
盈亏比: ${metrics.profitFactor.toFixed(2)}
胜率: ${metrics.winRate}%
`);
按时间段分析
function analyzeByPeriod(trades, periodHours = 24) {
const now = Date.now();
const periodMs = periodHours * 60 * 60 * 1000;
const recentTrades = trades.filter(t =>
now - t.created_at < periodMs
);
const totalPnl = recentTrades.reduce((sum, t) =>
sum + parseFloat(t.realized_pnl), 0
);
const totalFee = recentTrades.reduce((sum, t) =>
sum + parseFloat(t.fee), 0
);
return {
period: `${periodHours}小时`,
tradeCount: recentTrades.length,
totalPnl,
totalFee,
netProfit: totalPnl - totalFee
};
}
// 使用
const daily = analyzeByPeriod(trades, 24);
console.log(`
24小时交易统计:
交易次数: ${daily.tradeCount}
总盈亏: ${daily.totalPnl.toFixed(2)} USDT
总手续费: ${daily.totalFee.toFixed(2)} USDT
净利润: ${daily.netProfit.toFixed(2)} USDT
`);
导出交易记录
CSV 导出
function exportToCSV(trades) {
const headers = [
'Time', 'Symbol', 'Side', 'Price', 'Amount',
'Fee', 'Role', 'PnL', 'Type'
];
const rows = trades.map(trade => [
new Date(trade.created_at).toISOString(),
trade.symbol,
trade.side,
trade.price,
trade.amount,
trade.fee,
trade.role,
trade.realized_pnl,
trade.is_opening ? 'Open' : 'Close'
]);
const csv = [
headers.join(','),
...rows.map(row => row.join(','))
].join('\n');
// 下载 CSV
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `trades_${Date.now()}.csv`;
a.click();
}
Excel 导出
import XLSX from 'xlsx';
function exportToExcel(trades) {
const data = trades.map(trade => ({
'时间': new Date(trade.created_at).toLocaleString(),
'交易对': trade.symbol,
'方向': trade.side === 'buy' ? '买入' : '卖出',
'价格': trade.price,
'数量': trade.amount,
'成交额': trade.quote_amount,
'手续费': trade.fee,
'角色': trade.role === 'maker' ? '挂单' : '吃单',
'盈亏': trade.realized_pnl,
'类型': trade.is_opening ? '开仓' : '平仓'
}));
const ws = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '交易记录');
XLSX.writeFile(wb, `trades_${Date.now()}.xlsx`);
}
WebSocket 实时成交
通过 WebSocket 订阅实时成交数据:
// 订阅用户成交
ws.send(JSON.stringify({
action: 'subscribe',
channel: 'user_trades'
}));
// 监听成交推送
ws.on('message', (event) => {
const data = JSON.parse(event.data);
if (data.type === 'trade') {
console.log('新成交:', data.data);
// 更新 UI
updateTradesList(data.data);
// 通知用户
if (Notification.permission === 'granted') {
new Notification('交易成交', {
body: `${data.data.symbol} ${data.data.side} ${data.data.amount} @ ${data.data.price}`
});
}
}
});
常见问题
Q: 交易记录会保存多久?
A: 永久保存。您可以查询任意时间段的交易记录。
Q: 为什么有些交易没有 realized_pnl?
A: 只有平仓交易才有 realized_pnl。开仓交易的盈亏尚未实现,记录在仓位的 unrealized_pnl 中。
Q: Maker 和 Taker 有什么区别 ?
A:
- Maker: 挂单到订单簿,提供流动性,享受较低手续费
- Taker: 吃单(立即成交),消耗流动性,手续费较高
Q: 如何计算总盈亏?
A: 总盈亏 = Σ(realized_pnl) - Σ(fee)
const totalPnl = trades.reduce((sum, t) =>
sum + parseFloat(t.realized_pnl) - parseFloat(t.fee), 0
);
Q: 可以查询其他用户的交易记录吗?
A: 不可以。交易记录是私有数据,只能查询自己的记录。