Skip to content

jassassin68/nfl_batchgineering

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

59 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

nfl_batchgineering

An end-to-end NFL game prediction system targeting point spread and totals betting. The pipeline ingests play-by-play and schedule data from nflverse, transforms it through a layered dbt project in Snowflake, trains a hybrid ensemble ML model, and generates weekly predictions β€” all orchestrated by Dagster.

Primary Goal: Achieve >52.4% ATS (against-the-spread) accuracy to overcome standard -110 juice.


Tech Stack

Layer Technology
Data Warehouse Snowflake
Data Transformations dbt (dbt-snowflake)
Data Ingestion Python Β· Polars Β· nflverse parquet releases
ML Models XGBoost Β· PyMC (Bayesian) Β· PyTorch (Neural Net) Β· scikit-learn
Orchestration Dagster (assets, jobs, schedules)
Code Quality ruff (lint + format)
Logging loguru
Auth Snowflake key-pair authentication
Runtime Python 3.11+

Repository Structure

nfl_batchgineering/
β”œβ”€β”€ dagster_project/               # Dagster orchestration definitions
β”‚   β”œβ”€β”€ __init__.py                # Definitions entry point (assets, jobs, schedules)
β”‚   β”œβ”€β”€ constants.py               # Shared path constants
β”‚   β”œβ”€β”€ assets/
β”‚   β”‚   β”œβ”€β”€ ingestion.py           # nflverse β†’ Snowflake raw assets
β”‚   β”‚   β”œβ”€β”€ dbt_assets.py          # dbt model assets (auto-generated from manifest)
β”‚   β”‚   β”œβ”€β”€ ml_training.py         # XGBoost training asset
β”‚   β”‚   └── predictions.py         # Weekly predictions asset
β”‚   β”œβ”€β”€ jobs/
β”‚   β”‚   └── weekly_pipeline.py     # Full pipeline job definition
β”‚   β”œβ”€β”€ resources/
β”‚   β”‚   └── dbt_resource.py        # dbt CLI resource configuration
β”‚   └── schedules/
β”‚       └── weekly_schedule.py     # Tuesday 8AM NFL-season schedule
β”‚
β”œβ”€β”€ dbt_project/                   # dbt transformations
β”‚   β”œβ”€β”€ dbt_project.yml            # Project config (lookback: 10 seasons)
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ 1_staging/nflverse/    # Cleaned views over raw Snowflake tables
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_play_by_play.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_player_summary_stats.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_team_summary_stats.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_rosters.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_schedules.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ stgnv_injuries.sql
β”‚   β”‚   β”‚   └── stgnv_play_by_play_participation.sql
β”‚   β”‚   β”œβ”€β”€ 2_intermediate/        # Feature engineering (views + materialized tables)
β”‚   β”‚   β”‚   β”œβ”€β”€ int_plays_cleaned.sql
β”‚   β”‚   β”‚   β”œβ”€β”€ int_team_offensive_metrics.sql     # Rolling 4-week EPA/play
β”‚   β”‚   β”‚   β”œβ”€β”€ int_team_defensive_strength.sql    # Rolling defensive EPA allowed
β”‚   β”‚   β”‚   β”œβ”€β”€ int_situational_efficiency.sql     # Red zone, 3rd down, etc.
β”‚   β”‚   β”‚   β”œβ”€β”€ int_game_vegas_lines.sql           # Opening lines, line movement
β”‚   β”‚   β”‚   └── int_upcoming_games.sql             # Future schedule with context
β”‚   β”‚   └── 3_marts/               # Final ML-ready tables
β”‚   β”‚       β”œβ”€β”€ mart_predictive_features.sql       # Per-team, per-week feature set
β”‚   β”‚       β”œβ”€β”€ mart_game_prediction_features.sql  # Historical game rows for training
β”‚   β”‚       β”œβ”€β”€ mart_upcoming_game_predictions.sql # Upcoming games for inference
β”‚   β”‚       β”œβ”€β”€ mart_weather_impact_metrics.sql    # Weather-adjusted efficiency
β”‚   β”‚       └── mart_model_validation.sql          # Walk-forward validation dataset
β”‚   β”œβ”€β”€ macros/
β”‚   └── tests/
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ ingestion/
β”‚   β”‚   └── training_data_loader.py   # Bulk parquet β†’ Snowflake loader (Polars)
β”‚   └── ml/
β”‚       β”œβ”€β”€ base.py                    # Abstract BasePredictor interface
β”‚       β”œβ”€β”€ models/
β”‚       β”‚   β”œβ”€β”€ spread_predictor.py    # XGBoost spread model
β”‚       β”‚   β”œβ”€β”€ elo_model.py           # Elo rating baseline
β”‚       β”‚   β”œβ”€β”€ bayesian.py            # Bayesian state-space model (PyMC)
β”‚       β”‚   β”œβ”€β”€ neural.py              # Shallow neural network (PyTorch)
β”‚       β”‚   └── ensemble.py            # Ridge stacking meta-learner
β”‚       β”œβ”€β”€ utils/
β”‚       β”‚   β”œβ”€β”€ feature_engineering.py # Feature selection and preparation
β”‚       β”‚   β”œβ”€β”€ evaluation.py          # ATS accuracy, Brier score, RMSE
β”‚       β”‚   └── validation.py          # Walk-forward CV helpers
β”‚       β”œβ”€β”€ train_spread_model.py      # XGBoost training entry point
β”‚       β”œβ”€β”€ train_elo_model.py         # Elo model training
β”‚       β”œβ”€β”€ train_ensemble.py          # Full ensemble training
β”‚       └── predict.py                 # Inference: load models β†’ generate predictions
β”‚
β”œβ”€β”€ models/                        # Serialized model artifacts (.pkl, .json)
β”œβ”€β”€ data/                          # Prediction CSV outputs
β”œβ”€β”€ notebooks/                     # Exploratory analysis
β”œβ”€β”€ snowflake_sql/                 # Ad-hoc SQL scripts
β”œβ”€β”€ logs/                          # Rotating application logs
β”œβ”€β”€ pyproject.toml                 # Project metadata, uv/ruff config
β”œβ”€β”€ requirements.txt               # Python dependencies
β”œβ”€β”€ TESTING_GUIDE.md               # dbt layer validation walkthrough
└── CLAUDE.md                      # AI assistant project instructions

