This document serves as the canonical, highly detailed reference guide and architectural overview for the Football Market Intelligence Engine (also known as Football Market Intelligence). It spans all aspects of the system, including domain logic, software architecture, data pipelines, machine learning models, simulation engines, and deployment strategies.
- Executive Summary & Vision
- Domain Background: Football Betting Markets
- System Architecture
- Data Ingestion and Pipelines
- Database Design and Schemas
- Machine Learning and Predictive Modeling
- Simulation Engine
- Market Evaluation and Value Betting
- Backend Implementation Details
- Frontend Implementation Details
- DevOps, Docker, and Deployment
- System Workflows
- Future Enhancements and Roadmap
The Football Market Intelligence Engine is a prediction and market analysis platform. Its primary goal is to identify mispriced betting markets for the World Cup by comparing real-time bookmaker odds against proprietary, data-driven probability models.
At its core, the system acts as an automated quantitative analyst for sports markets. Rather than relying on intuition or simple heuristic rules, the engine leverages:
- Historical Data: Decades of international match results (e.g., Fjelstul World Cup Database).
- Advanced Machine Learning: Models like XGBoost and Logistic Regression to predict match outcomes (1X2, Over/Under, Both Teams to Score).
- Monte Carlo Simulations: Simulating entire tournaments thousands of times to determine the probability of specific teams reaching certain stages (e.g., "Team A to reach Semi-Finals").
- Live Data Feeds: Ingesting real-time odds, lineups, and injury reports via the API-Football integration.
By continuously polling the market and recalculating probabilities, the system highlights "Value Bets" — instances where the bookmakers' implied probability is lower than our model's predicted probability, thereby yielding a positive Expected Value (EV).
To understand the system, one must understand the financial mechanics of sports betting.
Bookmakers offer odds in various formats, but the underlying concept is always an "Implied Probability".
- Decimal Odds (European): E.g.,
2.50. Implied Probability =1 / 2.50 = 0.40(or 40%). - Fractional Odds (UK): E.g.,
6/4. Implied Probability =4 / (6 + 4) = 0.40. - American Odds (Moneyline): E.g.,
+150. Implied Probability =100 / (150 + 100) = 0.40.
Our system standardizes all ingested market data into Decimal odds for internal mathematical operations.
Bookmakers do not offer fair odds. If Team A and Team B are perfectly matched (50% chance each), fair decimal odds would be 2.00 and 2.00. However, a bookmaker will typically offer 1.90 and 1.90.
- Implied Prob A:
1 / 1.90 = 0.526 - Implied Prob B:
1 / 1.90 = 0.526 - Total Market Percentage =
0.526 + 0.526 = 1.052(105.2%)
That extra 5.2% is the Vigorish (vig), overround, or bookmaker's margin. It ensures the bookmaker makes a profit over the long run regardless of the outcome, assuming perfectly balanced action. Our system's goal is to find odds that are so poorly priced by the bookmaker that they overcome this vig.
A "Value Bet" occurs when:
True Probability > Implied Probability
Or, mathematically:
True Probability * Decimal Odds > 1.0
For example, if our XGBoost model determines Team A has a 50% (0.50) chance of winning, but the bookmaker offers decimal odds of 2.20 (implied probability 45.4%), the Expected Value (EV) for a $1 bet is:
EV = (Probability of Win * Profit) - (Probability of Loss * Stake)
EV = (0.50 * $1.20) - (0.50 * $1.00) = $0.60 - $0.50 = +$0.10 (a 10% edge).
The Market Intelligence Engine systematically searches for these edges.
Arbitrage occurs when different bookmakers offer differing odds on the same event, such that backing all outcomes guarantees a profit. While rare and quickly corrected by the market, our market_evaluator continuously cross-references odds across providers (e.g., Bet365, Pinnacle, DraftKings) to flag arbitrage opportunities.
Once a value bet is identified, the system must decide how much of a bankroll should theoretically be allocated. The Kelly Criterion is a formula used for this:
f* = (bp - q) / b
Where:
f*= fraction of the bankroll to wagerb= decimal odds - 1p= probability of winning (from our ML model)q= probability of losing (1 - p)
Our analytics dashboard visualizes Kelly recommendations alongside the identified value bets.
The application follows a modern, decoupled, microservices-oriented architecture running within Docker containers.
The docker-compose.yml defines the following primary services:
db(PostgreSQL): The central relational datastore containing all historical data, market data, and model predictions.backend(FastAPI/Python): The core intelligence engine. It handles API requests, background data polling, and triggering ML pipelines.frontend(Next.js/React): The user interface providing dashboards for analysts to review predictions and market opportunities.prometheus(Optional Analytics): For system monitoring and metrics.
- Ingestion: Background scripts (
scripts/poll_api_football.py) hit external REST APIs (API-Football) and store raw JSON in the database. - Transformation: Data is moved from the
rawschema into normalized relational tables in thecoreschema. - Feature Engineering:
ml/feature_pipeline.pyextracts features (Elo, resting days, squad value) and writes to thefeaturesschema. - Prediction: Models (
ml/train_xgboost.py, etc.) read features, generate probabilities, and write to thepredictionsschema. - Evaluation:
app/evaluation/market_evaluator.pycomparespredictionsvsmarketodds, generating rows in theopportunitiesschema. - Presentation: The Next.js frontend queries the FastAPI backend to display these
opportunitiesto the user in real-time.
Accurate predictions require high-quality data. The system uses a dual-source approach: historical static datasets for training, and live dynamic APIs for inference.
For training models on World Cup data, the engine relies heavily on comprehensive datasets covering international football history.
Various ingestion scripts (such as scripts/ingest_data.py, scripts/ingest_market_values.py, scripts/populate_elo_fifa.py, and scripts/ingest_transfermarkt_players.py) handle:
- Parsing
matches.csv,players.csv,teams.csv. - Normalizing team names (crucial for international football where naming conventions vary, e.g., "USA" vs "United States", "Korea Republic" vs "South Korea").
- Resolving historical anomalies (e.g., West Germany vs Germany).
For real-time operational capability, the system integrates with API-Football.
- Fixtures & Results: Polled every 6 hours to update the core database.
- Lineups & Injuries: Polled at the same time as fixtures. If a star player (e.g., Mbappe, Messi) is unexpectedly benched, the system must detect this and immediately trigger a model recalculation.
- Market Odds: Polled continuously (e.g., every 5-15 minutes) for upcoming matches to track line movement and identify fleeting value bets.
A major challenge in sports analytics is identity resolution across different data providers. The system implements a robust mapping layer in the core.teams and core.players tables to ensure that the "Brazil" from our CSV dataset exactly matches the "Brazil" provided by the live API.
To maintain a clean separation of concerns, the PostgreSQL database is divided into distinct operational schemas, initialized via init-schemas.sql.
Stores raw, unprocessed data exactly as received from external sources.
- Tables:
raw_api_responses,raw_csv_dumps. - Purpose: Acts as a data lake. If parsing logic changes, we can re-process historical data without needing to re-fetch from the API (saving API credits).
The normalized, relational truth of the football domain.
core.teams: Team ID, Name, FIFA Code, Confederation.core.players: Player ID, Name, Position, Date of Birth.core.matches: Match ID, Home Team, Away Team, Date, Competition, Status.core.match_events: Goals, Cards, Substitutions linked to specific minutes and players.
Stores computed metrics used specifically for machine learning.
features.team_elo: Historical and current Elo ratings.features.match_features: A flattened, wide table where each row represents a match, and columns represent the features available exactly prior to kickoff (e.g.,home_elo_diff,away_rest_days,home_squad_value). Crucially, this prevents data leakage.
Stores bookmaker data.
market.odds: Match ID, Bookmaker ID, Market Type (1X2, Over/Under 2.5), Selection (Home, Draw, Away), Decimal Price, Timestamp. This table is highly volatile and grows rapidly.
predictions.model_outputs: The probabilistic output of our ML models.opportunities.value_bets: The actionable outputs, joining predictions against market odds to log identified EV+ bets.
The backend/alembic/ directory contains the version control for the database schema. All changes to the database structure (adding a column, creating an index) are handled via Alembic revision scripts, ensuring deterministic deployments.
The ML pipeline is executed via run_ml_pipeline.py, which orchestrates feature generation and model training.
The success of the predictive models relies entirely on the quality of the features. ml/feature_pipeline.py calculates:
- Elo Ratings: A zero-sum rating system originally developed for chess. When Team A beats Team B, points are transferred from B to A based on the expected outcome. We maintain historical Elo ratings for all national teams, heavily weighted by recent tournament performance.
- Rest Days: The number of days a team has rested since their last competitive match. Crucial in tightly packed tournaments like the World Cup.
- Travel Distance: Approximate geographic distance traveled between venues.
- Squad Market Value: Aggregated financial valuations of the starting XI (often derived from sources like Transfermarkt), acting as a proxy for raw talent.
- Head-to-Head History: Historical dominance between two specific nations.
XGBoost (eXtreme Gradient Boosting) is the primary engine for prediction.
- Why XGBoost? It excels at tabular data, handles non-linear relationships gracefully, and provides built-in mechanisms for dealing with missing data (e.g., missing squad value for a minor team).
- Target Variables: The model is trained on a multi-class objective (
objective='multi:softprob') predicting 0 (Away Win), 1 (Draw), and 2 (Home Win). - Hyperparameter Tuning: Grid search or random search over
max_depth,learning_rate,n_estimators, andsubsampleto prevent overfitting. - Output: An array of probabilities:
[P(Away), P(Draw), P(Home)].
A simpler, highly interpretable model used as a baseline and sanity check. If the complex XGBoost model drastically diverges from the Logistic Regression output, it triggers an anomaly flag. Logistic regression is particularly useful for extracting clear feature coefficients (e.g., "Every 100 points of Elo difference adds X% to win probability").
To prove the model's profitability, it is run against historical market odds in a simulated betting environment.
- The backtester iterates through historical matches chronologically.
- It "places" simulated bets whenever the model identifies a Value Bet.
- It calculates Return on Investment (ROI) and Yield over time.
- It includes realistic parameters like simulating a 5% bookmaker margin and applying Kelly Criterion sizing.
While ML models predict individual match outcomes, the app/simulation/ directory handles full-tournament dynamics.
To price complex "Futures" markets (e.g., "Will France win the World Cup?", "Will England reach the Semi-Finals?"), the system uses Monte Carlo methods.
- The entire tournament structure (Group Stage -> Round of 16 -> Quarters -> Semis -> Final) is programmed into the engine.
- The engine simulates the tournament 10,000 to 100,000 times.
- For each simulated match, a random number is drawn against the model's predicted probabilities to determine the winner.
- If it's a knockout match, draws are resolved via simulated extra time or penalty shootouts (using historical conversion rates).
- The output is a probability distribution for every team reaching every stage.
The power of the simulation engine is its dynamic nature. As soon as a real-world match concludes (e.g., Argentina unexpectedly loses their first group game), the entire simulation is re-run. This instantly updates the probability of Argentina advancing, allowing the system to rapidly identify mispriced odds in the live futures market before bookmakers fully adjust.
The core business logic resides in app/evaluation/market_evaluator.py and app/simulation/market_parser.py.
Bookmaker APIs often provide data in messy, highly nested JSON structures. market_parser.py is responsible for extracting the relevant decimal odds for specific selections and normalizing the data structure. It standardizes market identifiers (e.g., mapping "Full Time Result" to "1X2", mapping "Total Goals Over 2.5" to "O/U 2.5").
The market_evaluator.py service runs continuously:
- Fetch latest predictions from the
predictionsschema. - Fetch the most recent odds snapshot from the
marketschema. - Iterate through all available markets for a match.
- Compare
Model Implied ProbabilityagainstBookmaker Implied Probability. - If
Model Prob > Bookmaker Prob + Threshold(where Threshold is a configurable margin of safety, e.g., 2% to account for model variance), register an Opportunity.
Not all mathematically positive EV bets should be taken. The evaluator includes filtering logic:
- Liquidity Filters: Ignoring odds from obscure bookmakers with low betting limits.
- Volatility Filters: Flagging markets where odds are swinging wildly, which might indicate asymmetric information (e.g., a leaked injury) that the model hasn't ingested yet.
- Maximum Exposure: Tracking simulated bankroll to ensure the system doesn't recommend over-exposure on a single match or team.
The backend is built with FastAPI, providing a high-performance, asynchronous REST API.
The main.py script serves as the master orchestrator. Upon startup, it:
- Triggers Alembic migrations to ensure the DB is up to date.
- Checks if the database is empty (
check_db_empty()). - If empty, it automatically triggers the ingestion scripts, market value population, and the initial ML feature pipeline to bootstrap the system.
- Spawns background processes (like
poll_api_football.py). - Boots the Uvicorn ASGI server to serve the FastAPI application.
Defines the HTTP endpoints consumed by the Next.js frontend.
GET /api/matches- Returns scheduled matches and their predictions.GET /api/opportunities- Returns live value bets.POST /api/simulate- Triggers a manual Monte Carlo simulation.GET /api/analytics/performance- Returns historical ROI data.
Following domain-driven design principles:
- Repositories: Handle direct database interaction (SQLAlchemy queries). E.g.,
MatchRepository.get_upcoming(). - Services: Contain the business logic. They call repositories, process the data, and return standard DTOs (Data Transfer Objects) to the API layer. E.g.,
MarketService.evaluate_match(match_id).
By utilizing AsyncSessionLocal from SQLAlchemy, the backend handles thousands of concurrent requests efficiently, crucial for a system that must constantly poll data and serve real-time web dashboards.
The frontend (frontend/ directory) is a modern web application built for data analysts.
- Framework: Next.js (App Router paradigm).
- Language: TypeScript for type safety across the full stack.
- Styling: Tailwind CSS for rapid, utility-first UI development.
- Components: React components (
frontend/components/cards.tsx) often utilizing headless UI libraries like shadcn/ui for accessible, beautiful interactive elements.
- The 'Command Center' (Home): Displays upcoming high-profile matches, live system status (polling health, model freshness), and a ticker of the most recent highly profitable opportunities found.
- Match Analysis View: A deep dive into a specific fixture. Shows the XGBoost probabilities, Historical Elo charts, recent team form, and a comparison matrix of all bookmaker odds currently available.
- Simulation Hub: A visual representation of the Monte Carlo tournament output. Displays an interactive bracket where users can click a team to see their simulated probability of reaching various stages.
- Performance Analytics: Charts and graphs displaying the theoretical historical ROI of the model if all recommended bets were placed.
This file handles communication with the FastAPI backend. It utilizes native fetch or libraries like axios or SWR/React Query to poll for live updates. It defines TypeScript interfaces that strictly map to the JSON responses from the backend, ensuring frontend reliability.
The entire system is containerized to ensure "works on my machine" translates to "works in production."
The docker-compose.yml orchestrates the multi-container environment.
- Maps internal ports to host ports (e.g., PostgreSQL on 5432, FastAPI on 8080, Next.js on 3000).
- Manages volumes to ensure database persistence between container restarts.
- Sets up internal networking so the backend can communicate directly with the database using internal service names (e.g.,
postgres://user:pass@db:5432/wc_database).
Sensitive data and environment-specific settings are managed via .env files.
DATABASE_URL: Connection string.API_FOOTBALL_KEY: Secret key for data ingestion.ENVIRONMENT: Distinguishes betweendevelopment,staging, andproduction.
Updating the application involves pulling the latest Git changes and rebuilding the containers (docker compose up --build). In a production scenario, this would be handled by a CI/CD pipeline (e.g., GitHub Actions) that runs tests, builds images, and pushes them to a registry before rolling deployment.
Understanding the chronological flow of operations is critical.
When spinning up a fresh instance:
- Docker initializes PostgreSQL.
- FastAPI backend boots.
main.pydetects an empty DB. - Alembic creates all tables.
scripts/ingest_data.pyloads CSV history.ml/feature_pipeline.pycalculates historical Elos and features.ml/train_xgboost.pytrains the initial model.- Backend begins serving requests and starts the polling loop.
Instead of strict cron-based schedules or dynamic match-day loops, the system currently relies on continuous background polling processes and standalone scripts.
-
API Football Poller (
poll_api_football.py): This script runs continuously in the background (launched bymain.py). After an initial 10-minute startup delay to ensure database readiness, it enters a loop where it:- Executes
fetch_wc_data.pyto retrieve the latest 2026 World Cup data. - Executes
scripts/ingest_api_football.pyto process and store this data in the database. - Sleeps for 6 hours before repeating the cycle.
- Executes
-
Betting Markets Poller (
poll_betting_markets.py): A script that fetches current prediction market data from Polymarket and Kalshi. It parses the odds, attempts fuzzy matching to map markets to internal team IDs, and records snapshots of implied probabilities, volume, and liquidity.
The architecture is built to be extensible. Future development phases include:
- Player-Level Analytics: Transitioning from team-level metrics (Elo) to player-level expected goals (xG) and expected assists (xA) models. This would allow the system to price "Player Prop" markets (e.g., "Mbappe to score over 1.5 goals").
- Live In-Play Modeling: Ingesting live match state (possession, dangerous attacks, clock time) to predict outcomes while the match is being played, capitalizing on market overreactions to early goals.
- Automated Bet Placement: Integrating directly with bookmaker or exchange APIs (like Betfair) to automatically execute trades when value is found, completing the transition from an analytical tool to a fully automated algorithmic trading bot.
- Expansion Beyond World Cup: Adapting the pipelines to ingest club data (Premier League, Champions League), which requires handling domestic cup fatigue, transfer windows, and varying league qualities.
End of Document. Maintained by the Advanced Agentic Coding Team.