What each part of the site does, how our models work, and what the terms mean - then the statistics underneath, technique by technique, for anyone who wants the theory. Backtest numbers and implementation detail live in the README.

What each tool does

Today

One page for the day: Beat the Streak picks (on days there are any), today's baseball game picks and this week's football picks, each next to how our picks have done.

MLB · Picks

Beat the Streak: the hitters most likely to get a hit today, for MLB's contest of picking a hitter a day and building a streak - with our own running streak. Hit chances: every hitter's chance of a hit today. Game picks: who we think wins each game, next to Vegas, with our record and any bet we'd suggest.

MLB · Stats

What feeds the picks: every hitter's WAVE, hit chance, CAVE and WOPS (as played, or adjusted for the pitchers he faced), active hit streaks, a player lookup, today's probable starters, team ratings and each team's schedule.

MLB · Age Curves

How a player's season compares with history at his age: the 25 most similar seasons by players the same age, and what those players did the next year - his projection.

MLB · Prospects

Several published prospect and draft rankings blended into one consensus list.

MLB · Payroll

Which teams get the most winning for their money, every season since 1988 (opening-day payrolls from The Baseball Cube): payroll effectiveness, expected wins from payroll (what the 10 seasons that spent most like it won, plus the front office's recent edge), wins against that, all-time franchise averages and year-by-year grids, with the whole thing downloadable as a workbook.

NFL · Picks

Game picks for the week next to Vegas, outlined by whether the pick came before the final injury report (Tuesday) or after it (the morning of, or the day before, the game). What if?: swap a starting QB, mark players out or set any unit's efficiency and see the chance move; last week's games can be replayed to test your settings against how they finished.

NFL · Stats

Every team's schedule, power rankings by net EPA per play, team profiles, the team units (QB+, Receiving+, Rushing+, Pass D+, Run D+) and each game's matchup differentials, and players' season views.

NFL · Player Props

Players with the friendliest matchups for receptions, yards and sacks this week, from their recent games and what their opponent allows, plus each position's every-game floors. Places to look, not checked against sportsbook lines.

NFL · Prospects

Published college big boards for the next NFL draft, blended into one ranking; players already drafted are left out.

Install app

The button in the header installs the site as an app with its own icon: one tap in Chrome, Edge or on Android; on iPhone and iPad, Share → Add to Home Screen. The latest pages and data you've opened stay readable offline.

NFL · Payroll

The MLB Payroll page's numbers for NFL teams since 2014, on cash paid or cap spending (summed from OverTheCap's player contracts): which teams win the most for their money, expected wins from spending, franchise averages and year-by-year grids, with a workbook download.

Our models

Beat the Streak

Each hitter's hit rates are blended over the season and his last 81, 30 and 10 days (recent games count more), pulled toward the league for small samples, and combined with today's matchup - the opposing starter's and bullpen's PAVE and the park - in a model trained on past seasons and calibrated so its percentages mean what they say. A hitter qualifies only if both his hit chance and his games-with-a-hit rate clear 70%; the best Approach is the pick. Whether to risk the streak today is an optimal-stopping decision (Bellman, below).

MLB game picks

Each team's Pythagorean strength (from runs scored and allowed), its schedule-adjusted rating and its hitting power, adjusted by the starter and bullpen it faces today and home field; then calibrated. Every pick is logged before the game and graded after, next to Vegas's closing line. A bet is suggested only when our chance beats the de-vigged market by enough, sized by fractional Kelly in units.

NFL game picks

