Files
sanguo_vnpy_v2/scripts/factor_research/run_eval.py
T
claude_dev e5ed91d755 feat(factor): run_eval CLI暴露--fund-data-dir——容器财务批可传静态域(WP1-3收尾补工) [nas]
- --fund-data-dir default=None 透传 batch_eval;默认语义已有三层fallback:
  参数 → cfg.data_paths['static_dir'] → /volume1/stock/sanguo_vnpy_v2/data/static
  (None≠不join,CLI不传参容器内外同路径可跑财务批;量价批不含fundamental因子零开销)
- docstring补财务批用法示例(--categories fundamental)
- 验证: pytest tests/factor/ -x -q 145绿; --list-factors fundamental 列出32个

[nas]

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 09:31:52 +08:00

92 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
财务批(32因子;静态域默认 /volume1/stock/sanguo_vnpy_v2/data/static 容器内外同路径):
venv310/bin/python scripts/factor_research/run_eval.py \
--start 2024-01-01 --end 2026-06-30 --categories fundamental --label fund-p0
"""
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("--run-id", default=None, help="断点续跑:复用既有 run_id,跳过已落库因子")
ap.add_argument("--fund-data-dir", default=None,
help="财务静态域根目录;默认 cfg.data_paths['static_dir'] → "
"/volume1/stock/sanguo_vnpy_v2/data/static(容器内外同路径),"
"仅含 fundamental 因子时才读取")
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,
run_id=args.run_id, fund_data_dir=args.fund_data_dir)
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())