mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-09 14:00:57 +08:00
fix(stats): fixed the PNL calculation (#963)
This commit is contained in:
@@ -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), // 持仓数量
|
||||
|
||||
Reference in New Issue
Block a user