account system、custom prompt

This commit is contained in:
icy
2025-10-31 03:42:01 +08:00
parent bbe1e1f929
commit ceb2f7b435
32 changed files with 3873 additions and 465 deletions

View File

@@ -28,13 +28,20 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
tm.mu.Lock()
defer tm.mu.Unlock()
// 根据admin_mode确定用户ID
adminModeStr, _ := database.GetSystemConfig("admin_mode")
userID := "default"
if adminModeStr != "false" { // 默认为true
userID = "admin"
}
// 获取数据库中的所有交易员
traders, err := database.GetTraders()
traders, err := database.GetTraders(userID)
if err != nil {
return fmt.Errorf("获取交易员列表失败: %w", err)
}
log.Printf("📋 加载数据库中的交易员配置: %d 个", len(traders))
log.Printf("📋 加载数据库中的交易员配置: %d 个 (用户: %s)", len(traders), userID)
// 获取系统配置
coinPoolURL, _ := database.GetSystemConfig("coin_pool_api_url")
@@ -61,7 +68,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
// 为每个交易员获取AI模型和交易所配置
for _, traderCfg := range traders {
// 获取AI模型配置
aiModels, err := database.GetAIModels()
aiModels, err := database.GetAIModels(userID)
if err != nil {
log.Printf("⚠️ 获取AI模型配置失败: %v", err)
continue
@@ -86,7 +93,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
}
// 获取交易所配置
exchanges, err := database.GetExchanges()
exchanges, err := database.GetExchanges(userID)
if err != nil {
log.Printf("⚠️ 获取交易所配置失败: %v", err)
continue
@@ -155,6 +162,11 @@ func (tm *TraderManager) addTraderFromConfig(traderCfg *config.TraderConfig, aiM
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
} else if exchangeCfg.ID == "hyperliquid" {
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
} else if exchangeCfg.ID == "aster" {
traderConfig.AsterUser = exchangeCfg.AsterUser
traderConfig.AsterSigner = exchangeCfg.AsterSigner
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
}
// 根据AI模型设置API密钥
@@ -169,6 +181,17 @@ func (tm *TraderManager) addTraderFromConfig(traderCfg *config.TraderConfig, aiM
if err != nil {
return fmt.Errorf("创建trader失败: %w", err)
}
// 设置自定义prompt如果有
if traderCfg.CustomPrompt != "" {
at.SetCustomPrompt(traderCfg.CustomPrompt)
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
if traderCfg.OverrideBasePrompt {
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
} else {
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
}
}
tm.traders[traderCfg.ID] = at
log.Printf("✓ Trader '%s' (%s + %s) 已加载到内存", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
@@ -213,6 +236,11 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderConfig, aiModel
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
} else if exchangeCfg.ID == "hyperliquid" {
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
} else if exchangeCfg.ID == "aster" {
traderConfig.AsterUser = exchangeCfg.AsterUser
traderConfig.AsterSigner = exchangeCfg.AsterSigner
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
}
// 根据AI模型设置API密钥
@@ -227,6 +255,17 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderConfig, aiModel
if err != nil {
return fmt.Errorf("创建trader失败: %w", err)
}
// 设置自定义prompt如果有
if traderCfg.CustomPrompt != "" {
at.SetCustomPrompt(traderCfg.CustomPrompt)
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
if traderCfg.OverrideBasePrompt {
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
} else {
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
}
}
tm.traders[traderCfg.ID] = at
log.Printf("✓ Trader '%s' (%s + %s) 已添加", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
@@ -331,3 +370,236 @@ func (tm *TraderManager) GetComparisonData() (map[string]interface{}, error) {
return comparison, nil
}
// GetCompetitionData 获取竞赛数据(特定用户的所有交易员)
func (tm *TraderManager) GetCompetitionData(userID string) (map[string]interface{}, error) {
tm.mu.RLock()
defer tm.mu.RUnlock()
comparison := make(map[string]interface{})
traders := make([]map[string]interface{}, 0)
// 只获取该用户的交易员
for traderID, t := range tm.traders {
// 检查trader是否属于该用户通过ID前缀判断
// 格式userID_traderName
if !isUserTrader(traderID, userID) {
continue
}
account, err := t.GetAccountInfo()
if err != nil {
log.Printf("⚠️ 获取交易员 %s 账户信息失败: %v", traderID, err)
continue
}
status := t.GetStatus()
traders = append(traders, map[string]interface{}{
"trader_id": t.GetID(),
"trader_name": t.GetName(),
"ai_model": t.GetAIModel(),
"total_equity": account["total_equity"],
"total_pnl": account["total_pnl"],
"total_pnl_pct": account["total_pnl_pct"],
"position_count": account["position_count"],
"margin_used_pct": account["margin_used_pct"],
"is_running": status["is_running"],
})
}
comparison["traders"] = traders
comparison["count"] = len(traders)
return comparison, nil
}
// isUserTrader 检查trader是否属于指定用户
func isUserTrader(traderID, userID string) bool {
// trader ID格式: userID_traderName 或 randomUUID_modelName
// 为了兼容性,我们检查前缀
if len(traderID) >= len(userID) && traderID[:len(userID)] == userID {
return true
}
// 对于老的default用户所有没有明确用户前缀的都属于default
if userID == "default" && !containsUserPrefix(traderID) {
return true
}
return false
}
// containsUserPrefix 检查trader ID是否包含用户前缀
func containsUserPrefix(traderID string) bool {
// 检查是否包含邮箱格式的前缀user@example.com_traderName
for i, ch := range traderID {
if ch == '@' {
// 找到@符号说明可能是email前缀
return true
}
if ch == '_' && i > 0 {
// 找到下划线但前面没有@可能是UUID或其他格式
break
}
}
return false
}
// LoadUserTraders 为特定用户加载交易员到内存
func (tm *TraderManager) LoadUserTraders(database *config.Database, userID string) error {
tm.mu.Lock()
defer tm.mu.Unlock()
// 获取指定用户的所有交易员
traders, err := database.GetTraders(userID)
if err != nil {
return fmt.Errorf("获取用户 %s 的交易员列表失败: %w", userID, err)
}
log.Printf("📋 为用户 %s 加载交易员配置: %d 个", userID, len(traders))
// 获取系统配置
coinPoolURL, _ := database.GetSystemConfig("coin_pool_api_url")
maxDailyLossStr, _ := database.GetSystemConfig("max_daily_loss")
maxDrawdownStr, _ := database.GetSystemConfig("max_drawdown")
stopTradingMinutesStr, _ := database.GetSystemConfig("stop_trading_minutes")
// 解析配置
maxDailyLoss := 10.0 // 默认值
if val, err := strconv.ParseFloat(maxDailyLossStr, 64); err == nil {
maxDailyLoss = val
}
maxDrawdown := 20.0 // 默认值
if val, err := strconv.ParseFloat(maxDrawdownStr, 64); err == nil {
maxDrawdown = val
}
stopTradingMinutes := 60 // 默认值
if val, err := strconv.Atoi(stopTradingMinutesStr); err == nil {
stopTradingMinutes = val
}
// 为每个交易员获取AI模型和交易所配置
for _, traderCfg := range traders {
// 检查是否已经加载过这个交易员
if _, exists := tm.traders[traderCfg.ID]; exists {
log.Printf("⚠️ 交易员 %s 已经加载,跳过", traderCfg.Name)
continue
}
// 获取AI模型配置使用该用户的配置
aiModels, err := database.GetAIModels(userID)
if err != nil {
log.Printf("⚠️ 获取用户 %s 的AI模型配置失败: %v", userID, err)
continue
}
var aiModelCfg *config.AIModelConfig
for _, model := range aiModels {
if model.ID == traderCfg.AIModelID {
aiModelCfg = model
break
}
}
if aiModelCfg == nil {
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
continue
}
if !aiModelCfg.Enabled {
log.Printf("⚠️ 交易员 %s 的AI模型 %s 未启用,跳过", traderCfg.Name, traderCfg.AIModelID)
continue
}
// 获取交易所配置(使用该用户的配置)
exchanges, err := database.GetExchanges(userID)
if err != nil {
log.Printf("⚠️ 获取用户 %s 的交易所配置失败: %v", userID, err)
continue
}
var exchangeCfg *config.ExchangeConfig
for _, exchange := range exchanges {
if exchange.ID == traderCfg.ExchangeID {
exchangeCfg = exchange
break
}
}
if exchangeCfg == nil {
log.Printf("⚠️ 交易员 %s 的交易所 %s 不存在,跳过", traderCfg.Name, traderCfg.ExchangeID)
continue
}
if !exchangeCfg.Enabled {
log.Printf("⚠️ 交易员 %s 的交易所 %s 未启用,跳过", traderCfg.Name, traderCfg.ExchangeID)
continue
}
// 使用现有的方法加载交易员
err = tm.loadSingleTrader(traderCfg, aiModelCfg, exchangeCfg, coinPoolURL, maxDailyLoss, maxDrawdown, stopTradingMinutes)
if err != nil {
log.Printf("⚠️ 加载交易员 %s 失败: %v", traderCfg.Name, err)
}
}
return nil
}
// loadSingleTrader 加载单个交易员(从现有代码提取的公共逻辑)
func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderConfig, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int) error {
// 构建AutoTraderConfig
traderConfig := trader.AutoTraderConfig{
ID: traderCfg.ID,
Name: traderCfg.Name,
AIModel: aiModelCfg.Provider, // 使用provider作为模型标识
Exchange: exchangeCfg.ID, // 使用exchange ID
InitialBalance: traderCfg.InitialBalance,
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
CoinPoolAPIURL: coinPoolURL,
MaxDailyLoss: maxDailyLoss,
MaxDrawdown: maxDrawdown,
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
}
// 根据交易所类型设置API密钥
if exchangeCfg.ID == "binance" {
traderConfig.BinanceAPIKey = exchangeCfg.APIKey
traderConfig.BinanceSecretKey = exchangeCfg.SecretKey
} else if exchangeCfg.ID == "hyperliquid" {
traderConfig.HyperliquidPrivateKey = exchangeCfg.APIKey // hyperliquid用APIKey存储private key
traderConfig.HyperliquidWalletAddr = exchangeCfg.HyperliquidWalletAddr
} else if exchangeCfg.ID == "aster" {
traderConfig.AsterUser = exchangeCfg.AsterUser
traderConfig.AsterSigner = exchangeCfg.AsterSigner
traderConfig.AsterPrivateKey = exchangeCfg.AsterPrivateKey
}
// 根据AI模型设置API密钥
if aiModelCfg.Provider == "qwen" {
traderConfig.QwenKey = aiModelCfg.APIKey
} else if aiModelCfg.Provider == "deepseek" {
traderConfig.DeepSeekKey = aiModelCfg.APIKey
}
// 创建trader实例
at, err := trader.NewAutoTrader(traderConfig)
if err != nil {
return fmt.Errorf("创建trader失败: %w", err)
}
// 设置自定义prompt如果有
if traderCfg.CustomPrompt != "" {
at.SetCustomPrompt(traderCfg.CustomPrompt)
at.SetOverrideBasePrompt(traderCfg.OverrideBasePrompt)
if traderCfg.OverrideBasePrompt {
log.Printf("✓ 已设置自定义交易策略prompt (覆盖基础prompt)")
} else {
log.Printf("✓ 已设置自定义交易策略prompt (补充基础prompt)")
}
}
tm.traders[traderCfg.ID] = at
log.Printf("✓ Trader '%s' (%s + %s) 已为用户加载到内存", traderCfg.Name, aiModelCfg.Provider, exchangeCfg.ID)
return nil
}