mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 23:07:01 +08:00
sync fork
This commit is contained in:
138
config/config.go
138
config/config.go
@@ -1,138 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TraderConfig 单个trader的配置
|
||||
type TraderConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
AIModel string `json:"ai_model"` // "qwen" or "deepseek"
|
||||
|
||||
// 交易平台选择(二选一)
|
||||
Exchange string `json:"exchange"` // "binance" or "hyperliquid"
|
||||
|
||||
// 币安配置
|
||||
BinanceAPIKey string `json:"binance_api_key,omitempty"`
|
||||
BinanceSecretKey string `json:"binance_secret_key,omitempty"`
|
||||
|
||||
// Hyperliquid配置
|
||||
HyperliquidPrivateKey string `json:"hyperliquid_private_key,omitempty"`
|
||||
HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"`
|
||||
|
||||
// AI配置
|
||||
QwenKey string `json:"qwen_key,omitempty"`
|
||||
DeepSeekKey string `json:"deepseek_key,omitempty"`
|
||||
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
}
|
||||
|
||||
// Config 总配置
|
||||
type Config struct {
|
||||
Traders []TraderConfig `json:"traders"`
|
||||
UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表
|
||||
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
||||
OITopAPIURL string `json:"oi_top_api_url"`
|
||||
APIServerPort int `json:"api_server_port"`
|
||||
MaxDailyLoss float64 `json:"max_daily_loss"`
|
||||
MaxDrawdown float64 `json:"max_drawdown"`
|
||||
StopTradingMinutes int `json:"stop_trading_minutes"`
|
||||
}
|
||||
|
||||
// LoadConfig 从文件加载配置
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 设置默认值:如果use_default_coins未设置(为false)且没有配置coin_pool_api_url,则默认使用默认币种列表
|
||||
if !config.UseDefaultCoins && config.CoinPoolAPIURL == "" {
|
||||
config.UseDefaultCoins = true
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("配置验证失败: %w", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// Validate 验证配置有效性
|
||||
func (c *Config) Validate() error {
|
||||
if len(c.Traders) == 0 {
|
||||
return fmt.Errorf("至少需要配置一个trader")
|
||||
}
|
||||
|
||||
traderIDs := make(map[string]bool)
|
||||
for i, trader := range c.Traders {
|
||||
if trader.ID == "" {
|
||||
return fmt.Errorf("trader[%d]: ID不能为空", i)
|
||||
}
|
||||
if traderIDs[trader.ID] {
|
||||
return fmt.Errorf("trader[%d]: ID '%s' 重复", i, trader.ID)
|
||||
}
|
||||
traderIDs[trader.ID] = true
|
||||
|
||||
if trader.Name == "" {
|
||||
return fmt.Errorf("trader[%d]: Name不能为空", i)
|
||||
}
|
||||
if trader.AIModel != "qwen" && trader.AIModel != "deepseek" {
|
||||
return fmt.Errorf("trader[%d]: ai_model必须是 'qwen' 或 'deepseek'", i)
|
||||
}
|
||||
|
||||
// 验证交易平台配置
|
||||
if trader.Exchange == "" {
|
||||
trader.Exchange = "binance" // 默认使用币安
|
||||
}
|
||||
if trader.Exchange != "binance" && trader.Exchange != "hyperliquid" {
|
||||
return fmt.Errorf("trader[%d]: exchange必须是 'binance' 或 'hyperliquid'", i)
|
||||
}
|
||||
|
||||
// 根据平台验证对应的密钥
|
||||
if trader.Exchange == "binance" {
|
||||
if trader.BinanceAPIKey == "" || trader.BinanceSecretKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用币安时必须配置binance_api_key和binance_secret_key", i)
|
||||
}
|
||||
} else if trader.Exchange == "hyperliquid" {
|
||||
if trader.HyperliquidPrivateKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用Hyperliquid时必须配置hyperliquid_private_key", i)
|
||||
}
|
||||
}
|
||||
|
||||
if trader.AIModel == "qwen" && trader.QwenKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用Qwen时必须配置qwen_key", i)
|
||||
}
|
||||
if trader.AIModel == "deepseek" && trader.DeepSeekKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用DeepSeek时必须配置deepseek_key", i)
|
||||
}
|
||||
if trader.InitialBalance <= 0 {
|
||||
return fmt.Errorf("trader[%d]: initial_balance必须大于0", i)
|
||||
}
|
||||
if trader.ScanIntervalMinutes <= 0 {
|
||||
trader.ScanIntervalMinutes = 3 // 默认3分钟
|
||||
}
|
||||
}
|
||||
|
||||
if c.APIServerPort <= 0 {
|
||||
c.APIServerPort = 8080 // 默认8080端口
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetScanInterval 获取扫描间隔
|
||||
func (tc *TraderConfig) GetScanInterval() time.Duration {
|
||||
return time.Duration(tc.ScanIntervalMinutes) * time.Minute
|
||||
}
|
||||
390
config/database.go
Normal file
390
config/database.go
Normal file
@@ -0,0 +1,390 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// Database 配置数据库
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewDatabase 创建配置数据库
|
||||
func NewDatabase(dbPath string) (*Database, error) {
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("打开数据库失败: %w", err)
|
||||
}
|
||||
|
||||
database := &Database{db: db}
|
||||
if err := database.createTables(); err != nil {
|
||||
return nil, fmt.Errorf("创建表失败: %w", err)
|
||||
}
|
||||
|
||||
if err := database.initDefaultData(); err != nil {
|
||||
return nil, fmt.Errorf("初始化默认数据失败: %w", err)
|
||||
}
|
||||
|
||||
return database, nil
|
||||
}
|
||||
|
||||
// createTables 创建数据库表
|
||||
func (d *Database) createTables() error {
|
||||
queries := []string{
|
||||
// AI模型配置表
|
||||
`CREATE TABLE IF NOT EXISTS ai_models (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT 0,
|
||||
api_key TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
|
||||
// 交易所配置表
|
||||
`CREATE TABLE IF NOT EXISTS exchanges (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL, -- 'cex' or 'dex'
|
||||
enabled BOOLEAN DEFAULT 0,
|
||||
api_key TEXT DEFAULT '',
|
||||
secret_key TEXT DEFAULT '',
|
||||
testnet BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
|
||||
// 交易员配置表
|
||||
`CREATE TABLE IF NOT EXISTS traders (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
ai_model_id TEXT NOT NULL,
|
||||
exchange_id TEXT NOT NULL,
|
||||
initial_balance REAL NOT NULL,
|
||||
scan_interval_minutes INTEGER DEFAULT 3,
|
||||
is_running BOOLEAN DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (ai_model_id) REFERENCES ai_models(id),
|
||||
FOREIGN KEY (exchange_id) REFERENCES exchanges(id)
|
||||
)`,
|
||||
|
||||
// 系统配置表
|
||||
`CREATE TABLE IF NOT EXISTS system_config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`,
|
||||
|
||||
// 触发器:自动更新 updated_at
|
||||
`CREATE TRIGGER IF NOT EXISTS update_ai_models_updated_at
|
||||
AFTER UPDATE ON ai_models
|
||||
BEGIN
|
||||
UPDATE ai_models SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_exchanges_updated_at
|
||||
AFTER UPDATE ON exchanges
|
||||
BEGIN
|
||||
UPDATE exchanges SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_traders_updated_at
|
||||
AFTER UPDATE ON traders
|
||||
BEGIN
|
||||
UPDATE traders SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||
END`,
|
||||
|
||||
`CREATE TRIGGER IF NOT EXISTS update_system_config_updated_at
|
||||
AFTER UPDATE ON system_config
|
||||
BEGIN
|
||||
UPDATE system_config SET updated_at = CURRENT_TIMESTAMP WHERE key = NEW.key;
|
||||
END`,
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
if _, err := d.db.Exec(query); err != nil {
|
||||
return fmt.Errorf("执行SQL失败 [%s]: %w", query, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initDefaultData 初始化默认数据
|
||||
func (d *Database) initDefaultData() error {
|
||||
// 初始化AI模型
|
||||
aiModels := []struct {
|
||||
id, name, provider string
|
||||
}{
|
||||
{"deepseek", "DeepSeek", "deepseek"},
|
||||
{"qwen", "Qwen", "qwen"},
|
||||
}
|
||||
|
||||
for _, model := range aiModels {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO ai_models (id, name, provider, enabled)
|
||||
VALUES (?, ?, ?, 0)
|
||||
`, model.id, model.name, model.provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化AI模型失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化交易所
|
||||
exchanges := []struct {
|
||||
id, name, typ string
|
||||
}{
|
||||
{"binance", "Binance", "cex"},
|
||||
{"hyperliquid", "Hyperliquid", "dex"},
|
||||
}
|
||||
|
||||
for _, exchange := range exchanges {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO exchanges (id, name, type, enabled)
|
||||
VALUES (?, ?, ?, 0)
|
||||
`, exchange.id, exchange.name, exchange.typ)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化交易所失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化系统配置
|
||||
systemConfigs := map[string]string{
|
||||
"api_server_port": "8081",
|
||||
"use_default_coins": "true",
|
||||
"coin_pool_api_url": "",
|
||||
"oi_top_api_url": "",
|
||||
"max_daily_loss": "10.0",
|
||||
"max_drawdown": "20.0",
|
||||
"stop_trading_minutes": "60",
|
||||
}
|
||||
|
||||
for key, value := range systemConfigs {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR IGNORE INTO system_config (key, value)
|
||||
VALUES (?, ?)
|
||||
`, key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("初始化系统配置失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AIModelConfig AI模型配置
|
||||
type AIModelConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ExchangeConfig 交易所配置
|
||||
type ExchangeConfig struct {
|
||||
ID string `json:"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"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TraderConfig 交易员配置
|
||||
type TraderConfig struct {
|
||||
ID string `json:"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"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// GetAIModels 获取所有AI模型配置
|
||||
func (d *Database) GetAIModels() ([]*AIModelConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, name, provider, enabled, api_key, created_at, updated_at
|
||||
FROM ai_models ORDER BY id
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var models []*AIModelConfig
|
||||
for rows.Next() {
|
||||
var model AIModelConfig
|
||||
err := rows.Scan(
|
||||
&model.ID, &model.Name, &model.Provider,
|
||||
&model.Enabled, &model.APIKey,
|
||||
&model.CreatedAt, &model.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models = append(models, &model)
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// UpdateAIModel 更新AI模型配置
|
||||
func (d *Database) UpdateAIModel(id string, enabled bool, apiKey string) error {
|
||||
_, err := d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ? WHERE id = ?
|
||||
`, enabled, apiKey, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetExchanges 获取所有交易所配置
|
||||
func (d *Database) GetExchanges() ([]*ExchangeConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, name, type, enabled, api_key, secret_key, testnet, created_at, updated_at
|
||||
FROM exchanges ORDER BY id
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var exchanges []*ExchangeConfig
|
||||
for rows.Next() {
|
||||
var exchange ExchangeConfig
|
||||
err := rows.Scan(
|
||||
&exchange.ID, &exchange.Name, &exchange.Type,
|
||||
&exchange.Enabled, &exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exchanges = append(exchanges, &exchange)
|
||||
}
|
||||
|
||||
return exchanges, nil
|
||||
}
|
||||
|
||||
// UpdateExchange 更新交易所配置
|
||||
func (d *Database) UpdateExchange(id string, enabled bool, apiKey, secretKey string, testnet bool) error {
|
||||
_, err := d.db.Exec(`
|
||||
UPDATE exchanges SET enabled = ?, api_key = ?, secret_key = ?, testnet = ? WHERE id = ?
|
||||
`, enabled, apiKey, secretKey, testnet, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateTrader 创建交易员
|
||||
func (d *Database) CreateTrader(trader *TraderConfig) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO traders (id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, trader.ID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTraders 获取所有交易员
|
||||
func (d *Database) GetTraders() ([]*TraderConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, created_at, updated_at
|
||||
FROM traders ORDER BY created_at DESC
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var traders []*TraderConfig
|
||||
for rows.Next() {
|
||||
var trader TraderConfig
|
||||
err := rows.Scan(
|
||||
&trader.ID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
traders = append(traders, &trader)
|
||||
}
|
||||
|
||||
return traders, nil
|
||||
}
|
||||
|
||||
// UpdateTraderStatus 更新交易员状态
|
||||
func (d *Database) UpdateTraderStatus(id string, isRunning bool) error {
|
||||
_, err := d.db.Exec(`UPDATE traders SET is_running = ? WHERE id = ?`, isRunning, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteTrader 删除交易员
|
||||
func (d *Database) DeleteTrader(id string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM traders WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTraderConfig 获取交易员完整配置(包含AI模型和交易所信息)
|
||||
func (d *Database) GetTraderConfig(traderID string) (*TraderConfig, *AIModelConfig, *ExchangeConfig, error) {
|
||||
var trader TraderConfig
|
||||
var aiModel AIModelConfig
|
||||
var exchange ExchangeConfig
|
||||
|
||||
err := d.db.QueryRow(`
|
||||
SELECT
|
||||
t.id, t.name, t.ai_model_id, t.exchange_id, t.initial_balance, t.scan_interval_minutes, t.is_running, t.created_at, t.updated_at,
|
||||
a.id, a.name, a.provider, a.enabled, a.api_key, a.created_at, a.updated_at,
|
||||
e.id, e.name, e.type, e.enabled, e.api_key, e.secret_key, e.testnet, e.created_at, e.updated_at
|
||||
FROM traders t
|
||||
JOIN ai_models a ON t.ai_model_id = a.id
|
||||
JOIN exchanges e ON t.exchange_id = e.id
|
||||
WHERE t.id = ?
|
||||
`, traderID).Scan(
|
||||
&trader.ID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
&aiModel.ID, &aiModel.Name, &aiModel.Provider, &aiModel.Enabled, &aiModel.APIKey,
|
||||
&aiModel.CreatedAt, &aiModel.UpdatedAt,
|
||||
&exchange.ID, &exchange.Name, &exchange.Type, &exchange.Enabled,
|
||||
&exchange.APIKey, &exchange.SecretKey, &exchange.Testnet,
|
||||
&exchange.CreatedAt, &exchange.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return &trader, &aiModel, &exchange, nil
|
||||
}
|
||||
|
||||
// GetSystemConfig 获取系统配置
|
||||
func (d *Database) GetSystemConfig(key string) (string, error) {
|
||||
var value string
|
||||
err := d.db.QueryRow(`SELECT value FROM system_config WHERE key = ?`, key).Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
// SetSystemConfig 设置系统配置
|
||||
func (d *Database) SetSystemConfig(key, value string) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT OR REPLACE INTO system_config (key, value) VALUES (?, ?)
|
||||
`, key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Close 关闭数据库连接
|
||||
func (d *Database) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user