A context-aware machine learning system that predicts Premier League match outcomes with 62.76% accuracy โ beating random chance (33%), the "always home" strategy (46%), and basic form-based models (56%).
Unlike traditional predictors, this engine considers momentum, fixture congestion, derby matches, and Expected Goals (xG) to generate probabilities that reflect real-world football dynamics.
Most prediction models just look at "who won last week." This one asks:
- ๐ฅ "Are they on a hot streak or declining?" (Momentum analysis)
- ๐ด "Are they playing their 3rd game in 7 days?" (Fatigue tracking)
- โ๏ธ "Is this a derby match?" (High-stakes context)
- ๐ฒ "What does xG say about their luck?" (Performance vs Results)
Result: A 62.76% accurate ensemble model trained on 2,390 historical matches.
| Live Predictions | Custom Match Simulator | Context-Aware Analysis |
|---|---|---|
graph TB
A[FBref Scraper] --> B[PostgreSQL Database]
C[Football-Data API] --> B
B --> D[Feature Engineer]
D --> E[Draw Detector Model]
D --> F[Winner Predictor Model]
E --> G[Ensemble Predictor]
F --> G
G --> H[FastAPI Backend]
H --> I[React Frontend]
I --> J[User]
K[Scheduler] --> A
K --> L[Model Retrainer]
L --> E
L --> F
- FBref Scraper: Historical match data (scores, xG, shots, possession)
- Football-Data.org API: Live fixtures, team info, real-time updates
- PostgreSQL Database: Stores 2,390+ matches, 20 teams, user predictions
A two-stage ensemble system:
Stage 1: Draw Detector
- Specialized Random Forest model
- Trained to identify draw patterns (evenly matched teams, defensive tactics)
- Accuracy: 71% overall, 24% recall on draws
Stage 2: Winner Predictor
- Predicts home or away win when draw is unlikely
- Balanced for home/away bias
- Accuracy: 69% (73% home, 64% away)
Ensemble Strategy:
if draw_probability > 0.40:
return "DRAW"
else:
return winner_predictor.predict()Final Performance: 62.76% accuracy
- Backend: FastAPI serves predictions, manages users, stores history
- Frontend: React + Vite UI with interactive charts (Recharts)
The model doesn't just see "Arsenal won 2-1." It sees:
- Goals scored/conceded per match
- Expected Goals (xG) for/against
- Win rate, Points Per Game (PPG)
| Feature | Why It Matters |
|---|---|
| Derby Match | Arsenal vs Tottenham is more unpredictable than stats suggest |
| Fixture Congestion | Teams playing 3 games in 7 days underperform by ~15% |
| Momentum | Comparing recent 5 vs previous 5 matches (improving or declining?) |
| League Position | Top 6 clash? Relegation battle? Stakes change behavior |
- Historical win/loss/draw record between these specific teams
- Some teams just have a "bogey team"
- Betting odds probabilities (wisdom of the crowd)
Output: A 39-dimensional feature vector for each match.
- Python 3.11+
- Node.js 18+
- Docker (optional, for PostgreSQL)
git clone https://github.com/YOUR_USERNAME/football-predictor-ai.git
cd football-predictor-aidocker run --name postgres-predictor \
-e POSTGRES_PASSWORD=yourpassword \
-e POSTGRES_DB=football_prediction \
-p 5432:5432 \
-d postgres:15cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt# Create tables
python -c "from models.database import Base, engine; Base.metadata.create_all(engine)"
# Seed teams
python scripts/seed_teams.py
# Import historical data
python scripts/import_history.py
# Fetch upcoming fixtures
python scripts/seed_upcoming.pypython ml/train_ensemble.pyThis will create draw_detector_model.pkl, winner_predictor_model.pkl, and scaler.pkl in /models/.
uvicorn main:app --reloadBackend runs at http://localhost:8000
Open a new terminal:
cd frontend
npm install
npm run devFrontend runs at http://localhost:5173
| Strategy | Accuracy | Description |
|---|---|---|
| Random Guess | 33.3% | Pick home/draw/away randomly |
| Always Home | 46% | Always predict home win |
| Basic Form Model | 56% | Just recent results |
| Our Ensemble | 62.76% | Context + xG + Momentum |
precision recall f1-score support
Home Win 0.72 0.79 0.76 227
Draw 0.23 0.16 0.19 106
Away Win 0.66 0.71 0.69 145
accuracy 0.63 478
โ
Home Wins: 79% recall (very reliable)
โ
Away Wins: 71% recall (solid)
Why are draws hard?
Draws are inherently unpredictable (only 22% of matches). The model correctly avoids false draw predictions but misses real ones.
The system runs autonomously with a background scheduler:
python backend/tasks/scheduler.pySchedule:
- Daily (02:00 AM): Scrape yesterday's results, update database
- Weekly (Monday 03:00 AM): Retrain models with new data
- Framework: FastAPI (async Python web framework)
- Database: PostgreSQL + SQLAlchemy ORM
- ML: Scikit-Learn (Random Forest), Pandas, NumPy
- Data: BeautifulSoup (scraping), Requests (API calls)
- Automation: Python
schedulelibrary
- Framework: React 18 + Vite
- Styling: Tailwind CSS
- Charts: Recharts
- State: React Hooks
- Containerization: Docker (PostgreSQL)
- Version Control: Git
| Method | Endpoint | Description |
|---|---|---|
GET |
/teams |
List all 20 Premier League teams |
GET |
/upcoming-matches |
Next 10 fixtures with AI predictions |
POST |
/predict |
Custom match simulator (any 2 teams) |
GET |
/match/{id}/prediction |
Get prediction for specific match |
POST |
/match/{id}/user-prediction |
Submit your own prediction |
GET |
/user/{id}/history |
View your prediction accuracy |
curl -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{
"home_team_id": 1,
"away_team_id": 5,
"date": "2025-01-15"
}'Response:
{
"home_win_probability": 0.55,
"draw_probability": 0.28,
"away_win_probability": 0.17,
"predicted_outcome": "HOME_WIN",
"confidence": "MEDIUM",
"reasoning": {
"home_momentum": 0.5,
"away_momentum": -0.2,
"fixture_congestion": "Home team played 3 games in 7 days"
}
}- Player Injury Integration: Adjust predictions when key players are missing
- Live Match Updates: Real-time probability adjustments during matches
- Betting Value Finder: Compare AI odds vs bookmaker odds
- Multi-League Support: Extend to La Liga, Bundesliga, Serie A
- Mobile App: React Native version
- Explainable AI: SHAP values to explain each prediction
Contributions are welcome! Here's how:
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Away xG Average (5.47%) - How dangerous is the away team?
- Away xG Against (5.25%) - How leaky is the away defense?
- Home Momentum (xG Change) (5.12%) - Is the home team improving?
- Away Momentum (xG Change) (5.00%) - Is the away team peaking?
- Home Momentum Trend (4.91%) - Overall trajectory
Key Insight: Momentum features (context-aware) dominate the top 10, proving the value of looking beyond basic stats.
Distributed under the MIT License. See LICENSE for more information.
- FBref for historical match data
- Football-Data.org for live fixtures
- Scikit-Learn team for amazing ML tools
Dylan Mascarenhas
GitHub: @dylanmascarenhas
Project Link: https://github.com/dylanmascarenhas/football-predictor-ai
Built with โฝ and ๐ค | Predicting the beautiful game, one match at a time.