Warehouse fresh · 2026-10-30 14:15 UTC
Model R̂ max 1.010
ESS min 1656
Divergences 0
Trained 2026-10-30 14:39 UTC

Methodology

An honest description of the model behind these forecasts — the data it sees, the assumptions it makes, and the things it deliberately ignores. Everything on this site is generated by the pipeline described here.

This page describes how the forecast works rather than reproducing the model specification. The parameterisation, priors and tuning are proprietary.

1 · Overview

The forecast is produced by a hierarchical Bayesian generalised linear model fitted with Hamiltonian Monte Carlo (PyMC's NUTS sampler). It estimates each team's latent offensive and defensive strength, each starting pitcher's quality as a random effect on opponent run-rate, and a per-venue park factor capturing each ballpark's run environment, using every regular-season and postseason game from 2022 to date, then projects the remainder of the 2026 regular season via Monte Carlo simulation of the posterior.

We chose a Bayesian framework for three reasons: posterior credible intervals quantify what we don't know; partial pooling keeps early-season ratings honest when a team has only played a dozen games; and posterior draws plug straight into a forward simulation, so the league standings we show are calibrated, uncertainty-aware projections rather than point estimates.

2 · Data pipeline

All training data flows from a separate warehouse repository. The ingestion layer pulls every MLB game from the MLB Stats API into a raw landing table, which dbt transforms into modelled feature and game-level fact tables. This model reads both.

Critically, the rolling features in the warehouse are computed using only games that occurred before the current row — no target leakage. When we use, for example, a team's last-30-game win percentage on a given date, it genuinely means "as of the morning of that day".

3 · What the model estimates

Each game contributes two observations: the runs the home team scored, and the runs the away team scored. Runs are modelled as Poisson counts with a rate that depends on the teams involved and on whether the scoring team was at home. The Poisson is the natural distribution for count outcomes, and it fits MLB run totals well because runs are rare events across a fixed inning structure.

Five things move that rate: the batting team's offensive strength, the opposing team's defensive strength, the quality of the opposing starting pitcher, the ballpark, and home-field advantage. They combine multiplicatively, which matches how scoring actually works — a strong offence raises expected runs by a proportion, not by a constant.

The opponent's starting pitcher is what suppresses a team's runs in any given game, so the random effect is keyed on the pitcher taking the mound for the team being scored against. Two games of the same matchup at the same venue will therefore carry different expected run totals if different starters are pitching — which is exactly the source of game-to-game variation across a series that is otherwise identical at team level.

4 · Partial pooling

Team-level ratings are drawn from league-level distributions, so early in a season every team is pulled towards the American or National League average until its own results earn it some distance. Without this "shrinkage" a team that started 3–0 would briefly look world-class; with it, the model concedes uncertainty and waits for more data. How much shrinkage to apply is itself learnt from the data rather than fixed in advance: if the two leagues diverge sharply, or one is markedly more spread out than the other, the posterior reflects that.

Pitcher quality is partially pooled the same way, but around a single league-wide average — pitchers move between leagues too freely for a league-level split to add much. A pitcher with three good starts isn't treated as a Cy Young candidate, while a pitcher with eighty starts of consistent above-average performance can earn a meaningful positive estimate. Pitchers not seen in training — recent debuts, traded-in arms, fresh call-ups — fall back to league average at predict time, and are picked up automatically on the next nightly fit once they have a few starts on the books.

Each ballpark gets its own effect on the run environment. Coors Field, Petco Park and the Yankee Stadium short porch all materially shift per-game expected runs; previously the model absorbed those venue effects into the home team's offence and defence terms, which is mis-attribution. Park effects are pooled more tightly than team or pitcher effects, because realistic park factors are smaller in magnitude. Both teams share the same park effect at a given venue, which keeps the offence and defence ratings clean of venue noise.

5 · Recency weighting

Baseball teams change — rosters turn over, managers get fired, front offices tear down and rebuild. A team's 2022 performance is not particularly informative about its 2026 performance, yet an unweighted fit would let four years of ancient results drown out a few dozen current ones. That's how a team in the middle of a genuine rebound (hello, Athletics) ends up stuck near the bottom of the posterior long after it should have moved.

Rather than model each season as its own parameter — tried; the posterior geometry was unkind to the sampler and the fit took too long on the CI runner — each game's contribution to the fit is discounted by its age, on a decay short enough that the current season carries essentially all the weight. The back half of the previous season contributes a meaningful but reduced slice; anything older is effectively zero.

The decay was shortened in May 2026 after a calibration audit found the posterior home-field advantage collapsing well below its prior — older seasons' lower observed home advantage was dragging the estimate down. A shorter memory lets the current sample dominate.

6 · Current form

Recency weighting is a long, slow signal: it discounts old games against new ones across years. It does not capture current form — the fact that a strong-rated team can be in a two-week cold streak without the season-level posterior meaningfully moving against them. This surfaced as a real failure mode in early-May 2026, when the model kept picking slumping favourites at season-strength expectation because the strength posterior was correct on the year and the decay was too long to register a fortnight of bad results.

The model now carries an explicit short-window form term alongside the long-run team strengths, built from each team's run differential over their previous ten completed games. It is deliberately constrained to be a modulation of the team-strength estimate rather than a dominant signal — a hot or cold streak moves a given game's expected scoring by a few percent, not by a tier. The current-form column on the Team Strength tab shows each team's raw ten-game run differential alongside their long-run rating.

Why this isn't double-counting recency: the weighting in §5 determines how much each historical game contributes to estimating a team's true skill; the form term modulates the predicted run rate around that skill estimate according to current circumstances. They operate at different time scales — years for the strength posterior, days for form — and are jointly identified because of that separation. A team that has always been a 90-win club but is currently 1–9 over two weeks should keep its strength estimate while having its per-game prediction pulled toward weaker outputs.

No leakage: the form value for any training game is computed strictly from games completed before that game's date. A game never contributes to its own form.

7 · League-wide effects

Two effects are shared across the whole league: the league-average run rate, centred on the long-run MLB average of roughly 4.5 runs per team per game, and home-field advantage, a small bump for the team at home consistent with MLB's long-run 52–54% home win rate.

Home-field advantage is held fairly close to that historical norm. A May 2026 calibration audit over 293 graded games found the model emitting an average predicted home win probability of 48.3% against an actual home-win rate of 51.2%; allowing the parameter more freedom was letting it drift several points below reality. The historical home-win rate has been stable enough for decades that little flexibility is needed.

8 · Inference

Posteriors are sampled with PyMC's No-U-Turn Sampler (NUTS), the current default for Hamiltonian Monte Carlo on continuous parameter spaces. Each nightly run samples multiple chains in parallel, discards a warm-up phase used to tune the sampler, and retains only post-warm-up draws for inference and simulation.

A single fit takes roughly twelve minutes on a GitHub Actions runner — most of the cost is in the per-pitcher random effect, around 1,500 parameters, added to an otherwise small team-level model. The sampler is run slightly more conservatively than its default setting, which keeps divergences at zero on this model.

9 · Convergence diagnostics

Every run writes three headline diagnostics, and the current values appear in the status strip at the top of every page.

10 · Season simulation

The posterior alone tells you how strong each team is; to translate that into standings and playoff odds we simulate the remaining regular season ten thousand times. Each simulation draws a full set of parameters from the posterior, computes both teams' expected run rates for every remaining scheduled game, draws simulated run totals, and awards the win to whoever scored more.

Tied simulated scores — around 13% of draws at typical run rates — are resolved by a fair coin flip per game, a close approximation to extra innings. That was a correctness fix landed in May 2026: previously the per-game predictor dropped ties entirely and the standings predictor awarded all ties to the home team, both of which introduced systematic bias.

Across ten thousand simulations this produces a full distribution of final-season win counts for every team, which is where the 80% credible ranges and the division-winner probabilities come from.

11 · Post-fit calibration

A May 2026 held-out audit graded the model against 293 completed games from the prior 22 days and surfaced two failure modes that no parameter tweak inside the Bayesian fit could cleanly address. Three post-fit adjustments now sit between the Monte Carlo and the probability we publish.

Both the adjusted and the raw probability are stored, so future audits can attribute which adjustment is paying off and which is hurting.

12 · Series Focus pages

The Series Focus tab hosts longer-form editorial pieces that decompose a single matchup or modelling question. Live data on those pages is joined at render time, so each refresh of the training pipeline updates the prose embedded in the article. Four things feed them: the same per-game win probability that drives the Today tab; posterior means and credible intervals for each starting pitcher's suppression effect; team strength alongside actual season standings, giving both the model's view and the box-score reality on one page; and Pythagorean expected win percentage computed live from runs scored and allowed. The gap between actual win percentage and Pythagorean is the "luck" panel — positive means a team is winning more close games than its run differential implies.

For series of three or four games, an outcome-distribution simulation sits on top of the per-game probabilities. The current implementation treats games as independent, which is fine for editorial framing but ignores the correlation that bullpen state induces from one night to the next. A correlated multi-game simulator is on the roadmap.

13 · Live in-game win probability

Once a game starts, the win-probability bar on the Today tab switches from the morning pre-game number to a live estimate that updates roughly every eleven minutes. The pre-game number stays visible underneath, so the delta between "what the model said" and "where the game is now" is readable: a live 63% means very different things for a 55% favourite than for a 35% underdog.

The live estimate is a Monte Carlo simulation seeded by the morning fit. It takes that game's expected run totals, converts them to a per-inning scoring rate for each side, and plays out the remainder of the game a thousand times. The current half-inning is handled separately, using standard base-out run expectancy rescaled both by the batting team's own scoring rate and by the run-suppression rating of the pitcher actually on the mound — so a reliever weaker than the starter raises expected runs in that inning. End-of-game rules are applied explicitly: the home team doesn't bat in the bottom of the ninth or later when already ahead, and extras continue while the score is level. The live probability is the fraction of simulations the home team wins.

What this captures: score differential; the current base-out state (two out and empty reads very differently from bases loaded with none out at the same score); each team's own offensive rate rather than league average; the specific pitcher on the mound, with starters rated by the Bayesian model and relievers by a sample-size-regressed rating so the bullpen is covered; and home-team walk-off mechanics.

What it does not capture, honestly:

Cadence. A scheduled job runs through the overnight game window, polls the MLB Stats API for live game state, simulates any game in progress, and writes a small JSON file the browser fetches. The interval sits just above the average half-inning, so each refresh is likely to land on a genuinely new game state. The series outcome table on each card recomputes in the browser on every successful poll, mixing completed results, live probabilities for the game in progress, and pre-game numbers for games still to come.

Starter pitch charts live on the Pitching tab. For each starter in a live game, every pitch thrown is rendered on a strike-zone plot from the catcher's perspective, using the plate coordinates returned by the MLB Stats API live feed, coloured by pitch type. No model inference is applied to this view — it is the raw pitch data, which the model could in principle ingest in a future feature.

14 · Bullpen leverage

MLB games end up in the bullpens. The starter rarely reaches the ninth, the leverage of every plate appearance climbs through the middle innings, and late-inning matchup choices decide one-run games. Public models typically don't price this: they price the starter, the team, the park, and call the bullpen a wash. We have the ingredients to do better.

Two sources combine. The warehouse tracks every active reliever's availability — fresh, taxed, or unavailable — from days since last appearance and recent workload, which determines whether a manager can realistically use that arm tonight and at what cost. The model supplies each reliever's posterior quality, the same per-pitcher effect that prices starters on the Today tab. Joining the two gives each team a projected high-leverage core: the arms most likely to be leaned on if the game is tight late. The Today tab surfaces the comparison as a late-inning edge per game, and Series Focus pages fold it into the editorial framing.

Limitations. The current iteration is independence-naïve: it does not model that a reliever used in Friday's ninth is taxed for Saturday's eighth. A simulator that walks bullpen state forward across a series is the next iteration. Warehouse ingestion currently captures only pitchers who have appeared in a 2026 game, which leaves bullpens a few arms shallower than reality — the metric is correct given its inputs, but those inputs understate depth on both sides until ingestion widens. Pitcher handedness against the opposing late-inning lineup is not yet factored in either.

15 · Pitch-level data

Since May 2026 every tracked pitch from the MLB Stats API live feed is persisted to the warehouse. Each pitch carries plate coordinates, official zone, pitch type, velocity, the call, and the at-bat result — the same structure the live pitching tab consumes, now aggregated historically.

Downstream transforms normalise the raw stream, collapse noisy pitch codes into families, and roll pitches up per pitcher and zone with usage share, called-strike rate, whiff rate and in-play counts. That rollup renders the per-starter heat maps on Series Focus. A separate transform joins batter platoon splits to player metadata, so lineup blocks on the Today tab can swap a season slash line for the actual line against the opposing hand, with sample size on hover.

16 · Posterior-update preview

Series Focus pages project what each team's rating would look like after each possible outcome of the upcoming series. It is a fast closed-form approximation: the current rating and its credible interval are treated as a prior, the scenario's run differential as new evidence, and the two are combined analytically.

What this is not. It is an approximation, not the actual posterior from a re-fit on data plus tonight. A real re-fit would account for opponent strength — beating a strong team is more informative than beating a weak one — along with park effects, pitcher matchup quality, and drift in true talent. The closed-form version is fast enough to compute inside a single page render and gives roughly the right magnitude and sign. For a genuine "what is our posterior after this game?" analysis you would run an overnight fit including the new game; this is the cheap preview.

17 · Playoff qualification

The playoff probability on the League Predictions tab is the fraction of simulations in which a team finished in the 2026 postseason field. Under current rules that's six teams per league: three division winners plus three wild cards taken from the rest by win percentage. Ties are broken by assigning fractional wins to every tied team, so the reported odds always sum exactly to three division winners and three wild cards per league.

18 · Data sources

19 · Deliberate omissions

Several signals that might plausibly improve accuracy are not in the model. Each is a conscious choice; each is a candidate for a future release.