Most football data APIs will tell you what the odds are right now. A few keep yesterday around. Almost none will show you the full journey — every price change from the moment a bookmaker opened the line to the final whistle.
That journey is where the interesting information lives. A 1X2 price drifting from 2.10 to 1.85 over three days tells you which way the money went. A corner line jumping half a goal at kickoff tells you how the market read the starting lineups. If you've ever wanted to study closing line value, detect steam moves, or just chart how a price evolved, you need ticks, not snapshots.
This tutorial builds a small odds-movement tracker in Python. We'll use 5DollarFootballAPI, which stores the complete tick history for every price it has recorded since 2014 — including corner and card lines, which most APIs don't carry at all.
Disclosure: I build that API. The technique here isn't specific to it — anything that serves you a price history works the same way. I'm using mine because I know exactly what's in it.
Setup
pip install fivedollarfootball
Grab an API key at 5dollarfootballapi.com. Worth knowing before you start, so nothing in this tutorial surprises you at the paywall:
- Free (no card): fixtures, live scores, standings — enough for Step 1.
- $5/mo: current odds, which is Step 2.
- $25/mo: the tick history in Steps 3–5. Storing every price change for every market is the expensive part, and it's the tier this tutorial really needs.
from fivedollarfootball import Client
client = Client("fb_live_your_key")
Step 1: find today's matches
The fixtures endpoint defaults to a today-UTC kickoff window:
for match in client.fixtures():
teams = match["teams"]
print(match["id"], teams["home"]["name"], "vs", teams["away"]["name"], "-", match["status"])
Pick a fixture id from the output — ideally one a few hours from kickoff, so the market has had time to move.
Step 2: what do the odds look like right now?
odds = client.fixture_odds(FIXTURE_ID, bookmakers=["bet365"], market="1x2")
print(odds)
This is the snapshot every API gives you. Now for the part most of them can't do.
Step 3: pull the full movement history
history = client.odds_history(FIXTURE_ID, market="1x2", bookmaker="bet365")
for tick in history:
print(tick["recorded_at"], tick["home"], tick["draw"], tick["away"])
Each tick carries the price at that moment, the running score, the match minute (None before kickoff), and a UTC timestamp:
{
"minute": null,
"home": 2.05,
"draw": 3.40,
"away": 3.60,
"score": { "home": null, "away": null },
"recorded_at": "2026-08-18T14:32:07+00:00"
}
The endpoint is paginated; iter_all walks every page for you:
ticks = list(client.iter_all(client.odds_history, fixture_id=FIXTURE_ID, market="1x2"))
Step 4: find the biggest pre-match moves
Let's answer a concrete question: which side did the market move against before kickoff?
prematch = [t for t in ticks if t["minute"] is None and t["home"] is not None]
if len(prematch) >= 2:
opening, closing = prematch[0], prematch[-1]
for side in ("home", "draw", "away"):
move = (closing[side] - opening[side]) / opening[side] * 100
arrow = "▼ backed" if move < 0 else "▲ drifted"
print(f"{side:>5}: {opening[side]:.2f} → {closing[side]:.2f} {arrow} {abs(move):.1f}%")
Sample output:
home: 2.05 → 1.87 ▼ backed 8.8%
draw: 3.40 → 3.55 ▲ drifted 4.4%
away: 3.60 → 4.10 ▲ drifted 13.9%
A shortening price means money arrived on that side. Sum this over a season and you're measuring closing line value — the metric most serious bettors and modelers care about more than win rate.
Step 5: the same trick works on corner lines
This is the fun part. Corner totals move too, and that market is far less efficient than 1X2:
corner_ticks = list(client.iter_all(client.odds_history, fixture_id=FIXTURE_ID, market="corner"))
for tick in corner_ticks[-5:]:
print(tick["recorded_at"], "line", tick["line"], "over", tick["over"], "under", tick["under"])
Line moves (say 9.5 → 10) are a stronger signal than price moves — a bookmaker only reprices the whole market when the flow forces them to.
Where to go from here
- Store ticks in SQLite and chart them with matplotlib — one fixture is a few hundred rows at most.
- Compare opening vs closing across a whole league season (
league_fixtures+ a loop). - Watch live: in-play ticks carry the match minute and running score, so you can line odds moves up against goals and red cards.
Full endpoint reference: 5dollarfootballapi.com/docs. The Python client is on PyPI, and there's a matching Node.js client on npm if JavaScript is more your thing.
Questions or feedback? I'm happy to answer in the comments.
Top comments (0)