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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""财务因子批 P0 表达式库: 32 个因子注册(category="fundamental").
|
||||
|
||||
选型来源 docs/fundamental_factor_survey_20260907.md(§3 候选池 96 个 + §4.1 去重),
|
||||
首批 32 = P0 50 个按族去重后的主代表;全部「因子 = cs_rank(基础指标)」一层截面,
|
||||
负 IC 因子表达式取负(统一高=好,与量价批 IC 口径一致;原始方向见下表)。
|
||||
|
||||
基础指标列由 fundamental_adapter.build_fundamental_features 产出并 join 到
|
||||
alpha_df;估值族市值 = close × share_capital 自算(三表股本,规避 valuation
|
||||
中文列名表,PIT 口径与报表一致)。
|
||||
|
||||
P0 32 因子名单(编号=调研文档 §3):
|
||||
A 盈利能力(7): A01 ROE / A02 扣非ROE / A04 ROA / A05 GP/A(族主代表) /
|
||||
A06 毛利率 / A07 净利率 / A13 CFO/TA
|
||||
B 盈利质量(7): B01 TACC(Sloan,族核心) / B04 非经常占比 / B05 减值冲击 /
|
||||
B07 投资收益依赖 / B08 应收异常 / B10 销售收现率 / B11 其他应收占比
|
||||
C 成长(6): C01 营收单季同比 / C02 净利单季同比 / C09 增速剪刀差 /
|
||||
C10 ΔGM(副代表) / C12 ΔROE / C15 资产增速(投资-融资群代表)
|
||||
D 估值(4): D01 EP_TTM / D03 扣非EP(A股特色) / D04 BP / D06 CP(CNE5 CETOP)
|
||||
E 资本结构/行为(4): E01 NSI 净股票发行 / E02 股权融资强度 / E05 有息负债率 /
|
||||
E09 商誉占比
|
||||
F 预期事件(4): F01 SUE(净利,Foster) / F02 SUE(营收) / F04 预告类型分 /
|
||||
F05 预告幅度
|
||||
同族被裁变体(A03/A08/A09/A10/A12/B02/B06/C03/C04/C05/C06/D02/D05/E04 等)
|
||||
在 P1 或互证后再议,见文档 §4。
|
||||
"""
|
||||
from .registry import register_factor, _REGISTRY
|
||||
|
||||
# (name, expression, 文档编号, 原始 IC 方向)
|
||||
FUNDAMENTAL_FACTORS: list[tuple[str, str, str, str]] = [
|
||||
# ---- A 盈利能力(7) ----
|
||||
("fund_roe_ttm", "cs_rank(roe_ttm)", "A01", "+"),
|
||||
("fund_roe_deduct_ttm", "cs_rank(roe_deduct_ttm)", "A02", "+"),
|
||||
("fund_roa_ttm", "cs_rank(roa_ttm)", "A04", "+"),
|
||||
("fund_gp_over_assets", "cs_rank(gp_over_assets)", "A05", "+"),
|
||||
("fund_gross_margin", "cs_rank(gross_margin)", "A06", "+"),
|
||||
("fund_net_margin", "cs_rank(net_margin)", "A07", "+"),
|
||||
("fund_cfo_over_assets", "cs_rank(cfo_over_assets)", "A13", "+"),
|
||||
# ---- B 盈利质量(7) ----
|
||||
("fund_tacc", "cs_rank(-tacc)", "B01", "-"),
|
||||
("fund_nonrec_ratio", "cs_rank(-nonrec_ratio)", "B04", "-"),
|
||||
("fund_impairment_ratio", "cs_rank(-impairment_ratio)", "B05", "-"),
|
||||
("fund_invest_income_dep", "cs_rank(-invest_income_dep)", "B07", "-"),
|
||||
("fund_receivables_anomaly", "cs_rank(-receivables_anomaly)", "B08", "-"),
|
||||
("fund_sales_cash_ratio", "cs_rank(sales_cash_ratio)", "B10", "+"),
|
||||
("fund_other_rece_ratio", "cs_rank(-other_rece_ratio)", "B11", "-"),
|
||||
# ---- C 成长(6) ----
|
||||
("fund_rev_q_yoy", "cs_rank(rev_q_yoy)", "C01", "+"),
|
||||
("fund_np_q_yoy", "cs_rank(np_q_yoy)", "C02", "+"),
|
||||
("fund_growth_scissors", "cs_rank(growth_scissors)", "C09", "+"),
|
||||
("fund_gm_delta", "cs_rank(gm_delta)", "C10", "+"),
|
||||
("fund_roe_delta", "cs_rank(roe_delta)", "C12", "+"),
|
||||
("fund_asset_growth", "cs_rank(-asset_growth)", "C15", "-"),
|
||||
# ---- D 估值(4) ----
|
||||
("fund_ep_ttm", "cs_rank(np_ttm / (close * share_capital))", "D01", "+"),
|
||||
("fund_ep_deduct_ttm", "cs_rank(dnp_ttm / (close * share_capital))", "D03", "+"),
|
||||
("fund_bp", "cs_rank(equity / (close * share_capital))", "D04", "+"),
|
||||
("fund_cp", "cs_rank(cfo_ttm / (close * share_capital))", "D06", "+"),
|
||||
# ---- E 资本结构/行为(4) ----
|
||||
("fund_nsi", "cs_rank(-nsi)", "E01", "-"),
|
||||
("fund_equity_fin_intensity",
|
||||
"cs_rank(-(acc_invest_cash_ttm / (close * share_capital)))", "E02", "-"),
|
||||
("fund_ibd_ratio", "cs_rank(-ibd_ratio)", "E05", "-"),
|
||||
("fund_goodwill_ratio", "cs_rank(-goodwill_ratio)", "E09", "-"),
|
||||
# ---- F 预期事件(4) ----
|
||||
("fund_sue_np", "cs_rank(sue_np)", "F01", "+"),
|
||||
("fund_sue_rev", "cs_rank(sue_rev)", "F02", "+"),
|
||||
("fund_forecast_type", "cs_rank(forecast_type_score)", "F04", "+"),
|
||||
("fund_forecast_change", "cs_rank(forecast_change_pct)", "F05", "+"),
|
||||
]
|
||||
|
||||
|
||||
def _register_all() -> None:
|
||||
"""注册全部财务因子(已存在同名跳过,幂等;同 library.py 模式)."""
|
||||
for name, expression, _doc_id, _ic in FUNDAMENTAL_FACTORS:
|
||||
if name not in _REGISTRY:
|
||||
register_factor(name, expression, category="fundamental")
|
||||
|
||||
|
||||
# 模块导入时自动注册(与 library.py/alpha_datasets.py 同一模式)
|
||||
_register_all()
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user