Skip to content

Does late money know something? Friday's odds against kick-off

Seven seasons of Bet365 prices on 2,710 Scottish matches, taken on Friday and again at kick-off. In the Premiership the kick-off price is no more accurate than Friday's. In the Championship it clearly is, and the big moves still didn't go far enough.

Intermediate

Contents

The research question

Between Friday and kick-off the odds move. Team news comes out, injuries are confirmed, money goes on. Punters talk about "following the money": a side whose price shortens is supposed to know something. Is the price at kick-off better than Friday's, and does the move tell you anything?

The site already found that the bookmakers' prices are remarkably accurate. That piece used the price the football-data.co.uk files record before kick-off without asking when it was taken. This one compares two prices for the same match.

The dataset

From 2019/20 the football-data.co.uk files hold two Bet365 prices for each match. The first is collected for weekend games on Friday afternoon (generally by 17:00) and for midweek games on Tuesday (by 13:00), according to their fixtures page. The second is the closing price, the last one before kick-off. Call them Friday's price and the kick-off price.

That gives 2,710 matches with both: 1,545 in the Premiership and 1,165 in the Championship, over seven seasons to 2025/26. One Championship match with a recorded price of 0 is left out.

The method

  • Prices to chances. Each price is turned into a chance and the bookmaker's margin taken out, exactly as in do the bookmakers get Scottish football right?.
  • Which price is more accurate? Log loss, the score explained in how do you know if a prediction model is any good?: lower is better, and it punishes being confident and wrong. Compared match by match, with a luck margin.
  • The big moves. Every home win, draw and away win whose chance moved 5 points or more between Friday and kick-off. Where did the results land: nearer Friday's chance or the kick-off one?
  • £1 at Friday's price. What a bet on each of those moved prices, struck on Friday, would have returned, compared with bets at the same odds whose price stayed within 2 points. This is a test of whether the moves carried information. It isn't a betting system: on Friday you don't know which way the price will go.
  • A second check. The accuracy test repeated on the market's average price instead of Bet365's.

Results

Prices don't move much

In the median match the biggest of the three chances moved by 2.5 points between Friday and kick-off in the Premiership and 2.1 in the Championship. A move of 5 points or more, say from 40% to 45%, happened in about one match in six. Moves of 10 points or more were rare: under 2% of matches.

Premiership: kick-off is no better than Friday

Across the 1,545 Premiership matches, Friday's prices scored 0.9335 and the kick-off prices 0.9333. The kick-off price is better by 0.0002, with a luck margin of ±0.0040: no difference at all. The market average agrees, a gap of 0.0028 ± 0.0037, still within luck.

Championship: kick-off is clearly better

In the Championship, Friday's prices scored 1.0501 and the kick-off prices 1.0435, better by 0.0066 ± 0.0046. That's beyond luck, and the market average shows the same (0.0057 ± 0.0040). It sounds tiny, but it's half the gap between the bookmakers and the best model this site has built (0.932 against 0.945), gained in the hours before kick-off.

Most of it comes from the matches whose prices moved 5 points or more: in those 173, the kick-off price was better by 0.028.

Where the results landed

Prices that moved 5 points or more: the chance on Friday (hollow), at kick-off (arrowhead), and how often it happened (gold).

When a price shortened by 5 points or more, the result followed the money in both leagues. Premiership prices that shortened went from 43.3% on Friday to 50.1% at kick-off, and happened 49.5% of the time: the kick-off price was almost exactly right.

The drifters are where the two leagues part. Premiership drifters went from 44.2% to 37.5% and happened 40.6% of the time, between the two. Championship drifters went from 38.9% to 31.8%, and happened only 25.2% of the time. The price drifted, and it should have drifted further.

Taking every move together, Premiership results went 0.74 of the way from Friday's chance to the kick-off one (luck range 0.03 to 1.44): the right direction, but a range too wide to say how far. Championship results went 1.54 of the way (0.79 to 2.35). On a scale where 0 means Friday was right and 1 means kick-off was right, more than 1 means the move didn't go far enough. Joseph Buchdahl found the same in the four English divisions (see Further reading).

