#!/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())