feat(factor): challenger CLI+月度链 stage6 接线(影子失败不阻链;宽表源=stage4 截面盘,月度批 values 不含 12 源) [nas]

This commit is contained in:
2026-10-10 12:42:28 +08:00
parent 066a2d3b87
commit d51ee7df72
3 changed files with 59 additions and 0 deletions
+21
View File
@@ -203,3 +203,24 @@ def run_challenge(values_dir: str, vnpy_db: str, as_of: str, out_dir: str,
with open(path, "w", encoding="utf-8") as f:
json.dump(doc, f, ensure_ascii=False, indent=2)
return path
def main(argv: list[str] | None = None) -> int:
import argparse
ap = argparse.ArgumentParser(description="LGBM challenger 三路对拍(月度链 stage6)")
ap.add_argument("--values-dir", required=True,
help="因子截面宽表目录(每因子一 parquet;NAS=stage4 截面盘"
" /volume1/stock/factor_cross_section,月度批 values/ 只含"
"注册表因子不含 quant12 12 源)")
ap.add_argument("--as-of", required=True)
ap.add_argument("--out-dir", required=True, help="报告根(factor_monthly)")
ap.add_argument("--vnpy-db", default=None)
ap.add_argument("--host", default="nas")
a = ap.parse_args(argv)
try:
path = run_challenge(a.values_dir, a.vnpy_db, a.as_of, a.out_dir, host=a.host)
except FileNotFoundError as e:
print(f"[challenger] skip: {e}")
return 0 # 无宽表=非错误(截面盘未建),不阻链
print(f"[challenger] 对拍件: {path}")
return 0
@@ -156,6 +156,25 @@ PY
rc_replay=$?
echo "=== $(date '+%F %T') stage5 verdict_replay exit=$rc_replay (派生件,不进 final) ==="
# stage6 challenger 三路对拍(影子实验,失败不阻月度链;产物落子目录
# 判定层端点只读根层零影响)。宽表源=stage4 截面盘(12 源主 parquet,
# daily_section 幂等 merge 累积)——月度批 values/ 只含注册表因子,
# 不含 quant12 12 源(2026-10-10 现场核实 NAS values/monthly_2026-09)。
"$DOCKER" run --rm --name sanguo-factor-chal --user "${UID_ADMIN}:${GID_ADMIN}" \
--group-add "${GID_ADMINS}" --no-healthcheck --entrypoint python \
-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl \
-v /volume1/stock:/volume1/stock \
-v "$APP":/app:ro \
-w /app \
sanguo_vnpy_v2:lock-aligned \
-m sanguo_factor.challenger_lgbm \
--values-dir /volume1/stock/factor_cross_section \
--as-of "$AS_OF" --out-dir "$OUT_DIR" \
--vnpy-db /volume1/stock/sanguo_vnpy_v2/data_backup/quant_trading.db \
--host nas
rc_chal=$?
echo "=== $(date '+%F %T') stage6 challenger exit=$rc_chal (影子,不进 final) ==="
# 真失败=②或③ rc=1(读件错);exit 2 是信号按 0 处理;④ 非零=真失败
final=0
[ "$rc_review" -eq 1 ] && final=1
+19
View File
@@ -145,3 +145,22 @@ def test_run_challenge_end_to_end(tmp_path, monkeypatch):
assert doc["pool"]["names"] == ["s0", "s1"]
assert len(doc["feature_importance"]) == 2
assert doc["split"]["oos_month"] == "2025-03"
def test_cli_smoke(tmp_path, monkeypatch, capsys):
from sanguo_factor import challenger_lgbm as cl
idx = pd.date_range("2025-01-01", "2025-03-31", freq="B")
close = pd.DataFrame(100 + np.cumsum(np.random.default_rng(1).normal(0.4, size=(len(idx), 2)), axis=0),
index=idx, columns=["a", "b"])
monkeypatch.setattr(cl, "_load_close_wide", lambda db, c, s, e: close)
monkeypatch.setattr(cl, "load_pool", lambda: {
"s0": {"direction": "+", "weight": 0.6},
"s1": {"direction": "-", "weight": 0.4}})
vdir = tmp_path / "vals"; vdir.mkdir()
for n in ("s0", "s1"):
pd.DataFrame(np.random.default_rng(2).normal(size=(len(idx), 2)),
index=idx, columns=["a", "b"]).to_parquet(vdir / f"{n}.parquet")
rc = cl.main(["--values-dir", str(vdir), "--as-of", "2025-03-31",
"--out-dir", str(tmp_path), "--vnpy-db", "fake.db"])
assert rc == 0
assert (tmp_path / "challenger_lgbm" / "nas_2025-03-31.json").exists()