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

311 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 因子计算一致性问题分析报告
## 概述
本报告分析 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 差异巨大(数万级别)
**原因:**
```python
# 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 行):
```python
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 行):
```python
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. **排除高风险因子**
```python
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 处理是否正确
- 防止未来数据泄露
## 测试建议
### 新增测试用例
```python
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