fix(stats): fixed the PNL calculation (#963)

This commit is contained in:
Diego
2025-11-13 01:27:13 -05:00
committed by tangmengqiu
parent 970cfaadf3
commit fc8a4d3d63
11 changed files with 549 additions and 304 deletions

View File

@@ -286,101 +286,6 @@ func (at *AutoTrader) Stop() {
log.Println("⏹ 自动交易系统停止")
}
// autoSyncBalanceIfNeeded 自动同步余额每10分钟检查一次变化>5%才更新)
func (at *AutoTrader) autoSyncBalanceIfNeeded() {
// 距离上次同步不足10分钟跳过
if time.Since(at.lastBalanceSyncTime) < 10*time.Minute {
return
}
log.Printf("🔄 [%s] 开始自动检查余额变化...", at.name)
// 查询实际余额
balanceInfo, err := at.trader.GetBalance()
if err != nil {
log.Printf("⚠️ [%s] 查询余额失败: %v", at.name, err)
at.lastBalanceSyncTime = time.Now() // 即使失败也更新时间,避免频繁重试
return
}
// 提取可用余额
var actualBalance float64
if availableBalance, ok := balanceInfo["available_balance"].(float64); ok && availableBalance > 0 {
actualBalance = availableBalance
} else if availableBalance, ok := balanceInfo["availableBalance"].(float64); ok && availableBalance > 0 {
actualBalance = availableBalance
} else if totalBalance, ok := balanceInfo["balance"].(float64); ok && totalBalance > 0 {
actualBalance = totalBalance
} else {
log.Printf("⚠️ [%s] 无法提取可用余额", at.name)
at.lastBalanceSyncTime = time.Now()
return
}
oldBalance := at.initialBalance
// 防止除以零:如果初始余额无效,直接更新为实际余额
if oldBalance <= 0 {
log.Printf("⚠️ [%s] 初始余额无效 (%.2f),直接更新为实际余额 %.2f USDT", at.name, oldBalance, actualBalance)
at.initialBalance = actualBalance
if at.database != nil {
type DatabaseUpdater interface {
UpdateTraderInitialBalance(userID, id string, newBalance float64) error
}
if db, ok := at.database.(DatabaseUpdater); ok {
if err := db.UpdateTraderInitialBalance(at.userID, at.id, actualBalance); err != nil {
log.Printf("❌ [%s] 更新数据库失败: %v", at.name, err)
} else {
log.Printf("✅ [%s] 已自动同步余额到数据库", at.name)
}
} else {
log.Printf("⚠️ [%s] 数据库类型不支持UpdateTraderInitialBalance接口", at.name)
}
} else {
log.Printf("⚠️ [%s] 数据库引用为空,余额仅在内存中更新", at.name)
}
at.lastBalanceSyncTime = time.Now()
return
}
changePercent := ((actualBalance - oldBalance) / oldBalance) * 100
// 变化超过5%才更新
if math.Abs(changePercent) > 5.0 {
log.Printf("🔔 [%s] 检测到余额大幅变化: %.2f → %.2f USDT (%.2f%%)",
at.name, oldBalance, actualBalance, changePercent)
// 更新内存中的 initialBalance
at.initialBalance = actualBalance
// 更新数据库(需要类型断言)
if at.database != nil {
// 这里需要根据实际的数据库类型进行类型断言
// 由于使用了 interface{},我们需要在 TraderManager 层面处理更新
// 或者在这里进行类型检查
type DatabaseUpdater interface {
UpdateTraderInitialBalance(userID, id string, newBalance float64) error
}
if db, ok := at.database.(DatabaseUpdater); ok {
err := db.UpdateTraderInitialBalance(at.userID, at.id, actualBalance)
if err != nil {
log.Printf("❌ [%s] 更新数据库失败: %v", at.name, err)
} else {
log.Printf("✅ [%s] 已自动同步余额到数据库", at.name)
}
} else {
log.Printf("⚠️ [%s] 数据库类型不支持UpdateTraderInitialBalance接口", at.name)
}
} else {
log.Printf("⚠️ [%s] 数据库引用为空,余额仅在内存中更新", at.name)
}
} else {
log.Printf("✓ [%s] 余额变化不大 (%.2f%%),无需更新", at.name, changePercent)
}
at.lastBalanceSyncTime = time.Now()
}
// runCycle 运行一个交易周期使用AI全权决策
func (at *AutoTrader) runCycle() error {
at.callCount++
@@ -412,9 +317,6 @@ func (at *AutoTrader) runCycle() error {
log.Println("📅 日盈亏已重置")
}
// 3. 自动同步余额每10分钟检查一次充值/提现后自动更新)
at.autoSyncBalanceIfNeeded()
// 4. 收集交易上下文
ctx, err := at.buildTradingContext()
if err != nil {
@@ -426,11 +328,12 @@ func (at *AutoTrader) runCycle() error {
// 保存账户状态快照
record.AccountState = logger.AccountSnapshot{
TotalBalance: ctx.Account.TotalEquity,
TotalBalance: ctx.Account.TotalEquity - ctx.Account.UnrealizedPnL,
AvailableBalance: ctx.Account.AvailableBalance,
TotalUnrealizedProfit: ctx.Account.TotalPnL,
TotalUnrealizedProfit: ctx.Account.UnrealizedPnL,
PositionCount: ctx.Account.PositionCount,
MarginUsedPct: ctx.Account.MarginUsedPct,
InitialBalance: at.initialBalance, // 记录当时的初始余额基准
}
// 保存持仓快照
@@ -714,6 +617,7 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) {
Account: decision.AccountInfo{
TotalEquity: totalEquity,
AvailableBalance: availableBalance,
UnrealizedPnL: totalUnrealizedProfit,
TotalPnL: totalPnL,
TotalPnLPct: totalPnLPct,
MarginUsed: totalMarginUsed,
@@ -1361,7 +1265,7 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
}
totalMarginUsed := 0.0
totalUnrealizedPnL := 0.0
totalUnrealizedPnLCalculated := 0.0
for _, pos := range positions {
markPrice := pos["markPrice"].(float64)
quantity := pos["positionAmt"].(float64)
@@ -1369,7 +1273,7 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
quantity = -quantity
}
unrealizedPnl := pos["unRealizedProfit"].(float64)
totalUnrealizedPnL += unrealizedPnl
totalUnrealizedPnLCalculated += unrealizedPnl
leverage := 10
if lev, ok := pos["leverage"].(float64); ok {
@@ -1379,10 +1283,19 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
totalMarginUsed += marginUsed
}
// 验证未实现盈亏的一致性API值 vs 从持仓计算)
diff := math.Abs(totalUnrealizedProfit - totalUnrealizedPnLCalculated)
if diff > 0.1 { // 允许0.01 USDT的误差
log.Printf("⚠️ 未实现盈亏不一致: API=%.4f, 计算=%.4f, 差异=%.4f",
totalUnrealizedProfit, totalUnrealizedPnLCalculated, diff)
}
totalPnL := totalEquity - at.initialBalance
totalPnLPct := 0.0
if at.initialBalance > 0 {
totalPnLPct = (totalPnL / at.initialBalance) * 100
} else {
log.Printf("⚠️ Initial Balance异常: %.2f无法计算PNL百分比", at.initialBalance)
}
marginUsedPct := 0.0
@@ -1394,15 +1307,14 @@ func (at *AutoTrader) GetAccountInfo() (map[string]interface{}, error) {
// 核心字段
"total_equity": totalEquity, // 账户净值 = wallet + unrealized
"wallet_balance": totalWalletBalance, // 钱包余额(不含未实现盈亏)
"unrealized_profit": totalUnrealizedProfit, // 未实现盈亏(从API
"unrealized_profit": totalUnrealizedProfit, // 未实现盈亏(交易所API官方值
"available_balance": availableBalance, // 可用余额
// 盈亏统计
"total_pnl": totalPnL, // 总盈亏 = equity - initial
"total_pnl_pct": totalPnLPct, // 总盈亏百分比
"total_unrealized_pnl": totalUnrealizedPnL, // 未实现盈亏(从持仓计算)
"initial_balance": at.initialBalance, // 初始余额
"daily_pnl": at.dailyPnL, // 日盈亏
"total_pnl": totalPnL, // 总盈亏 = equity - initial
"total_pnl_pct": totalPnLPct, // 总盈亏百分比
"initial_balance": at.initialBalance, // 初始余额
"daily_pnl": at.dailyPnL, // 日盈亏
// 持仓信息
"position_count": len(positions), // 持仓数量