Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mundial Predictor 2026

Python 3.10+ License: MIT

A Python CLI application that simulates the 2026 FIFA World Cup end-to-end, from the group stage through the final. Features a Poisson-based match engine with upset mechanics, extra time, penalty shootouts, and full FIFA 2026 regulations compliance (head-to-head tiebreakers, 495-combination third-place bracket table).

Win probabilities are calibrated against real betting market odds from Sportium, Betway, and Retabet (May 2026). After 100,000 simulations, champion distributions match market expectations within ~2 percentage points.

Features

  • 48 teams, 12 groups, 104 matches per complete simulation
  • Poisson-based match engine with realistic goal distributions
  • Upset mechanics: 7% chance of underdog heroics per match
  • FIFA 2026 tiebreakers: Head-to-head → GD → GF → FIFA ranking → random draw
  • 495-combination bracket table: Exact match per FIFA Annex C for Round of 32
  • Two simulation modes: Batch (default: 107 sims) and interactive (live step-by-step)
  • SQLite storage: Persist and query thousands of simulations
  • Team advancement tracking: Per-team phase-exit probabilities (Group → R32 → R16 → QF → SF → Final → Champion)
  • HTML export: Self-contained report with champion distribution, advancement stats, and match details
  • 100% offline: No network dependencies at runtime
  • Calibrated coefficients: Real betting market odds, power-law strength curve

Installation

# Clone the repository
git clone https://github.com/<your-username>/mundial-predictor.git
cd mundial-predictor

# Create a virtual environment (recommended)
python -m venv .venv

# Activate the virtual environment
# On macOS/Linux:
source .venv/bin/activate
# On Windows:
.venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

Requires Python 3.10+.

Quick Start

# Run 107 batch simulations (default)
python main.py simular

# Run 1000 simulations with seed and HTML report
python main.py simular --cantidad 1000 --seed 42 --export-html

# Interactive step-by-step simulation
python main.py live

# Show champion distribution
python main.py stats --top 10

# Show all teams' advancement probabilities
python main.py stats --avance

# Show advancement stats for a specific team
python main.py stats --avance --equipo "España"

CLI Commands

simular — Batch Simulation Mode

python main.py simular --cantidad 1000 --seed 42 --export-html

Options:

  • --cantidad N: Number of simulations to run (default: 107)
  • --seed S: Random seed for reproducibility
  • --export-html: Generate HTML report after simulation
  • --db-path PATH: Custom SQLite database path (default: mundial.db)

live — Interactive Mode

python main.py live --seed 42 --velocidad paso

Options:

  • --seed S: Random seed for reproducibility
  • --velocidad [rapido|paso]: Auto-advance or step-by-step (default: paso)

stats — Statistics Mode

python main.py stats --top 10 --equipo "España" --sorpresas --avance

Options:

  • --top N: Show top N champions by frequency
  • --equipo X: Detailed stats for a specific team
  • --sorpresas: Show underdog champion surprises (wins outside top 8 by probability)
  • --avance: Show team advancement statistics (phase-exit probabilities for all 48 teams)
  • --desde-sim N --hasta-sim M: Filter by simulation ID range
  • --db-path PATH: Custom SQLite database path

migrar-avance — Advancement Migration

python main.py migrar-avance

Populates the avance_equipos table from existing partidos data. Only needed for databases created before advancement tracking was added. New simulations automatically record advancement data.

Options:

  • --db-path PATH: Custom SQLite database path

Architecture

