This page documents the data pipeline, the projection engine, and — most importantly — the validation discipline and live track record behind every number on the tabs. The philosophy is show the receipts, not the recipe: the exact rules that decide whichbets fire are proprietary, but everything you'd need to judge whether the system actually works — where the data comes from, how accurate it is out-of-sample, and what it has really returned live — is laid out in full below.
Team-run and game-total projections come out of bbsim, a Monte-Carlo play-by-play simulator that runs 5,000 simulated games for each scheduled matchup. For each plate appearance, it draws event outcomes from a joint model built on pitcher rates, hitter rates, park factors and weather — then aggregates across the 5,000 sims to produce run means, win probabilities and p10/p50/p90 bands. Player props are a separate model: a closed-form per-plate-appearance projection, not a draw from the simulator. The bbsim cache covers 2020–2026 and feeds the accuracy table below. Each season is projected using only seasons before it, so no game contributes to the model that projects it.
| Feed | Source | Refresh |
|---|---|---|
| Schedule, lineups, probable pitchers | MLB Stats API | every 5 min on game day |
| Hitter + pitcher season stats, Statcast xStats | Baseball Savant | nightly |
| Park factors (per-side HR, runs, K) | Curated park factor table, reviewed annually | nightly refresh |
| Game weather (temp, wind, humidity) | Open-Meteo forecast, 3-hr avg from first pitch | hourly on game day |
| Moneyline / game total / run line | TheOddsAPI (best line across books) — h2h+totals+spreads bundle | every 15 min in game windows |
| Team totals (book-direct prices) | TheOddsAPI per-event team_totals market | every 15 min in game windows |
| Umpire zone tendencies | Not currently applied — the available table is too thin to trust, and ABS compresses the real effect | — |
| Player prop lines (HR, K, TB, etc.) | TheOddsAPI, multi-book | every 15 min in game windows |
Each plate-appearance draw is a multinomial over {K, BB, HBP, 1B, 2B, 3B, HR, other BIP} with probabilities shaped by three layers:
Umpire tendencies are not modelled inside the simulator — they are applied in the matchup and prop-projection layers instead.
The resulting per-PA probability is drawn 5,000 times per game across both lineups, with base-state progression from an empirical 2019–2023 run-expectancy matrix — deliberately recent, so it reflects the current run environment rather than averaging across decades of rule changes. Aggregate stats come from averaging the 5,000 sim outcomes.
LII is a 0–100 measure of how well a hitter is "seeing the ball" right now — a pure recent-form signal, independent of tonight's matchup. It is computed over rolling 7-, 14-, 21-, and 30-day windows; every input is z-scored against the current league distribution for that window, so 50 is league-average and 70+ is a genuine hot streak. The headline is the weighted average of its component sub-scores, so a weak component visibly drags it down — the parts always sum to the whole.
Matchup Score blends four percentile-ranked factors into a single 0–100 number for tonight's specific batter / pitcher / park / weather spot, where 50 is a neutral matchup and higher favors the hitter. It answers "how good is this hitter's spot tonight, all things considered?"
It's a weighted blend of four components, each scored on its own 0–100 percentile scale, then multiplied by a park-context factor:
The leaderboard's headline Scoreblends this Matchup Score (70%) with a talent-anchored form index (30%): 75% multi-year Marcel-regressed xwOBA talent + 25% the 14-day Locked-In Index, standardized across that day's slate. Stable talent anchors the number; recent form tilts it.
Skill estimates use Statcast xwOBA for batted balls (physics-based, park-and-weather-neutral) — that's the noise-resistant signal we want for batter / pitcher quality. Park and weather deltas use actual wOBA — those layers exist to capture the things xwOBA filters out (warning-track FBs becoming HRs, ball carry on hot days). Mixing the two on a single additive scale is intentional.
50 is the median matchup across three seasons; the tails are anchored to real, recognizable spots:
| Matchup | Score |
|---|---|
| Aaron Judge vs LHP @ Yankee Stadium, warm night, wind blowing out | 99 |
| Top of the Reds lineup vs Kyle Freeland @ Coors, summer night | 96 - 98 |
| Median plate appearance across all 2023-2025 matchups | 50 |
| League-average bat vs Tarik Skubal @ T-Mobile, cold night, wind in | 0 - 1 |
The skill and park components freeze when the game's lineup flips to confirmed in the daily-lineups feed. After lock-in only the weather delta keeps refreshing (Open-Meteo forecasts update through the day), so users see a stable Matchup Score that drifts at most a few points as the wind / temperature forecast tightens.
Rate stats need a minimum sample before their observed value is signal. The Sport Stack follows Russell Carleton's r=0.5 thresholds: below these, cells render without heatmap color and carry a ★marker. The displayed value is always Marcel-regressed toward league mean:
So the display and prop-projection layers see the same regressed number and never diverge. The simulator applies its own Marcel regression with its own constants, so its team-run projections are stabilized separately rather than from this table.
| k_pct | 60 |
| bb_pct | 120 |
| iso | 160 |
| xwoba | 100 |
| woba | 100 |
| xba | 100 |
| avg | 100 |
| slg | 120 |
| ops | 120 |
| barrel_pct | 150 |
| hard_pct | 150 |
| chase_pct | 100 |
| whiff_pct | 100 |
| k_pct | 70 |
| k_per_9 | 70 |
| bb_pct | 170 |
| bb_per_9 | 170 |
| xwoba_against | 120 |
| fip | 200 |
| xfip | 200 |
| siera | 200 |
| whip | 200 |
| era | 250 |
| csw_pct | 150 |
| swstr_pct | 150 |
| o_swing_pct | 150 |
| gb_pct | 200 |
| barrel_pct_against | 150 |
| hard_pct_against | 150 |
| avg_fb_velo | 20 |
Below are the bbsim-engine accuracy metrics on every precomputed projection in the cache — 15,988 games across 7seasons. Lower is better on every column. This measures the simulator's raw run-level and win-probability accuracy, and nothing else — it is not a betting result. A Brier score near 0.25 is what a market-efficient binary looks like; read it as “the simulator is not miscalibrated,” not as “the simulator beats the market.”
The game-level model that priced moneylines, game totals and team totals off the simulator was retired on 2026-05-08. It still runs in grade-only mode for research; it stakes nothing. Sections 1–3 below describe that retired system and are kept as a record of how it worked — they do not describe anything that fires today.
What stakes units today is narrower: a batter RBI prop selector, a line-shop totals arm that bets when a book lags the sharp consensus, and an anchor-fair-edge moneyline arm. These do not read a simulator probability — they price off market structure — so the calibration and Kelly machinery described below does not sit in their path.
For each market the retired generator covered — moneyline, game total, team total — it converted the offered American price to an implied probability and subtracted it from the model's probability:
The live selectors compute edge against the de-viggedfair price, not the raw vigged number — so a stated edge is measured against the market's fair estimate, not against the price you pay. The vig is accounted for separately, in grading and in the ROI figures on the record below, which use the actual prices taken.
Raw Monte-Carlo probabilities are over-confident at the tails: the simulator will say 70% when reality is closer to 64%. The retired generator passed every probability through a calibration layer before any edge math.
This layer is not in the live path. None of the currently-staked systems derives its probability from the simulator — they read market structure directly — so there is no Monte-Carlo output to calibrate. Several calibration blocks are additionally disabled pending held-out validation that meets our own gate (see §5).
Full-Kelly is the growth-optimal stake but its variance is brutal: a 50% drawdown is normal even when the model is right. The retired game-level generator staked a fraction of full Kelly against a 100-unit bankroll (1 unit = 1% of roll):
What the live systems actually stake is simpler and more conservative: the RBI prop selector and the moneyline arm stake a flat 1 unit; line-shop totals uses one-tenth Kelly with a 1u floor and a 2u cap, and in practice the cap binds on nearly every fire. No live bet is sized off a simulator probability.
The real protection against a mis-calibrated line is not a size cap but a refusal: above a per-market edge ceiling the stake is set to zero rather than scaled up, on the reasoning that an implausibly large edge is far more likely to be a broken number than a genuine one. Edges approaching that ceiling are decayed in bands before they reach it.
Suppose bbsim says BOS ML wins 57.4% of the time and the book has BOS at -110 (decimal 1.91, implied 52.4% with vig). On a $10,000 bankroll where 1 unit = $100:
Expected return on this single bet: +$25.50 (2.65u · 9.6% EV). But that expectation is never what you experience. A single -110 wager has exactly two outcomes — +$240.91 or −$265.00 — and a 57.4% shot loses more than four times in ten. One bet tells you nothing about whether an edge is real; only a large sample separates edge from variance, which is why the only performance figure on this page is the live graded record in §6.
This is the arithmetic the retired game-level generator used. The Best Bets table no longer exposes model internals — it shows the bet, the book, the price taken, when it was captured, and how it settled.
The first time a selector sees a bet that clears its gates, the row is written and frozen at the price and stake captured in that moment. Re-runs throughout the day update nothing on that row except the grader, which sets status and pnl_units after the game finishes.
Gates differ by system and are not a single global threshold — each selector carries its own qualifying conditions and its own pre-registered kill rules. Rows that fail a staking gate can still be written with zero units for audit; those carry no stake and are excluded from the record below.
Why: if we kept re-pricing pending rows, the displayed edge would drift toward the closing line and the realized ROI column would measure "how well does our number track the market" instead of "how well do we beat the price we actually got." The Added column on the Best Bets table shows the moment of capture.
Calibration coefficients are fit on seasons strictly earlier than the season they are scored against, so no game contributes to the model that prices it. That much holds. But we previously published a stronger claim on this page — that every shipped fit had passed a strict hold-out — and it did not survive our own audit, because the coefficient that shipped was chosen as the best of several variants ranked on the very season being called held-out. Selection on the test set is not a hold-out.
This page previously showed a 2025 hold-out figure of +22.21% ROI at a 65.07% win rate over 7,779 bets on game totals. That number was an artifact of its test harness and has been removed. The harness invented five round total lines (7, 8, 9, 10, 11) for every game instead of using the line the market actually offered, and graded every bet at a flat −110 instead of the price a bettor would have been given.
It did not measure the model. Betting those synthetic lines with no model at all — blindly taking over 7 and under 10 and under 11 on every game — scores about +25% on the same harness, beating the published figure. Real MLB totals simply do not land on round numbers the way that grid assumes.
The honest measurement of that market, pre-registered and graded at real captured prices, is −7.13% ROI [−11.20, −3.06]. That result is why the game-level model was retired on 2026-05-08 and why it stakes nothing today. The defect was identified internally on 2026-05-01; it should have come off this page then, and did not until 2026-07-25.
We are not replacing it with another backtest figure. Held-out metrics for the calibration blocks that remain in service are pending a backfill that meets our current gate — which requires a non-empty test cohort, disjoint from the fit cohort, showing positive lift — and blocks that fail it are disabled rather than shipped. Until those are computed, the only performance number we will publish is the live graded record below.
The most credible number on this page is what the deployed system has actually returned. These are bets the production pipeline locked in (entry price, units, edge), the grader settled with the final score, and stored in the best_bets table — no backtesting, no replay, no re-pricing. It includes manually-entered plays alongside pipeline-generated ones; both are graded identically and neither is re-priced after entry. The MLB record was reset to a clean slate on July 25, 2026; every graded play from that date forward is on it, win or lose. The History tab carries the full per-bet ledger behind it.
Plus 4 bets currently pending — locked-in but waiting on the game to finish. Live ROI will move when those settle.
Season-by-season
An empty or thin March–April row is expected rather than a feed failure: early-season backtests are the weakest cohort we measure (cold weather, unstable rotations, little current-season signal on fresh starters), and the retired game-level generator was coded to skip that window entirely. The systems staking today are not gated on the calendar, so a quiet March is a function of thin qualifying slates, not a hard pause.
Beyond the game-level markets above, The Sport Stack runs specialized player-prop models that go a layer deeper than the simulator alone. Each pairs the projection with a cohort-specific filter: a set of conditions, learned from several years of data, that isolate the player and matchup profiles where the model has a durable, validated edge and stay out of the spots where it does not.
The exact cohort definitions, edge bands, and selection rules are the proprietary core of the product, so they are not published here — and neither is the mapping from any individual play back to the model that produced it. On the board a play is a play. What is worth stating plainly is the bar each one has to clear before it fires a single live bet:
As with the game-level layer, the credibility check that matters is the live track record above, not the backtest and certainly not the rules themselves.
Earlier versions + full detail: click the v2.2.0 chip next to the The Sport Stack title.