A logistic regression on each team's Pythagorean strength, schedule-adjusted confidence, offensive and defensive EPA edges, turnover margin and points per drive, plus each starting QB's QB+. Trained on every game since 2016 with each week predicted only from earlier games, the feature set chosen by walk-forward validation and checked on a season it never saw. When we disagree sharply with Vegas we defer to it (it's proven right more often there), then calibrate.

Injury report and What if?

A team's run game, pass game, run defense and pass defense are rated in percent of their normal yards per play. A player out lowers his units by what an absence at his position has cost teams in results (or what Vegas charges, where results are too noisy), times his snap share; a QB change goes through the model with the new QB's rating. Yards become points (0.07 a yard) and points become win chance (0.137 log-odds a point, from how Vegas turns spreads into odds). The final injury report's Out and Doubtful players reprice each game the morning of, or the day before, it.

NFL team units

Each unit's EPA per play, solved for every offense and defense at once so each is credited for the opponents it faced, with recent games weighted more and reaching back into last season. Shown on a 100 = league-average scale, 30 points per standard deviation.

Player props, age curves, prospects

Props: a player's production blended over his season, last 8 and last 4 games, against what his opponent allows his position. Age curves: nearest neighbors among historical seasons at the same age. Prospects: each source's ranks blended into one list. The Stats pages (WAVE, CAVE, WOPS, QB+ and the rest) describe; only the Picks pages predict.

Glossary

Approach
A hitter's games-with-a-hit rate times his hit chance - how Beat the Streak ranks qualified hitters.
Average regular / average starter
In What if?, a typical regular at a position (or a typical starting QB) - for someone not listed or whose role isn't known yet.
Beat the Streak
MLB's contest: pick a hitter each day; the streak grows when he gets a hit and resets when he doesn't.
Brier score
The average squared gap between a probability and what happened (1 or 0). Lower is better.
Calibration
Adjusting a model's probabilities so its 60% calls win about 60% of the time.
Cash flow / spending change
A team's relative payroll minus its average over the previous three seasons - positive means it spent up.
Cash paid / cap spending
On the NFL Payroll tab: the dollars a team actually paid its players that season (signing bonuses counted when paid), or the cap charges of its contracts. Dead money isn't included.
CAVE (Carried Average)
The share of a hitter's games with at least one hit - a 1-for-4 and a 4-for-4 count the same.
De-vig
Removing the sportsbook's margin from both sides' odds so they add to 100% - what "Vegas" means on this site.
EPA (expected points added)
How many points a play adds compared with what was expected from the down, distance and field position. Per play, per dropback or per target.
Expected wins
On the Payroll page, the wins per game of the 10 seasons whose relative payroll was closest (other franchises, 11 or more years earlier), plus the team's front office score.
Floor
The fewest a player has put up in a single game this season.
Front office score
How many wins a franchise beat its payroll's expectation by, averaged over its previous three seasons.
Hit chance
A hitter's chance of at least one hit in 3.5 at-bats at his WAVE.
Injury report: before / after
An NFL pick logged Tuesday, before the final injury report (purple outline), or repriced with it the morning of, or the day before, the game (blue).
Kelly / units
A bet-sizing rule that stakes more the bigger our edge over the odds; a unit is one standard stake.
Log loss
A score for probabilities that punishes confident misses hardest. Lower is better.
Net EPA per play
A team's offensive EPA per play minus the EPA per play its defense allows.
PAVE / PAVE+
The batting average hitters manage against a pitcher, blended like WAVE; PAVE+ compares it with the league (below 1 is tougher). Bullpen PAVE is the same for a team's relievers.
Park factor
How much a ballpark helps or hurts hitting compared with an average park.
Payroll effectiveness
Wins over average divided by payroll per win over the league's. Above 1 bought more winning per dollar than the league did.
Pythagorean strength
The winning percentage a team's points (or runs) scored and allowed would predict.
QB+
A quarterback's EPA per dropback over his last 16 games, pulled toward a replacement-level backup when he has few. On the Stats tab, on the 100 scale.
Receiving+, Rushing+, Pass D+, Run D+
Team unit ratings: 100 is league average, higher is always stronger, 30 points is one standard deviation.
Regular
A player on at least half his side's snaps over his last 4 active games (What if? lists anyone at 30%).
Relative payroll
A team's payroll per game over the league's that season - 2.0 spent twice the average.
Snap share
The share of his side's plays a player was on the field for.
Strength of schedule
How good the opponents a team has played were.
Unit %
In What if?, a unit's yards per play as a percent of its normal; a defense at 90% allows 10% more.
vs. pitchers faced / vs. defenses faced
A player's numbers adjusted for how tough his opponents were, from what each allowed all season.
Walk-forward
Testing a model only on games after everything it learned from - never peeking ahead.
WAVE
A hitter's batting average blended over the season and his last 81, 30 and 10 days, pulled toward the league for small samples.
WOPS
A hitter's OPS (on-base plus slugging), blended the same way.

The statistics underneath

Statistical Estimation & Smoothing

Empirical-Bayes (Beta-Binomial) Shrinkage

What it is

A way to estimate a rate (like a hit rate) from a small sample without over-trusting it. Instead of using the raw observed rate, it's pulled toward a "prior" rate (usually the population/league average), weighted by how many real observations back it up - a batter with 3 at-bats gets pulled hard toward league average; one with 300 barely moves. The prior itself is estimated from the data ("empirical"), not asserted from theory.

How we use it

helpers.shrink_rate shrinks every small-sample hitter hit rate toward the league rate, weighted by config.WAVE_SHRINKAGE_STRENGTH real pseudo-observations. Used in hitters.compute_wave and compute_game_hit_probability so a hitter with 5 at-bats against lefties doesn't get an absurd extreme rate just from noise.

Learn more →

Wilson Score Confidence Interval

What it is

A confidence interval for a proportion (a rate out of n trials) that stays well-behaved at small n and near 0%/100% - unlike the simpler normal-approximation interval, which can produce nonsense bounds outside [0, 1] on small samples.

How we use it

helpers.wilson_ci wraps statsmodels's Wilson implementation. Computed on the raw (unshrunk) hit/at-bat counts in hitters.compute_wave/compute_game_hit_probability - deliberately never on the shrunk point estimate above, since shrinkage already borrows strength from the prior and isn't a plain sample proportion anymore. The same formula also backs evaluation.wilson_confidence_interval (a scalar wrapper for one aggregate backtest rate rather than a per-row Series) - applied to day_survival_rate, market_accuracy, beat_closing_line_rate, and win_rate_on_advised_bets, so a bare percentage on the dashboard is never read as more certain than its real sample size supports. Quant-analytics item #5, "backtest scope and statistical significance."

Learn more →

Weighted Moving-Window Averaging

What it is

Blending several time windows of different lengths (e.g. last 7 days, last 30 days, full season) into one number using fixed weights, so a signal reflects both recent form and a larger, more stable sample. A simplified relative of exponential smoothing, using a handful of discrete windows with hand-set weights rather than one continuously-decaying weight.

How we use it

hitters._blend_windows/_side_window_agg is the shared engine behind WAVE, WTB/WHOPS, pitch-family rates, plate-discipline rates, and batted-ball-quality signals - each computes a rate per config.WAVE_WINDOWS window, split by opposing throwing hand, then blends by fixed weight.

The Stats page shows three such views of a hitter's season: WAVE (hits per at-bat), CAVE - Carried Average - (the share of games with at least one hit) and WOPS (OPS: on-base per plate appearance plus total bases per at-bat), each blending the full season with the last 81, 30 and 10 days. A window a hitter has no at-bats in, because he was hurt or benched, carries his next-longer window rather than counting as zero. Their vs. pitchers faced versions compare what he did with what a league-average hitter would have done against the same pitchers (each pitcher's season hits, on-base and total bases allowed, shrunk toward the league), so hits off tough pitching count for more. WAVE counts at-bats only - walks and hit-by-pitches aren't at-bats - here and in the picks alike: replaying the 2026 season, the picks hit more often that way than counting every plate appearance.

