From e6440d582f9fe35b6511485c9703acfb930a4513 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:54:47 +0800 Subject: [PATCH 1/3] fix: resolve go vet warnings for non-constant format strings Replace log.Printf with log.Print for static strings to resolve go vet warnings about non-constant format strings. This is a security best practice as using Printf with dynamic strings can lead to format string vulnerabilities. Fixed 6 instances in trader/auto_trader.go: - Line 260: Decision cycle separator (=) - Line 262: Decision cycle separator (=) - Line 349: System prompt separator (=) - Line 353: System prompt separator (=) - Line 357: CoT trace separator (-) - Line 361: CoT trace separator (-) --- trader/auto_trader.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trader/auto_trader.go b/trader/auto_trader.go index b23bb052..d27fcd35 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -257,9 +257,9 @@ func (at *AutoTrader) Stop() { func (at *AutoTrader) runCycle() error { at.callCount++ - log.Printf("\n" + strings.Repeat("=", 70)) + log.Print("\n" + strings.Repeat("=", 70)) log.Printf("⏰ %s - AI决策周期 #%d", time.Now().Format("2006-01-02 15:04:05"), at.callCount) - log.Printf(strings.Repeat("=", 70)) + log.Print(strings.Repeat("=", 70)) // 创建决策记录 record := &logger.DecisionRecord{ @@ -346,19 +346,19 @@ func (at *AutoTrader) runCycle() error { // 打印系统提示词和AI思维链(即使有错误,也要输出以便调试) if decision != nil { if decision.SystemPrompt != "" { - log.Printf("\n" + strings.Repeat("=", 70)) + log.Print("\n" + strings.Repeat("=", 70)) log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate) log.Println(strings.Repeat("=", 70)) log.Println(decision.SystemPrompt) - log.Printf(strings.Repeat("=", 70) + "\n") + log.Print(strings.Repeat("=", 70) + "\n") } if decision.CoTTrace != "" { - log.Printf("\n" + strings.Repeat("-", 70)) + log.Print("\n" + strings.Repeat("-", 70)) log.Println("💭 AI思维链分析(错误情况):") log.Println(strings.Repeat("-", 70)) log.Println(decision.CoTTrace) - log.Printf(strings.Repeat("-", 70) + "\n") + log.Print(strings.Repeat("-", 70) + "\n") } } From 5fe671b254de36db70b7c1312770eb2d99f4de5c Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:47:25 +0800 Subject: [PATCH 2/3] chore(web): add peer dependency markers to package-lock.json npm v7+ automatically marks packages as peer dependencies when they are declared in peerDependencies of installed packages. This commit adds these markers to ensure consistent dependency resolution across all development environments and CI/CD pipelines. Affected packages (10): - @babel/core (dev peer) - @types/react (devOptional peer) - browserslist - jiti (dev peer) - postcss - react - react-dom - picomatch (2 instances, dev peer) - vite (dev peer) Benefits: - Prevents duplicate installations of peer dependencies - Ensures consistent package versions across the project - Improves npm install performance - Reduces package-lock.json conflicts --- web/package-lock.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/package-lock.json b/web/package-lock.json index a6afa248..08b930ea 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -72,6 +72,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1275,6 +1276,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz", "integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==", "devOptional": true, + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1462,6 +1464,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2157,6 +2160,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2477,6 +2481,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2653,6 +2658,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2664,6 +2670,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3172,6 +3179,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -3286,6 +3294,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -3377,6 +3386,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, From 62ebd58a1f490ec670bf2779f5401a0bc5743a25 Mon Sep 17 00:00:00 2001 From: hzb1115 Date: Mon, 3 Nov 2025 17:22:11 +0000 Subject: [PATCH 3/3] style(backend): go fmt code --- api/server.go | 197 +++++++++++++++++------------------ auth/auth.go | 6 +- manager/trader_manager.go | 62 +++++------ trader/aster_trader.go | 36 +++---- trader/auto_trader.go | 18 ++-- trader/binance_futures.go | 8 +- trader/hyperliquid_trader.go | 2 +- 7 files changed, 164 insertions(+), 165 deletions(-) diff --git a/api/server.go b/api/server.go index 92c2f174..28fd70ae 100644 --- a/api/server.go +++ b/api/server.go @@ -71,20 +71,20 @@ func (s *Server) setupRoutes() { { // 健康检查 api.Any("/health", s.handleHealth) - + // 认证相关路由(无需认证) api.POST("/register", s.handleRegister) api.POST("/login", s.handleLogin) api.POST("/verify-otp", s.handleVerifyOTP) api.POST("/complete-registration", s.handleCompleteRegistration) - + // 系统支持的模型和交易所(无需认证) api.GET("/supported-models", s.handleGetSupportedModels) api.GET("/supported-exchanges", s.handleGetSupportedExchanges) - + // 系统配置(无需认证) api.GET("/config", s.handleGetSystemConfig) - + // 系统提示词模板管理(无需认证) api.GET("/prompt-templates", s.handleGetPromptTemplates) api.GET("/prompt-templates/:name", s.handleGetPromptTemplate) @@ -114,10 +114,9 @@ func (s *Server) setupRoutes() { protected.GET("/user/signal-sources", s.handleGetUserSignalSource) protected.POST("/user/signal-sources", s.handleSaveUserSignalSource) - // 竞赛总览 protected.GET("/competition", s.handleCompetition) - + // 指定trader的数据(使用query参数 ?trader_id=xxx) protected.GET("/status", s.handleStatus) protected.GET("/account", s.handleAccount) @@ -151,24 +150,24 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) { // 使用硬编码的默认币种 defaultCoins = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "HYPEUSDT"} } - + // 获取杠杆配置 btcEthLeverageStr, _ := s.database.GetSystemConfig("btc_eth_leverage") altcoinLeverageStr, _ := s.database.GetSystemConfig("altcoin_leverage") - + btcEthLeverage := 5 if val, err := strconv.Atoi(btcEthLeverageStr); err == nil && val > 0 { btcEthLeverage = val } - + altcoinLeverage := 5 if val, err := strconv.Atoi(altcoinLeverageStr); err == nil && val > 0 { altcoinLeverage = val } - + c.JSON(http.StatusOK, gin.H{ - "admin_mode": auth.IsAdminMode(), - "default_coins": defaultCoins, + "admin_mode": auth.IsAdminMode(), + "default_coins": defaultCoins, "btc_eth_leverage": btcEthLeverage, "altcoin_leverage": altcoinLeverage, }) @@ -178,20 +177,20 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) { func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, string, error) { userID := c.GetString("user_id") traderID := c.Query("trader_id") - + // 确保用户的交易员已加载到内存中 err := s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 加载用户 %s 的交易员失败: %v", userID, err) } - + if traderID == "" { // 如果没有指定trader_id,返回该用户的第一个trader ids := s.traderManager.GetTraderIDs() if len(ids) == 0 { return nil, "", fmt.Errorf("没有可用的trader") } - + // 获取用户的交易员列表,优先返回用户自己的交易员 userTraders, err := s.database.GetTraders(userID) if err == nil && len(userTraders) > 0 { @@ -200,7 +199,7 @@ func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, str traderID = ids[0] } } - + return s.traderManager, traderID, nil } @@ -296,13 +295,13 @@ func (s *Server) handleCreateTrader(c *gin.Context) { // 生成交易员ID traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) - + // 设置默认值 isCrossMargin := true // 默认为全仓模式 if req.IsCrossMargin != nil { isCrossMargin = *req.IsCrossMargin } - + // 设置杠杆默认值(从系统配置获取) btcEthLeverage := 5 altcoinLeverage := 5 @@ -326,7 +325,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } } } - + // 设置系统提示词模板默认值 systemPromptTemplate := "default" if req.SystemPromptTemplate != "" { @@ -339,8 +338,8 @@ func (s *Server) handleCreateTrader(c *gin.Context) { scanIntervalMinutes = 3 // 默认3分钟 } - // 创建交易员配置(数据库实体) - trader := &config.TraderRecord{ + // 创建交易员配置(数据库实体) + trader := &config.TraderRecord{ ID: traderID, UserID: userID, Name: req.Name, @@ -357,7 +356,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { SystemPromptTemplate: systemPromptTemplate, IsCrossMargin: isCrossMargin, ScanIntervalMinutes: scanIntervalMinutes, - IsRunning: false, + IsRunning: false, } // 保存到数据库 @@ -386,24 +385,24 @@ func (s *Server) handleCreateTrader(c *gin.Context) { // UpdateTraderRequest 更新交易员请求 type UpdateTraderRequest struct { - Name string `json:"name" binding:"required"` - AIModelID string `json:"ai_model_id" binding:"required"` - ExchangeID string `json:"exchange_id" binding:"required"` - InitialBalance float64 `json:"initial_balance"` - ScanIntervalMinutes int `json:"scan_interval_minutes"` - BTCETHLeverage int `json:"btc_eth_leverage"` - AltcoinLeverage int `json:"altcoin_leverage"` - TradingSymbols string `json:"trading_symbols"` - CustomPrompt string `json:"custom_prompt"` - OverrideBasePrompt bool `json:"override_base_prompt"` - IsCrossMargin *bool `json:"is_cross_margin"` + Name string `json:"name" binding:"required"` + AIModelID string `json:"ai_model_id" binding:"required"` + ExchangeID string `json:"exchange_id" binding:"required"` + InitialBalance float64 `json:"initial_balance"` + ScanIntervalMinutes int `json:"scan_interval_minutes"` + BTCETHLeverage int `json:"btc_eth_leverage"` + AltcoinLeverage int `json:"altcoin_leverage"` + TradingSymbols string `json:"trading_symbols"` + CustomPrompt string `json:"custom_prompt"` + OverrideBasePrompt bool `json:"override_base_prompt"` + IsCrossMargin *bool `json:"is_cross_margin"` } // handleUpdateTrader 更新交易员配置 func (s *Server) handleUpdateTrader(c *gin.Context) { userID := c.GetString("user_id") traderID := c.Param("id") - + var req UpdateTraderRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -416,7 +415,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取交易员列表失败"}) return } - + var existingTrader *config.TraderRecord for _, trader := range traders { if trader.ID == traderID { @@ -424,7 +423,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { break } } - + if existingTrader == nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) return @@ -435,7 +434,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { if req.IsCrossMargin != nil { isCrossMargin = *req.IsCrossMargin } - + // 设置杠杆默认值 btcEthLeverage := req.BTCETHLeverage altcoinLeverage := req.AltcoinLeverage @@ -452,8 +451,8 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值 } - // 更新交易员配置 - trader := &config.TraderRecord{ + // 更新交易员配置 + trader := &config.TraderRecord{ ID: traderID, UserID: userID, Name: req.Name, @@ -498,14 +497,14 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { func (s *Server) handleDeleteTrader(c *gin.Context) { userID := c.GetString("user_id") traderID := c.Param("id") - + // 从数据库删除 err := s.database.DeleteTrader(userID, traderID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("删除交易员失败: %v", err)}) return } - + // 如果交易员正在运行,先停止它 if trader, err := s.traderManager.GetTrader(traderID); err == nil { status := trader.GetStatus() @@ -514,7 +513,7 @@ func (s *Server) handleDeleteTrader(c *gin.Context) { log.Printf("⏹ 已停止运行中的交易员: %s", traderID) } } - + log.Printf("✓ 交易员已删除: %s", traderID) c.JSON(http.StatusOK, gin.H{"message": "交易员已删除"}) } @@ -522,20 +521,20 @@ func (s *Server) handleDeleteTrader(c *gin.Context) { // handleStartTrader 启动交易员 func (s *Server) handleStartTrader(c *gin.Context) { traderID := c.Param("id") - + trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) return } - + // 检查交易员是否已经在运行 status := trader.GetStatus() if isRunning, ok := status["is_running"].(bool); ok && isRunning { c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已在运行中"}) return } - + // 启动交易员 go func() { log.Printf("▶️ 启动交易员 %s (%s)", traderID, trader.GetName()) @@ -543,14 +542,14 @@ func (s *Server) handleStartTrader(c *gin.Context) { log.Printf("❌ 交易员 %s 运行错误: %v", trader.GetName(), err) } }() - + // 更新数据库中的运行状态 userID := c.GetString("user_id") err = s.database.UpdateTraderStatus(userID, traderID, true) if err != nil { log.Printf("⚠️ 更新交易员状态失败: %v", err) } - + log.Printf("✓ 交易员 %s 已启动", trader.GetName()) c.JSON(http.StatusOK, gin.H{"message": "交易员已启动"}) } @@ -558,30 +557,30 @@ func (s *Server) handleStartTrader(c *gin.Context) { // handleStopTrader 停止交易员 func (s *Server) handleStopTrader(c *gin.Context) { traderID := c.Param("id") - + trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) return } - + // 检查交易员是否正在运行 status := trader.GetStatus() if isRunning, ok := status["is_running"].(bool); ok && !isRunning { c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已停止"}) return } - + // 停止交易员 trader.Stop() - + // 更新数据库中的运行状态 userID := c.GetString("user_id") err = s.database.UpdateTraderStatus(userID, traderID, false) if err != nil { log.Printf("⚠️ 更新交易员状态失败: %v", err) } - + log.Printf("⏹ 交易员 %s 已停止", trader.GetName()) c.JSON(http.StatusOK, gin.H{"message": "交易员已停止"}) } @@ -590,24 +589,24 @@ func (s *Server) handleStopTrader(c *gin.Context) { func (s *Server) handleUpdateTraderPrompt(c *gin.Context) { traderID := c.Param("id") userID := c.GetString("user_id") - + var req struct { - CustomPrompt string `json:"custom_prompt"` - OverrideBasePrompt bool `json:"override_base_prompt"` + CustomPrompt string `json:"custom_prompt"` + OverrideBasePrompt bool `json:"override_base_prompt"` } - + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - + // 更新数据库 err := s.database.UpdateTraderCustomPrompt(userID, traderID, req.CustomPrompt, req.OverrideBasePrompt) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新自定义prompt失败: %v", err)}) return } - + // 如果trader在内存中,更新其custom prompt和override设置 trader, err := s.traderManager.GetTrader(traderID) if err == nil { @@ -615,7 +614,7 @@ func (s *Server) handleUpdateTraderPrompt(c *gin.Context) { trader.SetOverrideBasePrompt(req.OverrideBasePrompt) log.Printf("✓ 已更新交易员 %s 的自定义prompt (覆盖基础=%v)", trader.GetName(), req.OverrideBasePrompt) } - + c.JSON(http.StatusOK, gin.H{"message": "自定义prompt已更新"}) } @@ -630,7 +629,7 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) { return } log.Printf("✅ 找到 %d 个AI模型配置", len(models)) - + c.JSON(http.StatusOK, models) } @@ -674,7 +673,7 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) { return } log.Printf("✅ 找到 %d 个交易所配置", len(exchanges)) - + c.JSON(http.StatusOK, exchanges) } @@ -719,7 +718,7 @@ func (s *Server) handleGetUserSignalSource(c *gin.Context) { }) return } - + c.JSON(http.StatusOK, gin.H{ "coin_pool_url": source.CoinPoolURL, "oi_top_url": source.OITopURL, @@ -733,18 +732,18 @@ func (s *Server) handleSaveUserSignalSource(c *gin.Context) { CoinPoolURL string `json:"coin_pool_url"` OITopURL string `json:"oi_top_url"` } - + if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - + err := s.database.CreateUserSignalSource(userID, req.CoinPoolURL, req.OITopURL) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("保存用户信号源配置失败: %v", err)}) return } - + log.Printf("✓ 用户信号源配置已保存: user=%s, coin_pool=%s, oi_top=%s", userID, req.CoinPoolURL, req.OITopURL) c.JSON(http.StatusOK, gin.H{"message": "用户信号源配置已保存"}) } @@ -820,21 +819,21 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) { aiModelID := traderConfig.AIModelID result := map[string]interface{}{ - "trader_id": traderConfig.ID, - "trader_name": traderConfig.Name, - "ai_model": aiModelID, - "exchange_id": traderConfig.ExchangeID, - "initial_balance": traderConfig.InitialBalance, - "scan_interval_minutes": traderConfig.ScanIntervalMinutes, - "btc_eth_leverage": traderConfig.BTCETHLeverage, - "altcoin_leverage": traderConfig.AltcoinLeverage, - "trading_symbols": traderConfig.TradingSymbols, - "custom_prompt": traderConfig.CustomPrompt, - "override_base_prompt": traderConfig.OverrideBasePrompt, - "is_cross_margin": traderConfig.IsCrossMargin, - "use_coin_pool": traderConfig.UseCoinPool, - "use_oi_top": traderConfig.UseOITop, - "is_running": isRunning, + "trader_id": traderConfig.ID, + "trader_name": traderConfig.Name, + "ai_model": aiModelID, + "exchange_id": traderConfig.ExchangeID, + "initial_balance": traderConfig.InitialBalance, + "scan_interval_minutes": traderConfig.ScanIntervalMinutes, + "btc_eth_leverage": traderConfig.BTCETHLeverage, + "altcoin_leverage": traderConfig.AltcoinLeverage, + "trading_symbols": traderConfig.TradingSymbols, + "custom_prompt": traderConfig.CustomPrompt, + "override_base_prompt": traderConfig.OverrideBasePrompt, + "is_cross_margin": traderConfig.IsCrossMargin, + "use_coin_pool": traderConfig.UseCoinPool, + "use_oi_top": traderConfig.UseOITop, + "is_running": isRunning, } c.JSON(http.StatusOK, result) @@ -1001,13 +1000,13 @@ func (s *Server) handleStatistics(c *gin.Context) { // handleCompetition 竞赛总览(对比所有trader) func (s *Server) handleCompetition(c *gin.Context) { userID := c.GetString("user_id") - + // 确保用户的交易员已加载到内存中 err := s.traderManager.LoadUserTraders(s.database, userID) if err != nil { log.Printf("⚠️ 加载用户 %s 的交易员失败: %v", userID, err) } - + competition, err := s.traderManager.GetCompetitionData() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ @@ -1015,7 +1014,7 @@ func (s *Server) handleCompetition(c *gin.Context) { }) return } - + c.JSON(http.StatusOK, competition) } @@ -1142,7 +1141,7 @@ func (s *Server) authMiddleware() gin.HandlerFunc { c.Next() return } - + authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少Authorization头"}) @@ -1225,11 +1224,11 @@ func (s *Server) handleRegister(c *gin.Context) { // 返回OTP设置信息 qrCodeURL := auth.GetOTPQRCodeURL(otpSecret, req.Email) c.JSON(http.StatusOK, gin.H{ - "user_id": userID, - "email": req.Email, - "otp_secret": otpSecret, + "user_id": userID, + "email": req.Email, + "otp_secret": otpSecret, "qr_code_url": qrCodeURL, - "message": "请使用Google Authenticator扫描二维码并验证OTP", + "message": "请使用Google Authenticator扫描二维码并验证OTP", }) } @@ -1314,8 +1313,8 @@ func (s *Server) handleLogin(c *gin.Context) { // 检查OTP是否已验证 if !user.OTPVerified { c.JSON(http.StatusUnauthorized, gin.H{ - "error": "账户未完成OTP设置", - "user_id": user.ID, + "error": "账户未完成OTP设置", + "user_id": user.ID, "requires_otp_setup": true, }) return @@ -1323,9 +1322,9 @@ func (s *Server) handleLogin(c *gin.Context) { // 返回需要OTP验证的状态 c.JSON(http.StatusOK, gin.H{ - "user_id": user.ID, - "email": user.Email, - "message": "请输入Google Authenticator验证码", + "user_id": user.ID, + "email": user.Email, + "message": "请输入Google Authenticator验证码", "requires_otp": true, }) } @@ -1387,7 +1386,7 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的AI模型失败"}) return } - + c.JSON(http.StatusOK, models) } @@ -1400,7 +1399,7 @@ func (s *Server) handleGetSupportedExchanges(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的交易所失败"}) return } - + c.JSON(http.StatusOK, exchanges) } @@ -1436,7 +1435,7 @@ func (s *Server) Start() error { func (s *Server) handleGetPromptTemplates(c *gin.Context) { // 导入 decision 包 templates := decision.GetAllPromptTemplates() - + // 转换为响应格式 response := make([]map[string]interface{}, 0, len(templates)) for _, tmpl := range templates { @@ -1444,7 +1443,7 @@ func (s *Server) handleGetPromptTemplates(c *gin.Context) { "name": tmpl.Name, }) } - + c.JSON(http.StatusOK, gin.H{ "templates": response, }) @@ -1453,13 +1452,13 @@ func (s *Server) handleGetPromptTemplates(c *gin.Context) { // handleGetPromptTemplate 获取指定名称的提示词模板内容 func (s *Server) handleGetPromptTemplate(c *gin.Context) { templateName := c.Param("name") - + template, err := decision.GetPromptTemplate(templateName) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("模板不存在: %s", templateName)}) return } - + c.JSON(http.StatusOK, gin.H{ "name": template.Name, "content": template.Content, diff --git a/auth/auth.go b/auth/auth.go index 685d08e6..89c58e5c 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -61,7 +61,7 @@ func GenerateOTPSecret() (string, error) { if err != nil { return "", err } - + key, err := totp.Generate(totp.GenerateOpts{ Issuer: OTPIssuer, AccountName: uuid.New().String(), @@ -69,7 +69,7 @@ func GenerateOTPSecret() (string, error) { if err != nil { return "", err } - + return key.Secret(), nil } @@ -118,4 +118,4 @@ func ValidateJWT(tokenString string) (*Claims, error) { // GetOTPQRCodeURL 获取OTP二维码URL func GetOTPQRCodeURL(secret, email string) string { return fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", OTPIssuer, email, secret, OTPIssuer) -} \ No newline at end of file +} diff --git a/manager/trader_manager.go b/manager/trader_manager.go index d3861cdb..ebff453c 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -84,7 +84,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro } // 为每个交易员获取AI模型和交易所配置 - for _, traderCfg := range allTraders { + for _, traderCfg := range allTraders { // 获取AI模型配置(使用交易员所属的用户ID) aiModels, err := database.GetAIModels(traderCfg.UserID) if err != nil { @@ -157,7 +157,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro } // 添加到TraderManager - err = tm.addTraderFromDB(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins) + err = tm.addTraderFromDB(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins) if err != nil { log.Printf("❌ 添加交易员 %s 失败: %v", traderCfg.Name, err) continue @@ -186,7 +186,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel } } } - + // 如果没有指定交易币种,使用默认币种 if len(tradingCoins) == 0 { tradingCoins = defaultCoins @@ -200,7 +200,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel } // 构建AutoTraderConfig - traderConfig := trader.AutoTraderConfig{ + traderConfig := trader.AutoTraderConfig{ ID: traderCfg.ID, Name: traderCfg.Name, AIModel: aiModelCfg.Provider, // 使用provider作为模型标识 @@ -253,7 +253,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel if err != nil { return fmt.Errorf("创建trader失败: %w", err) } - + // 设置自定义prompt(如果有) if traderCfg.CustomPrompt != "" { at.SetCustomPrompt(traderCfg.CustomPrompt) @@ -293,7 +293,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel } } } - + // 如果没有指定交易币种,使用默认币种 if len(tradingCoins) == 0 { tradingCoins = defaultCoins @@ -359,7 +359,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel if err != nil { return fmt.Errorf("创建trader失败: %w", err) } - + // 设置自定义prompt(如果有) if traderCfg.CustomPrompt != "" { at.SetCustomPrompt(traderCfg.CustomPrompt) @@ -488,9 +488,9 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) { for _, t := range tm.traders { account, err := t.GetAccountInfo() status := t.GetStatus() - + var traderData map[string]interface{} - + if err != nil { // 如果获取账户信息失败,使用默认值但仍然显示交易员 log.Printf("⚠️ 获取交易员 %s 账户信息失败: %v", t.GetID(), err) @@ -522,7 +522,7 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) { "is_running": status["is_running"], } } - + traders = append(traders, traderData) } comparison["traders"] = traders @@ -708,7 +708,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode } } } - + // 如果没有指定交易币种,使用默认币种 if len(tradingCoins) == 0 { tradingCoins = defaultCoins @@ -723,25 +723,25 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode // 构建AutoTraderConfig traderConfig := trader.AutoTraderConfig{ - ID: traderCfg.ID, - Name: traderCfg.Name, - AIModel: aiModelCfg.Provider, // 使用provider作为模型标识 - Exchange: exchangeCfg.ID, // 使用exchange ID - InitialBalance: traderCfg.InitialBalance, - BTCETHLeverage: traderCfg.BTCETHLeverage, - AltcoinLeverage: traderCfg.AltcoinLeverage, - ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute, - CoinPoolAPIURL: effectiveCoinPoolURL, - CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL - CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称 - UseQwen: aiModelCfg.Provider == "qwen", - MaxDailyLoss: maxDailyLoss, - MaxDrawdown: maxDrawdown, - StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, - IsCrossMargin: traderCfg.IsCrossMargin, - DefaultCoins: defaultCoins, - TradingCoins: tradingCoins, - SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板 + ID: traderCfg.ID, + Name: traderCfg.Name, + AIModel: aiModelCfg.Provider, // 使用provider作为模型标识 + Exchange: exchangeCfg.ID, // 使用exchange ID + InitialBalance: traderCfg.InitialBalance, + BTCETHLeverage: traderCfg.BTCETHLeverage, + AltcoinLeverage: traderCfg.AltcoinLeverage, + ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute, + CoinPoolAPIURL: effectiveCoinPoolURL, + CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL + CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称 + UseQwen: aiModelCfg.Provider == "qwen", + MaxDailyLoss: maxDailyLoss, + MaxDrawdown: maxDrawdown, + StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, + IsCrossMargin: traderCfg.IsCrossMargin, + DefaultCoins: defaultCoins, + TradingCoins: tradingCoins, + SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板 } // 根据交易所类型设置API密钥 @@ -769,7 +769,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode if err != nil { return fmt.Errorf("创建trader失败: %w", err) } - + // 设置自定义prompt(如果有) if traderCfg.CustomPrompt != "" { at.SetCustomPrompt(traderCfg.CustomPrompt) diff --git a/trader/aster_trader.go b/trader/aster_trader.go index e4d7f12d..d9ba82a6 100644 --- a/trader/aster_trader.go +++ b/trader/aster_trader.go @@ -27,8 +27,8 @@ import ( // AsterTrader Aster交易平台实现 type AsterTrader struct { ctx context.Context - user string // 主钱包地址 (ERC20) - signer string // API钱包地址 + user string // 主钱包地址 (ERC20) + signer string // API钱包地址 privateKey *ecdsa.PrivateKey // API钱包私钥 client *http.Client baseURL string @@ -99,9 +99,9 @@ func (t *AsterTrader) getPrecision(symbol string) (SymbolPrecision, error) { body, _ := io.ReadAll(resp.Body) var info struct { Symbols []struct { - Symbol string `json:"symbol"` - PricePrecision int `json:"pricePrecision"` - QuantityPrecision int `json:"quantityPrecision"` + Symbol string `json:"symbol"` + PricePrecision int `json:"pricePrecision"` + QuantityPrecision int `json:"quantityPrecision"` Filters []map[string]interface{} `json:"filters"` } `json:"symbols"` } @@ -506,14 +506,14 @@ func (t *AsterTrader) GetPositions() ([]map[string]interface{}, error) { // 返回与Binance相同的字段名 result = append(result, map[string]interface{}{ - "symbol": pos["symbol"], - "side": side, - "positionAmt": posAmt, - "entryPrice": entryPrice, - "markPrice": markPrice, - "unRealizedProfit": unRealizedProfit, - "leverage": leverageVal, - "liquidationPrice": liquidationPrice, + "symbol": pos["symbol"], + "side": side, + "positionAmt": posAmt, + "entryPrice": entryPrice, + "markPrice": markPrice, + "unRealizedProfit": unRealizedProfit, + "leverage": leverageVal, + "liquidationPrice": liquidationPrice, }) } @@ -827,18 +827,18 @@ func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error { if !isCrossMargin { marginType = "ISOLATED" } - + params := map[string]interface{}{ "symbol": symbol, "marginType": marginType, } - + // 使用request方法调用API _, err := t.request("POST", "/fapi/v3/marginType", params) if err != nil { // 如果错误表示无需更改,忽略错误 - if strings.Contains(err.Error(), "No need to change") || - strings.Contains(err.Error(), "Margin type cannot be changed") { + if strings.Contains(err.Error(), "No need to change") || + strings.Contains(err.Error(), "Margin type cannot be changed") { log.Printf(" ✓ %s 仓位模式已是 %s 或有持仓无法更改", symbol, marginType) return nil } @@ -846,7 +846,7 @@ func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error { // 不返回错误,让交易继续 return nil } - + log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginType) return nil } diff --git a/trader/auto_trader.go b/trader/auto_trader.go index d27fcd35..9a68ed17 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -68,8 +68,8 @@ type AutoTraderConfig struct { IsCrossMargin bool // true=全仓模式, false=逐仓模式 // 币种配置 - DefaultCoins []string // 默认币种列表(从数据库获取) - TradingCoins []string // 实际交易币种列表 + DefaultCoins []string // 默认币种列表(从数据库获取) + TradingCoins []string // 实际交易币种列表 // 系统提示词模板 SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive") @@ -87,9 +87,9 @@ type AutoTrader struct { decisionLogger *logger.DecisionLogger // 决策日志记录器 initialBalance float64 dailyPnL float64 - customPrompt string // 自定义交易策略prompt - overrideBasePrompt bool // 是否覆盖基础prompt - systemPromptTemplate string // 系统提示词模板名称 + customPrompt string // 自定义交易策略prompt + overrideBasePrompt bool // 是否覆盖基础prompt + systemPromptTemplate string // 系统提示词模板名称 defaultCoins []string // 默认币种列表(从数据库获取) tradingCoins []string // 实际交易币种列表 lastResetTime time.Time @@ -1016,7 +1016,7 @@ func (at *AutoTrader) getCandidateCoins() ([]decision.CandidateCoin, error) { if len(at.tradingCoins) == 0 { // 使用数据库配置的默认币种列表 var candidateCoins []decision.CandidateCoin - + if len(at.defaultCoins) > 0 { // 使用数据库中配置的默认币种 for _, coin := range at.defaultCoins { @@ -1032,7 +1032,7 @@ func (at *AutoTrader) getCandidateCoins() ([]decision.CandidateCoin, error) { } else { // 如果数据库中没有配置默认币种,则使用AI500+OI Top作为fallback const ai500Limit = 20 // AI500取前20个评分最高的币种 - + mergedPool, err := pool.GetMergedCoinPool(ai500Limit) if err != nil { return nil, fmt.Errorf("获取合并币种池失败: %w", err) @@ -1073,11 +1073,11 @@ func (at *AutoTrader) getCandidateCoins() ([]decision.CandidateCoin, error) { func normalizeSymbol(symbol string) string { // 转为大写 symbol = strings.ToUpper(strings.TrimSpace(symbol)) - + // 确保以USDT结尾 if !strings.HasSuffix(symbol, "USDT") { symbol = symbol + "USDT" } - + return symbol } diff --git a/trader/binance_futures.go b/trader/binance_futures.go index c10fadeb..354415a0 100644 --- a/trader/binance_futures.go +++ b/trader/binance_futures.go @@ -139,18 +139,18 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error { } else { marginType = futures.MarginTypeIsolated } - + // 尝试设置仓位模式 err := t.client.NewChangeMarginTypeService(). Symbol(symbol). MarginType(marginType). Do(context.Background()) - + marginModeStr := "全仓" if !isCrossMargin { marginModeStr = "逐仓" } - + if err != nil { // 如果错误信息包含"No need to change",说明仓位模式已经是目标值 if contains(err.Error(), "No need to change margin type") { @@ -166,7 +166,7 @@ func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error { // 不返回错误,让交易继续 return nil } - + log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr) return nil } diff --git a/trader/hyperliquid_trader.go b/trader/hyperliquid_trader.go index 3073b342..c189dbdc 100644 --- a/trader/hyperliquid_trader.go +++ b/trader/hyperliquid_trader.go @@ -17,7 +17,7 @@ type HyperliquidTrader struct { ctx context.Context walletAddr string meta *hyperliquid.Meta // 缓存meta信息(包含精度等) - isCrossMargin bool // 是否为全仓模式 + isCrossMargin bool // 是否为全仓模式 } // NewHyperliquidTrader 创建Hyperliquid交易器