Skip to content

Conversation

@xqliu
Copy link
Contributor

@xqliu xqliu commented Nov 5, 2025

Closes #574

Summary / 概述

Automatically restarts traders that were running before service restart in admin mode. This improves local development experience while keeping cloud deployments safe.

在 admin 模式下,自动重启服务重启前正在运行的交易员。这改善了本地开发体验,同时保持云端部署的安全性。

Changes / 修改内容

1. Added StartRunningTraders method / 新增 StartRunningTraders 方法

Location: manager/trader_manager.go:444-490

  • Queries database for all traders with is_running=true
  • Only starts traders that are loaded in memory
  • Logs progress and skips missing traders
  • 查询数据库中所有 is_running=true 的交易员
  • 只启动已加载到内存的交易员
  • 记录进度并跳过缺失的交易员

2. Auto-start in admin mode / Admin 模式下自动启动

Location: main.go:313-318

  • Calls StartRunningTraders() when admin_mode=true
  • No auto-start when admin_mode=false (cloud deployments)
  • admin_mode=true 时调用 StartRunningTraders()
  • admin_mode=false 时不自动启动(云端部署)

Testing / 测试验证

Test Case 1: Trader with is_running=0 / 测试场景 1:is_running=0

# Stop trader
curl -X POST http://localhost:8080/api/traders/{id}/stop

# Verify database
sqlite3 config.db "SELECT is_running FROM traders"
# Output: 0

# Restart service
docker compose restart nofx

# Check logs
docker compose logs nofx | grep "自动启动"
# Output: 📋 没有需要自动启动的交易员

Result: No auto-start (expected behavior)

Test Case 2: Trader with is_running=1 / 测试场景 2:is_running=1

# Start trader
curl -X POST http://localhost:8080/api/traders/{id}/start

# Verify database
sqlite3 config.db "SELECT is_running FROM traders"
# Output: 1

# Restart service
docker compose restart nofx

# Check logs
docker compose logs nofx | grep "自动启动"
# Output: 🚀 自动启动 1 个标记为运行状态的交易员...
#         ▶️  启动 招财猫...

Result: Auto-started successfully (expected behavior)

Benefits / 优点

For Local Development / 本地开发

  • ✅ Traders automatically resume after container restart
  • ✅ No need to manually restart via UI/API
  • ✅ Maintains trader state across restarts
  • ✅ 交易员在容器重启后自动恢复
  • ✅ 无需通过 UI/API 手动重启
  • ✅ 保持交易员状态跨重启

For Cloud Deployment / 云端部署

  • ✅ Safe: No auto-start when admin_mode=false
  • ✅ Users must explicitly start traders via API
  • ✅ Better control over resource usage
  • ✅ 安全:admin_mode=false 时不自动启动
  • ✅ 用户必须通过 API 显式启动交易员
  • ✅ 更好地控制资源使用

⚠️ Reviewer Notes / 审查注意事项

Potential Concerns for Multi-User Scenarios / 多用户场景的潜在影响

Please consider the following when reviewing:

  1. API Rate Limiting / API 限流影响

    • All traders with is_running=true start simultaneously on service restart
    • This could cause burst requests to third-party APIs (exchanges, AI models, market data)
    • Consider: Should we add staggered startup delays?
    • 所有 is_running=true 的交易员在服务重启时同时启动
    • 可能对第三方 API(交易所、AI 模型、行情数据)造成瞬时请求冲击
    • 考虑:是否需要添加错峰启动延迟?
  2. System Resource Usage / 系统资源使用

    • Multiple traders starting concurrently could spike CPU/memory usage
    • Current implementation: All traders start via goroutines (non-blocking)
    • Consider: Should we limit concurrent starts or add resource monitoring?
    • 多个交易员并发启动可能导致 CPU/内存使用峰值
    • 当前实现:所有交易员通过 goroutine 启动(非阻塞)
    • 考虑:是否需要限制并发启动数量或添加资源监控?
  3. Database Load / 数据库负载

    • Current: Queries all users and traders at startup
    • Impact: Should be minimal for typical deployments
    • Consider: Is the current query pattern efficient for large user bases?
    • 当前:启动时查询所有用户和交易员
    • 影响:对典型部署应该影响较小
    • 考虑:对于大量用户场景,当前查询模式是否高效?

Current Mitigations / 当前的缓解措施:

  • ✅ Only active in admin_mode=true (typically single-user local dev)
  • ✅ Uses goroutines to avoid blocking startup
  • ✅ Logs clearly show startup progress
  • ✅ 仅在 admin_mode=true 时启用(通常是单用户本地开发)
  • ✅ 使用 goroutine 避免阻塞启动流程
  • ✅ 日志清晰显示启动进度

