Files
sanguo_vnpy_v2/tests/factor/test_fundamental_p1_library.py
T
claude_dev 66619c17d8 feat(factor): P1-B批library注册6因子(四新域5因子+D14双注册) [nas]
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 路径。
2026-09-09 12:12:00 +08:00

104 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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(IBDMON)."""
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