Skip to main content
Glama
tpomerance

worldcup-ai-forecaster

by tpomerance
README.md
# ⚽ World Cup AI Forecaster

**Can a time-series "foundation model" predict the 2026 FIFA World Cup?** A hands-on, honest case study in when modern AI helps a prediction problem — and when a 50-year-old algorithm does just as well.

![Python](https://img.shields.io/badge/python-3.10%2B-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Status](https://img.shields.io/badge/status-live%20during%20WC%202026-orange)

---

> I tried to forecast the 2026 World Cup with the AI everyone's hyping — a foundation model (Google's **TimesFM**) that predicts *any* series of numbers, zero training required. A football rating formula from the **1960s (Elo) beat it.** Then one small change — feeding it a *smoother* signal — flipped it from worst to best. The real twist: do it right, and *everything* ties — the old formula, the cutting-edge AI, a trained meta-model, even the live betting market — all jammed against the same ceiling.
>
> 📝 **Read the full story:** [`docs/article.md`](docs/article.md) · 🔬 **Methodology:** [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) · 📊 **All results:** [`docs/RESULTS.md`](docs/RESULTS.md)

## The one finding, in one chart

Same foundation model, two different inputs. Fed raw goal counts it's the **worst** model; fed a *smooth* signal (each team's Elo trajectory) the **identical** model becomes the best. The representation mattered more than the model.

![The flip](assets/fig2_the_flip.png)

## What this is

A complete, reproducible forecaster for the 2026 World Cup that:

- **Forecasts every match** (win / draw / loss + expected score) from team strength,
- **Simulates the whole tournament** (groups → the official 48-team bracket → champion) via Monte-Carlo,
- **Validates live** against games already played and against a real prediction market (Polymarket),
- Pits a **TimesFM foundation model** against honest classical baselines (Elo, Dixon-Coles Poisson), a **trained meta-model**, and **TabPFN** (a tabular foundation model),
- Ships an **MCP server** so any LLM can use it as a forecasting agent.

Everything runs on **free, no-key data** (match history + fixtures from one public CSV; optional Polymarket odds from a public API).

## Quickstart

```bash
git clone https://github.com/tpomerance/worldcup-ai-forecaster.git && cd worldcup-ai-forecaster
python -m venv .venv && source .venv/bin/activate

# install torch for your hardware first (https://pytorch.org), then:
pip install -r requirements.txt

# 1) pull data + show live group standings
python -m src.ingest

# 2) honest backtest: TimesFM vs Elo vs Poisson vs blend (add --timesfm)
python -m src.evaluate --timesfm

# 3) predict upcoming matches / a single match / the whole tournament
python predict.py --next 6
python predict.py --match "Spain vs Morocco"
python predict.py --simulate --sims 10000

# 4) full illustrated report (Markdown + PDF) and the figures
python -m src.report
python -m src.make_pdf

# tests
python tests/test_sanity.py
```

## The forecaster's current call

| | Model (simulation) | Live market (Polymarket) |
|---|---|---|
| Favorite | **Argentina ~30%** | France ~18% |
| Then | Spain · France · England | Argentina · Spain · England |

![Predicted bracket](assets/fig6_bracket.png)

*(Numbers update as the tournament progresses — re-run `python refresh.py`.)*

## How well does it actually do?

Validated **pre-kickoff** on games already played, scored with RPS (lower = better):

| | RPS | vs |
|---|----:|----|
| **Our model** | **0.168** | — |
| Polymarket (the market) | 0.152 | sharper (markets are hard to beat) |
| Clueless (1/3 each) | ~0.24 | we're far better than chance |

The model and the market agree on ~all match picks; its misses are genuine upsets that **fooled the market too**. See [`docs/RESULTS.md`](docs/RESULTS.md) for the full tables and the surprise analysis.

![Upsets vs fair mismatches](assets/fig3_surprise.png)

## Project structure

```
worldcup-ai-forecaster/
├── src/
│   ├── ingest.py            # data: results + fixtures + live standings (no API key)
│   ├── elo.py               # World-Football Elo ratings
│   ├── features.py          # time-decayed attack/defense + per-team series
│   ├── match_model.py       # Dixon-Coles / Poisson → W/D/L, scorelines
│   ├── forecast.py          # TimesFM forecasting (goals & Elo trajectory)
│   ├── baselines.py         # Elo, last-value Poisson, market-implied
│   ├── odds.py / polymarket*.py  # bookmaker + Polymarket (champion & per-match)
│   ├── blend.py / meta_model.py  # ensemble + trained stacker + TabPFN
│   ├── evaluate.py / validate_live.py   # backtests & live validation (RPS/Brier)
│   ├── simulate.py / bracket2026.py     # Monte-Carlo over the official bracket
│   ├── report.py / make_pdf.py / export_figures.py   # reporting & figures
│   └── ...
├── predict.py               # CLI: --match / --next / --simulate / --report
├── refresh.py               # daily refresh of data + report + figures
├── polymarket_mcp.py        # MCP server → LLM forecasting agent
├── agent_run.py             # run the agent over the MCP tools
├── tests/                   # sanity tests
├── docs/                    # article, methodology, results, MCP/agent guide
└── assets/                  # figures
```

## The forecasting agent (MCP)

`polymarket_mcp.py` exposes the data + model + simulator as MCP tools, so any
MCP-capable LLM client becomes a World Cup forecasting agent — pull live
odds, get a model forecast, and simulate the bracket in plain English. Full
wiring and an honest take on what the agent does (it *relays* the market, it
doesn't out-predict it) in [`docs/MCP_AGENT.md`](docs/MCP_AGENT.md).

## Honest disclaimer

This is a **study in forecasting methodology**, not betting advice. The headline
lesson is deliberately un-hyped: a foundation model is a tool with a *shape* —
match it to the problem and it helps; otherwise a one-line baseline wins. The
market remains the sharpest forecaster, and beating it is not the goal.

## Acknowledgements

- Google Research — [TimesFM](https://github.com/google-research/timesfm)
- [martj42/international_results](https://github.com/martj42/international_results) — match data
- [Polymarket](https://polymarket.com) public API — prediction-market odds
- Prior Labs — [TabPFN](https://github.com/PriorLabs/tabpfn)

## License

MIT — see [LICENSE](LICENSE).