DashboardHow It Works

Seven models, one rule engine, and a bar it has to clear.

The full architecture, for the data-curious: how raw FPL data becomes a predicted eleven, what was measured rather than assumed, what measurement rejected — including things this engine used to do — and where it still falls short.

01

The number that matters

Most published models tell you how accurate they are. Accuracy is the wrong question. FPL is a selection problem: you pick a handful of players and never need a calibrated absolute number for anyone. What matters is whether the eleven it names beats the eleven you would have picked without it.

So here is the honest comparison. Every method below was replayed across all 38 gameweeks of a full season, trained only on what was known at the time, and scored on the same universe — every registered player, with anyone who did not play counting zero, exactly as they would in your team.

MethodBest XI, pts/weekCaptureWhat it is
This engine50.80.369
Points per game so far46.50.338one line of code
Crowd ownership47.30.345what everyone else owns
Form, last four games42.60.309the obvious heuristic
Eleven names at random12.80.093the floor

The margin over points-per-game is +4.3 points a week, 95% confidence interval [+0.7, +8.1]. That interval excludes zero, so it is a real edge — but note how wide it is, and note that a one-line heuristic gets most of the way there. Anyone quoting a model's accuracy without naming what it beat is not telling you the useful half.

Capture is the eleven's points as a share of the best possible eleven in hindsight. It sits around 0.37 for everything that isn't random, because perfect hindsight is unreachable — which is exactly why it is a poor headline and a baseline is a good one.
02

The pipeline

Ingest → Features → Predict → Score → Optimize → Verify. Player match histories, fixtures and live results come from the official FPL API. A feature factory turns each player-fixture into roughly fifty model-ready signals. Seven gradient-boosted models each predict one scoring event. A rule engine converts events to points using FPL's actual scoring rules. A linear program assembles the legal squad, and a reconciliation job scores everything once results land.

03

Why seven models instead of one

Predicting “total points” directly buries the reasons inside a black box. Predicting the seven events that generate points keeps every claim inspectable — you can see that the model expects a goal but no clean sheet, not just a mysterious “7.2”. Each head uses an objective matched to its statistical shape:

EventObjectiveWhy
GoalsPoissoncount data with a low rate
AssistsPoissoncount data with a low rate
Clean sheetsLogisticbinary outcome per match
SavesPoissonkeeper workload is a count
Bonus pointsPoissondiscrete 0–3 award
Defensive contributionLogisticthreshold event (10+/12+ CBIT)
MinutesSquared errorcontinuous playing time

These heads are expectations, not classifiers whose verdicts get taken. That distinction matters more than it sounds, and getting it wrong was this engine's most expensive mistake — see below.

04

What it trains on

113,592 player-gameweeks across four seasons, 2022-23 onward. For a long time the engine held a single season, which meant it had never observed a season begin — and so could not have learned how one begins. Three seasons were reconstructed from the public archive and pushed through the identical feature pipeline, turning one observed opening weekend into four.

Roughly 60% of those rows are players who did not play. They used to be discarded, which quietly did two bad things: it left the minutes model — whose entire job is answering will he be on the pitch — fitted on a sample where everybody always was, and it made every backtest score the model on a universe already known to have turned out. They are kept now, which is what lets the comparison in section 01 use the real universe rather than a flattering one.

Everything is computed from match history as it stood before each gameweek — never season totals, which would leak the future into the past. Multi-horizon form — rolling points and expected goal involvement over 3, 5, 7, 10 and 15-game windows, because a hot streak and a season trend are different signals. Venue splits — home and away output tracked separately. Set pieces — penalty and corner duty from FPL's own designations. Opponent context — position-specific difficulty from each team's rolling form, not the static difficulty badge.

Price is deliberately absent. Cost is derived from past output, so using it to predict output is circular. Ownership is not absent, and this page used to claim it was. Removing it was measured rather than assumed: it costs −1.5 points a week, interval [−4.2, +1.1], which is indistinguishable from zero. The principle is therefore free to keep — the honest statement today is that it is still an input, and that this is a decision outstanding rather than a considered choice.
05

