a
This commit is contained in:
parent
3e9d24ebea
commit
078576ddf8
|
|
@ -240,86 +240,15 @@ const TradeList = () => {
|
|||
|
||||
{
|
||||
stats && (
|
||||
<div className="stats-summary">
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">整体统计</div>
|
||||
<div className="stat-value">总交易数:{stats.total_trades}</div>
|
||||
<div className="stat-value">胜率:{stats.win_rate.toFixed(2)}%</div>
|
||||
<div className="stat-value">总盈亏:{stats.total_pnl.toFixed(2)} USDT</div>
|
||||
<div className="stat-value">平均盈亏:{stats.avg_pnl.toFixed(2)} USDT</div>
|
||||
<div className="stat-value">平均持仓时长(分钟):{stats.avg_duration_minutes ? Number(stats.avg_duration_minutes).toFixed(0):0}</div>
|
||||
<div className="stat-value">平仓原因(有意义交易):
|
||||
<div className="stat-value" style={{ fontSize: '1.1rem' }}>
|
||||
{(() => {
|
||||
const m = stats.exit_reason_counts || {}
|
||||
const stopLoss = Number(m.stop_loss || 0)
|
||||
const takeProfit = Number(m.take_profit || 0)
|
||||
const trailing = Number(m.trailing_stop || 0)
|
||||
const manual = Number(m.manual || 0)
|
||||
const sync = Number(m.sync || 0)
|
||||
const other = Number(m.unknown || 0)
|
||||
const parts = []
|
||||
if (stopLoss) parts.push(`止损 ${stopLoss}`)
|
||||
if (takeProfit) parts.push(`止盈 ${takeProfit}`)
|
||||
if (trailing) parts.push(`移动止损 ${trailing}`)
|
||||
if (manual) parts.push(`手动 ${manual}`)
|
||||
if (sync) parts.push(`同步 ${sync}`)
|
||||
if (other) parts.push(`其他 ${other}`)
|
||||
return parts.length ? parts.join(' / ') : '—'
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-value">平均盈利 / 平均亏损(期望 3:1):{Number(stats.avg_win_loss_ratio || 0).toFixed(2)} : 1</div>
|
||||
<div className="stat-value">总交易量(名义):{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{stats && (
|
||||
<div className="stats-summary">
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易数</div>
|
||||
<div className="stat-value">{stats.total_trades}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
{stats.meaningful_trades !== undefined && (
|
||||
<>(有意义: {stats.meaningful_trades},0盈亏: {stats.zero_pnl_trades || 0})</>
|
||||
)}
|
||||
{stats.meaningful_trades === undefined && <>(已平仓的完整交易)</>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">胜率</div>
|
||||
<div className="stat-value">{stats.win_rate.toFixed(2)}%</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
{stats.meaningful_trades !== undefined && <>(已排除0盈亏订单)</>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总盈亏</div>
|
||||
<div className={`stat-value ${stats.total_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{stats.total_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈亏</div>
|
||||
<div className={`stat-value ${stats.avg_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{stats.avg_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
{"avg_duration_minutes" in stats && stats.avg_duration_minutes !== null && stats.avg_duration_minutes !== undefined && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均持仓时长(分钟)</div>
|
||||
<div className="stat-value">{Number(stats.avg_duration_minutes || 0).toFixed(0)}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(仅统计“有意义交易”;优先使用 duration_minutes,缺失时用 exit_time-entry_time 计算)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"exit_reason_counts" in stats && stats.exit_reason_counts && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平仓原因(有意义交易)</div>
|
||||
<div className="stat-value" style={{ fontSize: '1.1rem' }}>
|
||||
<div>
|
||||
<div>整体统计</div>
|
||||
<div>总交易数:{stats.total_trades}</div>
|
||||
<div>胜率:{stats.win_rate.toFixed(2)}%</div>
|
||||
<div>总盈亏:{stats.total_pnl.toFixed(2)} USDT</div>
|
||||
<div>平均盈亏:{stats.avg_pnl.toFixed(2)} USDT</div>
|
||||
<div>平均持仓时长(分钟):{stats.avg_duration_minutes ? Number(stats.avg_duration_minutes).toFixed(0) : 0}</div>
|
||||
<div>平仓原因(有意义交易):
|
||||
<div style={{ fontSize: '1.1rem' }}>
|
||||
{(() => {
|
||||
const m = stats.exit_reason_counts || {}
|
||||
const stopLoss = Number(m.stop_loss || 0)
|
||||
|
|
@ -339,267 +268,340 @@ const TradeList = () => {
|
|||
})()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-value">平均盈利 / 平均亏损(期望 3:1):{Number(stats.avg_win_loss_ratio || 0).toFixed(2)} : 1</div>
|
||||
<div className="stat-value">总交易量(名义):{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
stats && (
|
||||
<div className="stats-summary">
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易数</div>
|
||||
<div className="stat-value">{stats.total_trades}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
{stats.meaningful_trades !== undefined && (
|
||||
<>(有意义: {stats.meaningful_trades},0盈亏: {stats.zero_pnl_trades || 0})</>
|
||||
)}
|
||||
{"avg_win_pnl" in stats && "avg_loss_pnl_abs" in stats && Number(stats.total_pnl || 0) > 0 && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈利 / 平均亏损(期望 3:1)</div>
|
||||
<div
|
||||
className={`stat-value ${typeof stats.avg_win_loss_ratio === 'number' && stats.avg_win_loss_ratio >= 3 ? 'positive' : ''
|
||||
}`}
|
||||
>
|
||||
{typeof stats.avg_win_loss_ratio === 'number'
|
||||
? `${stats.avg_win_loss_ratio.toFixed(2)} : 1`
|
||||
: '—'}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
+{Number(stats.avg_win_pnl || 0).toFixed(2)} / -{Number(stats.avg_loss_pnl_abs || 0).toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"total_notional_usdt" in stats && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易量(名义)</div>
|
||||
<div className="stat-value">{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(口径:入场价×数量)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{stats.meaningful_trades === undefined && <>(已平仓的完整交易)</>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">胜率</div>
|
||||
<div className="stat-value">{stats.win_rate.toFixed(2)}%</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
{stats.meaningful_trades !== undefined && <>(已排除0盈亏订单)</>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总盈亏</div>
|
||||
<div className={`stat-value ${stats.total_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{stats.total_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈亏</div>
|
||||
<div className={`stat-value ${stats.avg_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{stats.avg_pnl.toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
{"avg_duration_minutes" in stats && stats.avg_duration_minutes !== null && stats.avg_duration_minutes !== undefined && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均持仓时长(分钟)</div>
|
||||
<div className="stat-value">{Number(stats.avg_duration_minutes || 0).toFixed(0)}</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(仅统计“有意义交易”;优先使用 duration_minutes,缺失时用 exit_time-entry_time 计算)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{trades.length === 0 ? (
|
||||
<div className="no-data">暂无交易记录</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 桌面端表格 */}
|
||||
<table className="trades-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易ID</th>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>名义</th>
|
||||
<th>保证金</th>
|
||||
<th>入场价</th>
|
||||
<th>出场价</th>
|
||||
<th>盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>状态</th>
|
||||
<th>平仓类型</th>
|
||||
<th>币安订单号</th>
|
||||
<th>入场时间</th>
|
||||
<th>平仓时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{trades.map(trade => {
|
||||
// 名义/保证金:优先使用后端返回字段(notional_usdt / margin_usdt),否则回退计算
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
|
||||
// 计算盈亏比例(盈亏/保证金)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||
|
||||
// 格式化时间为北京时间
|
||||
// 支持Unix时间戳(秒数)或日期字符串
|
||||
const formatTime = (timeValue) => {
|
||||
if (!timeValue) return '-'
|
||||
try {
|
||||
let date
|
||||
// 如果是数字(Unix时间戳),转换为毫秒
|
||||
if (typeof timeValue === 'number') {
|
||||
date = new Date(timeValue * 1000)
|
||||
} else {
|
||||
date = new Date(timeValue)
|
||||
}
|
||||
if (isNaN(date.getTime())) return String(timeValue)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Shanghai'
|
||||
})
|
||||
} catch (e) {
|
||||
return String(timeValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化订单号显示
|
||||
const formatOrderIds = () => {
|
||||
const entry = trade.entry_order_id || '-'
|
||||
const exit = trade.exit_order_id || '-'
|
||||
if (entry === '-' && exit === '-') return '-'
|
||||
if (entry !== '-' && exit !== '-') {
|
||||
return `开仓: ${entry} / 平仓: ${exit}`
|
||||
}
|
||||
return entry !== '-' ? `开仓: ${entry}` : `平仓: ${exit}`
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={trade.id}>
|
||||
<td style={{ fontSize: '12px', color: '#999' }}>#{trade.id}</td>
|
||||
<td>{trade.symbol}</td>
|
||||
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
||||
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
||||
<td>{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</td>
|
||||
<td>{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</td>
|
||||
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
||||
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
||||
<td className={pnl >= 0 ? 'positive' : 'negative'}>
|
||||
{pnl.toFixed(2)} USDT
|
||||
</td>
|
||||
<td className={pnlPercent >= 0 ? 'positive' : 'negative'}>
|
||||
{pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}%
|
||||
</td>
|
||||
<td>
|
||||
<span className={`status ${trade.status}`}>{trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'}</span>
|
||||
</td>
|
||||
<td>{trade.exit_reason_display || '-'}</td>
|
||||
<td className="order-id" style={{ fontSize: '12px' }}>{formatOrderIds()}</td>
|
||||
<td>{formatTime(trade.entry_time)}</td>
|
||||
<td>{trade.exit_time ? formatTime(trade.exit_time) : '-'}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* 移动端卡片 */}
|
||||
<div className="trades-cards">
|
||||
{trades.map(trade => {
|
||||
// 名义/保证金:优先后端字段
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||
|
||||
// 格式化时间为北京时间
|
||||
// 支持Unix时间戳(秒数)或日期字符串
|
||||
const formatTime = (timeValue) => {
|
||||
if (!timeValue) return '-'
|
||||
try {
|
||||
let date
|
||||
// 如果是数字(Unix时间戳),转换为毫秒
|
||||
if (typeof timeValue === 'number') {
|
||||
date = new Date(timeValue * 1000)
|
||||
} else {
|
||||
date = new Date(timeValue)
|
||||
}
|
||||
if (isNaN(date.getTime())) return String(timeValue)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Shanghai'
|
||||
})
|
||||
} catch (e) {
|
||||
return String(timeValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={trade.id} className="trade-card">
|
||||
<div className="trade-card-header">
|
||||
<span className="trade-card-symbol">{trade.symbol}</span>
|
||||
<span style={{ fontSize: '11px', color: '#999', marginLeft: '8px' }}>交易ID: #{trade.id}</span>
|
||||
<span className={`trade-card-side ${trade.side === 'BUY' ? 'buy' : 'sell'} status ${trade.status}`}>
|
||||
{trade.side === 'BUY' ? '买入' : '卖出'} · {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="trade-card-body">
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">数量</span>
|
||||
<span className="trade-card-value">{parseFloat(trade.quantity).toFixed(4)}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">名义</span>
|
||||
<span className="trade-card-value">{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">保证金</span>
|
||||
<span className="trade-card-value">{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">入场价</span>
|
||||
<span className="trade-card-value">{parseFloat(trade.entry_price).toFixed(4)}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">出场价</span>
|
||||
<span className="trade-card-value">{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">盈亏</span>
|
||||
<span className={`trade-card-value ${pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{pnl.toFixed(2)} USDT
|
||||
</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">盈亏比例</span>
|
||||
<span className={`trade-card-value ${pnlPercent >= 0 ? 'positive' : 'negative'}`}>
|
||||
{pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
{trade.exit_reason_display && (
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">平仓类型</span>
|
||||
<span className="trade-card-value">{trade.exit_reason_display}</span>
|
||||
</div>
|
||||
)}
|
||||
{(trade.entry_order_id || trade.exit_order_id) && (
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">币安订单号</span>
|
||||
<span className="trade-card-value order-id" style={{ fontSize: '12px' }}>
|
||||
{trade.entry_order_id ? `开仓: ${trade.entry_order_id}` : ''}
|
||||
{trade.entry_order_id && trade.exit_order_id ? ' / ' : ''}
|
||||
{trade.exit_order_id ? `平仓: ${trade.exit_order_id}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="trade-card-footer">
|
||||
<div className="trade-time-item">
|
||||
<span className="time-label">入场:</span>
|
||||
<span>{formatTime(trade.entry_time)}</span>
|
||||
</div>
|
||||
{trade.exit_time && (
|
||||
<div className="trade-time-item">
|
||||
<span className="time-label">平仓:</span>
|
||||
<span>{formatTime(trade.exit_time)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{"exit_reason_counts" in stats && stats.exit_reason_counts && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平仓原因(有意义交易)</div>
|
||||
<div className="stat-value" style={{ fontSize: '1.1rem' }}>
|
||||
{(() => {
|
||||
const m = stats.exit_reason_counts || {}
|
||||
const stopLoss = Number(m.stop_loss || 0)
|
||||
const takeProfit = Number(m.take_profit || 0)
|
||||
const trailing = Number(m.trailing_stop || 0)
|
||||
const manual = Number(m.manual || 0)
|
||||
const sync = Number(m.sync || 0)
|
||||
const other = Number(m.unknown || 0)
|
||||
const parts = []
|
||||
if (stopLoss) parts.push(`止损 ${stopLoss}`)
|
||||
if (takeProfit) parts.push(`止盈 ${takeProfit}`)
|
||||
if (trailing) parts.push(`移动止损 ${trailing}`)
|
||||
if (manual) parts.push(`手动 ${manual}`)
|
||||
if (sync) parts.push(`同步 ${sync}`)
|
||||
if (other) parts.push(`其他 ${other}`)
|
||||
return parts.length ? parts.join(' / ') : '—'
|
||||
})()}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
{"avg_win_pnl" in stats && "avg_loss_pnl_abs" in stats && Number(stats.total_pnl || 0) > 0 && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">平均盈利 / 平均亏损(期望 3:1)</div>
|
||||
<div
|
||||
className={`stat-value ${typeof stats.avg_win_loss_ratio === 'number' && stats.avg_win_loss_ratio >= 3 ? 'positive' : ''
|
||||
}`}
|
||||
>
|
||||
{typeof stats.avg_win_loss_ratio === 'number'
|
||||
? `${stats.avg_win_loss_ratio.toFixed(2)} : 1`
|
||||
: '—'}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
+{Number(stats.avg_win_pnl || 0).toFixed(2)} / -{Number(stats.avg_loss_pnl_abs || 0).toFixed(2)} USDT
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{"total_notional_usdt" in stats && (
|
||||
<div className="stat-card">
|
||||
<div className="stat-label">总交易量(名义)</div>
|
||||
<div className="stat-value">{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT</div>
|
||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '4px' }}>
|
||||
(口径:入场价×数量)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
trades.length === 0 ? (
|
||||
<div className="no-data">暂无交易记录</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 桌面端表格 */}
|
||||
<table className="trades-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>交易ID</th>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>名义</th>
|
||||
<th>保证金</th>
|
||||
<th>入场价</th>
|
||||
<th>出场价</th>
|
||||
<th>盈亏</th>
|
||||
<th>盈亏比例</th>
|
||||
<th>状态</th>
|
||||
<th>平仓类型</th>
|
||||
<th>币安订单号</th>
|
||||
<th>入场时间</th>
|
||||
<th>平仓时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{trades.map(trade => {
|
||||
// 名义/保证金:优先使用后端返回字段(notional_usdt / margin_usdt),否则回退计算
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
|
||||
// 计算盈亏比例(盈亏/保证金)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||
|
||||
// 格式化时间为北京时间
|
||||
// 支持Unix时间戳(秒数)或日期字符串
|
||||
const formatTime = (timeValue) => {
|
||||
if (!timeValue) return '-'
|
||||
try {
|
||||
let date
|
||||
// 如果是数字(Unix时间戳),转换为毫秒
|
||||
if (typeof timeValue === 'number') {
|
||||
date = new Date(timeValue * 1000)
|
||||
} else {
|
||||
date = new Date(timeValue)
|
||||
}
|
||||
if (isNaN(date.getTime())) return String(timeValue)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Shanghai'
|
||||
})
|
||||
} catch (e) {
|
||||
return String(timeValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化订单号显示
|
||||
const formatOrderIds = () => {
|
||||
const entry = trade.entry_order_id || '-'
|
||||
const exit = trade.exit_order_id || '-'
|
||||
if (entry === '-' && exit === '-') return '-'
|
||||
if (entry !== '-' && exit !== '-') {
|
||||
return `开仓: ${entry} / 平仓: ${exit}`
|
||||
}
|
||||
return entry !== '-' ? `开仓: ${entry}` : `平仓: ${exit}`
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={trade.id}>
|
||||
<td style={{ fontSize: '12px', color: '#999' }}>#{trade.id}</td>
|
||||
<td>{trade.symbol}</td>
|
||||
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
||||
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
||||
<td>{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</td>
|
||||
<td>{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</td>
|
||||
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
||||
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
||||
<td className={pnl >= 0 ? 'positive' : 'negative'}>
|
||||
{pnl.toFixed(2)} USDT
|
||||
</td>
|
||||
<td className={pnlPercent >= 0 ? 'positive' : 'negative'}>
|
||||
{pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}%
|
||||
</td>
|
||||
<td>
|
||||
<span className={`status ${trade.status}`}>{trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'}</span>
|
||||
</td>
|
||||
<td>{trade.exit_reason_display || '-'}</td>
|
||||
<td className="order-id" style={{ fontSize: '12px' }}>{formatOrderIds()}</td>
|
||||
<td>{formatTime(trade.entry_time)}</td>
|
||||
<td>{trade.exit_time ? formatTime(trade.exit_time) : '-'}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* 移动端卡片 */}
|
||||
<div className="trades-cards">
|
||||
{trades.map(trade => {
|
||||
// 名义/保证金:优先后端字段
|
||||
const notional = trade.notional_usdt !== undefined && trade.notional_usdt !== null
|
||||
? parseFloat(trade.notional_usdt)
|
||||
: (
|
||||
trade.entry_value_usdt !== undefined && trade.entry_value_usdt !== null
|
||||
? parseFloat(trade.entry_value_usdt)
|
||||
: (parseFloat(trade.quantity || 0) * parseFloat(trade.entry_price || 0))
|
||||
)
|
||||
const leverage = parseFloat(trade.leverage || 10)
|
||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||
? parseFloat(trade.margin_usdt)
|
||||
: (leverage > 0 ? notional / leverage : 0)
|
||||
const pnl = parseFloat(trade.pnl || 0)
|
||||
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||
|
||||
// 格式化时间为北京时间
|
||||
// 支持Unix时间戳(秒数)或日期字符串
|
||||
const formatTime = (timeValue) => {
|
||||
if (!timeValue) return '-'
|
||||
try {
|
||||
let date
|
||||
// 如果是数字(Unix时间戳),转换为毫秒
|
||||
if (typeof timeValue === 'number') {
|
||||
date = new Date(timeValue * 1000)
|
||||
} else {
|
||||
date = new Date(timeValue)
|
||||
}
|
||||
if (isNaN(date.getTime())) return String(timeValue)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZone: 'Asia/Shanghai'
|
||||
})
|
||||
} catch (e) {
|
||||
return String(timeValue)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={trade.id} className="trade-card">
|
||||
<div className="trade-card-header">
|
||||
<span className="trade-card-symbol">{trade.symbol}</span>
|
||||
<span style={{ fontSize: '11px', color: '#999', marginLeft: '8px' }}>交易ID: #{trade.id}</span>
|
||||
<span className={`trade-card-side ${trade.side === 'BUY' ? 'buy' : 'sell'} status ${trade.status}`}>
|
||||
{trade.side === 'BUY' ? '买入' : '卖出'} · {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="trade-card-body">
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">数量</span>
|
||||
<span className="trade-card-value">{parseFloat(trade.quantity).toFixed(4)}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">名义</span>
|
||||
<span className="trade-card-value">{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">保证金</span>
|
||||
<span className="trade-card-value">{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">入场价</span>
|
||||
<span className="trade-card-value">{parseFloat(trade.entry_price).toFixed(4)}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">出场价</span>
|
||||
<span className="trade-card-value">{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">盈亏</span>
|
||||
<span className={`trade-card-value ${pnl >= 0 ? 'positive' : 'negative'}`}>
|
||||
{pnl.toFixed(2)} USDT
|
||||
</span>
|
||||
</div>
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">盈亏比例</span>
|
||||
<span className={`trade-card-value ${pnlPercent >= 0 ? 'positive' : 'negative'}`}>
|
||||
{pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
{trade.exit_reason_display && (
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">平仓类型</span>
|
||||
<span className="trade-card-value">{trade.exit_reason_display}</span>
|
||||
</div>
|
||||
)}
|
||||
{(trade.entry_order_id || trade.exit_order_id) && (
|
||||
<div className="trade-card-field">
|
||||
<span className="trade-card-label">币安订单号</span>
|
||||
<span className="trade-card-value order-id" style={{ fontSize: '12px' }}>
|
||||
{trade.entry_order_id ? `开仓: ${trade.entry_order_id}` : ''}
|
||||
{trade.entry_order_id && trade.exit_order_id ? ' / ' : ''}
|
||||
{trade.exit_order_id ? `平仓: ${trade.exit_order_id}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="trade-card-footer">
|
||||
<div className="trade-time-item">
|
||||
<span className="time-label">入场:</span>
|
||||
<span>{formatTime(trade.entry_time)}</span>
|
||||
</div>
|
||||
{trade.exit_time && (
|
||||
<div className="trade-time-item">
|
||||
<span className="time-label">平仓:</span>
|
||||
<span>{formatTime(trade.exit_time)}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export default TradeList
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user