Skip to content

Commit 4a583de

Browse files
SkillingXLindenWang
authored andcommitted
fix:完善aster账户净值和盈亏计算|Improve the calculation of the net value and profit/loss of the aster account (NoFxAiOS#695)
Co-authored-by: LindenWang <[email protected]>
1 parent bdc1065 commit 4a583de

8 files changed

Lines changed: 88 additions & 71 deletions

File tree

api/server.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,6 @@ func (s *Server) authMiddleware() gin.HandlerFunc {
14831483
return
14841484
}
14851485

1486-
14871486
tokenString := tokenParts[1]
14881487

14891488
// 黑名单检查

decision/engine.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ type Context struct {
8282

8383
// Decision AI的交易决策
8484
type Decision struct {
85-
Symbol string `json:"symbol"`
86-
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "update_stop_loss", "update_take_profit", "partial_close", "hold", "wait"
85+
Symbol string `json:"symbol"`
86+
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "update_stop_loss", "update_take_profit", "partial_close", "hold", "wait"
8787

8888
// 开仓参数
8989
Leverage int `json:"leverage,omitempty"`
@@ -92,14 +92,14 @@ type Decision struct {
9292
TakeProfit float64 `json:"take_profit,omitempty"`
9393

9494
// 调整参数(新增)
95-
NewStopLoss float64 `json:"new_stop_loss,omitempty"` // 用于 update_stop_loss
96-
NewTakeProfit float64 `json:"new_take_profit,omitempty"` // 用于 update_take_profit
97-
ClosePercentage float64 `json:"close_percentage,omitempty"` // 用于 partial_close (0-100)
95+
NewStopLoss float64 `json:"new_stop_loss,omitempty"` // 用于 update_stop_loss
96+
NewTakeProfit float64 `json:"new_take_profit,omitempty"` // 用于 update_take_profit
97+
ClosePercentage float64 `json:"close_percentage,omitempty"` // 用于 partial_close (0-100)
9898

9999
// 通用参数
100-
Confidence int `json:"confidence,omitempty"` // 信心度 (0-100)
101-
RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险
102-
Reasoning string `json:"reasoning"`
100+
Confidence int `json:"confidence,omitempty"` // 信心度 (0-100)
101+
RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险
102+
Reasoning string `json:"reasoning"`
103103
}
104104

105105
// FullDecision AI的完整决策(包含思维链)
@@ -691,8 +691,8 @@ func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoi
691691

692692
// ✅ 验证最小开仓金额(防止数量格式化为 0 的错误)
693693
// Binance 最小名义价值 10 USDT + 安全边际
694-
const minPositionSizeGeneral = 12.0 // 10 + 20% 安全边际
695-
const minPositionSizeBTCETH = 60.0 // BTC/ETH 因价格高和精度限制需要更大金额(更灵活)
694+
const minPositionSizeGeneral = 12.0 // 10 + 20% 安全边际
695+
const minPositionSizeBTCETH = 60.0 // BTC/ETH 因价格高和精度限制需要更大金额(更灵活)
696696

697697
if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" {
698698
if d.PositionSizeUSD < minPositionSizeBTCETH {

logger/telegram_sender.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ func NewTelegramSender(botToken string, chatID int64) (*TelegramSender, error) {
3333
sender := &TelegramSender{
3434
bot: bot,
3535
chatID: chatID,
36-
msgChan: make(chan string, 20), // 固定缓冲区大小: 20
37-
retryCount: 3, // 固定重试次数: 3
38-
retryInterval: 3 * time.Second, // 固定重试间隔: 3秒
36+
msgChan: make(chan string, 20), // 固定缓冲区大小: 20
37+
retryCount: 3, // 固定重试次数: 3
38+
retryInterval: 3 * time.Second, // 固定重试间隔: 3秒
3939
stopChan: make(chan struct{}),
4040
}
4141

mcp/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (client *Client) SetQwenAPIKey(apiKey string, customURL string, customModel
9696
client.Model = customModel
9797
log.Printf("🔧 [MCP] Qwen 使用自定义 Model: %s", customModel)
9898
} else {
99-
client.Model = "qwen3-max"
99+
client.Model = "qwen3-max"
100100
log.Printf("🔧 [MCP] Qwen 使用默认 Model: %s", client.Model)
101101
}
102102
// 打印 API Key 的前后各4位用于验证

trader/aster_trader.go

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -438,55 +438,78 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) {
438438
return nil, err
439439
}
440440

441-
// 🔍 调试:打印原始API响应
442-
log.Printf("🔍 Aster API原始响应: %s", string(body))
443-
444441
// 查找USDT余额
445-
totalBalance := 0.0
446442
availableBalance := 0.0
447443
crossUnPnl := 0.0
444+
crossWalletBalance := 0.0
445+
foundUSDT := false
448446

449447
for _, bal := range balances {
450-
// 🔍 调试:打印每条余额记录
451-
log.Printf("🔍 余额记录: %+v", bal)
452-
453448
if asset, ok := bal["asset"].(string); ok && asset == "USDT" {
454-
// 🔍 调试:打印USDT余额详情
455-
log.Printf("🔍 USDT余额详情: balance=%v, availableBalance=%v, crossUnPnl=%v",
456-
bal["balance"], bal["availableBalance"], bal["crossUnPnl"])
449+
foundUSDT = true
457450

458-
if wb, ok := bal["balance"].(string); ok {
459-
totalBalance, _ = strconv.ParseFloat(wb, 64)
460-
}
451+
// 解析Aster字段(参考: https://github.com/asterdex/api-docs)
461452
if avail, ok := bal["availableBalance"].(string); ok {
462453
availableBalance, _ = strconv.ParseFloat(avail, 64)
463454
}
464455
if unpnl, ok := bal["crossUnPnl"].(string); ok {
465456
crossUnPnl, _ = strconv.ParseFloat(unpnl, 64)
466457
}
458+
if cwb, ok := bal["crossWalletBalance"].(string); ok {
459+
crossWalletBalance, _ = strconv.ParseFloat(cwb, 64)
460+
}
467461
break
468462
}
469463
}
470464

471-
// ✅ Aster API完全兼容Binance API格式
472-
// balance字段 = wallet balance(不包含未实现盈亏)
473-
// crossUnPnl = unrealized profit(未实现盈亏)
474-
// crossWalletBalance = balance + crossUnPnl(全仓钱包余额,包含盈亏)
475-
//
476-
// 参考Binance官方文档:
477-
// - Account Information V2: marginBalance = walletBalance + unrealizedProfit
478-
// - Balance V3: crossWalletBalance = balance + crossUnPnl
465+
if !foundUSDT {
466+
log.Printf("⚠️ 未找到USDT资产记录!")
467+
}
468+
469+
// 获取持仓计算保证金占用和真实未实现盈亏
470+
positions, err := t.GetPositions()
471+
if err != nil {
472+
log.Printf("⚠️ 获取持仓信息失败: %v", err)
473+
// fallback: 无法获取持仓时使用简单计算
474+
return map[string]interface{}{
475+
"totalWalletBalance": crossWalletBalance,
476+
"availableBalance": availableBalance,
477+
"totalUnrealizedProfit": crossUnPnl,
478+
}, nil
479+
}
480+
481+
// ⚠️ 关键修复:从持仓中累加真正的未实现盈亏
482+
// Aster 的 crossUnPnl 字段不准确,需要从持仓数据中重新计算
483+
totalMarginUsed := 0.0
484+
realUnrealizedPnl := 0.0
485+
for _, pos := range positions {
486+
markPrice := pos["markPrice"].(float64)
487+
quantity := pos["positionAmt"].(float64)
488+
if quantity < 0 {
489+
quantity = -quantity
490+
}
491+
unrealizedPnl := pos["unRealizedProfit"].(float64)
492+
realUnrealizedPnl += unrealizedPnl
493+
494+
leverage := 10
495+
if lev, ok := pos["leverage"].(float64); ok {
496+
leverage = int(lev)
497+
}
498+
marginUsed := (quantity * markPrice) / float64(leverage)
499+
totalMarginUsed += marginUsed
500+
}
479501

480-
log.Printf("✓ Aster API返回: 钱包余额=%.2f, 未实现盈亏=%.2f, 可用余额=%.2f",
481-
totalBalance,
482-
crossUnPnl,
483-
availableBalance)
502+
// ✅ Aster 正确计算方式:
503+
// 总净值 = 可用余额 + 保证金占用
504+
// 钱包余额 = 总净值 - 未实现盈亏
505+
// 未实现盈亏 = 从持仓累加计算(不使用API的crossUnPnl)
506+
totalEquity := availableBalance + totalMarginUsed
507+
totalWalletBalance := totalEquity - realUnrealizedPnl
484508

485-
// 返回与Binance相同的字段名,确保AutoTrader能正确解析
486509
return map[string]interface{}{
487-
"totalWalletBalance": totalBalance, // 钱包余额(不含未实现盈亏)
488-
"availableBalance": availableBalance,
489-
"totalUnrealizedProfit": crossUnPnl, // 未实现盈亏
510+
"totalWalletBalance": totalWalletBalance, // 钱包余额(不含未实现盈亏)
511+
"availableBalance": availableBalance, // 可用余额
512+
"totalUnrealizedProfit": realUnrealizedPnl, // 未实现盈亏(从持仓累加)
490513
}, nil
491514
}
492515

@@ -1010,8 +1033,6 @@ func (t *AsterTrader) SetTakeProfit(symbol string, positionSide string, quantity
10101033
return err
10111034
}
10121035

1013-
1014-
10151036
// CancelStopLossOrders 仅取消止损单(不影响止盈单)
10161037
func (t *AsterTrader) CancelStopLossOrders(symbol string) error {
10171038
// 获取该币种的所有未完成订单

trader/auto_trader.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,16 @@ type AutoTrader struct {
9797
lastResetTime time.Time
9898
stopUntil time.Time
9999
isRunning bool
100-
startTime time.Time // 系统启动时间
101-
callCount int // AI调用次数
102-
positionFirstSeenTime map[string]int64 // 持仓首次出现时间 (symbol_side -> timestamp毫秒)
103-
stopMonitorCh chan struct{} // 用于停止监控goroutine
104-
monitorWg sync.WaitGroup // 用于等待监控goroutine结束
105-
peakPnLCache map[string]float64 // 最高收益缓存 (symbol -> 峰值盈亏百分比)
106-
peakPnLCacheMutex sync.RWMutex // 缓存读写锁
107-
lastBalanceSyncTime time.Time // 上次余额同步时间
108-
database interface{} // 数据库引用(用于自动更新余额)
109-
userID string // 用户ID
100+
startTime time.Time // 系统启动时间
101+
callCount int // AI调用次数
102+
positionFirstSeenTime map[string]int64 // 持仓首次出现时间 (symbol_side -> timestamp毫秒)
103+
stopMonitorCh chan struct{} // 用于停止监控goroutine
104+
monitorWg sync.WaitGroup // 用于等待监控goroutine结束
105+
peakPnLCache map[string]float64 // 最高收益缓存 (symbol -> 峰值盈亏百分比)
106+
peakPnLCacheMutex sync.RWMutex // 缓存读写锁
107+
lastBalanceSyncTime time.Time // 上次余额同步时间
108+
database interface{} // 数据库引用(用于自动更新余额)
109+
userID string // 用户ID
110110
}
111111

112112
// NewAutoTrader 创建自动交易器
@@ -436,7 +436,7 @@ func (at *AutoTrader) runCycle() error {
436436
})
437437
}
438438

439-
log.Print(strings.Repeat("=", 70))
439+
log.Print(strings.Repeat("=", 70))
440440
for _, coin := range ctx.CandidateCoins {
441441
record.CandidateCoins = append(record.CandidateCoins, coin.Symbol)
442442
}
@@ -465,11 +465,11 @@ func (at *AutoTrader) runCycle() error {
465465

466466
// 打印系统提示词和AI思维链(即使有错误,也要输出以便调试)
467467
if decision != nil {
468-
log.Print("\n" + strings.Repeat("=", 70) + "\n")
469-
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
470-
log.Println(strings.Repeat("=", 70))
471-
log.Println(decision.SystemPrompt)
472-
log.Println(strings.Repeat("=", 70))
468+
log.Print("\n" + strings.Repeat("=", 70) + "\n")
469+
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
470+
log.Println(strings.Repeat("=", 70))
471+
log.Println(decision.SystemPrompt)
472+
log.Println(strings.Repeat("=", 70))
473473

474474
if decision.CoTTrace != "" {
475475
log.Print("\n" + strings.Repeat("-", 70) + "\n")
@@ -508,9 +508,9 @@ func (at *AutoTrader) runCycle() error {
508508
// }
509509
// }
510510
log.Println()
511-
log.Print(strings.Repeat("-", 70))
511+
log.Print(strings.Repeat("-", 70))
512512
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
513-
log.Print(strings.Repeat("-", 70))
513+
log.Print(strings.Repeat("-", 70))
514514

515515
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
516516
sortedDecisions := sortDecisionsByPriority(decision.Decisions)

trader/binance_futures.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,6 @@ func (t *FuturesTrader) CloseShort(symbol string, quantity float64) (map[string]
491491
return result, nil
492492
}
493493

494-
495-
496494
// CancelStopLossOrders 仅取消止损单(不影响止盈单)
497495
func (t *FuturesTrader) CancelStopLossOrders(symbol string) error {
498496
// 获取该币种的所有未完成订单

trader/hyperliquid_trader.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
175175
// 原因:Spot 和 Perpetuals 是独立帐户,需手动 ClassTransfer 才能转账
176176
totalWalletBalance := walletBalanceWithoutUnrealized + spotUSDCBalance
177177

178-
result["totalWalletBalance"] = totalWalletBalance // 总资产(Perp + Spot)
179-
result["availableBalance"] = availableBalance // 可用余额(仅 Perpetuals,不含 Spot)
180-
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏(仅来自 Perpetuals)
181-
result["spotBalance"] = spotUSDCBalance // Spot 现货余额(单独返回)
178+
result["totalWalletBalance"] = totalWalletBalance // 总资产(Perp + Spot)
179+
result["availableBalance"] = availableBalance // 可用余额(仅 Perpetuals,不含 Spot)
180+
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏(仅来自 Perpetuals)
181+
result["spotBalance"] = spotUSDCBalance // Spot 现货余额(单独返回)
182182

183183
log.Printf("✓ Hyperliquid 完整账户:")
184184
log.Printf(" • Spot 现货余额: %.2f USDC (需手动转账到 Perpetuals 才能开仓)", spotUSDCBalance)
@@ -551,7 +551,6 @@ func (t *HyperliquidTrader) CloseShort(symbol string, quantity float64) (map[str
551551

552552
// CancelStopOrders 取消该币种的止盈/止
553553

554-
555554
// CancelStopLossOrders 仅取消止损单(Hyperliquid 暂无法区分止损和止盈,取消所有)
556555
func (t *HyperliquidTrader) CancelStopLossOrders(symbol string) error {
557556
// Hyperliquid SDK 的 OpenOrder 结构不暴露 trigger 字段

0 commit comments

Comments
 (0)