d1d9dd50ff
- 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>
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
# 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
|