This commit is contained in:
薇薇安 2026-02-03 10:13:18 +08:00
parent 449ad01ede
commit 48f6ab4fea
3 changed files with 52 additions and 24 deletions

View File

@ -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,

View File

@ -1151,17 +1151,7 @@ const GlobalConfig = () => {
<section className="global-section system-section">
<div className="system-header">
<h3>系统控制</h3>
<div className="system-status">
<span className={`system-status-badge ${systemStatus?.running ? 'running' : 'stopped'}`}>
交易系统 {systemStatus?.running ? '运行中' : '未运行'}
</span>
{systemStatus?.pid ? <span className="system-status-meta">PID: {systemStatus.pid}</span> : null}
<span className={`system-status-badge ${backendStatus?.running ? 'running' : 'stopped'}`} style={{ marginLeft: '10px' }}>
后端服务 {backendStatus?.running ? '运行中' : '未知'}
</span>
{backendStatus?.pid ? <span className="system-status-meta">PID: {backendStatus.pid}</span> : null}
</div>
{/* 交易系统状态展示已移除 */}
</div>
<div className="system-control-group">

View File

@ -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
//