反向Beta套利策略设计方案
反向Beta套利策略设计方案
做空ETH + 做多BTC,与现有策略(做多ETH + 做空BTC)互为镜像
1. 策略概述
1.1 核心思想
现有策略捕捉的是 ETH下跌弹性收敛(负收益beta EMA下降 → 做多ETH等待弹性恢复)。
反向策略捕捉的是 ETH上涨弹性收敛(正收益beta EMA下降 → 做空ETH,因上涨放大效应减弱意味着ETH相对BTC走弱)。
1.2 策略对比
| 维度 | 现有策略(做多) | 反向策略(做空) |
|---|---|---|
| 方向 | Long ETH + Short BTC | Short ETH + Long BTC |
| 主要beta | 负收益beta(ETH下跌时的放大倍数) | 正收益beta(ETH上涨时的放大倍数) |
| 条件1 | 负收益EMA < 负收益Mean | 正收益EMA < 正收益Mean |
| 条件2 | 负收益EMA < 正收益EMA | 正收益EMA < 负收益EMA |
| 经济含义 | 下跌放大效应减弱 → 做多 | 上涨放大效应减弱 → 做空 |
| BTC仓位倍数 | 负收益beta Mean | 正收益beta Mean |
| direction | "long" |
"short" |
2. 信号条件详解
2.1 开仓条件
取最近10根 正收益率 ETH K线(eth_return > 0)对应的beta值:
beta = eth_return / btc_return (同涨:ETH涨 & BTC涨)
beta = 3.0 (背离:ETH涨 & BTC跌,惩罚性封顶)
跳过:BTC波动 < min_btc_return_abs (分母不可靠)
条件1:正收益beta序列的 EMA(span=5) < 正收益beta序列的 Mean
- 含义:近期正收益beta呈下降趋势,ETH上涨放大效应正在减弱
条件2:正收益beta EMA < 负收益beta EMA
- 含义:上涨放大效应 < 下跌放大效应,ETH相对BTC的正向弹性弱于负向弹性
- 注:若负收益K线不足10根,此条件默认通过(与现有策略的容错逻辑对称)
2.2 仓位比例
ETH仓位(做空)= eth_position_usd(固定,如1000 USD)
BTC仓位(做多)= positive_mean_beta × eth_position_usd
positive_mean_beta 为正收益beta序列的算术均值,代表ETH上涨时相对BTC的平均放大倍数,用作BTC的对冲倍数。
2.3 平仓逻辑
完全复用现有风控框架,无需新增逻辑:
| 平仓类型 | 条件 | 说明 |
|---|---|---|
| 止损 | PnL < -3% | 与现有相同 |
| 移动止盈 | 峰值PnL ≥ 5% 后回撤 0.5% | 与现有相同 |
| 持仓超时 | 持仓 > 72h | 与现有相同 |
| 信号消退 | PnL > 2% 且反向开仓条件不满足 | 使用反向条件复查 |
信号消退的反向条件复查:
- 复查条件1: 正收益EMA 是否仍 < 正收益Mean
- 复查条件2: 正收益EMA 是否仍 < 负收益EMA
- 任一不满足 + PnL > 阈值 → 触发平仓
3. 代码改动设计
3.1 涉及文件
| 文件 | 改动类型 | 改动量 |
|---|---|---|
src/trading/strategy.py |
新增方法 | ~180行 |
src/trading/risk_manager.py |
修改pre_trade_check | ~10行 |
src/trading/position_manager.py |
内部key扩展 | ~30行 |
src/trading/orchestrator.py |
新增反向流程 | ~150行 |
3.2 strategy.py 改动
在 BetaArbitrageStrategy 类中新增:
新增状态字段
self._has_reverse_position: bool = False
self._reverse_cooldown_until: datetime | None = None
新增方法
| 方法 | 用途 |
|---|---|
check_reverse_signal() -> EntrySignal | None |
检查反向开仓信号 |
check_reverse_entry_conditions_met() -> (bool, str) |
反向信号消退复查 |
get_reverse_signal_snapshot() -> dict | None |
反向信号日志快照 |
on_reverse_position_opened() |
反向持仓状态更新 |
on_reverse_position_closed() |
反向平仓状态更新 |
has_reverse_position (property) |
反向持仓查询 |
check_reverse_signal 核心逻辑
def check_reverse_signal(self) -> EntrySignal | None:
# 1. 冷却/持仓检查
if self._has_reverse_position: return None
# 2. 计算正收益beta序列 (复用 _calculate_positive_beta_series)
positive_betas = self._calculate_positive_beta_series()
if positive_betas is None: return None
positive_ema = EMA(positive_betas, span=5)
positive_mean = mean(positive_betas)
# 3. 条件1: 正收益EMA < 正收益Mean
cond1 = positive_ema < positive_mean
# 4. 条件2: 正收益EMA < 负收益EMA
negative_betas = self._calculate_beta_series()
if negative_betas is not None:
negative_ema = EMA(negative_betas, span=5)
cond2 = positive_ema < negative_ema
else:
cond2 = True # 负收益不足时默认通过
# 5. 信号触发
if cond1 and cond2:
return EntrySignal(
ema_beta=positive_ema, # 用于记录
mean_beta=positive_mean, # 用于BTC仓位计算
eth_price=..., btc_price=...
)
设计要点:
- 复用
_calculate_positive_beta_series()和_calculate_beta_series(),不新增beta计算逻辑 EntrySignal.mean_beta放入positive_mean,这样calculate_position_size()中btc_notional = signal.beta_mean * eth_position_usd直接生效
3.3 risk_manager.py 改动
当前 pre_trade_check() 的重复仓位检查:
# 现有逻辑:有任何仓位就拒绝
if open_positions:
return False, "已有活跃仓位"
改为 方向感知:
# 新逻辑:仅同方向仓位才拒绝
for pos in open_positions:
if pos.direction == signal.direction:
return False, f"已有同方向({signal.direction})活跃仓位"
保证金检查:无需改动。available_balance 已扣除现有仓位占用的保证金,自动处理两个仓位并存的情况。
3.4 position_manager.py 改动
问题
当前仓位字典以 PairKey = (symbol, base_symbol) 为key:
self._positions: dict[PairKey, PairPosition] = {}
做多和做空都是 (ETH, BTC),key相同会互相覆盖。
方案:内部key扩展为3元组
# 改前
def _pair_key(pos: PairPosition) -> PairKey:
return (pos.symbol, pos.base_symbol)
# 改后
def _pair_key(pos: PairPosition) -> tuple[str, str, str]:
return (pos.symbol, pos.base_symbol, pos.direction)
影响范围:
_positions,_opening_pairs,_close_cooldown,_ghost_strike_count的key类型has_position(symbol, base_symbol)→ 新增direction参数close_position(symbol, base_symbol, ...)→ 新增direction参数open_position()中的key构造加入signal.directionsync_with_exchange()返回值扩展为3元组_pair_label()兼容2元组和3元组
def _pair_label(key) -> str:
if len(key) >= 3 and key[2]:
dir_str = "做多" if key[2] == "long" else "做空"
return f"{key[0]}|{key[1]}|{dir_str}" if key[1] else f"{key[0]}|{dir_str}"
return f"{key[0]}|{key[1]}" if len(key) >= 2 and key[1] else str(key[0])
3.5 orchestrator.py 改动
启动阶段
# 恢复反向仓位状态
if any(
p.pair_mode == "pair"
and p.symbol == ba.eth_symbol
and p.base_symbol == ba.btc_symbol
and p.direction == "short"
for p in self._position_manager.open_positions
):
self._strategy.on_reverse_position_opened()
K线更新入口
def on_kline_update(self, ...):
self._strategy.update_kline(...)
# 现有策略信号检测
if not self._strategy.has_position:
signal = self._strategy.check_signal()
if signal:
self._try_open_position(signal)
# 反向策略信号检测(独立于现有策略)
if not self._strategy.has_reverse_position:
reverse_signal = self._strategy.check_reverse_signal()
if reverse_signal:
self._try_open_reverse_position(reverse_signal)
新增 _try_open_reverse_position
与 _try_open_position 结构相同,差异:
signal.direction = "short"signal.beta_mean = entry.mean_beta(即 positive_mean,用于BTC仓位计算)- 日志标记为"ETH空 + BTC多"
- 开仓成功后调用
self._strategy.on_reverse_position_opened()
监控线程改动
def _stop_loss_monitor(self):
for pos in self._position_manager.open_positions:
# ... 止损/移动止盈/超时检查(无需修改,已方向无关)
# 信号消退平仓:根据仓位方向选择对应的条件复查
elif pnl_pct > 0 and self._strategy:
if pos.direction == "short":
entry_met, detail = self._strategy.check_reverse_entry_conditions_met()
else:
entry_met, detail = self._strategy.check_entry_conditions_met()
if self._risk_manager.check_signal_exit(pos, entry_met):
close_reason = ...
平仓回调
def _close_with_retry(self, pos, reason):
# ... 平仓执行
if close_result:
# 根据方向调用对应的策略状态更新
if pos.direction == "short":
self._strategy.on_reverse_position_closed()
else:
self._strategy.on_position_closed()
仓位同步
def _position_sync(self):
closed_pairs, adopted_pairs, ... = self._position_manager.sync_with_exchange()
for pair in closed_pairs:
if pair[:2] == beta_pair:
if len(pair) >= 3 and pair[2] == "short":
self._strategy.on_reverse_position_closed()
else:
self._strategy.on_position_closed()
4. 数据流图
WebSocket K线 (1h ETH/BTC)
│
▼
strategy.update_kline() ← 共用同一份K线缓冲区
│
├──▶ check_signal() [现有:做多ETH做空BTC]
│ └── 负收益beta EMA/Mean
│ 条件1: 负EMA < 负Mean
│ 条件2: 负EMA < 正EMA
│ │
│ ▼ EntrySignal (direction=long)
│ _try_open_position()
│ └── signal.beta_mean = negative_mean
│
└──▶ check_reverse_signal() [新增:做空ETH做多BTC]
└── 正收益beta EMA/Mean
条件1: 正EMA < 正Mean
条件2: 正EMA < 负EMA
│
▼ EntrySignal (direction=short)
_try_open_reverse_position()
└── signal.beta_mean = positive_mean
监控线程 (每3秒)
│
├── 止损: PnL < -3% ← 方向无关
├── 移动止盈: 峰值 ≥ 5% 后回撤 0.5% ← 方向无关
├── 超时: 持仓 > 72h ← 方向无关
└── 信号消退: PnL > 2% 且条件不满足 ← 根据 direction 分派
├── direction=long → check_entry_conditions_met()
└── direction=short → check_reverse_entry_conditions_met()
5. 仓位并存规则
- 做多仓位和做空仓位 可以同时存在(不同方向,互不影响)
- 同方向 不允许 重复开仓(做多最多1个,做空最多1个)
- 两个仓位共用同一份K线缓冲区,共用同一个策略实例
- 独立的冷却期、持仓状态、峰值PnL追踪
- 风控保证金检查自动处理(available_balance已扣除现有仓位占用)
6. 不需要改动的模块
| 模块 | 原因 |
|---|---|
executor.py |
已支持 buy/sell 双向,无需改动 |
risk_manager.calculate_pnl() |
基于 alt_side/base_side 计算,方向无关 |
risk_manager.check_stop_loss() |
基于 PnL 百分比,方向无关 |
risk_manager.check_trailing_stop() |
基于 PnL 百分比,方向无关 |
risk_manager.check_signal_exit() |
接收外部传入的 entry_conditions_met |
risk_manager.calculate_position_size() |
使用 signal.beta_mean,通过传参适配 |
trade_repository.py |
通用存储,无方向耦合 |
realtime_kline_service_base.py |
K线推送层,与策略无关 |
config.py (BetaArbConfig) |
两个策略共用相同参数 |
7. 风险与注意事项
7.1 同时开仓场景
理论上两个信号可能在同一根K线同时触发。由于共用 available_balance,第二个开仓的保证金检查会自动考虑第一个已占用的保证金。
7.2 对冲效果
做多+做空同时开仓时,ETH的多空会部分对冲,但BTC也是多空部分对冲。整体收益取决于两组beta值的差异。
7.3 配置共享
两个策略共用相同的 BetaArbConfig 参数(止损、止盈、杠杆、仓位金额等)。如未来需要差异化配置,可扩展 BetaArbConfig 添加 reverse_ 前缀字段。
7.4 日志区分
所有反向策略日志加"反向"前缀,Lark告警标注"ETH空+BTC多"以区分方向。