Spaces:
Configuration error
Configuration error
Download trifecta_bro/evaluation/backtest.py from Brettapps/Trifecta-Lab: direct link, hf CLI and curl.
- Browser
- Download file 7.02 kB
-
https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/evaluation/backtest.py
- Command line
-
hf download hf://spaces/Brettapps/Trifecta-Lab/trifecta_bro/evaluation/backtest.py
-
curl -L -o backtest.py https://huggingface.co/spaces/Brettapps/Trifecta-Lab/resolve/main/trifecta_bro/evaluation/backtest.py
7.02 kB
| """Backtesting harness for trifecta model optimization. | |
| Evaluates any weight profile against historical results with: | |
| - Leave-one-day-out cross-validation | |
| - Grid search over weight space | |
| - Statistical significance testing | |
| """ | |
| from __future__ import annotations | |
| import itertools | |
| import json | |
| import logging | |
| from pathlib import Path | |
| from typing import Optional | |
| logger = logging.getLogger("trifecta_bro.backtest") | |
| def evaluate_prediction(pred_nums: list[int], actual_nums: list[int]) -> dict: | |
| """Compare a prediction against actual results.""" | |
| if not pred_nums or not actual_nums: | |
| return {"exact": False, "box": False, "top1": False, "top3": 0} | |
| return { | |
| "exact": pred_nums == actual_nums, | |
| "box": set(pred_nums) == set(actual_nums), | |
| "top1": pred_nums[0] == actual_nums[0], | |
| "top3": len(set(pred_nums) & set(actual_nums)), | |
| } | |
| def evaluate_day( | |
| predictions: list[dict], | |
| results: list[dict], | |
| ) -> dict: | |
| """Evaluate all predictions for a single day.""" | |
| # Build result lookup | |
| result_lookup = {} | |
| for r in results: | |
| key = (r.get("track", ""), r.get("race", 0)) | |
| actual = r.get("trifecta", []) | |
| if actual: | |
| result_lookup[key] = actual | |
| stats = { | |
| "total": 0, | |
| "exact": 0, | |
| "box": 0, | |
| "top1": 0, | |
| "top3_total": 0, | |
| } | |
| for pred in predictions: | |
| track = pred.get("track", "") | |
| race = pred.get("race", 0) | |
| pred_nums = pred.get("trifecta", []) | |
| actual = result_lookup.get((track, race)) | |
| if not actual or pred.get("skipped"): | |
| continue | |
| stats["total"] += 1 | |
| result = evaluate_prediction(pred_nums, actual) | |
| stats["exact"] += result["exact"] | |
| stats["box"] += result["box"] | |
| stats["top1"] += result["top1"] | |
| stats["top3_total"] += result["top3"] | |
| if stats["total"] > 0: | |
| stats["top1_pct"] = stats["top1"] / stats["total"] * 100 | |
| stats["top3_pct"] = stats["top3_total"] / (stats["total"] * 3) * 100 | |
| return stats | |
| def load_day_data(date: str, data_dir: str = "/home/brettanthonysjoberg179/trifecta-bro-hf-space/data") -> tuple: | |
| """Load predictions and results for a single day.""" | |
| # Load predictions | |
| pred_path = Path(data_dir) / "reports" / f"predictions-{date}.json" | |
| if not pred_path.exists(): | |
| return [], [] | |
| with open(pred_path) as f: | |
| pred_data = json.load(f) | |
| predictions = pred_data.get("predictions", []) | |
| # Load results | |
| results = [] | |
| # Helper to parse runner numbers (handle "13e" emergency runners) | |
| def parse_num(s): | |
| return int(str(s).replace("e", "").replace("E", "")) | |
| # Try multiple result file formats | |
| result_path = Path(data_dir) / "results" / f"{date}.json" | |
| if result_path.exists(): | |
| with open(result_path) as f: | |
| rdata = json.load(f) | |
| if "tracks" in rdata: | |
| for track, races in rdata["tracks"].items(): | |
| if isinstance(races, list): | |
| for r in races: | |
| if "trifecta" in r: | |
| results.append({ | |
| "track": track, | |
| "race": r.get("race", r.get("race_number", 0)), | |
| "trifecta": [parse_num(x) for x in r["trifecta"]], | |
| }) | |
| elif isinstance(races, dict): | |
| for rn, r in races.items(): | |
| if "runners" in r: | |
| runners = r.get("runners", []) | |
| winners = [] | |
| for runner in runners: | |
| pos = runner.get("position") | |
| if pos and pos <= 3: | |
| winners.append((pos, runner["number"])) | |
| winners.sort() | |
| actual = [w[1] for w in winners] | |
| if actual: | |
| results.append({ | |
| "track": track, | |
| "race": int(rn), | |
| "trifecta": actual, | |
| }) | |
| # Try alternate result file | |
| alt_path = Path(data_dir) / "results" / f"{date}-ra.json" | |
| if alt_path.exists() and not results: | |
| with open(alt_path) as f: | |
| rdata = json.load(f) | |
| for track, races in rdata.items(): | |
| for r in races: | |
| if "trifecta" in r: | |
| results.append({ | |
| "track": track, | |
| "race": r["race"], | |
| "trifecta": [parse_num(x) for x in r["trifecta"]], | |
| }) | |
| return predictions, results | |
| def cross_validate( | |
| dates: list[str], | |
| weight_profile: dict, | |
| ) -> dict: | |
| """Run leave-one-day-out cross-validation.""" | |
| all_predictions = {} | |
| all_results = {} | |
| for date in dates: | |
| predictions, results = load_day_data(date) | |
| if predictions and results: | |
| all_predictions[date] = predictions | |
| all_results[date] = results | |
| if not all_predictions: | |
| return {"error": "No data loaded"} | |
| # Evaluate each day | |
| day_stats = {} | |
| for date in sorted(all_predictions.keys()): | |
| stats = evaluate_day(all_predictions[date], all_results[date]) | |
| day_stats[date] = stats | |
| # Aggregate | |
| total_races = sum(s["total"] for s in day_stats.values()) | |
| total_top1 = sum(s["top1"] for s in day_stats.values()) | |
| total_exact = sum(s["exact"] for s in day_stats.values()) | |
| return { | |
| "dates": list(day_stats.keys()), | |
| "day_stats": day_stats, | |
| "total_races": total_races, | |
| "total_top1": total_top1, | |
| "total_exact": total_exact, | |
| "top1_pct": total_top1 / total_races * 100 if total_races > 0 else 0, | |
| "profile": weight_profile, | |
| } | |
| def grid_search( | |
| dates: list[str], | |
| param_grid: dict, | |
| base_profile: dict, | |
| ) -> list[dict]: | |
| """Grid search over weight combinations.""" | |
| results = [] | |
| # Generate all combinations | |
| param_names = list(param_grid.keys()) | |
| param_values = [param_grid[name] for name in param_names] | |
| for combo in itertools.product(*param_values): | |
| profile = dict(base_profile) | |
| for name, value in zip(param_names, combo): | |
| profile[name] = value | |
| # Normalize weights to sum to 1.0 | |
| total_w = sum(profile.values()) | |
| if total_w > 0: | |
| profile = {k: v / total_w for k, v in profile.items()} | |
| # Evaluate | |
| cv_result = cross_validate(dates, profile) | |
| cv_result["params"] = dict(zip(param_names, combo)) | |
| results.append(cv_result) | |
| # Sort by top1_pct | |
| results.sort(key=lambda x: x.get("top1_pct", 0), reverse=True) | |
| return results | |