Expected Value in Football Betting: We Predicted 24.3%, Recorded 10.9%

Data as of 2026-08-06 · sample 12,104 settled bets · window 2026-01-28 to 2026-08-05 · signal-time record, computed by src/lib/research/queries/expected-value-football-betting-results.ts

When our model says a bet has positive expected value, what does it actually return?

Summary

  • These are signal-time (paper) records, not real-money results. Every bet below is priced at the reference odds our engine saw at the moment the signal fired — none of it is a fill obtained with money at a sportsbook, so nothing here is net of slippage, reduced limits, commission, or bets that could not be placed at the recorded size at all. This is the ceiling on the signal, not a record of money captured. Across 12,104 settled football bets recorded between 2026-01-28 and 2026-08-05, OddsFlow signals returned a signal-time realized 10.93% on turnover (11.80% if voided bets are excluded), against a stake-weighted predicted expected value of 24.30% — the like-for-like comparison, since realized ROI is itself stake-weighted. On an unweighted per-bet basis the model predicted 23.34%. Realized return came in far below predicted EV on either convention.
  • Taken on its own, that realized figure is a good result. A signal-time return of 10.93% on turnover over 12,104 settled bets is clear of zero, and we are not going to bury it because the rest of this page is about where the model is wrong. What it is not is the 24.30% the model expected, and it carries none of the costs a real-money version would.
  • The gap has a measurable cause: the model is systematically overconfident. Across the 11,236 bets that produced a directional outcome, the model assigned an average probability of 64.19% to the side it backed. Those sides actually came in 52.96% of the time — +11.23pp of overconfidence. Priced through, a shortfall of that size is enough to account for a headline gap of this shape without any other explanation being needed.
  • The overconfidence is concentrated where the model is surest. Bets the model rated below its own average confidence run +5.37pp overconfident; bets it rated above run +14.95pp. The worst-calibrated band is ≥ 90%, where the model claimed 94.37% and got 73.94%.
  • A large predicted edge survives that haircut; a small one does not. The ≥ 10% bucket holds 8,755 of the 12,104 bets, is the worst-calibrated bucket in the table at +14.36pp, and still returned 16.07% — a big enough edge absorbs a big enough error. Nearer the break-even line the same error is fatal, which is what the middle of the table shows.
  • By market, the record was carried by HANDICAP (15.21%) and OVER_UNDER (12.88%), while MONEYLINE_1X2 was net negative at -0.31% over 2,993 settled bets.
  • 5 of the 21 league rows finished negative, the worst being UEFA Europa League at -36.21% over 50 bets. League-level returns at these sample sizes are not stable enough to act on.

Method

Every row comes from live_bets_v8, the table our live signal engine writes to when it records a bet. No public odds feed, tipster list, or third-party dataset is involved. A bet enters this study when its status is one of WON, LOST, HALF_WON, HALF_LOST, PUSH, VOID — bets still open (PENDING) are excluded, because including them would let an unsettled position flatter the number.

These are signal-time records, not real-money execution. Each row is stamped with the reference odds available when the signal fired, and settled against the final result at that price. Nothing in this study passed through a sportsbook account: there is no fill, no bookmaker limit, no commission, and no check that the recorded stake could have been matched at the recorded price. That makes every figure here a ceiling. The separate real-money record — bets our agents actually placed, each settled with a sportsbook PDF — runs lower, and is published on the accuracy hub. The gap between the two is the point: a signal-time number presented as a result is a ceiling being called an outcome.

Expected valueis the model's own estimate at the moment the bet was recorded, stored on the bet row as a decimal, and never revised afterwards. It is the prediction being tested here, not an output of the settlement.

It is notthe textbook product of the model's probability and the price. Each row also stores the raw model probability, and reverse-solving the stored EV back through the recorded odds gives an implied probability of 59.13% on average, against a raw model probability averaging 64.72% — a 5.59pp haircut applied before the number is written down. 9,215 of the 12,104 rows (76.13%) differ from the textbook product by more than half a percentage point. The haircut is uneven across markets — largest on HANDICAP at 9.40pp, smallest on MONEYLINE_1X2 at 2.30pp. The rule that produces it is not visible in this dataset. The engine that applies it is not in this repository, so we are reporting the two averages and the deviation count and stopping there. We are not going to fit a formula to the residuals and publish it as though it were the engine's actual logic.

Outcome units are how a settled status becomes a number the calibration section can average. WON counts as 1, HALF_WON counts as 0.75, HALF_LOST counts as 0.25 and LOST counts as 0, and PUSH and VOID are excluded, because a returned stake produced no directional outcome to score. This is a modelling choice, not a field in the data, so it is stored in the snapshot alongside the figures it produces. The fractional units exist for quarter-line bets, where half the stake settles on each of two lines: 331 of the 11,236 scored bets settle that way. Scoring a HALF_WON as 0.75 is exactly profit-equivalent at even money and drifts from it as prices move away — see Limitations.

