Needs the Pro plan Python 2 API calls

Backtest opening vs closing odds

A season of results, each with the price Bet365 opened at and the price it closed at. Settle the same simple bets at both prices and compare what they returned.

The finished backtest

Sample output: the Premier League 25/26 season, one unit staked on every match.

One unit on Return at the opening price Return at the closing price
Every home team -9.7% -8.5%
Every draw +3.0% +6.4%
Every away team -9.0% -11.4%
Every favourite -10.9% -8.6%

Premier League 25/26, 380 matches, Bet365 1X2 prices. One season is a small sample. Captured 8 October 2026 from GET /v1/leagues/{id}/fixtures?season=25/26&include=odds

The API calls behind it

  1. 1
    GET /v1/leagues?search=Premier League&country=GB-ENG

    Find the league id. Look it up once and keep it. Reference →

  2. 2
    GET /v1/leagues/{id}/fixtures?season=25/26&status=finished&include=odds&per_page=50

    A season of results, 50 a page, each row with its opening and closing odds in place. Reference →

What it takes to run

  • Odds on a fixture list (include=odds) are Pro and above, $5/mo. A free key reads the same prices one match at a time from /v1/fixtures/{id}/odds.
  • Nine requests for a 380-match season: one to find the league, eight pages of 50 matches.
  • Pro reads the last 12 months, so it returns the part of last season inside that window. Ultra returns the whole season and reaches back to 2014.
Compare plans

The code

Install the client, save the script, run it with your key.

pip install fivedollarfootball
import os
from fivedollarfootball import Client

client = Client(os.environ["FIVEDOLLARFOOTBALL_API_KEY"])
SEASON = "25/26"

leagues = client.leagues(search="Premier League", country="GB-ENG")
league = next(l for l in leagues if l["name"] == "England Premier League")

# 1. A season of results, each row with its opening and closing 1X2 odds.
matches = client.iter_all(
    client.league_fixtures, league_id=league["id"], season=SEASON,
    status="finished", include="odds", per_page=50,
)
rows = [m for m in matches if m["odds"]["1x2"]["opening"] and m["odds"]["1x2"]["closing"]]


def result(m):
    home, away = m["goals"]["home"], m["goals"]["away"]
    return "home" if home > away else "away" if away > home else "draw"


# 2. Four simple bets, one unit on every match.
bets = {
    "home team": lambda price: "home",
    "draw": lambda price: "draw",
    "away team": lambda price: "away",
    "favourite": lambda price: min(price, key=price.get),
}

# 3. Settle each bet twice: at the opening price and at the closing price.
print(f'{league["name"]} {SEASON}: {len(rows)} matches, one unit on each')
print(f'{"Back every":<12}{"At opening":>12}{"At closing":>12}')
for name, pick in bets.items():
    returns = []
    for stage in ("opening", "closing"):
        profit = 0
        for m in rows:
            price = m["odds"]["1x2"][stage]
            side = pick(price)
            profit += price[side] - 1 if side == result(m) else -1
        returns.append(profit / len(rows))
    print(f'{name:<12}{returns[0]:>+12.1%}{returns[1]:>+12.1%}')
England Premier League 25/26: 380 matches, one unit on each
Back every    At opening  At closing
home team          -9.7%       -8.5%
draw               +3.0%       +6.4%
away team          -9.0%      -11.4%
favourite         -10.9%       -8.6%
FIVEDOLLARFOOTBALL_API_KEY=fb_live_your_key python odds_backtest.py

The full project is on GitHub: 5dollarfootball-api/football-odds-backtest →

Take it further

  • Run several seasons and leagues before reading anything into a number: one season is 380 matches.
  • Add your own rule to the bets: it only has to return "home", "draw" or "away" for a match.
  • Each row also carries the Asian handicap, goal line and corner line at opening and closing: bet only when a line moved, and compare.
  • To compare the two prices as predictions instead of as bets, score them with a Brier score or log loss.
  • For the price at any moment in between, not just the two ends, read the tick history from /v1/fixtures/{id}/odds/history (Ultra).

Frequently asked questions

Pro, at $5/mo. Reading odds for a whole fixture list in one call (include=odds) starts on Pro, and Pro reaches back 12 months. Ultra returns complete past seasons back to 2014. A free key can read the same opening and closing prices one match at a time.

One unit is staked on every match. A winning bet pays the price minus the stake and a losing bet loses the stake. The figure is the total profit divided by the number of matches, so -8.6% means 8.6 units lost for every 100 staked.

Yes. For a finished match, closing is the last pre-match price Bet365 quoted and opening is the first. Settling at the closing price, not at a later in-play price, is what keeps a backtest honest.

Start building for free

Get a working API key in under a minute. No credit card, no sales call.