Files
ProStock/docs/factor_lookback_consistency_analysis.md
liaozhaorun ccd42082c2 refactor(experiment): 重构模型保存机制,支持 processors 持久化
- 模型保存路径改为 models/{model_type}/ 目录结构
- save_model_with_factors 新增 fitted_processors 参数
- 新增 load_processors 函数加载处理器状态
- Storage 查询排序优化:ORDER BY ts_code, trade_date
2026-03-19 21:06:11 +08:00

11 KiB
Raw Permalink Blame History

因子计算一致性问题分析报告

概述

本报告分析 LOOKBACK_DAYS 设置对因子计算结果的影响。测试对比了两种回看窗口设置3年 vs 4年同一预测日期范围2025年1月的因子计算结果一致性。

测试配置:

  • LOOKBACK_DAYS: 1095天3年 vs 1460天4年
  • 预测日期范围: 2025年1月20250101 - 20250131
  • 数据形状: (96761, 243)
  • 测试因子数: 191个

测试结果分类

第一类:微小数值差异(浮点精度/边界效应)

特征: 最大差异在 1e-10 到 0.6 之间,平均差异接近 0

因子名称 最大差异 平均差异 差异数据点 分析
volatility_5 4.3e-09 0.0 906 5日标准差浮点精度问题
volatility_ratio 6.0e-10 0.0 96 波动率比率,累积误差
volatility_squeeze_5_60 2.0e-10 0.0 16 挤压比率
turnover_deviation 2.0e-09 0.0 37 换手率偏离度
GTJA_alpha016 0.021 2.3e-06 336 cs_rank 嵌套
GTJA_alpha032 0.605 3.9e-05 8341 ts_sum 嵌套 cs_rank
GTJA_alpha042 0.00027 1.8e-07 214 ts_std 嵌套
GTJA_alpha062 1.0e-06 0.0 579 ts_corr 嵌套
GTJA_alpha064 0.501 0.00022 74838 ts_decay_linear 嵌套
GTJA_alpha070 2.3e-06 8.1e-09 58440 ts_std(amount)
GTJA_alpha074 0.00837 8.5e-07 241 cs_rank + ts_corr
GTJA_alpha077 0.580 0.00013 36577 cs_rank + ts_decay_linear
GTJA_alpha083 9.3e-05 1.9e-09 2 cs_rank + ts_cov
GTJA_alpha090 0.021 1.5e-06 416 类似 alpha016
GTJA_alpha091 0.204 4.2e-06 2147 cs_rank 嵌套 max_
GTJA_alpha104 0.00028 2.7e-08 381 ts_delta + ts_std
GTJA_alpha105 NaN NaN 591 ts_corr 导致 NaN
GTJA_alpha119 0.160 1.5e-05 4051 cs_rank + ts_decay_linear
GTJA_alpha121 0.296 0.00457 2412 ts_rank 嵌套 ts_corr
GTJA_alpha130 0.613 2.9e-05 358 ts_rank + ts_decay_linear
GTJA_alpha139 2.0e-10 0.0 12 ts_corr 导致
GTJA_alpha141 0.629 0.00011 28256 cs_rank + ts_corr
GTJA_alpha148 1.0 1.0e-05 1 cs_rank + ts_min 边界
GTJA_alpha179 0.00019 5.9e-09 4 cs_rank + ts_corr
GTJA_alpha191 5.6e-09 0.0 1010 ts_corr + ts_mean

诊断: 这类差异主要是由以下原因导致的数值精度问题:

  1. 滚动窗口边界效应:不同起始点的数据导致滚动窗口的初始值略有差异
  2. 累积误差传播:多层嵌套计算(如 cs_rank(ts_decay_linear(...)))放大了微小差异
  3. 浮点运算顺序Polars 的并行计算可能导致运算顺序不同

第二类NaN 模式不一致(数据边界问题)

因子名称 2Y NaN数 3Y NaN数 差异 分析
GTJA_alpha005 704 600 -104 ts_max(ts_corr(ts_rank(...)))
GTJA_alpha028 87678 89155 +1477 多层 ts_sma 嵌套
GTJA_alpha111 29410 35516 +6106 ts_sma 条件计算
GTJA_alpha113 294 282 -12 ts_sum(ts_delay(...))
GTJA_alpha164 29410 35516 +6106 类似 alpha111