Suggested Future Improvements / 未来改进建议:

  • Add configurable startup delay between traders (e.g., 100-500ms)
  • Implement batch startup with size limits (e.g., 5 traders at a time)
  • Add startup timeout and failure recovery
  • Monitor and log API rate limit responses
  • 添加可配置的交易员启动间隔(如 100-500ms)
  • 实现批量启动,限制批次大小(如每次 5 个交易员)
  • 添加启动超时和失败恢复机制
  • 监控和记录 API 限流响应

Configuration / 配置

Set in config.json:

{
  "admin_mode": true   // Enable auto-start (local dev)
}

or

{
  "admin_mode": false  // Disable auto-start (cloud)
}

Related / 相关信息

  • Resolves TODO at main.go:313
  • Compatible with existing trader management APIs
  • No breaking changes
  • 解决了 main.go:313 处的 TODO
  • 兼容现有的交易员管理 API
  • 无破坏性改动

## Changes / 修改内容

### 1. Added StartRunningTraders method / 新增 StartRunningTraders 方法
- Only starts traders with `is_running=true` in database
- Queries all users and their traders to find running ones
- Logs auto-start progress and skips traders not loaded in memory
- 只启动数据库中 `is_running=true` 的交易员
- 查询所有用户及其交易员,找到运行中的
- 记录自动启动进度,跳过未加载到内存的交易员

### 2. Auto-start in admin mode / Admin 模式下自动启动
- In admin mode, automatically restarts traders that were running before
- Preserves user's trader state across service restarts
- No auto-start when admin_mode=false (cloud deployments)
- Admin 模式下自动重启之前运行的交易员
- 保持用户的交易员状态跨服务重启
- admin_mode=false 时不自动启动(云端部署)

## Testing / 测试

- ✅ Verified traders with `is_running=0` don't auto-start
- ✅ Verified traders with `is_running=1` auto-start on restart
- ✅ Confirmed correct behavior in admin mode
- ✅ 验证了 `is_running=0` 的交易员不会自动启动
- ✅ 验证了 `is_running=1` 的交易员重启后自动启动
- ✅ 确认了 admin 模式下的正确行为

## Use Case / 使用场景

Local development with admin_mode=true benefits from automatic trader recovery after restarts, while cloud deployments with admin_mode=false require manual start via API.

本地开发环境(admin_mode=true)在重启后自动恢复交易员,云端部署(admin_mode=false)需要通过 API 手动启动。
@github-actions
Copy link

github-actions bot commented Nov 5, 2025

🤖 Advisory Check Results

These are advisory checks to help improve code quality. They won't block your PR from being merged.

📋 PR Information

Title Format: ✅ Good - Follows Conventional Commits
PR Size: 🟢 Small (56 lines: +54 -2)

🔧 Backend Checks

Go Formatting: ⚠️ Needs formatting

Files needing formatting
api/server.go
decision/engine.go
logger/telegram_sender.go
mcp/client.go
trader/aster_trader.go
trader/auto_trader.go
trader/binance_futures.go
trader/hyperliquid_trader.go

Go Vet: ✅ Good
Tests: ✅ Passed

Fix locally:

go fmt ./...      # Format code
go vet ./...      # Check for issues
go test ./...     # Run tests

⚛️ Frontend Checks

Build & Type Check: ✅ Success

Fix locally:

cd web
npm run build  # Test build (includes type checking)

📖 Resources

Questions? Feel free to ask in the comments! 🙏


These checks are advisory and won't block your PR from being merged. This comment is automatically generated from pr-checks-run.yml.

@xqliu xqliu closed this Nov 8, 2025
@xqliu
Copy link
Contributor Author

xqliu commented Nov 8, 2025

代码审查报告 - PR #573

📋 基本信息


1️⃣ 业务逻辑审查

✅ 问题定义

核心需求: 本地开发时,服务重启后需要手动重启trader(体验差)

问题场景:

本地开发流程(修复前):
1. 用户启动trader进行测试
2. 修改代码 → docker compose restart
3. 服务重启 → 所有trader停止
4. 用户需要手动点击UI重新启动trader ❌(繁琐)

影响:
- 开发效率低(每次重启都要手动操作)
- 容易忘记重启trader(测试不完整)
- 本地调试体验差

云端部署考虑:

云端场景(需要慎重):
- 多用户共享系统
- 服务重启时所有trader同时启动 → API限流风险
- 用户应该显式控制trader启动(资源管理)

