Beta套利止盈止损算法说明
Beta套利止盈止损算法说明
架构概览
监控线程 (_stop_loss_monitor)
│ 每 3 秒循环一次
│
├─ 1. update_position_prices() ← 从 WS L2缓存/REST API 获取最新价格
├─ 2. calculate_pnl() ← 计算组合 PnL
├─ 3. check_stop_loss() ← 固定止损检查
├─ 4. check_trailing_stop() ← 移动止盈检查
├─ 5. check_max_hold_duration() ← 持仓超时检查
└─ 6. _close_with_retry() ← 触发平仓(带重试)
检查优先级:Kill Switch > 止损 > 移动止盈 > 持仓超时
1. 组合 PnL 计算
核心公式
avg_margin = (alt_size × alt_entry_price / leverage + base_size × base_entry_price / leverage) / 2
ETH多单盈亏 = alt_size × alt_entry_price × (alt_current_price / alt_entry_price - 1)
BTC空单盈亏 = base_size × base_entry_price × (1 - base_current_price / base_entry_price)
combined_pnl = ETH多单盈亏 + BTC空单盈亏
pnl_pct = combined_pnl / avg_margin
计算示例
以如下仓位为例:
| 参数 | ETH (多) | BTC (空) |
|---|---|---|
| size | 0.0701 | 0.00320 |
| entry_price | $2,162.9 | $71,267 |
| current_price | $2,172.9 | $71,213 |
| leverage | 10x | 10x |
计算过程:
ETH 保证金 = 0.0701 × 2162.9 / 10 = $15.16
BTC 保证金 = 0.00320 × 71267 / 10 = $22.81
avg_margin = (15.16 + 22.81) / 2 = $18.98
ETH PnL = 0.0701 × 2162.9 × (2172.9 / 2162.9 - 1) = +$0.70
BTC PnL = 0.00320 × 71267 × (1 - 71213 / 71267) = +$0.17
combined_pnl = 0.70 + 0.17 = +$0.87
pnl_pct = 0.87 / 18.98 = +4.58%
关键说明
avg_margin是两腿保证金的平均值,不是总保证金。因此pnl_pct反映的是相对于单腿保证金的收益率,数值会比 ROE 更高alt_current_price和base_current_price由监控线程每 3 秒通过update_position_prices()从交易所获取最新中间价更新- 价格来源优先级:WS L2Book 缓存 > REST API (
get_all_mids)
2. 固定止损
判定逻辑
触发条件: pnl_pct < -stop_loss_pct
当组合 PnL 百分比低于止损阈值的负值时,立即触发平仓。
参数
| 参数 | 默认值 | 环境变量 |
|---|---|---|
stop_loss_pct |
0.03 (3%) | BETA_ARB_STOP_LOSS_PCT |
示例
配置: stop_loss_pct = 0.03
当前: pnl_pct = -3.1%
判定: -3.1% < -3.0% → 触发止损
平仓方式
止损触发时使用市价单 (force_market=True),确保快速成交。
3. 移动止盈 (Trailing Stop)
判定逻辑
移动止盈分两个阶段:
阶段1 - 激活:当 pnl_pct 首次达到 trailing_activation_pct 时,开始追踪峰值
阶段2 - 触发:当 pnl_pct 从峰值回落超过 trailing_callback_pct 时,触发平仓
触发条件: peak_pnl_pct >= trailing_activation_pct
AND pnl_pct < peak_pnl_pct - trailing_callback_pct
峰值追踪
每次监控循环都会调用 update_peak_pnl(current_pnl_pct),线程安全地更新历史最高 PnL:
if current_pnl_pct > peak_pnl_pct:
peak_pnl_pct = current_pnl_pct
peak_pnl_pct 只增不减,记录仓位生命周期内的最高收益。
参数
| 参数 | 默认值 | 环境变量 |
|---|---|---|
trailing_activation_pct |
0.05 (5%) | BETA_ARB_TRAILING_ACTIVATION_PCT |
trailing_callback_pct |
0.005 (0.5%) | BETA_ARB_TRAILING_CALLBACK_PCT |
示例
配置: activation=5%, callback=0.5%
时间线:
T1: pnl_pct=3.0% → peak=3.0%, 未激活 (3% < 5%)
T2: pnl_pct=5.2% → peak=5.2%, 已激活 ✓
T3: pnl_pct=6.8% → peak=6.8%, 未触发 (6.8% > 6.8%-0.5%=6.3%)
T4: pnl_pct=7.1% → peak=7.1%, 未触发
T5: pnl_pct=6.9% → peak=7.1%, 未触发 (6.9% > 7.1%-0.5%=6.6%)
T6: pnl_pct=6.5% → peak=7.1%, 触发 ✓ (6.5% < 6.6%)
收益
7.1% ──────────●─ peak
\
6.6% ─ ─ ─ ─ ─ ┼─ 触发线 (peak - 0.5%)
\
6.5% ────────────●─ 触发平仓
T6
5.0% ─ ─ ─ ─ ─ ─ ─ 激活线
平仓方式
移动止盈触发时使用限价单(非 force_market),因为此时仍在盈利区间,不需要紧急市价成交。
4. 持仓超时
判定逻辑
触发条件: (当前时间 - 开仓时间) >= max_hold_hours
参数
| 参数 | 默认值 | 环境变量 |
|---|---|---|
max_hold_hours |
72.0 (3天) | BETA_ARB_MAX_HOLD_HOURS |
说明
无论盈亏,超时即平仓。避免仓位长期暴露在市场风险中。
5. 监控线程运行机制
执行周期
| 参数 | 默认值 | 环境变量 |
|---|---|---|
stop_loss_check_interval |
3 秒 | TRADING_STOP_LOSS_CHECK_INTERVAL |
每轮执行流程
1. 检查 Kill Switch → 若激活,立即平掉所有仓位
2. 调用 update_position_prices()
→ executor.get_all_mids() 获取 ETH/BTC 最新中间价
→ 更新 pos.alt_current_price, pos.base_current_price
3. 遍历所有 OPEN 状态仓位:
a. calculate_pnl(pos) → 得到 combined_pnl, pnl_pct
b. check_stop_loss(pos) → 亏损超阈值?
c. check_trailing_stop(pos) → 盈利回撤超阈值?
d. check_max_hold_duration(pos) → 持仓超时?
4. 若触发任一条件 → _close_with_retry(pos, reason)
价格更新来源
get_all_mids()
├─ 优先: WS L2Book 缓存 (_get_all_mids_from_ws)
│ └─ 从 EnhancedWebSocketManager.latest_data 读取
│ mid = (best_bid + best_ask) / 2
│ 延迟: <10ms
│
└─ 降级: REST API (_get_all_mids_from_api)
└─ 调用 info.all_mids()
延迟: 50-200ms
6. 平仓重试机制
参数
| 参数 | 默认值 | 环境变量 |
|---|---|---|
stop_loss_max_retries |
3 | TRADING_STOP_LOSS_MAX_RETRIES |
stop_loss_retry_backoff |
1.0 秒 | TRADING_STOP_LOSS_RETRY_BACKOFF |
重试策略
第1次: 立即执行
第2次: 等待 1.0s (backoff × 2^0)
第3次: 等待 2.0s (backoff × 2^1)
全部失败: 发送飞书告警
下单方式
| 触发原因 | 下单方式 | 说明 |
|---|---|---|
| 固定止损 | 市价单 | 紧急止损,优先成交速度 |
| 移动止盈 | 限价单 | 仍在盈利,可等待更优价格 |
| 持仓超时 | 限价单 | 非紧急,正常平仓 |
| Kill Switch | 限价单 | 批量平仓 |
7. 已知修复记录
avg_margin 冷启动恢复 (2026-03-24)
问题: avg_margin 未持久化到数据库,服务重启后从 DB 恢复仓位时 avg_margin=0.0,导致 calculate_pnl() 直接返回 (0.0, 0.0),止盈止损完全失效。
修复: DB 恢复时用入场价和杠杆重新计算 avg_margin:
avg_margin = (alt_size * alt_entry_price / leverage
+ base_size * base_entry_price / leverage) / 2
涉及文件: src/trading/position_manager.py — _build_position_from_row()
8. 完整参数汇总
| 参数 | 默认值 | 说明 |
|---|---|---|
stop_loss_pct |
3% | 组合 PnL 低于 -3% 触发止损 |
trailing_activation_pct |
5% | 组合 PnL 达到 5% 后激活移动止盈 |
trailing_callback_pct |
0.5% | 从峰值回落 0.5% 触发止盈平仓 |
max_hold_hours |
72h | 持仓超过 72 小时强制平仓 |
stop_loss_check_interval |
3s | 监控线程检查间隔 |
stop_loss_max_retries |
3 | 平仓失败最大重试次数 |
stop_loss_retry_backoff |
1.0s | 重试退避基数 |
leverage |
10x | 杠杆倍数(影响 avg_margin 计算) |