Data Pipeline Overview

nflverse GitHub Releases (parquet)
         β”‚
         β–Ό
 TrainingDataLoader (Polars + Snowflake connector)
         β”‚  bulk COPY INTO
         β–Ό
 Snowflake RAW.NFLVERSE  ──────────────────────────────────────┐
         β”‚                                                       β”‚
         β–Ό  dbt run                                             β”‚
 1_staging (views)                                              β”‚
  Β· stgnv_play_by_play                                          β”‚
  Β· stgnv_schedules, stgnv_rosters                              β”‚
  Β· stgnv_player/team_summary_stats                             β”‚
  Β· stgnv_injuries, stgnv_pbp_participation                     β”‚
         β”‚                                                       β”‚
         β–Ό  dbt run                                             β”‚
 2_intermediate (views + tables)                                β”‚
  Β· int_plays_cleaned                                           β”‚
  Β· int_team_offensive_metrics  (rolling EPA/play)              β”‚
  Β· int_team_defensive_strength (rolling EPA allowed)           β”‚
  Β· int_situational_efficiency                                  β”‚
  Β· int_game_vegas_lines                                        β”‚
  Β· int_upcoming_games                                          β”‚
         β”‚                                                       β”‚
         β–Ό  dbt run                                             β”‚
 3_marts (tables)                                               β”‚
  Β· mart_game_prediction_features  ← training data             β”‚
  Β· mart_predictive_features       ← per-team feature store    β”‚
  Β· mart_upcoming_game_predictions ← inference input           β”‚
  Β· mart_model_validation                                       β”‚
  Β· mart_weather_impact_metrics                                 β”‚
         β”‚                                                       β”‚
         β–Ό  Python                                              β”‚
 ML Training (Snowflake β†’ Polars β†’ sklearn/XGBoost/PyMC)       β”‚
  Β· SpreadPredictor (XGBoost)                                   β”‚
  Β· EloModel (baseline)                                         β”‚
  Β· BayesianStateSpace (PyMC AR(1))                             β”‚
  Β· NeuralNetPredictor (PyTorch)                                β”‚
  Β· StackingEnsemble (Ridge meta-learner)                       β”‚
         β”‚                                                       β”‚
         β–Ό  Python                                              β”‚
 Weekly Predictions β†’ Snowflake ML schema + CSV output β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ML Model Architecture

The system uses a stacking ensemble of four base models, combined by a Ridge regression meta-learner:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   XGBoost   β”‚  β”‚  Bayesian   β”‚  β”‚   Neural    β”‚  β”‚    Elo      β”‚
β”‚  Regressor  β”‚  β”‚ State-Space β”‚  β”‚   Network   β”‚  β”‚  Baseline   β”‚
β”‚ (max_depth4)β”‚  β”‚  (PyMC AR1) β”‚  β”‚ (PyTorch,   β”‚  β”‚  (Glickman) β”‚
β”‚ (lr=0.05)   β”‚  β”‚             β”‚  β”‚  ≀3 layers) β”‚  β”‚             β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚   Ridge Regression  β”‚
                   β”‚   Meta-Learner      β”‚
                   β”‚   (+ uncertainty)   β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                   β”‚  Betting Decision   β”‚
                   β”‚  (edge β‰₯ 3 pts β†’   β”‚
                   β”‚   0.25x Kelly bet) β”‚
                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

All models implement the BasePredictor abstract interface (src/ml/base.py) providing fit(), predict(), predict_proba(), save_model(), and load_model().

Validation Strategy

Walk-forward cross-validation is required β€” random splits are never used:

  • Train on seasons [t-n ... t-1], test on season t
  • Repeat sliding the window forward for each available test season
  • Target metrics: ATS Accuracy (>52.4%), Brier Score, RMSE vs Vegas line

Dagster Orchestration

