Every likely starter across today's slate, ranked by a self-calibrating model of their probability of getting at least one hit — the pitcher he faces, the bullpen behind him, his own contact and power, the park and his place in the order.
How it works. A principled base — how often this batter really gets a hit (three parts Statcast expected batting average to one part his actual average) combined with the opposing pitcher's average-against via a log5 / odds-ratio matchup, scaled by ballpark and expected at-bats. Because a starter only lasts about two thirds of a game, the plate appearances he does not cover are priced against the opponent's bullpen instead. That base is then nudged by a self-calibrating layer: each night grade.py scores the previous day's picks against real box scores and refits the weights. Factors:
Five factors, not the nine this model started with. The weights are fit on every game of two seasons — 67,000 batter-days reconstructed from box scores, each using only what was known that morning. Contact rate, on-base skill, pitcher WHIP, recent form, platoon and home/road splits and batter-vs-pitcher history were all tried and dropped rather than kept for the sake of looking thorough. They are still shown on every card, because they tell a human something the model cannot use. BABIP and power carry negative weights by design: both run above average partly on luck already counted in the batting average.
Hits per game went too. It scored best of any factor on the board as a whole, and it was still wrong to keep: a season-long counting stat duplicates the batting average already inside the base rate, so the fit split the credit and starved the part of the score that knows who is pitching. The board stopped pricing matchups and became a standings table — one player led it on 29 days out of 94. Judged only on the bats anyone would actually pick, hits per game turned out to be worth nothing at all; it had been earning its keep sorting the bottom of the board, which nobody reads. Dropping it nearly doubled the weight on the opposing pitcher.
Expected batting average does the luck-correcting now. A batting average counts hits; Statcast's xBA asks what each ball deserved, from how hard and at what angle it left the bat, with strikeouts as automatic outs. The two come apart all the time — a hitter can be batting .253 on contact worth .272 — and the gap is mostly luck that is about to disappear. The model used to grope at this with a blunt penalty on batted-ball average; now it measures it. Where it sits matters more than that it exists: added as another weighted factor, xBA fights the base rate for credit and the board collapses back onto the same few names. Fed into the base rate instead, as a better answer to "how good is this hitter really", it improves every level tested and gives the opposing pitcher more say, not less.
And then there are legs. Expected batting average scores how well a ball was struck, which makes it blind to the one way of reaching that owes nothing to contact: outrunning a routine ground ball. Sprint speed is the only thing tried here that turned out to be genuinely new information rather than a rearrangement of what the base rate already knew — almost everything else that looked promising was a restatement of the batting average in disguise. It is worth about a fifth of a percentage point of hit probability for the fastest men in the league, which is small, but it is real and it points the same way in both seasons tested. Last year's speed is used rather than this year's, so nothing about a row can know its own future.
Lineup slot matters more than it looks — batting first instead of ninth is most of an extra trip to the plate. When a lineup has not been posted yet, the slot is inferred from where that batter has actually been hitting over the previous fortnight rather than assumed to be the middle of the order: right to within one spot about four times in five. Otherwise a board built at noon would rank teams by whether their manager had posted a lineup yet.
It is a model estimate from public data, not betting advice — lineups and pitchers can change before first pitch. Honest about its own limits: it orders a random hit / no-hit pair correctly about 56% of the time. Most of whether a hitter gets a hit today is irreducible noise, and no amount of modelling changes that.