diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index 4005de7..eb8bba2 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -223,25 +223,38 @@ async def get_trade_stats( trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id) closed_trades = [t for t in trades if t['status'] == 'closed'] + # 辅助函数:计算净盈亏(优先使用 realized_pnl - commission) + def get_net_pnl(t): + pnl = float(t.get('pnl') or 0) + realized = t.get('realized_pnl') + if realized is not None: + pnl = float(realized) + commission = float(t.get('commission') or 0) + commission_asset = t.get('commission_asset') + # 如果手续费是 USDT,则扣除 + if commission_asset == 'USDT': + pnl -= commission + return pnl + # 排除0盈亏的订单(abs(pnl) < 0.01 USDT视为0盈亏),这些订单不应该影响胜率统计 ZERO_PNL_THRESHOLD = 0.01 # 0.01 USDT的阈值,小于此值视为0盈亏 - meaningful_trades = [t for t in closed_trades if abs(float(t['pnl'])) >= ZERO_PNL_THRESHOLD] - zero_pnl_trades = [t for t in closed_trades if abs(float(t['pnl'])) < ZERO_PNL_THRESHOLD] + meaningful_trades = [t for t in closed_trades if abs(get_net_pnl(t)) >= ZERO_PNL_THRESHOLD] + zero_pnl_trades = [t for t in closed_trades if abs(get_net_pnl(t)) < ZERO_PNL_THRESHOLD] # 只统计有意义的交易(排除0盈亏)的胜率 - win_trades = [t for t in meaningful_trades if float(t['pnl']) > 0] - loss_trades = [t for t in meaningful_trades if float(t['pnl']) < 0] + win_trades = [t for t in meaningful_trades if get_net_pnl(t) > 0] + loss_trades = [t for t in meaningful_trades if get_net_pnl(t) < 0] # 盈利/亏损均值(用于观察是否接近 3:1) - avg_win_pnl = sum(float(t["pnl"]) for t in win_trades) / len(win_trades) if win_trades else 0.0 + avg_win_pnl = sum(get_net_pnl(t) for t in win_trades) / len(win_trades) if win_trades else 0.0 avg_loss_pnl_abs = ( - sum(abs(float(t["pnl"])) for t in loss_trades) / len(loss_trades) if loss_trades else 0.0 + sum(abs(get_net_pnl(t)) for t in loss_trades) / len(loss_trades) if loss_trades else 0.0 ) win_loss_ratio = (avg_win_pnl / avg_loss_pnl_abs) if avg_loss_pnl_abs > 0 else None # 实际盈亏比(所有盈利单的总盈利 / 所有亏损单的总亏损,必须 > 1.5,目标 2.5-3.0) - total_win_pnl = sum(float(t["pnl"]) for t in win_trades) if win_trades else 0.0 - total_loss_pnl_abs = sum(abs(float(t["pnl"])) for t in loss_trades) if loss_trades else 0.0 + total_win_pnl = sum(get_net_pnl(t) for t in win_trades) if win_trades else 0.0 + total_loss_pnl_abs = sum(abs(get_net_pnl(t)) for t in loss_trades) if loss_trades else 0.0 actual_profit_loss_ratio = (total_win_pnl / total_loss_pnl_abs) if total_loss_pnl_abs > 0 else None # 盈利因子(总盈利金额 / 总亏损金额,必须 > 1.1,目标 1.5+) @@ -286,8 +299,8 @@ async def get_trade_stats( "win_trades": len(win_trades), "loss_trades": len(loss_trades), "win_rate": len(win_trades) / len(meaningful_trades) * 100 if meaningful_trades else 0, # 基于有意义的交易计算胜率 - "total_pnl": sum(float(t['pnl']) for t in closed_trades), - "avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0, + "total_pnl": sum(get_net_pnl(t) for t in closed_trades), + "avg_pnl": sum(get_net_pnl(t) for t in closed_trades) / len(closed_trades) if closed_trades else 0, # 额外统计:盈利单均值 vs 亏损单均值(绝对值)以及比值(目标 3:1) "avg_win_pnl": avg_win_pnl, "avg_loss_pnl_abs": avg_loss_pnl_abs, diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index 65bbe30..e81db5e 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -1151,17 +1151,7 @@ const GlobalConfig = () => {

系统控制

-
- - 交易系统 {systemStatus?.running ? '运行中' : '未运行'} - - {systemStatus?.pid ? PID: {systemStatus.pid} : null} - - - 后端服务 {backendStatus?.running ? '运行中' : '未知'} - - {backendStatus?.pid ? PID: {backendStatus.pid} : null} -
+ {/* 交易系统状态展示已移除 */}
diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index d24246e..b586d08 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -480,8 +480,20 @@ const TradeList = () => { ? parseFloat(trade.margin_usdt) : (leverage > 0 ? notional / leverage : 0) - // 计算盈亏比例(盈亏/保证金) - const pnl = parseFloat(trade.pnl || 0) + // 计算盈亏(优先使用币安实际结算盈亏 realized_pnl - commission) + let pnl = parseFloat(trade.pnl || 0) + const realizedPnl = trade.realized_pnl !== undefined && trade.realized_pnl !== null ? parseFloat(trade.realized_pnl) : null + const commission = trade.commission !== undefined && trade.commission !== null ? parseFloat(trade.commission) : 0 + const commissionAsset = trade.commission_asset || 'USDT' + + if (realizedPnl !== null) { + pnl = realizedPnl + // 如果手续费是 USDT,则从盈亏中扣除(得到净值) + if (commissionAsset === 'USDT') { + pnl -= commission + } + } + const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0 // 格式化时间为北京时间 @@ -566,7 +578,20 @@ const TradeList = () => { 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) + + // 计算盈亏(优先使用币安实际结算盈亏 realized_pnl - commission) + let pnl = parseFloat(trade.pnl || 0) + const realizedPnl = trade.realized_pnl !== undefined && trade.realized_pnl !== null ? parseFloat(trade.realized_pnl) : null + const commission = trade.commission !== undefined && trade.commission !== null ? parseFloat(trade.commission) : 0 + const commissionAsset = trade.commission_asset || 'USDT' + + if (realizedPnl !== null) { + pnl = realizedPnl + if (commissionAsset === 'USDT') { + pnl -= commission + } + } + const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0 // 格式化时间为北京时间