FIFA Ultimate Team Squad Builder: ML Optimization with Random Forest & MILP
January 12, 2020
The Problem
FIFA Ultimate Team gives you 15,000+ player cards, a budget measured in virtual coins, and exactly 11 squad slots. Every card has stats for pace, shooting, passing, dribbling, defending, physicality, plus dozens of hidden in-game attributes. Prices fluctuate daily on the transfer market.So how do most people build squads? They pick Mbappe because he's Mbappe. They watch a YouTube tier list and buy whoever the content creator told them to buy. They spend half their budget on one flashy striker and fill the rest with whoever's cheap.I wanted to know: what happens if you throw all of that out and let math decide?The idea was simple. Scrape every player card in the game. Build a model that predicts who actually scores goals. Then use an optimizer to pick the best possible 11 players within a budget. No gut feelings. No hype. Just numbers.
Scraping the Data
All the player data lives on Futbin, the community's go-to database for FUT stats and prices. The catch is that Futbin is a JavaScript-heavy site. You can't just download a CSV. Pages load dynamically as you scroll, paginate, and click around.I built a Selenium scraper that automated a Chrome browser to crawl through 50+ pages of player listings. For every card, it grabbed the six face-card attributes (pace, shooting, passing, dribbling, defending, physicality), deeper in-game stats like acceleration, sprint speed, positioning, composure, strength, stamina, and aggression, the current market price, primary and secondary positions, and historical performance data including goals scored.Everything went into a SQLite database with indexes on player name, position, and price. SQLite over a flat CSV was a deliberate choice. I could slice the data by position or price range with a quick query instead of loading the whole dataset into memory. And when I re-ran the scraper to get updated prices, the database handled deduplication cleanly.The end result was a structured, queryable dataset of thousands of FUT player cards with every attribute that matters for squad building.
Exploring the Data
Before building any models, I spent time just looking at the data. Some patterns were obvious. Some were not.Pace among attackers skews heavily to the right. Most strikers are fast. Physicality follows the opposite pattern for wingers. These distributions tell you where the market is saturated and where genuine bargains hide.A correlation matrix across all attributes showed the expected relationships. Pace and shooting correlate strongly for attackers. Defending and physicality cluster together for center-backs. Passing and dribbling are tightly linked for midfielders.The interesting part was correlating attributes with goals scored. Shooting, the stat you'd assume matters most for putting the ball in the net, was not the strongest predictor. That finding shaped everything that came after.There's also a clear market inefficiency when you plot price against actual goal output. High-profile cards carry massive premiums that don't scale with on-pitch production. Meanwhile, a sweet spot of mid-priced players dramatically outperform the expensive stars in stats-per-coin. The transfer market overvalues name recognition and card rarity. An algorithm doesn't care about either.
Engineering Better Features
Raw attributes are fine, but they miss how stats combine to make a player effective. A striker with 90 acceleration and 60 strength plays completely differently from one with 75 in both. I built three composite features to capture these interactions.Speed Index combines pace, acceleration, and sprint speed into a single number. It represents a player's ability to beat defenders in a foot race. The weighting leans toward acceleration because most attacking situations in FIFA happen in tight spaces where that first burst matters more than top-end speed over 50 yards.Physical Dominance rolls together strength, stamina, and aggression. Can a player hold off a defender, win aerial duels, and still be running hard in the 85th minute? This turned out to be especially important for target-man strikers and box-to-box midfielders.Finishing Score is a composite of shooting, positioning, and composure. Raw shooting alone doesn't tell the full story. A player with 85 shooting but 60 composure will miss sitters under pressure. A player with 78 shooting but 90 composure and 88 positioning will consistently be in the right place and keep their nerve when it counts.These composites outperformed raw attributes in the prediction model because they reflect how football actually works. Goals aren't scored by a single stat. They're scored by players who combine speed, physicality, and finishing instinct in ways that individual attribute columns can't capture on their own.
The Prediction Model
With the engineered features ready, I trained a Random Forest Regressor to predict how many goals a player would score. 200 trees, max depth of 10. The tree count gives stable, low-variance predictions. The depth cap prevents overfitting. Feature subsampling (square root of available features per split) forces individual trees to learn different aspects of the data.The target variable was goals scored. The one metric that wins FIFA matches.Here's where it got fun. When you rank features by importance in the trained model, the hierarchy shakes out like this: acceleration at the top by a meaningful margin, then Physical Dominance, then Finishing Score.The feature literally named "Finishing Score" isn't the best predictor of finishing. That sounds wrong until you think about it through a gameplay lens. In FIFA, the hardest part of scoring isn't the shot itself. It's getting into position to take the shot. Acceleration determines whether a striker can exploit a half-second gap in the defensive line. Physical Dominance determines whether they can hold off a challenge long enough to pull the trigger. The shooting is almost a formality once you've created the space.This actually mirrors real football analytics. Expected goals models show that shot quality depends more on the situation than on the shooter. Getting into a high-xG position matters more than having a lethal finish. The model figured this out on its own from the data, which was a satisfying result.Cross-validation confirmed solid generalization to unseen players. The trained model was serialized with joblib for reuse in the optimization stage.
Optimization
Predicting goals is half the problem. The other half is picking the best 11 players from thousands of options while staying under budget. This is a combinatorial optimization problem, and the right tool is Mixed-Integer Linear Programming.MILP works like this: every player gets a binary variable. They're either on the squad (1) or they're not (0). The solver searches through the massive space of possible 11-player combinations and finds the one that maximizes total predicted goals.But it's not a free-for-all. The optimizer enforces hard constraints. Exactly 11 players. Exactly 1 goalkeeper. Exactly 4 defenders (CB, LB, RB). Exactly 3 midfielders (CM, CAM, CDM). Exactly 3 forwards (ST, LW, RW). And the total cost of all 11 players must stay under the budget cap.I implemented this with PuLP, a Python library for linear programming that hands the problem off to the CBC solver under the hood. The key property of MILP is that the solution isn't an approximation. It's provably optimal. Given the model's predictions and the constraints, no other combination of 11 players within budget will produce a higher predicted goal tally. Not a heuristic. Not a greedy algorithm. A mathematical guarantee.The solver returns the optimal squad in seconds.
Did It Work?
The model-generated squads consistently made choices a human wouldn't. Instead of blowing 40% of the budget on a single marquee striker, the algorithm spread resources more evenly, finding mid-price players whose stats-per-coin ratio was dramatically better than the big names.A typical example: the algorithm skipped a 200,000-coin striker with 89 shooting in favor of a 35,000-coin alternative with 82 shooting but 91 acceleration and 85 strength. Looks worse on the card. But the model predicted, correctly, that the cheaper player's combination of speed and physicality would generate more goals.Where human squads tend to have one or two expensive stars surrounded by budget filler, the optimized squads had every player pulling their weight. No wasted coins.A few patterns kept showing up. Acceleration is the single best investment for attackers. Physical midfielders are undervalued because the market prices passing and dribbling stats too heavily. For defenders, the algorithm preferred strong positioning over raw tackling. Smart defenders who are in the right place beat aggressive ones who overcommit. And across the board, the market overprices shooting because that's what most human buyers filter by. High-shooting cards carry a premium that their actual goal output doesn't justify.The algorithm found value where humans weren't looking.
Tools
The full pipeline ran end-to-end in Python. Selenium for browser-automated scraping from Futbin. SQLite for structured storage. pandas for exploration and feature engineering. scikit-learn's RandomForestRegressor for the prediction model. joblib for model serialization. PuLP with the CBC solver for MILP optimization.No cloud services, no external APIs. Everything runs locally from raw HTML to an optimal 11-player squad printout.