feat(factor): monthly_batch --daily 日度模式——as_of 直用不归一月末+daily_ 标签(决议 H 修订夜班批 as-of=T-1) [vps]
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user