12 KiB
12 KiB
Code Review Report - 超激进配置和账户止损
DATE: 2026-03-28 REVIEWER: Claude Code Agent
📋 审查范围
- 超激进仓位配置(20%/15%/8%)
- 账户级止损功能(25%止损,15%警告)
- 统一杠杆配置(10x)
- 移动止损功能
- 飞书通知集成
🔴 严重问题 (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
})
缺陷:
- 没有考虑波动率: 高波动币种2%盈利很常见,低波动币种2%可能很罕见
- 没有考虑持仓时间: 刚开仓就2% vs 持仓3天2%含义不同
- 一次性移动: 从-3%直接移动到0%,跨度较大
- 未考虑市场状态: 震荡市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:提高max_margin_pct # 或方案2:调整base_margin_pct与限制对齐 -
修复紧急平仓await
# 添加asyncio.iscoroutinefunction检查 -
修复初始余额获取
# 使用配置中的初始余额或持久化
第二阶段(优化)- P1问题
- 简化杠杆限制逻辑
- 增强移动止损策略
- 添加通知重试机制
第三阶段(测试)
- 编写单元测试
- 编写集成测试
- 模拟盘验证
- 小资金实盘测试