The Dagster project (dagster_project/) defines the full pipeline as software-defined assets:

Asset Description
raw_nflverse_data Loads current-season parquet datasets to Snowflake
raw_schedules Loads full schedules file (all seasons, full replace)
nfl_dbt_assets Runs dbt models (staging β†’ intermediate β†’ marts)
trained_xgboost_model Trains XGBoost spread predictor, saves to models/
weekly_predictions Loads upcoming games mart, runs ensemble inference, writes to Snowflake + CSV

Schedule: Every Tuesday at 8:00 AM during the NFL season (September–February). The schedule auto-derives the current NFL week and season from the execution date.

To launch the Dagster UI locally:

dagster dev -m dagster_project

Snowflake Schema Structure

NFL_ANALYTICS.RAW.NFLVERSE       -- Bulk-loaded parquet tables
NFL_ANALYTICS.STAGING            -- dbt views (stgnv_*)
NFL_ANALYTICS.INTERMEDIATE       -- dbt views/tables (int_*)
NFL_ANALYTICS.MARTS              -- dbt materialized tables (mart_*)
NFL_ANALYTICS.ML                 -- Model artifacts and predictions

Setup

Prerequisites

  • Python 3.11+
  • Snowflake account with key-pair authentication configured
  • dbt CLI (installed via dbt-snowflake)

Install

# Clone and create virtual environment
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

pip install -r requirements.txt

Environment Variables

Create a .env file at the project root:

SNOWFLAKE_ACCOUNT=your_account_identifier
SNOWFLAKE_USER=your_username
SNOWFLAKE_WAREHOUSE=NFL_ANALYTICS_WH
SNOWFLAKE_DATABASE=NFL_ANALYTICS
SNOWFLAKE_SCHEMA=RAW
SNOWFLAKE_ROLE=your_role

# Key-pair authentication
SNOWFLAKE_KEYPAIR_PRIVATE_KEY="-----BEGIN ENCRYPTED PRIVATE KEY-----\n...\n-----END ENCRYPTED PRIVATE KEY-----"
SNOWFLAKE_KEYPAIR_PASSPHRASE=your_key_passphrase

dbt Profile

Add the following to ~/.dbt/profiles.yml:

nfl_batchgineering:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
      user: "{{ env_var('SNOWFLAKE_USER') }}"
      private_key: "{{ env_var('SNOWFLAKE_KEYPAIR_PRIVATE_KEY') }}"
      private_key_passphrase: "{{ env_var('SNOWFLAKE_KEYPAIR_PASSPHRASE') }}"
      role: "{{ env_var('SNOWFLAKE_ROLE') }}"
      database: NFL_ANALYTICS
      warehouse: NFL_ANALYTICS_WH
      schema: STAGING
      threads: 4

Running the Pipeline

1. Load raw data to Snowflake

python src/ingestion/training_data_loader.py

Loads the following nflverse datasets for the current season (historical years are a one-time load):

  • play_by_play β€” full play-by-play with EPA
  • rosters β€” active and historical rosters
  • player_summary_stats β€” per-player season/weekly stats
  • team_summary_stats β€” per-team summary stats
  • play_by_play_participation β€” player participation per play
  • injuries β€” weekly injury reports
  • schedules β€” full schedule with Vegas lines (all seasons, single file)

2. Run dbt transformations

cd dbt_project
dbt run        # Build all models
dbt test       # Run data quality tests

Or layer by layer:

dbt run --select 1_staging
dbt run --select 2_intermediate
dbt run --select 3_marts

See TESTING_GUIDE.md for validation queries and expected row counts.

3. Train models

# XGBoost spread model (primary)
python src/ml/train_spread_model.py

# Elo baseline model
python src/ml/train_elo_model.py

# Full ensemble (requires base models trained first)
python src/ml/train_ensemble.py

Trained artifacts are serialized to the models/ directory.

4. Generate predictions

python src/ml/predict.py --week 15 --season 2025 --output predictions_week15.csv

Or run the full pipeline via Dagster:

dagster dev -m dagster_project

Key Design Decisions

No Look-Ahead Bias

All rolling features use point-in-time joins. Window functions use ROWS BETWEEN N PRECEDING AND 1 PRECEDING β€” never including the current game. This is enforced in dbt intermediate models and validated via mart_model_validation.

Sample Size Constraints

NFL seasons have ~270 games. With ~20 years of data that is ~5,000 samples. The architecture deliberately avoids deep learning (LSTM, transformers) in favor of shallow models with strong regularization.

Calibration Over Accuracy

Models are optimized for Brier Score. Well-calibrated probabilities produce higher ROI than raw directional accuracy (Walsh & Joshi, 2024).

Fractional Kelly Sizing

All bet sizing uses 0.25x Kelly to reduce variance. A minimum edge threshold of 3–4 points against the Vegas line is required before any bet is recommended.


Code Quality

# Lint and format with ruff
ruff check .
ruff format .

Configuration in pyproject.toml: line length 88, Python 3.11 target, isort integrated.


License

MIT

About

Manage code related to data work using NFL datasets

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages