A Python project for modelling F1 Fantasy expected points and optimising team selection under official squad constraints. It combines live fantasy market data, historical race results, a transparent scoring model, and mixed-integer optimisation to rank teams, simulate chips, and evaluate transfer moves.
This project is not affiliated with Formula 1, FIA, or the official F1 Fantasy game.
- downloads the latest available official F1 Fantasy market snapshot automatically
- pulls historical race, qualifying, sprint, and schedule data from an Ergast-compatible source
- computes driver and constructor fantasy scores from raw results
- estimates expected value over an upcoming race horizon
- optimises the best legal squad under a configurable budget cap
- evaluates chip scenarios separately
- suggests transfer moves from a saved current squad, including realistic transfer budget handling
The model is designed to stay simple, inspectable, and easy to tweak rather than chase noisy marginal features.
- Automatic fantasy market feed detection
- the code probes the latest available
drivers/{n}_en.jsonfantasy feed automatically - no manual round-number update is needed after price changes
- the code probes the latest available
- Live driver and constructor prices
- Historical results ingestion
- race
- qualifying
- sprint
- schedule
- Rule-based fantasy scoring engine
- drivers and constructors handled separately
- qualifying, race, sprint, DNFs, DSQs, and constructor aggregation included
- Expected-value model with transparent weighting
- MILP optimisation via PuLP
- Chip scenarios
- 2x Boost
- 3x Boost
- No Negative
- Limitless
- Transfer recommendations
- uses current squad IDs from
data/current_team.json - prints current team and current EV first
- prints recommendations with both IDs and names
- outputs an updated suggested
current_team.json - updates bank correctly after suggested moves
- uses current squad IDs from
- Transfer budget handling based on current team value + bank
- Roster ID helper utility
- Debug / validation utilities
f1fantasy/
__init__.py
debug_checks.py
ergast.py
fantasy_api.py
fantasy_prices.py
model.py
optimize.py
print_roster_ids.py
recommend.py
transfers.py
update_cache.py
data/
cache/
tests/
test_smoke.py
Requires Python 3.10+.
git clone https://github.com/dnpjr/f1_fantasy_optimizer.git
cd f1_fantasy_optimizer
python -m venv .venv
# Windows
.venv\Scripts�ctivate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txtRun the main optimiser:
python -m f1fantasy.recommendThis prints the top teams under the configured budget cap and evaluates chip scenarios separately.
You can also use the installed console entry points:
f1fantasy-recommend
f1fantasy-debugTo generate transfer suggestions, create:
data/current_team.json
Example:
{
"drivers": [121, 11031, 114, 129, 131],
"constructors": [27, 29],
"free_transfers": 2,
"bank": 0.5
}Fields:
drivers: list of current driverplayerIdsconstructors: list of current constructorteamIdsfree_transfers: free transfers still availablebank: money left in the bank
To print the currently available IDs:
python -m f1fantasy.print_roster_idsTransfer recommendations use:
transfer budget = current market value of held squad + bank
This means the transfer solver reflects price changes in your existing squad, rather than assuming a flat 100.0 budget.
For fresh-team optimisation, the configurable cap lives in:
f1fantasy/recommend.py
Look for:
TEAM_BUDGET_CAP = 100.0You can change this to values such as:
TEAM_BUDGET_CAP = 101.8if you want to test the best full rebuild team at a different effective budget.
This is separate from transfer mode, which calculates its own budget from:
current team value + bank
The expected-value logic is in:
f1fantasy/model.py
The current setup separates:
- current-season form
- historical track-aware signal
Controlled by:
def _current_season_share(...)Default behaviour:
- 0 completed races ->
0.00current /1.00historical - 1 completed race ->
0.50current /0.50historical - scales linearly up to
- 10+ completed races ->
0.75current /0.25historical
Controlled by:
def _current_round_weight(...)Default behaviour:
- latest completed race =
1.00 - previous race =
0.95 - then
0.95^2,0.95^3, ...
Controlled by:
def _historical_season_weight_hist_only(...)Default behaviour:
- previous season =
0.75^0 = 1.0 - two seasons back =
0.75^1 - three seasons back =
0.75^2 - and so on
The current-season block uses only completed races from the current season. Historical data is blended in only on the historical side of the forecast, so prior seasons do not leak into the current-season form signal.
The scoring engine includes:
- qualifying position points
- race finishing points
- sprint finishing points
- positions gained / lost proxy
- overtake proxy
- DNF / NC / DSQ handling
- fastest lap where available
The scoring engine includes:
- both drivers' qualifying totals
- both drivers' race totals
- both drivers' sprint totals
- qualifying stage bonuses
- constructor-level DNF / DSQ handling
- true official overtake counts
- sprint fastest lap
- Driver of the Day
- pit stop scoring
- world-record pit stop bonus
- stochastic simulation / distributions
The project intentionally stays fairly simple and rule-consistent rather than trying to overfit noisy inputs.
Useful helper scripts:
python -m f1fantasy.debug_checks
python -m f1fantasy.print_roster_idsThese are useful for checking:
- raw data ingestion
- constructor aggregation
- DNF handling
- current vs historical blend behaviour
- current fantasy roster IDs and prices
- depends on public / unofficial data feeds that may change schema or availability
- some fantasy sub-components are approximated or omitted
- expected value is deterministic rather than fully probabilistic
- no historical backtest framework is included yet
MIT License — see LICENSE.