feat(config): 增加新闻源配置与数据库迁移支持

- 在config.json.example中添加新闻源相关配置示例,支持telegram频道订阅
- 扩展数据库表结构,新增traders表多字段支持信号源和杠杆参数
- 新增NewsConfig结构体及相关telegram新闻配置数据模型
- 在交易决策上下文结构Context中添加新闻数据news字段支持传递新闻
- 在交易决策构建用户提示信息时加入相关新闻内容
- 优化数据库操作代码,支持交易所和交易员配置的完整字段读取与更新
- 添加数据库exchanges表迁移逻辑,重建表结构和触发器以支持新字段
- 引入第三方库github.com/samber/lo用于集合操作
- 在go.mod添加新的依赖模块,并更新相关依赖版本
This commit is contained in:
wwg
2025-11-01 23:10:02 +08:00
parent 17c927b04a
commit d2af549bac
12 changed files with 934 additions and 209 deletions

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"nofx/config"
"nofx/news"
"nofx/trader"
"strconv"
"strings"
@@ -83,8 +84,31 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
}
}
// 获取新闻源配置
var newsCfg []config.NewsConfig
newsConfStr, _ := database.GetSystemConfig("news_config")
if newsConfStr != "" {
if err := json.Unmarshal([]byte(newsConfStr), &newsCfg); err != nil {
log.Printf("⚠️ 解析新闻源配置失败: %v使用空列表", err)
newsCfg = []config.NewsConfig{}
}
// 新闻配置处理
for index, newsConf := range newsCfg {
switch newsConf.Provider {
case news.ProviderTelegram:
if newsConf.Telegram.BaseURL == "" {
newsCfg[index].Telegram.BaseURL = "https://t.me/s"
}
if len(newsConf.TelegramChannel) == 0 {
newsCfg[index].TelegramChannel = append(newsCfg[index].TelegramChannel, config.NewsConfigTelegramChannel{ID: "ChannelPANews", Name: "PANews"})
}
}
}
}
// 为每个交易员获取AI模型和交易所配置
for _, traderCfg := range allTraders {
for _, traderCfg := range allTraders {
// 获取AI模型配置使用交易员所属的用户ID
aiModels, err := database.GetAIModels(traderCfg.UserID)
if err != nil {
@@ -157,7 +181,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, newsCfg, coinPoolURL, oiTopURL, maxDailyLoss, maxDrawdown, stopTradingMinutes, defaultCoins)
if err != nil {
log.Printf("❌ 添加交易员 %s 失败: %v", traderCfg.Name, err)
continue
@@ -169,7 +193,7 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
}
// addTraderFromConfig 内部方法:从配置添加交易员(不加锁,因为调用方已加锁)
func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModelCfg *config.AIModelConfig, exchangeCfg *config.ExchangeConfig, newsCfg []config.NewsConfig, coinPoolURL, oiTopURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, defaultCoins []string) error {
if _, exists := tm.traders[traderCfg.ID]; exists {
return fmt.Errorf("trader ID '%s' 已存在", traderCfg.ID)
}
@@ -186,7 +210,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
}
}
}
// 如果没有指定交易币种,使用默认币种
if len(tradingCoins) == 0 {
tradingCoins = defaultCoins
@@ -200,7 +224,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作为模型标识
@@ -226,6 +250,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
DefaultCoins: defaultCoins,
TradingCoins: tradingCoins,
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
NewsConfig: newsCfg, // 新闻源配置
}
// 根据交易所类型设置API密钥
@@ -253,7 +278,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 +318,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
}
}
}
// 如果没有指定交易币种,使用默认币种
if len(tradingCoins) == 0 {
tradingCoins = defaultCoins
@@ -359,7 +384,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 +513,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 +547,7 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) {
"is_running": status["is_running"],
}
}
traders = append(traders, traderData)
}
comparison["traders"] = traders
@@ -708,7 +733,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
}
}
}
// 如果没有指定交易币种,使用默认币种
if len(tradingCoins) == 0 {
tradingCoins = defaultCoins
@@ -723,25 +748,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 +794,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)