The most expensive mistake

Attacking returns are outnumbered by blanks about five to one, so for a long time the training rows containing goals and clean sheets were up-weighted to compensate. That is a standard and correct technique — for a classifier, whose job is to draw a boundary. These heads are not classifiers. They feed an expected points calculation, and up-weighting the positive class biases an expectation upward by construction.

Downstream, a set of hard ceilings then capped every prediction — goals at 0.75, assists at 0.75, clean sheets at 0.95. Those sat at roughly the level of the best player in the league, so anyone at or above them became tied. Two errors partly cancelling, with the top of the distribution flattened in between, in a product whose only job is ordering the top of a list.

Removing the weights is worth +7.0 points a week, interval [+3.0, +10.9]. Widening the ceilings to sanity bounds is worth +5.1, interval [+2.1, +8.4]. Before both, the engine was not statistically distinguishable from the points-per-game heuristic in section 01.

A hand-tuned stack of form and fixture multipliers sat on top of the model output. On the old, distorted heads it was worth +5.0 points a week. On the corrected ones it is worth +0.7, interval [−2.0, +3.4] — nothing. It was never signal; it was compensation for a defect one level down, and it had been measured favourably precisely because the defect was still there. Layers that patch a broken foundation will always measure well against that foundation.
06

How fast it should learn

The hardest question early in a season is how much one gameweek should change the picture. It was answered by replaying past seasons and finding, at each point, the blend of last season and this one that best predicted what actually happened. The answer is that there is no single answer — minutes and points are learned at completely different speeds, and giving them one dial is why a single week used to swing ratings it had no business swinging.

QuestionPrior weightMeasured
Whether he startszerothis season beats last season by 46% after one gameweek
How much he scores0.75–0.90live points are 9–16% worse than ignoring this season, through GW3

One match settles whether a player is in the team. It says almost nothing about how good he is. The engine moves immediately on the first and slowly on the second, and a player with no top-flight record at all is shrunk toward the league's ordinary return, so one big afternoon cannot masquerade as a rate.

07

Gameweek 1 is a different problem

Opening weekend gets its own solver. There is no form, no current team strength and no existing squad — but the whole budget is in play at once, and the team has to survive the opening run on one free transfer a week. Solved as “who scores most this Saturday”, it produced a squad that spent £84.5m of £100m and owned no premium at all.

The opening model instead scores players on expected points across the next six gameweeks, decay-weighted, using only what genuinely exists before a ball is kicked: last season's per-90 rates shrunk by sample size, how often a player actually returned, set-piece duty, and the real published fixture list. Minutes security is a gate rather than a bonus, and it separates role from durability — a first-choice player who missed games is not the same as a substitute who was always fit.

Club exposure is capped at two rather than the three the rules allow. Three players share one team's fixtures, one manager and one injury crisis; the cap costs about 0.6% of expected points and removes that concentration. Players with no Premier League history are not rated at all — they get no invented number.

08

From events to points

The seven event predictions are not fed to another model — they're multiplied by FPL's actual scoring rules, position by position: a defender's clean sheet is worth 4, a midfielder's 1; goals range from 4 to 6 by position; a point per three saves; minus for goals conceded. Transparent arithmetic, not a second black box.

On top of the point estimate, a 2,000-run Monte Carlo simulation turns event rates into probabilities — the chance of a haul, of any return, of a ceiling week — including the real-world correlation that a goalscorer is likelier to also collect bonus.

09

Why the site shows a range, not a decimal

A single player's projection is typically wrong by about 2.8 points. That is not a flaw to be apologised for — it is what one game of football is like. But it means a projection of 6.3 and one of 5.1 are the same number as far as this engine is concerned, and printing them to one decimal invites a comparison it cannot support.