解决方案: 仅在admin_mode=true时自动启动(本地开发模式)

✅ 解决方案验证

设计策略: 仅在admin模式自动启动,云端模式不启动

// main.go:337
if adminMode {  // ✅ 仅admin模式启用
    if err := traderManager.StartRunningTraders(database); err != nil {
        log.Printf("⚠️  自动启动交易员失败: %v", err)
    }
}

启动逻辑:

1. 查询数据库: 获取所有is_running=true的trader
2. 过滤已加载: 只启动已在内存中的trader
3. 并发启动: 使用goroutine非阻塞启动
4. 日志记录: 清晰显示启动进度

业务逻辑正确性: ✅ 完全满足需求

  • 本地开发: 自动恢复trader状态(提升体验)
  • 云端部署: admin_mode=false,不自动启动(安全)
  • 状态一致: 依据数据库is_running字段(数据源可靠)

2️⃣ 技术实现审查

✅ 核心实现: StartRunningTraders方法

// manager/trader_manager.go:444
func (tm *TraderManager) StartRunningTraders(database *config.Database) error {
    tm.mu.RLock()
    defer tm.mu.RUnlock()

    // 1. 获取所有用户
    userIDs, err := database.GetAllUsers()
    if err != nil {
        return fmt.Errorf("获取用户列表失败: %w", err)
    }

    // 2. 收集所有应该启动的交易员
    var runningTraders []*config.TraderRecord
    for _, userID := range userIDs {
        traders, err := database.GetTraders(userID)
        if err != nil {
            log.Printf("⚠️ 获取用户 %s 的交易员失败: %v", userID, err)
            continue  // ✅ 部分失败不影响其他用户
        }
        for _, trader := range traders {
            if trader.IsRunning {  // ✅ 根据数据库状态
                runningTraders = append(runningTraders, trader)
            }
        }
    }

    // 3. 无需启动时提前返回
    if len(runningTraders) == 0 {
        log.Println("📋 没有需要自动启动的交易员")
        return nil
    }

    // 4. 并发启动
    log.Printf("🚀 自动启动 %d 个标记为运行状态的交易员...", len(runningTraders))
    for _, traderCfg := range runningTraders {
        if t, exists := tm.traders[traderCfg.ID]; exists {
            go func(at *trader.AutoTrader, name string) {  // ✅ goroutine非阻塞
                log.Printf("▶️  启动 %s...", name)
                if err := at.Run(); err != nil {
                    log.Printf("❌ %s 运行错误: %v", name, err)
                }
            }(t, traderCfg.Name)
        } else {
            // ✅ 数据库有记录但内存中未加载(可能是配置错误)
            log.Printf("⚠️  交易员 %s (ID: %s) 未加载到内存,跳过", traderCfg.Name, traderCfg.ID)
        }
    }

    return nil
}