Every table reports predicted EV twice, because the two conventions are not interchangeable: stake-weighted averages each bet in proportion to the stake behind it, while unweighted gives every bet equal say. Realized ROI is sum(profit_loss) / sum(stake), which is stake-weighted by construction, so only the stake-weighted EV column is a like-for-like comparison against it — and stakes vary by more than an order of magnitude across leagues, so the two EV figures genuinely differ (24.30% against 23.34% over the whole sample). We lead with the stake-weighted figure and publish both.

Realized return is total profit and loss divided by total stake: sum(profit_loss) / sum(stake). It is return on turnover, not return on a bankroll, so it does not depend on any staking plan. Over the whole sample that is 633,534 net on 5,796,307 staked, both in model stake units rather than currency.

Leagues are joined on fixture_id against match_reports_v8. Bets whose fixture has no matching report are reported as Unknown rather than dropped, and leagues below the per-league minimum sample used by the query script are rolled into a single Other row. Both choices exist so the league table still accounts for the full sample — a table that silently drops rows cannot be reconciled against the headline count.

Why two ROI figures

866 of the 12,104 settled bets were VOID: the bet was settled with the stake returned and no profit or loss, usually because the market was cancelled or the line no longer existed. That is 429,514 of stake that produced exactly zero.

The two conventions differ only in what they do with that stake. Realized ROI keeps VOID stake in the denominator, which treats a cancelled bet as capital that was committed and returned nothing — 10.93% overall. Realized ROI ex-VOID removes VOID from both numerator and denominator, which measures only bets that had a real outcome — 11.80% overall. We lead with the lower, more conservative figure and publish both, because voids are not evenly spread: the gap between the two conventions is widest in the ≥ 10% predicted-EV bucket (16.07% against 17.71%), so picking one convention silently changes the size of the study's central finding. Both columns are published for every row in the tables below, so which convention a figure uses is never ambiguous.

The gap

Bucketing every settled bet by the EV the model predicted for it, and comparing that against what the bucket actually returned:

Predicted expected value bucket against realized return on turnover
Predicted EV bucketSettled betsPredicted EV (stake-weighted)Predicted EV (unweighted)Realized ROIRealized ROI ex-VOID
< 0%184-17.56%-16.05%2.69%2.93%
0–2%940.91%0.84%18.82%18.82%
2–5%5314.07%4.09%-6.90%-7.08%
5–10%2,5407.59%7.63%0.28%0.29%
≥ 10%8,75533.36%30.14%16.07%17.71%

Two things are true about this table at once, and most write-ups of a record like this pick one. The first: 10.93% on turnover over 12,104 settled bets is a genuinely good signal-time result — 633,534 net on 5,796,307 staked, in model stake units. The second: it falls 13.37pp short of the 24.30% the model expected, and a study that reports the first without the second is a marketing page.

The result is also concentrated. The ≥ 10% bucket holds 8,755 of the 12,104 bets and returned 16.07% (17.71% ex-VOID), producing 102.40%of the sample's net result: 648,768 against 633,534 overall, with the other 4 buckets combined at -15,234 units. Restricting to bets the model rated positive-EV at all — 11,919 bets — moves the headline only to 11.25%, so the sign of the predicted edge sorts almost nothing. Its size does. The rest of this page is about why.

Why: the model is overconfident

Predicted EV is downstream of a probability. If those probabilities are too high, every EV built on them is too high, and the size of the ROI gap follows from the size of the probability error rather than needing any separate explanation. So the direct test is to ignore EV entirely, bucket the bets by the probability the model assigned to the side it backed, and ask how often that side actually came in.

Model probability against actual outcome rate
Model probabilityScored betsModel saidActually happenedOverconfidence
< 30%12525.05%46.00%-20.95pp
30–40%81935.94%33.42%+2.51pp
40–50%1,18245.36%39.09%+6.28pp
50–60%2,24255.27%47.86%+7.41pp
60–70%2,22164.71%50.90%+13.81pp
70–80%2,72473.37%60.30%+13.07pp
80–90%1,35884.11%65.78%+18.33pp
≥ 90%56594.37%73.94%+20.43pp

Over 11,236 scored bets the model averaged 64.19% confidence and was right 52.96% of the time: +11.23pp. That is not a rounding error in a corner of the sample, it is the central tendency of the whole thing. 868 further bets are excluded here because their stake was returned without a directional outcome, which is why this count is lower than the 12,104 in the ROI tables.

