feat(factor): 批量评估CLI——冒烟/全量/列因子,进度打印+Top15汇总表(NAS docker exec 冒烟入口) [vps]

This commit is contained in:
2026-08-25 00:26:45 +08:00
parent d3226ebe71
commit dfa4f90ecd
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python
"""批量因子评估 CLI(冒烟/全量;NAS docker exec / 本地直跑).
用法示例:
冒烟(50只×2024×10因子):
venv310/bin/python scripts/factor_research/run_eval.py \
--start 2024-01-01 --end 2024-12-31 --limit 50 --label smoke-2024 \
--factors alpha2,alpha6,alpha12,alpha18,kmid,klen,roc_5,ma_20,std_20,wvma_20
全量(Alpha101+158 × 全A × 8.5年):
venv310/bin/python scripts/factor_research/run_eval.py --label batch1-full
"""
import argparse
import os
import sys
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")))
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--start", default="2018-01-01")
ap.add_argument("--end", default="2026-06-30")
ap.add_argument("--categories", nargs="*", default=["alpha101", "alpha158"])
ap.add_argument("--factors", default="", help="逗号分隔,优先于 --categories")
ap.add_argument("--symbols", default="", help="逗号分隔裸代码;空=全A")
ap.add_argument("--limit", type=int, default=None, help="随机抽样 N 只(种子42)")
ap.add_argument("--label", default="batch1")
ap.add_argument("--db", default=None, help="factor_eval.db 路径;默认 default_eval_db_path()")
ap.add_argument("--list-factors", default="", metavar="CATEGORY", help="列出类目因子后退出")
args = ap.parse_args()
from sanguo_factor.alpha_datasets import mount_all
mount_all()
from sanguo_factor.registry import list_factors
from sanguo_factor.batch_eval import run_batch_eval
from sanguo_factor.eval_store import default_eval_db_path, get_rows
if args.list_factors:
for f in list_factors(args.list_factors):
print(f"{f['name']:16s} [{f['category']}] {f['expression'][:80]}")
return 0
if args.factors:
factor_names = [s.strip() for s in args.factors.split(",") if s.strip()]
else:
factor_names = [f["name"] for c in args.categories for f in list_factors(c)]
if not factor_names:
print("未找到因子(检查 --categories/--factors)", file=sys.stderr)
return 1
db_path = args.db or default_eval_db_path()
symbols = [s.strip() for s in args.symbols.split(",") if s.strip()] or None
print(f"[eval] {len(factor_names)} 因子 × {args.start}~{args.end}{db_path}")
def _cb(done: int, total: int, current: str) -> None:
print(f"[eval] {done}/{total} {current}", flush=True)
out = run_batch_eval(factor_names, args.start, args.end, db_path, label=args.label,
symbols=symbols, limit=args.limit, cfg=None, progress_cb=_cb)
print(f"[eval] run_id={out['run_id']} done={out['factors_done']} "
f"errors={len(out['errors'])} elapsed={out['elapsed_sec']}s symbols={out['symbols_count']}")
if out["errors"]:
print(f"[eval] 失败因子: {', '.join(out['errors'][:20])}")
rows = get_rows(db_path, out["run_id"])
scored = []
for r in rows:
p1 = (r.get("metrics") or {}).get("1") or {}
if p1.get("icir") is not None:
scored.append((abs(p1["icir"]), r["factor"], p1))
scored.sort(reverse=True)
print(f"\n{'因子':<14s} {'IC':>8s} {'ICIR':>8s} {'t':>8s} {'胜率':>7s} 结论")
for _, name, p1 in scored[:15]:
print(f"{name:<14s} {p1['ic_mean']:>8.4f} {p1['icir']:>8.3f} "
f"{p1['t_stat']:>8.2f} {p1['win_rate']:>7.1%} {p1['conclusion']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())