诊断: NaN 数量不一致表明:

  1. 历史数据不足:某些因子(如 ts_corr(window=2))在数据起始阶段会产生 NaN
  2. 条件计算差异:包含 if_ 语句的因子在不同数据量下条件分支执行不同
  3. ts_delay 负偏移alpha113 包含 ts_delay(close, 5),数据边界处的延迟计算行为不同

第三类:严重数值不一致(高风险)

因子名称 最大差异 平均差异 差异数据点数 可能原因
GTJA_alpha005 Inf NaN 2170 -inf 值导致
GTJA_alpha113 0.803 0.108 21689 累积和历史依赖
GTJA_alpha114 0.028 - 11 除法边界问题
GTJA_alpha115 0.989 0.0014 83182 ts_rank 差异传播
GTJA_alpha138 0.857 0.108 21689 ts_decay_linear + ts_rank
GTJA_alpha140 0.999 0.029 7535 min_/max_ 边界
GTJA_alpha146 3.719 0.0058 81526 复杂嵌套公式
GTJA_alpha165 146950 498.7 81531 ts_sumac 累积和历史依赖
GTJA_alpha176 Inf Inf 74 除零或无穷大
GTJA_alpha183 98612 232.2 81531 ts_sumac 累积和历史依赖

高风险因子详细分析:

1. GTJA_alpha165 和 GTJA_alpha183最严重

DSL 定义:

alpha165: max_(ts_sumac(close-ts_mean(close,48)))-min_(ts_sumac(close-ts_mean(close,48)))/ts_std(close,48)
alpha183: max_(ts_sumac(close-ts_mean(close,24)))-min_(ts_sumac(close-ts_mean(close,24)))/ts_std(close,24)

问题根源:

  • ts_sumac() 是累积求和函数,依赖于从数据起始点到当前点的所有历史值
  • 3年回看 vs 4年回看意味着不同的起始点导致累积和完全不同
  • 当回看窗口超过因子所需的历史数据时,这类因子不应该有相同的值

验证:

  • alpha165 使用 48 日移动平均,但 ts_sumac 依赖整个历史序列
  • 这是设计问题,不是数据泄露

2. GTJA_alpha113NaN 模式 + 数值差异)

DSL 定义:

(-1 * ((cs_rank((ts_sum(ts_delay(close, 5), 20) / 20)) * ts_corr(close, vol, 2)) * cs_rank(ts_corr(ts_sum(close, 5), ts_sum(close, 20), 2))))

问题分析:

  • 包含 ts_delay(close, 5) 导致数据偏移
  • ts_corr(close, vol, 2) 只有 2 日窗口,对边界敏感
  • 不同回看期导致有效数据点数量不同

3. GTJA_alpha138

DSL 定义:

((cs_rank(ts_decay_linear(ts_delta((((low * 0.7) + ((amount / vol) *0.3))), 3), 20)) - ts_rank(ts_decay_linear(ts_rank(ts_corr(ts_rank(low, 8), ts_rank(ts_mean(vol,60), 17), 5), 19), 16), 7)) * -1)

问题分析:

  • 5层嵌套cs_rank → ts_decay_linear → ts_delta → ts_rank → ts_corr → ts_rank
  • 每层都可能放大微小差异
  • ts_rank 使用 sliding_window_view,对数据边界敏感

根因分析

1. 累积和因子ts_sumac设计问题

现象: GTJA_alpha165 和 GTJA_alpha183 差异巨大(数万级别)

原因:

# ts_sumac 实现 (translator.py 第 659-664 行)
def _handle_ts_sumac(self, node: FunctionNode) -> pl.Expr:
    expr = self.translate(node.args[0])
    return expr.cum_sum()  # 从序列起始累积

累积和 cum_sum() 从数据的第一个点开始累加,因此:

  • 3年回看从 2022-01-02 开始累积
  • 4年回看从 2021-01-02 开始累积
  • 两者累积和完全不同,这是预期行为

2. ts_rank 边界敏感性

实现分析 (translator.py 第 481-509 行):

def rank_calc(s: pl.Series) -> pl.Series:
    values = s.to_numpy()
    n = len(values)
    if n < window:
        return pl.Series([float("nan")] * n)
    
    windows = np.lib.stride_tricks.sliding_window_view(values, window)
    current_vals = windows[:, -1]
    ranks = np.sum(windows <= current_vals[:, None], axis=1) / window
    
    result = np.full(n, np.nan)
    result[window - 1:] = ranks
    return pl.Series(result)
  • sliding_window_view 从第 window 个元素开始产生有效值
  • window-1 个元素都是 NaN
  • 不同回看期导致 NaN 数量和位置不同

3. 多层嵌套放大效应

