(✅)改进_开源证券_VCF_多尺度量价背离检测因子!

发布于:2025-07-20 ⋅ 阅读:(12) ⋅ 点赞:(0)

原因子( 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

收益率
分布
散点图
累积IC, Rank_IC

问题分析

深入分析因子构建逻辑的不足之处

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区间的信号预测能力是否确实更强

改进方向建议(暂不实施)

  1. 重构背离检测

    • 改用动态阈值:|price_momentum| > σ(noise) 且 sign(price) != sign(volume)
    • 引入自适应动量强度权重:div_strength = tanh(|Δprice|) * tanh(|Δvolume|) * sign_conflict
  2. 优化特征工程

    # 建议替代方案
    vp_ratio = volume_std / (volatility * typical_price)  # 引入典型价格归一化
    divergence_score = MACD(price) * MACD(volume) < 0  # 基于指标协同性
    
  3. 改进标准化方法

    # 动态滚动窗口 + 稳健标准化
    window = min(1440, int(22.5 / volatility))  # 波动率自适应窗口
    scaled = (x - median) / IQR  # 使用四分位距抗异常值
    
  4. 因子合成逻辑调整

    # 分层加权方案
    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
    • 导致尾部区域的噪声信号被双重放大,而核心信号(资金流方向)被弱化

4. 资金流信号的未被充分利用

  • buy_ratio的线性使用
    factor = ... * df['buy_ratio']  # 线性乘以资金流
    
    • 在头部区域(因子值最大处),高因子值可能由以下组成:
      • 高buy_ratio(看涨) + 高看跌背离(看跌)
    • 在尾部区域(因子值最小处):
      • 低buy_ratio(看跌) + 高看涨背离(看涨)
    • 线性相乘无法解决这种内在矛盾

5. 极端值区域的逻辑矛盾

  • 因子头部(最大值区域)

    成分 期望信号 实际信号
    量价趋势(vp_matrix) 看涨 ✅ 看涨
    背离信号 - ❌ 同时含多空
    buy_ratio 看涨 ✅ 看涨
    综合效果 看涨 方向模糊
  • 因子尾部(最小值区域)

    成分 期望信号 实际信号
    量价趋势(vp_matrix) 看跌 ✅ 看跌
    背离信号 - ❌ 同时含多空
    buy_ratio 看跌 ✅ 看跌
    综合效果 看跌 方向模糊

根本原因总结

  1. 信号方向冲突:背离检测未区分看涨/看跌类型,导致头部混入看跌信号,尾部混入看涨信号
  2. 特征标准化混淆:动量特征(vp_matrix)与反转特征(divergence_matrix)被合并处理,模糊经济含义
  3. 尾部过度放大:双重放大机制强化了噪声而非有效信号
  4. 资金流未主导:在极端区域,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)绝对值小。
  • 非尾部且波动率适中

    • 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 偏低)。
      • 反常表现:因子值在熊市初期系统性偏低,即使出现有效背离。
  • 自适应阈值失效

    • rolling_q80 - rolling_q20 < 1e-5 时,使用 0.01 * 滚动平均绝对值 替代分母:
      • 在超窄区间震荡市中,分母可能被压至极低 → 标准化值 scaled_col 异常波动。
      • 反常表现:因子值在无趋势市场中随机跳变(假信号)。
3. 成交量与波动率的极端耦合
  • 超高波动 + 超高成交量(如闪崩):

    • vp_ratio = volume / volatility 可能因分子分母同增而保持常态。
    • log1p(volume) 极大,掩盖背离信号不足的问题 → 因子值虚高。
    • 风险:在流动性危机中,因子误判为强势。
  • 超低波动 + 超低成交量(如假期行情):

    • vp_ratio 计算的分母趋近于 0 → 数值不稳定。
    • 标准化时滚动分位数可能失效(因 min_periods=168 未满足)。
    • 风险:因子值随机波动,失去预测性。

建议改进方向

  1. 动态阈值平滑

    • dynamic_tail_threshold 增加波动率变化率的条件(如 volatility.pct_change()),避免市场突变时的误判。
  2. 标准化窗口自适应

    • 缩短滚动窗口(如 2800 → 1000)或引入指数加权分位数,提高对市场结构变化的敏感度。
  3. 尾部条件优化

    • tail_weight 计算中加入成交量过滤(如仅当 volume > 滚动中位数 时生效),避免低流动性环境下的噪声放大。
  4. 极端值熔断

    • volatility < 1e-5volume < 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) 图片展示:
----------------------------------------

收益率
分布
散点图
累积IC, Rank_IC

评价

其实是把尾部硬截掉的。因为实在没办法了。
(然后其实是把预测时间周期从10提到26的。没什么道理,纯粹硬凑。
唉,还是菜啊

救命😭😭


网站公告

今日签到

点亮在社区的每一天
去签到