polyglot-alpha / scripts /run_backtest.py
licaomeng
deploy: main@8970ffb → HF Spaces (2026-05-27T05:19Z)
88d2f2a
"""CLI for the PolyglotAlpha v2 backtest framework.
Usage:
python scripts/run_backtest.py --n 100 --random-seed 42 \\
--out outputs/backtest/ --mock-llm
python scripts/run_backtest.py --n 20 --real-llm
Set ``--real-llm`` only if ``GEMINI_API_KEY`` or ``OPENROUTER_API_KEY``
is in the environment (the LLM layer falls back to the deterministic
``MockLLM`` if no key is set).
"""
from __future__ import annotations
import argparse
import logging
import sys
import time
from pathlib import Path
# Allow running this file directly without an editable install.
_REPO_ROOT = Path(__file__).resolve().parents[1]
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from polyglot_alpha.backtest.runner import ( # noqa: E402
DEFAULT_OUTPUT_DIR,
DEFAULT_RESOLVED_PARQUET,
run_backtest,
)
LOGGER = logging.getLogger("polyglot_alpha.backtest.cli")
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--n", type=int, default=100, help="Number of markets to backtest.")
parser.add_argument(
"--random-seed", type=int, default=42, help="Seed for reproducibility."
)
parser.add_argument(
"--out",
type=Path,
default=DEFAULT_OUTPUT_DIR,
help="Output directory for results (default: outputs/backtest/).",
)
parser.add_argument(
"--resolved-parquet",
type=Path,
default=DEFAULT_RESOLVED_PARQUET,
help=f"Path to resolved markets parquet (default: {DEFAULT_RESOLVED_PARQUET}).",
)
mode = parser.add_mutually_exclusive_group()
mode.add_argument(
"--mock-llm",
action="store_true",
default=True,
help="Use deterministic mock LLM (default; fast).",
)
mode.add_argument(
"--real-llm",
action="store_false",
dest="mock_llm",
help="Use real LLM via existing make_llm() (slower; needs API key).",
)
parser.add_argument(
"--no-embeddings",
action="store_true",
help="Force the deterministic Jaccard similarity fallback.",
)
parser.add_argument("--verbose", action="store_true", help="Enable INFO logging.")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = _parse_args(argv)
logging.basicConfig(
level=logging.INFO if args.verbose else logging.WARNING,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
started = time.time()
summary = run_backtest(
n=args.n,
seed=args.random_seed,
output_dir=args.out,
mock_llm=args.mock_llm,
use_embeddings=not args.no_embeddings,
parquet_path=args.resolved_parquet,
)
elapsed = time.time() - started
print(
f"Backtest done in {elapsed:.1f}s | n={summary.get('n_markets', 0)} | "
f"accuracy={summary.get('outcome_accuracy', 0) * 100:.1f}% | "
f"ROI=${summary.get('estimated_total_roi_usdc', 0):,.2f} | "
f"PASS={summary.get('n_PASS', 0)} FAIL={summary.get('n_FAIL', 0)} "
f"BORDERLINE={summary.get('n_BORDERLINE', 0)}"
)
print(f"Artifacts: {args.out}")
return 0
if __name__ == "__main__": # pragma: no cover
sys.exit(main())