Multi-Sport Prediction Stack

Summary

A multi-sport prediction stack is a unified modeling architecture that shares infrastructure, feature engineering, and calibration logic across football (soccer), NFL, NBA, and other sports. Rather than building separate silos, the stack uses a shared "core" (feature engineering pipeline, base model, calibration layer) with sport-specific "heads" (sport-specific feature sets, prediction heads, and calibration parameters).

The core insight is that many modeling decisions are sport-agnostic: ELO-style rating updates, Bayesian calibration, probability calibration, and EV calculation all work the same way regardless of the sport. What differs is the feature set (xG in football vs. yards per play in NFL), the outcome space (1X2 in football vs. point spreads in NFL), and the market structure (American odds vs. decimal odds).

A well-designed stack reduces maintenance overhead, enables cross-sport learning (similar team quality signals in basketball may inform football predictions), and provides consistent risk management across all positions.

Architecture

Layer 1: Core Infrastructure
- Data ingestion: Unified connector that normalizes match data, odds data, and statistics into a common schema
- Feature store: Precomputed features shared across sports (team form over last N games, H2H history, rest days, home/away split)
- Model registry: Central registry of model versions with metadata (sport, training period, validation metrics)

Layer 2: Sport-Specific Feature Engineering

Football features:
  - xG (expected goals), xGA (expected goals against)
  - Shot counts, shot-on-target counts
  - Recent form (WDL in last 5/10 games)
  - Head-to-head record
  - ELO/Glicko-2 rating

NFL features:
  - DVOA (Defense-adjusted Value Over Average)
  - yards per play (offense and defense)
  - turnover differential
  -EPA per play (Expected Points Added)
  - Rest days, travel distance

NBA features:
  - Four factors (eFG%, TOV%, ORB%, FT Rate)
  - Pace (possessions per game)
  - Net rating (offensive rating - defensive rating)
  - B2B game fatigue

Layer 3: Sport-Specific Model Heads
- Each sport has its own model architecture (Poisson for football, linear regression for NFL spreads, logistic regression for NBA moneylines)
- Models share the same training loop, validation framework, and hyperparameter space
- Outputs: probability distributions over the relevant outcome space

Layer 4: Shared Calibration Layer
- All sport outputs pass through a single calibration module (isotonic regression or Platt scaling)
- Calibrated probabilities enable sport-agnostic EV calculation: EV = p * decimal_odds - 1
- Cross-sport Kelly sizing: portfolio-level risk management that allocates bet size based on perceived edge across all sports simultaneously

Key Design Decisions

  • Shared vs. sport-specific: The calibration layer and EV calculation are shared. Feature engineering and model architecture are sport-specific.
  • Cross-validation strategy: Walk-forward validation per sport with non-overlapping time windows; cross-sport validation tests whether the calibration layer generalizes
  • Feature sharing: Some features (team form, rest days, travel) are genuinely sport-agnostic and can be computed once in the core layer
  • Market-specific odds normalization: Convert American, decimal, and implied probability odds to a common format before feeding into the calibration layer

Python Structure

class MultiSportStack:
    def __init__(self):
        self.core = FeatureStore()          # Shared feature engineering
        self.calibrator = Calibrator()       # Isotonic regression, shared
        self.heads = {
            "football": FootballModel(),      # Poisson-based
            "nfl": NFLModel(),               # Linear regression
            "nba": NBAModel(),               # Logistic regression
        }

    def predict(self, sport, home_team, away_team, bookmaker_odds):
        # 1. Compute shared features
        features = self.core.build(sport, home_team, away_team)

        # 2. Sport-specific prediction
        raw_probs = self.heads[sport].predict(features)

        # 3. Calibrate probabilities
        calibrated = self.calibrator.calibrate(raw_probs)

        # 4. Compute EV for each outcome
        ev = self.compute_ev(calibrated, bookmaker_odds)
        return {"probabilities": calibrated, "ev": ev}

    def compute_ev(self, probabilities, odds):
        """Sport-agnostic EV calculation."""
        evs = {}
        for outcome, prob in probabilities.items():
            decimal_odds = odds[outcome]
            evs[outcome] = prob * decimal_odds - 1  # EV as decimal
        return evs

    def Kelly_sizing(self, ev, bankroll, kelly_fraction=0.5):
        """Cross-sport Kelly position sizing."""
        # Size all positions by Kelly fraction of bankroll
        if ev <= 0:
            return 0
        # Kelly fraction limits overfitting to uncertain edges
        return kelly_fraction * ev * bankroll

Notes

  • Multi-sport stacks require discipline in avoiding information leakage: a football model should not train on NFL data, but the calibration layer can use cross-sport data for regularization
  • The shared calibration layer is particularly valuable when one sport has very little historical data (e.g., international tournaments) โ€” calibration parameters from high-data sports can bootstrap the low-data sport
  • Cross-sport portfolio management requires normalizing bet sizes: NFL point spread bets and NBA moneylines have different vig structures, so true Kelly sizing must de-vig odds before computing edge
  • Key failure mode: sport-specific heads that are too similar (share too many parameters) cause overfitting; heads that are completely separate lose cross-sport learning benefits. The balance point is to share the calibration layer but keep the feature and model layers separate