# 因子回看一致性测试问题报告 **测试日期:** 2026-03-19 **测试文件:** `tests/debug/test_lookback_consistency.py::test_simple_factor_consistency` **测试结果:** FAILED - 33个因子不一致 --- ## 1. 测试概述 ### 1.1 测试目的 验证不同 LOOKBACK_DAYS(回看窗口)设置下,同一预测日期范围的因子值是否一致。如果结果不一致,可能存在以下问题: 1. **数据泄露**:因子计算使用了超出合理回看期的历史数据 2. **设计缺陷**:某些因子天然依赖更长的历史数据(如累积和因子) 3. **边界效应**:滚动窗口在数据边界处的处理差异 ### 1.2 测试配置 | 参数 | 值 | 说明 | |-----|-----|-----| | LOOKBACK_2Y | 1095天 (3年) | 较短回看窗口 | | LOOKBACK_3Y | 1460天 (4年) | 较长回看窗口 | | PREDICT_START | 20250101 | 预测起始日期 | | PREDICT_END | 20250131 | 预测结束日期 | | 2Y实际数据范围 | 20220102 - 20250131 | 3年数据 | | 3Y实际数据范围 | 20210102 - 20250131 | 4年数据 | ### 1.3 测试结果摘要 ``` 数据集形状: 2Y 回看: (96761, 241) 3Y 回看: (96761, 241) 总因子数: 191 一致因子数: 158 不一致因子数: 33 (17.3%) ``` --- ## 2. 不一致因子详细分类 ### 2.1 分类标准 | 类别 | 标准 | 风险等级 | |-----|-----|---------| | 浮点精度差异 | max_diff < 1e-6 | 低 - 可忽略 | | 边界效应差异 | 1e-6 <= max_diff < 0.01 | 中 - 需关注 | | 数值显著差异 | 0.01 <= max_diff < 0.1 | 高 - 需修复 | | 严重不一致 | max_diff >= 0.1 或 inf/nan | 极高 - 必须修复 | ### 2.2 严重不一致因子(必须修复) | 因子名称 | 最大差异 | 平均差异 | 差异数据点 | 问题类型 | |---------|---------|---------|-----------|---------| | GTJA_alpha005 | inf | inf | 2170 | -inf值产生 | | GTJA_alpha113 | 0.803 | 0.108 | 95337 | 累积和历史依赖 | | 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_alpha148 | 1.000 | 1e-5 | 1 | cs_rank+ts_min边界 | | GTJA_alpha176 | inf | inf | 74 | 除零/无穷大 | #### 2.2.1 GTJA_alpha005(-inf值问题) ``` 差异数据点示例: idx=56: 2Y=-0.0000000262, 3Y=-inf, diff=inf idx=57: 2Y=-0.0000000262, 3Y=-inf, diff=inf idx=58: 2Y=-0.4564354646, 3Y=-inf, diff=inf ``` **问题分析:** - 3Y模式下产生-inf值,说明存在除零或对负数取对数 - DSL中可能包含 `log` 或 `sqrt` 操作 #### 2.2.2 GTJA_alpha113(累积和历史依赖) ``` 差异数据点示例: idx=0: 2Y=0.1848, 3Y=0.9883, diff=0.8035 idx=1: 2Y=-0.1146, 3Y=-0.9890, diff=0.8744 idx=2: 2Y=0.1098, 3Y=0.9783, diff=0.8684 ``` **问题分析:** - 包含 `ts_delay(close, 5)` 导致数据偏移 - `ts_corr(close, vol, 2)` 只有2日窗口,对边界敏感 #### 2.2.3 GTJA_alpha138(复杂嵌套) ``` 差异数据点示例: idx=3: 2Y=0.9808, 3Y=0.8380, diff=0.1429 idx=4: 2Y=0.9782, 3Y=0.8354, diff=0.1429 idx=5: 2Y=0.9738, 3Y=0.6880, diff=0.2857 idx=6: 2Y=0.9780, 3Y=0.4066, diff=0.5714 idx=7: 2Y=0.5586, 3Y=0.1300, diff=0.4286 ``` **问题分析:** - 5层嵌套:`cs_rank → ts_decay_linear → ts_delta → ts_rank → ts_corr → ts_rank` - `ts_decay_linear` 使用 `numpy.convolve`,结果依赖输入序列长度 - `ts_rank` 使用 `sliding_window_view`,对数据起始点敏感 #### 2.2.4 GTJA_alpha176(无穷大值) ``` 差异数据点示例: idx=948: 2Y=-0.9146, 3Y=-0.9146, diff=0.0000000001 idx=949: 2Y=-0.8809, 3Y=-0.8809, diff=0.0000000002 (差异本身很小,但3Y模式下产生了inf值) ``` **问题分析:** - 存在除零操作导致inf值 - 需要添加epsilon保护 --- ### 2.3 数值显著差异因子(需修复) | 因子名称 | 最大差异 | 平均差异 | 差异数据点 | 可能原因 | |---------|---------|---------|-----------|---------| | GTJA_alpha016 | 0.021 | 2.3e-6 | 336 | cs_rank嵌套 | | GTJA_alpha032 | 0.605 | 3.9e-5 | 8341 | ts_sum嵌套cs_rank | | GTJA_alpha077 | 0.580 | 0.00013 | 36577 | cs_rank+ts_decay_linear | | GTJA_alpha091 | 0.204 | 4.2e-6 | 2147 | cs_rank嵌套max_ | | GTJA_alpha121 | 0.296 | 0.00457 | 2412 | ts_rank嵌套ts_corr | | GTJA_alpha130 | 0.613 | 2.9e-5 | 358 | ts_rank+ts_decay_linear | | GTJA_alpha141 | 0.629 | 0.00011 | 28256 | cs_rank+ts_corr | --- ### 2.4 边界效应差异因子(需关注) | 因子名称 | 最大差异 | 平均差异 | 差异数据点 | 问题类型 | |---------|---------|---------|-----------|---------| | GTJA_alpha042 | 0.00027 | 1.8e-7 | 214 | ts_std嵌套 | | GTJA_alpha062 | 1.0e-6 | 0.0 | 579 | ts_corr嵌套 | | GTJA_alpha064 | 0.501 | 0.00022 | 74838 | ts_decay_linear嵌套 | | GTJA_alpha070 | 2.3e-6 | 8.1e-9 | 58440 | ts_std(amount) | | GTJA_alpha074 | 0.00837 | 8.5e-7 | 241 | cs_rank+ts_corr | | GTJA_alpha104 | 0.00028 | 2.7e-8 | 381 | ts_delta+ts_std | | GTJA_alpha119 | 0.160 | 1.5e-5 | 4051 | cs_rank+ts_decay_linear | --- ### 2.5 浮点精度差异因子(可忽略) | 因子名称 | 最大差异 | 平均差异 | 差异数据点 | |---------|---------|---------|-----------| | volatility_5 | 4.3e-9 | 0.0 | 906 | | volatility_ratio | 6.0e-10 | 0.0 | 96 | | volatility_squeeze_5_60 | 2.0e-10 | 0.0 | 16 | | turnover_deviation | 2.0e-9 | 0.0 | 37 | | GTJA_alpha083 | 9.3e-5 | 1.9e-9 | 2 | | GTJA_alpha139 | 2.0e-10 | 0.0 | 12 | | GTJA_alpha179 | 1.9e-4 | 5.9e-9 | 4 | | GTJA_alpha191 | 5.6e-9 | 0.0 | 1010 | --- ### 2.6 NaN模式不一致因子(需关注) | 因子名称 | 2Y NaN数 | 3Y NaN数 | 差异 | |---------|---------|---------|------| | GTJA_alpha005 | 704 | 600 | -104 | | GTJA_alpha028 | 87678 | 89155 | +1477 | | GTJA_alpha111 | 29410 | 35516 | +6106 | | GTJA_alpha113 | 294 | 282 | -12 | | GTJA_alpha164 | 29410 | 35516 | +6106 | --- ## 3. 根因分析 ### 3.1 问题分类 #### 3.1.1 设计缺陷型(无法修复,只能排除) 以下因子天然依赖从数据起始点开始的累积计算,**不应该**在不同回看期下产生相同结果: | 因子 | DSL公式 | 问题 | |-----|--------|-----| | GTJA_alpha165 | `ts_sumac(close-ts_mean(close,48))` | 累积和从数据起点开始 | | GTJA_alpha183 | `ts_sumac(close-ts_mean(close,24))` | 累积和从数据起点开始 | > 注:本次测试中这两个因子未出现,但之前测试中差异巨大(14万级别) #### 3.1.2 数值稳定性问题(需要修复) | 问题类型 | 相关因子 | 修复方案 | |---------|---------|---------| | 除零导致inf | GTJA_alpha005, GTJA_alpha176 | 添加epsilon保护 | | 负数取对数 | GTJA_alpha005 | 检查log输入是否为正 | | 负数取平方根 | 待查 | 检查sqrt输入 | #### 3.1.3 滚动窗口边界效应(需要优化) 以下模式会导致不同回看期下边界数据点不同: ```python # ts_rank 实现 def rank_calc(s: pl.Series) -> pl.Series: values = s.to_numpy() n = len(values) windows = np.lib.stride_tricks.sliding_window_view(values, window) # 从第 window 个元素开始产生有效值 # 不同起始点导致滑动窗口内容不同 ``` ```python # ts_decay_linear 实现 # 使用 numpy.convolve,输出依赖输入序列长度 ``` #### 3.1.4 cs_rank截面排名差异(需要优化) `cs_rank` 对截面数据进行排名,不同回看期下: - 有效数据点数量不同 - 排名时的百分位数分母不同 --- ## 4. 修复建议 ### 4.1 立即修复(高优先级) #### 4.1.1 排除问题因子 将以下因子加入排除列表: ```python EXCLUDED_FACTORS = [ # 设计缺陷 - 累积和因子 "GTJA_alpha165", # ts_sumac 累积和历史依赖 "GTJA_alpha183", # ts_sumac 累积和历史依赖 # 数值稳定性问题 "GTJA_alpha005", # 产生-inf值 "GTJA_alpha176", # 产生inf值 ] ``` #### 4.1.2 修复数值稳定性 **修复位置:** `src/factors/translator.py` ```python # 为除法操作添加 epsilon 保护 def _safe_divide(numerator, denominator, epsilon=1e-10): return numerator / (denominator + epsilon) # 为 log 函数添加输入检查 def _safe_log(x, epsilon=1e-10): return log(x + epsilon) # 确保输入为正 # 为 sqrt 函数添加输入检查 def _safe_sqrt(x, epsilon=1e-10): return sqrt(abs(x)) # 确保输入非负 ``` ### 4.2 中期优化(中优先级) #### 4.2.1 优化 ts_rank 边界处理 **当前问题:** - `sliding_window_view` 从第 `window` 个元素开始产生有效值 - 不同起始点导致初始NaN数量不同 **优化方案:** ```python # 使用动态起始点对齐 def ts_rank_aligned(expr, window): # 确保不同回看期产生相同起始点 pass ``` #### 4.2.2 优化 cs_rank 排名基准 **当前问题:** - 排名时使用滑动窗口内的元素 - 不同回看期下窗口内元素不同 **优化方案:** ```python def cs_rank_aligned(expr): # 使用固定的排名基准 # 例如:固定使用当日全部股票进行排名 pass ``` ### 4.3 长期改进(低优先级) #### 4.3.1 建立因子分类体系 ```python class FactorCategory(Enum): """因子分类""" TIME_SERIES_ROLLING = "ts_rolling" # 滚动窗口型(对回看期不敏感) TIME_SERIES_CUMULATIVE = "ts_cumulative" # 累积型(对回看期敏感) CROSS_SECTIONAL = "cs_rank" # 截面型(对回看期敏感) HYBRID = "hybrid" # 混合型 ``` #### 4.3.2 增强一致性测试 ```python def test_factor_consistency_threshold(): """测试因子一致性阈值""" THRESHOLDS = { "float_precision": 1e-6, # 浮点精度差异可接受 "boundary_effect": 0.01, # 边界效应差异需关注 "significant": 0.1, # 显著差异需修复 } ``` --- ## 5. 影响评估 ### 5.1 对模型训练的影响 | 风险等级 | 因子数量 | 影响 | |---------|---------|-----| | 低 | 8 | 浮点精度差异,不影响训练 | | 中 | 16 | 边界效应,可能轻微影响 | | 高 | 8 | 显著不一致,会影响模型 | | 极高 | 1 | inf/nan值,必须修复 | ### 5.2 对回测的影响 **关键问题:** - 训练时使用3年回看,预测时用4年回看 → 因子值不一致 - 可能导致回测结果与实盘表现不符 **建议:** - 训练和预测使用相同的LOOKBACK_DAYS配置 - 或在模型中记录回看期设置 --- ## 6. 测试代码分析 ### 6.1 测试逻辑 ```python def compute_simple_factors(lookback_days: int) -> pl.DataFrame: actual_start = get_lookback_start_date(PREDICT_START, lookback_days) # 从 actual_start 开始加载数据 # 计算因子 # 过滤到 PREDICT_START 之后的日期 return data ``` ### 6.2 比较逻辑 ```python def compare_factor_values(data_2y, data_3y, feature_cols): # 使用 np.allclose(valid_2y, valid_3y, rtol=1e-10, atol=1e-10) # rtol=1e-10 相对容差 # atol=1e-10 绝对容差 ``` ### 6.3 断言 ```python assert results["inconsistent_factors"] == 0, ( f"发现 {results['inconsistent_factors']} 个简单因子在不同 LOOKBACK_DAYS 下结果不一致" ) ``` --- ## 7. 结论 ### 7.1 关键发现 1. **33个因子(17.3%)在不同回看期下结果不一致** 2. **其中8个因子存在严重差异(max_diff >= 0.1 或 inf/nan)** 3. **问题根源包括:设计缺陷、数值稳定性、边界效应** ### 7.2 修复优先级 | 优先级 | 因子 | 行动 | |-------|-----|-----| | P0 | GTJA_alpha005, GTJA_alpha176 | 排除(产生inf值) | | P1 | GTJA_alpha113, GTJA_alpha138, GTJA_alpha140, GTJA_alpha146 | 修复数值稳定性 | | P2 | 其他16个显著差异因子 | 优化滚动窗口处理 | | P3 | 8个浮点精度因子 | 可忽略 | ### 7.3 后续步骤 1. **立即**:更新 `SELECTED_FACTORS` 排除问题因子 2. **本周**:修复 `translator.py` 中的数值稳定性问题 3. **本月**:建立因子分类和一致性测试体系 --- ## 附录:完整不一致因子列表 | 序号 | 因子名称 | 最大差异 | 平均差异 | 差异数据点 | 风险等级 | |-----|---------|---------|---------|-----------|---------| | 1 | volatility_5 | 4.3e-09 | 0.0 | 906 | 低 | | 2 | volatility_ratio | 6.0e-10 | 0.0 | 96 | 低 | | 3 | volatility_squeeze_5_60 | 2.0e-10 | 0.0 | 16 | 低 | | 4 | turnover_deviation | 2.0e-09 | 0.0 | 37 | 低 | | 5 | GTJA_alpha005 | inf | inf | 2170 | 极高 | | 6 | GTJA_alpha016 | 0.021 | 2.3e-06 | 336 | 高 | | 7 | GTJA_alpha032 | 0.605 | 3.9e-05 | 8341 | 高 | | 8 | GTJA_alpha042 | 0.00027 | 1.8e-07 | 214 | 中 | | 9 | GTJA_alpha062 | 1.0e-06 | 0.0 | 579 | 中 | | 10 | GTJA_alpha064 | 0.501 | 0.00022 | 74838 | 高 | | 11 | GTJA_alpha070 | 2.3e-06 | 8.1e-09 | 58440 | 中 | | 12 | GTJA_alpha074 | 0.00837 | 8.5e-07 | 241 | 中 | | 13 | GTJA_alpha077 | 0.580 | 0.00013 | 36577 | 高 | | 14 | GTJA_alpha083 | 9.3e-05 | 1.9e-09 | 2 | 低 | | 15 | GTJA_alpha090 | 0.021 | 1.5e-06 | 416 | 高 | | 16 | GTJA_alpha091 | 0.204 | 4.2e-06 | 2147 | 高 | | 17 | GTJA_alpha104 | 0.00028 | 2.7e-08 | 381 | 中 | | 18 | GTJA_alpha105 | nan | nan | 591 | 极高 | | 19 | GTJA_alpha113 | 0.803 | 0.108 | 95337 | 极高 | | 20 | GTJA_alpha114 | nan | nan | 11 | 极高 | | 21 | GTJA_alpha115 | 0.989 | 0.0014 | 83182 | 极高 | | 22 | GTJA_alpha119 | 0.160 | 1.5e-05 | 4051 | 高 | | 23 | GTJA_alpha121 | 0.296 | 0.00457 | 2412 | 高 | | 24 | GTJA_alpha130 | 0.613 | 2.9e-05 | 358 | 高 | | 25 | GTJA_alpha138 | 0.857 | 0.108 | 21689 | 极高 | | 26 | GTJA_alpha139 | 2.0e-10 | 0.0 | 12 | 低 | | 27 | GTJA_alpha140 | 0.999 | 0.029 | 7535 | 极高 | | 28 | GTJA_alpha141 | 0.629 | 0.00011 | 28256 | 高 | | 29 | GTJA_alpha146 | 3.719 | 0.0058 | 81526 | 极高 | | 30 | GTJA_alpha148 | 1.000 | 1.0e-05 | 1 | 极高 | | 31 | GTJA_alpha176 | inf | inf | 74 | 极高 | | 32 | GTJA_alpha179 | 0.00019 | 5.9e-09 | 4 | 低 | | 33 | GTJA_alpha191 | 5.6e-09 | 0.0 | 1010 | 低 |