BTC滞后因子设计原理
BTC 滞后因果因子(BTC Lagged Causal Factor)
1. 设计动机
加密货币市场存在一个被广泛观察到的现象:BTC 的剧烈波动会以 10-20 分钟的延迟传导到山寨币。
这种传导效应的成因包括:
- 流动性级联:BTC 剧烈波动触发大量清算 → 做市商全市场撤单/对冲 → 山寨币流动性骤降 → 随后山寨币出现同向剧烈波动
- 套利传导:量化套利策略在 BTC 波动后调整跨品种仓位,价格冲击逐步从 BTC 扩散到山寨币
- 情绪传染:BTC 作为市场风向标,其异常波动触发恐慌/贪婪情绪蔓延
这意味着:如果在 BTC 刚发生暴涨暴跌后,对山寨币开仓(特别是配对交易的单腿),极有可能在山寨币随后的跟随波动中被止损。
核心思路:利用 BTC 波动对山寨币的 领先因果关系,在 BTC 异常波动后主动收紧山寨币的开仓过滤阈值,等传导效应消退后再恢复正常敏感度。
2. 在系统中的位置
4 层过滤架构:
Layer 0 (硬过滤) ─ BOCPD 趋势检测 ← 判断是否处于趋势 regime
Layer 1 (软过滤) ─ ER 持续动量确认 ← 确认趋势是否持续
Layer 2 (硬过滤) ─ CUSUM 暴涨暴跌检测 ← ★ BTC 因子在此层生效
Layer 3 (仲裁) ─ 价差均值回归仲裁 ← 最终仲裁
BTC 因子作用于 Layer 2(暴涨暴跌检测),通过动态调整 CUSUM 阈值来实现:
- BTC 平静时 → CUSUM 阈值保持默认 → 正常开仓灵敏度
- BTC 异常波动后 → CUSUM 阈值被收紧(降低) → 更容易触发过滤 → 暂时阻止山寨币开仓
3. 算法详解
3.1 数据采集:Stress 指标的积累
每当任意币种(包括 BTC 自身)有新 K 线到来时,系统会:
- 计算该币种的 Huber-CUSUM 累积值(正向 S+ 和负向 S-)
- 取
max(S+, S-)作为该币种当前的 stress 值 - 将 stress 值追加到该币种的历史队列中
# 每根新 K 线更新后(update 方法内)
sp, sn = self._cusum_state.get(symbol, (0.0, 0.0))
self._btc_stress_history[symbol].append(max(sp, sn))
其中 CUSUM 值的计算采用 Huber 鲁棒化处理:
# Rogers-Satchell 波动率估计
rs_vol = sqrt(RS_EMA)
# 标准化收益 z 值(Huber clip 在 ±5.0)
z_raw = (close - prev_close) / prev_close / rs_vol
z = copysign(min(|z_raw|, 5.0), z_raw)
# CUSUM 递推(自适应 drift)
S+ = max(0, S+_prev + z - drift)
S- = max(0, S-_prev - z - drift)
关键点:CUSUM 是一个有记忆的累积统计量。单根 K 线的小幅波动会被 drift 项吸收归零,但连续多根同向大幅波动会使 CUSUM 值持续攀升。这使得 stress 指标能区分"正常波动"和"持续剧烈波动"。
3.2 滞后回看:提取 BTC 近期最大 stress
当需要判断某个山寨币是否可以开仓时(_check_spike 方法),系统会:
- 跳过 BTC 自身(BTC 不需要参考自己)
- 从 BTC 的 stress 历史队列中取 最近 3 根 K 线(= 15 分钟,5 分钟 K 线 × 3)
- 取这 3 根中的 最大值 作为
btc_stress
if symbol != self._market_ref:
btc_hist = self._btc_stress_history.get(self._market_ref)
btc_stress = max(list(btc_hist)[-3:]) # lag = 3 根 K 线
为什么是 lag=3(15 分钟):
实证研究表明 BTC 到主流山寨币的波动传导延迟在 10-20 分钟之间。取 3 根 5 分钟 K 线(15 分钟窗口)能覆盖大部分传导延迟,同时避免窗口过长导致误判。
为什么取 max 而非 mean:
传导效应由 BTC 的峰值冲击驱动,不是平均水平。一根极端 K 线就足以触发后续传导,所以用 max 更准确。
3.3 阈值收紧:指数衰减缩放因子
当 btc_stress 超过阈值时,计算一个 缩放因子 sf 来收紧 CUSUM 阈值:
btc_stress_thresh = 2.0 # stress 触发阈值
decay_rate = 0.3 # 衰减速率
factor_min = 0.7 # 最大收紧幅度(阈值最低缩到 70%)
if btc_stress > btc_stress_thresh:
excess = btc_stress - btc_stress_thresh
sf = max(factor_min, exp(-decay_rate * excess))
eff_cusum_up *= sf # 收紧暴涨阈值
eff_cusum_down *= sf # 收紧暴跌阈值
缩放因子的数学形式:
sf = max(0.7, e^(-0.3 × (btc_stress - 2.0)))
行为特性:
| BTC stress 值 | excess | sf = e^(-0.3×excess) | 实际 sf(clamp) | 阈值缩放效果 |
|---|---|---|---|---|
| ≤ 2.0 | 0 | — | 1.0(不触发) | 无影响 |
| 2.5 | 0.5 | 0.861 | 0.861 | 阈值降至 86% |
| 3.0 | 1.0 | 0.741 | 0.741 | 阈值降至 74% |
| 3.5 | 1.5 | 0.638 | 0.700 | 阈值降至 70%(触底) |
| 4.0+ | 2.0+ | < 0.549 | 0.700 | 阈值降至 70%(触底) |
设计要点:
- 指数衰减(而非线性):BTC stress 轻微超标时温和收紧,严重超标时快速收紧,符合市场冲击的非线性传导特性
- 下限 0.7:阈值最多降到原来的 70%,防止过度收紧导致完全无法开仓
- 阈值 2.0:CUSUM 累积值达到 2.0 意味着 BTC 已经出现了显著超过正常波动的异常运动
3.4 实际过滤判定
收紧后的阈值直接参与 Layer 2 的过滤判定:
# strength=0.5 时的默认阈值
eff_up = 3.5 # 暴涨阈值
eff_down = 2.5 # 暴跌阈值
# BTC 因子收紧后(假设 sf=0.75)
eff_up = 3.5 × 0.75 = 2.625
eff_down = 2.5 × 0.75 = 1.875
# 过滤判定
if direction == 'short' and CUSUM+ >= eff_up: → 阻止做空(暴涨中)
if direction == 'long' and CUSUM- >= eff_down: → 阻止做多(暴跌中)
原本山寨币的 CUSUM 值需要达到 3.5 才会被过滤,BTC 异常后只需达到 2.625 就会触发过滤。这意味着更早地阻止了在传导波动中的错误开仓。
4. 与跨层修正的叠加
BTC 因子和 Layer 0 的 BOCPD 趋势概率可以叠加生效:
# 第一步:BTC 因子收紧
eff_up *= sf_btc # 例如 ×0.75
# 第二步:BOCPD 跨层修正(如果 Layer 0 检测到趋势)
if bocpd_trend_prob > 0.5:
adj = 1.0 - 0.2 × (bocpd_trend_prob - 0.5)
eff_up *= adj # 例如再 ×0.9
# 最终阈值 = 原始 × sf_btc × adj
这意味着:BTC 暴跌 + 山寨币自身也处于下行趋势 时,过滤阈值会被双重收紧,对开仓的保护最为严格。这正是最危险的市场环境。
5. 完整数据流
时间线(5min K 线)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
t=0 BTC 新 K 线到来
│
├─ 计算 BTC 的 RS 波动率 & Huber-CUSUM
├─ stress = max(S+, S-) = 3.2
└─ 追加到 btc_stress_history[BTC]
t=5 山寨币 X 新 K 线到来,策略尝试开空仓
│
├─ 计算 X 的 CUSUM: S+ = 2.8
├─ 查询 BTC 最近 3 根 stress: [3.2, 1.5, 1.0]
├─ btc_stress = max = 3.2 > 2.0 → 触发
├─ excess = 1.2, sf = max(0.7, e^(-0.36)) = 0.698 → 0.700
├─ eff_up = 3.5 × 0.70 = 2.45
├─ S+(2.8) >= eff_up(2.45) → 阻止做空
└─ 日志: "Layer2-暴涨不做空: CUSUM+=2.80>=2.45 BTC(lag=3)->x0.70"
t=20 BTC 恢复平静,stress 历史中最近 3 根均 < 2.0
│
├─ btc_stress = max(0.8, 0.5, 1.2) = 1.2 < 2.0
├─ BTC 因子不触发,eff_up 保持 3.5
└─ 正常判定:S+(1.5) < 3.5 → 允许开仓
6. 参数一览
| 参数 | 值 | 含义 | 类型 |
|---|---|---|---|
market_ref_symbol |
"BTC" |
参考基准币种 | 可配置 |
btc_stress_lag |
3 |
回看 K 线数量(= 15 分钟) | 硬编码常量 |
btc_stress_thresh |
2.0 |
stress 触发阈值 | 硬编码常量 |
btc_decay_rate |
0.3 |
指数衰减速率 | 硬编码常量 |
btc_stress_factor_min |
0.7 |
缩放因子下限(最大收紧 30%) | 硬编码常量 |
其中只有 market_ref_symbol 是外部可配置参数(通过 strength 元参数无法控制,因为参考币种本身不是松紧度问题)。其余 4 个参数作为算法常量硬编码在 MomentumFilter.__init__ 中。
7. 设计权衡
为什么不直接用 BTC 价格变化率?
CUSUM stress 比单根 K 线的价格变化率更稳健:
- 抗噪声:drift 项过滤掉正常波动,只有持续异常才会累积
- 有记忆:连续 3 根中等幅度下跌比单根大跌更危险,CUSUM 能捕捉这种"持续冲击"
- Huber 鲁棒化:z 值 clip 在 ±5.0,防止极端异常值(如交易所闪崩)污染指标
为什么不用相关系数/Granger 因果检验?
- 相关系数是静态的,无法反映"此刻 BTC 是否正在剧烈波动"
- Granger 因果检验需要大量历史样本且结论不实时
- CUSUM stress 是实时计算的,能立即反映 BTC 的当前市场状态
为什么下限是 0.7 而非更低?
阈值缩到 70% 以下会导致正常市场波动也频繁触发过滤,错过大量合理的开仓机会。0.7 是在"保护力度"和"策略可用性"之间的平衡点。
8. 源码位置
| 模块 | 位置 | 说明 |
|---|---|---|
| 参数定义 | momentum_filter.py:451-454 |
4 个 BTC 因子常量 |
| 状态存储 | momentum_filter.py:481 |
_btc_stress_history 字典 |
| Stress 积累 | momentum_filter.py:797-801 |
update() 中追加 stress 值 |
| 因子生效 | momentum_filter.py:704-713 |
_check_spike() 中阈值收紧 |
| 配置入口 | config.py → momentum_market_ref_symbol |
外部可配置的参考币种 |