Nihar Bagkar

Case file · Soccer Analytics

Case file · Data + Forecasting

Soccer Analytics

A football analytics centre built under a hard $0 rule: no paid APIs, no subscriptions, no cloud hosting. It forecasts matches and full seasons, scouts players with xG and expected threat, and measures every model against the bookmaker’s closing line instead of assuming it works. Every data source was checked by fetching it directly before any code relied on it.

Status
Built and running locally, in a public repo with GitHub Actions CI.
Built with
Python, DuckDB, SQLite, Polars, NumPy and SciPy, Streamlit, and Altair.
Data
About 274k match results across 34 divisions from 1993 to 2026, plus 3,500 StatsBomb matches with about 89k shots.
Scale
About 20k lines of source code, 610 tests, and 155 commits. Solo, from July 2026.

The problem

Good football analytics usually sit behind paid data

Free sources exist, but their real limits are rarely written down: delayed scores, no lineups, endpoints nobody documents.

Forecasting projects also tend to report accuracy without comparing it to the obvious benchmark, the betting market.

What it does

Five steps from free data to a graded forecast

  • 1. Collect

    Pulls fixtures, results, squads, and near-live scores from free sources. Each fetch is kept as an unchanged raw snapshot that can be replayed later.

  • 2. Match

    Matches club names across sources, such as Köln and Cologne or PSV and PSV Eindhoven, into one canonical record per club and per match.

  • 3. Forecast

    Rates teams and forecasts matches with Poisson and Dixon-Coles models, and simulates full seasons 10,000 times for title, top-four, and relegation odds.

  • 4. Grade

    Scores every forecast walk-forward, with each match predicted from earlier data only, against base rates and the closing odds, with calibration charts.

  • 5. Ask

    Shows it all in a Streamlit dashboard, plus an offline, rule-based assistant that answers plain-English questions like “Arsenal vs Chelsea?”

The Predictions page's Matchup tab for Arsenal v Chelsea, with win, draw and loss prices, expected goals, double chance, total goals and correct-score markets, and a correct-score probability heatmap
Any Premier League matchup, with odds, goals markets, and a correct-score heatmap
Shot map for France v Argentina at the 2018 World Cup, with France xG of 1.64 and Argentina xG of 1.45, and each shot drawn as a circle sized by xG
Match xG and shot map from StatsBomb event data: France 4–3 Argentina, 2018 World Cup. Data: StatsBomb.

Design choices

Four rules it was built under

  • Check sources first.

    Every claim about a data source was confirmed by a direct fetch. Several assumptions in the original plan were wrong, and the corrections are now regression tests.

  • Measure before shipping.

    Changes like Dixon-Coles maximum-likelihood fitting and team-specific home advantage were backtested, and dropped when they didn’t help.

  • Use the market as the benchmark.

    The model learns from the closing odds of matches already played, which closed about a third of its gap to the bookmaker. It never uses odds for upcoming games.

  • Stay local and private.

    No LLM and no cloud. Usage logging stays on the machine, and is used to decide which features to keep or cut.

Under the hood

A replayable pipeline, with its limits written down

Source adapters write raw snapshots, which flow into canonical match resolution, a SQLite live store, and a DuckDB analytics store. The models are Elo, ratio-method and Dixon-Coles Poisson, a shots-on-target blend, market-implied ratings, Monte Carlo season simulation, and expected threat built from StatsBomb events.

The caveats are part of the design. Free live scores come from one undocumented source. StatsBomb data has no current-season coverage and a licence that forbids redistribution. The Premier League team-news adjustment is a prior that hasn’t been backtested. And the model still trails the closing line.

Where it stands

Better informed, and still behind the market

It works end to end, with automatic data refresh. On 10,021 matches across nine leagues the model was never tuned on, market-implied ratings cut the log-loss gap to the closing line from +0.0194 to +0.0127, and all 18 leagues tested improved.

Cloud hosting is built but paused. Next, the chat assistant is meant to become the main interface.