Trifecta-Lab / optimize.py
Brettapps's picture
Upload folder using huggingface_hub (part 21)
e23172f verified
Raw
History Blame Contribute Delete
12.3 kB
"""Optimized weight finder using random search on cached data."""
import json
import random
import math
from pathlib import Path
from collections import defaultdict
import itertools
import sys
sys.path.insert(0, '/home/brettanthonysjoberg179/trifecta-bro-hf-space')
from trifecta_bro.data.models import RaceModel, RunnerModel
from trifecta_bro.model.scoring import score_runner
from trifecta_bro.model.pace_analysis import classify_pace
from trifecta_bro.model.distance_profiles import classify_distance
DATA_DIR = Path('/home/brettanthonysjoberg179/trifecta-bro-hf-space/data')
CACHE_DIR = DATA_DIR / 'cache'
RESULTS_DIR = DATA_DIR / 'results'
random.seed(42)
def load_cached_races(date: str) -> list[dict]:
"""Load all cached form data for a date."""
races = []
for f in CACHE_DIR.glob('*.json'):
try:
with open(f) as fp:
data = json.load(fp)
if data.get('date') == date:
races.append(data)
except:
continue
return races
def load_results(date: str) -> dict:
"""Load actual results for a date."""
result_lookup = {}
result_path = RESULTS_DIR / f"{date}-ra.json"
if not result_path.exists():
result_path = RESULTS_DIR / f"{date}.json"
if not result_path.exists():
return result_lookup
with open(result_path) as f:
rdata = json.load(f)
if isinstance(rdata, dict):
if 'tracks' in rdata:
for track, races in rdata['tracks'].items():
if isinstance(races, dict):
for rn, r in races.items():
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:
result_lookup[(track, int(rn))] = actual
elif isinstance(races, list):
for r in races:
if 'trifecta' in r:
result_lookup[(track, r.get('race', 0))] = [int(x) for x in r['trifecta']]
else:
for track, races in rdata.items():
if isinstance(races, list):
for r in races:
if 'trifecta' in r:
result_lookup[(track, r['race'])] = [int(x) for x in r['trifecta']]
return result_lookup
def score_race_with_weights(race_data: dict, weights: dict) -> list[int] | None:
"""Score a race with given weights, return trifecta."""
try:
runners = []
for r in race_data.get('runners', []):
if r.get('scratched'):
continue
runner = RunnerModel(
number=r['number'],
name=r.get('name', ''),
jockey=r.get('jockey'),
trainer=r.get('trainer'),
weight=r.get('weight'),
barrier=r.get('barrier'),
age=r.get('age'),
sex=r.get('sex'),
form=r.get('form', ''),
last20_starts=r.get('last20Starts', ''),
stats=r.get('stats', {}),
)
runners.append(runner)
if len(runners) < 3:
return None
race = RaceModel(
date=race_data.get('date', ''),
track=race_data.get('track', ''),
track_slug=race_data.get('slug', race_data.get('track', '').lower()),
race_number=race_data.get('raceNumber', 0),
race_name=race_data.get('raceName', ''),
distance=race_data.get('distance'),
condition=race_data.get('condition'),
race_class=race_data.get('raceClass'),
abandoned=race_data.get('abandoned', False),
start_time=race_data.get('startTime'),
prize_money=str(race_data.get('prizeMoney', '')),
number_of_runners=race_data.get('numberOfRunners', len(runners)),
runners=runners,
)
pace = classify_pace(race, runners)
scored = []
for r in runners:
sc = score_runner(r, race, pace, weights)
scored.append((r.number, sc['score']))
scored.sort(key=lambda x: x[1], reverse=True)
return [s[0] for s in scored[:3]]
except:
return None
def evaluate_weights_on_date(date: str, weights: dict, result_lookup: dict) -> dict:
"""Evaluate weights on a single date."""
races = load_cached_races(date)
stats = {"total": 0, "top1": 0, "exact": 0, "box": 0}
for race_data in races:
track = race_data.get('track', '')
race_num = race_data.get('raceNumber', 0)
actual = result_lookup.get((track, race_num))
if not actual:
continue
pred = score_race_with_weights(race_data, weights)
if not pred:
continue
stats["total"] += 1
if pred == actual:
stats["exact"] += 1
if set(pred) == set(actual):
stats["box"] += 1
if pred[0] == actual[0]:
stats["top1"] += 1
return stats
def evaluate_weights_all_dates(dates: list[str], weights: dict) -> dict:
"""Evaluate weights across all dates."""
agg = {"total": 0, "top1": 0, "exact": 0, "box": 0}
for date in dates:
result_lookup = load_results(date)
stats = evaluate_weights_on_date(date, weights, result_lookup)
agg["total"] += stats["total"]
agg["top1"] += stats["top1"]
agg["exact"] += stats["exact"]
agg["box"] += stats["box"]
agg["top1_pct"] = agg["top1"] / agg["total"] * 100 if agg["total"] > 0 else 0
return agg
def random_weights() -> dict:
"""Generate random weights that sum to ~1.0."""
weights = {}
for k in ["form", "class", "distance", "track", "track_distance",
"condition", "jockey", "fitness", "barrier", "weight", "pace"]:
weights[k] = random.random()
total = sum(weights.values())
return {k: v / total for k, v in weights.items()}
def random_search(dates: list[str], n_iterations: int = 200) -> list[dict]:
"""Random search for optimal weights."""
results = []
# Also test the base weights
base_weights = {
"form": 0.18, "class": 0.14, "distance": 0.10, "track": 0.10,
"track_distance": 0.10, "condition": 0.05, "jockey": 0.06,
"fitness": 0.08, "barrier": 0.08, "weight": 0.08, "pace": 0.03,
}
# Evaluate base
base_stats = evaluate_weights_all_dates(dates, base_weights)
results.append({"weights": base_weights, "stats": base_stats, "top1_pct": base_stats["top1_pct"]})
print(f"Base weights: {base_stats['top1_pct']:.1f}%")
# Random search
for i in range(n_iterations):
weights = random_weights()
stats = evaluate_weights_all_dates(dates, weights)
results.append({"weights": weights, "stats": stats, "top1_pct": stats["top1_pct"]})
if (i + 1) % 50 == 0:
best_so_far = max(results, key=lambda x: x["top1_pct"])
print(f" Iteration {i+1}: best = {best_so_far['top1_pct']:.1f}%")
results.sort(key=lambda x: x["top1_pct"], reverse=True)
return results
def coordinate_descent(dates: list[str], base_weights: dict, n_rounds: int = 10) -> dict:
"""Optimize one parameter at a time while holding others fixed."""
weights = dict(base_weights)
param_ranges = {
"form": [0.10, 0.15, 0.20, 0.25, 0.30],
"class": [0.08, 0.12, 0.16, 0.20],
"distance": [0.04, 0.08, 0.12, 0.16],
"track": [0.04, 0.08, 0.12, 0.16],
"track_distance": [0.04, 0.08, 0.12, 0.16],
"condition": [0.02, 0.05, 0.08],
"jockey": [0.02, 0.05, 0.08, 0.10],
"fitness": [0.04, 0.08, 0.12],
"barrier": [0.04, 0.08, 0.12, 0.16],
"weight": [0.04, 0.08, 0.12],
"pace": [0.02, 0.04, 0.06, 0.08],
}
current_best = evaluate_weights_all_dates(dates, weights)
current_pct = current_best["top1_pct"]
print(f"Starting coordinate descent from {current_pct:.1f}%")
for round_num in range(n_rounds):
improved = False
for param in param_ranges:
best_value = weights[param]
best_pct = current_pct
for value in param_ranges[param]:
test_weights = dict(weights)
test_weights[param] = value
# Normalize
total = sum(test_weights.values())
test_weights = {k: v / total for k, v in test_weights.items()}
stats = evaluate_weights_all_dates(dates, test_weights)
if stats["top1_pct"] > best_pct:
best_pct = stats["top1_pct"]
best_value = value
improved = True
weights[param] = best_value
# Normalize after each round
total = sum(weights.values())
weights = {k: v / total for k, v in weights.items()}
current_best = evaluate_weights_all_dates(dates, weights)
current_pct = current_best["top1_pct"]
print(f" Round {round_num + 1}: {current_pct:.1f}%")
if not improved:
print(f" No improvement, stopping")
break
return {"weights": weights, "stats": current_best, "top1_pct": current_pct}
def main():
dates = ["2026-08-07", "2026-08-08", "2026-08-09", "2026-08-14"]
print("=" * 60)
print("WEIGHT OPTIMIZATION")
print("=" * 60)
# Test base weights first
base_weights = {
"form": 0.18, "class": 0.14, "distance": 0.10, "track": 0.10,
"track_distance": 0.10, "condition": 0.05, "jockey": 0.06,
"fitness": 0.08, "barrier": 0.08, "weight": 0.08, "pace": 0.03,
}
print("\n--- Base weights ---")
base_stats = evaluate_weights_all_dates(dates, base_weights)
print(f"Top 1: {base_stats['top1_pct']:.1f}% ({base_stats['top1']}/{base_stats['total']})")
# Random search
print("\n--- Random search ---")
random_results = random_search(dates, n_iterations=200)
print("\n--- Top 5 from random search ---")
for i, r in enumerate(random_results[:5]):
w = r['weights']
print(f"{i+1}. Top1: {r['top1_pct']:.1f}% | "
f"form={w['form']:.2f} class={w['class']:.2f} dist={w['distance']:.2f} "
f"track={w['track']:.2f} td={w['track_distance']:.2f} "
f"fitness={w['fitness']:.2f} barrier={w['barrier']:.2f} "
f"weight={w['weight']:.2f} pace={w['pace']:.2f}")
# Coordinate descent from best random
best_random = random_results[0]
print("\n--- Coordinate descent from best random ---")
cd_result = coordinate_descent(dates, best_random["weights"])
print("\n--- Final optimized weights ---")
print(f"Top 1: {cd_result['top1_pct']:.1f}% ({cd_result['stats']['top1']}/{cd_result['stats']['total']})")
w = cd_result['weights']
print(f"form={w['form']:.2f}, class={w['class']:.2f}, distance={w['distance']:.2f}, "
f"track={w['track']:.2f}, track_distance={w['track_distance']:.2f}, "
f"condition={w['condition']:.2f}, jockey={w['jockey']:.2f}, "
f"fitness={w['fitness']:.2f}, barrier={w['barrier']:.2f}, "
f"weight={w['weight']:.2f}, pace={w['pace']:.2f}")
# Per-date breakdown
print("\nPer-date breakdown:")
for date in dates:
result_lookup = load_results(date)
stats = evaluate_weights_on_date(date, cd_result["weights"], result_lookup)
if stats['total'] > 0:
print(f" {date}: {stats['total']} races, {stats['top1']} top1 ({stats['top1']/stats['total']*100:.0f}%)")
return cd_result
if __name__ == "__main__":
main()