How Our MLB Model Works: Poisson Runs and Pitcher Adjustments
Baseball is a counting-stats sport dressed up as a win/loss sport. Every game boils down to two numbers β how many runs the home team scores, how many the away team scores β and almost every market you can bet (moneyline, run line, totals) is a derivative of those two numbers. That is why our MLB model does not try to predict a winner directly. It predicts runs, and lets the maths of runs tell you who wins, by how much, and whether the total goes over or under.
Runs, not wins: why Poisson
We use an XGBoost-Poisson runs model. Gradient boosting (XGBoost) looks at everything we know about a matchup and outputs each team's expected runs β a single number called lambda (Ξ»). A Poisson distribution then converts those two lambdas into a full probability distribution over every possible scoreline. From that distribution we can price five markets off a single pair of numbers: moneyline (who wins), run line Β±1.5 (who wins by 2+ runs), totals over/under (combined runs above or below a line), first-5-innings moneyline, and first-5-innings totals.
Skip the hand-calculation.
Get real value bets flagged for you β 7-day free trialThe first-5-innings (F5) markets deserve a note, because they are not simply half of the full-game numbers. Bullpens, blowout garbage time, and extra-innings variance all inflate the back end of a game β the first five innings average around 4.6 runs combined, versus roughly 8.5 for the full nine. So F5 expectation is scaled at roughly 0.54 of the full-game total rather than 0.5, which keeps the F5 line honest instead of systematically too low.
The model is trained on ~19,000 MLB games since 2017, sourced from the MLB Stats API, across 63 features. In-sample mean absolute error is about 2.36 runs for the home team and 2.42 for the away team β meaning on average our run prediction misses by roughly two and a half runs, which sounds like a lot until you remember baseball scores are naturally noisy. On a genuinely held-out 2025 season the error rises to 2.41 / 2.61 β worse than the in-sample number, and we say so plainly rather than only quoting the flattering figure. A model that only looks good on data it has already seen is not a model you should trust with your bankroll.
The pitcher is half the model
In baseball, one player β the starting pitcher β controls a disproportionate share of the outcome. So starting-pitcher features (ERA, WHIP, strikeout rate, and more) sit at the heart of the model. But there is a rule we enforce strictly here: every pitcher stat has to be point-in-time. A pitcher's numbers are computed season-to-date, using only games played up to the day of the matchup being predicted β never the pitcher's final season line, which of course did not exist yet on the day of the game.
This sounds obvious, but it is the single easiest mistake to make when building a sports model, and we made it once. An earlier version of the pitcher features accidentally leaked the pitcher's full-season ERA into games played in April β months before that ERA existed. The model was effectively cheating: predicting April games with June information.
- Before the fix: pitcher ERA/WHIP/K-rate were computed from the season-final line and joined onto every game in that season, including games played before that line existed.
- After the fix: every pitcher stat is a season-to-date snapshot, computed strictly from starts made before the game being predicted.
- Result: the leaky ERA feature dropped from the #1 importance slot, Elo-style team-strength signals rose in its place, and out-of-sample accuracy got a little worse β the honest number replacing the inflated one.
A worse-but-honest number beats a great-but-fake one every time; it is the only number you can actually trust when real money is on the line. Team batting stats follow the same discipline, leaning on the prior season as a prior early in a new season before enough current-season games accumulate.
Umpires, weather, ballparks: the adjustment layer
On top of the base lambdas from the runs model, we layer several situational adjustments that shift expected runs up or down for a specific game:
- Home-plate umpire tendencies β some umpires call a noticeably bigger strike zone than others, which suppresses scoring; others squeeze the zone and inflate it.
- Weather β wind direction/speed and temperature move totals (data from OpenWeatherMap); a stiff wind blowing out at Wrigley is a different game than the same matchup in a dome.
- Ballpark factors β some parks are simply easier or harder to score in, independent of weather on a given day.
- Confirmed starting pitchers β pre-game lineup confirmations refine the pitcher inputs as soon as they are official.
- Playoff series context β game number within the series, the current series score, and whether a team is facing a must-win game all carry situational signal in October.
Every one of these adjustments nudges the base expectation, but none of them is allowed to run wild. The total adjustment is capped at Β±35% of the base expectation β so even if several adjustments stack in the same direction (say, a hitter's park plus a favorable wind plus a weak umpire), the combined shift cannot spiral into an unrealistic number. It is a guardrail against the model talking itself into an extreme line.
Calibration: fixing 3Γ overconfidence
A model can be good at ranking games (correctly sorting favorites from underdogs) while still being badly miscalibrated β meaning the actual probability numbers it outputs are too extreme in one direction. That is exactly what an audit of the raw MLB model found: it ranked games as well as, or better than, Pinnacle's closing lines, but its probabilities were roughly 3Γ overconfident (a calibration slope of about 0.32β0.41, where 1.0 would be perfectly calibrated).
Overconfident probabilities are not a cosmetic problem β they directly inflate expected value calculations and, left unchecked, they push Kelly staking to bet far bigger than it should. The fix is a per-market Platt calibration layer: a monotone logistic correction applied separately to moneyline, run line, and totals probabilities. "Monotone" is the important word β it rescales confidence without ever changing which side the model favors, so the model's underlying ranking skill is exactly preserved while its stated probabilities are pulled back toward reality.
On held-out 2026 data, the calibrated model reaches sharp-market parity or better across all three core markets: moneyline 0.2472 Brier score (essentially tied with Pinnacle's 0.2473), totals 0.2484 (better than Pinnacle's 0.2497), and run line 0.2423 (better than Pinnacle's 0.2436). Lower Brier scores mean more accurate probabilities. Matching or beating the sharpest market in the world on its own metric is the standard we hold ourselves to.
From probability to bet
A calibrated probability is not automatically a bet. For moneyline, run line, and F5 markets we require a Pinnacle anchor β if we cannot see a de-vigged Pinnacle price for that exact market, we do not bet it, full stop. We also cap how far our price can sit above Pinnacle's implied probability, as a sanity check against stale or broken odds elsewhere. For totals, we bet the line closest to the model's expected total, not whichever line produces the highest expected value on paper β chasing the highest-EV line tends to push you toward extreme, thinly-traded lines where calibration is weakest.
Every surfaced bet still has to clear a 5% expected-value floor against the de-vigged Pinnacle price, and we cap odds at 3.50 β long-shot prices are notoriously hard to calibrate well, and the payoff is not worth the added uncertainty. Stakes follow fractional Kelly at 25%, capped at 5% of bankroll on any single bet. Fractional Kelly is not a stylistic choice; it is a direct consequence of everything above. Even a well-calibrated model has residual error, and using anything close to full Kelly on imperfect probabilities is a fast way to blow up a bankroll on a bad month.
- Pinnacle anchor required β no de-vigged Pinnacle price on that market, no bet, for moneyline / run line / F5.
- Ratio sanity check β our price cannot sit too far above Pinnacle's implied probability, guarding against stale odds elsewhere.
- Nearest-line totals β the totals bet is the line closest to the model's own expected total, not the highest-EV line.
- 5% EV floor, 3.50 max odds β the minimum edge required, and the point past which long-shot calibration gets unreliable.
- 25% fractional Kelly, 5% bankroll cap β sized for the model's real, residual error, not its stated confidence.
How we know it works
We do not grade ourselves on results alone, because a single season is short enough that variance can swamp skill. Different markets get graded on different metrics, matching the doctrine we apply across every sport we model. Moneyline performance is judged primarily on closing line value β did we consistently get a better price than the market closed at, which is the most reliable long-run signal in betting. Totals and run line are judged on realized ROI plus calibration, because in handicap-style markets, positive CLV alone can be misleading β you can beat the closing number and still lose money if the model is systematically off on the actual distribution of outcomes.
A 162-game MLB season per team gives us a large amount of data to learn from β but it also means a large amount of game-to-game variance to live through. We will not promise you a profitable month, a profitable week, or even that any single bet is more likely to win than lose in the eyes of the market taking the other side. What we can promise is a model built on honest, point-in-time inputs, calibrated against the sharpest line in the sport, and staked in a way that survives a losing streak instead of being wiped out by one.
If you want more depth on the calibration release itself, the baseball model v2 upgrade post covers it in detail, and our totals explainer covers the over/under mechanics that apply here just as much as anywhere else. You can see every current MLB price on our model page, and every settled result β wins, losses, CLV, no cherry-picking β on the public track record.