refactor(experiment): 重构模型保存机制,支持 processors 持久化

- 模型保存路径改为 models/{model_type}/ 目录结构
- save_model_with_factors 新增 fitted_processors 参数
- 新增 load_processors 函数加载处理器状态
- Storage 查询排序优化:ORDER BY ts_code, trade_date
This commit is contained in:
2026-03-19 21:06:11 +08:00
parent 0a29506f45
commit ccd42082c2
8 changed files with 1705 additions and 81 deletions

View File

@@ -0,0 +1,441 @@
# 因子回看一致性测试问题报告
**测试日期:** 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 | 低 |

View File

@@ -0,0 +1,310 @@
# 因子计算一致性问题分析报告
## 概述
本报告分析 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