Learn more →

Z-Score Standardization

What it is

Rescaling a value to "how many standard deviations from the mean" it is: (x - mean) / std. Puts variables of very different raw scales onto one comparable axis.

How we use it

Two real uses: teams.py's normalize() helper puts team-strength composites (Pythagorean strength, SOS, confidence) on a common ~1.0-centered scale before they're blended into one game-pick signal; separately, scripts/train_hitter_hit_model.py/ train_game_pick_model.py z-score features before fitting statsmodels.Logit purely so the resulting coefficients are comparable across features of very different natural scales.

Learn more →

Regression & Classification Models

Every model here is grid-searched against at least one real alternative on identical, no-lookahead cross-validation, and only shipped if it also beats a naive baseline and the existing heuristic on an untouched holdout - see Model Validation below.

Logistic Regression

What it is

A linear model for binary outcomes: it fits a weighted sum of features and squashes it through the sigmoid function into a 0-1 probability. The workhorse baseline classifier in applied statistics - simple, fast, and its coefficients are directly interpretable.

How we use it

A candidate model (vs. gradient boosting) for "does this hitter get a hit today" (scripts/train_hitter_hit_model.py) and for game-level home-win probability (scripts/train_game_pick_model.py).

Learn more →

Ridge Regression

What it is

Ordinary linear regression with an L2 penalty added on the coefficients, shrinking them toward zero. Trades a little bias for lower variance - useful when features are correlated or numerous relative to the sample size, both common here.

How we use it

A candidate model (vs. gradient boosting) for the Age Curves page's next-season HR9 projection (scripts/train_age_curve_hr9_model.py).

