"I'm at β4% ROI over 400 bets, my model doesn't work." That sentence gets said every day, and most of the time it is wrong β not because the model is good, but because 400 bets simply cannot settle the question.
Sports betting has an appalling signal-to-noise ratio. A 2% edge lives inside tens of percent of variance. Before you interpret a result, you need to know what a sample that size is capable of showing.
The maths, in three lines
For flat-stake bets at price c with win probability p, profit per bet has an expectation and a standard deviation you can write down directly. Uncertainty on the observed ROI shrinks with the square root of n β that is where everything is decided.
import math
def roi_noise(odds=1.95, edge=0.02, n=400):
p = (1 + edge) / odds # win probability implied by the target edge
gain, loss = odds - 1, -1.0
mean = p * gain + (1 - p) * loss # = edge
var = p * (gain - mean) ** 2 + (1 - p) * (loss - mean) ** 2
return mean, math.sqrt(var / n) # expectation, std dev of observed ROI
mean, se = roi_noise(n=400)
print(f"true edge {mean:.2%} | noise at 400 bets Β±{se:.2%}")
# true edge 2.00 % | noise at 400 bets Β±4.87 %In other words: with a genuine 2% edge, your observed ROI over 400 bets wanders across a range of roughly Β±10 points (two standard deviations). Anything between β8% and +12% is perfectly consistent with the same winning model.
The odds of showing a loss while everything is fine
Flip the question: what is the probability of posting a negative ROI after n bets, given that the edge is real? The answer is brutal.
from statistics import NormalDist
def p_losing(odds=1.95, edge=0.02, n=400):
mean, se = roi_noise(odds, edge, n)
return NormalDist(mean, se).cdf(0) # P(observed ROI < 0)
for n in (200, 500, 1000, 2000, 5000):
print(f"{n:>5} bets β {p_losing(n=n):.0%} chance of being down")
# 200 bets β 39 %
# 500 bets β 32 %
# 1000 bets β 26 %
# 2000 bets β 18 %
# 5000 bets β 7 %One bettor in three with a real 2% edge is still in the red after 500 bets. Statistically there is nothing to fix: they have simply not played enough for the signal to surface.
These numbers assume independent bets at a constant price. If you take several correlated selections on the same day, or your prices range from 1.3 to 5.0, real variance is higher β so you need even more bets.
So how many bets?
To separate an edge from zero with reasonable confidence, the edge has to be worth at least twice the noise. On a 2% edge at 1.95, that ratio only reaches 1.6 at 6,000 bets and 2.0 at 10,000. The requirement grows with the inverse square of the edge: halve your edge and you quadruple the sample you need.
- 5% edge at 1.95: a few hundred bets are enough to see clearly.
- 2% edge: several thousand.
- 1% edge on 3.00 prices: tens of thousands β out of reach of any manual tracking.
Measure something other than the result
The practical conclusion is not "place 6,000 bets before concluding", it is "stop measuring the thing that converges slowest". A bet's outcome is a very noisy binary variable; the quality of the price you took is observable immediately.
- Gap to the closing line: your price against the market's last price. Every bet yields a measurement, with no need to wait for the result.
- Gap to fair odds at the moment you bet: measures your selection, independent of any later move.
- Realised slippage: the difference between the price you aimed at and the price you got. That is often where the edge disappears, not in the model.
All three share one virtue: they do not depend on how the game ended, so they do not carry outcome variance. A few hundred bets are enough to know whether your prices are good β while your ROI is still years away from saying anything.
The result tells you what happened. The price tells you whether you were right. Both converge, but not at the same speed.
Set your thresholds cold
This calculation has a preventive use: it sets your thresholds before the run, not during it. Knowing your ROI will wander across Β±10 points, a stop-loss at β5% guarantees you will kill a healthy strategy. The threshold has to sit beyond the expected noise, computed for YOUR estimated edge and YOUR volume.
And to estimate that edge without chasing your tail, do not start from past ROI β start from your prices. Rebuilding fair odds and closing prices line by line across your bet history gives a stable edge estimate you can feed into the maths above.
# the closing price of the exact line you played
curl -H "X-API-Key: $APINN_KEY" \
"https://api.apinn.io/api/closing?event_id=1610000123"
# the full move, opening β closing, to place your entry
curl -H "X-API-Key: $APINN_KEY" \
"https://api.apinn.io/api/history?event_id=1610000123"An edge estimated from prices, a stop threshold computed from expected variance, and a stake sized to survive the range: that trio is what stops you abandoning a winning strategy on bet 400.
Run this maths on real data
Real-time Pinnacle odds with fair odds included, plus opening and closing history β self-serve access, API key in minutes.