- 分析GTJA_alpha032等因子在不同LOOKBACK_DAYS下的差异来源 - 验证cs_rank嵌套和截面股票数量对结果的影响 - 测试ts_rank NaN处理和除法除零修复
254 lines
7.9 KiB
Python
254 lines
7.9 KiB
Python
"""
|
||
深入分析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()
|