a
This commit is contained in:
parent
449ad01ede
commit
48f6ab4fea
|
|
@ -223,25 +223,38 @@ async def get_trade_stats(
|
||||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id)
|
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']
|
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盈亏),这些订单不应该影响胜率统计
|
# 排除0盈亏的订单(abs(pnl) < 0.01 USDT视为0盈亏),这些订单不应该影响胜率统计
|
||||||
ZERO_PNL_THRESHOLD = 0.01 # 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]
|
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(float(t['pnl'])) < ZERO_PNL_THRESHOLD]
|
zero_pnl_trades = [t for t in closed_trades if abs(get_net_pnl(t)) < ZERO_PNL_THRESHOLD]
|
||||||
|
|
||||||
# 只统计有意义的交易(排除0盈亏)的胜率
|
# 只统计有意义的交易(排除0盈亏)的胜率
|
||||||
win_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 float(t['pnl']) < 0]
|
loss_trades = [t for t in meaningful_trades if get_net_pnl(t) < 0]
|
||||||
|
|
||||||
# 盈利/亏损均值(用于观察是否接近 3:1)
|
# 盈利/亏损均值(用于观察是否接近 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 = (
|
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
|
win_loss_ratio = (avg_win_pnl / avg_loss_pnl_abs) if avg_loss_pnl_abs > 0 else None
|
||||||
|
|
||||||
# 实际盈亏比(所有盈利单的总盈利 / 所有亏损单的总亏损,必须 > 1.5,目标 2.5-3.0)
|
# 实际盈亏比(所有盈利单的总盈利 / 所有亏损单的总亏损,必须 > 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_win_pnl = sum(get_net_pnl(t) 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_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
|
actual_profit_loss_ratio = (total_win_pnl / total_loss_pnl_abs) if total_loss_pnl_abs > 0 else None
|
||||||
|
|
||||||
# 盈利因子(总盈利金额 / 总亏损金额,必须 > 1.1,目标 1.5+)
|
# 盈利因子(总盈利金额 / 总亏损金额,必须 > 1.1,目标 1.5+)
|
||||||
|
|
@ -286,8 +299,8 @@ async def get_trade_stats(
|
||||||
"win_trades": len(win_trades),
|
"win_trades": len(win_trades),
|
||||||
"loss_trades": len(loss_trades),
|
"loss_trades": len(loss_trades),
|
||||||
"win_rate": len(win_trades) / len(meaningful_trades) * 100 if meaningful_trades else 0, # 基于有意义的交易计算胜率
|
"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),
|
"total_pnl": sum(get_net_pnl(t) for t in closed_trades),
|
||||||
"avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
|
"avg_pnl": sum(get_net_pnl(t) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
|
||||||
# 额外统计:盈利单均值 vs 亏损单均值(绝对值)以及比值(目标 3:1)
|
# 额外统计:盈利单均值 vs 亏损单均值(绝对值)以及比值(目标 3:1)
|
||||||
"avg_win_pnl": avg_win_pnl,
|
"avg_win_pnl": avg_win_pnl,
|
||||||
"avg_loss_pnl_abs": avg_loss_pnl_abs,
|
"avg_loss_pnl_abs": avg_loss_pnl_abs,
|
||||||
|
|
|
||||||
|
|
@ -1151,17 +1151,7 @@ const GlobalConfig = () => {
|
||||||
<section className="global-section system-section">
|
<section className="global-section system-section">
|
||||||
<div className="system-header">
|
<div className="system-header">
|
||||||
<h3>系统控制</h3>
|
<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>
|
||||||
|
|
||||||
<div className="system-control-group">
|
<div className="system-control-group">
|
||||||
|
|
|
||||||
|
|
@ -480,8 +480,20 @@ const TradeList = () => {
|
||||||
? parseFloat(trade.margin_usdt)
|
? parseFloat(trade.margin_usdt)
|
||||||
: (leverage > 0 ? notional / leverage : 0)
|
: (leverage > 0 ? notional / leverage : 0)
|
||||||
|
|
||||||
// 计算盈亏比例(盈亏/保证金)
|
// 计算盈亏(优先使用币安实际结算盈亏 realized_pnl - commission)
|
||||||
const pnl = parseFloat(trade.pnl || 0)
|
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
|
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||||
|
|
||||||
// 格式化时间为北京时间
|
// 格式化时间为北京时间
|
||||||
|
|
@ -566,7 +578,20 @@ const TradeList = () => {
|
||||||
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
const margin = trade.margin_usdt !== undefined && trade.margin_usdt !== null
|
||||||
? parseFloat(trade.margin_usdt)
|
? parseFloat(trade.margin_usdt)
|
||||||
: (leverage > 0 ? notional / leverage : 0)
|
: (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
|
const pnlPercent = margin > 0 ? (pnl / margin) * 100 : 0
|
||||||
|
|
||||||
// 格式化时间为北京时间
|
// 格式化时间为北京时间
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user