← back

writings · 2026-10-15 · 5 min

A Betting Agent on Live Data

A side project to see if an AI-built agent can run on live sports data every day without me. The pipeline works. The edge does not, yet. So it's open source.

Diagram of the betting agent's daily loop, from candidates through a rule gate to the card, then settle, closing line value and a learn step that feeds rules back to the gate

This is a fun side project, not a business. I wanted to know if I could build something with AI that runs on its own, on live data, every day, and keeps an honest record of whether it is any good. Sports betting is the test case because the feedback is fast and the market is brutal. Every pick gets graded within a day, and the closing line tells you whether you were smart or lucky.

It has been running since July. Here is what it looks like, where it struggles, and why it's now on GitHub.

What runs every morning

At 7:30 a launchd job runs 20 steps. It pulls schedules for the NFL, college football and the NHL from ESPN. It pulls one paid odds snapshot from The Odds API (six US books, three credits a pull, so it rotates which league gets the paid pull each day). It pulls weather for every outdoor venue from api.weather.gov, injuries, college ratings from CollegeFootballData, and a small ration of player props. Then it settles yesterday's picks from box scores, grades every logged pick against the closing line, and writes the dashboard.

A second job runs every 30 minutes and pulls free lines: Pinnacle's guest API for the sharp reference price, and DraftKings as ESPN embeds it, because that is the line I can actually bet. Rows captured inside 75 minutes of kickoff become the close, so closing line value is graded against a real number instead of a guess.

Diagram of the pipeline: seven live sources on the left, the fetch steps in the middle, flat files on the right, and the single football.py agent underneath
Seven sources, five of them keyless. Everything lands in plain CSV, JSON and markdown inside my Obsidian vault. There is no database, which means I can open any state in a text editor and see exactly what the agent saw.

All of it is plain files. The odds log is 97,000 rows and 180MB on disk, and when it hit 66MB in one file the agent started archiving rows older than 30 days. The ledger is a CSV. The rules it has learned are a JSON file. I did this on purpose: when the output looks wrong I want to read the input, not query it.

The whole thing is about 14,700 lines of Python across 50 scripts, all written with Claude Code. One file, football.py, is the agent. Everything else is a fetcher, a settler, or research that lost.

The loop

The agent does not predict games. That was the first thing that failed. I backtested an Elo model against 715 college games with real closing lines and it landed at 52.5, 49.3, 44.2 and 56.4 percent against the spread by confidence bucket. That is noise around the 52.4 percent you need to break even. An NFL SRS model on 208 games had the same shape. The closing line predicted the margin better than either model on every axis.

So the agent is price-first now. It takes both sides of every market, works out Pinnacle's no-vig fair probability at the same line, and asks one question: is DraftKings offering a better price than fair? Ratings, weather and situational flags are context for me, never a gate.

Diagram of the daily loop: candidates, edge versus Pinnacle, the gate, the card, the human bet, then settle, closing line value and learn feeding rules back into the gate
Price in, rule out. The learn step grades every slice of the record, and any league and market combination that is under water on 30 or more graded rows gets written into a block list the gate reads the next morning.

The gate is the part I care about most. A pick has to clear an edge floor, the price cannot be stale, and the slice it belongs to cannot be blocked. For parlays the rules are hard: three legs at most, every leg at 56 percent or better, half a unit, one a day. If a ticket breaks a rule it prints BLOCKED and the place command refuses to log it.

Terminal output showing the parlay builder returning BLOCKED because only one leg cleared the floor, and the status command showing a 49-40-3 record
Thursday's college slate. One leg cleared the floor, so no ticket. The agent never wagers; it proposes, and a written rule decides. I place what survives, by hand.

Where it struggles

The honest part. The record since the price-first rewrite is 49-40-3, up about four units flat, beating the close 41 percent of the time. That is not under water, and it is nowhere near proof of an edge. The old model-driven totals went 11-21 and lost nearly 11 units, which is why totals are price-only now.

The specific things that keep eating time:

Team names. Every feed spells teams differently. Pinnacle says Ole Miss, The Odds API says Ole Miss Rebels, ESPN says Mississippi. There is an alias table and an unmatched log, and this week it still mislabeled a Missouri game as Mississippi State.

Credits. The paid odds pull is rationed, so most of the day the agent is comparing a fresh DraftKings line to a morning snapshot of the other books. That gap hides edges and invents them.

The parlay pool. The builder only admits games where DraftKings disagrees with Pinnacle, so a plain 70 percent favorite with a fair price never gets in. On a Saturday with twenty favorites it found one leg.

The hold. Every favorite at a fair price loses about four percent to the book's margin. The agent knows this and sizes those at zero units, which is correct and also means most days it tells me not to bet.

It's open source

The whole thing is on GitHub: github.com/skeltonmedia/football-picks-agent. Not because it wins, because it doesn't yet. Because the plumbing is the interesting part: keyless sources, flat files, a settle step that grades itself against the close, and a learn step that writes rules the gate enforces the next day. I suspect other people are building the same thing and hitting the same walls, and I'd rather compare notes than keep guessing alone.

What's in the repo is every script and doc, the launchd schedules with paths blanked, the example config, and the alias table. What's not: my keys, my ledgers, and the 180MB odds log, which regenerates itself after a day of running. Standard library only, so it runs on a plain Python 3.9.

If that is you, the questions I have are simple. Where does a price-first agent find edge the market prices slowly? What would you log that I'm not? And how are you handling team names, because I refuse to believe the alias table is the answer. Open an issue.

share