feat(factor): monthly_batch --daily 日度模式——as_of 直用不归一月末+daily_ 标签(决议 H 修订夜班批 as-of=T-1) [vps]

This commit is contained in:
2026-10-10 07:22:15 +08:00
parent 97764cee80
commit 3ced1474a8
2 changed files with 44 additions and 4 deletions
+10 -4
View File
@@ -81,7 +81,7 @@ def resolve_with_yaml(registry: dict):
def plan_batch(registry: dict, as_of: str, window_months: int = 12,
resolver=None) -> dict[str, Any]:
resolver=None, daily: bool = False) -> dict[str, Any]:
"""注册表→批参数(main 只做 IO 编排,可单测).
「纯函数」契约勘正(P3-12): resolver=get_factor(默认)时纯读;
@@ -94,14 +94,17 @@ def plan_batch(registry: dict, as_of: str, window_months: int = 12,
曾漂移 5 名(fa_gross_margin/s0* vs 库名),月度批评会空转.
"""
resolve = resolver or get_factor
end = month_end(as_of)
# 日度化(决议 H 修订):daily 时 as_of 直用(T-1 夜班),不归一月末;
# 月度缺省行为不变(月末快照=判定层语义,月度链照旧归一).
end = as_of if daily else month_end(as_of)
names = sorted(n for n, e in registry["factors"].items()
if e["status"] not in EXCLUDED_STATUSES)
skipped = [n for n in names if resolve(n) is None]
return {"factor_names": [n for n in names if resolve(n) is not None],
"skipped_unregistered": skipped,
"start": window_start(end, window_months),
"end": end, "label": f"monthly_{as_of[:7]}"}
"end": end,
"label": (f"daily_{as_of}" if daily else f"monthly_{as_of[:7]}")}
def decomposer_new_names(registry: dict, factor_names: list[str]) -> list[str]:
@@ -152,10 +155,13 @@ def main(argv: list[str] | None = None) -> int:
ap.add_argument("--fund-data-dir", default=None,
help="财务静态域根目录 override(VPS 镜像路径与 NAS 默认各异时直传,"
"缺省走 cfg/默认解析)")
ap.add_argument("--daily", action="store_true",
help="日度模式(决议 H 修订):as_of 直用不归一月末,"
"label=daily_ 前缀;夜班批 as-of=T-1")
args = ap.parse_args(argv)
registry = load_registry(args.registry)
plan = plan_batch(registry, args.as_of, args.window_months,
resolver=resolve_with_yaml(registry))
resolver=resolve_with_yaml(registry), daily=args.daily)
if not plan["factor_names"]:
print("[monthly_batch] 注册表无可跑因子(全 retired/graveyard?)", file=sys.stderr)
return 1
+34
View File
@@ -51,6 +51,40 @@ def test_plan_batch_excludes_dead_and_labels(registry):
assert plan["label"] == "monthly_2026-09"
def test_plan_batch_daily_uses_asof_direct(registry):
"""日度化(决议 H 修订):daily=True 时 as_of 直用不归一月末,label 前缀 daily_.
夜班批 as-of=T-1——若仍归一,10-09 会吸到 10-31(未来日),窗 end 越过
数据水位即盲区消解失效."""
plan = plan_batch(registry, "2026-10-09", daily=True)
assert plan["end"] == "2026-10-09"
assert plan["label"] == "daily_2026-10-09"
# 窗起点同款滚动(含 end 当月共 12M:2025-11 起算)
assert plan["start"] == "2025-11-01"
# 月度缺省行为不变(防回归)
monthly = plan_batch(registry, "2026-10-09")
assert monthly["end"] == "2026-10-31"
assert monthly["label"] == "monthly_2026-10"
def test_main_daily_flag_reaches_plan(monkeypatch, registry, tmp_path):
"""CLI --daily 透传 plan_batch(main 只做 IO 编排)."""
calls = {}
def fake_run(factor_names, start, end, eval_db, label, **kw):
calls.update(end=end, label=label)
return {"run_id": "x"}
monkeypatch.setattr(monthly_batch, "run_batch_eval", fake_run)
p = str(tmp_path / "reg.yaml")
save_registry(p, registry)
rc = main(["--as-of", "2026-10-09", "--daily", "--registry", p,
"--eval-db", "fake.db"])
assert rc == 0
assert calls["end"] == "2026-10-09"
assert calls["label"] == "daily_2026-10-09"
def test_main_invokes_run_batch_eval_with_plan(monkeypatch, registry, tmp_path):
calls = {}