Files
ProStock/tests/debug/fix_lookback_issue23/analyze_alpha032.py

254 lines
7.9 KiB
Python
Raw Normal View History

"""
深入分析GTJA_alpha032差异来源
GTJA_alpha032 DSL: (-1 * ts_sum(cs_rank(ts_corr(cs_rank(high), cs_rank(vol), 3)), 3))
问题该因子在不同LOOKBACK_DAYS下有8341个差异数据点max_diff=0.605
目标分析差异来源
"""
import numpy as np
import polars as pl
from datetime import datetime, timedelta
# =============================================================================
# 配置
# =============================================================================
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 analyze_data_boundary_effect():
"""
分析数据边界效应
问题不同LOOKBACK_DAYS下边界处的ts_corr/ts_sum计算结果可能不同
"""
print("=" * 80)
print("分析GTJA_alpha032的数据边界效应")
print("=" * 80)
actual_start_2y = get_lookback_start_date(PREDICT_START, LOOKBACK_2Y)
actual_start_3y = get_lookback_start_date(PREDICT_START, LOOKBACK_3Y)
print(f"\n2Y数据起始点: {actual_start_2y}")
print(f"3Y数据起始点: {actual_start_3y}")
print(f"差异: {LOOKBACK_3Y - LOOKBACK_2Y} 天 = {1460 - 1095}")
print("\n关键发现:")
print("-" * 60)
print("ts_corr(window=3) 计算时:")
print(" - 需要当前日期 + 前2天共3天数据")
print(" - 对于预测日期20250101:")
print(" * 2Y模式下需要: 20241230, 20241231, 20250101")
print(" * 3Y模式下需要: 20241230, 20241231, 20250101")
print(" * 两者从同一预测日期向前看,需要的历史数据相同")
print(" - 但如果2Y在20221230有NA而3Y在20211230有数据结果会不同")
def analyze_cs_rank_nesting_issue():
"""
分析 cs_rank 嵌套问题
GTJA_alpha032 = -1 * ts_sum(cs_rank(ts_corr(...)), 3)
嵌套结构: cs_rank(ts_corr(...))
- ts_corr输出可能有NA值边界数据不足等
- cs_rank(ts_corr(...)) 对截面进行排名
- 问题不同日期截面内可能有不同数量的NA值
"""
print("\n" + "=" * 80)
print("分析 cs_rank 嵌套问题")
print("=" * 80)
# 模拟截面数据:同一日期内不同股票有/无NA
print("\n场景1同一截面内有NA值")
df_with_na = pl.DataFrame(
{
"trade_date": ["20250101"] * 5,
"ts_code": ["001", "002", "003", "004", "005"],
"corr_val": [0.5, 0.6, None, 0.8, 0.9], # 003是NA
}
)
print("数据:")
print(df_with_na)
result = (
df_with_na.lazy()
.with_columns(
[
pl.col("corr_val").rank().alias("rank"),
pl.col("corr_val").count().over("trade_date").alias("count"),
]
)
.with_columns([(pl.col("rank") / pl.col("count")).alias("cs_rank")])
.collect()
)
print("\ncs_rank结果 (有NA):")
print(result.select(["ts_code", "corr_val", "rank", "count", "cs_rank"]))
# 验证004的值0.8在有NA时排名是3/4=0.75
print("\n验证: 004的0.8是第3名count=4所以cs_rank=3/4=0.75")
def analyze_ts_sum_boundary():
"""
分析 ts_sum 边界效应
GTJA_alpha032 = -1 * ts_sum(cs_rank(ts_corr(...)), 3)
ts_sum(window=3) 是时间序列求和不是截面求和
问题不同起始点导致ts_sum在边界处的有效窗口数量不同
"""
print("\n" + "=" * 80)
print("分析 ts_sum 边界效应")
print("=" * 80)
# 创建不同起始点的数据
df1 = pl.DataFrame(
{
"trade_date": ["20250101", "20250102", "20250103", "20250104", "20250105"],
"ts_code": ["001"] * 5,
"value": [1.0, 2.0, 3.0, 4.0, 5.0],
}
)
df2 = pl.DataFrame(
{
"trade_date": [
"20241229",
"20241230",
"20241231",
"20250101",
"20250102",
"20250103",
"20250104",
"20250105",
],
"ts_code": ["001"] * 8,
"value": [-1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
}
)
print("\n数据集1从20250101开始:")
print(df1)
print("\n数据集2从20241229开始:")
print(df2.filter(pl.col("trade_date") >= "20250101"))
# 模拟ts_sum(window=3)的结果
def rolling_sum(values, window):
result = np.full(len(values), np.nan)
for i in range(window - 1, len(values)):
result[i] = np.nansum(values[i - window + 1 : i + 1])
return result
vals1 = df1["value"].to_numpy()
vals2 = df2["value"].to_numpy()[3:] # 取相同日期部分
sum1 = rolling_sum(vals1, 3)
sum2 = rolling_sum(vals2, 3)
print("\nts_sum(window=3) 结果:")
print(f" 数据集1: {sum1}")
print(f" 数据集2: {sum2}")
print("\n分析: 对于同一日期20250105:")
print(f" 数据集1使用: [3,4,5] -> sum=12")
print(
f" 数据集2使用: [2,3,4] -> sum=9 (因为20241229=-1,20241230=0,20241231=1,20250101=2,...)"
)
print(
" 但这里使用的是相同日期的数据[1,2,3,4,5] vs [2,3,4,5,6],所以不同日期的数据本身不同"
)
def identify_root_cause():
"""
识别GTJA_alpha032差异的根本原因
结论
1. GTJA_alpha032 = -1 * ts_sum(cs_rank(ts_corr(cs_rank(high), cs_rank(vol), 3)), 3)
2. cs_rank(high) cs_rank(vol) 是截面排名
3. 如果2Y和3Y在某个日期的截面组成不同股票池差异cs_rank结果会不同
4. ts_corr(window=3) 对嵌套的cs_rank值计算相关系数
5. ts_sum(window=3) 对ts_corr结果进行滚动求和
"""
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) - 截面排名每天独立计算
L3: ts_corr(..., 3) - 滚动相关3日窗口
L4: cs_rank(ts_corr(...)) - 对ts_corr结果再做截面排名
L5: ts_sum(..., 3) - 滚动求和3日窗口
L6: -1 * ... - 取反
差异来源:
1. 主要cs_rank(high) cs_rank(vol) 是截面排名
- 2Y和3Y的股票池可能在边界处有差异
- 新上市/退市股票导致截面组成不同
- 导致排名结果不同
2. 次要ts_corr(window=3) 的边界效应
- 不同起始点导致有效数据点不同
- 但由于window=3较小影响有限
3. 主要cs_rank(ts_corr(...)) 嵌套排名
- 每天对ts_corr结果再做截面排名
- 如果2Y和3Y的ts_corr值不同排名结果也不同
""")
def analyze_cross_section_composition():
"""
分析截面组成的差异
如果2Y和3Y的股票池在边界处有差异cs_rank结果会不同
"""
print("\n" + "=" * 80)
print("分析截面组成差异")
print("=" * 80)
print("""
关键问题cs_rank 是截面排名
例如对于日期20250101
- 2Y模式股票池A假设3000只
- 3Y模式股票池B假设3200只
如果股票池B包含一些股票A没有的早期股票但这些股票在20250101也存在于A
那么在20250101的截面排名中
- 2Y: 对3000只股票排名
- 3Y: 对3200只股票排名
假设股票X在2Y模式下排名第1500/3000 = 0.5
但在3Y模式下排名第1500/3200 = 0.47因为分母变大了
这就是 cs_rank 嵌套导致差异的根本原因
""")
if __name__ == "__main__":
analyze_data_boundary_effect()
analyze_cs_rank_nesting_issue()
analyze_ts_sum_boundary()
identify_root_cause()
analyze_cross_section_composition()