From 0bd88b6e37d5b83aa233c1e749a4388a6244ffc3 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Fri, 11 Sep 2026 09:29:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(factor):=20v2b=E6=A1=A3=E6=A1=88=E4=B8=8A?= =?UTF-8?q?=E7=BA=BF=E6=9A=B4=E9=9C=B2=E7=9A=84=E6=B3=A8=E5=86=8C=E9=93=BE?= =?UTF-8?q?=E7=BC=BA=E5=8F=A3=E2=80=94=E2=80=94composite=5Fweighting.=5Fre?= =?UTF-8?q?gister=5Fall=E5=B9=82=E7=AD=89=E9=87=8D=E6=8C=82fund=5F*=5Fneu(?= =?UTF-8?q?reload=E5=9C=BA=E6=99=AFregistry=E6=B8=85=E7=A9=BA=E5=90=8Eneu?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E4=B8=8D=E9=9A=8F=E4=B9=8B=E9=87=8D=E6=89=A7?= =?UTF-8?q?=E8=A1=8C);=E6=B5=8B=E8=AF=95=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E4=B8=96=E7=95=8C=3D=E8=AE=A1=E6=95=B075=E2=86=9275+6=E6=B4=BE?= =?UTF-8?q?=E7=94=9F/=5FALLOWED=E6=94=BE=E8=A1=8Ccs=5Fneutralize/absent?= =?UTF-8?q?=E6=A1=A3=E6=A1=88=E6=B5=8B=E8=AF=95=E6=94=B9monkeypatch?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E7=9C=9F=E6=A1=A3=E6=A1=88=E5=B7=B2=E4=B8=8A?= =?UTF-8?q?=E7=BA=BF=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_factor/composite_weighting.py | 4 ++++ tests/factor/test_fundamental_library.py | 11 +++++++---- tests/factor/test_fundamental_neutralize.py | 8 +++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/sanguo_factor/composite_weighting.py b/sanguo_factor/composite_weighting.py index f7d9bb0..c883d62 100644 --- a/sanguo_factor/composite_weighting.py +++ b/sanguo_factor/composite_weighting.py @@ -155,6 +155,10 @@ def _register_all() -> None: 文件到位即自动注册,不 ImportError). """ composite_library._register_all() + # 幂等重挂 fund_*_neu(reload 场景 registry 被清后,neu 模块不会随之重执行, + # v2b 档案存在时其源必须可重建——v2b 档案上线前此缺口被 graceful 分支掩盖) + from .fundamental_neutralize import _register_all as _neu_register_all + _neu_register_all() register_composite_from_profile(load_profile(str(_DEFAULT_PROFILE_PATH)), _DEFAULT_COMPOSITE_NAME) if _V2B_PROFILE_PATH.exists(): diff --git a/tests/factor/test_fundamental_library.py b/tests/factor/test_fundamental_library.py index 741566c..985c027 100644 --- a/tests/factor/test_fundamental_library.py +++ b/tests/factor/test_fundamental_library.py @@ -19,8 +19,9 @@ def _ensure_fundamental_registered(): # 表达式可引用的列 = adapter 特征列 + 行情列 close(估值类 ÷ close×share_capital) -# + 表达式函数名(P1 起用到 log) -_ALLOWED = set(FEATURE_COLUMNS) | {"close", "cs_rank", "log"} +# + 表达式函数名(P1 起用到 log;v2b 起中性化因子用 cs_neutralize) +from sanguo_factor.fundamental_neutralize import FUND_NEU_SOURCES +_ALLOWED = set(FEATURE_COLUMNS) | {"close", "cs_rank", "log", "cs_neutralize"} def _fundamental_factors() -> list[dict]: @@ -31,7 +32,9 @@ def test_p0_32_factors_registered(): facs = _fundamental_factors() names = {f["name"] for f in facs} # P0 32 + P1 37(35 因子 + 2 互评变体;P1 名单见 test_fundamental_p1_library) - assert len(facs) == 75, f"P0+P1+P1-B 应为 75 个,实际 {len(facs)}" + # + v2b 中性化 6(fund_*_neu,注册链经 composite_weighting 可在场,故派生不钉死) + assert len(facs) == 75 + len(FUND_NEU_SOURCES), \ + f"P0+P1+P1-B+neu 应为 {75 + len(FUND_NEU_SOURCES)} 个,实际 {len(facs)}" # 六族代表抽查(全部名单见 fundamental_library 注释) expect = { "fund_roe_ttm", "fund_gp_over_assets", # A @@ -83,4 +86,4 @@ def test_registration_idempotent(): """重复 import 不炸(注册表防重入,同 library.py 模式).""" import importlib importlib.reload(fundamental_library) - assert len(_fundamental_factors()) == 75 + assert len(_fundamental_factors()) == 75 + len(FUND_NEU_SOURCES) diff --git a/tests/factor/test_fundamental_neutralize.py b/tests/factor/test_fundamental_neutralize.py index c37ebc9..c36756c 100644 --- a/tests/factor/test_fundamental_neutralize.py +++ b/tests/factor/test_fundamental_neutralize.py @@ -227,10 +227,12 @@ def test_weighting_rejects_mixed_direction_drift(): assert get_factor(name) is None -def test_v2b_profile_absent_skips_registration(): - """v2b 档案不存在(仓库当前态): _register_all 不炸、不注册 composite_all18_v2b.""" +def test_v2b_profile_absent_skips_registration(tmp_path, monkeypatch): + """v2b 档案不存在(真档案已上线,monkeypatch 指向不存在路径模拟缺席): + _register_all 不炸、不注册 composite_all18_v2b.""" _REGISTRY.pop(composite_weighting._V2B_COMPOSITE_NAME, None) - assert not composite_weighting._V2B_PROFILE_PATH.exists() + monkeypatch.setattr(composite_weighting, "_V2B_PROFILE_PATH", + tmp_path / "absent_v2b.json") composite_weighting._register_all() # 不应 raise assert get_factor(composite_weighting._V2B_COMPOSITE_NAME) is None