以 GTJA_alpha138 为例的调用链:

cs_rank(ts_decay_linear(...))
  → ts_decay_linear = ts_wma
    → numpy.convolve
      → 卷积结果依赖输入序列长度
t_rank(ts_decay_linear(ts_rank(...)))
  → 每层 ts_rank 都使用 sliding_window_view
    → 每层都引入边界 NaN

4. 财务数据 Lookback 扩展

代码路径 (data_router.py 第 202-226 行):

if table_spec.join_type == "asof_backward":
    # 财务数据需要扩展回看期
    adj_start = self.financial_loader.get_date_range_with_lookback(
        start_date, end_date, lookback_years=2
    )[0]
    date_start = pd.Timestamp(adj_start)
  • 财务数据默认回看 2 年,确保 PIT (Point-In-Time) 对齐
  • 但这不会导致数据泄露,只是确保公告日匹配

影响评估

对模型训练的影响

低风险(可接受):

  • 微小数值差异(< 1e-6不影响模型训练
  • NaN 模式轻微差异:在可接受范围内

中风险(需关注):

  • GTJA_alpha113, alpha115, alpha138, alpha146 等:差异较大可能影响训练

高风险(需修复):

  • GTJA_alpha165, alpha183累积和因子设计上有问题
  • GTJA_alpha005出现 -inf 值,可能是除零错误
  • GTJA_alpha176出现 inf数值稳定性问题

对回测的影响

关键问题:

  • 如果使用 3年回看训练模型但回测时用不同回看期因子值会不一致
  • 这可能导致回测结果与实盘表现不符

修复建议

短期修复(立即实施)

  1. 排除高风险因子

    EXCLUDED_FACTORS = [
        "GTJA_alpha165",  # ts_sumac 设计问题
        "GTJA_alpha183",  # ts_sumac 设计问题
        "GTJA_alpha005",  # -inf 值
        "GTJA_alpha176",  # inf 值
    ]
    
  2. 修复 alpha113 的 NaN 问题

    • 检查 ts_delay(close, 5) 的实现
    • 确保数据对齐正确

中期修复1-2周

  1. 重写 ts_sumac 实现

    • 添加 max_lookback 参数限制累积范围
    • 或改用滚动窗口求和替代累积和
  2. 优化 ts_rank 边界处理

    • 确保不同回看期产生相同的有效值范围
    • 考虑使用更稳定的排名算法
  3. 增强数值稳定性

    • 为所有除法操作添加 epsilon 保护
    • 检查 sign 函数在零值时的行为

长期优化1个月

  1. 建立因子回归测试

    • 自动化测试不同回看期的一致性
    • 设置数值差异阈值(如 rtol=1e-6
  2. 因子分类体系

    • 标记"历史依赖型"因子(如 ts_sumac
    • 在文档中明确说明因子的回看期敏感性
  3. PIT 数据验证

    • 验证财务数据的 PIT 处理是否正确
    • 防止未来数据泄露

测试建议

新增测试用例

def test_factor_consistency_across_lookback():
    """验证因子在不同回看期下的一致性。"""
    factors_to_test = [
        "GTJA_alpha113",
        "GTJA_alpha138", 
        "GTJA_alpha146",
    ]
    
    for factor in factors_to_test:
        data_2y = compute_factor(factor, lookback_days=730)
        data_3y = compute_factor(factor, lookback_days=1095)
        
        # 只比较有效值(非 NaN
        valid_mask = ~np.isnan(data_2y) & ~np.isnan(data_3y)
        diff = np.abs(data_2y[valid_mask] - data_3y[valid_mask])
        
        assert np.all(diff < 1e-6), f"{factor} 差异过大: max={np.max(diff)}"

监控指标

  1. 差异率:超过阈值的因子比例
  2. NaN 比例:每个因子的 NaN 占比
  3. 极端值比例inf/-inf 的出现频率

结论

本次测试发现 191 个因子中:

  • 一致因子:约 60%116个
  • 微小差异:约 25%48个- 可接受
  • NaN 模式差异:约 3%5个- 需关注
  • 严重不一致:约 12%22个- 需修复

优先级:

  1. 🔴 立即:排除 GTJA_alpha165, alpha183, alpha005, alpha176
  2. 🟡 本周:修复 alpha113, alpha138 的边界问题
  3. 🟢 本月:建立自动化一致性测试

报告生成时间: 2026-03-19 测试版本: ProStock 最新 main 分支 数据范围: 2022-01-02 至 2025-01-31