What it would have been worth on Friday

£1 on Friday Top flight Second tier
Shortened later +2p +9p
Drifted later −19p −42p
Odds unmoved about −5p about −10p

What £1 at Friday's price returned in the Premiership (top flight) and the Championship (second tier), by what the price did before kick-off. "Odds unmoved" is bets at the same odds whose price stayed within 2 points. The luck margins are about ±16p in the Premiership and ±20p in the Championship.

A bet at Friday's price normally loses about the bookmaker's margin, a few pence in the pound, as the bottom row shows. Championship sides whose price later drifted lost 42p in the pound, four times as much, and the gap is well beyond luck. The ones that later shortened made a small profit, though not by more than luck. The late moves carried real information.

That doesn't make it a betting system. To use it you would need to know on Friday which way the price will go, which is the whole skill of professional betting and why bookmakers watch so closely who backs what.

Why the Championship?

The data can't say, but the likeliest reason is how much is known by Friday. Premiership team news, injuries and suspensions are widely reported during the week, so Friday's price already has most of it. Championship news travels less far and less early, so more of it reaches the price on the day, when the teams are named. That's a reasonable guess, not something this data tests.

Limitations

  • Seven seasons. Closing prices only start in 2019/20, and the moves that matter are about one match in six, so the luck margins are wide, especially for the Premiership's drifters.
  • One bookmaker. Bet365 throughout, with the market average as a second check on accuracy only.
  • Friday is a rough time. The files say "generally not later than 17:00", and midweek games are priced on Tuesday. The comparison is "a day or so before" against "at kick-off", not an exact clock.
  • Why, not just whether. The data shows the moves carried information, not what the information was.
  • Not betting advice. The returns are a test of the prices, not a strategy anyone could have followed.

Conclusion

In the Premiership, Friday's price was as accurate as the kick-off price: the late moves that did happen pointed the right way, but overall they added nothing measurable. In the Championship the kick-off price was clearly better, and the moves still didn't go far enough: sides whose odds drifted won a quarter of the time when the kick-off price said nearly a third. If you want to know what the market knows, in the Championship it pays to wait for the team news.

Reproduce the analysis

The results files are published by football-data.co.uk. Download the Premiership (SC0) and Championship (SC1) files with the download script, which names them as the snippet expects; they aren't rehosted on this site. It runs in under a second:

Show the Python76 lines, ready to copy and run.
# From Football Data Science by Bryan McGuire. Free to use with credit.
# https://www.footballdatascience.co.uk/research/late-money
import csv
import random
from math import log
from statistics import mean, median, stdev

SEASONS = ["1920", "2021", "2122", "2223", "2324", "2425", "2526"]  # closing prices start in 2019/20
DIVISIONS = {"SC0": "Premiership", "SC1": "Championship"}
rnd = random.Random(2026)  # fixed, so the luck margins repeat exactly

def probs(odds):  # prices to chances, with the bookmaker's margin taken out
    inv = [1 / o for o in odds]
    return [p / sum(inv) for p in inv]

def load(div, friday="B365", kickoff="B365C"):
    matches = []
    for s in SEASONS:
        with open(f"{div}_{s}.csv", encoding="latin-1") as f:
            for r in csv.DictReader(f):
                try:
                    fri = [float(r[friday + x]) for x in "HDA"]
                    ko = [float(r[kickoff + x]) for x in "HDA"]
                except (KeyError, ValueError):
                    continue
                if r.get("FTR") in ("H", "D", "A") and min(fri + ko) > 1:  # one row has a price of 0
                    matches.append({"fri_odds": fri, "fri": probs(fri), "ko": probs(ko), "result": "HDA".index(r["FTR"])})
    return matches

def margin(values):  # about 2 standard errors
    return 2 * stdev(values) / len(values) ** 0.5

def outcomes(matches):  # every home win, draw and away win priced, with both chances and whether it happened
    return [(m["fri"][k], m["ko"][k], m["fri_odds"][k], k == m["result"]) for m in matches for k in range(3)]

