style: format Go code with go fmt

- Fix formatting issues flagged by PR advisory checks
- Run go fmt ./... on all Go files
- No functional changes, code style improvements only

Files formatted:
- api/server.go
- auth/auth.go
- decision/engine.go
- logger/decision_logger.go
- manager/trader_manager.go
- market/monitor.go
- market/types.go
- mcp/client.go
- trader/*.go (aster, auto, binance, hyperliquid)
This commit is contained in:
ZhouYongyou
2025-11-03 19:35:41 +08:00
parent bb2edfc293
commit 366ae87077
12 changed files with 190 additions and 191 deletions

View File

@@ -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
}
@@ -295,13 +294,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
@@ -325,15 +324,15 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
}
}
}
// 设置系统提示词模板默认值
systemPromptTemplate := "default"
if req.SystemPromptTemplate != "" {
systemPromptTemplate = req.SystemPromptTemplate
}
// 创建交易员配置(数据库实体)
trader := &config.TraderRecord{
// 创建交易员配置(数据库实体)
trader := &config.TraderRecord{
ID: traderID,
UserID: userID,
Name: req.Name,
@@ -350,7 +349,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
SystemPromptTemplate: systemPromptTemplate,
IsCrossMargin: isCrossMargin,
ScanIntervalMinutes: 3, // 默认3分钟
IsRunning: false,
IsRunning: false,
}
// 保存到数据库
@@ -379,23 +378,23 @@ 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"`
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"`
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()})
@@ -408,7 +407,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 {
@@ -416,7 +415,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
break
}
}
if existingTrader == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"})
return
@@ -427,7 +426,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
if req.IsCrossMargin != nil {
isCrossMargin = *req.IsCrossMargin
}
// 设置杠杆默认值
btcEthLeverage := req.BTCETHLeverage
altcoinLeverage := req.AltcoinLeverage
@@ -437,9 +436,9 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
if altcoinLeverage <= 0 {
altcoinLeverage = existingTrader.AltcoinLeverage // 保持原值
}
// 更新交易员配置
trader := &config.TraderRecord{
// 更新交易员配置
trader := &config.TraderRecord{
ID: traderID,
UserID: userID,
Name: req.Name,
@@ -483,14 +482,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()
@@ -499,7 +498,7 @@ func (s *Server) handleDeleteTrader(c *gin.Context) {
log.Printf("⏹ 已停止运行中的交易员: %s", traderID)
}
}
log.Printf("✓ 交易员已删除: %s", traderID)
c.JSON(http.StatusOK, gin.H{"message": "交易员已删除"})
}
@@ -507,20 +506,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())
@@ -528,14 +527,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": "交易员已启动"})
}
@@ -543,30 +542,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": "交易员已停止"})
}
@@ -575,24 +574,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 {
@@ -600,7 +599,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已更新"})
}
@@ -615,7 +614,7 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) {
return
}
log.Printf("✅ 找到 %d 个AI模型配置", len(models))
c.JSON(http.StatusOK, models)
}
@@ -659,7 +658,7 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) {
return
}
log.Printf("✅ 找到 %d 个交易所配置", len(exchanges))
c.JSON(http.StatusOK, exchanges)
}
@@ -704,7 +703,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,
@@ -718,18 +717,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": "用户信号源配置已保存"})
}
@@ -759,7 +758,7 @@ func (s *Server) handleTraderList(c *gin.Context) {
result = append(result, map[string]interface{}{
"trader_id": trader.ID,
"trader_name": trader.Name,
"ai_model": trader.AIModelID, // 使用完整 ID
"ai_model": trader.AIModelID, // 使用完整 ID
"exchange_id": trader.ExchangeID,
"is_running": isRunning,
"initial_balance": trader.InitialBalance,
@@ -797,21 +796,21 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
// 返回完整的 AIModelID如 "admin_deepseek"),不要截断
// 前端需要完整 ID 来验证模型是否存在
result := map[string]interface{}{
"trader_id": traderConfig.ID,
"trader_name": traderConfig.Name,
"ai_model": traderConfig.AIModelID, // 使用完整 ID
"exchange_id": traderConfig.ExchangeID,
"initial_balance": traderConfig.InitialBalance,
"btc_eth_leverage": traderConfig.BTCETHLeverage,
"altcoin_leverage": traderConfig.AltcoinLeverage,
"trading_symbols": traderConfig.TradingSymbols,
"custom_prompt": traderConfig.CustomPrompt,
"override_base_prompt": traderConfig.OverrideBasePrompt,
"system_prompt_template": traderConfig.SystemPromptTemplate, // 添加此字段
"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": traderConfig.AIModelID, // 使用完整 ID
"exchange_id": traderConfig.ExchangeID,
"initial_balance": traderConfig.InitialBalance,
"btc_eth_leverage": traderConfig.BTCETHLeverage,
"altcoin_leverage": traderConfig.AltcoinLeverage,
"trading_symbols": traderConfig.TradingSymbols,
"custom_prompt": traderConfig.CustomPrompt,
"override_base_prompt": traderConfig.OverrideBasePrompt,
"system_prompt_template": traderConfig.SystemPromptTemplate, // 添加此字段
"is_cross_margin": traderConfig.IsCrossMargin,
"use_coin_pool": traderConfig.UseCoinPool,
"use_oi_top": traderConfig.UseOITop,
"is_running": isRunning,
}
c.JSON(http.StatusOK, result)
@@ -978,13 +977,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{
@@ -992,7 +991,7 @@ func (s *Server) handleCompetition(c *gin.Context) {
})
return
}
c.JSON(http.StatusOK, competition)
}
@@ -1119,7 +1118,7 @@ func (s *Server) authMiddleware() gin.HandlerFunc {
c.Next()
return
}
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少Authorization头"})
@@ -1202,11 +1201,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",
})
}
@@ -1291,8 +1290,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
@@ -1300,9 +1299,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,
})
}
@@ -1364,7 +1363,7 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的AI模型失败"})
return
}
c.JSON(http.StatusOK, models)
}
@@ -1377,7 +1376,7 @@ func (s *Server) handleGetSupportedExchanges(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取支持的交易所失败"})
return
}
c.JSON(http.StatusOK, exchanges)
}
@@ -1413,7 +1412,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 {
@@ -1421,7 +1420,7 @@ func (s *Server) handleGetPromptTemplates(c *gin.Context) {
"name": tmpl.Name,
})
}
c.JSON(http.StatusOK, gin.H{
"templates": response,
})
@@ -1430,13 +1429,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,

View File

@@ -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)
}
}

View File

@@ -70,8 +70,8 @@ type Context struct {
// Decision AI的交易决策
type Decision struct {
Symbol string `json:"symbol"`
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "update_stop_loss", "update_take_profit", "partial_close", "hold", "wait"
Symbol string `json:"symbol"`
Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "update_stop_loss", "update_take_profit", "partial_close", "hold", "wait"
// 开仓参数
Leverage int `json:"leverage,omitempty"`
@@ -80,14 +80,14 @@ type Decision struct {
TakeProfit float64 `json:"take_profit,omitempty"`
// 调整参数(新增)
NewStopLoss float64 `json:"new_stop_loss,omitempty"` // 用于 update_stop_loss
NewTakeProfit float64 `json:"new_take_profit,omitempty"` // 用于 update_take_profit
ClosePercentage float64 `json:"close_percentage,omitempty"` // 用于 partial_close (0-100)
NewStopLoss float64 `json:"new_stop_loss,omitempty"` // 用于 update_stop_loss
NewTakeProfit float64 `json:"new_take_profit,omitempty"` // 用于 update_take_profit
ClosePercentage float64 `json:"close_percentage,omitempty"` // 用于 partial_close (0-100)
// 通用参数
Confidence int `json:"confidence,omitempty"` // 信心度 (0-100)
RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险
Reasoning string `json:"reasoning"`
Confidence int `json:"confidence,omitempty"` // 信心度 (0-100)
RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险
Reasoning string `json:"reasoning"`
}
// FullDecision AI的完整决策包含思维链

View File

@@ -245,7 +245,7 @@ func (l *DecisionLogger) GetStatistics() (*Statistics, error) {
stats.TotalOpenPositions++
case "close_long", "close_short", "partial_close":
stats.TotalClosePositions++
// update_stop_loss 和 update_take_profit 不計入統計
// update_stop_loss 和 update_take_profit 不計入統計
}
}
}
@@ -380,7 +380,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna
case "close_long", "close_short":
// 移除已平仓记录
delete(openPositions, posKey)
// partial_close 不處理,保留持倉記錄
// partial_close 不處理,保留持倉記錄
}
}
}

View File

@@ -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)

View File

@@ -18,20 +18,20 @@ const (
)
type WSMonitor struct {
wsClient *WSClient
combinedClient *CombinedStreamsClient
symbols []string
featuresMap sync.Map
alertsChan chan Alert
wsClient *WSClient
combinedClient *CombinedStreamsClient
symbols []string
featuresMap sync.Map
alertsChan chan Alert
klineDataMap3m sync.Map // 存储每个交易对的K线历史数据
klineDataMap15m sync.Map // 存储每个交易对的15分钟K线历史数据
klineDataMap1h sync.Map // 存储每个交易对的1小时K线历史数据
klineDataMap4h sync.Map // 存储每个交易对的K线历史数据
tickerDataMap sync.Map // 存储每个交易对的ticker数据
batchSize int
filterSymbols sync.Map // 使用sync.Map来存储需要监控的币种和其状态
symbolStats sync.Map // 存储币种统计信息
FilterSymbol []string //经过筛选的币种
batchSize int
filterSymbols sync.Map // 使用sync.Map来存储需要监控的币种和其状态
symbolStats sync.Map // 存储币种统计信息
FilterSymbol []string //经过筛选的币种
}
type SymbolStats struct {
LastActiveTime time.Time

View File

@@ -13,10 +13,10 @@ type Data struct {
CurrentRSI7 float64
OpenInterest *OIData
FundingRate float64
IntradaySeries *IntradayData // 3分钟数据 - 实时价格
MidTermSeries15m *MidTermData15m // 15分钟数据 - 短期趋势
MidTermSeries1h *MidTermData1h // 1小时数据 - 中期趋势
LongerTermContext *LongerTermData // 4小时数据 - 长期趋势
IntradaySeries *IntradayData // 3分钟数据 - 实时价格
MidTermSeries15m *MidTermData15m // 15分钟数据 - 短期趋势
MidTermSeries1h *MidTermData1h // 1小时数据 - 中期趋势
LongerTermContext *LongerTermData // 4小时数据 - 长期趋势
}
// OIData Open Interest数据

View File

@@ -280,8 +280,8 @@ func isRetryableError(err error) bool {
"connection refused",
"temporary failure",
"no such host",
"stream error", // HTTP/2 stream 错误
"INTERNAL_ERROR", // 服务端内部错误
"stream error", // HTTP/2 stream 错误
"INTERNAL_ERROR", // 服务端内部错误
}
for _, retryable := range retryableErrors {
if strings.Contains(errStr, retryable) {

View File

@@ -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
}

View File

@@ -69,8 +69,8 @@ type AutoTraderConfig struct {
IsCrossMargin bool // true=全仓模式, false=逐仓模式
// 币种配置
DefaultCoins []string // 默认币种列表(从数据库获取)
TradingCoins []string // 实际交易币种列表
DefaultCoins []string // 默认币种列表(从数据库获取)
TradingCoins []string // 实际交易币种列表
// 系统提示词模板
SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive"
@@ -88,9 +88,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
@@ -1241,7 +1241,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 {
@@ -1257,7 +1257,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)
@@ -1298,11 +1298,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
}

View File

@@ -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
}

View File

@@ -17,7 +17,7 @@ type HyperliquidTrader struct {
ctx context.Context
walletAddr string
meta *hyperliquid.Meta // 缓存meta信息包含精度等
isCrossMargin bool // 是否为全仓模式
isCrossMargin bool // 是否为全仓模式
}
// NewHyperliquidTrader 创建Hyperliquid交易器