"""合成层 v1(Combiner): 量价 12 独立源 + 财务 6 源 → 等权 rank 合成总分因子. 「因子战法八环」第④环首个落地. 因子 = 表达式字符串注册(category="composite"), 表达式由 build 函数从源清单常量表循环拼接(18 源数千字符,引擎 eval 解析无碍; 可读性/可维护性由源常量表承载,不手写长表达式). cs_rank 语义(vnpy_v4.4.0/vnpy/alpha/dataset/cs_function.py:15): pl.col("data").rank().over("datetime") —— 按 datetime 分组、组内全池 截面 rank(batch_eval alpha_df 为全池长表,即每日全市场截面排名,正确口径; polars rank 默认 average 法,值域 1..N 非归一化,N=当日非空源值数). 方向口径(钉死,二者 IC 等价——每日仅差截面常数,不影响秩相关): - 量价源: 注册表达式为原始连续值(无方向) → build 统一包 cs_rank 截面化; 负向源项 = `(-1) * cs_rank(())`,负号在 rank 外(与 alpha16 引擎先例 `-1 * cs_rank(...)` 同族);备选 `cs_rank((-1) * (...))`(rank 内取负=秩反转) 弃用,全文统一 rank 外. - 财务源: 注册表达式本身已是 `cs_rank(指标)`(负 IC 已在表达式内取负定向, 高=好) → 直接内嵌、不再套 cs_rank;源表方向列 = 内嵌后净方向,登记 "-" 时才前置 `(-1) * `(当前 6 源全为已定向正向,不触发). Null 传播: polars 算术 null 传染 → 合成值任一源 null 即 null(覆盖 = 18 源 交集;gdhs 等事件流源在上市早期覆盖受限,合成因子覆盖期短于 单源,属已知限制非缺陷). warmup: 量价源最大窗 vma_60=60 bar / alpha83 内层 ts_sum(ts_mean(volume,10),50) ≈ 59 bar < batch_eval WARMUP_BARS=120,统一预热掩码覆盖. """ from .registry import register_factor, get_factor, _REGISTRY # 量价 12 独立源(08-31 族分析固化;方向 = 该源 ICIR 符号) QUANT_SOURCES: list[tuple[str, str]] = [ ("vma_60", "+"), # alpha158 量能收缩(族1 代表族) ("alpha16", "+"), # alpha101 量价背离(族2 代表,全池最强单源) ("alpha83", "+"), # alpha101 ("alpha12", "+"), # alpha101 ("alpha2", "+"), # alpha101 ("alpha42", "+"), # alpha101 ("vol_ma5", "-"), # builtin ("wvma_20", "-"), # alpha158 ("klow", "-"), # alpha158 ("cord_5", "-"), # alpha158 ("kup", "-"), # alpha158 ("alpha81", "-"), # alpha101 ] # 财务 6 源(P0+P1 IC 结论 + 族分析独立性选定;注册表达式已 cs_rank 定向). # v1.1: 剔 fund_topholder_chg —— top_holders 源数据 2021Q3 起(NAS 镜像实测,2020Q1 # 契约口径与实际不符),首期无差分 → 特征 2022 年中才生效;null 传染使合成因子在 # h1(2016-2021)整窗空序列(实测 months=0).引擎无 fill_null 类算子,无法表达式内 # null-safe 平均,故 v1 剔除;v2 数据补全或特征层合成时回填. FUND_SOURCES: list[tuple[str, str]] = [ ("fund_bp", "+"), # h2 ICIR 0.319 唯一 effective ("fund_nsi", "+"), # h2 0.233 ("fund_gp_over_m", "+"), # h2 0.212 新晋 ("fund_gdhs_chg", "+"), # 独立源 0.15/0.11 ("fund_growth_scissors", "+"), # h1 0.236(h2 0.076 衰减但方向稳) ("fund_sue_np", "+"), # h1 0.200 ] # (合成名, 源清单)——项形态由源注册 category 决定: fundamental=内嵌,其余=cs_rank 包裹 COMPOSITE_FACTORS: list[tuple[str, list[tuple[str, str]]]] = [ ("composite_quant12", QUANT_SOURCES), ("composite_fund6", FUND_SOURCES), ("composite_all18", QUANT_SOURCES + FUND_SOURCES), ] def rank_term(expression: str, direction: str) -> str: """原始值源贡献项: cs_rank 截面化;负向 = rank 外前置 (-1) *(口径钉死).""" term = f"cs_rank(({expression}))" return f"(-1) * {term}" if direction == "-" else term def _embed_term(expression: str, direction: str) -> str: """已定向源(财务)贡献项: 原样内嵌;净方向 "-" 时才前置负号.""" return f"({expression})" if direction == "+" else f"(-1) * ({expression})" def build_composite_expression(terms: list[str]) -> str: """等权平均拼接: (项1 + 项2 + ...) / N.""" return "(" + " + ".join(terms) + f") / {len(terms)}" def _register_all() -> None: """注册全部合成因子(幂等);先幂等补挂源(测试清空 _REGISTRY 后可独立重建).""" from . import library as _builtin_lib from . import alpha_datasets as _alpha_ds from . import fundamental_library as _fund_lib _builtin_lib._register_all() _alpha_ds.mount_all() _fund_lib._register_all() for name, sources in COMPOSITE_FACTORS: if name in _REGISTRY: continue terms = [] for src_name, direction in sources: src = get_factor(src_name) if src is None: raise ValueError(f"合成源未注册: {src_name}") if src["category"] == "fundamental": terms.append(_embed_term(src["expression"], direction)) else: terms.append(rank_term(src["expression"], direction)) register_factor(name, build_composite_expression(terms), category="composite") # 模块导入时自动注册(与 library.py/fundamental_library.py 同模式) _register_all()