Learn more →

Gradient Boosting (Histogram-Based)

What it is

An ensemble method that builds many shallow decision trees in sequence, each one fitting the residual errors of the ones before it. Can capture nonlinear relationships and feature interactions that a linear model can't, at the cost of interpretability. "Histogram-based" bins continuous features first, trading a little precision for large speed gains on bigger datasets.

How we use it

sklearn.ensemble.HistGradientBoostingClassifier/ Regressor is the alternative candidate in every model selection above - it's the one that actually won and shipped for HR9 projection (beating the KNN baseline, see below), while logistic regression/Ridge have won the others so far.

Learn more →

k-Nearest Neighbors

What it is

A non-parametric method that predicts by finding the k most similar historical examples (by distance on some metric) and looking at what actually happened to them, rather than fitting any equation. Simple, and naturally gives a real empirical distribution of outcomes, not just one point estimate.

How we use it

age_curve.find_comparables is a hand-implemented 1-D KNN: for a given player-age-metric, it finds the k closest historical Lahman seasons by that one metric and reports what those comparable players actually did the next season (mean, 25th/75th percentile) - project_next_season. Deliberately hand-rolled rather than pulling in sklearn.neighbors for one simple 1-D lookup.

Learn more →

Model Validation

Walk-Forward (Time-Blocked) Cross-Validation

What it is

Cross-validation adapted for time-ordered data: instead of randomly shuffling rows into folds (which would let a model "see the future"), each fold trains only on an earlier block of time and tests on the block immediately after it, then the training window rolls forward. The only CV scheme that doesn't leak future information into training for sequential data.

How we use it

ml_models.WalkForwardDateSplit blocks by calendar date (used for every daily hitter/game model); age_curve_ml.YearBlockedSplit does the identical thing blocked by season instead, for year-level Lahman data. Both plug directly into sklearn.model_selection.GridSearchCV as a custom cv= splitter.

Learn more →

Train / Holdout Split

What it is

Setting aside a final chunk of data that no amount of model selection or hyperparameter tuning is ever allowed to touch, then evaluating the already-chosen model on it exactly once. The single honest estimate of real-world performance - anything tuned using the holdout itself isn't a real holdout anymore.

How we use it

Every scripts/train_*.py carves off the LAST N distinct dates (config.ML_FINAL_HOLDOUT_DATES and its per-model variants) before any grid search runs, and reports final numbers only from that untouched block - not from cross-validation folds reused for reporting.

Learn more →

Data Leakage Prevention

What it is

A named failure mode, not a technique in itself: a model that accidentally has access to information it wouldn't have in the real world at prediction time (most often, something from the future). Leakage inflates validation numbers in a way that quietly disappears the moment the model is used for real - the single most common way a backtest lies.

How we use it

