quant_test / scripts /run_factor.py
lucky-loster's picture
Upload folder using huggingface_hub
590a501 verified
Raw
History Blame Contribute Delete
7.91 kB
#!/usr/bin/env python3
"""
Factor formula registry CLI.
Record formulas in config/factor_registry.yaml, then:
- compute: auto-calc via qlib
- backtest: single-factor IC + strategy backtest
- add: append a new formula to the registry file
"""
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from factor_engine.formula_registry import (
add_factor_to_registry,
build_combined_panel,
compute_all_factors,
compute_factor,
export_gp_seed_formulas,
list_factor_specs,
registry_output_dir,
)
from factor_engine.operator_builder import (
build_expression,
get_operator_catalog,
list_built_factors,
register_all_built_factors,
register_built_factor,
)
from factor_engine.single_factor_backtest import (
analyze_single_factor,
backtest_single_factor,
run_all_single_factor_backtests,
)
def cmd_list(args):
specs = list_factor_specs(enabled_only=args.enabled_only, tag=args.tag)
if not specs:
print("No factors in registry.")
return
for s in specs:
flag = "✓" if s.enabled else "✗"
tags = ",".join(s.tags or [])
print(f"[{flag}] {s.name}")
print(f" expr: {s.expression}")
if s.description:
print(f" desc: {s.description}")
if tags:
print(f" tags: {tags}")
def cmd_compute(args):
if args.all:
compute_all_factors(
enabled_only=not args.include_disabled,
start_time=args.start,
end_time=args.end,
cache=not args.no_cache,
)
print(f"All factors cached under: {registry_output_dir()}")
return
if not args.name:
raise SystemExit("Provide --name or --all")
s = compute_factor(args.name, start_time=args.start, end_time=args.end, cache=not args.no_cache)
print(f"Computed {args.name}: {s.notna().sum()} values -> {registry_output_dir() / args.name}.parquet")
def cmd_analyze(args):
res = analyze_single_factor(args.name, segment=args.segment, start_time=args.start, end_time=args.end)
print(json.dumps({**res, "ic_series": None, "quantile_spread": None}, ensure_ascii=False, indent=2, default=str))
print(f"\nIC metrics: {res['metrics']}")
def cmd_backtest(args):
if args.all:
run_all_single_factor_backtests(strategy_name=args.strategy, segment=args.segment)
return
if not args.name:
raise SystemExit("Provide --name or --all")
res = backtest_single_factor(
args.name,
strategy_name=args.strategy,
segment=args.segment,
start_time=args.start,
end_time=args.end,
)
print(json.dumps(res, ensure_ascii=False, indent=2, default=str))
def cmd_add(args):
spec = add_factor_to_registry(
name=args.name,
expression=args.expression,
description=args.description or "",
tags=[t.strip() for t in args.tags.split(",")] if args.tags else None,
)
print(f"Added factor: {spec.name}")
print(f" expression: {spec.expression}")
def cmd_export_panel(args):
panel = build_combined_panel(
factor_names=args.names.split(",") if args.names else None,
enabled_only=not args.include_disabled,
)
out = registry_output_dir() / "combined_panel.parquet"
panel.to_parquet(out, index=False)
print(f"Combined panel: {panel.shape} -> {out}")
def cmd_export_seeds(args):
out = export_gp_seed_formulas()
print(f"GP seed formulas exported: {out}")
def cmd_build(args):
if args.all:
exprs = register_all_built_factors(enabled_only=not args.include_disabled)
for name, expr in exprs.items():
print(f"[{name}] {expr}")
print(f"Registered {len(exprs)} built factors -> config/factor_registry.yaml")
return
if not args.name:
raise SystemExit("Provide --name or --all")
if args.register:
expr = register_built_factor(args.name)
print(f"Registered {args.name} -> {expr}")
else:
expr = build_expression(args.name)
print(f"{args.name}: {expr}")
def cmd_operators(args):
catalog = get_operator_catalog()
print("Operator catalog:")
for k, v in catalog.items():
print(f" {k}: {v}")
print("\nBuilt factors:")
for spec in list_built_factors(enabled_only=args.enabled_only):
flag = "✓" if spec.enabled else "✗"
print(f" [{flag}] {spec.name}{spec.description}")
def main():
parser = argparse.ArgumentParser(description="Factor formula registry: compute, analyze, backtest")
sub = parser.add_subparsers(dest="command", required=True)
p_list = sub.add_parser("list", help="List registered factors")
p_list.add_argument("--enabled-only", action="store_true")
p_list.add_argument("--tag", type=str, default=None)
p_list.set_defaults(func=cmd_list)
p_compute = sub.add_parser("compute", help="Compute factor(s) via qlib")
p_compute.add_argument("--name", type=str, default=None)
p_compute.add_argument("--all", action="store_true")
p_compute.add_argument("--start", type=str, default=None)
p_compute.add_argument("--end", type=str, default=None)
p_compute.add_argument("--no-cache", action="store_true")
p_compute.add_argument("--include-disabled", action="store_true")
p_compute.set_defaults(func=cmd_compute)
p_analyze = sub.add_parser("analyze", help="IC analysis for one factor")
p_analyze.add_argument("--name", type=str, required=True)
p_analyze.add_argument("--segment", type=str, default="test")
p_analyze.add_argument("--start", type=str, default=None)
p_analyze.add_argument("--end", type=str, default=None)
p_analyze.set_defaults(func=cmd_analyze)
p_bt = sub.add_parser("backtest", help="Single-factor IC + strategy backtest")
p_bt.add_argument("--name", type=str, default=None)
p_bt.add_argument("--all", action="store_true")
p_bt.add_argument("--strategy", type=str, default="topk_dropout")
p_bt.add_argument("--segment", type=str, default="test")
p_bt.add_argument("--start", type=str, default=None)
p_bt.add_argument("--end", type=str, default=None)
p_bt.set_defaults(func=cmd_backtest)
p_add = sub.add_parser("add", help="Add a new factor formula to registry YAML")
p_add.add_argument("--name", type=str, required=True)
p_add.add_argument("--expression", type=str, required=True)
p_add.add_argument("--description", type=str, default="")
p_add.add_argument("--tags", type=str, default="custom")
p_add.set_defaults(func=cmd_add)
p_panel = sub.add_parser("export-panel", help="Merge registry factors into one panel")
p_panel.add_argument("--names", type=str, default=None, help="Comma-separated factor names")
p_panel.add_argument("--include-disabled", action="store_true")
p_panel.set_defaults(func=cmd_export_panel)
p_seeds = sub.add_parser("export-seeds", help="Export formulas as GP mining seeds")
p_seeds.set_defaults(func=cmd_export_seeds)
p_build = sub.add_parser("build", help="Build qlib expression from operator tree")
p_build.add_argument("--name", type=str, default=None)
p_build.add_argument("--all", action="store_true", help="Register all built factors to factor_registry.yaml")
p_build.add_argument("--register", action="store_true", help="Write compiled expression into factor_registry.yaml")
p_build.add_argument("--include-disabled", action="store_true")
p_build.set_defaults(func=cmd_build)
p_ops = sub.add_parser("operators", help="Show operator catalog and built factor trees")
p_ops.add_argument("--enabled-only", action="store_true")
p_ops.set_defaults(func=cmd_operators)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()