Autonomous optimization of NCAA tournament spread predictions.
- Agree on a run tag: e.g.
mar29 - Create the branch:
git checkout -b autoresearch/<tag> - Read these files for full context:
config.py— metric (MAE), guard rails, known dead endsprepare.py— data loading (1,048 tournament games, 2009-2025) + evaluationexperiment.py— the spread formula you modify
- Verify data:
ls ~/.cache/autoresearch/march-madness/data/. If missing:uv run prepare.py - Initialize results.tsv with header row
- Confirm and go
Launch: uv run experiment.py
Modify only: experiment.py
Do NOT modify: prepare.py, config.py
Goal: lowest MAE (mean absolute error of predicted margin vs actual margin)
Secondary metrics (win_accuracy, ats_accuracy, per-bucket breakdown) are printed for context but MAE drives keep/discard.
---
mae: 9.1234
rmse: 11.5678
win_accuracy: 0.7500
ats_accuracy: 0.5200
total_seconds: 1.3
Bucket breakdown (test set):
toss_up n= 28 mae=8.2 win%=57.1%
lean n= 35 mae=7.8 win%=74.3%
solid n= 42 mae=9.1 win%=83.3%
blowout n= 15 mae=12.4 win%=93.3%
Extract: grep "^mae:" run.log
results.tsv — tab-separated, 4 columns:
commit mae status description
a1b2c3d 9.4500 keep baseline — BL=1.1 * emDiff/100 * avgPace
LOOP FOREVER:
- Check git state and read
results.tsv+combat_log.md - Think of an experimental idea (see "Research directions" below)
- Modify
experiment.py git commituv run experiment.py > run.log 2>&1grep "^mae:" run.log- Record in
results.tsv - If MAE improved → keep. If worse →
git reset --hard HEAD~1, log incombat_log.md
NEVER STOP. Run experiments continuously until interrupted.
Each game in the dataset:
game = {
"year": 2024,
"date": "2024-03-21",
"round": "r64", # first_four, r64, r32, s16, e8, f4, championship
"fav": {
"name": "UConn Huskies",
"seed": 1,
"kp_rank": 1,
"kp_em": 35.22, # KenPom Adjusted Efficiency Margin (per 100 possessions)
"kp_tempo": 68.5, # KenPom Adjusted Tempo (possessions per game)
"score": 87
},
"dog": {
"name": "Stetson Hatters",
"seed": 16,
"kp_rank": 230,
"kp_em": -5.41,
"kp_tempo": 64.2,
"score": 52
},
"seed_diff": -15, # fav_seed - dog_seed (negative = bigger mismatch)
"em_diff": 40.63, # fav_kp_em - dog_kp_em (always positive)
"actual_margin": 35 # fav_score - dog_score (positive = fav won)
}Train: ~930 games (2009-2023). Test: ~120 games (2024-2025).
Ideas to explore (not exhaustive — get creative):
- Scaling factor (BLOWOUT): currently 1.1, try other values
- Pace weighting: does pace matter equally for all games? High-pace vs low-pace dynamics
- Nonlinear transforms: sqrt compression, log scaling, piecewise functions on em_diff
- Per-round adjustments: R64 might behave differently than F4
- Seed-based corrections: 1v16 might need different treatment than 5v12
- Rank difference (kp_rank): does ordinal rank add signal beyond em_diff?
- Tempo mismatch: when fast team plays slow team, what happens?
- Seed upset probability: historical upset rates by seed matchup
- Round progression: do margins compress as tournament progresses?
- Linear regression on multiple features
- Polynomial features on em_diff
- Binned predictions: learn average margin per em_diff bucket
- Weighted training: recent years weighted more than older years
- Formula + correction: base formula + learned residual
- Multi-model blend: average multiple approaches
- KenPom AdjEM is per 100 possessions. Multiply by (avg_pace / 100) to get per-game margin.
- All games are neutral site (tournament). No home court advantage.
- "Favorite" is the team with higher KenPom AdjEM (not necessarily higher seed).
- Tournament margins are nonlinear: KenPom overshoots on 1v16/2v15 (blowout predictions too high) and undershoots on 7v10/8v9 (toss-up predictions too low).
- Sqrt compression (sign * k * sqrt(|spread|/k), k=15) was derived from this dataset and showed -0.8 MAE improvement in cross-validation. The agent may rediscover this or find something better.
These have been extensively tested in the main march-madness repo:
| Approach | Result | Why it failed |
|---|---|---|
| ATS coverage optimization | Smaller spreads, worse margin prediction | Rewards timidity, not accuracy |
| BLOWOUT > 1.1 | +noise, no improvement | More aggression = worse on neutral games |
| Momentum factors | Degrades upset prediction | Refiner, not a flipper — and we have no momentum data here |
| Conference strength | Added noise | Not predictive in tournament setting |
| Rest day adjustments | No signal | Tournament games are scheduled, not random |
| MAE < 8.0 target | Spreads hug margins, no betting edge | Sweet spot is 8-10 for practical use |