Two-stage Bayesian hierarchical model. Per-PA strikeout probability
(Model A v4) × Batters Faced distribution (Model B Binomial) →
posterior predictive K count. Both fit with NumPyro NUTS on CPU,
4 chains, 1500 warmup / 1000 samples.
- Model A v4: per-PA strikeout logistic with
pitcher / batter / park / umpire random effects + same-hand,
is-home, temp-z, wind-z, and TTO (times-through-order) effects.
Non-centered parameterization throughout.
- Model B Binom: Binomial(MAX_BF=45, p) on each
pitcher's batters faced, with team random effects + days_rest,
is_home, recent-pitch-count effects. Init via init_to_median
(structural multimodality fix from Phase 4).
- Simulator: composes the two; static-shape JAX
with MAX_BF=45 mask. ~5 seconds per pitcher × 4000 sims after
JIT warmup.
Validated on 1,949 out-of-sample predictions across 4 monthly walk-forward
refits (May–August 2025).
| metric | value |
| Mean predicted K | 4.99 |
| Mean actual K | 4.82 |
| Bias (predicted − actual) | +0.17 |
| MAE (per start) | 1.79 K |
| RMSE | 2.24 K |
| Poisson(λ=5) noise floor | ≈2.24 |
RMSE is essentially the Poisson noise floor — the model is operating
at the irreducible aleatoric limit on a single integer K count.
Probability calibration is within ±5pp of advertised in most
well-populated buckets.
+0.1 to +0.3 K positive bias
Model over-predicts mean K by ~0.10 to 0.30 across all months.
Marginal over edges (4–6%) may be optimistic. Prefer ≥6% edges
on overs.
L=7.5+ over-prediction
Persistent ~3–5pp over-prediction at L≥7 across all training-start
cohorts. Surfaced per-pitcher when p_over_7.5 > 0.40.
Low-info pitchers (<5 training starts)
RMSE 2.48 vs 2.21–2.23 for medium / heavy cohorts (~12% noisier).
Half-stake or skip per OPERATIONS.md.
Lineup proxy
For games where the lineup hasn't been posted yet (typical for
evening games at morning-of refresh), the model uses each team's
most recent confirmed lineup as a proxy. Re-pull predictions on
game day if betting; lineups can shift mean K by ±0.3–0.5.
Model is refit monthly on the 1st with warm-start from the prior
month's trace. After every refit, the two-branch convergence check
runs (R-hat ≤ 1.01 strict, OR R-hat ≤ 1.05 AND bulk-ESS ≥ 100 AND
tail-ESS ≥ 200 AND per-chain spread/SD < 0.5). Predictions are
not generated from a refit that fails convergence.
Code:
github.com/jmc486/mlb-k-props
(set this once the repo is up). Phase 6 acceptance:
SOUND WITH CAVEATS. Phase 7 monitoring: weekly review of calibration
by edge bucket; alarms on Model B convergence trajectory.