(✅)改进:开源证券_VCF_多尺度量价背离检测因子!
原因子( 2025.7.14 2025.7.14 2025.7.14)
代码实现
def factor(df):
# 计算主动买入量占比
df['buy_ratio'] = df['taker_buy_volume'] / (df['volume'] + 1e-7)
# 计算价格波动率
price_change = df['close'].pct_change()
volatility = price_change.ewm(span=96, min_periods=24).std()
# 计算量价比率
vp_ratio = df['volume'] / (volatility + 1e-7)
# 关键周期定义
periods = [96, 288, 672, 1440, 2880]
# 计算各周期量价比率
vp_matrix = np.column_stack([
vp_ratio.ewm(span=p, min_periods=int(p/4)).mean()
for p in periods
])
# === 核心改进3: 多尺度量价背离检测 ===
divergence_matrix = np.zeros((len(df), len(periods)))
for i, p in enumerate(periods):
# 价格动量
price_momentum = df['close'].pct_change(p)
# 成交量动量
volume_momentum = df['volume'].pct_change(p)
# 资金流动量
flow_momentum = df['buy_ratio'].diff(p)
# 量价背离检测
price_volume_div = np.sign(price_momentum) * np.sign(volume_momentum) < 0
# 价资背离检测
price_flow_div = np.sign(price_momentum) * np.sign(flow_momentum) < 0
# 综合背离强度
div_strength = (price_volume_div.astype(int) + price_flow_div.astype(int)) * np.abs(flow_momentum)
# 在尾部区域放大背离信号
is_tail = (df['buy_ratio'] < 0.25)
div_strength = np.where(is_tail, div_strength * 1.5, div_strength)
divergence_matrix[:, i] = div_strength
# 组合特征矩阵
combined_matrix = np.concatenate((vp_matrix, divergence_matrix), axis=1) # 使用背离矩阵替代协同矩阵
# 滚动分位数标准化
scaled_std = np.zeros_like(combined_matrix)
for i in range(combined_matrix.shape[1]):
col = pd.Series(combined_matrix[:, i])
rolling_q20 = col.rolling(window=2880, min_periods=720).quantile(0.20)
rolling_q80 = col.rolling(window=2880, min_periods=720).quantile(0.80)
scaled_std[:, i] = (col - rolling_q20) / (rolling_q80 - rolling_q20 + 1e-7)
# 最终因子计算 (尾部敏感加权)
# 在尾部区域增加背离信号的权重
tail_weight = np.where(df['buy_ratio'] < 0.25, 1.5, 1.0)
factor = np.mean(scaled_std, axis=1) * np.log1p(df['volume']) * df['buy_ratio'] * tail_weight
return factor
因子表现
📊 单币种 (single) 详细评估结果:
--------------------------------------------------
📈 平稳性检验 (ADF):
p_value: 0.000000
是否平稳: 是
🔗 相关性分析:
IC (Pearson): 0.015414
Rank_IC (Spearman): 0.016373
📊 信息比率:
IR: 0.244797
问题分析
深入分析因子构建逻辑的不足之处
1. 背离检测逻辑存在根本性缺陷
- 问题核心:背离检测基于
np.sign(price_momentum) * np.sign(other_momentum) < 0
,但未考虑动量强度- 仅依赖符号会导致弱动量与强动量被同等对待(例如:价格变动0.01%与成交量变动50%被视为有效背离)
- 未设置动量阈值过滤,大量噪音信号被纳入(如微小价格波动+随机成交量波动)
- 量纲不一致:
price_momentum
使用百分比变化(无量纲)volume_momentum
使用百分比变化(无量纲)flow_momentum
却使用原始差分(有量纲),导致三者不可直接比较
- 尾部放大机制失真:
div_strength = ... * np.abs(flow_momentum)
在buy_ratio<0.25
时放大,但:- 低buy_ratio时段本身flow_momentum绝对值较小(分母效应)
- 放大操作反而可能强化无效信号
2. 特征工程结构性问题
- 量价比率(vp_ratio)构建不合理:
vp_ratio = df['volume'] / (volatility + 1e-7)
- 分子
volume
具有时间累积性,分母volatility
是瞬时波动率,两者时间尺度不匹配 - 未考虑成交量自身的波动特性(应使用成交量波动率而非原始volume)
- 分子
- 多周期处理方式粗糙:
vp_matrix = np.column_stack([vp_ratio.ewm(span=p...]) divergence_matrix = np.zeros((len(df), len(periods)))
- 所有周期采用相同权重,未考虑市场周期特性(如:短周期应更高频更新)
- 未处理周期间的共线性问题(如96与288周期高度相关)
3. 标准化方法导致前瞻性偏差
- 滚动分位数标准化缺陷:
rolling_q20 = col.rolling(window=2880).quantile(0.20) scaled_std = (col - rolling_q20) / (rolling_q80 - rolling_q20)
- 窗口长度2880(≈30天)在加密货币市场过长,无法适应快速波动
- 未进行边界处理:当
rolling_q80 ≈ rolling_q20
时产生数值爆炸(尽管有1e-7但治标不治本) - 滚动窗口在时间序列末端使用未来信息(需改为expanding或固定滞后)
4. 最终因子合成逻辑冲突
- 相互矛盾的权重设计:
factor = mean(scaled_std) * log1p(volume) * buy_ratio * tail_weight
log1p(volume)
强化大成交量时段buy_ratio
强化主动买入时段- 但两者常呈负相关(大成交量往往伴随buy_ratio回归均值)
tail_weight
在低buy_ratio区二次放大,形成逻辑闭环但缺乏实证支持
5. 市场机制未充分考量
- 加密货币特性缺失:
- 未处理“鲸鱼地址”的影响(大单导致的瞬时buy_ratio跳变)
- 忽略交易所API的taker数据可靠性问题(部分交易所包含自成交)
- 未考虑山寨币联动效应(BTC主导行情时的传导关系)
- 波动率计算缺陷:
volatility = price_change.ewm(span=96).std()
- 使用固定span=96(24小时)未适应市场波动率聚类特征
- 应引入GARCH类模型或波动率回归调整
6. 计算效率与实现风险
- 循环矩阵操作低效:
for i, p in enumerate(periods): # 5周期循环 divergence_matrix[:, i] = ... # 向量化操作可提速10倍+
- 对每个周期独立计算未利用矩阵并行运算
- 滚动分位数循环
for i in range(combined_matrix.shape[1])
更应避免
- 内存消耗隐患:
- 创建
vp_matrix
(5列),divergence_matrix
(5列),combined_matrix
(10列)等中间矩阵 - 在长时序(如ETH 15m 4年数据≈140,160条)消耗超2GB内存
- 创建
7. 理论依据薄弱环节
- 量价背离假设待验证:
- 传统市场“价涨量缩→反转”规律在加密货币市场有效性存疑
- 未区分牛市/熊市中的背离信号差异(如牛市中的无量上涨可持续)
- 尾部强化机制缺乏实证:
buy_ratio<0.25
作为尾部区域的阈值设定随意- 未验证低buy_ratio区间的信号预测能力是否确实更强
改进方向建议(暂不实施)
重构背离检测:
- 改用动态阈值:
|price_momentum| > σ(noise) 且 sign(price) != sign(volume)
- 引入自适应动量强度权重:
div_strength = tanh(|Δprice|) * tanh(|Δvolume|) * sign_conflict
- 改用动态阈值:
优化特征工程:
# 建议替代方案 vp_ratio = volume_std / (volatility * typical_price) # 引入典型价格归一化 divergence_score = MACD(price) * MACD(volume) < 0 # 基于指标协同性
改进标准化方法:
# 动态滚动窗口 + 稳健标准化 window = min(1440, int(22.5 / volatility)) # 波动率自适应窗口 scaled = (x - median) / IQR # 使用四分位距抗异常值
因子合成逻辑调整:
# 分层加权方案 alpha = entropy_weights(vp_matrix) # 信息熵赋权 beta = decay_weights(divergence_matrix) # 时间衰减赋权 factor = dot(alpha, vp_scaled) + dot(beta, div_scaled)
该因子核心问题在于试图用复杂结构弥补基础逻辑缺陷。建议回归市场微观结构本质,重点攻关:订单流不平衡度 + 波动率异常检测 + 多尺度市场情绪合成三个核心维度。当前方案中约60%的代码(尤其是矩阵操作部分)可被更简洁的物理意义明确的方法替代。
尝试对以上问题进行了改进。然而无果、效果都不怎么样。
具体分析:因子头尾部收益率表现反常
1. 背离信号的方向性冲突(核心问题)
背离检测逻辑:
price_volume_div = np.sign(price_momentum) * np.sign(volume_momentum) < 0 # 量价背离 price_flow_div = np.sign(price_momentum) * np.sign(flow_momentum) < 0 # 价资背离
- 此逻辑将所有类型的背离都标记为正向信号(背离强度值 > 0)
- 但实际包含两种相反的市场信号:
- 看涨背离(价格↓ + 资金流↑):预示底部反转
- 看跌背离(价格↑ + 资金流↓):预示顶部反转
尾部放大机制的副作用:
is_tail = (df['buy_ratio'] < 0.25) div_strength = np.where(is_tail, div_strength * 1.5, div_strength) # 尾部区域放大
- 在尾部区域(
buy_ratio < 0.25
),同时放大了看涨和看跌背离信号 - 导致因子头部同时包含:
- 真实的顶部看跌背离(应做空)
- 被放大的底部看涨背离(应做多)
- 在尾部区域(
2. 标准化过程的特征混淆
- 混合标准化问题:
combined_matrix = np.concatenate((vp_matrix, divergence_matrix), axis=1) scaled_std = ... # 对组合矩阵统一标准化 factor = np.mean(scaled_std, axis=1) # 取均值
- 将量价趋势特征(vp_matrix)和背离特征(divergence_matrix)合并后标准化
- 两类特征的经济含义相反:
vp_matrix
代表动量强度(值越大越看涨)divergence_matrix
代表反转强度(值越大反转风险越高)
- 在头部区域,高动量值和高背离值相互抵消,导致因子无法明确表达市场方向
3. 尾部权重的自我冲突
- 重复放大逻辑:
tail_weight = np.where(df['buy_ratio'] < 0.25, 1.5, 1.0) # 二次放大 factor = ... * tail_weight
- 在背离检测阶段已对尾部区域放大1.5倍(
div_strength * 1.5
) - 最终因子又乘以相同的尾部权重(
tail_weight
) - 导致尾部区域的噪声信号被双重放大,而核心信号(资金流方向)被弱化
- 在背离检测阶段已对尾部区域放大1.5倍(
4. 资金流信号的未被充分利用
- buy_ratio的线性使用:
factor = ... * df['buy_ratio'] # 线性乘以资金流
- 在头部区域(因子值最大处),高因子值可能由以下组成:
- 高buy_ratio(看涨) + 高看跌背离(看跌)
- 在尾部区域(因子值最小处):
- 低buy_ratio(看跌) + 高看涨背离(看涨)
- 线性相乘无法解决这种内在矛盾
- 在头部区域(因子值最大处),高因子值可能由以下组成:
5. 极端值区域的逻辑矛盾
因子头部(最大值区域):
成分 期望信号 实际信号 量价趋势(vp_matrix) 看涨 ✅ 看涨 背离信号 - ❌ 同时含多空 buy_ratio 看涨 ✅ 看涨 综合效果 看涨 方向模糊 因子尾部(最小值区域):
成分 期望信号 实际信号 量价趋势(vp_matrix) 看跌 ✅ 看跌 背离信号 - ❌ 同时含多空 buy_ratio 看跌 ✅ 看跌 综合效果 看跌 方向模糊
根本原因总结
- 信号方向冲突:背离检测未区分看涨/看跌类型,导致头部混入看跌信号,尾部混入看涨信号
- 特征标准化混淆:动量特征(vp_matrix)与反转特征(divergence_matrix)被合并处理,模糊经济含义
- 尾部过度放大:双重放大机制强化了噪声而非有效信号
- 资金流未主导:在极端区域,buy_ratio未能主导因子方向,被冲突信号抵消
这些设计缺陷导致因子在极端值区域失去方向性预测能力:头部可能包含真实看跌信号,尾部可能包含真实看涨信号,与预期收益方向相反。
头尾分析
因子值大小的分析
1. 因子值较小时的情况(低因子值)
低成交量环境:
np.log1p(volume)
在成交量极低时趋近于 0,直接压制因子值。- 例如:市场流动性枯竭时(如深夜交易时段或低波动横盘期),因子值显著减小。
买方力量弱(低 buy_ratio):
buy_ratio
直接作为乘数,当taker_buy_volume
占比低时(如持续卖方主导),因子值减小。- 例外:若同时满足尾部条件(见下文),
tail_weight
可能部分抵消,但整体仍偏小。
标准化特征值低迷:
scaled_std
均值低表明:- VP 比率(
volume/volatility
)处于历史低位(滚动分位数标准化后接近 0)。 - 背离信号弱(
divergence_matrix
值小),例如:- 价格动量与成交量/资金流同向(无背离)。
- 资金流动量变化(
flow_momentum
)绝对值小。
- VP 比率(
非尾部且波动率适中:
- 当
buy_ratio
未触发尾部阈值(即buy_ratio > dynamic_tail_threshold
),且vol_rank
处于中间区间(0.3-0.7),此时tail_weight=1
,无放大效应。
- 当
2. 因子值较大时的情况(高因子值)
高成交量 + 买方主导:
np.log1p(volume)
和buy_ratio
双高时,因子值被显著推升。- 例如:突破行情中放量上涨(高
buy_ratio
+ 高volume
)。
标准化特征值强劲:
scaled_std
均值高表明:- VP 比率处于历史高位(滚动分位数标准化后接近 3)。
- 强背离信号(
divergence_matrix
值大),例如:- 价格上涨但成交量萎缩(
price_volume_div
为真)。 - 价格上涨但资金流入减少(
price_flow_div
为真)。 - 资金流动量变化(
flow_momentum
)绝对值大。
- 价格上涨但成交量萎缩(
尾部条件触发且波动率适配:
- 当
buy_ratio < dynamic_tail_threshold
(尾部)时:- 低波动市场(
vol_rank < 0.3
):阈值收紧至 0.20,更容易触发尾部,且tail_weight
最大(1.8 - 0.6 * 低 vol_rank
),因子值显著放大。 - 高波动市场(
vol_rank > 0.7
):阈值放宽至 0.30,触发尾部后tail_weight
仍较高(约 1.32),但放大效应弱于低波动环境。
- 低波动市场(
- 例如:恐慌性抛售(
buy_ratio
极低)但波动率处于历史低位时,因子值可能异常高。
- 当
可能遗漏的反常情况(头尾部失效风险)
1. 尾部失效场景
高波动市场中的假尾部:
- 当市场突然转向高波动(如黑天鹅事件),
vol_rank
需 168 根 K 线才更新,导致:- 实际波动率高,但
vol_rank
仍显示低位 → 错误使用低波动阈值(0.20)。 - 结果:本应宽松的尾部阈值被收紧,正常
buy_ratio
被误判为尾部,过度放大因子值。
- 实际波动率高,但
- 反常表现:因子值虚高,但实际市场处于无序状态(无有效背离)。
- 当市场突然转向高波动(如黑天鹅事件),
低波动市场中的尾部漏判:
- 若
buy_ratio
略高于动态阈值(如 0.21),但处于极低波动环境:- 尾部条件未触发(
tail_weight=1
),但此时轻微买方力量减弱可能预示反转。 - 反常表现:因子值未充分放大,错过底部信号。
- 尾部条件未触发(
- 若
2. 标准化滞后导致的分位数扭曲
滚动窗口(2800 根 K 线)的滞后性:
- 市场结构突变时(如牛市转熊市),历史分位数(20%/80%)无法及时调整:
- 旧牛市的高 VP 比率被用作标准化基准 → 新熊市中 VP 比率被错误压缩(
scaled_std
偏低)。 - 反常表现:因子值在熊市初期系统性偏低,即使出现有效背离。
- 旧牛市的高 VP 比率被用作标准化基准 → 新熊市中 VP 比率被错误压缩(
- 市场结构突变时(如牛市转熊市),历史分位数(20%/80%)无法及时调整:
自适应阈值失效:
- 当
rolling_q80 - rolling_q20 < 1e-5
时,使用0.01 * 滚动平均绝对值
替代分母:- 在超窄区间震荡市中,分母可能被压至极低 → 标准化值
scaled_col
异常波动。 - 反常表现:因子值在无趋势市场中随机跳变(假信号)。
- 在超窄区间震荡市中,分母可能被压至极低 → 标准化值
- 当
3. 成交量与波动率的极端耦合
超高波动 + 超高成交量(如闪崩):
vp_ratio = volume / volatility
可能因分子分母同增而保持常态。- 但
log1p(volume)
极大,掩盖背离信号不足的问题 → 因子值虚高。 - 风险:在流动性危机中,因子误判为强势。
超低波动 + 超低成交量(如假期行情):
vp_ratio
计算的分母趋近于 0 → 数值不稳定。- 标准化时滚动分位数可能失效(因
min_periods=168
未满足)。 - 风险:因子值随机波动,失去预测性。
建议改进方向
动态阈值平滑:
- 对
dynamic_tail_threshold
增加波动率变化率的条件(如volatility.pct_change()
),避免市场突变时的误判。
- 对
标准化窗口自适应:
- 缩短滚动窗口(如 2800 → 1000)或引入指数加权分位数,提高对市场结构变化的敏感度。
尾部条件优化:
- 在
tail_weight
计算中加入成交量过滤(如仅当volume > 滚动中位数
时生效),避免低流动性环境下的噪声放大。
- 在
极端值熔断:
- 当
volatility < 1e-5
或volume < 1e-5
时,暂停因子计算,防止数值不稳定。
- 当
通过上述分析,建议在回测中重点关注 高波动环境下的尾部信号 和 市场结构突变期的标准化过程,这两者是头尾部失效的高发区。可借助 evaluator.run_full_evaluation()
输出的分位数转换图(Quintile Transition)和 IC 衰减曲线进一步验证。
改不出来了。操!( 2025.7.16 2025.7.16 2025.7.16)
代码实现(硬上弓)
def factor(df):
# 原始因子计算(保持不变)
df['buy_ratio'] = df['taker_buy_volume'] / (df['volume'] + 1e-7)
price_change = df['close'].pct_change()
volatility = price_change.ewm(span=96, min_periods=24).std()
vp_ratio = df['volume'] / (volatility + 1e-7)
periods = [96, 288, 672, 1440, 2880]
vp_matrix = np.column_stack([
vp_ratio.ewm(span=p, min_periods=int(p/4)).mean()
for p in periods
])
# === 改进点:波动率动态尾部阈值 ===
# 计算波动率分位数
vol_rank = volatility.rolling(window=672, min_periods=168).rank(pct=True)
# 动态调整尾部阈值
dynamic_tail_threshold = np.where(
vol_rank < 0.3,
0.20, # 低波动市场收紧阈值
np.where(vol_rank > 0.7, 0.30, 0.25) # 高波动市场放宽阈值
)
divergence_matrix = np.zeros((len(df), len(periods)))
for i, p in enumerate(periods):
price_momentum = df['close'].pct_change(p)
volume_momentum = df['volume'].pct_change(p)
flow_momentum = df['buy_ratio'].diff(p)
price_volume_div = np.sign(price_momentum) * np.sign(volume_momentum) < 0
price_flow_div = np.sign(price_momentum) * np.sign(flow_momentum) < 0
div_strength = (price_volume_div.astype(int) + price_flow_div.astype(int)) * np.abs(flow_momentum)
# 使用动态尾部阈值
is_tail = (df['buy_ratio'] < dynamic_tail_threshold)
div_strength = np.where(is_tail, div_strength * (1.8 - vol_rank * 0.6), div_strength)
divergence_matrix[:, i] = div_strength
combined_matrix = np.concatenate((vp_matrix, divergence_matrix), axis=1)
# 标准化(保持不变)
scaled_std = np.zeros_like(combined_matrix)
window = 2800
min_periods = 168
for i in range(combined_matrix.shape[1]):
col = pd.Series(combined_matrix[:, i])
rolling_q20 = col.shift(1).rolling(window=window, min_periods=min_periods).quantile(0.20)
rolling_q80 = col.shift(1).rolling(window=window, min_periods=min_periods).quantile(0.80)
denominator = rolling_q80 - rolling_q20
adaptive_threshold = 0.01 * col.abs().rolling(288, min_periods=72).mean()
denominator = np.where(denominator < 1e-5, adaptive_threshold, denominator)
scaled_col = (col - rolling_q20) / (denominator + 1e-7)
scaled_col = np.clip(scaled_col, -3, 3)
scaled_std[:, i] = scaled_col
# 最终因子计算(使用动态尾部权重)
tail_weight = np.where(df['buy_ratio'] < dynamic_tail_threshold,
(2 - vol_rank * 0.5),
1.0)
factor = (np.mean(scaled_std, axis=1) * np.log1p(df['volume']) * df['buy_ratio'] * tail_weight).values
factor = np.where(factor < 0.76, np.nan, factor)
return factor
因子表现
📊 单币种 (single) 详细评估结果:
--------------------------------------------------
📈 平稳性检验 (ADF):
p_value: 0.000000
是否平稳: 是
🔗 相关性分析:
IC (Pearson): 0.020159
Rank_IC (Spearman): 0.023357
📊 信息比率:
IR: 0.450116
有效分组数: 10
📊 因子分布:
📋 数据概况:
数据长度: 112865
因子列: factor
收益率列: future_return
未来收益周期: 26
--------------------------------------------------
🖼️ 单币种 (single) 图片展示:
----------------------------------------
评价
其实是把尾部硬截掉的。因为实在没办法了。
(然后其实是把预测时间周期从10提到26的。没什么道理,纯粹硬凑。
唉,还是菜啊
救命😭😭