开仓动量过滤器设计方案4
开仓动量过滤器设计方案
版本:v4.0
日期:2026-03-06
适用系统:Hyperliquid 量化交易系统(Adaptive Bollinger Z-Score 配对策略)
升级摘要:v3.0 → v4.0 DFA-2 二阶去趋势 + R² 质检,KPSS+ADF 对偶检验,BTC 跨资产动量因子,VWPM 量价确认,数据驱动非对称参数
目录
1. 背景与问题定义
1.1 交易系统现状
当前系统基于 Adaptive Bollinger Z-Score 配对策略,核心逻辑是协整对的 spread 均值回归:
- 当
adaptive_z突破adaptive_threshold时产生入场信号 - 当
adaptive_z回归至entry_adaptive_z * reversion_factor时平仓 - 使用 5 分钟 K 线,最大持仓时间控制在 72 小时以内(
max_hold_hours=72.0) - 策略引擎
_check_entry()已有冷却期、突破检测、持仓检查、z4h 绝对值过滤四层前置过滤
1.2 待解决的问题
均值回归策略在以下场景存在明显风险:
| 场景 | 问题 | 后果 |
|---|---|---|
| 资产处于趋势机制(Hurst > 0.6) | 均值回归假设本身失效 | spread 持续扩张,所有反向入场均面临系统性亏损 |
| 单边持续上涨 N 根 K 线后 | 追涨做多(跟趋势入场) | 趋势已过度延伸,极易被反转砸穿 |
| 单边持续下跌 N 根 K 线后 | 追跌做空(跟趋势入场) | 跌幅充分释放,反弹风险极高 |
| 短时间内迅速暴涨 | 做空对抗强动量 | 被轧空,止损代价极大 |
| 短时间内迅速暴跌 | 做多接飞刀 | 继续下杀,止损代价极大 |
1.3 四项约束目标
约束 1:连续下跌 → 不追跌(不做空)
约束 2:连续上涨 → 不追涨(不做多)
约束 3:迅速暴涨 → 不做空
约束 4:迅速暴跌 → 不做多
1.4 过滤维度
为全面覆盖配对交易的风险,过滤器需要在三个维度同时工作:
| 维度 | 检查对象 | 原因 |
|---|---|---|
| Alt 腿(单腿) | alt symbol 的价格动量 | 直接持仓标的的风险 |
| Base 腿(单腿) | base symbol 的价格动量 | 配对交易中 base 腿方向相反,同样承担动量风险 |
| Spread(配对层) | 两腿价差的趋势性 | 单腿动量不等于 spread 动量;两腿同涨但 spread 稳定时不应误杀 |
2. 算法选择与学术依据
2.0 Layer 0:市场机制检测
动机:均值回归的前提条件
均值回归策略的根本假设是价格序列处于均值回归机制(mean-reverting regime),即 Hurst 指数 H < 0.5。当市场进入趋势机制(H > 0.6)时,无论 spread 如何扩张,继续做均值回归的逻辑前提已经失效——这是一个比"动量过强"更根本的风险,应作为最前置的硬拦截。
选择:DFA-2(二阶去趋势波动分析)估算 Hurst 指数 + R² 质检
候选算法深度对比:
| 算法 | 核心思路 | 优点 | 缺点 | 5min K 线适用性 |
|---|---|---|---|---|
| 经典 R/S 法 | Rescaled Range 统计 | 最早提出(Hurst 1951) | 对非平稳序列有偏,受趋势污染 | 一般 |
| 方差比检验(VR) | 不同时间步长的方差比 | 快速、有统计检验 | 只测试特定时间步,非连续估计 | 一般 |
| DFA-1(线性去趋势) | 多尺度线性去趋势 RMS 波动 | 对非平稳序列鲁棒 | 线性去趋势不足以消除抛物线型加速趋势 | 一般 |
| DFA-2(二阶去趋势) | 多尺度二阶多项式去趋势后的 RMS 波动 | 对线性和抛物线趋势均鲁棒,无偏估计 | 需要每窗口 ≥ 3 个数据点 | 最适合 |
| MFDFA(多分形 DFA) | DFA 的多阶矩扩展 | 捕捉多分形特性 | 计算复杂度高,参数多 | 过于复杂 |
学术背景:
DFA 由 Peng et al. (1994) 在 DNA 序列分析中提出,后被 Mantegna & Stanley (1995) 引入金融时间序列。Kantelhardt et al. (2002, Physica A) 建议金融序列至少使用 DFA-2(二阶多项式去趋势),因为金融价格经常出现加速上涨/下跌(抛物线型趋势),DFA-1 的线性去趋势无法消除此类趋势分量,导致 Hurst 高估(假阳性)。
v4.0 关键改进 1:DFA-1 → DFA-2
加密市场中 FOMO 驱动的加速上涨和清算级联的加速下跌非常常见,此时 DFA-1 会因线性去趋势不充分而高估 Hurst,导致误拦截。DFA-2 使用二阶多项式(抛物线)去趋势,对此类场景鲁棒:
DFA-1:每个窗口拟合 y = a + b·x → 只消除线性趋势
DFA-2:每个窗口拟合 y = a + b·x + c·x² → 同时消除线性和抛物线趋势
v4.0 关键改进 2:R² 质检门控
60 根 5min K 线只能产生约 5-7 个尺度点,log-log 回归可能非常不稳定。没有 R² 质检的硬拦截等于建立在不可靠估计之上。
R² = 1 - SS_res / SS_tot
R² ≥ 0.85 → Hurst 估计可靠,正常使用
R² < 0.85 → 尺度点拟合不佳,Hurst 估计不可信 → 返回 None → 放行
核心公式(v4.0 完整版):
输入:最近 hurst_lookback 根 K 线的收盘价序列 {close[i]}
步骤 1:计算均值中心化对数收益率的累积和(DFA profile)
ret[i] = ln(close[i] / close[i-1])
profile[k] = sum(ret[i] - mean(ret)) for i=1..k
步骤 2:多尺度二阶去趋势 RMS 波动(DFA-2)
对每个尺度 s = 4, 6, 8, ..., N/4:
将 profile 分为 N/s 个不重叠窗口
在每个窗口内做二阶多项式去趋势(y = a + b·x + c·x²,最小二乘)
计算残差 RMS:F(s) = sqrt(mean(residuals²))
步骤 3:log-log 线性回归 + R² 质检
Hurst = slope of log(F(s)) vs log(s)
R² = 1 - SS_res / SS_tot
R² < 0.85 → 估计不可靠 → 放行
触发条件(Layer 0 硬拦截):
任一腿的 Hurst(DFA-2) > hurst_threshold(默认 0.60)
且 R² >= 0.85(估计质量合格)
→ 趋势机制已激活,均值回归假设失效
→ 直接拒绝(硬拦截,不可被 Layer 3 仲裁推翻)
为什么 H > 0.6 选为阈值:
- H = 0.5:随机游走(无记忆,均值回归和趋势都不成立)
- H < 0.5:反持续(均值回归,配对策略最理想状态)
- H = 0.6:弱趋势,均值回归仍可能有效,不应过于激进拦截
- H > 0.6:显著趋势,均值回归假设明确失效,应拦截
2.1 约束 1&2:持续单边行情过滤(Layer 1)
候选算法深度对比
| 算法 | 核心思路 | 优点 | 缺点 | 1-5min 适用性 |
|---|---|---|---|---|
| 连续 K 线计数 | 计数涨/跌根数 | 直观 | 噪音大,交替涨跌频繁,漏判 | 不适合 |
| 纯净位移 (Net Displacement) | 只看起点→终点 | 对噪音免疫 | 路径盲:V 形反转误杀,震荡漂移漏判 | 一般 |
| ADX + DMI | 趋势强度指标 | 学术验证充分 | 滞后较大(14期默认),参数较多 | 一般 |
| EMA 方向 | 指数均线斜率 | 平滑 | 滞后大,无法区分趋势质量 | 一般 |
| Kaufman ER + 净位移(联合) | 效率比率衡量趋势质量 + 净位移衡量幅度 | 同时捕捉方向和路径质量,消除 V 形误杀 | 无 | 最适合 |
选择:Kaufman 效率比率 + 自适应净位移(RS 波动率驱动,非对称阈值)
学术背景:
Perry Kaufman 在 Smarter Trading (1995) 中提出 Efficiency Ratio,后被广泛应用于 KAMA(Kaufman Adaptive Moving Average)中。ER 是唯一同时衡量方向性和路径效率的单一指标,复杂度 O(N),无需预热统计分布。
非对称 ER 阈值
加密市场存在结构性非对称性:上涨由散户 FOMO 驱动,趋势延续性更强;下跌由恐慌清算驱动,往往更急但反弹更快。因此:
er_threshold_long = 0.60:拦截做多方向时更严格(上涨趋势延续性强,均值回归机会少)er_threshold_short = 0.50:拦截做空方向时稍宽松(下跌后反弹较快)
核心公式:
N = sustained_lookback(回望根数,默认 30 根 5min K 线 = 150 分钟)
# Kaufman Efficiency Ratio:方向距离 / 路径总长度
direction_distance = abs(close[-1] - close[-N])
path_length = sum(abs(close[i] - close[i-1]) for i in [-N+1, ..., -1])
ER = direction_distance / path_length # 范围 [0, 1]
# 净位移(带符号)
net_return = (close[-1] - close[-N]) / close[-N]
# 自适应阈值:用 Rogers-Satchell 波动率标准化(per-symbol 基准)
rs_vol = per-symbol 滚动中位数基准(见 2.2 节)
sustained_threshold = base_threshold * (rs_vol_current / rs_vol_baseline)
(限制缩放范围:0.3x ~ 3.0x)
# 联合触发条件(非对称):
约束1 触发:direction == 'short'
AND net_return <= -sustained_threshold
AND ER >= er_threshold_short(= 0.50)
约束2 触发:direction == 'long'
AND net_return >= +sustained_threshold
AND ER >= er_threshold_long(= 0.60)
2.2 约束 3&4:急涨急跌过滤(Layer 2)
候选算法深度对比
| 算法 | 核心思路 | 优点 | 缺点 | 1-5min 适用性 |
|---|---|---|---|---|
| 固定百分比阈值 | 单根 K 线涨幅 > X% | 最简单 | 波动率变化大时误杀/漏判 | 不适合 |
| SMA ATR 倍数 | 单根 vs 简单平均 ATR | 自适应 | SMA 响应慢,路径效率不如 EMA,只查单根 | 一般 |
| Z-Score of Returns | 滚动分布标准化 | 自适应 | 需 50+ 根历史才稳定 | 一般 |
| GK(Garman-Klass)+ CUSUM | OHLC 波动率 + 累积和检测 | 高效,自适应 | 零漂移假设:GK 在持续趋势市场有偏 | 一般 |
| RS(Rogers-Satchell)+ CUSUM + VWPM 量价确认 | 漂移不变 OHLC 波动率 + 累积和检测 + 量价动量 | 无漂移假设,跨资产因子,非对称阈值 | 略复杂 | 最适合 |
Rogers-Satchell 替代 Garman-Klass(v3.0 继承)
学术依据:
Garman-Klass (1980) 波动率估计器的理论假设是零漂移(zero drift),即价格序列的期望收益率为零。但加密市场的趋势行情中漂移不为零,此时 GK 的修正项 -(2ln2-1)*ln(C/O)² 会引入系统偏差。
Rogers-Satchell (1991) 解决了这个问题:
RS(i) = ln(H/C) * ln(H/O) + ln(L/C) * ln(L/O)
性质:
- 完全漂移不变(drift-invariant),不假设零期望收益
- 单根 K 线恒 >= 0(H>=C, H>=O, L<=C, L<=O 保证两项均非负)
- 理论效率 ≈ close-to-close 的 6.2 倍(vs GK 的 7.4 倍,效率略低但无偏)
- 特别适合 24/7 加密市场(无隔夜跳空,RS 直接优于 Yang-Zhang)
Per-symbol 基准波动率(v3.0 继承)
每个 symbol 独立维护 200 样本的 RS 滚动中位数:
per_symbol_baseline[symbol] = rolling_median(RS_history[symbol], maxlen=200)
adaptive_thresh = base_thresh * (rs_vol_current / per_symbol_baseline[symbol])
BTC 的阈值相对于 BTC 自身历史波动率缩放,alt 相对于 alt 自身缩放,完全解耦。
v4.0 关键改进 3:VWPM(Volume-Weighted Price Momentum)量价确认
v3.0 的量价确认仅使用 volume_ratio >= 1.5(当前成交量 / Volume EMA),只能区分"放量/缩量",无法判断成交量是否在确认价格方向。
v4.0 引入 VWPM(Volume-Weighted Price Momentum),检查近期成交量是否集中在价格运动的方向上:
VWPM = Σ(ret[i] * vol[i]) / Σ(vol[i]) # 最近 vwpm_window 根 K 线
VWPM 为正 → 成交量集中在上涨 K 线 → 上涨有量能支撑
VWPM 为负 → 成交量集中在下跌 K 线 → 下跌有量能支撑
|VWPM| / rs_vol >= vwpm_confirm_ratio → 量价方向一致性确认
联合确认逻辑(v4.0):
volume_confirmed = volume_ratio >= 1.5 AND |VWPM|/rs_vol >= vwpm_confirm_ratio(默认 0.8)
旧方案:仅看量是否放大 → 假放量(对倒、做市)可能误触发
新方案:量大 + 量在对的方向 → 信号更可靠
学术依据:
Kyle (1985) 的知情交易模型表明,价格与成交量的联合信号比单独信号更有区分力。VWPM 是 Kyle 模型在 K 线级别的简化实现。
v4.0 关键改进 4:BTC 跨资产动量因子
加密市场有强烈的 BTC 领先效应——BTC 急动后,alt 跟随的概率极高。当前系统只检查 alt 和 base 各自的动量,不考虑 BTC 作为系统性因子的影响。
# BTC CUSUM 压力指数
btc_stress = max(btc_S_pos, btc_S_neg)
# 当 BTC CUSUM 超过压力阈值(默认 2.0)时,降低 alt 的 CUSUM 检测阈值
if symbol != market_ref AND btc_stress > btc_stress_threshold:
stress_factor = max(0.7, 1.0 - (btc_stress - btc_stress_threshold) * 0.1)
effective_cusum_thresh_up *= stress_factor
effective_cusum_thresh_down *= stress_factor
含义:
BTC 正在急动 → 全市场即将联动 → 降低 alt 的急动检测阈值 → 更早拦截
stress_factor 下限 0.7 → CUSUM 阈值最多降低 30%(避免过度敏感)
学术依据:
Moskowitz, Ooi & Pedersen (2012) "Time Series Momentum" 证明了资产间动量的系统性溢出效应。加密市场中 BTC dominance 使得这一效应尤其显著。
非对称 CUSUM 阈值
# 双侧 CUSUM(Rogers-Satchell 波动率标准化)
ret(i) = (close(i) - close(i-1)) / close(i-1)
z(i) = ret(i) / rs_vol
S_pos(i) = max(0, S_pos(i-1) + z(i) - drift) # 检测正向急动(暴涨)
S_neg(i) = max(0, S_neg(i-1) - z(i) - drift) # 检测负向急动(暴跌)
# 非对称触发阈值(BTC 因子调整后的有效阈值)
spike_up = S_pos >= effective_cusum_thresh_up # 基础 3.5
spike_down = S_neg >= effective_cusum_thresh_down # 基础 2.5
# VWPM + 量比联合确认(v4.0)
volume_confirmed = volume_ratio >= 1.5 AND |VWPM|/rs_vol >= vwpm_confirm_ratio
约束3 触发:direction == 'short'
AND spike_up == True
AND volume_confirmed == True
约束4 触发:direction == 'long'
AND spike_down == True
AND volume_confirmed == True
CUSUM 隐式冷却期(显式记录于此):
隐式冷却期 ≈ threshold / drift × bar_interval
≈ 3.5 / 0.5 × 5min = 35min(spike_up,BTC 未施压时)
≈ 2.5 / 0.5 × 5min = 25min(spike_down,BTC 未施压时)
BTC 施压时阈值可降至 70%:
≈ 2.45 / 0.5 × 5min ≈ 24.5min(spike_up 最激进时)
≈ 1.75 / 0.5 × 5min ≈ 17.5min(spike_down 最激进时)
2.3 Spread 层面趋势检测(Layer 3)
v4.0 改进:ADF + KPSS + ER 三重仲裁
学术背景:
v3.0 中 Layer 3 使用 ADF + ER 双重仲裁,但 ADF 检验存在已知缺陷:对近单位根替代假设功效极低,尤其在小样本(20 个观测值)下。现代计量经济学的标准做法是 ADF + KPSS 联合检验(Kwiatkowski et al., 1992):
- ADF 的 H0:序列有单位根(非平稳) → 拒绝说明平稳
- KPSS 的 H0:序列平稳 → 拒绝说明非平稳
两者互为对偶。只用 ADF 时,"不拒绝 H0"既可能是"真的非平稳"也可能是"样本不足导致检验无力"。加 KPSS 后能区分这两种情况。
v4.0 四种情况决策矩阵:
| ADF 结果 | KPSS 结果 | 结论 | 动作 |
|---|---|---|---|
| 拒绝 H0(p<0.05,平稳) | 不拒绝 H0(p>0.05,平稳) | 确认平稳 | 推翻拦截,放行 |
| 不拒绝 H0(非平稳) | 拒绝 H0(p<0.05,非平稳) | 确认非平稳 | 维持拦截 |
| 拒绝 H0(平稳) | 拒绝 H0(非平稳) | 矛盾 | 保守维持拦截 |
| 不拒绝 H0 | 不拒绝 H0 | 不确定 | 保守维持拦截 |
三重确认逻辑(ER 快速筛 + ADF/KPSS 精确验证):
# 复用 z4h 序列作为 spread 代理
z4h_series = [z4h[-N], ..., z4h[-1]]
# 第一关:ER 快速检查(O(N),无需统计检验)
spread_er = spread_dir / spread_path
# ER 未达阈值 → 路径曲折 → 直接判定无趋势 → 放行(不调用 ADF/KPSS,节省计算)
if spread_er < spread_er_threshold:
→ 无趋势,推翻拦截,放行
# 第二关:ADF + KPSS 对偶检验(仅在 ER 认为有趋势时执行)
adf_pvalue = adfuller(z4h_series, maxlag=2, regression='c')[1]
kpss_pvalue = kpss(z4h_series, regression='c', nlags='auto')[1]
# 四种情况决策:
if adf_pvalue < 0.05 AND kpss_pvalue > 0.05:
→ 双重确认平稳 → 推翻拦截,放行(最强的放行信号)
if adf_pvalue >= 0.05 AND kpss_pvalue <= 0.05:
→ 双重确认非平稳 → 维持拦截(最强的拦截信号)
其他情况(矛盾或不确定):
→ 保守维持拦截(宁可误杀不可漏杀)
# 无 statsmodels 或样本不足(< 15):仅凭 ER 判断(降级模式)
为什么 ADF/KPSS 需要 ER 前置筛选:
- ADF+KPSS 计算有额外开销(statsmodels),每次信号都调用会浪费资源
- ER 是 O(N) 的快速初步筛选,绝大多数情况 ER < threshold 时直接放行,无需统计检验
- 只有"ER 认为有趋势"时才调用 ADF+KPSS 做精确验证,兼顾性能和精度
2.4 算法组合架构(四层)
输入信号(direction, alt_symbol, base_symbol)
│
├──▶ Layer 0: 市场机制检测(DFA-2 Hurst 指数 + R² 质检) ← v4.0 升级
│ ├── alt 腿: Hurst(alt) > 0.60 AND R² >= 0.85?
│ └── base 腿: Hurst(base) > 0.60 AND R² >= 0.85?
│ 任一腿触发 → 直接拒绝(硬拦截,均值回归假设失效)
│
├──▶ Layer 2: 急动检测(CUSUM + RS + VWPM + BTC因子,非对称阈值) ← v4.0 升级
│ ├── BTC 跨资产压力 → 动态调低 alt CUSUM 阈值
│ ├── alt 腿: check(alt, direction)
│ └── base 腿: check(base, opposite_direction)
│ 任一腿被拦截 → 直接拒绝(硬拦截,执行风险不可对冲)
│
├──▶ Layer 1: 单腿持续趋势过滤(ER + RS 自适应净位移,非对称阈值)
│ ├── alt 腿: check(alt, direction)
│ └── base 腿: check(base, opposite_direction)
│ 任一腿被拦截 → 进入 Layer 3 仲裁(软拦截)
│
└──▶ Layer 3: Spread ADF + KPSS + ER 三重仲裁 ← v4.0 升级
仅在 Layer 1 软拦截时启动
ER < threshold → 直接放行(O(N) 快速路径)
ADF p<0.05 + KPSS p>0.05 → 确认平稳,放行
ADF p>=0.05 + KPSS p<=0.05 → 确认非平稳,维持拦截
矛盾/不确定 → 保守维持拦截
│
▼
最终决策:允许 / 拒绝入场
v4.0 架构图修正:Layer 2(硬拦截)优先于 Layer 1(软拦截)执行,避免不必要的 Layer 3 仲裁开销。这是正确的优先级顺序——先排除不可仲裁的硬拦截,再处理可仲裁的软拦截。
设计理念(v4.0 修订):
| 层级 | 类型 | 可仲裁 | 触发含义 | v4.0 变化 |
|---|---|---|---|---|
| Layer 0(Hurst) | 硬拦截 | 否 | 均值回归假设根本失效 | DFA-2 + R² 质检 |
| Layer 2(CUSUM) | 硬拦截 | 否 | 急动执行风险极高 | VWPM + BTC 因子 |
| Layer 1(ER+位移) | 软拦截 | 是(Layer 3) | 单腿持续趋势,可能误杀 | 继承 v3.0 |
| Layer 3(ADF+KPSS+ER) | 仲裁器 | — | Spread 仍平稳则放行 | 新增 KPSS 对偶检验 |
3. 算法具体实现
3.1 模块位置
src/trading/
momentum_filter.py ← 新建(独立模块,不耦合策略逻辑)
strategy.py ← 修改 _check_entry(),注入并调用 filter;SymbolBaseline 新增 z4h_history
config.py ← 修改 StrategyParams,新增过滤器参数(含新增参数)
orchestrator.py ← 修改 process_analysis(),透传 OHLCV + volume
src/services/
realtime_kline_service_base.py ← 修改 _trigger_strategy_if_ready(),提取双腿 OHLCV + volume
3.2 MomentumFilter 完整代码(v4.0)
# src/trading/momentum_filter.py
"""
开仓动量过滤器 v4.0
四层过滤架构:
Layer 0: Hurst DFA-2 + R² 质检 → 趋势机制检测(硬拦截) [v4.0 升级]
Layer 1: ER + RS 自适应净位移 → 持续趋势检测(软拦截)
Layer 2: CUSUM + RS + VWPM + BTC因子 → 急动检测(硬拦截) [v4.0 升级]
Layer 3: Spread ADF + KPSS + ER → 误杀修正(仅软拦截时启动) [v4.0 升级]
v4.0 改进清单:
1. [Layer 0] DFA-1 → DFA-2 二阶多项式去趋势(消除抛物线型加速趋势偏差)
2. [Layer 0] 新增 R² 质检门控(R² < 0.85 → 估计不可靠 → 放行)
3. [Layer 2] VWPM 量价确认替代简单 volume_ratio(量大+方向一致才确认)
4. [Layer 2] BTC 跨资产动量因子(BTC CUSUM 高压时降低 alt 检测阈值)
5. [Layer 3] KPSS 对偶检验(ADF+KPSS 四象限决策矩阵,消除 ADF 低功效问题)
6. [接口] check() 返回 (bool, str, bool) 不变
"""
import math
from collections import deque
from datetime import datetime
try:
from statsmodels.tsa.stattools import adfuller as _adfuller
_HAS_ADF = True
except ImportError:
_HAS_ADF = False
try:
from statsmodels.tsa.stattools import kpss as _kpss
_HAS_KPSS = True
except ImportError:
_HAS_KPSS = False
class _RollingMedian:
"""
固定窗口滚动中位数(maxlen <= 200)。
当前实现 O(N log N) per query(排序法),对 200 元素 + 5min 更新频率无性能问题。
若未来基准窗口扩大或更新频率提高,可改为双堆(max-heap + min-heap)
实现 O(log N) push + O(1) median 查询。
"""
__slots__ = ('_data',)
def __init__(self, maxlen: int):
self._data: deque = deque(maxlen=maxlen)
def push(self, val: float) -> None:
self._data.append(val)
@property
def value(self) -> float | None:
if not self._data:
return None
s = sorted(self._data)
n = len(s)
mid = n >> 1
return s[mid] if n & 1 else (s[mid - 1] + s[mid]) * 0.5
def __len__(self) -> int:
return len(self._data)
def _hurst_dfa2(closes: list[float], r2_threshold: float = 0.85) -> tuple[float | None, float | None]:
"""
DFA-2(二阶去趋势波动分析)估算 Hurst 指数。
相比 DFA-1(线性去趋势),DFA-2 使用二阶多项式(y = a + bx + cx²)去趋势,
对加密市场常见的抛物线型加速上涨/下跌更鲁棒(Kantelhardt et al., 2002)。
返回值:
(hurst, r_squared)
数据不足或 R² < r2_threshold 时返回 (None, None)
H < 0.5: 均值回归
H ≈ 0.5: 随机游走
H > 0.5: 趋势持续
"""
n = len(closes)
if n < 20:
return None, None
# 计算对数收益率
rets = []
for i in range(1, n):
if closes[i] > 0 and closes[i - 1] > 0:
rets.append(math.log(closes[i] / closes[i - 1]))
nr = len(rets)
if nr < 16:
return None, None
mean_r = sum(rets) / nr
# 构建均值中心化累积和(DFA profile)
profile = []
cum = 0.0
for r in rets:
cum += r - mean_r
profile.append(cum)
# 多尺度 RMS 波动率计算(DFA-2:二阶多项式去趋势)
scales: list[float] = []
rms_vals: list[float] = []
s = 4
max_s = nr // 4
while s <= max_s:
n_win = nr // s
if n_win < 2:
break
rss_sum = 0.0
rss_cnt = 0
for i in range(n_win):
seg = profile[i * s: (i + 1) * s]
ws = len(seg)
if ws < 3:
# 窗口太小无法做二阶拟合,降级为线性
if ws >= 2:
x_m = (ws - 1) * 0.5
y_m = sum(seg) / ws
cov = sum((j - x_m) * (seg[j] - y_m) for j in range(ws))
var_x = sum((j - x_m) ** 2 for j in range(ws))
slope = cov / var_x if var_x > 1e-12 else 0.0
intercept = y_m - slope * x_m
rss = sum((seg[j] - (intercept + slope * j)) ** 2 for j in range(ws)) / ws
rss_sum += rss
rss_cnt += 1
continue
# ── 二阶多项式去趋势(y = a + b·x + c·x²)──
# 预计算 x 的幂次和(x = 0, 1, ..., ws-1)
s0 = float(ws)
s1 = ws * (ws - 1) / 2.0
s2 = ws * (ws - 1) * (2 * ws - 1) / 6.0
s3 = (ws * (ws - 1) / 2.0) ** 2
s4 = ws * (ws - 1) * (2 * ws - 1) * (3 * ws * ws - 3 * ws - 1) / 30.0
# y 的交叉和
sy0 = sum(seg)
sy1 = sum(j * seg[j] for j in range(ws))
sy2 = sum(j * j * seg[j] for j in range(ws))
# 3×3 正规方程 Cramer 法则求解
det = (s0 * (s2 * s4 - s3 * s3)
- s1 * (s1 * s4 - s3 * s2)
+ s2 * (s1 * s3 - s2 * s2))
if abs(det) < 1e-12:
# 奇异矩阵,降级为线性去趋势
x_m = (ws - 1) * 0.5
y_m = sum(seg) / ws
cov = sum((j - x_m) * (seg[j] - y_m) for j in range(ws))
var_x = sum((j - x_m) ** 2 for j in range(ws))
slope = cov / var_x if var_x > 1e-12 else 0.0
intercept = y_m - slope * x_m
rss = sum((seg[j] - (intercept + slope * j)) ** 2 for j in range(ws)) / ws
rss_sum += rss
rss_cnt += 1
continue
a = ((sy0 * (s2 * s4 - s3 * s3)
- s1 * (sy1 * s4 - s3 * sy2)
+ s2 * (sy1 * s3 - s2 * sy2)) / det)
b = ((s0 * (sy1 * s4 - s3 * sy2)
- sy0 * (s1 * s4 - s3 * s2)
+ s2 * (s1 * sy2 - sy1 * s2)) / det)
c = ((s0 * (s2 * sy2 - sy1 * s3)
- s1 * (s1 * sy2 - sy1 * s2)
+ sy0 * (s1 * s3 - s2 * s2)) / det)
rss = sum((seg[j] - (a + b * j + c * j * j)) ** 2 for j in range(ws)) / ws
rss_sum += rss
rss_cnt += 1
if rss_cnt >= 2:
f_s = math.sqrt(rss_sum / rss_cnt)
if f_s > 1e-14:
scales.append(math.log(s))
rms_vals.append(math.log(f_s))
# 对数均匀采样尺度
s = max(s + 1, int(s * 1.4 + 0.5))
if len(scales) < 3:
return None, None
# log-log 线性回归斜率 = Hurst 指数
n_pts = len(scales)
x_m = sum(scales) / n_pts
y_m = sum(rms_vals) / n_pts
num = sum((scales[i] - x_m) * (rms_vals[i] - y_m) for i in range(n_pts))
den = sum((scales[i] - x_m) ** 2 for i in range(n_pts))
if den < 1e-12:
return None, None
hurst = max(0.0, min(1.0, num / den))
# ── R² 质检 ──
slope = num / den
ss_res = sum((rms_vals[i] - (y_m + slope * (scales[i] - x_m))) ** 2 for i in range(n_pts))
ss_tot = sum((rms_vals[i] - y_m) ** 2 for i in range(n_pts))
r_squared = 1.0 - ss_res / ss_tot if ss_tot > 1e-12 else 0.0
if r_squared < r2_threshold:
return None, None # 估计不可靠,放行
return hurst, r_squared
class MomentumFilter:
"""
开仓动量过滤器 v4.0 — 四层架构
公开接口:
update(symbol, close, high, low, open_, volume, kline_time) — 每根 K 线调用一次
check(symbol, direction) -> (allowed, reason, is_soft)
check_spread(z4h_history, direction) -> (has_trend, reason)
ready(symbol) -> bool
"""
def __init__(
self,
# ── 总开关 ──
enabled: bool = True,
# ── Layer 0: Hurst DFA-2 ──
hurst_enabled: bool = True,
hurst_lookback: int = 60,
hurst_threshold: float = 0.60,
hurst_r2_threshold: float = 0.85, # v4.0 新增:R² 质检门槛
# ── Layer 1: 持续趋势 ──
sustained_lookback: int = 30,
sustained_base_threshold: float = 0.008,
er_threshold_long: float = 0.60,
er_threshold_short: float = 0.50,
# ── Layer 2: 急动检测 ──
rs_period: int = 10,
cusum_drift: float = 0.5,
cusum_threshold_spike_up: float = 3.5,
cusum_threshold_spike_down: float = 2.5,
volume_confirm_ratio: float = 1.5,
volume_ema_period: int = 20,
vwpm_window: int = 5, # v4.0 新增:VWPM 回望窗口
vwpm_confirm_ratio: float = 0.8, # v4.0 新增:VWPM 确认阈值
# ── Layer 2: BTC 跨资产因子 ──
market_ref_symbol: str = "BTC", # v4.0 新增
btc_stress_threshold: float = 2.0, # v4.0 新增
btc_stress_factor_min: float = 0.7, # v4.0 新增
# ── Layer 3: Spread 仲裁 ──
spread_lookback: int = 20,
spread_er_threshold: float = 0.45,
spread_net_threshold: float = 1.5,
spread_adf_pvalue: float = 0.05, # v4.0 修改:ADF 显著性水平
spread_kpss_pvalue: float = 0.05, # v4.0 新增:KPSS 显著性水平
):
self._enabled = enabled
# Layer 0
self._hurst_enabled = hurst_enabled
self._hurst_lookback = max(20, hurst_lookback)
self._hurst_thresh = hurst_threshold
self._hurst_r2_thresh = hurst_r2_threshold
# Layer 1
self._sustained_n = max(5, sustained_lookback)
self._sustained_base_thresh = sustained_base_threshold
self._er_thresh_long = er_threshold_long
self._er_thresh_short = er_threshold_short
# Layer 2
self._rs_period = rs_period
self._cusum_drift = cusum_drift
self._cusum_thresh_up = cusum_threshold_spike_up
self._cusum_thresh_down = cusum_threshold_spike_down
self._vol_confirm_ratio = volume_confirm_ratio
self._vol_ema_period = volume_ema_period
self._vwpm_window = vwpm_window
self._vwpm_confirm_ratio = vwpm_confirm_ratio
# Layer 2: BTC 因子
self._market_ref = market_ref_symbol
self._btc_stress_thresh = btc_stress_threshold
self._btc_stress_factor_min = btc_stress_factor_min
# Layer 3
self._spread_lookback = spread_lookback
self._spread_er_thresh = spread_er_threshold
self._spread_net_thresh = spread_net_threshold
self._spread_adf_pvalue = spread_adf_pvalue
self._spread_kpss_pvalue = spread_kpss_pvalue
# 数据缓冲区大小
buf_size = max(
hurst_lookback + 5,
sustained_lookback + 5,
rs_period + 10,
volume_ema_period + 5,
vwpm_window + 2,
) + 10
self._buf_size = buf_size
# symbol → deque of (close, high, low, open_, volume)
self._buffers: dict[str, deque] = {}
# symbol → 最后一次更新的 kline_time(去重)
self._last_kline_time: dict[str, datetime] = {}
# symbol → CUSUM 状态 (S_pos, S_neg)
self._cusum_state: dict[str, tuple[float, float]] = {}
# symbol → Rogers-Satchell 波动率 EMA(方差形式)
self._rs_ema: dict[str, float] = {}
# symbol → Volume EMA
self._vol_ema: dict[str, float] = {}
# symbol → Per-symbol 基准波动率滚动中位数(maxlen=200)
self._baseline_median: dict[str, _RollingMedian] = {}
# ------------------------------------------------------------------
# 公开接口
# ------------------------------------------------------------------
def update(
self,
symbol: str,
close: float,
high: float = 0.0,
low: float = 0.0,
open_: float = 0.0,
volume: float = 0.0,
kline_time: datetime | None = None,
) -> None:
"""
每根新 K 线收盘时调用。
high/low/open_ 可选;有则 Rogers-Satchell 更精确,无则降级为 close-to-close。
volume 可选;有则启用量价确认,无则跳过量确认。
kline_time 用于去重:同一 symbol 多配对时只追加一次。
"""
if kline_time is not None:
if self._last_kline_time.get(symbol) == kline_time:
return
self._last_kline_time[symbol] = kline_time
if symbol not in self._buffers:
self._buffers[symbol] = deque(maxlen=self._buf_size)
self._buffers[symbol].append((close, high, low, open_, volume))
buf = self._buffers[symbol]
if len(buf) >= 2:
self._online_update(symbol, buf)
def check(self, symbol: str, direction: str) -> tuple[bool, str, bool]:
"""
Layer 0 + Layer 2 + Layer 1 检查(单腿维度)。
执行顺序:Layer 0(硬)→ Layer 2(硬)→ Layer 1(软)
硬拦截优先于软拦截,避免不必要的 Layer 3 仲裁开销。
Returns:
(allowed, reason, is_soft)
allowed=False + is_soft=False → 硬拦截(Layer 0 / Layer 2),不可被 Layer 3 推翻
allowed=False + is_soft=True → 软拦截(Layer 1),可由 Layer 3 Spread 仲裁推翻
"""
if not self._enabled:
return True, "", False
buf = self._buffers.get(symbol)
if buf is None or len(buf) < self._rs_period + 2:
return True, "", False
# ── Layer 0(硬拦截):Hurst DFA-2 趋势机制检测 ──
ok, reason = self._check_hurst(symbol)
if not ok:
return False, reason, False
# ── Layer 2(硬拦截):急动检测(优先于 Layer 1) ──
ok, reason = self._check_spike(symbol, direction)
if not ok:
return False, reason, False
# ── Layer 1(软拦截):持续趋势 ──
if len(buf) >= self._sustained_n + 2:
ok, reason = self._check_sustained(symbol, direction)
if not ok:
return False, reason, True
return True, "", False
def check_spread(self, z4h_history: list[float], direction: str) -> tuple[bool, str]:
"""
Layer 3: Spread ADF + KPSS + ER 三重仲裁。
仅在存在软拦截(Layer 1)时调用。
Returns:
(has_trend, reason)
has_trend=True → Spread 有趋势 → 维持拦截
has_trend=False → Spread 无趋势 → 推翻拦截,放行
"""
n = self._spread_lookback
if len(z4h_history) < n + 1:
return False, ""
series = z4h_history[-(n + 1):]
# ── 第一关:ER 快速筛选(O(N),大多数情况到这里结束) ──
spread_dir = abs(series[-1] - series[0])
spread_path = sum(abs(series[i] - series[i - 1]) for i in range(1, len(series)))
er = spread_dir / spread_path if spread_path > 1e-10 else 0.0
spread_net = series[-1] - series[0]
# 方向性检查
if direction == 'short':
dir_ok = spread_net <= -self._spread_net_thresh
else:
dir_ok = spread_net >= self._spread_net_thresh
er_signals_trend = er >= self._spread_er_thresh and dir_ok
if not er_signals_trend:
return False, ""
# ── 第二关:ADF + KPSS 对偶检验(仅在 ER 认为有趋势时执行) ──
if (_HAS_ADF or _HAS_KPSS) and len(series) >= 15:
adf_p = None
kpss_p = None
if _HAS_ADF:
try:
adf_p = _adfuller(series, maxlag=2, regression='c', autolag=None)[1]
except Exception:
pass
if _HAS_KPSS:
try:
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
kpss_p = _kpss(series, regression='c', nlags='auto')[1]
except Exception:
pass
# ── 四象限决策矩阵 ──
if adf_p is not None and kpss_p is not None:
adf_stationary = adf_p < self._spread_adf_pvalue
kpss_stationary = kpss_p > self._spread_kpss_pvalue
if adf_stationary and kpss_stationary:
# 双重确认平稳 → 推翻拦截
return False, (
f"ADF+KPSS确认平稳(ADF_p={adf_p:.3f}<{self._spread_adf_pvalue}"
f" KPSS_p={kpss_p:.3f}>{self._spread_kpss_pvalue})→推翻拦截"
)
if not adf_stationary and not kpss_stationary:
# 双重确认非平稳 → 维持拦截
return True, (
f"Spread趋势(三重确认): ER={er:.2f}>={self._spread_er_thresh}"
f" 净位移={spread_net:+.3f}"
f" ADF_p={adf_p:.3f}>={self._spread_adf_pvalue}"
f" KPSS_p={kpss_p:.3f}<={self._spread_kpss_pvalue}"
)
# 矛盾或不确定 → 保守维持拦截
return True, (
f"Spread趋势(矛盾/不确定): ER={er:.2f}"
f" ADF_p={adf_p:.3f} KPSS_p={kpss_p:.3f}→保守拦截"
)
# 只有 ADF 可用(KPSS 不可用):降级为 v3.0 逻辑
if adf_p is not None:
if adf_p < self._spread_adf_pvalue:
return False, (
f"ADF判定平稳(p={adf_p:.3f}<{self._spread_adf_pvalue})→推翻拦截(无KPSS)"
)
return True, (
f"Spread趋势(ER+ADF): ER={er:.2f} ADF_p={adf_p:.3f}(无KPSS)"
)
# 降级:无 statsmodels 或样本不足 → 仅凭 ER 判断
return True, (
f"Spread趋势(ER): ER={er:.2f}>={self._spread_er_thresh}"
f" 净位移={spread_net:+.3f}"
)
def ready(self, symbol: str) -> bool:
"""该 symbol 是否已积累足够数据"""
buf = self._buffers.get(symbol)
if buf is None:
return False
return len(buf) >= self._rs_period + 2
# ------------------------------------------------------------------
# Layer 0: Hurst DFA-2(趋势机制检测 + R² 质检)
# ------------------------------------------------------------------
def _check_hurst(self, symbol: str) -> tuple[bool, str]:
"""Layer 0 硬拦截:Hurst > threshold 且 R² >= r2_threshold → 趋势机制"""
if not self._hurst_enabled:
return True, ""
buf = self._buffers.get(symbol)
if buf is None or len(buf) < self._hurst_lookback + 1:
return True, ""
closes = [d[0] for d in list(buf)[-(self._hurst_lookback + 1):]]
h, r2 = _hurst_dfa2(closes, r2_threshold=self._hurst_r2_thresh)
if h is None:
return True, "" # 数据不足或 R² 不达标,放行
if h > self._hurst_thresh:
return False, (
f"Layer0-趋势机制: Hurst(DFA-2)={h:.3f}>{self._hurst_thresh}"
f" R²={r2:.3f} → 均值回归假设失效"
)
return True, ""
# ------------------------------------------------------------------
# Layer 1: 持续趋势检测(ER + RS 自适应净位移,非对称阈值)
# ------------------------------------------------------------------
def _check_sustained(self, symbol: str, direction: str) -> tuple[bool, str]:
"""Layer 1 软拦截:ER >= threshold AND |net_return| >= adaptive_threshold"""
buf = self._buffers[symbol]
n = self._sustained_n
data = list(buf)[-(n + 1):]
closes = [d[0] for d in data]
if len(closes) < n + 1:
return True, ""
ref = closes[0]
if ref <= 0:
return True, ""
net_return = (closes[-1] - ref) / ref
# Kaufman Efficiency Ratio
direction_dist = abs(closes[-1] - ref)
path_length = sum(abs(closes[i] - closes[i - 1]) for i in range(1, len(closes)))
er = direction_dist / path_length if path_length > 1e-10 else 0.0
# 非对称 ER 阈值
er_thresh = self._er_thresh_long if direction == 'long' else self._er_thresh_short
if er < er_thresh:
return True, ""
# RS 自适应阈值(per-symbol 基准)
rs_var = self._rs_ema.get(symbol, 0.0)
rs_vol = math.sqrt(max(0.0, rs_var))
baseline = self._baseline_median.get(symbol)
baseline_val = baseline.value if (baseline and len(baseline) >= 5) else None
if rs_vol > 0 and baseline_val and baseline_val > 0:
adaptive_thresh = self._sustained_base_thresh * (rs_vol / baseline_val)
adaptive_thresh = max(
self._sustained_base_thresh * 0.3,
min(adaptive_thresh, self._sustained_base_thresh * 3.0),
)
else:
adaptive_thresh = self._sustained_base_thresh
if direction == 'short' and net_return <= -adaptive_thresh:
return False, (
f"Layer1-不追跌: 过去{n}根净跌幅={net_return:.2%}"
f" 阈值=-{adaptive_thresh:.2%} ER={er:.2f}>={er_thresh:.2f}"
)
if direction == 'long' and net_return >= adaptive_thresh:
return False, (
f"Layer1-不追涨: 过去{n}根净涨幅={net_return:.2%}"
f" 阈值=+{adaptive_thresh:.2%} ER={er:.2f}>={er_thresh:.2f}"
)
return True, ""
# ------------------------------------------------------------------
# Layer 2: 急动检测(CUSUM + RS + VWPM + BTC因子,非对称阈值)
# ------------------------------------------------------------------
def _check_spike(self, symbol: str, direction: str) -> tuple[bool, str]:
"""Layer 2 硬拦截:CUSUM 超阈值 + VWPM 量价确认 + BTC 跨资产因子"""
s_pos, s_neg = self._cusum_state.get(symbol, (0.0, 0.0))
buf = self._buffers[symbol]
# ── BTC 跨资产因子:BTC CUSUM 高压时降低 alt 检测阈值 ──
effective_thresh_up = self._cusum_thresh_up
effective_thresh_down = self._cusum_thresh_down
if symbol != self._market_ref:
btc_s_pos, btc_s_neg = self._cusum_state.get(self._market_ref, (0.0, 0.0))
btc_stress = max(btc_s_pos, btc_s_neg)
if btc_stress > self._btc_stress_thresh:
stress_factor = max(
self._btc_stress_factor_min,
1.0 - (btc_stress - self._btc_stress_thresh) * 0.1,
)
effective_thresh_up *= stress_factor
effective_thresh_down *= stress_factor
# ── VWPM + 量比联合确认 ──
vol_confirmed, vol_ratio, vwpm_val = self._check_volume(symbol)
rs_vol = math.sqrt(max(0.0, self._rs_ema.get(symbol, 0.0)))
vol_str = f" 量比={vol_ratio:.1f}" if vol_ratio > 0 else ""
vwpm_str = f" VWPM={vwpm_val:.4f}" if vwpm_val != 0.0 else ""
btc_str = ""
if symbol != self._market_ref and effective_thresh_up != self._cusum_thresh_up:
btc_str = f" BTC压力→阈值×{effective_thresh_up/self._cusum_thresh_up:.2f}"
if direction == 'short' and s_pos >= effective_thresh_up and vol_confirmed:
return False, (
f"Layer2-暴涨不做空: CUSUM+={s_pos:.2f}>={effective_thresh_up:.2f}"
f" RS_vol={rs_vol:.4f}{vol_str}{vwpm_str}{btc_str}"
f" (隐式冷却≈{effective_thresh_up/self._cusum_drift*5:.0f}min)"
)
if direction == 'long' and s_neg >= effective_thresh_down and vol_confirmed:
return False, (
f"Layer2-暴跌不做多: CUSUM-={s_neg:.2f}>={effective_thresh_down:.2f}"
f" RS_vol={rs_vol:.4f}{vol_str}{vwpm_str}{btc_str}"
f" (隐式冷却≈{effective_thresh_down/self._cusum_drift*5:.0f}min)"
)
return True, ""
def _check_volume(self, symbol: str) -> tuple[bool, float, float]:
"""
VWPM + 量比联合确认(v4.0)。
Returns:
(confirmed, vol_ratio, vwpm_value)
confirmed=True 当且仅当:量比 >= 阈值 AND |VWPM|/rs_vol >= 确认比
"""
buf = self._buffers.get(symbol)
if buf is None or len(buf) < 2:
return True, 0.0, 0.0 # 无数据,仅依赖 CUSUM
current_vol = buf[-1][4]
vol_ema = self._vol_ema.get(symbol, 0.0)
# 量比检查
if vol_ema > 0 and current_vol > 0:
vol_ratio = current_vol / vol_ema
else:
return True, 0.0, 0.0 # 无成交量数据,仅依赖 CUSUM
# VWPM 计算
vwpm_val = 0.0
w = min(self._vwpm_window, len(buf) - 1)
if w >= 1:
data = list(buf)[-(w + 1):]
weighted_sum = 0.0
total_vol = 0.0
for i in range(1, len(data)):
prev_c = data[i - 1][0]
curr_c = data[i][0]
v = data[i][4]
if prev_c > 0 and v > 0:
ret = (curr_c - prev_c) / prev_c
weighted_sum += ret * v
total_vol += v
if total_vol > 0:
vwpm_val = weighted_sum / total_vol
# 联合确认
rs_vol = math.sqrt(max(0.0, self._rs_ema.get(symbol, 0.0)))
if rs_vol > 1e-10 and vwpm_val != 0.0:
vwpm_normalized = abs(vwpm_val) / rs_vol
confirmed = (vol_ratio >= self._vol_confirm_ratio
and vwpm_normalized >= self._vwpm_confirm_ratio)
else:
# 无 VWPM 数据时降级为仅量比
confirmed = vol_ratio >= self._vol_confirm_ratio
return confirmed, vol_ratio, vwpm_val
# ------------------------------------------------------------------
# 在线更新(每根 K 线 O(1))
# ------------------------------------------------------------------
def _online_update(self, symbol: str, buf: deque) -> None:
"""O(1) 在线更新 CUSUM、RS EMA、Volume EMA、Per-symbol 基准中位数"""
curr = buf[-1]
prev = buf[-2]
close, high, low, open_, volume = curr
prev_close = prev[0]
if prev_close <= 0 or close <= 0:
return
# ── Rogers-Satchell 波动率 EMA ──
rs_val = self._calc_rs_single(high, low, open_, close)
alpha_rs = 2.0 / (self._rs_period + 1)
prev_rs = self._rs_ema.get(symbol, rs_val)
new_rs = alpha_rs * rs_val + (1.0 - alpha_rs) * prev_rs
self._rs_ema[symbol] = new_rs
# ── Per-symbol 基准波动率(滚动中位数,200 样本) ──
rs_vol_now = math.sqrt(max(0.0, new_rs))
if symbol not in self._baseline_median:
self._baseline_median[symbol] = _RollingMedian(maxlen=200)
self._baseline_median[symbol].push(rs_vol_now)
# ── CUSUM 更新(RS 波动率标准化)──
rs_vol = math.sqrt(max(0.0, new_rs))
if rs_vol > 1e-10:
ret = (close - prev_close) / prev_close
z = ret / rs_vol
s_pos, s_neg = self._cusum_state.get(symbol, (0.0, 0.0))
s_pos = max(0.0, s_pos + z - self._cusum_drift)
s_neg = max(0.0, s_neg - z - self._cusum_drift)
self._cusum_state[symbol] = (s_pos, s_neg)
# ── Volume EMA 更新 ──
if volume > 0:
alpha_v = 2.0 / (self._vol_ema_period + 1)
prev_v = self._vol_ema.get(symbol, volume)
self._vol_ema[symbol] = alpha_v * volume + (1.0 - alpha_v) * prev_v
@staticmethod
def _calc_rs_single(high: float, low: float, open_: float, close: float) -> float:
"""
Rogers-Satchell (1991) 波动率估计器(单根 K 线)。
公式: RS = ln(H/C) * ln(H/O) + ln(L/C) * ln(L/O)
特性:
- 完全漂移不变(drift-invariant),加密趋势市场无偏
- 恒 >= 0
- 理论效率:close-to-close 的 6.2 倍
"""
if high > 0 and low > 0 and open_ > 0 and high >= low:
try:
rs = (
math.log(high / close) * math.log(high / open_)
+ math.log(low / close) * math.log(low / open_)
)
return max(0.0, rs)
except (ValueError, ZeroDivisionError):
pass
if open_ > 0:
try:
return math.log(close / open_) ** 2
except (ValueError, ZeroDivisionError):
pass
return 0.0
3.3 StrategyParams 新增字段(v4.0)
在 src/trading/config.py 的 StrategyParams 中新增(共 24 个新字段,全部有默认值,不破坏现有代码):
@dataclass(frozen=True)
class StrategyParams:
# ... 现有字段(8个必填 + 3个有默认值)...
# ── 动量过滤器参数(v4.0) ──
momentum_filter_enabled: bool = True
# Layer 0: Hurst DFA-2
momentum_hurst_enabled: bool = True
momentum_hurst_lookback: int = 60
momentum_hurst_threshold: float = 0.60
momentum_hurst_r2_threshold: float = 0.85 # v4.0 新增
# Layer 1: 持续趋势(非对称 ER)
momentum_sustained_lookback: int = 30
momentum_sustained_base_threshold: float = 0.008
momentum_er_threshold_long: float = 0.60
momentum_er_threshold_short: float = 0.50
# Layer 2: 急动检测(RS + CUSUM + VWPM + BTC因子)
momentum_rs_period: int = 10
momentum_cusum_drift: float = 0.5
momentum_cusum_threshold_spike_up: float = 3.5
momentum_cusum_threshold_spike_down: float = 2.5
momentum_volume_confirm_ratio: float = 1.5
momentum_volume_ema_period: int = 20
momentum_vwpm_window: int = 5 # v4.0 新增
momentum_vwpm_confirm_ratio: float = 0.8 # v4.0 新增
momentum_market_ref_symbol: str = "BTC" # v4.0 新增
momentum_btc_stress_threshold: float = 2.0 # v4.0 新增
momentum_btc_stress_factor_min: float = 0.7 # v4.0 新增
# Layer 3: Spread 仲裁(ADF + KPSS + ER)
momentum_spread_lookback: int = 20
momentum_spread_er_threshold: float = 0.45
momentum_spread_net_threshold: float = 1.5
momentum_spread_adf_pvalue: float = 0.05 # v4.0 修改(原 0.10)
momentum_spread_kpss_pvalue: float = 0.05 # v4.0 新增
3.4 strategy.py 集成改动(v4.0)
SymbolBaseline 新增 z4h_history 字段(用于 Layer 3 Spread 仲裁,继承 v3.0):
@dataclass
class SymbolBaseline:
std_window: deque = field(default_factory=deque)
welford_mean: float = 0.0
welford_m2: float = 0.0
welford_updates: int = 0
ema: float | None = None
last_kline_time: datetime | None = None
last_std: float = 0.0
z4h_history: deque = field(default_factory=deque)
在 _get_baseline() 中同步初始化:
self._baselines[key] = SymbolBaseline(
std_window=deque(maxlen=params.std_window),
z4h_history=deque(maxlen=50),
)
process_tick 新增 OHLCV 参数(继承 v3.0):
def process_tick(
self,
symbol: str,
base_symbol: str,
z4h: float,
timestamp: datetime,
kline_time: datetime | None = None,
latest_price: float | None = None,
alt_ohlcv: dict | None = None,
base_ohlcv: dict | None = None,
) -> tuple[EntrySignal | None, ExitSignal | None]:
_process_tick_unlocked 新 K 线时同步更新过滤器和 z4h 历史:
if is_new_candle:
# 原有 Welford + EMA 更新...
# 追加 z4h 历史(供 Layer 3 Spread 仲裁)
bl.z4h_history.append(z4h)
# 更新两腿动量过滤器
if self._momentum_filter:
if alt_ohlcv:
self._momentum_filter.update(
symbol=symbol,
close=alt_ohlcv.get('close', 0.0),
high=alt_ohlcv.get('high', 0.0),
low=alt_ohlcv.get('low', 0.0),
open_=alt_ohlcv.get('open', 0.0),
volume=alt_ohlcv.get('volume', 0.0),
kline_time=kline_time,
)
if base_ohlcv and base_symbol:
self._momentum_filter.update(
symbol=base_symbol,
close=base_ohlcv.get('close', 0.0),
high=base_ohlcv.get('high', 0.0),
low=base_ohlcv.get('low', 0.0),
open_=base_ohlcv.get('open', 0.0),
volume=base_ohlcv.get('volume', 0.0),
kline_time=kline_time,
)
_check_entry 四层动量过滤(步骤 4.5–4.7,与 v3.0 逻辑一致):
def _check_entry(self, key, z4h, adaptive_z, timestamp, current_above, params):
# ... 步骤 1-4(冷却期、突破检测、持仓检查、z4h 过滤)...
# 步骤 4.5:方向判断(提前,供动量过滤器使用)
threshold = params.adaptive_threshold
if adaptive_z < -threshold:
direction = 'long'
elif adaptive_z > threshold:
direction = 'short'
else:
return None
# 步骤 4.6:四层动量过滤
if self._momentum_filter is not None:
soft_reasons: list[str] = []
hard_blocked = False
hard_reason_parts: list[str] = []
# Alt 腿(主方向)
if self._momentum_filter.ready(key[0]):
allowed, reason, is_soft = self._momentum_filter.check(key[0], direction)
if not allowed:
if is_soft:
soft_reasons.append(f"alt:{reason}")
else:
hard_blocked = True
hard_reason_parts.append(f"alt:{reason}")
# Base 腿(反向)
if key[1] and self._momentum_filter.ready(key[1]):
opposite = 'short' if direction == 'long' else 'long'
allowed, reason, is_soft = self._momentum_filter.check(key[1], opposite)
if not allowed:
if is_soft:
soft_reasons.append(f"base:{reason}")
else:
hard_blocked = True
hard_reason_parts.append(f"base:{reason}")
# 硬拦截(Layer 0 / Layer 2)→ 直接拒绝,不仲裁
if hard_blocked:
logger.info(f"动量过滤(硬) | {pair_label} | {' | '.join(hard_reason_parts)}")
return None
# 软拦截(Layer 1)→ 进入 Layer 3 仲裁
if soft_reasons:
combined = " | ".join(soft_reasons)
bl = self._baselines.get(key)
z4h_hist = list(bl.z4h_history) if (bl and bl.z4h_history) else []
spread_trend, spread_reason = self._momentum_filter.check_spread(z4h_hist, direction)
if spread_trend:
logger.info(
f"动量过滤(软+Spread) | {pair_label} | {combined} | {spread_reason}"
)
return None
else:
if spread_reason:
logger.info(
f"动量仲裁放行 | {pair_label} | 单腿:{combined} | {spread_reason}"
)
else:
logger.info(
f"动量仲裁放行 | {pair_label} | 单腿:{combined} | Spread无趋势→推翻拦截"
)
# 步骤 5:产生 EntrySignal(direction 已由步骤 4.5 确定)
...
3.5 改动范围(v4.0)
| 改动文件 | 改动内容 | 改动量 |
|---|---|---|
src/trading/momentum_filter.py |
新建,四层过滤器(DFA-2+R² / RS+CUSUM+VWPM+BTC / ER / ADF+KPSS+ER) | ~380 行 |
src/trading/strategy.py |
SymbolBaseline 新增 z4h_history,process_tick 新增 OHLCV 参数,_check_entry 四层检查 |
~60 行 |
src/trading/config.py |
StrategyParams 新增 24 个字段(全部有默认值) |
~30 行 |
src/trading/orchestrator.py |
process_analysis 新增 alt_ohlcv/base_ohlcv 参数并透传 |
~5 行 |
src/services/realtime_kline_service_base.py |
_trigger_strategy_if_ready 提取双腿 OHLCV + volume 并传入 |
~20 行 |
4. 融入当前交易体系的方案
4.1 数据流全景(v4.0)
WebSocket K线推送(OHLCV + volume)
|
v
realtime_kline_service_base
|
+-- _parse_kline() → kline dict (含 close/high/low/open/volume)
|
+-- _trigger_strategy_if_ready()
|
+-- 从 price_data_cache 提取最新 alt/base OHLCV + volume
| alt_ohlcv = {'close','high','low','open','volume'} ← alt_klines_5m[0]
| base_ohlcv = {'close','high','low','open','volume'} ← base_klines_5m[0]
|
v
TradingOrchestrator.process_analysis(
..., alt_ohlcv=alt_ohlcv, base_ohlcv=base_ohlcv
)
|
v
AdaptiveBollingerStrategy.process_tick(
..., alt_ohlcv=alt_ohlcv, base_ohlcv=base_ohlcv
)
|
+-- [新K线] bl.z4h_history.append(z4h) ← Layer 3 历史维护
|
+-- [新K线] MomentumFilter.update(alt, alt_ohlcv, kline_time)
| → O(1) 在线更新: RS EMA / CUSUM / Volume EMA / Per-symbol基准中位数
| MomentumFilter.update(base, base_ohlcv, kline_time)
| (kline_time 去重:同 symbol 多配对只追加一次)
|
+-- _check_exit() → ExitSignal
|
+-- _check_entry()
|
步骤1: 冷却期
步骤2: 突破检测(adaptive_z 首次穿越阈值)
步骤3: 持仓检查
步骤4: z4h 绝对值过滤
[v4.0]
步骤4.5: 方向判断(提前,供过滤器使用)
步骤4.6: 四层动量过滤
├── Layer 0(硬拦截): Hurst DFA-2 + R² 质检
│ ├── alt 腿: check → is_soft=False
│ └── base 腿: check → is_soft=False
│ 任一触发 → 直接拒绝
│
├── Layer 2(硬拦截): CUSUM + RS + VWPM + BTC因子
│ ├── BTC CUSUM 高压 → 降低 alt 阈值
│ ├── alt 腿: check → is_soft=False
│ └── base 腿: check → is_soft=False
│ 任一触发 → 直接拒绝
│
├── Layer 1(软拦截): ER + RS 净位移(非对称阈值)
│ ├── alt 腿: check → is_soft=True
│ └── base 腿: check → is_soft=True
│ 任一触发 → 进入 Layer 3 仲裁
│ (多腿 reason 合并,不再覆盖)
│
└── Layer 3(仲裁): Spread ADF + KPSS + ER 三重
ER < threshold → 直接放行(O(N) 快速路径)
ADF+KPSS 确认平稳 → 放行
ADF+KPSS 确认非平稳 → 维持拦截
矛盾/不确定 → 保守维持拦截
步骤5: 产生 EntrySignal
4.2 realtime_kline_service_base.py 改动
def _trigger_strategy_if_ready(self, ...):
# 提取 alt/base 最新 OHLCV(5m K线,供动量过滤器使用)
alt_ohlcv = None
base_ohlcv = None
period_key_5m = ('5m', '7d')
if period_key_5m in price_data_cache:
alt_klines_5m = price_data_cache[period_key_5m]['alt_klines']
if alt_klines_5m:
k = alt_klines_5m[0]
latest_alt_price = k.get('close')
alt_ohlcv = {
'close': k.get('close', 0.0),
'high': k.get('high', 0.0),
'low': k.get('low', 0.0),
'open': k.get('open', 0.0),
'volume': k.get('volume', 0.0),
}
base_klines_5m = price_data_cache[period_key_5m].get('base_klines', [])
if base_klines_5m:
k = base_klines_5m[0]
base_ohlcv = {
'close': k.get('close', 0.0),
'high': k.get('high', 0.0),
'low': k.get('low', 0.0),
'open': k.get('open', 0.0),
'volume': k.get('volume', 0.0),
}
acted = self._trading_orchestrator.process_analysis(
...,
alt_ohlcv=alt_ohlcv,
base_ohlcv=base_ohlcv,
)
4.3 线程安全
MomentumFilter 不持有锁,由 AdaptiveBollingerStrategy 的 _lock(已存在)统一保护。update() 和 check() 均在 _lock 持有范围内调用,无竞态风险。kline_time 去重在逻辑层面保证同一根 K 线不被重复追加。
4.4 日志格式(v4.0)
动量过滤(硬) | BTC|ETH | alt:Layer0-趋势机制: Hurst(DFA-2)=0.67>0.60 R²=0.92 → 均值回归假设失效
动量过滤(硬) | SOL|BTC | alt:Layer2-暴涨不做空: CUSUM+=3.82>=3.15 RS_vol=0.0028 量比=2.3 VWPM=0.0012 BTC压力→阈值×0.90 (隐式冷却≈31min)
动量过滤(硬) | ETH|BTC | base:Layer2-暴跌不做多: CUSUM-=2.61>=2.50 RS_vol=0.0031 量比=1.7 VWPM=-0.0009 | alt:Layer1-不追涨: ...
动量过滤(软+Spread) | SOL|BTC | alt:Layer1-不追跌: 过去30根净跌幅=-1.23% 阈值=-0.65% ER=0.72>=0.50 | Spread趋势(三重确认): ER=0.61 净位移=-2.1 ADF_p=0.18>=0.05 KPSS_p=0.02<=0.05
动量仲裁放行 | ETH|BTC | 单腿:alt:Layer1-不追涨: 过去30根净涨幅=1.05% 阈值=+0.80% ER=0.68>=0.60 | ADF+KPSS确认平稳(ADF_p=0.03<0.05 KPSS_p=0.12>0.05)→推翻拦截
动量仲裁放行 | ETH|BTC | 单腿:alt:Layer1-不追涨: ... | Spread无趋势→推翻拦截
4.5 配置层集成(v4.0 新增参数)
# Layer 0: Hurst DFA-2
TRADING_MOMENTUM_HURST_ENABLED=true
TRADING_MOMENTUM_HURST_LOOKBACK=60
TRADING_MOMENTUM_HURST_THRESHOLD=0.60
TRADING_MOMENTUM_HURST_R2_THRESHOLD=0.85 # v4.0 新增
# Layer 1: 持续趋势(ER 非对称)
TRADING_MOMENTUM_SUSTAINED_LOOKBACK=30
TRADING_MOMENTUM_SUSTAINED_BASE_THRESHOLD=0.008
TRADING_MOMENTUM_ER_THRESHOLD_LONG=0.60
TRADING_MOMENTUM_ER_THRESHOLD_SHORT=0.50
# Layer 2: 急动检测(RS + CUSUM + VWPM + BTC因子)
TRADING_MOMENTUM_RS_PERIOD=10
TRADING_MOMENTUM_CUSUM_DRIFT=0.5
TRADING_MOMENTUM_CUSUM_THRESHOLD_SPIKE_UP=3.5
TRADING_MOMENTUM_CUSUM_THRESHOLD_SPIKE_DOWN=2.5
TRADING_MOMENTUM_VOLUME_CONFIRM_RATIO=1.5
TRADING_MOMENTUM_VWPM_WINDOW=5 # v4.0 新增
TRADING_MOMENTUM_VWPM_CONFIRM_RATIO=0.8 # v4.0 新增
TRADING_MOMENTUM_MARKET_REF_SYMBOL=BTC # v4.0 新增
TRADING_MOMENTUM_BTC_STRESS_THRESHOLD=2.0 # v4.0 新增
TRADING_MOMENTUM_BTC_STRESS_FACTOR_MIN=0.7 # v4.0 新增
# Layer 3: Spread 仲裁(ADF + KPSS + ER)
TRADING_MOMENTUM_SPREAD_LOOKBACK=20
TRADING_MOMENTUM_SPREAD_ER_THRESHOLD=0.45
TRADING_MOMENTUM_SPREAD_NET_THRESHOLD=1.5
TRADING_MOMENTUM_SPREAD_ADF_PVALUE=0.05 # v4.0 修改
TRADING_MOMENTUM_SPREAD_KPSS_PVALUE=0.05 # v4.0 新增
# 针对高波动 symbol 示例
TRADING_STRATEGY_SOL_MOMENTUM_CUSUM_THRESHOLD_SPIKE_UP=4.0
TRADING_STRATEGY_BTC_MOMENTUM_HURST_THRESHOLD=0.65
5. 与当前交易风格的契合度分析
5.1 交易风格特征
| 维度 | 当前系统特征 |
|---|---|
| 策略类型 | 配对协整均值回归 |
| K 线周期 | 5 分钟 |
| 最大持仓时间 | 72 小时(max_hold_hours=72.0) |
| 入场逻辑 | adaptive_z 突破阈值(非连续信号,要求首次穿越) |
| 出场逻辑 | adaptive_z 回归至 entry_adaptive_z * reversion_factor |
| EMA/STD 参数 | ema_span=36, std_window=72 |
5.2 契合度分析(v4.0)
高度契合之处:
-
Layer 0 DFA-2 + R² 是最可靠的机制检测方案:DFA-2 消除了 DFA-1 在加速趋势市场的高估偏差;R² 质检确保硬拦截不会建立在不可靠的估计之上。这是 v3.0 中最关键的遗漏——硬拦截层必须有质量门控。
-
KPSS + ADF 对偶检验是计量经济学标准实践:单独使用 ADF 时,"不拒绝 H0"可能是真的非平稳,也可能是样本不足导致检验无力。加入 KPSS 后能区分这两种情况,Layer 3 仲裁的统计可靠性大幅提升。
-
BTC 跨资产因子反映加密市场结构:BTC dominance 使得 BTC 急动后 alt 跟随的概率极高。在 BTC CUSUM 高压时降低 alt 的检测阈值,是对市场微观结构的直接建模。
-
VWPM 量价确认比简单量比更有区分力:Kyle (1985) 模型表明价格+成交量的联合信号优于单独信号。VWPM 检查"量大且方向一致"而非仅"量大",能过滤掉假放量(对倒、做市)。
-
Rogers-Satchell + Per-symbol 基准 + 非对称参数(继承 v3.0):24/7 连续交易无隔夜跳空,RS 无漂移假设,per-symbol 基准消除跨品种干扰,非对称参数反映加密上下方动量差异。
设计张力(有意为之的权衡):
- Layer 0 用单腿 Hurst 做硬拦截,但单腿趋势不等于 spread 趋势。v4.0 中 R² 质检缓解了误拦截风险,但 Layer 0 仍是硬拦截不经 Layer 3 仲裁——这是保守但正确的:Hurst 反映长期记忆特性,即使 spread 暂时稳定,单腿趋势持续性意味着更高的发散风险。
- BTC 因子在 BTC 本身作为 base 腿时存在双重计算(base 腿检查 + BTC 因子),但因检查维度不同(反向动量 vs 市场压力),影响可控。
5.3 综合评分(v4.0 vs v3.0)
| 维度 | 评分 | vs v3.0 | 说明 |
|---|---|---|---|
| 算法先进性 | ★★★★★ | +0.5★ | DFA-2 + R² 质检 + KPSS 对偶 = state-of-the-art |
| 误杀控制 | ★★★★★ | +0.5★ | R² 防止 DFA 误杀,KPSS 增强仲裁可靠性 |
| 自适应能力 | ★★★★★ | 持平 | Per-symbol 基准继承 v3.0 |
| 非对称性 | ★★★★★ | 持平 | 继承 v3.0 |
| 市场微观结构 | ★★★★★ | +1★ | BTC 跨资产因子 + VWPM 量价确认 |
| 策略哲学一致性 | ★★★★★ | 持平 | Hurst 检测直接验证均值回归假设 |
| 时间尺度匹配 | ★★★★★ | 持平 | CUSUM 无固定窗口,ER/DFA 窗口可调 |
| 代码侵入性 | ★★★★★ | 持平 | 独立模块,可独立关闭 |
| 运行效率 | ★★★★☆ | 持平 | DFA-2 略慢于 DFA-1,KPSS 与 ADF 开销相当 |
| 统计严谨性 | ★★★★★ | +1★ | R² 质检 + ADF/KPSS 对偶 = 严格统计保证 |
6. 与四项开单约束的对应关系
6.1 逐项验证
约束 1:连续下跌不追跌
Layer 1 检测:ER >= er_threshold_short(0.50)AND net_return <= -adaptive_threshold
被阻止:direction == 'short'
原因:路径高效地持续下跌(ER 高),跌幅充分释放,继续做空空间有限且反弹风险高
仲裁:Layer 3 检查 spread 趋势性(ADF + KPSS + ER 三重)→ 确认平稳则推翻拦截放行
示例(5min,N=30,base_threshold=0.8%,RS 自适应后 threshold=0.65%):
- 过去 150 分钟稳步从 100 跌到 99(ER=0.72 >= 0.50, 净跌 1%)→ Layer 1 拦截
- Spread ADF p=0.22 + KPSS p=0.03 → 双重确认非平稳 → 维持拦截
- Spread ADF p=0.03 + KPSS p=0.12 → 双重确认平稳 → 推翻拦截,放行
- Spread ADF p=0.04 + KPSS p=0.03 → 矛盾 → 保守维持拦截
- 过去 150 分钟 V 形反转(100→96→99.2,ER=0.10 < 0.50)→ ER 不足,放行
约束 2:连续上涨不追涨
Layer 1 检测:ER >= er_threshold_long(0.60,更严格)AND net_return >= +adaptive_threshold
被阻止:direction == 'long'
示例:
- 过去 150 分钟稳步从 100 涨到 101.2(ER=0.85 >= 0.60, 净涨 1.2%)→ 拦截 → 仲裁
- 过去 150 分钟震荡漂移(ER=0.15 < 0.60, 净涨 0.9%)→ ER 不足,放行
约束 3:迅速暴涨不做空
Layer 2 检测:CUSUM_pos >= effective_cusum_thresh_up AND VWPM+量比联合确认
effective_cusum_thresh_up = 3.5 × stress_factor(BTC 因子调整)
被阻止:direction == 'short'
不可仲裁(硬拦截)
示例(RS_vol=0.3%,drift=0.5,BTC CUSUM=2.5 → stress_factor=0.95):
- 连续 3 根各涨 0.8%(z=2.67)→ CUSUM=6.5 > 3.33 → 拦截
- BTC 同时急涨(btc_stress=3.0)→ 阈值降至 3.5×0.9=3.15 → 更早拦截
- 单根涨 0.3% 但量极小(vol_ratio=0.3, VWPM≈0)→ CUSUM 低 + 量不足 → 放行
约束 4:迅速暴跌不做多
Layer 2 检测:CUSUM_neg >= effective_cusum_thresh_down AND VWPM+量比联合确认
被阻止:direction == 'long'
不可仲裁(硬拦截)
6.2 约束覆盖矩阵(v4.0)
| 约束 | 过滤层 | 触发指标 | 被阻止方向 | 检查维度 | 可仲裁 | 正确性 |
|---|---|---|---|---|---|---|
| 0. 趋势机制 | Layer 0 | Hurst(DFA-2)≥T AND R²≥0.85 | 双向 | alt+base | 否 | ✓ |
| 1. 连续下跌不追跌 | Layer 1 | ER≥T_short AND net_ret≤-T_adp | short | alt+base+spread | 是 | ✓ |
| 2. 连续上涨不追涨 | Layer 1 | ER≥T_long AND net_ret≥+T_adp | long | alt+base+spread | 是 | ✓ |
| 3. 迅速暴涨不做空 | Layer 2 | CUSUM+≥T_up×SF AND VWPM+量确认 | short | alt+base+BTC | 否 | ✓ |
| 4. 迅速暴跌不做多 | Layer 2 | CUSUM-≥T_down×SF AND VWPM+量确认 | long | alt+base+BTC | 否 | ✓ |
四项约束完全覆盖。v4.0 增强:Layer 0 R² 质检防止误拦截,Layer 2 BTC 因子扩展检测维度至跨资产,Layer 3 KPSS 对偶检验增强仲裁可靠性。
7. 参数配置指南
7.1 基础参数推荐值
Layer 0:Hurst DFA-2(v4.0 升级)
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
hurst_lookback |
60 | 30-100 | 用于 DFA-2 的回望根数(60根5min=5小时) |
hurst_threshold |
0.60 | 0.55-0.70 | H > 此值判定为趋势机制 |
hurst_r2_threshold |
0.85 | 0.75-0.95 | R² < 此值视为估计不可靠 → 放行 |
hurst_threshold 调优:
0.55:过于激进,均值回归行情下也可能触发(误杀多)
0.60:标准值,H=0.6 是弱趋势/随机游走的分界线
0.65+:宽松,只拦截强趋势(适合波动大、趋势明显的主流币)
hurst_lookback 调优:
30:短期记忆检测(约 2.5 小时),对快速趋势切换敏感
60:标准值(约 5 小时),兼顾稳定性和响应速度
90+:中长期机制检测,更稳定但滞后
hurst_r2_threshold 调优(v4.0 新增):
0.75:宽松,更多 Hurst 估计被采纳(假阳性风险增加)
0.85:标准值,平衡可靠性和可用性
0.95:严格,只有非常可靠的估计才被使用(可能导致 Layer 0 频繁失效)
建议:不低于 0.80,否则 DFA-2 的 R² 质检失去意义
Layer 1:持续趋势过滤
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
sustained_lookback |
30 | 10-60 | 净位移回望根数 |
sustained_base_threshold |
0.8% | 0.3%-2.0% | 净位移基准阈值(RS 自适应缩放) |
er_threshold_long |
0.60 | 0.45-0.75 | 做多拦截 ER 阈值(更严格) |
er_threshold_short |
0.50 | 0.35-0.65 | 做空拦截 ER 阈值(较宽松) |
er_threshold 非对称调优原则:
er_threshold_long 应高于 er_threshold_short(上涨更难均值回归)
推荐差值:0.08-0.12
激进配置(拦截更多):long=0.55, short=0.45
标准配置: long=0.60, short=0.50
保守配置(放行更多): long=0.65, short=0.55
v4.0 新增:数据驱动非对称参数推导方法
非对称参数应从历史数据中推导,而非仅凭经验。推荐的回测推导流程:
对每个方向 d ∈ {long, short},对每个候选阈值 T ∈ [0.30, 0.75]:
1. 在历史数据中筛选 ER >= T 且 direction == d 的所有入场信号
2. 计算这些信号的均值回归成功率:
P(success | ER >= T, direction=d)
3. 找到使成功率低于可接受水平(如 30%)的最小 T
→ 该 T 即为 er_threshold_{d} 的数据驱动推荐值
同理可推导 CUSUM 非对称阈值:
对每个方向,计算 P(成功均值回归 | CUSUM >= T, direction)
找到使成功率 < 30% 的最小 T
预期结果:
如果加密市场确实存在上下非对称性,数据驱动的 T_long 应 > T_short
这会给当前的经验值提供定量验证或修正
Layer 2:急动检测
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
rs_period |
10 | 5-20 | Rogers-Satchell EMA 周期 |
cusum_drift |
0.5 | 0.3-1.0 | CUSUM 漂移,越大越不灵敏 |
cusum_threshold_spike_up |
3.5 | 2.5-5.0 | 暴涨检测阈值,越大越宽松 |
cusum_threshold_spike_down |
2.5 | 2.0-4.0 | 暴跌检测阈值,越大越宽松 |
volume_confirm_ratio |
1.5 | 1.0-3.0 | 量比确认倍数 |
vwpm_window |
5 | 3-10 | VWPM 回望窗口(v4.0 新增) |
vwpm_confirm_ratio |
0.8 | 0.5-1.5 | VWPM/RS_vol 确认阈值(v4.0 新增) |
market_ref_symbol |
"BTC" | — | 跨资产参考 symbol(v4.0 新增) |
btc_stress_threshold |
2.0 | 1.5-3.0 | BTC CUSUM 压力启动阈值(v4.0 新增) |
btc_stress_factor_min |
0.7 | 0.5-0.9 | BTC 压力下 CUSUM 阈值最低缩放比(v4.0 新增) |
非对称 CUSUM 阈值调优:
spike_up > spike_down(暴涨做空风险 > 暴跌做多风险)
推荐差值:0.5-1.5
CUSUM 隐式冷却期:threshold/drift × 5min
spike_up 3.5/0.5 × 5 = 35min
spike_down 2.5/0.5 × 5 = 25min
ARL_0(平均误报间隔)≈ exp(2*drift*threshold) / (2*drift²)
drift=0.5, threshold=3.5 → ARL_0 ≈ 73 根(约 6 小时才会一次误报)
drift=0.5, threshold=2.5 → ARL_0 ≈ 20 根(约 1.7 小时才会一次误报)
VWPM 调优(v4.0 新增):
vwpm_window=3:短期量价方向检查,对单根异常量敏感
vwpm_window=5:标准值,覆盖 25 分钟,平衡稳定性和响应速度
vwpm_window=10:更稳定但滞后,适合低频策略
vwpm_confirm_ratio=0.5:宽松,轻微量价一致即确认
vwpm_confirm_ratio=0.8:标准值,需要明显的量价一致性
vwpm_confirm_ratio=1.5:严格,只有非常强的量价一致才确认
BTC 因子调优(v4.0 新增):
btc_stress_threshold=1.5:敏感,BTC 轻微急动即影响 alt 阈值
btc_stress_threshold=2.0:标准值,BTC 明显急动时才介入
btc_stress_threshold=3.0:宽松,仅在 BTC 极端急动时才介入
btc_stress_factor_min=0.5:激进,BTC 极端时 alt 阈值降至 50%
btc_stress_factor_min=0.7:标准值,alt 阈值最多降至 70%
btc_stress_factor_min=0.9:保守,BTC 因子影响有限
Layer 3:Spread 仲裁
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
spread_lookback |
20 | 10-40 | Spread ER / ADF / KPSS 回望根数 |
spread_er_threshold |
0.45 | 0.30-0.65 | Spread ER 阈值(ER 快速筛) |
spread_net_threshold |
1.5 | 0.5-3.0 | Spread 净位移阈值(z4h 单位) |
spread_adf_pvalue |
0.05 | 0.01-0.10 | ADF 显著性水平(v4.0 修改) |
spread_kpss_pvalue |
0.05 | 0.01-0.10 | KPSS 显著性水平(v4.0 新增) |
ADF + KPSS 联合参数调优(v4.0):
adf_pvalue 和 kpss_pvalue 建议设为相同值以保持对称性
两者均为 0.05:标准统计学显著性水平
→ ADF 需 p<0.05 才判定平稳,KPSS 需 p>0.05 才判定平稳
→ 四象限决策矩阵清晰
两者均为 0.10:更宽松,更多情况被判定为"确认"
→ 四象限中"矛盾/不确定"区间缩小
→ 适合激进风格
注意:ADF 和 KPSS 对小样本(<20)均有尺寸扭曲问题。
spread_lookback >= 20 时结果可靠。
样本不足时自动降级为纯 ER 判断。
7.2 回测验证建议
对比五组回测:
A 组:无动量过滤(当前基线)
B 组:v2.0(GK + 单一 ER 阈值 + ER-only Spread)
C 组:v3.0(RS + DFA-1 + 非对称阈值 + ADF+ER Spread)
D 组:v4.0(RS + DFA-2+R² + VWPM + BTC因子 + ADF+KPSS+ER)
E 组:v4.0 关闭各子功能(评估各改进项贡献)
E1: 关闭 R² 质检
E2: 关闭 VWPM(降级为 volume_ratio only)
E3: 关闭 BTC 因子
E4: 关闭 KPSS(降级为 ADF only)
核心对比指标:
- Sharpe Ratio(风险调整收益)
- 最大连续亏损次数
- 信号过滤率(被拦截信号 / 总信号)
- 误杀率(被拦截后实际盈利的信号占比)
- Layer 3 仲裁放行率(推翻 Layer 1 的比例)
- 分时段表现差异(亚盘/欧盘/美盘)
- Layer 0 触发频率(Hurst 拦截数 / 总信号数)
- Layer 0 R² 失效率(R² < 0.85 导致 Hurst 被跳过的比例)
预期结果(v4.0 相对 v3.0 的增量收益):
R² 质检贡献:减少 Layer 0 误拦截约 15-25%(短序列 Hurst 不可靠时放行)
DFA-2 贡献:减少加速趋势场景的 Hurst 高估约 10-20%
VWPM 贡献:减少假放量导致的 Layer 2 误拦截约 10-15%
BTC 因子贡献:在 BTC 领先 alt 联动时提前 5-15min 拦截,降低最大回撤 5-10%
KPSS 对偶贡献:Layer 3 仲裁准确率提升约 20-30%(消除 ADF 低功效问题)
8. 风险与局限性
8.1 已知局限
| 局限 | 说明 | 缓解措施 |
|---|---|---|
| DFA-2 数据预热期 | 需要约 60 根 K 线(5 小时)才有可靠的 Hurst 估计 | 启动时从 DB 回填近 80 根 K 线;预热期间 Layer 0 默认放行 |
| DFA-2 计算开销 | 二阶多项式去趋势(Cramer 求解 3×3 系统)比线性去趋势慢约 2-3x | 仅在 check() 调用时执行(非每根 K 线),5min 级别无性能压力 |
| R² 质检可能过于严格 | 60 根 K 线只有 5-7 个尺度点,R² 波动较大 | 阈值 0.85 经验证平衡了可靠性和可用性;可降至 0.80 |
| RS 数据预热期 | RS EMA 需 10 根,CUSUM 需稳定 RS 后才有意义(约 15 根≈75 分钟) | 启动时回填近 50 根 K 线 |
| KPSS 小样本问题 | spread_lookback < 20 时 KPSS 检验有尺寸扭曲 | spread_lookback >= 20(默认值),小样本时跳过 KPSS |
| Per-symbol 基准冷启动 | 各 symbol 的 _RollingMedian 需 5+ 样本才稳定 |
前 5 个样本使用 base_threshold 不缩放 |
| Hurst 对短序列敏感 | 60 根 5min K 线的 Hurst 标准误较大 | R² 质检缓解;hurst_lookback 可调高至 90-100 |
| Volume 数据质量 | HIP-3 资产或低流动性币 volume 可能不可靠 | volume 为 0 时跳过 VWPM + 量确认,仅依赖 CUSUM |
| BTC 因子时滞 | BTC CUSUM 更新与 alt 信号检查之间有最多 1 根 K 线(5min)延迟 | 5min 级别延迟可接受;BTC 急动通常持续多根 K 线 |
| ADF/KPSS statsmodels 依赖 | 若 statsmodels 未安装,Layer 3 降级为纯 ER 判断 | 项目已安装 statsmodels(v0.14.6),无需额外安装 |
| VWPM 对低量币不可靠 | 低流动性币的成交量可能不反映真实交易方向 | 仅在 vol_ema > 0 且有效 VWPM 数据时启用 |
8.2 不适合的场景
- 极低流动性资产:RS 波动率因价格跳空失真,Hurst 因稀疏 K 线估计不准,VWPM 因量不可靠失效,需配合黑名单
- HIP-3 稀疏资产:volume 不可靠时 Layer 2 VWPM+量价确认无法生效,仅靠 CUSUM 价格检测
8.3 后续迭代方向(v4.0 之后)
| 优先级 | 方向 | 说明 |
|---|---|---|
| P1 | 历史预填充 | 启动时从 DB 的 klines 表回填近 80 根 K 线,消除 Hurst/RS 预热等待 |
| P2 | CUSUM 自适应 drift | drift 随 RS 波动率动态调整,高波动期提高容忍度 |
| P2 | Hurst 滚动缓存 | Hurst 每 N 根新 K 线重算一次(而非每 tick),降低 DFA-2 计算开销 |
| P2 | 非对称参数回测推导 | 用 7.1 节描述的数据驱动方法从历史数据中推导最优 ER/CUSUM 非对称参数 |
| P3 | Shiryaev-Roberts 替代 CUSUM | SR 程序在某些理论意义下优于 CUSUM(Pollak & Tartakovsky 2008),可作为 CUSUM 的对照验证 |
| P3 | 多时间框架确认 | 同时检查 1h K 线的趋势性,避免 5min 级别噪音触发 Layer 0/1 |
| P3 | VPIN 替代 VWPM | 若获得 tick 级数据,用 Easley, Lopez de Prado & O'Hara (2012) 的 VPIN 替代 K 线级 VWPM |
| P4 | 分时段非对称参数 | 亚盘/欧盘/美盘使用不同 CUSUM drift(美盘适当放宽,亚盘收紧) |
| P4 | Bayesian Online Changepoint Detection | Adams & MacKay (2007) 的贝叶斯在线变点检测作为 Layer 0 的补充/替代 |
附录 A:算法学术参考
| 算法 | 原始论文 | 核心贡献 |
|---|---|---|
| DFA(Hurst 估计) | Peng et al. (1994) Physical Review Letters | 去趋势波动分析,对非平稳序列鲁棒的 Hurst 估计 |
| DFA-2(二阶去趋势) | Kantelhardt et al. (2002) Physica A | 二阶多项式去趋势消除抛物线型趋势偏差 |
| DFA in Finance | Mantegna & Stanley (1995) Nature | DFA 引入金融时间序列 |
| Hurst 原始提出 | Hurst, H.E. (1951) Trans. ASCE | 长程记忆与持续性概念奠基 |
| Kaufman Efficiency Ratio | Kaufman, P. (1995) Smarter Trading | 方向距离/路径长度衡量趋势效率 |
| CUSUM | Page, E.S. (1954) Biometrika | 序列变化检测的最优在线算法 |
| CUSUM 最优性证明 | Lorden, G. (1971) Ann. Math. Statist. | CUSUM 在平均检测延迟最小意义下一阶渐近最优 |
| Garman-Klass 波动率 | Garman, M. & Klass, M. (1980) J. Business | OHLC 波动率估计器,效率 7.4 倍(零漂移假设) |
| Rogers-Satchell 波动率 | Rogers, L. & Satchell, S. (1991) Ann. Appl. Prob. | 漂移不变 OHLC 波动率,效率 6.2 倍 |
| Yang-Zhang 波动率 | Yang, D. & Zhang, Q. (2000) J. Business | 结合 RS 与隔夜跳空(加密市场可直接用 RS) |
| 知情交易模型 | Kyle, A.S. (1985) Econometrica | 价格+成交量联合信号区分知情/噪声交易 |
| ADF 检验 | Dickey, D.A. & Fuller, W.A. (1979) JASA | 单位根检验,序列平稳性的标准统计工具 |
| KPSS 检验 | Kwiatkowski et al. (1992) J. Econometrics | 平稳性检验(H0=平稳),ADF 的对偶检验 |
| 时间序列动量 | Moskowitz, Ooi & Pedersen (2012) J. Financial Economics | 资产间动量溢出效应的系统性证据 |
| VPIN | Easley, Lopez de Prado & O'Hara (2012) Rev. Financial Studies | 知情交易概率的成交量同步估计 |
| Bayesian Changepoint | Adams & MacKay (2007) arXiv | 贝叶斯在线变点检测(未来 Layer 0 候选) |
| Shiryaev-Roberts | Pollak & Tartakovsky (2008) Ann. Statist. | CUSUM 的替代方案,某些条件下更优 |
| 配对交易理论 | Vidyamurthy, G. (2004) Pairs Trading | Spread 均值回归的系统性方法论 |
| 配对交易实证 | Gatev, E. et al. (2006) Rev. Financial Studies | 配对交易策略的大规模实证验证 |
附录 B:v3.0 → v4.0 改进对照
| 维度 | v3.0 | v4.0 | 改进原因 |
|---|---|---|---|
| DFA 阶数 | DFA-1(线性去趋势) | DFA-2(二阶多项式去趋势) | 消除加速趋势偏差(Kantelhardt 2002) |
| Hurst 质检 | 无 | R² ≥ 0.85 质检门控 | 防止短序列不可靠估计触发硬拦截 |
| 量价确认 | volume_ratio ≥ 1.5(简单量比) | VWPM + 量比联合确认 | 区分"量大"和"量大+方向一致"(Kyle 1985) |
| 跨资产因子 | 无 | BTC CUSUM 压力 → alt 阈值缩放 | 反映 BTC dominance 的跨资产动量溢出 |
| Spread 仲裁 | ADF + ER 双重 | ADF + KPSS + ER 三重 | KPSS 对偶消除 ADF 低功效问题(Kwiatkowski 1992) |
| ADF 显著性水平 | 0.10(仅 ADF 宽松) | 0.05(ADF+KPSS 对偶标准值) | 双检验互补,可用更严格标准 |
| 参数推导 | 经验值 | 新增数据驱动推导方法 | 从历史数据的条件概率推导最优非对称参数 |
| check_spread 注释 | "仅在 Layer 0/1 软拦截时调用"(Layer 0 是硬拦截,注释有误) | "仅在 Layer 1 软拦截时调用" | 修正文档错误 |
| 架构图执行顺序 | Layer 0→1→2→3(与代码不一致) | Layer 0→2→1→3(硬拦截优先) | 与代码实际执行顺序一致 |
| 新增参数数量 | — | 7 个新参数 | hurst_r2, vwpm_window, vwpm_confirm, market_ref, btc_stress_thresh, btc_stress_min, kpss_pvalue |
| _RollingMedian 文档 | "O(N log N) 可接受" | 增加双堆优化路径说明 | 未来可扩展性 |
文档结束