The error is not flat across the range. Weighting by bet count, bands below the sample's average confidence of 64.19% run +5.37pp overconfident; bands above it run +14.95pp. Overconfidence also rises without interruption across the top 3 bands, from 70–80% upward. The extreme case is ≥ 90%: the model claimed 94.37% across 565 bets and those outcomes landed 73.94%of the time. In plain terms, the model's near-certainties are not near-certain.

The pattern reverses at the bottom, and we are not going to leave that out because it spoils a clean line. the < 30% band came in at 46.00% against a claimed 25.05%, 20.95pp the other way, on 125 bets. At that sample size we would not read anything into the direction; we report it because a calibration curve with its inconvenient end trimmed off is not a calibration curve.

What that does and does not mean

An overconfident model is not automatically a losing one. A bet is profitable when the true probability beats the price, not when the model's stated probability is accurate — so the question is whether the edge that remains after the error is still on the right side of the line. Putting the same buckets side by side with what the model claimed, what happened, and what the prices in that bucket required:

Model probability, actual outcome rate and break-even rate within each predicted-EV bucket
Predicted EV bucketScored betsModel saidActually happenedOverconfidenceMedian oddsBreak-even rate
< 0%16933.70%54.14%-20.44pp1.9351.81%
0–2%9453.50%47.87%+5.63pp1.9850.51%
2–5%52447.58%46.95%+0.63pp2.0848.19%
5–10%2,46753.16%47.42%+5.74pp2.0249.50%
≥ 10%7,98269.46%55.10%+14.36pp2.0548.78%

The worst-calibrated bucket in the table is the one carrying the record. ≥ 10% is +14.36pp overconfident — the largest error of any bucket here — and returned 16.07% across 8,755 bets, more than any other bucket holds in total. 0–2% shows a higher 18.82% on 94 bets, which at that size is a sample too thin to rank against it. That combination looks like a contradiction and is not one: a claimed edge large enough to land a bet in that bucket can absorb an error of that size and still clear the price. Overconfidence sets how much of a predicted edge evaporates; it does not by itself decide whether any is left.

The buckets that finished negative are the mirror image, and they are worth reading carefully rather than as a headline failure. 2–5% returned -6.90% over 531 bets, but its calibration is 0.63pp off — the model claimed 47.58% and got 46.95%, against a break-even rate of 48.19% implied by its median price of 2.08. That makes 2–5% the best-calibrated bucket on the page, not a broken one. A bucket sitting a fraction of a percentage point under break-even on a few hundred bets is a thin sample around a marginal edge. It is not evidence that the model fails there, and we are not going to present it as one — the honest reading is that at that end of the range the predicted edge is too small to survive contact with the price, in either direction.

The < 0% bucket — bets the model itself rated negative-EV, at a mean of -16.05% — returned 2.69% over 184 bets, with calibration running 20.44pp in the conservative direction. We read that as the EV estimate being noisy at the low end on a small sample, not as a reason to back negative-EV bets.

What we take from all of this: the EV figure is a ranking score, not a forecast of return. It orders bets usefully at the top of its range and carries a systematic upward bias that a reader should discount before treating any single number as a prediction. Nothing here says the next 12,104 bets return anything similar, and nothing here is a claim about profitability for anyone actually placing money.

By market

Realized return by bet market
MarketSettled betsPredicted EV (stake-weighted)Predicted EV (unweighted)Realized ROIRealized ROI ex-VOID
HANDICAP5,32327.46%26.77%15.21%18.06%
OVER_UNDER3,78824.75%23.07%12.88%13.06%
MONEYLINE_1X22,99317.50%17.60%-0.31%-0.31%

MONEYLINE_1X2 is net negative at -0.31% across 2,993 settled bets, against a stake-weighted predicted EV of 17.50%. That is no measurable edge on this market over this sample: a substantial predicted edge and a realized return sitting on zero. We have run no significance test on that gap and make no claim about whether it would survive one. The handicap and totals markets, where the model prices a line rather than a three-way outcome, are where the record was made.

By league

Leagues with enough settled bets to list separately; everything smaller is rolled into Other, and fixtures with no matched report into Unknown, so the rows still add back to the full sample.

