mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 21:12:00 +08:00
account system、custom prompt
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user