Files

289 lines
11 KiB
Python
Raw Permalink Normal View History

"""
验证问题2.3因子差异根本原因的测试
理论分析
1. GTJA_alpha032: cs_rank(high)和cs_rank(vol)是截面排名
- 不同LOOKBACK_DAYS下载面股票数量可能不同3Y包含更多历史股票
- cs_rank = rank/countcount不同导致排名分母不同
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_linearnp.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()