技术亮点:

  1. 线程安全: 使用tm.mu.RLock()保护读操作
  2. 错误容忍: 单个用户失败不影响其他用户(continue
  3. 非阻塞: 使用goroutine启动,不阻塞主流程
  4. 日志完善: 启动进度、成功、失败都有明确日志
  5. 防御性编程: 检查trader是否在内存中(避免nil panic)

✅ 主程序集成

// main.go:337
// Admin模式下自动启动标记为运行状态的交易员
if adminMode {
    if err := traderManager.StartRunningTraders(database); err != nil {
        log.Printf("⚠️  自动启动交易员失败: %v", err)
    }
}

技术评价: ✅ 正确

  • 条件判断: 仅admin模式启用
  • 错误处理: 失败不影响服务启动(记录日志)
  • 位置合理: 在信号等待之前(确保完成初始化)

3️⃣ 端到端验证

✅ 场景1: Admin模式 + Trader已停止

# 前提条件
config.json: {"admin_mode": true}
数据库: traders.is_running = 0

# 操作
docker compose restart nofx

# 预期结果
日志: "📋 没有需要自动启动的交易员"
Trader状态: 停止 ✅

# 验证
✅ 不会自动启动(符合预期)

✅ 场景2: Admin模式 + Trader运行中

# 前提条件
config.json: {"admin_mode": true}
操作: 用户通过UI启动trader "招财猫"
数据库: traders.is_running = 1

# 操作
docker compose restart nofx

# 预期结果
日志:
"🚀 自动启动 1 个标记为运行状态的交易员..."
"▶️  启动 招财猫..."

Trader状态: 运行中 ✅

# 验证
curl http://localhost:8080/api/my-traders
→ {"id": "xxx", "name": "招财猫", "is_running": true}

✅ 自动启动成功

✅ 场景3: 非Admin模式(云端)

# 前提条件
config.json: {"admin_mode": false}
数据库: traders.is_running = 1

# 操作
docker compose restart nofx

# 预期结果
日志: 无自动启动相关日志
Trader状态: 停止 ✅

# 验证
✅ 不会自动启动(安全)
用户需要通过API显式启动: POST /api/traders/{id}/start

✅ 场景4: 多用户场景

# 前提条件
用户A: 2个trader (is_running=1)
用户B: 1个trader (is_running=1)
用户C: 1个trader (is_running=0)

# 操作
docker compose restart nofx

# 预期结果
日志:
"🚀 自动启动 3 个标记为运行状态的交易员..."
"▶️  启动 用户A-Trader1..."
"▶️  启动 用户A-Trader2..."
"▶️  启动 用户B-Trader1..."

用户C-Trader: 不启动 ✅

# 验证
✅ 只启动is_running=1的trader
✅ 跨用户正确处理

✅ 场景5: Trader配置错误(未加载到内存)

# 前提条件
数据库: trader "错误配置" (is_running=1)
内存: trader未加载(模型ID错误)

# 操作
docker compose restart nofx

# 预期结果
日志:
"⚠️  交易员 错误配置 (ID: xxx) 未加载到内存,跳过"

# 验证
✅ 不会panic
✅ 其他trader正常启动
✅ 日志明确提示问题

4️⃣ 安全与性能审查

✅ 安全性

1. 模式隔离

Admin模式(本地开发):
- 自动启动 ✅
- 单用户场景
- 可接受的资源冲击

非Admin模式(云端):
- 不自动启动 ✅
- 多用户场景
- 用户显式控制

2. 数据库安全

// 只读操作,不修改数据库
traders, err := database.GetTraders(userID)  // ✅ SELECT查询
if trader.IsRunning {  // ✅ 只读判断
    runningTraders = append(runningTraders, trader)
}

评价: ✅ 无安全风险

⚠️ 性能考虑(PR作者已标注)

潜在问题1: API限流风险

场景: 10个trader同时启动
每个trader立即:
- 调用交易所API获取余额
- 调用交易所API获取持仓
- 调用AI API做决策

风险: 瞬时API请求量 = 10 * 3 = 30个请求

缓解措施(PR作者建议):
1. 添加启动延迟(100-500ms)
2. 批量启动(每次5个)
3. 监控API限流响应

潜在问题2: 系统资源峰值

场景: 20个trader同时启动
每个trader创建:
- 1个goroutine(主循环)
- 数据库连接
- HTTP客户端

风险: CPU/内存瞬时峰值

当前实现:
- ✅ 使用goroutine(非阻塞)
- ✅ 不影响服务启动
- ⚠️ 无启动速率限制

潜在问题3: 数据库查询

// 当前实现: 查询所有用户和trader
userIDs, err := database.GetAllUsers()
for _, userID := range userIDs {
    traders, err := database.GetTraders(userID)
    // ...
}

复杂度: O(用户数 * 平均trader数)

优化方案(可选):
// 单条SQL查询所有is_running=1的trader
SELECT * FROM traders WHERE is_running = 1

复杂度: O(1)

当前缓解措施: ✅ 符合预期

  • Admin模式通常是单用户(本地开发)
  • 即使多用户,trader数量通常<100
  • 启动是一次性操作(不是高频)

5️⃣ 代码质量

✅ 优点

  1. 需求明确: 解决实际痛点(本地开发体验)
  2. 设计合理: 模式隔离(admin vs 非admin)
  3. 错误处理: 完善(部分失败不影响整体)
  4. 日志清晰: 每个步骤都有日志
  5. 文档完善: PR描述包含测试用例和reviewer notes

⚠️ 建议改进

1. 优化数据库查询(可选)

// manager/trader_manager.go:444(建议优化)
func (tm *TraderManager) StartRunningTraders(database *config.Database) error {
    tm.mu.RLock()
    defer tm.mu.RUnlock()

    // ✅ 优化:单条SQL查询
    runningTraders, err := database.GetRunningTraders()  // 新增方法
    if err != nil {
        return fmt.Errorf("获取运行中的交易员失败: %w", err)
    }

    // ... 后续逻辑不变
}

// config/database.go(建议新增)
func (d *Database) GetRunningTraders() ([]*TraderRecord, error) {
    var traders []*TraderRecord
    err := d.db.Where("is_running = ?", true).Find(&traders).Error
    return traders, err
}

好处: 减少数据库查询次数(N+1 → 1)

2. 添加启动延迟(建议)

// manager/trader_manager.go:477(建议增强)
for i, traderCfg := range runningTraders {
    if t, exists := tm.traders[traderCfg.ID]; exists {
        // ✅ 添加启动延迟(避免API限流)
        delay := time.Duration(i*200) * time.Millisecond  // 每个trader延迟200ms
        
        go func(at *trader.AutoTrader, name string, d time.Duration) {
            time.Sleep(d)  // 错峰启动
            log.Printf("▶️  启动 %s...", name)
            if err := at.Run(); err != nil {
                log.Printf("❌ %s 运行错误: %v", name, err)
            }
        }(t, traderCfg.Name, delay)
    }
}

好处: 避免API限流风险

3. 添加启动超时(建议)

// manager/trader_manager.go:444(建议增强)
func (tm *TraderManager) StartRunningTraders(database *config.Database) error {
    // ...

    log.Printf("🚀 自动启动 %d 个标记为运行状态的交易员...", len(runningTraders))
    
    var wg sync.WaitGroup
    startCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    for _, traderCfg := range runningTraders {
        if t, exists := tm.traders[traderCfg.ID]; exists {
            wg.Add(1)
            go func(at *trader.AutoTrader, name string) {
                defer wg.Done()
                
                select {
                case <-startCtx.Done():
                    log.Printf("⏱️  %s 启动超时", name)
                    return
                default:
                    log.Printf("▶️  启动 %s...", name)
                    if err := at.Run(); err != nil {
                        log.Printf("❌ %s 运行错误: %v", name, err)
                    }
                }
            }(t, traderCfg.Name)
        }
    }
    
    // 等待所有trader启动完成(或超时)
    done := make(chan struct{})
    go func() {
        wg.Wait()
        close(done)
    }()
    
    select {
    case <-done:
        log.Println("✅ 所有交易员启动完成")
    case <-startCtx.Done():
        log.Println("⚠️  部分交易员启动超时")
    }
    
    return nil
}

好处: 避免启动卡死

4. 添加配置选项(可选)

// config.json(建议新增)
{
  "admin_mode": true,
  "auto_start_traders": true,        // ✅ 独立控制自动启动
  "auto_start_delay_ms": 200,        // ✅ 启动延迟(毫秒)
  "auto_start_batch_size": 5,        // ✅ 批量大小
  "auto_start_timeout_seconds": 30   // ✅ 启动超时
}

📊 总结

✅ 审查结果: 通过(建议优化启动策略)

维度 评分 说明
业务价值 ⭐⭐⭐⭐⭐ 显著提升本地开发体验
技术实现 ⭐⭐⭐⭐ 逻辑正确,建议添加启动延迟
安全性 ⭐⭐⭐⭐⭐ 模式隔离正确,无安全风险
性能 ⭐⭐⭐ Admin模式下可接受,建议优化多trader场景

🎯 核心价值

  1. 开发体验: 本地开发时trader自动恢复(无需手动重启)
  2. 状态一致: 依据数据库is_running字段(可靠)
  3. 云端安全: 非admin模式不启动(多用户安全)

🔧 行动建议

必须修复: 无
强烈建议:

  1. 添加启动延迟(200-500ms)避免API限流
  2. 优化数据库查询(单条SQL而非N+1)

可选优化:

  1. 添加启动超时机制(30秒)
  2. 添加配置选项控制启动策略
  3. 添加Prometheus指标监控启动成功率
  4. 批量启动(每次5个trader)

🌟 特别表扬

问题定位: 准确识别本地开发痛点
设计合理: 模式隔离保证云端安全
文档完善: PR描述包含测试用例和潜在风险分析(reviewer notes优秀)

PR作者自我审查:

  • ✅ 明确指出3个潜在问题(API限流、资源峰值、数据库负载)
  • ✅ 提供当前缓解措施说明
  • ✅ 列出未来改进建议

这是一个非常专业的PR描述,大幅降低了reviewer的工作量!


总评: 这是一个高质量的功能增强,显著提升本地开发体验,同时保持云端部署的安全性。当前实现适合admin模式(单用户),建议添加启动延迟避免API限流风险。PR作者的自我审查非常专业,值得学习。


审查时间: 2025-11-08
审查者: Claude AI Code Reviewer

@xqliu xqliu deleted the feat/auto-start-traders-admin-mode branch November 14, 2025 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Admin 模式下自动启动交易员

1 participant