Files
sanguo_vnpy_v2/tests/factor/test_fundamental_library.py
T
claude_dev d1d9dd50ff feat(factor): 财务因子P0表达式库——32因子注册cs_rank一层(财务批P0 WP2) [nas]
- fundamental_library: 32因子(盈利7/质量7/成长6/估值4/资本4/预期4),全名单注释在档
- 因子=cs_rank(基础指标),负IC因子取负统一高=好;估值族市值=close×share_capital自算
- 契约测试锁定: 表达式裸标识符⊆adapter特征列∪close(防拼写漏加列)/cs_rank一层/
  负向因子翻转/幂等注册

[nas]

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 09:31:51 +08:00

78 lines
3.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_library.py
"""财务因子表达式库: 32 个 P0 因子注册 + 表达式↔adapter 特征列契约锁定."""
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.registry import list_factors, get_factor
# 表达式可引用的列 = adapter 特征列 + 行情列 close(估值类 ÷ close×share_capital)
_ALLOWED = set(FEATURE_COLUMNS) | {"close", "cs_rank"}
def _fundamental_factors() -> list[dict]:
return list_factors("fundamental")
def test_p0_32_factors_registered():
facs = _fundamental_factors()
names = {f["name"] for f in facs}
assert len(facs) == 32, f"P0 首批应为 32 个,实际 {len(facs)}"
# 六族代表抽查(全部名单见 fundamental_library 注释)
expect = {
"fund_roe_ttm", "fund_gp_over_assets", # A
"fund_tacc", "fund_nonrec_ratio", # B
"fund_rev_q_yoy", "fund_np_q_yoy", "fund_asset_growth", # C
"fund_ep_ttm", "fund_bp", "fund_cp", # D
"fund_nsi", "fund_ibd_ratio", # E
"fund_sue_np", "fund_forecast_type", # F
}
assert expect <= names
def test_expressions_only_reference_feature_columns():
"""契约: 表达式裸标识符 ⊆ adapter 特征列 + close/cs_rank(漏加列=拼写错)."""
for f in _fundamental_factors():
idents = set(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", f["expression"]))
bad = idents - _ALLOWED
assert not bad, f"{f['name']} 引用了未产出列: {bad} in {f['expression']}"
def test_all_factors_are_cross_sectional_rank():
"""P0 设计: 因子 = cs_rank(基础指标) 一层(负 IC 因子取负定向,高=好)."""
for f in _fundamental_factors():
assert f["expression"].startswith("cs_rank("), f["name"]
assert f["expression"].endswith(")")
def test_negative_ic_factors_flipped():
"""文档负 IC 因子(B01/B04/B05/B07/B08/B11/C15/E01/E02/E05/E09)表达式含负号."""
flipped = {"fund_tacc", "fund_nonrec_ratio", "fund_impairment_ratio",
"fund_invest_income_dep", "fund_receivables_anomaly",
"fund_other_rece_ratio", "fund_asset_growth",
"fund_nsi", "fund_equity_fin_intensity", "fund_ibd_ratio",
"fund_goodwill_ratio"}
for name in flipped:
expr = get_factor(name)["expression"]
assert "(-" in expr or expr.startswith("cs_rank(-"), f"{name} 应翻转: {expr}"
def test_valuation_factors_use_close_times_share_capital():
"""估值族市值 = close × share_capital 自算(规避 valuation 中文列名表)."""
for name in ("fund_ep_ttm", "fund_ep_deduct_ttm", "fund_bp", "fund_cp",
"fund_equity_fin_intensity"):
expr = get_factor(name)["expression"]
assert "close * share_capital" in expr, f"{name}: {expr}"
def test_registration_idempotent():
"""重复 import 不炸(注册表防重入,同 library.py 模式)."""
import importlib
importlib.reload(fundamental_library)
assert len(_fundamental_factors()) == 32