Realized return by league
LeagueSettled betsPredicted EV (stake-weighted)Predicted EV (unweighted)Realized ROIRealized ROI ex-VOID
La Liga1,36022.30%22.05%17.64%19.13%
World Cup1,33529.89%20.65%1.24%1.32%
Serie A1,04121.02%20.80%18.38%20.20%
Premier League1,01522.17%21.88%22.95%25.24%
Brazilian Serie A89424.09%25.58%7.37%8.10%
Bundesliga87522.39%21.37%30.03%33.63%
Ligue 184523.09%21.75%25.49%27.62%
Brazilian Serie B67143.61%29.83%-2.61%-3.09%
Pro League66526.57%28.17%13.04%14.05%
Super League64124.73%25.90%27.37%29.33%
J1 League53926.08%28.68%0.74%0.80%
Eredivisie47322.89%22.74%13.55%15.06%
Primeira Liga35725.30%24.75%15.60%17.38%
Süper Lig33024.39%24.27%-8.18%-8.55%
Unknown31710.54%16.51%-5.67%-6.16%
UEFA Champions League24021.05%18.98%19.69%20.27%
Indian Super League17221.30%28.05%12.64%13.14%
K League 115927.17%27.87%3.65%3.82%
UEFA Europa Conference League7417.20%22.88%-5.73%-5.74%
UEFA Europa League5018.45%18.53%-36.21%-36.21%
Other5124.38%21.87%65.98%76.12%

The spread here is wide. Excluding the two roll-up rows — Other and Unknown are aggregates of many competitions, not leagues, so neither belongs at the end of a range — it runs from Bundesliga at 30.03% down to UEFA Europa League at -36.21%, and that spread is mostly a statement about sample size, not about which leagues are exploitable. UEFA Europa League (-36.21%), Süper Lig (-8.18%), UEFA Europa Conference League (-5.73%) and Brazilian Serie B (-2.61%) all finished negative. We are publishing the negative rows for the same reason we publish the positive ones: a league table filtered to its winners is not evidence of anything.

Limitations

  • The window is the window. This covers bets recorded between 2026-01-28 and 2026-08-05 only. It is not a forecast, and nothing here implies the next 12,104 bets return anything similar.
  • League and season composition is not controlled. The mix of competitions shifts across the window — a summer tournament, a mid-season break and a league restart all change which fixtures were available to bet. Differences between buckets and markets therefore partly reflect what was being played, not only how the model performed.
  • Real-money slippage is not accounted for. These figures use the odds recorded at signal time. They do not model the price moving before a bet lands, reduced limits on the lines the model likes most, commission, or bets that could not be placed at the recorded size at all. A real-money record will sit below this one.
  • This is one model family. Everything above describes the behaviour of our own signal engine over this period. It does not generalise to expected-value betting in general, to other models, or to a future version of this one.
  • The outcome-unit mapping is our choice, not a measurement. Turning a settled status into a number for the calibration section requires deciding what a part-settled bet is worth. We score HALF_WON as 0.75, which is exactly profit-equivalent at even money and loosens as prices move away from it — the median price in every predicted-EV bucket above sits near even money, so the approximation is tight on this sample and would not be on a sample of longer prices. It affects 331 of the 11,236 scored bets. PUSH and VOID are excluded rather than scored, which is also a choice: it keeps returned stake out of a hit-rate, at the cost of measuring calibration on a slightly different sample than the ROI tables use.
  • The EV discount is opaque to us here. The stored expected value is not the model probability multiplied through the price — its implied probability averages 5.59ppbelow the raw model probability, unevenly across markets. We can measure that the haircut exists and how large it is on average; the rule that produces it lives in the signal engine, not in this dataset, so we cannot state it, and we have not guessed at it. Any reading of "predicted EV" on this page is a reading of an already-adjusted number whose adjustment we are describing rather than specifying.
  • Calibration is measured, not corrected. The overconfidence reported above describes the model as it ran during this window. It is not a correction factor: nothing here establishes that subtracting it from a future probability would produce a calibrated one, and applying it that way assumes the error is stable, which this study does not test.
  • Return on turnover, not on bankroll. Realized ROI here divides by total stake. It says nothing about drawdown, variance, or how a bankroll would have survived the sequence — a positive return on turnover is entirely compatible with a losing experience under an aggressive staking plan.

Auditing this

Every figure on this page is produced by one script, checked into the repository at src/lib/research/queries/expected-value-football-betting-results.ts. It reads the settled bet rows, applies the status filter and the fixed data cut-off of 2026-08-06, and writes the snapshot JSON this page renders. The page holds no numbers of its own.

What is open here is the method, not the rows. The script can be read line by line — the status filter, the bucket boundaries, the league join, the pinned cut-off — and every derived figure checked against it. live_bets_v8 itself is a private table, so re-running the script requires our database credentials; it is not a dataset you can download, and we do not claim a third party can recompute these numbers from public data. Our public per-bet exports cover the Asian Handicap and Over/Under signal record and the real-money record; they do not contain the 1X2 rows counted above.

The cut-off is a pinned constant in that script rather than the current date, so re-running it reproduces these exact figures rather than quietly picking up bets settled since. To extend the study to newer data, move the constant deliberately and re-run — the sample size, window and as of date at the top of this page will change with it.

Further reading

  • Implied probability explained — the concept underneath expected value: how a price converts to a probability, and what has to be true for a bet to carry an edge at all.

Related