The whole pipeline is built around an explicit "as of date" cutoff (pipeline.run()'s own module docstring) - every feature is computed strictly before the date being predicted, walk-forward CV never trains on a later date than it tests, and the holdout dates are excluded from every stage of model selection, not just final scoring.

Learn more →

Probability Calibration

A model can rank outcomes well (good AUC) while still being over- or under-confident in its actual probability numbers. Calibration fixes the numbers without touching the ranking.

Isotonic Regression

What it is

A non-parametric calibration method: it fits the best possible non-decreasing (monotonic) step function mapping raw model scores to corrected probabilities. Flexible - it can fix almost any shape of miscalibration - but needs more data than a parametric method to avoid overfitting the correction itself.

How we use it

One of two candidate calibration methods in ml_models.fit_calibrated (sklearn.calibration.CalibratedClassifierCV(method="isotonic")), refit through the same walk-forward splitter as the underlying model. Selected in scripts/train_hitter_hit_model.py:_select_calibration by comparing holdout Brier score against the uncalibrated and sigmoid options.

Learn more →

Platt Scaling (Sigmoid Calibration)

What it is

A parametric calibration method: fits a single logistic (sigmoid) curve mapping raw model scores to corrected probabilities. Needs less data than isotonic regression to calibrate reliably, but can only correct a sigmoid-shaped miscalibration pattern.

How we use it

The other candidate in ml_models.fit_calibrated (method="sigmoid"), same selection process as isotonic above.

Learn more →

Statistical Significance Testing

Coefficient Significance (Wald Test)

What it is

After fitting a model like logistic regression, each coefficient comes with a standard error; dividing the coefficient by its standard error gives a z-statistic, which converts to a p-value - the probability of seeing a coefficient this large by chance if the true effect were zero. This is what separates "this feature looks predictive in this sample" from "this feature is actually predictive" - a different question from the Regression & Classification Models section above, which is about FITTING a model, not testing whether its coefficients are real.

How we use it

statsmodels.api.Logit, fit once per feature alone and once with all features together, in scripts/train_hitter_hit_model.py/train_game_pick_model.py's significance_report. This is real inference (fit on full history, not the holdout) - it answers "which features matter and in which direction," a different question from the walk-forward accuracy numbers above.

Learn more →

Exact Binomial Test

What it is

Given n real coin-flip-style trials with a known number of successes, this asks: how likely is a rate this extreme (or more extreme) if the true underlying rate were actually the null value (e.g. 0.5, "no real skill difference")? Unlike a normal-approximation test, it computes the exact binomial probability directly rather than approximating - reliable even at the small n a fresh real-money backtest actually starts with (a handful of games, not thousands).

How we use it

scipy.stats.binomtest, wrapped as evaluation.binomial_significance. Applied to beat_closing_line_rate - a genuinely symmetric "whose squared error was lower on this game" comparison, where 0.5 really is the "no skill difference" null. Deliberately NOT applied to a plain accuracy rate (real MLB home teams win somewhat more than half their games, so 0.5 isn't a fair null there) or to a betting win rate (bets are placed at different real prices, so win/loss counts alone can't measure edge - see the t-test below) - see game_evaluation.py's own docstrings for exactly which rate gets which test and why. Quant-analytics item #5, "backtest scope and statistical significance."

Learn more →

One-Sample t-Test

What it is

Tests whether the real mean of a sample of numbers is distinguishable from some null value, given the sample's own size and spread. Unlike a win/loss count, it uses the actual magnitude of each observation - the right tool when "how often" isn't the real question, "how much" is.

How we use it

scipy.stats.ttest_1samp, wrapped as evaluation.mean_significance, tests each advised bet's real bet_profit_units against a null of 0 (breaking even) - reported as roi_p_value. This is the honest answer to "did the advised bets actually make money, or is this within noise": a bet's win/loss outcome alone doesn't capture the real price it was placed at (a -150 favorite winning 55% of the time and a +150 underdog winning 55% of the time are very different real outcomes), but its real profit already prices that in. Quant-analytics item #5.

Learn more →

Evaluation Metrics

Brier Score

What it is

Mean squared error between a predicted probability and the actual binary outcome (0 or 1). Lower is better; 0 is perfect; a model that always predicts 0.5 scores 0.25 on a 50/50 outcome. Rewards both good ranking AND good calibration at once.

How we use it

evaluation.brier_score/ml_models.evaluate_classifier_predictions - the primary metric used to pick between calibration methods, and the basis for game_evaluation._beat_closing_line_rate's per-game model-vs-market comparison.

Learn more →

Log Loss (Cross-Entropy)

What it is

Negative log-likelihood of the actual outcome under the predicted probability. Like Brier score, it rewards calibrated probabilities - but it punishes a confident, wrong prediction (e.g. predicting 1% when the outcome happens) far more severely than Brier does, since the loss blows up as predicted probability approaches the wrong extreme.

How we use it

evaluation.log_loss/ml_models.evaluate_classifier_predictions - the primary metric used to select model FAMILY (logistic regression vs. gradient boosting) before calibration is even considered.

Learn more →

ROC AUC

What it is

The probability that a classifier ranks a random positive example above a random negative one. Measures pure ranking/discrimination ability - unlike Brier/log loss, it's completely insensitive to whether the predicted probabilities themselves are well-calibrated.

How we use it

ml_models.evaluate_classifier_predictions reports it alongside log loss for every classifier, as a check on whether a model that scores worse on log loss might still be separating outcomes better (or vice versa).

Learn more →

Permutation Feature Importance

What it is

A model-agnostic way to measure how much each feature actually matters: shuffle one feature's values (breaking its real relationship to the outcome) and see how much the model's performance drops. A bigger drop means the model was relying on that feature more.

How we use it

scripts/train_hitter_hit_model.py:feature_importance_report via sklearn.inspection.permutation_importance, computed only on the untouched holdout - chosen deliberately over SHAP to avoid adding a new dependency for one report.

Learn more →

Calibration / Reliability Diagrams

What it is

A diagnostic that bins predictions by predicted probability and compares each bin's average predicted probability to that bin's actual outcome rate. A perfectly calibrated model's points fall on the diagonal - systematic over- or under-confidence shows up as a visible bend away from it.

How we use it

evaluation.calibration_table - the same underlying idea that motivates fitting isotonic/Platt calibration in the first place (above), used here as a plain diagnostic table rather than a correction.

Learn more →

Pearson Correlation Coefficient

What it is

A number from -1 to 1 measuring the strength and direction of a LINEAR relationship between two variables. 0 means no linear relationship (though a real nonlinear one could still exist).

How we use it

ml_models.evaluate_predictions reports it between predicted and actual values for every regression target (DK points, HR9), as a simple, standard companion to mean absolute error.

Learn more →

Resampling

Bootstrap Resampling

What it is

Estimating the sampling distribution of a statistic by repeatedly drawing new samples, WITH replacement, from the real observed data itself, rather than assuming a theoretical distribution. Especially useful when the real underlying data-generating process is unknown or hard to model directly.

How we use it

scripts/backtest_streak_decision.py resamples real historical (probability, outcome) day-pairs 1,000 times to compare the decision-theory play/sit rule against the status-quo rule under many plausible real day-orderings, reporting the real win rate and mean/median final streak across resamples - a genuine nonparametric bootstrap, no distribution assumed on the underlying probabilities.

Learn more →

Decision Theory

Dynamic Programming / Bellman Optimal Stopping

What it is

A way to solve sequential decision problems by working BACKWARD from the end: define the value of being in each state on the final day, then recursively define the value of each state one day earlier as the best of "stop now" vs. "the expected value of continuing," given tomorrow's already-solved values. This is Bellman's optimality principle - it collapses an exponentially large decision tree into one manageable pass through states and days.

How we use it

decision_theory.solve_reservation_thresholds models "risk today's Beat the Streak pick, or protect the streak I already have" as a finite-horizon optimal-stopping problem: state = current streak length, recursion runs backward from the end of the remaining days. Because the next day's values are already known, the optimal action collapses to a closed-form probability threshold - should_play just compares today's real predicted probability against it.

Learn more →

Portfolio & Risk Concepts

Diversification (Correlation-Aware Tie-Breaking)

What it is

The general finance idea that combining imperfectly-correlated bets reduces the risk of them all failing together, compared to betting the same underlying risk twice. Formally this requires estimating a real correlation or covariance between the bets.

How we use it

Honestly labeled as a proxy, not the real thing: predictions._diversify_second_pick never estimates an actual correlation. It's a sign-only heuristic: if a day's #1 and #2 picks share the same real game, and a comparably-ranked pick from a DIFFERENT game exists nearby, prefer it - because shared-game correlation can only ever raise the chance both picks miss together, never lower it, so the direction of the swap is safe even without knowing the real magnitude. Gated at 0.0 (a no-op) in production until backtest evidence justifies turning it on.

Learn more →

Sabermetric Domain Formulas

Pythagorean Expectation

What it is

Bill James' formula estimating a team's "true" win percentage from its runs scored and allowed - RS^exp / (RS^exp + RA^exp) - on the idea that runs scored/allowed are a more stable, less luck-affected signal than actual win/loss record over a partial season.

How we use it

teams.compute_strength_metrics computes this per rolling window (with a custom-tuned exponent, config.PYTHAGOREAN_EXPONENT, not the classic 2), feeding the "Pythagorean strength"/"Pythagorean confidence" signals in game_picks.py.

Learn more →

log5

What it is

Also Bill James: an odds-ratio formula for combining two independent rates (a batter's rate against all pitchers, a pitcher's rate allowed to all batters) into the rate expected specifically in that matchup, given each side's rate relative to the league average.

How we use it

matchup._log5 combines a batter's WAVE hit rate with the opposing pitcher's blended PAVE allowed-rate into a real matchup-specific hit probability in compute_matchup_hit_probability.

Learn more →

Park Factor

What it is

A per-venue adjustment for how much a ballpark inflates or suppresses scoring relative to a league-average park, so a hitter's raw numbers aren't unfairly credited/blamed for the park they happen to play in.

How we use it

teams.compute_park_factors - each venue's average combined runs per game, normalized to the across-all-venues mean (1.0 = neutral), used to scale matchup rates in matchup._park_factor_multiplier.

Learn more →

FIP (Fielding Independent Pitching)

What it is

A pitcher-evaluation stat built only from outcomes a pitcher directly controls (home runs, walks/HBP, strikeouts), deliberately excluding balls in play - since what happens to a ball in play depends heavily on the defense behind the pitcher, not just the pitcher. A cleaner signal of pitcher skill than ERA for that reason.

How we use it

age_curve.build_historical_pitcher_seasons computes real FIP ((13*HR + 3*(BB+HBP) - 2*SO)/IP + config.FIP_CONSTANT) as an age-curve feature/label, used in place of ERA for exactly this reason.

Learn more →

Statcast-Derived Metrics (xBA, xwOBA, Barrel Rate, wOBA)

What it is

MLB's own tracking-data-based metrics: "expected" stats (xBA/xwOBA) model what a batted ball's outcome SHOULD have been given its exit velocity/launch angle, independent of whether a fielder happened to make a great play; "barrel" is a specific, officially-defined combination of exit velocity and launch angle known to produce great outcomes; wOBA weights every plate-appearance outcome by its real run value rather than counting a single and a home run equally.

How we use it

hitters.compute_quality_of_contact consumes these as Statcast's own precomputed per-pitch columns directly (not independently re-derived) - averaged/rated over real batted-ball events and recency-window-blended, same as every other windowed signal here.

Learn more →

Decision Score

What it is

A plate-discipline metric: per real pitch, was swinging or taking the better choice FOR THIS BATTER right now, given the pitch's real Statcast zone and their own recency-windowed OPS-equivalent production in that specific zone (shrunk toward their own overall OPS, never a league average - "is this a good zone for me," not "is this objectively a good pitch")? Decision_Score is the real % of pitches where a batter's actual swing/take choice matched that advice.

How we use it

decision_score.compute_decision_score builds each batter's real windowed OBP/SLG/OPS overall and per zone (compute_batter_overall_ops/compute_zone_ops, shrunk via helpers.shrink_rate, config.DECISION_SCORE_ZONE_SHRINKAGE_STRENGTH pseudo-PAs), then compares each pitch's zone-OPS against that batter's own overall OPS to classify the advised action. A real, no-lookahead backtest (scripts/backtest_decision_score.py; train Mar-Jun 2025, test Jul-Sep 2025, held out entirely from the reference it's graded against) validated the core zone signal: PA-ending pitches where the batter's real choice matched the advice scored a real 0.768 mean PA value (on-base + total bases) vs. 0.624 for mismatched ones (n=86,389, p<0.0001 pooled AND per-batter-paired). A real, honest negative finding from that same backtest: hand-designed count-context/game- situation threshold adjustments did NOT improve on the zone-only baseline at any magnitude swept, so both ship at a neutral 1.0 (the classifiers themselves - classify_count_context, the real high-leverage flag - stay in the code, just not yet wired to move the threshold). A separate real, no-lookahead significance test at the team level (does a team's average Decision_Score, weighted by who actually played, predict real game outcomes beyond the existing team model) did NOT clear a real bar (p=0.16 controlling for pyth_Strength) - so Decision_Score is a player-level-only metric here, not part of any team rating or Automated Game Pick.