mundial-predictor/
├── main.py                  # CLI entry point (Click)
├── data/
│   ├── teams.py             # 48 teams, groups, strengths
│   ├── probabilities.py     # Win probabilities, attack/defense derivation, constants
│   ├── schedule.py          # Match calendar and venues
│   └── bracket_table.py     # 495-combination R32 bracket table
├── engine/
│   ├── match.py             # Poisson match simulation
│   ├── group_stage.py       # Group stage, tiebreakers, 3rd place qualification
│   └── knockout.py          # Knockout bracket, advancement computation
├── storage/
│   ├── db.py                # SQLite persistence, stats queries, migrations
│   └── models.py            # Dataclasses for database rows and tournament results
├── cli/
│   ├── commands.py          # Command implementations
│   ├── display.py           # Rich terminal UI helpers
│   └── export_html.py       # Jinja2 HTML report generation
├── tests/
│   ├── test_match.py        # Match engine tests (13 tests)
│   ├── test_group_stage.py  # Group stage tests (15 tests)
│   └── test_advancement.py  # Advancement computation and storage tests (22 tests)
├── requirements.txt
└── README.md

Simulation Model

Strength & Probability Calibration

Team strengths are derived from real betting market odds:

strength = (win_probability * 100) ** 0.24
  • Win probabilities sourced from Sportium, Betway, Retabet averages (May 2026)
  • Overround removed (~22.3% normalization to sum to 1.0)
  • Pure betting-based: no FIFA ranking factor, no Elo — market odds already price in all relevant information
  • Power exponent 0.24 (between fourth-root 0.25 and cube-root 0.33) produces the best fit against real odds

Match Engine

  • Expected goals: λ = attack_strength × defense_strength × home_advantage
    • attack = strength × deterministic_variation
    • defense = 2.5 / (strength + 1.5) × deterministic_variation
    • deterministic_variation = 0.97 + hash(team, seed) % 7 / 100 (±3% per team, per simulation)
  • Lambda cap: λ clamped to [0.1, 4.0] after all modifiers to prevent extreme scores
  • Home advantage: ×1.1 for host nations (USA, Mexico, Canada) in group stage only
  • Upset mechanism: 7% chance per match (underdog λ ×1.8, favorite λ ×0.6)
  • Extra time: λ ×0.4 for 30 minutes in knockout ties
  • Penalty shootout: Binomial distribution per kick (~75% conversion), sudden death

Group Stage

  • 12 groups of 4 teams (A–L), round-robin (72 matches)
  • Points: Win=3, Draw=1, Loss=0
  • FIFA 2026 tiebreakers (in order): Points → Head-to-head → Goal difference → Goals for → FIFA ranking → Random draw
  • Top 2 per group + 8 best third-placed teams advance (32 teams total)

Knockout Stage

  • 32 teams, single-elimination bracket
  • Round of 32 matchups determined by 495-combination FIFA Annex C table (pre-parsed from Wikipedia)
  • R16 → Quarterfinals → Semifinals → Third-place match → Final

Calibration Results (100,000 simulations, seed 42)

Team Simulated Target (market) Δ
Spain 14.5% 14.4% +0.1
France 13.2% 12.8% +0.4
England 11.3% 12.0% -0.7
Argentina 11.2% 10.5% +0.7
Brazil 10.0% 10.3% -0.3
Portugal 7.9% 9.1% -1.2
Germany 6.4% 6.7% -0.3
Netherlands 4.5% 5.2% -0.7

All top 8 within ~1.2 percentage points of market odds.

Statistics Guide

The simulator produces several interrelated statistics. Here is what each metric means and how to interpret it.

Champion Distribution (stats --top N)

Shows how often each team wins the tournament across all simulations.

  • count: Number of simulations where this team was champion
  • percentage: (count / total_simulations) × 100
  • Percentages shown with 2 decimal places (so 22/100,000 = 0.02%, not rounded to 0.0%)
  • Underdog champions (stats --sorpresas): Teams outside the top 8 by pre-tournament win probability that won at least once

Interpretation: A team at 10% is expected to win 1 in 10 World Cups. The sum of all percentages = 100%. This is the most intuitive metric but also the noisiest at low counts — 1 win in 1,000 simulations = 0.10%.

Team Advancement Statistics (stats --avance)

Shows each team's probability of reaching every knockout phase:

Column Meaning
Best Highest phase reached on average (e.g., "Group Stage", "Round of 32", "Quarterfinals", "Semifinals", "Final", "Champion")
Group Exit Probability of advancing from the group stage (top 2 + best 3rd)
R32 Probability of winning the Round of 32 match
R16 Probability of reaching the Quarterfinals
QF Probability of reaching the Semifinals
SF Probability of reaching the Final
Final Probability of reaching the championship match
🏆 Probability of winning the tournament (same as champion distribution)

How to read it: Each column is conditional on reaching the previous phase. So SF = 30% means "30% chance of reaching the Final given the simulation started." The cascade naturally shrinks — even strong teams have ~5–10% chance of being upset in any single knockout match.

Specific team filter (stats --avance --equipo "España"): Shows all 8 columns for one team plus the distribution of which phase it reached most frequently as its "best" result.

Why phases are tracked per simulation: The data is stored at record time (not derived at query time), so filters like --desde-sim N --hasta-sim M work efficiently on advancement data.

Phase Descriptions

Phase Teams Description
Group Stage 48 12 groups of 4, round-robin
Round of 32 32 Group winners + runners-up + 8 best third-placed teams
Round of 16 16 Knockout winners from R32
Quarterfinals 8 Knockout winners from R16
Semifinals 4 Knockout winners from QF
Final 2 Semifinal winners
Champion 1 Final winner

The third-place match is played between semifinal losers but is not tracked as a separate phase — those teams are recorded as "Semifinals" best phase.

HTML Report

The generated HTML report (--export-html) includes:

  1. Summary card: Total simulations, most common champion, average goals, underdog champion percentage
  2. Champion distribution: Horizontal bar chart for all 48 teams (top 8 highlighted, underdogs in red)
  3. Team Advancement table: All 8 columns for every team
  4. Underdog Champions table: Teams outside top 8 that won at least once
  5. Last simulation details: Full group standings + all knockout match results

Sample Queries

# Who has the best final appearance rate outside the favorites?
python main.py stats --avance --top 5 --equipo "Noruega"

# Compare two teams' knockout stage performance
python main.py stats --avance --equipo "Marruecos"
python main.py stats --avance --equipo "Japón"

# See the full picture in one HTML file
python main.py simular --cantidad 10000 --seed 42 --export-html
open report.html

Testing

pytest tests/ -v

50 tests across 3 test files:

tests/test_match.py (13 tests):

  • Match engine convergence, determinism, upset mechanics
  • Penalty shootout correctness (binomial distribution, no score corruption)
  • Home advantage, seed reproducibility, extra time logic

tests/test_group_stage.py (15 tests):

  • Group stage integrity, points calculation, tiebreaker ordering
  • Head-to-head tiebreaker before goal difference (FIFA 2026 rule)
  • Third-place team ranking and qualification logic
  • Full tournament group simulation

tests/test_advancement.py (22 tests):

  • Advancement computation correctness across all phases
  • Edge cases: 0 simulations, single simulation, all teams same phase
  • Storage and retrieval from SQLite
  • Migration from existing partidos data
  • Display formatting for Rich tables

Troubleshooting

ModuleNotFoundError: No module named 'rich' (or similar)

Make sure you've activated the virtual environment and installed dependencies:

source .venv/bin/activate
pip install -r requirements.txt

Database file is too large

The mundial.db file grows with each simulation. You can safely delete it to start fresh:

rm mundial.db
python main.py simular --cantidad 100

HTML report not generating

Ensure you're using the --export-html flag. The report is saved as report.html in the project root:

python main.py simular --cantidad 1000 --export-html
open report.html  # macOS
xdg-open report.html  # Linux
start report.html  # Windows

Wrong Python version

Check your Python version:

python --version

If it shows Python 3.9 or lower, try python3 instead of python, or install Python 3.10+.

Performance

  • <0.5s per single simulation (104 matches)
  • <5 minutes for 10,000 simulations
  • SQLite queries on 100K rows return in <100ms

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages