stock-ai-agent/backend/app/crypto_agent/executor/CODE_REVIEW_2026-03-28.md
2026-03-28 22:51:49 +08:00

12 KiB
Raw Blame History

Code Review Report - 超激进配置和账户止损

DATE: 2026-03-28 REVIEWER: Claude Code Agent

📋 审查范围

  1. 超激进仓位配置20%/15%/8%
  2. 账户级止损功能25%止损15%警告)
  3. 统一杠杆配置10x
  4. 移动止损功能
  5. 飞书通知集成

🔴 严重问题 (Critical Issues)

1. 配置不一致A级信号实际只能用10%而非20%

位置: crypto_agent.py:2165, 2143

问题:

# Line 2143: A级信号配置
base_margin_pct = 0.20  # A级: 20%

# Line 2165: 平台最大限制
max_margin_pct = rules.get('max_margin_pct', 0.1)  # 10%

# Line 2175: 实际会被截断
if margin > max_margin:
    margin = max_margin  # $200 → $100

影响:

  • A级信号期望用20%仓位实际被限制到10%
  • B级信号15%也会被截断到10%
  • 文档说明与实际不符

建议修复:

# 方案1: 提高平台最大限制(超激进)
max_margin_pct = rules.get('max_margin_pct', 0.25)  # 改为25%

# 方案2: 调整基础配置与限制对齐
if confidence >= 90:
    base_margin_pct = 0.10  # A级: 10% (与限制对齐)
elif confidence >= 70:
    base_margin_pct = 0.10  # B级: 10%
else:
    base_margin_pct = 0.08  # C级: 8%

优先级: 🔴 - 配置与文档不一致


2. 紧急平仓方法调用可能缺少await

位置: crypto_agent.py:3656-3659

问题:

# 这些方法可能是同步的但在async函数中
if hasattr(platform_service, 'market_close_position'):
    result = platform_service.market_close_position(symbol)  # ❌ 缺少await?
elif hasattr(platform_service, 'close_position'):
    result = platform_service.close_position(symbol)  # ❌ 缺少await?

检查:

  • Bitget market_close_position() - 需要检查是否是async
  • Hyperliquid close_position() - 需要检查是否是async
  • PaperTrading close_position() - 可能是同步方法

风险:

  • 如果是异步方法但没有await会导致协程未执行
  • 紧急平仓失败,无法止损

建议修复:

# 修复方案:检查并正确调用
try:
    if hasattr(platform_service, 'market_close_position'):
        method = platform_service.market_close_position
        if asyncio.iscoroutinefunction(method):
            result = await method(symbol)
        else:
            result = method(symbol)
    elif hasattr(platform_service, 'close_position'):
        method = platform_service.close_position
        if asyncio.iscoroutinefunction(method):
            result = await method(symbol)
        else:
            result = method(symbol)
except Exception as e:
    logger.error(f"调用平仓方法异常: {e}")
    result = {'success': False, 'error': str(e)}

优先级: 🔴 - 可能导致止损失败


3. 初始余额获取逻辑有缺陷

位置: crypto_agent.py:3571

问题:

initial_balance = account_state.get('initial_balance',
                                     account_state.get('current_balance', 0))

场景分析:

场景1: 正常情况
account_state = {'initial_balance': 10000, 'current_balance': 8500}
→ initial_balance = 10000 ✅

场景2: 没有initial_balance字段Bitget实盘
account_state = {'current_balance': 10000, 'available': 9000}
→ initial_balance = 10000 (fallback到current_balance)
→ current_balance = 10000
→ drawdown = 0 ❌ 无法检测回撤!

场景3: 第一次运行(模拟盘)
account_state = {'initial_balance': 10000, 'balance': 10000}
→ initial_balance = 10000
→ current_balance = 0 (字段名不匹配)
→ drawdown计算失败 ❌

影响:

  • Bitget/Hyperliquid实盘可能无法检测回撤
  • 首次运行时initial_balance字段可能不存在
  • 字段名不一致导致获取失败

建议修复:

# 方案1: 使用配置中的初始余额(推荐)
initial_balance = account_state.get('initial_balance',
                    self.settings.paper_trading_initial_balance)  # 从配置读取

# 方案2: 记录并持久化初始余额
if not hasattr(self, '_initial_balances'):
    self._initial_balances = {}

if platform_name not in self._initial_balances:
    # 第一次运行,记录初始余额
    self._initial_balances[platform_name] = current_balance
    # 持久化到文件
    self._save_initial_balances()

initial_balance = self._initial_balances[platform_name]

# 方案3: 统一字段名获取
initial_balance = (
    account_state.get('initial_balance') or
    account_state.get('start_balance') or
    self._get_persisted_initial_balance(platform_name) or
    current_balance  # 最后的fallback
)

优先级: 🔴 - 账户止损可能失效


⚠️ 警告问题 (Warnings)

4. 杠杆限制逻辑冗余

位置: crypto_agent.py:2178-2188

问题:

# 这里已经限制了最大保证金为balance * 10%
max_margin = balance * max_margin_pct  # max_margin_pct = 0.1
if margin > max_margin:
    margin = max_margin

# 下面又根据剩余杠杆再次限制
max_margin_by_leverage = balance * remaining_leverage
if margin > max_margin_by_leverage:
    margin = max_margin_by_leverage

分析:

  • max_margin_pct = 0.1 已经限制了单笔最多10%
  • remaining_leverage = 10 - current 最大10x
  • 两层限制可能导致过度保守

示例:

balance = $1000
current_leverage = 5x
remaining = 5x

Layer 1: max_margin = $1000 × 10% = $100
Layer 2: max_margin_by_leverage = $1000 × 5 = $5000

实际: margin = min($100, $5000) = $100

问题: Layer 2 永远不会生效因为Layer 1已经限制了

建议:

# 方案1: 只保留杠杆限制去掉max_margin_pct
# 因为10x杠杆已经隐含了单笔最多100%的风险

# 方案2: 调整max_margin_pct为更合理的值
max_margin_pct = rules.get('max_margin_pct', 0.25)  # 25%

# 方案3: 明确说明两层限制的目的
# Layer 1: 平台风控限制单笔不超过余额的10%
# Layer 2: 杠杆空间限制总杠杆不超过10x

优先级: ⚠️ - 逻辑冗余但不影响功能


5. 移动止损触发条件可能过于简单

位置: base_executor.py:224-251

问题:

# 规则3: 移动止损
if pnl_pct >= 2:
    current_sl = pos.get('stop_loss')
    if side == 'buy' and current_sl and current_sl < entry_price:
        actions.append({
            'symbol': symbol,
            'action': 'MOVE_SL',
            'new_sl': entry_price,
            'reason': f"盈利 {pnl_pct:.1f}% >= 2%,移动止损到入场价",
            'priority': 3
        })

缺陷:

  1. 没有考虑波动率: 高波动币种2%盈利很常见低波动币种2%可能很罕见
  2. 没有考虑持仓时间: 刚开仓就2% vs 持仓3天2%含义不同
  3. 一次性移动: 从-3%直接移动到0%,跨度较大
  4. 未考虑市场状态: 震荡市vs趋势市应该不同策略

建议增强:

def check_position_management(self, positions, current_prices):
    """持仓管理检查(增强版)"""
    actions = []

    for pos in positions:
        pnl_pct = ...  # 计算盈亏
        hold_hours = ...  # 计算持仓时长
        volatility = self._get_symbol_volatility(symbol)  # 获取波动率

        # 根据波动率调整阈值
        if volatility > 0.05:  # 高波动
            move_sl_threshold = 3.0  # 需要盈利3%才移动
        elif volatility < 0.02:  # 低波动
            move_sl_threshold = 1.5  # 盈利1.5%就移动
        else:
            move_sl_threshold = 2.0  # 标准阈值

        # 分阶段移动止损
        if pnl_pct >= move_sl_threshold * 1.5:
            # 大幅盈利,移动到+1%
            actions.append({
                'action': 'MOVE_SL',
                'new_sl': entry_price * 1.01,
                'reason': f"盈利 {pnl_pct:.1f}% >= {move_sl_threshold*1.5:.1f}%,移动止损到+1%"
            })
        elif pnl_pct >= move_sl_threshold:
            # 达到阈值,移动到保本
            actions.append({
                'action': 'MOVE_SL',
                'new_sl': entry_price,
                'reason': f"盈利 {pnl_pct:.1f}% >= {move_sl_threshold:.1f}%,移动止损到保本"
            })

优先级: ⚠️ - 可以后续优化


6. 飞书通知缺少失败重试

位置: base_executor.py:457-495

问题:

async def send_execution_notification(self, operation, symbol, result, details):
    if not self.feishu:
        return

    try:
        # 直接发送,无重试
        await self._send_open_notification(...)
    except Exception as e:
        logger.error(f"发送执行通知失败: {e}")
        # ❌ 没有重试,通知丢失

影响:

  • 网络抖动导致通知失败
  • 无法知道关键交易执行情况
  • 飞书API限流时通知丢失

建议修复:

async def send_execution_notification(self, operation, symbol, result, details):
    if not self.feishu:
        return

    max_retries = 3
    for attempt in range(max_retries):
        try:
            await self._send_open_notification(...)
            break  # 成功则退出
        except Exception as e:
            if attempt < max_retries - 1:
                await asyncio.sleep(1 * (attempt + 1))  # 指数退避
                logger.warning(f"通知发送失败,重试 {attempt+1}/{max_retries}: {e}")
            else:
                # 最后一次失败,记录到本地
                logger.error(f"通知发送失败(已重试{max_retries}次): {e}")
                self._save_failed_notification(operation, symbol, result, details)

优先级: ⚠️ - 影响监控但不影响交易


优秀设计 (Good Practices)

1. 账户级止损设计合理

优点:

  • 统一的止损检查逻辑(所有平台共用)
  • 分级告警15%警告25%止损)
  • 紧急平仓机制
  • 完整的日志记录

2. 飞书通知设计良好

优点:

  • 统一的通知入口 send_execution_notification()
  • 根据操作类型分发到不同方法
  • 格式化的卡片消息
  • 成功/失败都有通知

3. 移动止损抽象合理

优点:

  • 基类定义抽象方法
  • 各平台独立实现
  • 统一的触发逻辑
  • 优先级排序

📊 测试建议

单元测试

def test_account_stop_loss():
    """测试账户级止损"""
    # 1. 测试回撤15%触发警告
    # 2. 测试回撤25%触发止损
    # 3. 测试紧急平仓
    # 4. 测试initial_balance获取

def test_position_sizing():
    """测试仓位计算"""
    # 1. 测试A级信号20%被限制到10%
    # 2. 测试杠杆限制
    # 3. 测试最小保证金限制
    # 4. 测试可用余额不足

def test_move_stop_loss():
    """测试移动止损"""
    # 1. 测试2%盈利触发
    # 2. 测试各平台实现
    # 3. 测试飞书通知

集成测试

async def test_emergency_close_integration():
    """测试紧急平仓完整流程"""
    # 1. 创建测试仓位
    # 2. 触发25%回撤
    # 3. 验证平仓执行
    # 4. 验证通知发送
    # 5. 验证系统停止

🔧 修复优先级

优先级 问题 影响 工作量
🔴 P0 配置不一致20%→10% 文档与实际不符
🔴 P0 紧急平仓缺少await 止损可能失败
🔴 P0 初始余额获取缺陷 回撤检测失效
⚠️ P1 杠杆限制冗余 逻辑混乱
⚠️ P1 移动止损过于简单 优化空间
⚠️ P2 通知缺少重试 监控遗漏

📝 建议行动计划

第一阶段(立即修复)- P0问题

  1. 修复配置不一致

    # 选择方案1提高max_margin_pct
    # 或方案2调整base_margin_pct与限制对齐
    
  2. 修复紧急平仓await

    # 添加asyncio.iscoroutinefunction检查
    
  3. 修复初始余额获取

    # 使用配置中的初始余额或持久化
    

第二阶段(优化)- P1问题

  1. 简化杠杆限制逻辑
  2. 增强移动止损策略
  3. 添加通知重试机制

第三阶段(测试)

  1. 编写单元测试
  2. 编写集成测试
  3. 模拟盘验证
  4. 小资金实盘测试

📚 相关文档