From eb168822827f54f4b829423e14d80f49074f830e Mon Sep 17 00:00:00 2001 From: Shui <88711385+hzb1115@users.noreply.github.com> Date: Thu, 6 Nov 2025 21:53:10 -0500 Subject: [PATCH] fix(bootstrap module): add bootstrap module to meet future function (#674) * fix(bootstrap module): add bootstrap module to meet future function * Fix readme * Fix panic because log.logger is nil * fix import --------- Co-authored-by: zbhan --- bootstrap/README.md | 455 ++++++++++++++++++++++++++++++++++++++ bootstrap/bootstrap.go | 168 ++++++++++++++ bootstrap/context.go | 49 ++++ bootstrap/hook_builder.go | 27 +++ bootstrap/init_hook.go | 22 ++ config/config.go | 193 ++-------------- main.go | 44 ++-- 7 files changed, 769 insertions(+), 189 deletions(-) create mode 100644 bootstrap/README.md create mode 100644 bootstrap/bootstrap.go create mode 100644 bootstrap/context.go create mode 100644 bootstrap/hook_builder.go create mode 100644 bootstrap/init_hook.go diff --git a/bootstrap/README.md b/bootstrap/README.md new file mode 100644 index 00000000..4db4b260 --- /dev/null +++ b/bootstrap/README.md @@ -0,0 +1,455 @@ +# Bootstrap 模块初始化框架 + +## 概述 + +Bootstrap 是一个模块化的初始化框架,允许各个模块通过注册钩子的方式自动完成初始化,支持优先级控制、条件初始化、错误策略等高级特性。 + +## 核心特性 + +- ✅ **优先级排序** - 保证模块按正确的顺序初始化 +- ✅ **钩子命名** - 每个钩子都有清晰的名称,便于日志追踪和错误定位 +- ✅ **上下文传递** - 模块之间可以共享数据(如数据库实例) +- ✅ **条件初始化** - 根据配置动态决定是否初始化某个模块 +- ✅ **灵活的错误处理** - 支持快速失败、继续执行、警告三种策略 +- ✅ **详细日志** - 显示初始化进度、耗时统计 +- ✅ **线程安全** - 使用互斥锁保护全局状态 +- ✅ **测试友好** - 提供 Clear() 方法清除钩子 + +## 快速开始 + +### 1. 在模块中注册初始化钩子 + +在你的模块包中创建 `init.go` 文件: + +```go +// proxy/init.go +package proxy + +import ( + "nofx/bootstrap" + "nofx/config" +) + +func init() { + // 注册初始化钩子 + bootstrap.Register("Proxy模块", bootstrap.PriorityCore, initProxyModule) +} + +func initProxyModule(ctx *bootstrap.Context) error { + // 从配置中读取 proxy 配置 + proxyConfig := ctx.Config.Proxy + + // 初始化代理管理器 + if err := InitGlobalProxyManager(proxyConfig); err != nil { + return err + } + + // 将实例存储到上下文,供其他模块使用 + ctx.Set("proxy_manager", GetGlobalProxyManager()) + + return nil +} +``` + +### 2. 在 main.go 中运行初始化 + +```go +package main + +import ( + "log" + "nofx/bootstrap" + "nofx/config" + + // 导入需要初始化的模块(触发 init() 注册) + _ "nofx/proxy" + _ "nofx/market" + _ "nofx/trader" +) + +func main() { + // 加载配置 + cfg, err := config.LoadConfig("config.json") + if err != nil { + log.Fatalf("加载配置失败: %v", err) + } + + // 创建初始化上下文 + ctx := bootstrap.NewContext(cfg) + + // 执行所有初始化钩子 + if err := bootstrap.Run(ctx); err != nil { + log.Fatalf("初始化失败: %v", err) + } + + // 启动业务逻辑... +} +``` + +### 3. 运行效果 + +``` +🔄 开始初始化 3 个模块... + [1/3] 初始化: Database模块 (优先级: 20) + ✓ 完成: Database模块 (耗时: 120ms) + [2/3] 初始化: Proxy模块 (优先级: 50) + ↳ 代理自动刷新已启动 (间隔: 30m0s) + ↳ 代理池状态: 总计=5, 黑名单=0, 可用=5 + ✓ 完成: Proxy模块 (耗时: 35ms) + [3/3] 初始化: Market模块 (优先级: 100) + ✓ 完成: Market模块 (耗时: 200ms) +✅ 所有模块初始化完成 (总耗时: 355ms) +📊 统计: 成功=3, 跳过=0 +``` + +## 优先级常量 + +系统预定义了以下优先级常量(数值越小越先执行): + +| 常量 | 值 | 用途 | 示例 | +|------|-----|------|------| +| `PriorityInfrastructure` | 10 | 基础设施 | 日志系统、配置加载 | +| `PriorityDatabase` | 20 | 数据库连接 | SQLite、Redis | +| `PriorityCore` | 50 | 核心模块 | Proxy、Market Monitor | +| `PriorityBusiness` | 100 | 业务模块 | Trader、API Server | +| `PriorityBackground` | 200 | 后台任务 | 定时任务、监控 | + +### 使用示例 + +```go +// 数据库模块(最先初始化) +bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase) + +// 代理模块(核心模块) +bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy) + +// Trader模块(依赖数据库和代理) +bootstrap.Register("Trader", bootstrap.PriorityBusiness, initTrader) +``` + +## 高级特性 + +### 1. 条件初始化 + +某些模块只在特定条件下才需要初始化: + +```go +bootstrap.Register("Proxy模块", bootstrap.PriorityCore, initProxy). + EnabledIf(func(ctx *bootstrap.Context) bool { + // 只在配置中启用 proxy 时才初始化 + return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled + }) +``` + +**输出**: +``` + [2/5] 跳过: Proxy模块 (条件未满足) +``` + +### 2. 错误处理策略 + +支持三种错误处理策略: + +#### FailFast(默认)- 遇到错误立即停止 + +```go +bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase) +// 默认就是 FailFast,无需显式设置 +``` + +**效果**:Database 初始化失败,整个系统停止启动 + +#### ContinueOnError - 继续执行,收集所有错误 + +```go +bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy). + OnError(bootstrap.ContinueOnError) +``` + +**效果**:Proxy 失败不影响其他模块,最后汇总所有错误 + +#### WarnOnError - 继续执行,只打印警告 + +```go +bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy). + OnError(bootstrap.WarnOnError) +``` + +**效果**:Proxy 失败只打印警告,不影响系统运行 + +**输出**: +``` + [2/5] 初始化: Proxy模块 (优先级: 50) + ⚠️ 警告: Proxy模块 (耗时: 15ms) - 连接代理服务器超时 +``` + +### 3. 上下文数据共享 + +模块之间可以通过 Context 共享数据: + +```go +// database/init.go - 存储数据库实例 +func initDatabase(ctx *bootstrap.Context) error { + db, err := sql.Open("sqlite", "config.db") + if err != nil { + return err + } + + // 存储到上下文 + ctx.Set("database", db) + return nil +} + +// trader/init.go - 获取数据库实例 +func initTrader(ctx *bootstrap.Context) error { + // 从上下文获取数据库实例 + db, ok := ctx.Get("database") + if !ok { + return fmt.Errorf("database 未初始化") + } + + database := db.(*sql.DB) + // 使用 database 初始化 trader... + return nil +} +``` + +**安全获取**: +```go +// 使用 MustGet,不存在会 panic(适合必需的依赖) +db := ctx.MustGet("database").(*sql.DB) +``` + +### 4. 链式调用 + +支持流畅的链式调用: + +```go +bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy). + EnabledIf(func(ctx *bootstrap.Context) bool { + return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled + }). + OnError(bootstrap.WarnOnError) +``` + +### 5. 自定义错误策略 + +在 Run 时可以指定全局默认错误策略: + +```go +// 所有钩子默认使用 ContinueOnError,除非钩子自己指定了 FailFast +err := bootstrap.RunWithPolicy(ctx, bootstrap.ContinueOnError) +``` + +## 完整示例 + +### 示例1:Database 模块 + +```go +// database/init.go +package database + +import ( + "database/sql" + "nofx/bootstrap" +) + +func init() { + bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase) +} + +func initDatabase(ctx *bootstrap.Context) error { + db, err := sql.Open("sqlite", "config.db") + if err != nil { + return err + } + + // 测试连接 + if err := db.Ping(); err != nil { + return err + } + + // 存储到上下文 + ctx.Set("database", db) + return nil +} +``` + +### 示例2:Proxy 模块(条件初始化 + 警告策略) + +```go +// proxy/init.go +package proxy + +import ( + "nofx/bootstrap" + "nofx/config" +) + +func init() { + bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy). + EnabledIf(func(ctx *bootstrap.Context) bool { + return ctx.Config.Proxy != nil && ctx.Config.Proxy.Enabled + }). + OnError(bootstrap.WarnOnError) // Proxy 失败不影响系统 +} + +func initProxy(ctx *bootstrap.Context) error { + proxyConfig := convertConfig(ctx.Config.Proxy) + + if err := InitGlobalProxyManager(proxyConfig); err != nil { + return err + } + + ctx.Set("proxy_manager", GetGlobalProxyManager()) + return nil +} +``` + +### 示例3:Trader 模块(依赖其他模块) + +```go +// trader/init.go +package trader + +import ( + "nofx/bootstrap" +) + +func init() { + bootstrap.Register("Trader", bootstrap.PriorityBusiness, initTrader) +} + +func initTrader(ctx *bootstrap.Context) error { + // 获取依赖 + db := ctx.MustGet("database").(*sql.DB) + + // 可选依赖 + var proxyMgr *proxy.ProxyManager + if pm, ok := ctx.Get("proxy_manager"); ok { + proxyMgr = pm.(*proxy.ProxyManager) + } + + // 使用依赖初始化 trader... + return nil +} +``` + +## 调试和测试 + +### 查看已注册的钩子 + +```go +hooks := bootstrap.GetRegistered() +for _, hook := range hooks { + fmt.Printf("钩子: %s, 优先级: %d\n", hook.Name, hook.Priority) +} +``` + +### 清除钩子(用于测试) + +```go +func TestMyModule(t *testing.T) { + // 清除之前注册的钩子 + bootstrap.Clear() + + // 注册测试钩子 + bootstrap.Register("Test", 10, func(ctx *bootstrap.Context) error { + return nil + }) + + // 运行测试... +} +``` + +### 统计钩子数量 + +```go +count := bootstrap.Count() +fmt.Printf("已注册 %d 个初始化钩子\n", count) +``` + +## 错误处理最佳实践 + +### 1. 关键模块使用 FailFast + +```go +// 数据库是关键依赖,失败必须停止 +bootstrap.Register("Database", bootstrap.PriorityDatabase, initDatabase) +// 默认是 FailFast,无需显式设置 +``` + +### 2. 可选模块使用 WarnOnError + +```go +// Proxy 是可选的,失败可以使用直连 +bootstrap.Register("Proxy", bootstrap.PriorityCore, initProxy). + OnError(bootstrap.WarnOnError) +``` + +### 3. 批量初始化使用 ContinueOnError + +```go +// 批量加载插件,希望看到所有失败的插件 +for _, plugin := range plugins { + bootstrap.Register(plugin.Name, 150, plugin.Init). + OnError(bootstrap.ContinueOnError) +} +``` + +## 常见问题 + +### Q1: 如何保证模块A在模块B之前初始化? + +使用优先级控制: +```go +bootstrap.Register("ModuleA", 50, initA) // 先执行 +bootstrap.Register("ModuleB", 100, initB) // 后执行 +``` + +### Q2: 如何在初始化失败时获取详细信息? + +钩子名称会自动包含在错误信息中: +``` +Error: [Proxy模块] 初始化失败: 连接代理服务器超时 +``` + +### Q3: 可以动态注册钩子吗? + +可以,但建议在 `init()` 函数中注册: +```go +// 推荐:在 init() 中注册(包加载时自动执行) +func init() { + bootstrap.Register("MyModule", 100, initModule) +} + +// 不推荐:在运行时注册(可能导致顺序问题) +func main() { + bootstrap.Register("MyModule", 100, initModule) +} +``` + +### Q4: 如何在钩子中访问命令行参数? + +通过 Context 的 Data 字段传递: +```go +// main.go +ctx := bootstrap.NewContext(cfg) +ctx.Set("args", os.Args) + +// module/init.go +func initModule(ctx *bootstrap.Context) error { + args := ctx.MustGet("args").([]string) + // 使用 args... +} +``` +## 性能考虑 + +- 钩子注册是线程安全的,但注册本身有轻微的锁开销 +- 建议在 `init()` 函数中注册,避免运行时动态注册 +- 钩子执行是顺序的,不会并发执行 +- 每个钩子的耗时会被记录并显示 + +## 许可证 + +本模块为 NOFX 项目内部模块,遵循项目整体许可证。 diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go new file mode 100644 index 00000000..6e28cbe7 --- /dev/null +++ b/bootstrap/bootstrap.go @@ -0,0 +1,168 @@ +package bootstrap + +import ( + "fmt" + "nofx/logger" + "sort" + "sync" + "time" +) + +// Priority 初始化优先级常量 +const ( + PriorityInfrastructure = 10 // 基础设施(日志、配置等) + PriorityDatabase = 20 // 数据库连接 + PriorityCore = 50 // 核心模块(Proxy、Market等) + PriorityBusiness = 100 // 业务模块(Trader、API等) + PriorityBackground = 200 // 后台任务 +) + +// ErrorPolicy 错误处理策略 +type ErrorPolicy int + +const ( + // FailFast 遇到错误立即停止(默认) + FailFast ErrorPolicy = iota + // ContinueOnError 继续执行,收集所有错误 + ContinueOnError + // WarnOnError 继续执行,只打印警告 + WarnOnError +) + +var ( + hooks []Hook + hooksMu sync.Mutex +) + +// Register 注册初始化钩子 +// name: 模块名称(如 "Proxy", "Database") +// priority: 优先级(建议使用常量:PriorityCore、PriorityBusiness等) +// fn: 初始化函数 +func Register(name string, priority int, fn func(*Context) error) *HookBuilder { + hooksMu.Lock() + defer hooksMu.Unlock() + + hook := Hook{ + Name: name, + Priority: priority, + Func: fn, + Enabled: nil, // 默认启用 + ErrorPolicy: FailFast, + } + + hooks = append(hooks, hook) + + return &HookBuilder{hook: &hooks[len(hooks)-1]} +} + +// Run 执行所有已注册的钩子 +func Run(ctx *Context) error { + return RunWithPolicy(ctx, FailFast) +} + +// RunWithPolicy 使用指定的默认错误策略执行所有钩子 +func RunWithPolicy(ctx *Context, defaultPolicy ErrorPolicy) error { + hooksMu.Lock() + hooksCopy := make([]Hook, len(hooks)) + copy(hooksCopy, hooks) + hooksMu.Unlock() + + if len(hooksCopy) == 0 { + logger.Log.Warnf("⚠️ 没有注册任何初始化钩子") + return nil + } + + // 按优先级排序 + sort.Slice(hooksCopy, func(i, j int) bool { + return hooksCopy[i].Priority < hooksCopy[j].Priority + }) + + logger.Log.Infof("🔄 开始初始化 %d 个模块...", len(hooksCopy)) + startTime := time.Now() + + var errors []error + successCount := 0 + skippedCount := 0 + + for i, hook := range hooksCopy { + // 检查是否启用 + if hook.Enabled != nil && !hook.Enabled(ctx) { + logger.Log.Infof(" [%d/%d] 跳过: %s (条件未满足)", + i+1, len(hooksCopy), hook.Name) + skippedCount++ + continue + } + + logger.Log.Infof(" [%d/%d] 初始化: %s (优先级: %d)", + i+1, len(hooksCopy), hook.Name, hook.Priority) + + hookStart := time.Now() + err := hook.Func(ctx) + elapsed := time.Since(hookStart) + + if err != nil { + errMsg := fmt.Errorf("[%s] 初始化失败: %w", hook.Name, err) + + // 根据错误策略处理 + policy := hook.ErrorPolicy + if policy == FailFast && defaultPolicy != FailFast { + policy = defaultPolicy + } + + switch policy { + case FailFast: + logger.Log.Errorf(" ❌ 失败: %s (耗时: %v)", hook.Name, elapsed) + return errMsg + case ContinueOnError: + logger.Log.Errorf(" ❌ 失败: %s (耗时: %v) - 继续执行", hook.Name, elapsed) + errors = append(errors, errMsg) + case WarnOnError: + logger.Log.Warnf(" ⚠️ 警告: %s (耗时: %v) - %v", hook.Name, elapsed, err) + } + } else { + logger.Log.Infof(" ✓ 完成: %s (耗时: %v)", hook.Name, elapsed) + successCount++ + } + } + + totalElapsed := time.Since(startTime) + + // 汇总结果 + if len(errors) > 0 { + logger.Log.Warnf("⚠️ 初始化完成,但有 %d 个模块失败 (总耗时: %v)", + len(errors), totalElapsed) + logger.Log.Infof("📊 统计: 成功=%d, 失败=%d, 跳过=%d", + successCount, len(errors), skippedCount) + + // 返回合并的错误 + return fmt.Errorf("以下模块初始化失败: %v", errors) + } + + logger.Log.Infof("✅ 所有模块初始化完成 (总耗时: %v)", totalElapsed) + logger.Log.Infof("📊 统计: 成功=%d, 跳过=%d", successCount, skippedCount) + return nil +} + +// GetRegistered 获取已注册的钩子列表(用于调试) +func GetRegistered() []Hook { + hooksMu.Lock() + defer hooksMu.Unlock() + + hooksCopy := make([]Hook, len(hooks)) + copy(hooksCopy, hooks) + return hooksCopy +} + +// Clear 清除所有钩子(用于测试) +func Clear() { + hooksMu.Lock() + defer hooksMu.Unlock() + hooks = nil +} + +// Count 返回已注册的钩子数量 +func Count() int { + hooksMu.Lock() + defer hooksMu.Unlock() + return len(hooks) +} diff --git a/bootstrap/context.go b/bootstrap/context.go new file mode 100644 index 00000000..3616d004 --- /dev/null +++ b/bootstrap/context.go @@ -0,0 +1,49 @@ +package bootstrap + +import ( + "context" + "fmt" + "nofx/config" + "sync" +) + +// Context 初始化上下文,用于在钩子之间传递数据 +type Context struct { + Config *config.Config + Data map[string]interface{} // 存储模块之间共享的数据(如数据库实例) + ctx context.Context + mu sync.RWMutex +} + +// NewContext 创建新的初始化上下文 +func NewContext(cfg *config.Config) *Context { + return &Context{ + Config: cfg, + Data: make(map[string]interface{}), + ctx: context.Background(), + } +} + +// Set 存储数据到上下文 +func (c *Context) Set(key string, value interface{}) { + c.mu.Lock() + defer c.mu.Unlock() + c.Data[key] = value +} + +// Get 从上下文获取数据 +func (c *Context) Get(key string) (interface{}, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + val, ok := c.Data[key] + return val, ok +} + +// MustGet 从上下文获取数据,不存在则 panic +func (c *Context) MustGet(key string) interface{} { + val, ok := c.Get(key) + if !ok { + panic(fmt.Sprintf("context key '%s' not found", key)) + } + return val +} diff --git a/bootstrap/hook_builder.go b/bootstrap/hook_builder.go new file mode 100644 index 00000000..5d88d175 --- /dev/null +++ b/bootstrap/hook_builder.go @@ -0,0 +1,27 @@ +package bootstrap + +// Hook 初始化钩子 +type Hook struct { + Name string // 钩子名称(模块名) + Priority int // 优先级(越小越先执行) + Func func(*Context) error // 初始化函数 + Enabled func(*Context) bool // 条件函数,返回 false 则跳过 + ErrorPolicy ErrorPolicy // 错误处理策略 +} + +// HookBuilder 钩子构建器(用于链式调用) +type HookBuilder struct { + hook *Hook +} + +// EnabledIf 设置条件函数(链式调用) +func (b *HookBuilder) EnabledIf(fn func(*Context) bool) *HookBuilder { + b.hook.Enabled = fn + return b +} + +// OnError 设置错误处理策略(链式调用) +func (b *HookBuilder) OnError(policy ErrorPolicy) *HookBuilder { + b.hook.ErrorPolicy = policy + return b +} diff --git a/bootstrap/init_hook.go b/bootstrap/init_hook.go new file mode 100644 index 00000000..d31283c5 --- /dev/null +++ b/bootstrap/init_hook.go @@ -0,0 +1,22 @@ +package bootstrap + +import "nofx/config" + +type InitHook func(config *config.Config) error + +var InitHooks []InitHook + +// RegisterInitHook 注册初始化钩子 +func RegisterInitHook(hook InitHook) { + InitHooks = append(InitHooks, hook) +} + +// RunInitHooks 运行所有注册的初始化钩子 +func RunInitHooks(c *config.Config) error { + for _, hookF := range InitHooks { + if err := hookF(c); err != nil { + return err + } + } + return nil +} diff --git a/config/config.go b/config/config.go index b913212f..773a8d45 100644 --- a/config/config.go +++ b/config/config.go @@ -3,47 +3,10 @@ package config import ( "encoding/json" "fmt" + "log" "os" - "time" ) -// TraderConfig 单个trader的配置 -type TraderConfig struct { - ID string `json:"id"` - Name string `json:"name"` - Enabled bool `json:"enabled"` // 是否启用该trader - 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"` - HyperliquidWalletAddr string `json:"hyperliquid_wallet_addr,omitempty"` - HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"` - - // Aster配置 - AsterUser string `json:"aster_user,omitempty"` // Aster主钱包地址 - AsterSigner string `json:"aster_signer,omitempty"` // Aster API钱包地址 - AsterPrivateKey string `json:"aster_private_key,omitempty"` // Aster API钱包私钥 - - // AI配置 - QwenKey string `json:"qwen_key,omitempty"` - DeepSeekKey string `json:"deepseek_key,omitempty"` - - // 自定义AI API配置(支持任何OpenAI格式的API) - CustomAPIURL string `json:"custom_api_url,omitempty"` - CustomAPIKey string `json:"custom_api_key,omitempty"` - CustomModelName string `json:"custom_model_name,omitempty"` - - InitialBalance float64 `json:"initial_balance"` - ScanIntervalMinutes int `json:"scan_interval_minutes"` -} - // LeverageConfig 杠杆配置 type LeverageConfig struct { BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数(主账户建议5-50,子账户≤5) @@ -66,149 +29,41 @@ type TelegramConfig struct { // Config 总配置 type Config struct { - Traders []TraderConfig `json:"traders"` - UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表 - DefaultCoins []string `json:"default_coins"` // 默认主流币种池 + AdminMode bool `json:"admin_mode"` + BetaMode bool `json:"beta_mode"` APIServerPort int `json:"api_server_port"` + UseDefaultCoins bool `json:"use_default_coins"` + DefaultCoins []string `json:"default_coins"` + CoinPoolAPIURL string `json:"coin_pool_api_url"` + OITopAPIURL string `json:"oi_top_api_url"` MaxDailyLoss float64 `json:"max_daily_loss"` MaxDrawdown float64 `json:"max_drawdown"` StopTradingMinutes int `json:"stop_trading_minutes"` - Leverage LeverageConfig `json:"leverage"` // 杠杆配置 - Log *LogConfig `json:"log"` // 日志配置(可选) + Leverage LeverageConfig `json:"leverage"` + JWTSecret string `json:"jwt_secret"` + DataKLineTime string `json:"data_k_line_time"` + Log *LogConfig `json:"log"` // 日志配置 } // LoadConfig 从文件加载配置 func LoadConfig(filename string) (*Config, error) { + // 检查filename是否存在 + if _, err := os.Stat(filename); os.IsNotExist(err) { + log.Printf("📄 %s不存在,使用默认配置", filename) + return &Config{}, nil + } + + // 读取 filename data, err := os.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("读取配置文件失败: %w", err) + return nil, fmt.Errorf("读取%s失败: %w", filename, err) } - var config Config - if err := json.Unmarshal(data, &config); err != nil { - return nil, fmt.Errorf("解析配置文件失败: %w", err) + // 解析JSON + var configFile Config + if err := json.Unmarshal(data, &configFile); err != nil { + return nil, fmt.Errorf("解析%s失败: %w", filename, err) } - // 设置默认值:确保使用默认币种列表 - if !config.UseDefaultCoins { - config.UseDefaultCoins = true - } - - // 设置默认币种池 - if len(config.DefaultCoins) == 0 { - config.DefaultCoins = []string{ - "BTCUSDT", - "ETHUSDT", - "SOLUSDT", - "BNBUSDT", - "XRPUSDT", - "DOGEUSDT", - "ADAUSDT", - "HYPEUSDT", - } - } - - // 验证配置 - 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" && trader.AIModel != "custom" { - return fmt.Errorf("trader[%d]: ai_model必须是 'qwen', 'deepseek' 或 'custom'", i) - } - - // 验证交易平台配置 - if trader.Exchange == "" { - trader.Exchange = "binance" // 默认使用币安 - } - if trader.Exchange != "binance" && trader.Exchange != "hyperliquid" && trader.Exchange != "aster" { - return fmt.Errorf("trader[%d]: exchange必须是 'binance', 'hyperliquid' 或 'aster'", 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) - } - } else if trader.Exchange == "aster" { - if trader.AsterUser == "" || trader.AsterSigner == "" || trader.AsterPrivateKey == "" { - return fmt.Errorf("trader[%d]: 使用Aster时必须配置aster_user, aster_signer和aster_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.AIModel == "custom" { - if trader.CustomAPIURL == "" { - return fmt.Errorf("trader[%d]: 使用自定义API时必须配置custom_api_url", i) - } - if trader.CustomAPIKey == "" { - return fmt.Errorf("trader[%d]: 使用自定义API时必须配置custom_api_key", i) - } - if trader.CustomModelName == "" { - return fmt.Errorf("trader[%d]: 使用自定义API时必须配置custom_model_name", 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端口 - } - - // 设置杠杆默认值(适配币安子账户限制,最大5倍) - if c.Leverage.BTCETHLeverage <= 0 { - c.Leverage.BTCETHLeverage = 5 // 默认5倍(安全值,适配子账户) - } - if c.Leverage.BTCETHLeverage > 5 { - fmt.Printf("⚠️ 警告: BTC/ETH杠杆设置为%dx,如果使用子账户可能会失败(子账户限制≤5x)\n", c.Leverage.BTCETHLeverage) - } - if c.Leverage.AltcoinLeverage <= 0 { - c.Leverage.AltcoinLeverage = 5 // 默认5倍(安全值,适配子账户) - } - if c.Leverage.AltcoinLeverage > 5 { - fmt.Printf("⚠️ 警告: 山寨币杠杆设置为%dx,如果使用子账户可能会失败(子账户限制≤5x)\n", c.Leverage.AltcoinLeverage) - } - - return nil -} - -// GetScanInterval 获取扫描间隔 -func (tc *TraderConfig) GetScanInterval() time.Duration { - return time.Duration(tc.ScanIntervalMinutes) * time.Minute + return &configFile, nil } diff --git a/main.go b/main.go index c805edb4..cc3f4b09 100644 --- a/main.go +++ b/main.go @@ -19,28 +19,23 @@ import ( "github.com/joho/godotenv" ) -// LeverageConfig 杠杆配置 -type LeverageConfig struct { - BTCETHLeverage int `json:"btc_eth_leverage"` - AltcoinLeverage int `json:"altcoin_leverage"` -} - // ConfigFile 配置文件结构,只包含需要同步到数据库的字段 +// TODO 现在与config.Config相同,未来会被替换, 现在为了兼容性不得不保留当前文件 type ConfigFile struct { - AdminMode bool `json:"admin_mode"` - BetaMode bool `json:"beta_mode"` - APIServerPort int `json:"api_server_port"` - UseDefaultCoins bool `json:"use_default_coins"` - DefaultCoins []string `json:"default_coins"` - CoinPoolAPIURL string `json:"coin_pool_api_url"` - OITopAPIURL string `json:"oi_top_api_url"` - MaxDailyLoss float64 `json:"max_daily_loss"` - MaxDrawdown float64 `json:"max_drawdown"` - StopTradingMinutes int `json:"stop_trading_minutes"` - Leverage LeverageConfig `json:"leverage"` - JWTSecret string `json:"jwt_secret"` - DataKLineTime string `json:"data_k_line_time"` - Log *config.LogConfig `json:"log"` // 日志配置 + AdminMode bool `json:"admin_mode"` + BetaMode bool `json:"beta_mode"` + APIServerPort int `json:"api_server_port"` + UseDefaultCoins bool `json:"use_default_coins"` + DefaultCoins []string `json:"default_coins"` + CoinPoolAPIURL string `json:"coin_pool_api_url"` + OITopAPIURL string `json:"oi_top_api_url"` + MaxDailyLoss float64 `json:"max_daily_loss"` + MaxDrawdown float64 `json:"max_drawdown"` + StopTradingMinutes int `json:"stop_trading_minutes"` + Leverage config.LeverageConfig `json:"leverage"` + JWTSecret string `json:"jwt_secret"` + DataKLineTime string `json:"data_k_line_time"` + Log *config.LogConfig `json:"log"` // 日志配置 } // loadConfigFile 读取并解析config.json文件 @@ -298,6 +293,15 @@ func main() { } } + // 创建初始化上下文 + // TODO : 传入实际配置, 现在并未实际使用,未来所有模块初始化都将通过上下文传递配置 + // ctx := bootstrap.NewContext(&config.Config{}) + + // // 执行所有初始化钩子 + // if err := bootstrap.Run(ctx); err != nil { + // log.Fatalf("初始化失败: %v", err) + // } + fmt.Println() fmt.Println("🤖 AI全权决策模式:") fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高5倍,BTC/ETH最高5倍)\n")