mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-05 12:00:59 +08:00
fix(hyperliquid): complete balance detection with 4 critical fixes
## 🎯 完整修復 Hyperliquid 餘額檢測的所有問題 ### 修復 1: ✅ 動態選擇保證金摘要 **問題**: 硬編碼使用 MarginSummary,但預設全倉模式 **修復**: 根據 isCrossMargin 動態選擇 - 全倉模式 → CrossMarginSummary - 逐倉模式 → MarginSummary ### 修復 2: ✅ 查詢 Spot 現貨帳戶 **問題**: 只查詢 Perpetuals,忽略 Spot 餘額 **修復**: 使用 SpotUserState() 查詢 USDC 現貨餘額 - 合併 Spot + Perpetuals 總餘額 - 解決用戶反饋「錢包有錢但顯示 0」的問題 ### 修復 3: ✅ 使用 Withdrawable 欄位 **問題**: 簡單計算 availableBalance = accountValue - totalMarginUsed 不可靠 **修復**: 優先使用官方 Withdrawable 欄位 - 整合 PR #443 的邏輯 - 降級方案:Withdrawable 不可用時才使用簡單計算 - 防止負數餘額 ### 修復 4: ✅ 清理混亂註釋 **問題**: 註釋說 CrossMarginSummary 但代碼用 MarginSummary **修復**: 根據實際使用的摘要類型動態輸出日誌 ## 📊 修復對比 | 問題 | 修復前 | 修復後 | |------|--------|--------| | 保證金摘要選擇 | ❌ 硬編碼 MarginSummary | ✅ 動態選擇 | | Spot 餘額查詢 | ❌ 從未查詢 | ✅ 完整查詢 | | 可用餘額計算 | ❌ 簡單相減 | ✅ 使用 Withdrawable | | 日誌註釋 | ❌ 不一致 | ✅ 準確清晰 | ## 🧪 測試場景 - ✅ Spot 有錢,Perp 沒錢 → 正確顯示 Spot 餘額 - ✅ Spot 沒錢,Perp 有錢 → 正確顯示 Perp 餘額 - ✅ 兩者都有錢 → 正確合併顯示 - ✅ 全倉模式 → 使用 CrossMarginSummary - ✅ 逐倉模式 → 使用 MarginSummary ## 相關 Issue 解決用戶反饋:「錢包中有幣卻沒被檢測到」 整合以下未合併的修復: - PR #443: Withdrawable 欄位優先 - Spot 餘額遺漏問題 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
@@ -76,23 +76,54 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
|
||||
func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
|
||||
log.Printf("🔄 正在调用Hyperliquid API获取账户余额...")
|
||||
|
||||
// 获取账户状态
|
||||
// ✅ Step 1: 查询 Spot 现货账户余额
|
||||
spotState, err := t.exchange.Info().SpotUserState(t.ctx, t.walletAddr)
|
||||
var spotUSDCBalance float64 = 0.0
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 查询 Spot 余额失败(可能无现货资产): %v", err)
|
||||
} else if spotState != nil && len(spotState.Balances) > 0 {
|
||||
for _, balance := range spotState.Balances {
|
||||
if balance.Coin == "USDC" {
|
||||
spotUSDCBalance, _ = strconv.ParseFloat(balance.Total, 64)
|
||||
log.Printf("✓ 发现 Spot 现货余额: %.2f USDC", spotUSDCBalance)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Step 2: 查询 Perpetuals 合约账户状态
|
||||
accountState, err := t.exchange.Info().UserState(t.ctx, t.walletAddr)
|
||||
if err != nil {
|
||||
log.Printf("❌ Hyperliquid API调用失败: %v", err)
|
||||
log.Printf("❌ Hyperliquid Perpetuals API调用失败: %v", err)
|
||||
return nil, fmt.Errorf("获取账户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 解析余额信息(MarginSummary字段都是string)
|
||||
result := make(map[string]interface{})
|
||||
|
||||
// 🔍 调试:打印API返回的完整CrossMarginSummary结构
|
||||
summaryJSON, _ := json.MarshalIndent(accountState.MarginSummary, " ", " ")
|
||||
log.Printf("🔍 [DEBUG] Hyperliquid API CrossMarginSummary完整数据:")
|
||||
log.Printf("%s", string(summaryJSON))
|
||||
// ✅ Step 3: 根据保证金模式动态选择正确的摘要(CrossMarginSummary 或 MarginSummary)
|
||||
var accountValue, totalMarginUsed float64
|
||||
var summaryType string
|
||||
var summary interface{}
|
||||
|
||||
accountValue, _ := strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64)
|
||||
totalMarginUsed, _ := strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64)
|
||||
if t.isCrossMargin {
|
||||
// 全仓模式:使用 CrossMarginSummary
|
||||
accountValue, _ = strconv.ParseFloat(accountState.CrossMarginSummary.AccountValue, 64)
|
||||
totalMarginUsed, _ = strconv.ParseFloat(accountState.CrossMarginSummary.TotalMarginUsed, 64)
|
||||
summaryType = "CrossMarginSummary (全仓)"
|
||||
summary = accountState.CrossMarginSummary
|
||||
} else {
|
||||
// 逐仓模式:使用 MarginSummary
|
||||
accountValue, _ = strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64)
|
||||
totalMarginUsed, _ = strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64)
|
||||
summaryType = "MarginSummary (逐仓)"
|
||||
summary = accountState.MarginSummary
|
||||
}
|
||||
|
||||
// 🔍 调试:打印API返回的完整摘要结构
|
||||
summaryJSON, _ := json.MarshalIndent(summary, " ", " ")
|
||||
log.Printf("🔍 [DEBUG] Hyperliquid API %s 完整数据:", summaryType)
|
||||
log.Printf("%s", string(summaryJSON))
|
||||
|
||||
// ⚠️ 关键修复:从所有持仓中累加真正的未实现盈亏
|
||||
totalUnrealizedPnl := 0.0
|
||||
@@ -109,16 +140,47 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) {
|
||||
// 需要返回"不包含未实现盈亏的钱包余额"
|
||||
walletBalanceWithoutUnrealized := accountValue - totalUnrealizedPnl
|
||||
|
||||
result["totalWalletBalance"] = walletBalanceWithoutUnrealized // 钱包余额(不含未实现盈亏)
|
||||
result["availableBalance"] = accountValue - totalMarginUsed // 可用余额(总净值 - 占用保证金)
|
||||
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏
|
||||
// ✅ Step 4: 使用 Withdrawable 欄位(PR #443)
|
||||
// Withdrawable 是官方提供的真实可提现余额,比简单计算更可靠
|
||||
availableBalance := 0.0
|
||||
if accountState.Withdrawable != "" {
|
||||
withdrawable, err := strconv.ParseFloat(accountState.Withdrawable, 64)
|
||||
if err == nil && withdrawable > 0 {
|
||||
availableBalance = withdrawable
|
||||
log.Printf("✓ 使用 Withdrawable 作为可用余额: %.2f", availableBalance)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✓ Hyperliquid 账户: 总净值=%.2f (钱包%.2f+未实现%.2f), 可用=%.2f, 保证金占用=%.2f",
|
||||
// 降级方案:如果没有 Withdrawable,使用简单计算
|
||||
if availableBalance == 0 && accountState.Withdrawable == "" {
|
||||
availableBalance = accountValue - totalMarginUsed
|
||||
if availableBalance < 0 {
|
||||
log.Printf("⚠️ 计算出的可用余额为负数 (%.2f),重置为 0", availableBalance)
|
||||
availableBalance = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Step 5: 正確處理 Spot + Perpetuals 余额
|
||||
// 重要:Spot 只加到總資產,不加到可用餘額
|
||||
// 原因:Spot 和 Perpetuals 是獨立帳戶,需手動 ClassTransfer 才能轉帳
|
||||
totalWalletBalance := walletBalanceWithoutUnrealized + spotUSDCBalance
|
||||
|
||||
result["totalWalletBalance"] = totalWalletBalance // 總資產(Perp + Spot)
|
||||
result["availableBalance"] = availableBalance // 可用餘額(僅 Perpetuals,不含 Spot)
|
||||
result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未實現盈虧(僅來自 Perpetuals)
|
||||
result["spotBalance"] = spotUSDCBalance // Spot 現貨餘額(單獨返回)
|
||||
|
||||
log.Printf("✓ Hyperliquid 完整账户:")
|
||||
log.Printf(" • Spot 现货余额: %.2f USDC (需手动转账到 Perpetuals 才能开仓)", spotUSDCBalance)
|
||||
log.Printf(" • Perpetuals 合约净值: %.2f USDC (钱包%.2f + 未实现%.2f)",
|
||||
accountValue,
|
||||
walletBalanceWithoutUnrealized,
|
||||
totalUnrealizedPnl,
|
||||
result["availableBalance"],
|
||||
totalMarginUsed)
|
||||
totalUnrealizedPnl)
|
||||
log.Printf(" • Perpetuals 可用余额: %.2f USDC (可直接用於開倉)", availableBalance)
|
||||
log.Printf(" • 保证金占用: %.2f USDC", totalMarginUsed)
|
||||
log.Printf(" • 總資產 (Perp+Spot): %.2f USDC", totalWalletBalance)
|
||||
log.Printf(" ⭐ 总资产: %.2f USDC | Perp 可用: %.2f USDC | Spot 余额: %.2f USDC",
|
||||
totalWalletBalance, availableBalance, spotUSDCBalance)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user