Learn more →

Sports-Betting Math

Moneyline Implied Probability & De-Vig

What it is

American moneyline odds convert to an "implied probability" per side, but a real two-sided book's implied probabilities always sum to MORE than 100% - the extra is the bookmaker's built-in margin (the "vig" or "overround"). De-vigging normalizes both sides back down so they sum to exactly 100%, recovering the book's actual best real estimate of the true probability.

How we use it

market_odds.moneyline_to_implied_probability (standard American-odds conversion) and devig (proportional normalization) turn real ESPN/DraftKings moneylines into a real market_home_win_probability, logged alongside this project's own model probability as an honest external benchmark - "did we beat the closing line," not just our own heuristic.

Learn more →

Data & Feature Engineering

Idempotent Dedup Keys

What it is

Designing a write/append operation so running it twice with the same input produces the same result as running it once - usually by deduping on a real natural key rather than blindly appending. Makes a daily pipeline safe to re-run without corrupting its own history.

How we use it

Every append-only log in this project dedupes on a real composite key before writing - predictions on (date, key_mlbam, metric) (predictions.append_predictions), game picks on (date, game_pk, metric) (game_predictions.append_game_predictions), keeping the LAST (most-resolved) row on a collision so a re-run never clobbers an already-known real outcome.

Learn more →