PaperTrade / research /compare_predictors.py
Khanna, Videh Rakesh Rakesh
refactor: remove GitHub Models + Anthropic; unify LLM provider chain
6f2bcff
Raw
History Blame Contribute Delete
4.96 kB
#!/usr/bin/env python3
"""
Run backtest A/B comparison for two predictor labels and print side-by-side accuracy.
This script executes research/backtest.py twice on the exact same test set and cache,
while overriding source metadata via environment variables:
- AI_FORECAST_SOURCE_PROVIDER
- AI_FORECAST_SOURCE_MODEL
- AI_FORECAST_SOURCE_PREFIX
Example:
python research/compare_predictors.py \
--a-provider openrouter --a-model openai/gpt-oss-120b:free \
--b-provider groq --b-model llama-3.3-70b-versatile \
--use-cache
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[1]
RESEARCH = ROOT / "research"
BACKTEST = RESEARCH / "backtest.py"
def _safe_label(provider: str, model: str) -> str:
return f"{provider}_{model}".replace("/", "_").replace(":", "_").replace(" ", "_")
def _run_backtest(
provider: str,
model: str,
use_cache: bool,
refresh_cache: bool,
cache_dir: str,
out_csv: Path,
limit_work_items: int,
) -> None:
env = os.environ.copy()
env["AI_FORECAST_SOURCE_PROVIDER"] = provider
env["AI_FORECAST_SOURCE_MODEL"] = model
env["AI_FORECAST_SOURCE_PREFIX"] = "ai_forecast"
cmd = [
sys.executable,
str(BACKTEST),
"--csv-out",
str(out_csv),
"--cache-dir",
cache_dir,
]
if limit_work_items and limit_work_items > 0:
cmd.extend(["--limit-work-items", str(limit_work_items)])
if use_cache:
cmd.append("--use-cache")
if refresh_cache:
cmd.append("--refresh-cache")
print(f"\n=== Running backtest for {provider}:{model} ===")
proc = subprocess.run(cmd, cwd=str(ROOT), env=env)
if proc.returncode != 0:
raise RuntimeError(f"Backtest failed for {provider}:{model} (exit {proc.returncode})")
def _metrics(csv_path: Path) -> dict:
df = pd.read_csv(csv_path)
if df.empty:
return {"rows": 0, "overall_target_hit": 0.0, "tf": {}}
out = {
"rows": int(len(df)),
"overall_target_hit": float(df["target_hit_for_tf"].mean() * 100.0),
"tf": {},
}
for tf in ["1D", "3D", "5D"]:
sub = df[df["timeframe"] == tf]
out["tf"][tf] = {
"n": int(len(sub)),
"target_hit": float(sub["target_hit_for_tf"].mean() * 100.0) if len(sub) else 0.0,
"direction_hit": float(sub["intraday_hit_for_tf"].mean() * 100.0) if len(sub) else 0.0,
}
return out
def _print_compare(a_label: str, a: dict, b_label: str, b: dict) -> None:
print("\n=== Predictor Comparison ===")
print(f"A: {a_label}")
print(f"B: {b_label}")
print("")
print(f"Overall target-hit: {a_label}={a['overall_target_hit']:.2f}% | {b_label}={b['overall_target_hit']:.2f}%")
print(f"Rows: {a_label}={a['rows']} | {b_label}={b['rows']}")
print("")
print("Timeframe target-hit / direction-hit")
for tf in ["1D", "3D", "5D"]:
atf = a["tf"][tf]
btf = b["tf"][tf]
print(
f" {tf}: "
f"{a_label}={atf['target_hit']:.2f}%/{atf['direction_hit']:.2f}% (n={atf['n']}) | "
f"{b_label}={btf['target_hit']:.2f}%/{btf['direction_hit']:.2f}% (n={btf['n']})"
)
winner = a_label if a["overall_target_hit"] > b["overall_target_hit"] else b_label
if abs(a["overall_target_hit"] - b["overall_target_hit"]) < 1e-9:
winner = "TIE"
print("")
print(f"Winner (overall target-hit): {winner}")
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--a-provider", default="openrouter")
p.add_argument("--a-model", default="openai/gpt-oss-120b:free")
p.add_argument("--b-provider", default="groq")
p.add_argument("--b-model", default="llama-3.3-70b-versatile")
p.add_argument("--use-cache", action="store_true")
p.add_argument("--refresh-cache", action="store_true")
p.add_argument("--cache-dir", default=str(RESEARCH / "cache"))
p.add_argument("--limit-work-items", type=int, default=0, help="Run only first N work items per model")
args = p.parse_args()
a_label = f"{args.a_provider}:{args.a_model}"
b_label = f"{args.b_provider}:{args.b_model}"
out_a = RESEARCH / f"ai_prompt_accuracy_{_safe_label(args.a_provider, args.a_model)}.csv"
out_b = RESEARCH / f"ai_prompt_accuracy_{_safe_label(args.b_provider, args.b_model)}.csv"
_run_backtest(
args.a_provider,
args.a_model,
args.use_cache,
args.refresh_cache,
args.cache_dir,
out_a,
args.limit_work_items,
)
_run_backtest(
args.b_provider,
args.b_model,
args.use_cache,
args.refresh_cache,
args.cache_dir,
out_b,
args.limit_work_items,
)
a = _metrics(out_a)
b = _metrics(out_b)
_print_compare(a_label, a, b_label, b)