From 93e9b505cfe2fa120bdf7c8d02c5c491d78df842 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:30:00 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(api):=20query=20actual=20exchange=20bal?= =?UTF-8?q?ance=20when=20creating=20trader=20Problem:=20-=20Users=20could?= =?UTF-8?q?=20input=20arbitrary=20initial=20balance=20when=20creating=20tr?= =?UTF-8?q?aders=20-=20This=20didn't=20reflect=20the=20actual=20available?= =?UTF-8?q?=20balance=20in=20exchange=20account=20-=20Could=20lead=20to=20?= =?UTF-8?q?incorrect=20position=20sizing=20and=20risk=20calculations=20Sol?= =?UTF-8?q?ution:=20-=20Before=20creating=20trader,=20query=20exchange=20A?= =?UTF-8?q?PI=20for=20actual=20balance=20-=20Use=20GetBalance()=20from=20r?= =?UTF-8?q?espective=20trader=20implementation:=20=20=20*=20Binance:=20New?= =?UTF-8?q?FuturesTrader=20+=20GetBalance()=20=20=20*=20Hyperliquid:=20New?= =?UTF-8?q?HyperliquidTrader=20+=20GetBalance()=20=20=20*=20Aster:=20NewAs?= =?UTF-8?q?terTrader=20+=20GetBalance()=20-=20Extract=20'available=5Fbalan?= =?UTF-8?q?ce'=20or=20'balance'=20from=20response=20-=20Override=20user=20?= =?UTF-8?q?input=20with=20actual=20balance=20-=20Fallback=20to=20user=20in?= =?UTF-8?q?put=20if=20query=20fails=20Changes:=20-=20Added=20'nofx/trader'?= =?UTF-8?q?=20import=20-=20Query=20GetExchanges()=20to=20find=20matching?= =?UTF-8?q?=20exchange=20config=20-=20Create=20temporary=20trader=20instan?= =?UTF-8?q?ce=20based=20on=20exchange=20type=20-=20Call=20GetBalance()=20t?= =?UTF-8?q?o=20fetch=20actual=20available=20balance=20-=20Use=20actualBala?= =?UTF-8?q?nce=20instead=20of=20req.InitialBalance=20-=20Comprehensive=20e?= =?UTF-8?q?rror=20handling=20with=20fallback=20logic=20Benefits:=20-=20?= =?UTF-8?q?=E2=9C=85=20Ensures=20accurate=20initial=20balance=20matches=20?= =?UTF-8?q?exchange=20account=20-=20=E2=9C=85=20Prevents=20user=20errors?= =?UTF-8?q?=20in=20balance=20input=20-=20=E2=9C=85=20Improves=20position?= =?UTF-8?q?=20sizing=20accuracy=20-=20=E2=9C=85=20Maintains=20data=20integ?= =?UTF-8?q?rity=20between=20system=20and=20exchange=20Example=20logs:=20?= =?UTF-8?q?=E2=9C=93=20=E6=9F=A5=E8=AF=A2=E5=88=B0=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E6=89=80=E5=AE=9E=E9=99=85=E4=BD=99=E9=A2=9D:=20150.00=20USDT?= =?UTF-8?q?=20(=E7=94=A8=E6=88=B7=E8=BE=93=E5=85=A5:=20100.00=20USDT)=20?= =?UTF-8?q?=E2=9A=A0=EF=B8=8F=20=E6=9F=A5=E8=AF=A2=E4=BA=A4=E6=98=93?= =?UTF-8?q?=E6=89=80=E4=BD=99=E9=A2=9D=E5=A4=B1=E8=B4=A5=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=94=A8=E6=88=B7=E8=BE=93=E5=85=A5=E7=9A=84=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E8=B5=84=E9=87=91:=20connection=20timeout=20Co-Author?= =?UTF-8?q?ed-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/server.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/api/server.go b/api/server.go index 94ae4a60..d7f0512d 100644 --- a/api/server.go +++ b/api/server.go @@ -9,6 +9,7 @@ import ( "nofx/config" "nofx/decision" "nofx/manager" + "nofx/trader" "strconv" "strings" "time" @@ -347,6 +348,73 @@ func (s *Server) handleCreateTrader(c *gin.Context) { scanIntervalMinutes = 3 // 默认3分钟 } + // ✨ 查询交易所实际余额,覆盖用户输入 + actualBalance := req.InitialBalance // 默认使用用户输入 + exchanges, err := s.database.GetExchanges(userID) + if err != nil { + log.Printf("⚠️ 获取交易所配置失败,使用用户输入的初始资金: %v", err) + } + + // 查找匹配的交易所配置 + var exchangeCfg *config.ExchangeConfig + for _, ex := range exchanges { + if ex.ID == req.ExchangeID { + exchangeCfg = ex + break + } + } + + if exchangeCfg == nil { + log.Printf("⚠️ 未找到交易所 %s 的配置,使用用户输入的初始资金", req.ExchangeID) + } else if !exchangeCfg.Enabled { + log.Printf("⚠️ 交易所 %s 未启用,使用用户输入的初始资金", req.ExchangeID) + } else { + // 根据交易所类型创建临时 trader 查询余额 + var tempTrader trader.Trader + var createErr error + + switch req.ExchangeID { + case "binance": + tempTrader = trader.NewFuturesTrader(exchangeCfg.APIKey, exchangeCfg.SecretKey) + case "hyperliquid": + tempTrader, createErr = trader.NewHyperliquidTrader( + exchangeCfg.APIKey, // private key + exchangeCfg.HyperliquidWalletAddr, + exchangeCfg.Testnet, + ) + case "aster": + tempTrader, createErr = trader.NewAsterTrader( + exchangeCfg.AsterUser, + exchangeCfg.AsterSigner, + exchangeCfg.AsterPrivateKey, + ) + default: + log.Printf("⚠️ 不支持的交易所类型: %s,使用用户输入的初始资金", req.ExchangeID) + } + + if createErr != nil { + log.Printf("⚠️ 创建临时 trader 失败,使用用户输入的初始资金: %v", createErr) + } else if tempTrader != nil { + // 查询实际余额 + balanceInfo, balanceErr := tempTrader.GetBalance() + if balanceErr != nil { + log.Printf("⚠️ 查询交易所余额失败,使用用户输入的初始资金: %v", balanceErr) + } else { + // 提取可用余额 + if availableBalance, ok := balanceInfo["available_balance"].(float64); ok && availableBalance > 0 { + actualBalance = availableBalance + log.Printf("✓ 查询到交易所实际余额: %.2f USDT (用户输入: %.2f USDT)", actualBalance, req.InitialBalance) + } else if totalBalance, ok := balanceInfo["balance"].(float64); ok && totalBalance > 0 { + // 有些交易所可能只返回 balance 字段 + actualBalance = totalBalance + log.Printf("✓ 查询到交易所实际余额: %.2f USDT (用户输入: %.2f USDT)", actualBalance, req.InitialBalance) + } else { + log.Printf("⚠️ 无法从余额信息中提取可用余额,使用用户输入的初始资金") + } + } + } + } + // 创建交易员配置(数据库实体) trader := &config.TraderRecord{ ID: traderID, @@ -354,7 +422,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { Name: req.Name, AIModelID: req.AIModelID, ExchangeID: req.ExchangeID, - InitialBalance: req.InitialBalance, + InitialBalance: actualBalance, // 使用实际查询的余额 BTCETHLeverage: btcEthLeverage, AltcoinLeverage: altcoinLeverage, TradingSymbols: req.TradingSymbols, @@ -369,7 +437,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } // 保存到数据库 - err := s.database.CreateTrader(trader) + err = s.database.CreateTrader(traderRecord) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建交易员失败: %v", err)}) return From 2ca627ff7276980c72647c0baf0a5bd984d30fcb Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:55:41 +0800 Subject: [PATCH 2/2] fix(api): correct variable name from traderRecord to trader Fixed compilation error caused by variable name mismatch: - Line 404: defined as 'trader' - Line 425: was using 'traderRecord' (undefined) This aligns with upstream dev branch naming convention. --- api/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server.go b/api/server.go index d7f0512d..ee255fe3 100644 --- a/api/server.go +++ b/api/server.go @@ -437,7 +437,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } // 保存到数据库 - err = s.database.CreateTrader(traderRecord) + err = s.database.CreateTrader(trader) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建交易员失败: %v", err)}) return