""" 深入分析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()