66619c17d8
fund_ccc(-)/fund_send_total_12m(不定正向注册待实证)/fund_gdhs_chg(-)/ fund_topholder_chg(+)/fund_ep_vb(+)/fund_bp_vb(+);注册总数 69→75 (P0 32+P1 37+P1-B 6,既有计数契约同步更新);batch e2e 锁死 vb+dividend 域级引用瘦身 join 路径。
104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
# tests/factor/test_fundamental_p1_library.py
|
||
"""P1 批财务因子表达式库: 37 个注册(35 因子 + 2 互评变体)与契约锁定.
|
||
|
||
契约:
|
||
- 表达式裸标识符 ⊆ adapter 特征列 + close/log/cs_rank(漏加列 = 拼写错)
|
||
- 全部 cs_rank 一层截面;文档负 IC 因子表达式取负(高=好统一)
|
||
- EV 群分母 = close×share_capital + ev_ex_mv;D12 是中性化控制变量非 alpha
|
||
"""
|
||
import re
|
||
import sys, os
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "vnpy_v4.4.0")))
|
||
|
||
import pytest
|
||
|
||
from sanguo_factor import fundamental_library # noqa: F401 import 即注册
|
||
from sanguo_factor.fundamental_adapter import FEATURE_COLUMNS
|
||
from sanguo_factor.fundamental_library import FUNDAMENTAL_P1_FACTORS
|
||
from sanguo_factor.registry import list_factors, get_factor
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def _ensure_fundamental_registered():
|
||
"""其它测试模块清空 _REGISTRY 后只重挂 alpha/builtin(顺序依赖前科),
|
||
这里逐测试幂等重注册财务因子,保证本模块与顺序无关."""
|
||
fundamental_library._register_all()
|
||
|
||
|
||
_ALLOWED = set(FEATURE_COLUMNS) | {"close", "cs_rank", "log"}
|
||
|
||
|
||
def _p1_names() -> set[str]:
|
||
return {name for name, _e, _d, _ic in FUNDAMENTAL_P1_FACTORS}
|
||
|
||
|
||
def test_p1_37_factors_registered():
|
||
facs = {f["name"]: f for f in list_factors("fundamental")}
|
||
p1 = _p1_names()
|
||
assert len(p1) == 37, f"P1 应 37 个(35+2 变体),实际 {len(p1)}"
|
||
for name in p1:
|
||
assert name in facs, f"{name} 未注册"
|
||
assert facs[name]["category"] == "fundamental"
|
||
|
||
|
||
def test_p1_expressions_only_reference_feature_columns():
|
||
"""契约: 表达式裸标识符 ⊆ adapter 特征列 + close/log/cs_rank."""
|
||
for name, expression, _d, _ic in FUNDAMENTAL_P1_FACTORS:
|
||
idents = set(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", expression))
|
||
bad = idents - _ALLOWED
|
||
assert not bad, f"{name} 引用了未产出列: {bad} in {expression}"
|
||
|
||
|
||
def test_p1_all_cross_sectional_rank_one_layer():
|
||
for name, expression, _d, _ic in FUNDAMENTAL_P1_FACTORS:
|
||
assert expression.startswith("cs_rank("), f"{name}: {expression}"
|
||
assert expression.endswith(")")
|
||
|
||
|
||
def test_p1_negative_ic_factors_flipped():
|
||
"""文档负 IC 的 P1 因子(B09/B12/B13/B14/B15/B18/C16/C18/E03/E10/A16)取负."""
|
||
flipped = {"fund_sale_expense_ratio", "fund_inventory_anomaly",
|
||
"fund_cash_ibd_double", "fund_vsig", "fund_vsig_acc",
|
||
"fund_vsig_cfo", "fund_gm_nm_scissors", "fund_nwc_growth",
|
||
"fund_invest_growth", "fund_debt_issue", "fund_goodwill_growth"}
|
||
for name in flipped:
|
||
expr = get_factor(name)["expression"]
|
||
assert "(-" in expr, f"{name} 应翻转: {expr}"
|
||
|
||
|
||
def test_p1_ev_group_denominator():
|
||
"""EV 群(D07/D08/D15)分母 = close×share_capital + ev_ex_mv(IBD−MON)."""
|
||
for name in ("fund_ebit_over_ev", "fund_ebitda_over_ev", "fund_fcf_over_ev"):
|
||
expr = get_factor(name)["expression"]
|
||
assert "close * share_capital + ev_ex_mv" in expr, f"{name}: {expr}"
|
||
|
||
|
||
def test_p1_valuation_uses_self_computed_mv():
|
||
"""市值类因子(D09/D13/E03)统一 close×share_capital 自算(不读 valuation)."""
|
||
for name in ("fund_gp_over_m", "fund_forward_ep", "fund_debt_issue"):
|
||
expr = get_factor(name)["expression"]
|
||
assert "close * share_capital" in expr, f"{name}: {expr}"
|
||
|
||
|
||
def test_p1_ln_mv_is_control_variable():
|
||
"""D12 = ln(MV) 中性化控制变量(非 alpha): log(close×share_capital)."""
|
||
expr = get_factor("fund_ln_mv")["expression"]
|
||
assert expr == "cs_rank(log(close * share_capital))"
|
||
|
||
|
||
def test_p1_two_variants():
|
||
"""互评变体: SUE 严窗列 + 资产增长负向对照(原始方向,不取负)."""
|
||
assert get_factor("fund_sue_np_strict")["expression"] == "cs_rank(sue_np_strict)"
|
||
# 负向对照 = 现有 fund_asset_growth(cs_rank(-asset_growth))的方向反转
|
||
assert get_factor("fund_asset_growth_neg")["expression"] == "cs_rank(asset_growth)"
|
||
assert "-" not in get_factor("fund_asset_growth_neg")["expression"].replace(
|
||
"cs_rank(", "").replace("asset_growth)", "")
|
||
|
||
|
||
def test_p1_registration_idempotent():
|
||
"""重复注册幂等(防重入)."""
|
||
fundamental_library._register_all()
|
||
fundamental_library._register_all()
|
||
assert len(list_factors("fundamental")) == 75
|