diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index a54a0a1..4976f48 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -240,86 +240,15 @@ const TradeList = () => { { stats && ( -
-
-
整体统计
-
总交易数:{stats.total_trades}
-
胜率:{stats.win_rate.toFixed(2)}%
-
总盈亏:{stats.total_pnl.toFixed(2)} USDT
-
平均盈亏:{stats.avg_pnl.toFixed(2)} USDT
-
平均持仓时长(分钟):{stats.avg_duration_minutes ? Number(stats.avg_duration_minutes).toFixed(0):0}
-
平仓原因(有意义交易): -
- {(() => { - 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(' / ') : '—' - })()} -
-
-
平均盈利 / 平均亏损(期望 3:1):{Number(stats.avg_win_loss_ratio || 0).toFixed(2)} : 1
-
总交易量(名义):{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT
-
-
- ) - } - - {stats && ( -
-
-
总交易数
-
{stats.total_trades}
-
- {stats.meaningful_trades !== undefined && ( - <>(有意义: {stats.meaningful_trades},0盈亏: {stats.zero_pnl_trades || 0}) - )} - {stats.meaningful_trades === undefined && <>(已平仓的完整交易)} -
-
-
-
胜率
-
{stats.win_rate.toFixed(2)}%
-
- {stats.meaningful_trades !== undefined && <>(已排除0盈亏订单)} -
-
-
-
总盈亏
-
= 0 ? 'positive' : 'negative'}`}> - {stats.total_pnl.toFixed(2)} USDT -
-
-
-
平均盈亏
-
= 0 ? 'positive' : 'negative'}`}> - {stats.avg_pnl.toFixed(2)} USDT -
-
- {"avg_duration_minutes" in stats && stats.avg_duration_minutes !== null && stats.avg_duration_minutes !== undefined && ( -
-
平均持仓时长(分钟)
-
{Number(stats.avg_duration_minutes || 0).toFixed(0)}
-
- (仅统计“有意义交易”;优先使用 duration_minutes,缺失时用 exit_time-entry_time 计算) -
-
- )} - {"exit_reason_counts" in stats && stats.exit_reason_counts && ( -
-
平仓原因(有意义交易)
-
+
+
整体统计
+
总交易数:{stats.total_trades}
+
胜率:{stats.win_rate.toFixed(2)}%
+
总盈亏:{stats.total_pnl.toFixed(2)} USDT
+
平均盈亏:{stats.avg_pnl.toFixed(2)} USDT
+
平均持仓时长(分钟):{stats.avg_duration_minutes ? Number(stats.avg_duration_minutes).toFixed(0) : 0}
+
平仓原因(有意义交易): +
{(() => { const m = stats.exit_reason_counts || {} const stopLoss = Number(m.stop_loss || 0) @@ -339,267 +268,340 @@ const TradeList = () => { })()}
+
平均盈利 / 平均亏损(期望 3:1):{Number(stats.avg_win_loss_ratio || 0).toFixed(2)} : 1
+
总交易量(名义):{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT
+
+ ) +} + +{ + stats && ( +
+
+
总交易数
+
{stats.total_trades}
+
+ {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 && ( -
-
平均盈利 / 平均亏损(期望 3:1)
-
= 3 ? 'positive' : '' - }`} - > - {typeof stats.avg_win_loss_ratio === 'number' - ? `${stats.avg_win_loss_ratio.toFixed(2)} : 1` - : '—'} -
-
- +{Number(stats.avg_win_pnl || 0).toFixed(2)} / -{Number(stats.avg_loss_pnl_abs || 0).toFixed(2)} USDT -
-
- )} - {"total_notional_usdt" in stats && ( -
-
总交易量(名义)
-
{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT
-
- (口径:入场价×数量) -
-
- )} + {stats.meaningful_trades === undefined && <>(已平仓的完整交易)} +
+
+
+
胜率
+
{stats.win_rate.toFixed(2)}%
+
+ {stats.meaningful_trades !== undefined && <>(已排除0盈亏订单)} +
+
+
+
总盈亏
+
= 0 ? 'positive' : 'negative'}`}> + {stats.total_pnl.toFixed(2)} USDT +
+
+
+
平均盈亏
+
= 0 ? 'positive' : 'negative'}`}> + {stats.avg_pnl.toFixed(2)} USDT +
+
+ {"avg_duration_minutes" in stats && stats.avg_duration_minutes !== null && stats.avg_duration_minutes !== undefined && ( +
+
平均持仓时长(分钟)
+
{Number(stats.avg_duration_minutes || 0).toFixed(0)}
+
+ (仅统计“有意义交易”;优先使用 duration_minutes,缺失时用 exit_time-entry_time 计算) +
)} - - {trades.length === 0 ? ( -
暂无交易记录
- ) : ( - <> - {/* 桌面端表格 */} - - - - - - - - - - - - - - - - - - - - - - {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 ( - - - - - - - - - - - - - - - - - - ) - })} - -
交易ID交易对方向数量名义保证金入场价出场价盈亏盈亏比例状态平仓类型币安订单号入场时间平仓时间
#{trade.id}{trade.symbol}{trade.side}{parseFloat(trade.quantity).toFixed(4)}{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT{parseFloat(trade.entry_price).toFixed(4)}{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}= 0 ? 'positive' : 'negative'}> - {pnl.toFixed(2)} USDT - = 0 ? 'positive' : 'negative'}> - {pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}% - - {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} - {trade.exit_reason_display || '-'}{formatOrderIds()}{formatTime(trade.entry_time)}{trade.exit_time ? formatTime(trade.exit_time) : '-'}
- - {/* 移动端卡片 */} -
- {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 ( -
-
- {trade.symbol} - 交易ID: #{trade.id} - - {trade.side === 'BUY' ? '买入' : '卖出'} · {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} - -
-
-
- 数量 - {parseFloat(trade.quantity).toFixed(4)} -
-
- 名义 - {notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT -
-
- 保证金 - {margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT -
-
- 入场价 - {parseFloat(trade.entry_price).toFixed(4)} -
-
- 出场价 - {trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'} -
-
- 盈亏 - = 0 ? 'positive' : 'negative'}`}> - {pnl.toFixed(2)} USDT - -
-
- 盈亏比例 - = 0 ? 'positive' : 'negative'}`}> - {pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}% - -
- {trade.exit_reason_display && ( -
- 平仓类型 - {trade.exit_reason_display} -
- )} - {(trade.entry_order_id || trade.exit_order_id) && ( -
- 币安订单号 - - {trade.entry_order_id ? `开仓: ${trade.entry_order_id}` : ''} - {trade.entry_order_id && trade.exit_order_id ? ' / ' : ''} - {trade.exit_order_id ? `平仓: ${trade.exit_order_id}` : ''} - -
- )} -
-
-
- 入场: - {formatTime(trade.entry_time)} -
- {trade.exit_time && ( -
- 平仓: - {formatTime(trade.exit_time)} -
- )} -
-
- ) - })} + {"exit_reason_counts" in stats && stats.exit_reason_counts && ( +
+
平仓原因(有意义交易)
+
+ {(() => { + 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(' / ') : '—' + })()}
- +
+ )} + {"avg_win_pnl" in stats && "avg_loss_pnl_abs" in stats && Number(stats.total_pnl || 0) > 0 && ( +
+
平均盈利 / 平均亏损(期望 3:1)
+
= 3 ? 'positive' : '' + }`} + > + {typeof stats.avg_win_loss_ratio === 'number' + ? `${stats.avg_win_loss_ratio.toFixed(2)} : 1` + : '—'} +
+
+ +{Number(stats.avg_win_pnl || 0).toFixed(2)} / -{Number(stats.avg_loss_pnl_abs || 0).toFixed(2)} USDT +
+
+ )} + {"total_notional_usdt" in stats && ( +
+
总交易量(名义)
+
{Number(stats.total_notional_usdt || 0).toFixed(2)} USDT
+
+ (口径:入场价×数量) +
+
)}
) } +{ + trades.length === 0 ? ( +
暂无交易记录
+ ) : ( + <> + {/* 桌面端表格 */} + + + + + + + + + + + + + + + + + + + + + + {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 ( + + + + + + + + + + + + + + + + + + ) + })} + +
交易ID交易对方向数量名义保证金入场价出场价盈亏盈亏比例状态平仓类型币安订单号入场时间平仓时间
#{trade.id}{trade.symbol}{trade.side}{parseFloat(trade.quantity).toFixed(4)}{notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT{margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT{parseFloat(trade.entry_price).toFixed(4)}{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}= 0 ? 'positive' : 'negative'}> + {pnl.toFixed(2)} USDT + = 0 ? 'positive' : 'negative'}> + {pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}% + + {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} + {trade.exit_reason_display || '-'}{formatOrderIds()}{formatTime(trade.entry_time)}{trade.exit_time ? formatTime(trade.exit_time) : '-'}
+ + {/* 移动端卡片 */} +
+ {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 ( +
+
+ {trade.symbol} + 交易ID: #{trade.id} + + {trade.side === 'BUY' ? '买入' : '卖出'} · {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} + +
+
+
+ 数量 + {parseFloat(trade.quantity).toFixed(4)} +
+
+ 名义 + {notional >= 0.01 ? notional.toFixed(2) : notional.toFixed(4)} USDT +
+
+ 保证金 + {margin >= 0.01 ? margin.toFixed(2) : margin.toFixed(4)} USDT +
+
+ 入场价 + {parseFloat(trade.entry_price).toFixed(4)} +
+
+ 出场价 + {trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'} +
+
+ 盈亏 + = 0 ? 'positive' : 'negative'}`}> + {pnl.toFixed(2)} USDT + +
+
+ 盈亏比例 + = 0 ? 'positive' : 'negative'}`}> + {pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}% + +
+ {trade.exit_reason_display && ( +
+ 平仓类型 + {trade.exit_reason_display} +
+ )} + {(trade.entry_order_id || trade.exit_order_id) && ( +
+ 币安订单号 + + {trade.entry_order_id ? `开仓: ${trade.entry_order_id}` : ''} + {trade.entry_order_id && trade.exit_order_id ? ' / ' : ''} + {trade.exit_order_id ? `平仓: ${trade.exit_order_id}` : ''} + +
+ )} +
+
+
+ 入场: + {formatTime(trade.entry_time)} +
+ {trade.exit_time && ( +
+ 平仓: + {formatTime(trade.exit_time)} +
+ )} +
+
+ ) + })} +
+ +) +} +
+ ) +} + export default TradeList