Machine Learning · Predictive Modeling
Hunter Metric
Applied ML platform for predictive sports analytics
The problem
Hunter Metric predicts game outcomes across six professional sports. The real engineering challenge isn't building a model that looks good on paper — it's proving, continuously, that a model's stated probabilities are actually trustworthy before anyone relies on them. The system is built around calibration and governance: every model earns its way into production.
- A model that says '70% chance' is worthless if that outcome only happens 55% of the time — raw scores must be calibrated into honest probabilities.
- Six different sports each need their own model, but a shared, consistent evaluation and promotion standard.
- Backtests are trivially easy to fake with future-leaking data, so evaluation has to be provably chronological.
- A single model can be confidently wrong — you need a way to know when to trust a prediction and when not to.
Architecture
The platform is a staged pipeline: raw game and line data flows into six native per-sport models, whose outputs are combined by a multi-family ensemble and passed through a shared assembler that calibrates, governs, and gates every probability before it surfaces.
Data & ingestion
SourcesPer-sport win models
Six native modelsEnsemble
Multi-familyAssembler & governance
Calibrate + gateSurface
DeliveryData flows downward — each stage feeds the next, from raw game data to a governed, calibrated probability.
Engineering in depth
The snippets below are illustrative of the techniques used — simplified to show the approach without exposing proprietary modeling logic.
// Fit p = sigmoid(A*z + B) so raw model scores become
// probabilities that match observed win frequencies.
public Calibration Fit(IReadOnlyList<(double Z, bool Won)> samples)
{
double a = 1.0, b = 0.0;
for (var iter = 0; iter < MaxIters; iter++)
{
double gradA = 0, gradB = 0;
foreach (var (z, won) in samples)
{
var p = Sigmoid(a * z + b);
var error = p - (won ? 1.0 : 0.0); // gradient of log-loss
gradA += error * z;
gradB += error;
}
a -= LearningRate * gradA / samples.Count;
b -= LearningRate * gradB / samples.Count;
}
return new Calibration(a, b);
}// Build expanding-window folds and refuse any that could
// leak the future into evaluation.
foreach (var fold in BuildExpandingFolds(games, folds: 5))
{
var maxTrain = fold.Train.Max(g => g.Date);
var minValidate = fold.Validate.Min(g => g.Date);
if (maxTrain >= minValidate)
throw new LeakageException(
$"Training through {maxTrain:d} overlaps validation at {minValidate:d}");
yield return Evaluate(fold);
}// A model's predictions only surface once it's been promoted to
// Active. In Shadow it runs on real games but stays invisible.
public WinProbability? Assemble(GameContext game)
{
var prediction = _calibrator.Apply(_model.Predict(game));
// Always record — feeds later calibration + backtesting.
_snapshots.Persist(game, prediction);
return _mode switch
{
AssemblerMode.Active => prediction, // authoritative
AssemblerMode.Shadow => null, // accrue data only
_ => null, // Off
};
}Key decisions
Calibration before confidence
A probability is only useful if it's honest. Fitting Platt scaling and checking reliability diagrams — with a minimum sample threshold before a sport is considered calibrated — means the system never presents overconfident numbers it hasn't earned.
Shadow mode before production
Every new model runs invisibly on live games first, recording predictions without affecting anything. It only graduates to authoritative once it has proven calibrated skill against real outcomes — de-risking changes to a live system.
An ensemble of models that disagree
Logistic regression, LightGBM, and ELO capture different structure. Scoring their agreement — and treating high disagreement as lower confidence — turns three imperfect models into a signal that also knows when to stay quiet.