mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 13:00:59 +08:00
feat(config): 增加新闻源配置与数据库迁移支持
- 在config.json.example中添加新闻源相关配置示例,支持telegram频道订阅 - 扩展数据库表结构,新增traders表多字段支持信号源和杠杆参数 - 新增NewsConfig结构体及相关telegram新闻配置数据模型 - 在交易决策上下文结构Context中添加新闻数据news字段支持传递新闻 - 在交易决策构建用户提示信息时加入相关新闻内容 - 优化数据库操作代码,支持交易所和交易员配置的完整字段读取与更新 - 添加数据库exchanges表迁移逻辑,重建表结构和触发器以支持新字段 - 引入第三方库github.com/samber/lo用于集合操作 - 在go.mod添加新的依赖模块,并更新相关依赖版本
This commit is contained in:
@@ -177,17 +177,17 @@ func (d *Database) createTables() error {
|
||||
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
|
||||
`ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`,
|
||||
`ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`,
|
||||
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
|
||||
`ALTER TABLE traders ADD COLUMN use_default_coins BOOLEAN DEFAULT 1`, // 默认使用默认币种
|
||||
`ALTER TABLE traders ADD COLUMN custom_coins TEXT DEFAULT ''`, // 自定义币种列表(JSON格式)
|
||||
`ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
|
||||
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
|
||||
`ALTER TABLE traders ADD COLUMN use_default_coins BOOLEAN DEFAULT 1`, // 默认使用默认币种
|
||||
`ALTER TABLE traders ADD COLUMN custom_coins TEXT DEFAULT ''`, // 自定义币种列表(JSON格式)
|
||||
`ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
|
||||
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||
`ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
|
||||
}
|
||||
|
||||
for _, query := range alterQueries {
|
||||
@@ -245,16 +245,16 @@ func (d *Database) initDefaultData() error {
|
||||
|
||||
// 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新
|
||||
systemConfigs := map[string]string{
|
||||
"admin_mode": "true", // 默认开启管理员模式,便于首次使用
|
||||
"api_server_port": "8080", // 默认API端口
|
||||
"use_default_coins": "true", // 默认使用内置币种列表
|
||||
"default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
|
||||
"max_daily_loss": "10.0", // 最大日损失百分比
|
||||
"max_drawdown": "20.0", // 最大回撤百分比
|
||||
"stop_trading_minutes": "60", // 停止交易时间(分钟)
|
||||
"btc_eth_leverage": "5", // BTC/ETH杠杆倍数
|
||||
"altcoin_leverage": "5", // 山寨币杠杆倍数
|
||||
"jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成
|
||||
"admin_mode": "true", // 默认开启管理员模式,便于首次使用
|
||||
"api_server_port": "8080", // 默认API端口
|
||||
"use_default_coins": "true", // 默认使用内置币种列表
|
||||
"default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式)
|
||||
"max_daily_loss": "10.0", // 最大日损失百分比
|
||||
"max_drawdown": "20.0", // 最大回撤百分比
|
||||
"stop_trading_minutes": "60", // 停止交易时间(分钟)
|
||||
"btc_eth_leverage": "5", // BTC/ETH杠杆倍数
|
||||
"altcoin_leverage": "5", // 山寨币杠杆倍数
|
||||
"jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成
|
||||
}
|
||||
|
||||
for key, value := range systemConfigs {
|
||||
@@ -281,14 +281,14 @@ func (d *Database) migrateExchangesTable() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 如果已经迁移过,直接返回
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
log.Printf("🔄 开始迁移exchanges表...")
|
||||
|
||||
|
||||
// 创建新的exchanges表,使用复合主键
|
||||
_, err = d.db.Exec(`
|
||||
CREATE TABLE exchanges_new (
|
||||
@@ -313,7 +313,7 @@ func (d *Database) migrateExchangesTable() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建新exchanges表失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 复制数据到新表
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO exchanges_new
|
||||
@@ -322,19 +322,19 @@ func (d *Database) migrateExchangesTable() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("复制数据失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 删除旧表
|
||||
_, err = d.db.Exec(`DROP TABLE exchanges`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("删除旧表失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 重命名新表
|
||||
_, err = d.db.Exec(`ALTER TABLE exchanges_new RENAME TO exchanges`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("重命名表失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 重新创建触发器
|
||||
_, err = d.db.Exec(`
|
||||
CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
|
||||
@@ -347,20 +347,20 @@ func (d *Database) migrateExchangesTable() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建触发器失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
log.Printf("✅ exchanges表迁移完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// User 用户配置
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"-"` // 不返回到前端
|
||||
OTPSecret string `json:"-"` // 不返回到前端
|
||||
OTPVerified bool `json:"otp_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"-"` // 不返回到前端
|
||||
OTPSecret string `json:"-"` // 不返回到前端
|
||||
OTPVerified bool `json:"otp_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AIModelConfig AI模型配置
|
||||
@@ -379,36 +379,36 @@ type AIModelConfig struct {
|
||||
|
||||
// ExchangeConfig 交易所配置
|
||||
type ExchangeConfig struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Testnet bool `json:"testnet"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Testnet bool `json:"testnet"`
|
||||
// Hyperliquid 特定字段
|
||||
HyperliquidWalletAddr string `json:"hyperliquidWalletAddr"`
|
||||
// Aster 特定字段
|
||||
AsterUser string `json:"asterUser"`
|
||||
AsterSigner string `json:"asterSigner"`
|
||||
AsterPrivateKey string `json:"asterPrivateKey"`
|
||||
AsterUser string `json:"asterUser"`
|
||||
AsterSigner string `json:"asterSigner"`
|
||||
AsterPrivateKey string `json:"asterPrivateKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TraderRecord 交易员配置(数据库实体)
|
||||
type TraderRecord struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
AIModelID string `json:"ai_model_id"`
|
||||
ExchangeID string `json:"exchange_id"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
IsRunning bool `json:"is_running"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
||||
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
AIModelID string `json:"ai_model_id"`
|
||||
ExchangeID string `json:"exchange_id"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
IsRunning bool `json:"is_running"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
||||
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
||||
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
||||
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
||||
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
||||
@@ -422,12 +422,31 @@ type TraderRecord struct {
|
||||
|
||||
// UserSignalSource 用户信号源配置
|
||||
type UserSignalSource struct {
|
||||
ID int `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CoinPoolURL string `json:"coin_pool_url"`
|
||||
OITopURL string `json:"oi_top_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID int `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
CoinPoolURL string `json:"coin_pool_url"`
|
||||
OITopURL string `json:"oi_top_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// NewsConfig 新闻配置
|
||||
type NewsConfig struct {
|
||||
Provider string `json:"provider"` // 新闻搜索器名称: telegram
|
||||
Telegram NewsConfigTelegram `json:"telegram"` // telegram客户端配置
|
||||
TelegramChannel []NewsConfigTelegramChannel `json:"channels"` // telegram频道配置
|
||||
}
|
||||
|
||||
// NewsConfigTelegram telegram配置
|
||||
type NewsConfigTelegram struct {
|
||||
BaseURL string `json:"baseurl"` // 基础url
|
||||
ProxyURL string `json:"proxyurl"` // 代理url
|
||||
}
|
||||
|
||||
// NewsConfigTelegramChannel 电报频道配置
|
||||
type NewsConfigTelegramChannel struct {
|
||||
ID string `json:"id"` // 频道id
|
||||
Name string `json:"name"` // 频道名称
|
||||
}
|
||||
|
||||
// GenerateOTPSecret 生成OTP密钥
|
||||
@@ -457,12 +476,12 @@ func (d *Database) EnsureAdminUser() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 如果已存在,直接返回
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// 创建admin用户(密码为空,因为管理员模式下不需要密码)
|
||||
adminUser := &User{
|
||||
ID: "admin",
|
||||
@@ -471,7 +490,7 @@ func (d *Database) EnsureAdminUser() error {
|
||||
OTPSecret: "",
|
||||
OTPVerified: true,
|
||||
}
|
||||
|
||||
|
||||
return d.CreateUser(adminUser)
|
||||
}
|
||||
|
||||
@@ -482,7 +501,7 @@ func (d *Database) GetUserByEmail(email string) (*User, error) {
|
||||
SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
|
||||
FROM users WHERE email = ?
|
||||
`, email).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -498,7 +517,7 @@ func (d *Database) GetUserByID(userID string) (*User, error) {
|
||||
SELECT id, email, password_hash, otp_secret, otp_verified, created_at, updated_at
|
||||
FROM users WHERE id = ?
|
||||
`, userID).Scan(
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.ID, &user.Email, &user.PasswordHash, &user.OTPSecret,
|
||||
&user.OTPVerified, &user.CreatedAt, &user.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -668,7 +687,7 @@ func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||
err := rows.Scan(
|
||||
&exchange.ID, &exchange.UserID, &exchange.Name, &exchange.Type,
|
||||
&exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||
&exchange.HyperliquidWalletAddr, &exchange.AsterUser,
|
||||
&exchange.HyperliquidWalletAddr, &exchange.AsterUser,
|
||||
&exchange.AsterSigner, &exchange.AsterPrivateKey,
|
||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
)
|
||||
@@ -684,7 +703,7 @@ func (d *Database) GetExchanges(userID string) ([]*ExchangeConfig, error) {
|
||||
// UpdateExchange 更新交易所配置,如果不存在则创建用户特定配置
|
||||
func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secretKey string, testnet bool, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey string) error {
|
||||
log.Printf("🔧 UpdateExchange: userID=%s, id=%s, enabled=%v", userID, id, enabled)
|
||||
|
||||
|
||||
// 首先尝试更新现有的用户配置
|
||||
result, err := d.db.Exec(`
|
||||
UPDATE exchanges SET enabled = ?, api_key = ?, secret_key = ?, testnet = ?,
|
||||
@@ -695,20 +714,20 @@ func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secre
|
||||
log.Printf("❌ UpdateExchange: 更新失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 检查是否有行被更新
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 获取影响行数失败: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
log.Printf("📊 UpdateExchange: 影响行数 = %d", rowsAffected)
|
||||
|
||||
|
||||
// 如果没有行被更新,说明用户没有这个交易所的配置,需要创建
|
||||
if rowsAffected == 0 {
|
||||
log.Printf("💡 UpdateExchange: 没有现有记录,创建新记录")
|
||||
|
||||
|
||||
// 根据交易所ID确定基本信息
|
||||
var name, typ string
|
||||
if id == "binance" {
|
||||
@@ -724,16 +743,16 @@ func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secre
|
||||
name = id + " Exchange"
|
||||
typ = "cex"
|
||||
}
|
||||
|
||||
|
||||
log.Printf("🆕 UpdateExchange: 创建新记录 ID=%s, name=%s, type=%s", id, name, typ)
|
||||
|
||||
|
||||
// 创建用户特定的配置,使用原始的交易所ID
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO exchanges (id, user_id, name, type, enabled, api_key, secret_key, testnet,
|
||||
hyperliquid_wallet_addr, aster_user, aster_signer, aster_private_key, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, id, userID, name, typ, enabled, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey)
|
||||
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ UpdateExchange: 创建记录失败: %v", err)
|
||||
} else {
|
||||
@@ -741,7 +760,7 @@ func (d *Database) UpdateExchange(userID, id string, enabled bool, apiKey, secre
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
log.Printf("✅ UpdateExchange: 更新现有记录成功")
|
||||
return nil
|
||||
}
|
||||
@@ -790,9 +809,9 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var traders []*TraderRecord
|
||||
var traders []*TraderRecord
|
||||
for rows.Next() {
|
||||
var trader TraderRecord
|
||||
var trader TraderRecord
|
||||
err := rows.Scan(
|
||||
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
@@ -847,7 +866,7 @@ func (d *Database) DeleteTrader(userID, id string) error {
|
||||
|
||||
// GetTraderConfig 获取交易员完整配置(包含AI模型和交易所信息)
|
||||
func (d *Database) GetTraderConfig(userID, traderID string) (*TraderRecord, *AIModelConfig, *ExchangeConfig, error) {
|
||||
var trader TraderRecord
|
||||
var trader TraderRecord
|
||||
var aiModel AIModelConfig
|
||||
var exchange ExchangeConfig
|
||||
|
||||
@@ -873,7 +892,7 @@ func (d *Database) GetTraderConfig(userID, traderID string) (*TraderRecord, *AIM
|
||||
`, traderID, userID).Scan(
|
||||
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols, &trader.UseCoinPool,
|
||||
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols, &trader.UseCoinPool,
|
||||
&trader.UseOITop, &trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
&aiModel.ID, &aiModel.UserID, &aiModel.Name, &aiModel.Provider, &aiModel.Enabled, &aiModel.APIKey,
|
||||
@@ -943,4 +962,4 @@ func (d *Database) UpdateUserSignalSource(userID, coinPoolURL, oiTopURL string)
|
||||
// Close 关闭数据库连接
|
||||
func (d *Database) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user