mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-16 17:12:25 +08:00
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 <zbhan@freewheel.tv>
This commit is contained in:
455
bootstrap/README.md
Normal file
455
bootstrap/README.md
Normal file
@@ -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 项目内部模块,遵循项目整体许可证。
|
||||||
168
bootstrap/bootstrap.go
Normal file
168
bootstrap/bootstrap.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
49
bootstrap/context.go
Normal file
49
bootstrap/context.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
27
bootstrap/hook_builder.go
Normal file
27
bootstrap/hook_builder.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
22
bootstrap/init_hook.go
Normal file
22
bootstrap/init_hook.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
193
config/config.go
193
config/config.go
@@ -3,47 +3,10 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"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 杠杆配置
|
// LeverageConfig 杠杆配置
|
||||||
type LeverageConfig struct {
|
type LeverageConfig struct {
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数(主账户建议5-50,子账户≤5)
|
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数(主账户建议5-50,子账户≤5)
|
||||||
@@ -66,149 +29,41 @@ type TelegramConfig struct {
|
|||||||
|
|
||||||
// Config 总配置
|
// Config 总配置
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Traders []TraderConfig `json:"traders"`
|
AdminMode bool `json:"admin_mode"`
|
||||||
UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表
|
BetaMode bool `json:"beta_mode"`
|
||||||
DefaultCoins []string `json:"default_coins"` // 默认主流币种池
|
|
||||||
APIServerPort int `json:"api_server_port"`
|
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"`
|
MaxDailyLoss float64 `json:"max_daily_loss"`
|
||||||
MaxDrawdown float64 `json:"max_drawdown"`
|
MaxDrawdown float64 `json:"max_drawdown"`
|
||||||
StopTradingMinutes int `json:"stop_trading_minutes"`
|
StopTradingMinutes int `json:"stop_trading_minutes"`
|
||||||
Leverage LeverageConfig `json:"leverage"` // 杠杆配置
|
Leverage LeverageConfig `json:"leverage"`
|
||||||
Log *LogConfig `json:"log"` // 日志配置(可选)
|
JWTSecret string `json:"jwt_secret"`
|
||||||
|
DataKLineTime string `json:"data_k_line_time"`
|
||||||
|
Log *LogConfig `json:"log"` // 日志配置
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig 从文件加载配置
|
// LoadConfig 从文件加载配置
|
||||||
func LoadConfig(filename string) (*Config, error) {
|
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)
|
data, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
return nil, fmt.Errorf("读取%s失败: %w", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var config Config
|
// 解析JSON
|
||||||
if err := json.Unmarshal(data, &config); err != nil {
|
var configFile Config
|
||||||
return nil, fmt.Errorf("解析配置文件失败: %w", err)
|
if err := json.Unmarshal(data, &configFile); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析%s失败: %w", filename, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置默认值:确保使用默认币种列表
|
return &configFile, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
44
main.go
44
main.go
@@ -19,28 +19,23 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LeverageConfig 杠杆配置
|
|
||||||
type LeverageConfig struct {
|
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
|
||||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
|
// ConfigFile 配置文件结构,只包含需要同步到数据库的字段
|
||||||
|
// TODO 现在与config.Config相同,未来会被替换, 现在为了兼容性不得不保留当前文件
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
AdminMode bool `json:"admin_mode"`
|
AdminMode bool `json:"admin_mode"`
|
||||||
BetaMode bool `json:"beta_mode"`
|
BetaMode bool `json:"beta_mode"`
|
||||||
APIServerPort int `json:"api_server_port"`
|
APIServerPort int `json:"api_server_port"`
|
||||||
UseDefaultCoins bool `json:"use_default_coins"`
|
UseDefaultCoins bool `json:"use_default_coins"`
|
||||||
DefaultCoins []string `json:"default_coins"`
|
DefaultCoins []string `json:"default_coins"`
|
||||||
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
||||||
OITopAPIURL string `json:"oi_top_api_url"`
|
OITopAPIURL string `json:"oi_top_api_url"`
|
||||||
MaxDailyLoss float64 `json:"max_daily_loss"`
|
MaxDailyLoss float64 `json:"max_daily_loss"`
|
||||||
MaxDrawdown float64 `json:"max_drawdown"`
|
MaxDrawdown float64 `json:"max_drawdown"`
|
||||||
StopTradingMinutes int `json:"stop_trading_minutes"`
|
StopTradingMinutes int `json:"stop_trading_minutes"`
|
||||||
Leverage LeverageConfig `json:"leverage"`
|
Leverage config.LeverageConfig `json:"leverage"`
|
||||||
JWTSecret string `json:"jwt_secret"`
|
JWTSecret string `json:"jwt_secret"`
|
||||||
DataKLineTime string `json:"data_k_line_time"`
|
DataKLineTime string `json:"data_k_line_time"`
|
||||||
Log *config.LogConfig `json:"log"` // 日志配置
|
Log *config.LogConfig `json:"log"` // 日志配置
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadConfigFile 读取并解析config.json文件
|
// 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()
|
||||||
fmt.Println("🤖 AI全权决策模式:")
|
fmt.Println("🤖 AI全权决策模式:")
|
||||||
fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高5倍,BTC/ETH最高5倍)\n")
|
fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高5倍,BTC/ETH最高5倍)\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user