- 分析GTJA_alpha032等因子在不同LOOKBACK_DAYS下的差异来源 - 验证cs_rank嵌套和截面股票数量对结果的影响 - 测试ts_rank NaN处理和除法除零修复
289 lines
11 KiB
Python
289 lines
11 KiB
Python
"""
|
||
验证问题2.3因子差异根本原因的测试
|
||
|
||
理论分析:
|
||
1. GTJA_alpha032: cs_rank(high)和cs_rank(vol)是截面排名
|
||
- 不同LOOKBACK_DAYS下载面股票数量可能不同(3Y包含更多历史股票)
|
||
- cs_rank = rank/count,count不同导致排名分母不同
|
||
|
||
2. GTJA_alpha077: ts_decay_linear + cs_rank
|
||
- ts_decay_linear使用np.convolve,边界效应
|
||
- cs_rank嵌套导致排名基准变化
|
||
|
||
3. GTJA_alpha121: ts_rank嵌套ts_corr
|
||
- ts_rank使用滑动窗口,边界敏感
|
||
- ts_corr的边界效应叠加
|
||
|
||
验证方法:直接计算2Y和3Y数据下的截面股票数量差异
|
||
"""
|
||
|
||
import polars as pl
|
||
from datetime import datetime, timedelta
|
||
from src.factors import FactorEngine
|
||
import numpy as np
|
||
|
||
|
||
# =============================================================================
|
||
# 配置
|
||
# =============================================================================
|
||
PREDICT_START = "20250101"
|
||
PREDICT_END = "20250131"
|
||
LOOKBACK_2Y = 365 * 3
|
||
LOOKBACK_3Y = 365 * 4
|
||
|
||
|
||
def get_lookback_start_date(start_date: str, lookback_days: int) -> str:
|
||
start_dt = datetime.strptime(start_date, "%Y%m%d")
|
||
lookback_dt = start_dt - timedelta(days=lookback_days)
|
||
return lookback_dt.strftime("%Y%m%d")
|
||
|
||
|
||
def verify_cross_section_stock_count():
|
||
"""
|
||
验证:不同LOOKBACK_DAYS下载面股票数量是否不同
|
||
|
||
关键问题:如果3Y数据包含更多历史股票,那么在相同日期D,
|
||
2Y和3Y的截面股票数量可能不同,导致cs_rank分母不同
|
||
"""
|
||
print("=" * 80)
|
||
print("验证截面股票数量差异")
|
||
print("=" * 80)
|
||
|
||
# 加载2Y数据
|
||
actual_start_2y = get_lookback_start_date(PREDICT_START, LOOKBACK_2Y)
|
||
engine_2y = FactorEngine()
|
||
data_2y = engine_2y.compute(
|
||
factor_names=["close"],
|
||
start_date=actual_start_2y,
|
||
end_date=PREDICT_END,
|
||
)
|
||
data_2y = data_2y.filter(data_2y["trade_date"] >= PREDICT_START)
|
||
|
||
# 加载3Y数据
|
||
actual_start_3y = get_lookback_start_date(PREDICT_START, LOOKBACK_3Y)
|
||
engine_3y = FactorEngine()
|
||
data_3y = engine_3y.compute(
|
||
factor_names=["close"],
|
||
start_date=actual_start_3y,
|
||
end_date=PREDICT_END,
|
||
)
|
||
data_3y = data_3y.filter(data_3y["trade_date"] >= PREDICT_START)
|
||
|
||
# 统计截面股票数量
|
||
stocks_per_date_2y = (
|
||
data_2y.group_by("trade_date")
|
||
.agg(pl.col("ts_code").count().alias("stock_count"))
|
||
.sort("trade_date")
|
||
)
|
||
|
||
stocks_per_date_3y = (
|
||
data_3y.group_by("trade_date")
|
||
.agg(pl.col("ts_code").count().alias("stock_count"))
|
||
.sort("trade_date")
|
||
)
|
||
|
||
print("\n2Y数据 - 每天截面股票数量:")
|
||
print(stocks_per_date_2y)
|
||
|
||
print("\n3Y数据 - 每天截面股票数量:")
|
||
print(stocks_per_date_3y)
|
||
|
||
# 比较差异
|
||
comparison = stocks_per_date_2y.join(
|
||
stocks_per_date_3y, on="trade_date", suffix="_3y"
|
||
).with_columns(
|
||
[(pl.col("stock_count_3y") - pl.col("stock_count")).alias("count_diff")]
|
||
)
|
||
|
||
print("\n股票数量差异 (3Y - 2Y):")
|
||
print(comparison)
|
||
|
||
diff_count = comparison.filter(pl.col("count_diff") != 0).height
|
||
print(f"\n有差异的日期数: {diff_count}")
|
||
if diff_count > 0:
|
||
print("结论:2Y和3Y的截面股票数量确实不同!")
|
||
else:
|
||
print("结论:2Y和3Y的截面股票数量相同,cs_rank分母应该一致")
|
||
|
||
|
||
def verify_cs_rank_formula_behavior():
|
||
"""
|
||
验证cs_rank在不同count下的行为
|
||
|
||
场景:如果2Y截面有3000只股票,3Y截面有3100只股票
|
||
假设股票X的rank都是1500(中间位置)
|
||
- 2Y: cs_rank = 1500/3000 = 0.5
|
||
- 3Y: cs_rank = 1500/3100 ≈ 0.484
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print("验证 cs_rank 在不同 count 下的行为")
|
||
print("=" * 80)
|
||
|
||
# 模拟:相同rank但不同count的情况
|
||
df = pl.DataFrame(
|
||
{
|
||
"trade_date": ["20250101"] * 2,
|
||
"ts_code": ["001", "002"],
|
||
"rank": [1500.0, 1500.0], # 相同rank
|
||
"count_2y": [3000, 3000], # 2Y count
|
||
"count_3y": [3100, 3100], # 3Y count
|
||
}
|
||
)
|
||
|
||
result = df.with_columns(
|
||
[
|
||
(pl.col("rank") / pl.col("count_2y")).alias("cs_rank_2y"),
|
||
(pl.col("rank") / pl.col("count_3y")).alias("cs_rank_3y"),
|
||
]
|
||
)
|
||
|
||
print("模拟结果(rank=1500的情况):")
|
||
print(
|
||
result.select(
|
||
["ts_code", "rank", "count_2y", "cs_rank_2y", "count_3y", "cs_rank_3y"]
|
||
)
|
||
)
|
||
print(f"\n差异: cs_rank_2y - cs_rank_3y = {1500 / 3000 - 1500 / 3100:.6f}")
|
||
|
||
|
||
def analyze_alpha032_deep():
|
||
"""
|
||
深入分析GTJA_alpha032 = -1 * ts_sum(cs_rank(ts_corr(cs_rank(high), cs_rank(vol), 3)), 3)
|
||
|
||
差异来源层级:
|
||
1. cs_rank(high) - 截面排名,count不同导致差异
|
||
2. cs_rank(vol) - 截面排名,count不同导致差异
|
||
3. ts_corr(..., 3) - 滚动相关,使用上述排名结果作为输入
|
||
4. cs_rank(ts_corr(...)) - 嵌套:对ts_corr结果再做截面排名
|
||
5. ts_sum(..., 3) - 滚动求和
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print("GTJA_alpha032 深入分析")
|
||
print("=" * 80)
|
||
|
||
print("""
|
||
公式: (-1 * ts_sum(cs_rank(ts_corr(cs_rank(high), cs_rank(vol), 3)), 3))
|
||
|
||
差异传递链:
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ L1: high, vol │
|
||
│ ↓ │
|
||
│ L2: cs_rank(high), cs_rank(vol) │
|
||
│ 问题: 2Y和3Y的截面股票数量可能不同 │
|
||
│ 例如: 股票X在2Y下cs_rank=1500/3000=0.5 │
|
||
│ 股票X在3Y下cs_rank=1500/3100=0.484 │
|
||
│ ↓ │
|
||
│ L3: ts_corr(cs_rank(high), cs_rank(vol), 3) │
|
||
│ 问题: 输入的cs_rank值已经不同,ts_corr结果自然不同 │
|
||
│ ↓ │
|
||
│ L4: cs_rank(ts_corr(...)) │
|
||
│ 问题: 每天对ts_corr结果做截面排名,count可能不同 │
|
||
│ ↓ │
|
||
│ L5: ts_sum(..., 3) │
|
||
│ 问题: ts_sum对L4的排名结果求和,边界效应 │
|
||
│ ↓ │
|
||
│ L6: -1 * ... │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
|
||
结论:GTJA_alpha032的差异主要来自L2的cs_rank嵌套问题
|
||
""")
|
||
|
||
|
||
def analyze_alpha077_deep():
|
||
"""
|
||
深入分析GTJA_alpha077 = min_(cs_rank(ts_decay_linear(...)), cs_rank(ts_decay_linear(...)))
|
||
|
||
涉及:ts_decay_linear(np.convolve边界效应)+ cs_rank嵌套
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print("GTJA_alpha077 深入分析")
|
||
print("=" * 80)
|
||
|
||
print("""
|
||
公式: min_(cs_rank(ts_decay_linear(DECAY1)), cs_rank(ts_decay_linear(DECAY2)))
|
||
|
||
其中:
|
||
DECAY1 = ((((high + low) / 2) + high) - ((amount / vol) + high))
|
||
DECAY2 = ts_corr(((high + low) / 2), ts_mean(vol, 40), 3)
|
||
|
||
差异来源:
|
||
1. ts_decay_linear使用np.convolve
|
||
- mode='valid' 只返回完全重叠的结果
|
||
- 边界处(前window-1个)数据会是NaN
|
||
- 但2Y和3Y的边界位置相同,结果应该一样
|
||
|
||
2. 真正的问题:cs_rank(ts_decay_linear(...))
|
||
- 每天对ts_decay_linear结果做截面排名
|
||
- 2Y和3Y截面股票数量不同 → cs_rank分母不同 → 结果不同
|
||
|
||
结论:差异主要来自cs_rank嵌套问题
|
||
""")
|
||
|
||
|
||
def analyze_alpha121_deep():
|
||
"""
|
||
深入分析GTJA_alpha121 = (cs_rank(...) ** ts_rank(ts_corr(...), 3)) * -1
|
||
|
||
涉及:ts_rank(滑动窗口边界敏感)+ ts_corr + cs_rank嵌套
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print("GTJA_alpha121 深入分析")
|
||
print("=" * 80)
|
||
|
||
print("""
|
||
公式: ((cs_rank(((amount / vol) - min_((amount / vol), 12))) ** ts_rank(ts_corr(...), 3)) * -1)
|
||
|
||
差异来源:
|
||
1. ts_rank(ts_corr(...), 3)
|
||
- ts_rank使用sliding_window_view
|
||
- 对边界敏感:不同起始点导致边界处有效窗口数量不同
|
||
- ts_corr本身也有边界效应
|
||
|
||
2. cs_rank((amount / vol) - min_(..., 12))
|
||
- 截面排名问题
|
||
- 2Y和3Y截面股票数量不同
|
||
|
||
3. cs_rank(...) ** ts_rank(...)
|
||
- 嵌套:截面排名结果作为指数
|
||
- 两个差异来源叠加
|
||
|
||
结论:差异来自ts_rank/ts_corr边界效应 + cs_rank嵌套问题
|
||
""")
|
||
|
||
|
||
def summarize_root_causes():
|
||
"""
|
||
总结问题2.7因子的根本原因
|
||
"""
|
||
print("\n" + "=" * 80)
|
||
print("问题2.3因子差异根本原因总结")
|
||
print("=" * 80)
|
||
|
||
print("""
|
||
┌─────────────────┬──────────────────────────────────────────────────────┐
|
||
│ 因子 │ 根本原因 │
|
||
├─────────────────┼──────────────────────────────────────────────────────┤
|
||
│ GTJA_alpha016 │ cs_rank嵌套:ts_corr结果再做截面排名,count不同 │
|
||
│ GTJA_alpha032 │ cs_rank嵌套:cs_rank(high)和cs_rank(vol)截面count不同 │
|
||
│ GTJA_alpha077 │ ts_decay_linear边界效应 + cs_rank嵌套 │
|
||
│ GTJA_alpha091 │ cs_rank嵌套:max_(close,5)结果再做截面排名 │
|
||
│ GTJA_alpha121 │ ts_rank/ts_corr边界效应 + cs_rank嵌套 │
|
||
│ GTJA_alpha130 │ ts_decay_linear边界效应 + cs_rank嵌套 │
|
||
│ GTJA_alpha141 │ cs_rank嵌套:ts_corr结果再做截面排名 │
|
||
└─────────────────┴──────────────────────────────────────────────────────┘
|
||
|
||
核心问题:cs_rank是截面排名函数,当不同LOOKBACK_DAYS下截面股票数量
|
||
不同时,cs_rank的分母(count)不同,导致归一化排名结果不同。
|
||
|
||
这是一个结构性问题,与具体实现无关。
|
||
""")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
verify_cross_section_stock_count()
|
||
verify_cs_rank_formula_behavior()
|
||
analyze_alpha032_deep()
|
||
analyze_alpha077_deep()
|
||
analyze_alpha121_deep()
|
||
summarize_root_causes()
|