So the pitch shows a whole number and the detail sheet shows the range the projection actually implies. The width is read from the public record and moves as the engine's real accuracy moves; it is not a constant someone typed once. On a scored week the per-player line reads landed in range: yes or no, rather than a signed difference to one decimal that would invite reading 0.4 as better than 0.8.

10

How changes are validated

Every change is gated on a walk-forward replay: train on what was known at the time, predict the week that follows, roll forward, measure. Every comparison is reported as a paired bootstrap over gameweeks — a point estimate and a 95% interval — and the unit of independence is the gameweek, so a season is 38 observations, not eleven thousand player-rows.

That last point is the one that had to be learned the hard way. Several past decisions on this project were made on differences of three to six percentage points across 38 gameweeks, where one standard error is about five. Those were not measurements; they were single draws from a wide distribution. A difference whose interval contains zero is not a result, and this page now says so wherever it quotes one.

The yardstick is ranking against a baseline, not average error. Adding three seasons of history made average error worse by around 12% while making the cold-start picks dramatically better — the average score of the top twenty predicted players rose from 4.15 to 5.75 in the opening weeks. Tuning on error would have rejected the single most useful change made that year.

11

The ledger: no revisions, no hindsight

Before every deadline the full prediction set is frozen to an immutable ledger and committed. After the final whistle, one reconciliation job fetches the official results and writes a single canonical record of what happened. The public scoreboard is computed from those two files and nothing else.

Reconciliation refuses to run on a gameweek FPL has not confirmed, which is a few hours after the last whistle rather than at it, because bonus points are exactly what moves in between. A week in that state is shown as played, awaiting confirmation rather than hidden — the eleven is there, locked, with dashes where the scores will go. A ledger entry that postdates its own deadline would be worthless as evidence, so the system also refuses to write one retroactively, even when that means a gameweek has no entry at all.

Each season is self-contained: when one ends it is sealed into its own archive and the public record resets to zero. The models still learn across seasons; the scoreboard you see is always this season's honest, from-scratch record.

12

Honest limitations

Participation is the big one. The seven heads are fitted on players who appeared, so they estimate what a player does given that he plays, while the points calculation consumes them as though he certainly will. That systematically overrates rotation risks — precisely the players a manager most needs warning about. The fix is a separate model for the chance of starting, combined multiplicatively. The data for it is in place; the model is not built yet. Pouring the non-appearances into the existing heads instead was tried, and measurably damaged the captain pick by 1.6 points a week — one model cannot carry both questions.

Clean-sheet probabilities used to be roughly double reality. They are not any more: against a held-out season, goalkeepers and defenders now average 21.0% predicted against 21.5% actual. The class weights described in section 05 were the cause. What remains is overconfidence at the top end — players rated above 50% keep one about 31% of the time, on a small sample — so a high clean-sheet number is still better read as a strong ranking than as a literal probability.

The transfer-pick hit rates on the guide page predate this rebuild. They were measured on a replay of the previous season with the model as it stood before the corrections in section 05. The pick logic itself is unchanged, but the model underneath it is not, so those figures are due a re-measurement and should be read as indicative.

Single-gameweek football is mostly noise. No amount of feature engineering changes that; the aim is a durable edge, not weekly certainty. Promoted sides arrive with no top-flight record and start from cautious priors. Managerial changes, new signings still settling, and pre-season fitness are not modelled at all — a human who follows a club closely will beat this engine on those, reliably. Defensive contribution only began scoring in 2025-26, so seasons before it are excluded from that model alone. And no model knows about a bust-up in training.

13

The stack

Python with XGBoost for the seven models, PuLP for the squad-optimization linear program, pandas and NumPy throughout. The site is a static Next.js build — every JSON it reads is generated by scheduled jobs, so there is no server to fall over on deadline day. Training, prediction and scoring all run in CI on pinned dependencies, so the model that makes a public prediction is the model the repository describes.

Just want to pick a better team? Read the manager's guide →