def followed(moved):  # how far the result went towards the kick-off price: 0 = Friday was right, 1 = kick-off was right
    return sum((won - fri) * (1 if ko > fri else -1) for fri, ko, _, won in moved) / sum(abs(ko - fri) for fri, ko, _, _ in moved)

for div, name in DIVISIONS.items():
    matches = load(div)
    print(f"\n{name}: {len(matches)} matches with a Friday price and a kick-off price")
    move = [max(abs(m["ko"][k] - m["fri"][k]) for k in range(3)) for m in matches]
    print(f"  biggest move per match: median {median(move):.1%}; 5 points or more in {mean(x >= 0.05 for x in move):.1%}, "
          f"10 or more in {mean(x >= 0.10 for x in move):.1%}")

    # 1. which price is more accurate? log loss, lower is better; paired, match by match
    gain = [log(m["ko"][m["result"]]) - log(m["fri"][m["result"]]) for m in matches]
    fri_ll = mean(-log(m["fri"][m["result"]]) for m in matches)
    print(f"  log loss: Friday {fri_ll:.4f}, kick-off {fri_ll - mean(gain):.4f}; kick-off better by {mean(gain):.4f} ± {margin(gain):.4f}")
    for label, keep in (("moved 5+ points", lambda x: x >= 0.05), ("moved less", lambda x: x < 0.05)):
        g = [d for d, x in zip(gain, move) if keep(x)]
        print(f"    {label}: {len(g)} matches, kick-off better by {mean(g):.4f} ± {margin(g):.4f}")

    # 2. the big moves: what Friday said, what kick-off said, what happened
    every = outcomes(matches)
    for label, keep in (("shortened", lambda f, k: k - f >= 0.05), ("drifted", lambda f, k: f - k >= 0.05)):
        g = [o for o in every if keep(o[0], o[1])]
        print(f"  {label} 5+ points: {len(g)}; Friday said {mean(o[0] for o in g):.1%}, kick-off {mean(o[1] for o in g):.1%}, "
              f"happened {mean(o[3] for o in g):.1%}")
    moved = [o for o in every if abs(o[1] - o[0]) >= 0.05]
    boots = sorted(followed([moved[rnd.randrange(len(moved))] for _ in moved]) for _ in range(1000))
    print(f"  results followed the money {followed(moved):.2f} of the way ({boots[25]:.2f} to {boots[975]:.2f})")

    # 3. £1 at Friday's price on what later moved, against bets at the same odds whose price stayed put
    still = {}
    for fri, ko, odds, won in every:
        if abs(ko - fri) < 0.02:
            still.setdefault(min(int(odds * 2) / 2, 8), []).append(odds * won - 1)  # half-point odds bands
    for label, keep in (("shortened", lambda f, k: k - f >= 0.05), ("drifted", lambda f, k: f - k >= 0.05)):
        g = [o for o in every if keep(o[0], o[1])]
        ret = [odds * won - 1 for _, _, odds, won in g]
        usual = mean(mean(still[min(int(o[2] * 2) / 2, 8)]) for o in g)
        print(f"  £1 at Friday's price on what later {label}: {mean(ret):+.2f} ± {margin(ret):.2f}; same odds, price unmoved: {usual:+.2f}")

    # 4. a second check: the market's average price instead of Bet365's
    avg = load(div, "Avg", "AvgC")
    gain = [log(m["ko"][m["result"]]) - log(m["fri"][m["result"]]) for m in avg]
    print(f"  market average, {len(avg)} matches: kick-off better by {mean(gain):.4f} ± {margin(gain):.4f}")

Further reading

  • Joseph Buchdahl, Steamers and drifters in a football match betting market (football-data.co.uk, 2016). Five seasons of the four English divisions, 10,180 matches: prices that shortened before kick-off hadn't shortened enough, and prices that drifted hadn't drifted enough.
  • Efficient-market hypothesis, Wikipedia. The idea that prices already reflect what's known, which betting markets test every weekend.
  • Notes for football data, football-data.co.uk. What each column means, including the closing-price columns (B365CH and so on).

Get the weekly email

One email a week, on Fridays, with everything new, and the occasional note. Unsubscribe in one click. How your email is used.