FanGraphs WAR model · 2026 season

Crow-Armstrong / Ohtani
fWAR sandbox

Crow-Armstrong
8.20
baseline
Even
Ohtani
6.50
baseline
Pitching the rest of the way
Framework

Move any input and both totals recompute. Every number below feeds a real FanGraphs WAR formula, so you can see exactly what a home run, a caught stealing, or a run-saving catch is worth in wins. The beside an input snaps that one value back to the player's actual number and leaves everything else where you put it.

Counting stats Checking the MLB Stats API…
fWAR Fetching from FanGraphs… Leaderboard ↗
Fetch routes & fallbacks
Proxy
Same origin Path on your own server that forwards ?url= to the source and returns the body unchanged. Used for both FanGraphs and Baseball-Reference, and tried automatically when this page is served over http(s).
Attempts Not run yet.
Paste
Pete Crow-Armstrong
CF · Cubs
Actual 8.20 fWAR
Shohei Ohtani
DH / SP · Dodgers
Actual 6.50 fWAR
Scenarios
League environment & model constants

How each number gets used

Hitting. Your event counts build wOBA with linear weights, then wRAA = (wOBA − lgwOBA) / wOBAScale × PA. Raising HR without raising PA converts outs into home runs, which is the right behaviour for "what if he hit more."

Defense. Fielding runs come from whichever metric the active framework uses. Position is three dropdown slots with a games slider each, so a player who splits time is handled properly. The adjustments are C +12.5, SS +7.5, 2B / 3B / CF +2.5, LF / RF −7.5, 1B −12.5, DH −17.5 runs per 162 games, and Neutral is 0. Everything else is held fixed when you change a position, so the swing you see is the positional adjustment and nothing else.

Framework. The two buttons at the top swap the model, not just the label. fWAR takes fielding from Outs Above Average and pitching from FIP. bWAR takes fielding from Defensive Runs Saved and judges pitchers on the runs that actually scored. Inputs belonging to the other framework grey out but stay editable, and each keeps its own anchor and its own live feed.

Pitching. FIP from HR, BB, HBP and K, converted to a runs-allowed scale, then run through FanGraphs' dynamic runs-per-win with a starter replacement level of .380. Both players carry the same pitching block; Crow-Armstrong simply starts at zero innings, which zeroes the whole term.

Playing out the season. Where they are now is the season to date. Play out the season holds each man's rates steady — plate appearances, home runs, steals, outs above average, games by position — and scales him to 162 games from however many he has actually played, using the live game counts from the MLB feed. Touch any input afterwards and the switch drops to Custom, so you always know whether you are looking at a preset or your own tinkering.

The arm is projected separately, because "rest of season" means something different for a pitcher than for a lineup spot, and Ohtani has not started since July 3. Three choices, and per-inning rates are held constant in all of them so only the workload changes:

A player with no innings on the board is untouched by all three, so the control never invents a pitching line for Crow-Armstrong.

Total. (batting + baserunning + fielding + position + replacement) / runs-per-win, plus pitching WAR. Both cards run the identical set of inputs and the identical formula, so any difference you see is coming from the numbers, not from the model treating them differently.

Where the numbers come from

On load the page makes three live calls. First the MLB Stats API (statsapi.mlb.com) for season-to-date hitting, pitching and fielding, which fills the sliders. Then FanGraphs for current fWAR and the Baseball-Reference WAR archive for current bWAR, each pulling batting and pitching and summing them per player, since a two-way player's total is the sum of the two. Whatever succeeds wins; whatever fails leaves the previous values in place and says so.

The bWAR call tries two things in order. First the player pages, reading the WAR figure straight out of the summary block at the top — the same number you see when you visit the site, already combined for a two-way player, and about a hundredth the weight of the alternative. If a page does not answer, or its summary is showing a different season, it falls back to the daily WAR archive: plain CSV that Baseball-Reference links from their own WAR Explained page and keys on the same MLBAM ids the MLB feed uses, so there is no name-parsing guesswork. The archive is the full history in one file, so it is streamed and filtered as it arrives rather than buffered. Only the players the pages failed to answer for are looked up there, so nothing is counted twice.

Both WAR calls try three routes in order, and the Fetch routes panel shows exactly what each one did for the selected framework:

Override the endpoints with ?fg= and ?br=, and the same-origin path with ?path=, if your setup differs.

What can and cannot be computed from scratch

Most of a WAR calculation is reachable from the free MLB Stats API, which is why this model runs at all. Two pieces are not, and they are the reason a published anchor is worth having:

So a from-scratch figure is an estimate of bWAR rather than bWAR. Turn calibration off and the model will happily produce one, clearly labelled; the gap it opens against the published number is roughly the size of the pieces listed above.

How calibration works

The model is calibrated by a flat run offset: the fWAR anchor minus what the model produces on the inputs that were true when that anchor was measured. The offset is then held fixed, so when the MLB feed advances the counting stats the totals move with them rather than staying pinned. The Calibration row in each breakdown shows the offset in runs. A successful FanGraphs pull re-points the calibration to the current stats; the toggle switches it off entirely.

Confidence and caveats

Sources