Files
sanguo_vnpy_v2/tests/factor/test_fundamental_library.py
T

85 lines
3.6 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
@pytest.fixture(autouse=True)
def _ensure_fundamental_registered():
"""其它测试模块清空 _REGISTRY 后只重挂 alpha/builtin(顺序依赖前科),
这里逐测试幂等重注册财务因子,保证本模块与顺序无关."""
fundamental_library._register_all()
# 表达式可引用的列 = 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