Skip to content

Repository files navigation

🏎 F1 Strategy AI — Monte Carlo Pit Stop & Rival Strategy Engine

Real F1 telemetry. Physically-grounded tyre modelling. Probabilistic strategy decisions. A full-stack race strategy simulator built on FastF1 data, a 3-phase piecewise degradation model, vectorised Monte Carlo simulation, and an undercut/overcut rival engine — with a live Streamlit dashboard inspired by F1 broadcast telemetry design.

Tests Python FastF1 Streamlit License: MIT


What This Does

F1 race strategy is a real-time optimisation problem under uncertainty: noisy tyre degradation, an opponent whose next move you don't control, and thousands of possible strategy combinations, all under a hard time budget.

This project builds a strategy decision-support system from the data up — not a toy pit-stop calculator, but a system that models the physics of tyre wear, the economics of a pit stop, and the game-theoretic question of when to fight for track position:

  • Loads real race data via FastF1 — lap times, tyre compounds, pit stops, weather, final classification — for any season 2018–2024, not a hardcoded race
  • Cleans and engineers features — removes SC/VSC laps, inaccurate laps, fuel-corrects lap times, tracks stint-relative degradation
  • Fits a 3-phase piecewise-linear degradation model per compound (bedding → plateau → cliff), with the cliff lap reported as a first-class output with a 95% confidence interval
  • Scales degradation by live track temperature pulled from session weather data
  • Generates every valid 1-stop and 2-stop strategy under F1 compound rules and runs vectorised Monte Carlo — thousands of strategies × simulations in seconds
  • Models both undercut and overcut against a configurable rival, with side-by-side expected-value comparison
  • Backtests the AI-optimal strategy against what the driver actually did, using real stint data
  • Visualises everything in a production-grade dark-mode dashboard with an F1-app-inspired timing tower and weather strip

Architecture

FastF1 API
    │
    ▼
[pipeline/loader.py]       ← Session loading, caching, live race schedule/laps,
    │                         weather, final classification — no hardcoded season data
    ▼
[pipeline/cleaner.py]      ← Remove SC/VSC laps, inaccurate laps, per-compound
    │                         outlier removal (vectorised, not per-group apply())
    ▼
[pipeline/features.py]     ← Tyre age, stint number, fuel correction, DegDelta
    │                         (vectorised transform — no groupby().apply() column loss)
    ▼
[models/tyre_deg.py]       ← 3-phase piecewise-linear degradation model (pwlf),
[models/pit_loss.py]         cliff detection with CI, driver-weighted fitting,
    │                         track temperature scaling
    ▼
[simulator/strategy.py]    ← Strategy & Stint dataclasses, generator functions
[simulator/race_sim.py]    ← Deterministic race simulation, backtest reconstruction
[simulator/monte_carlo.py] ← Vectorised N-iteration Monte Carlo runner
[simulator/rival.py]       ← Undercut AND overcut EV scanning against a rival
    │
    ▼
[visualisation/]           ← Dashboard, degradation/strategy/rival plots
f1-strategy-ai/
├── src/
│   ├── pipeline/
│   │   ├── loader.py        # Session loading, live schedule, weather, classification
│   │   ├── cleaner.py       # SC laps, inaccurate laps, vectorised outlier removal
│   │   └── features.py      # Fuel correction, tyre age, vectorised stint deltas
│   ├── models/
│   │   ├── tyre_deg.py      # 3-phase piecewise model, cliff detection, temp scaling
│   │   └── pit_loss.py      # Data-driven pit stop time loss
│   ├── simulator/
│   │   ├── strategy.py      # Strategy / Stint dataclasses + generation
│   │   ├── race_sim.py      # Deterministic race simulation + backtest support
│   │   ├── monte_carlo.py   # Vectorised MC engine (NumPy matrix ops)
│   │   └── rival.py         # Undercut / overcut EV scanning, H2H Monte Carlo
│   ├── visualisation/
│   │   ├── dashboard.py     # Streamlit app (main entry point)
│   │   ├── tyre_plots.py    # Piecewise degradation curves, cliff markers
│   │   ├── strategy_plots.py  # Comparison bars, lap traces, undercut heatmap
│   │   └── rival_plots.py   # Undercut/overcut EV timeline, H2H, comparison chart
│   └── utils/
│       ├── config.py        # Central config
│       └── logger.py        # Structured logging
├── tests/                   # 91 tests across pipeline, model, and rival logic
│   ├── test_pipeline.py
│   ├── test_tyre_deg.py
│   ├── test_simulator.py
│   └── test_rival.py
├── docs/
│   ├── architecture.md
│   ├── decisions.md         # Real engineering decisions and bugs found & fixed
│   ├── model_notes.md
│   └── improvements.md
├── .github/workflows/test.yml  # CI — runs full suite on push/PR
├── Dockerfile
├── docker-compose.yml
└── data/cache/               # FastF1 HTTP cache (auto-populated)

Technical Highlights

3-Phase Piecewise Tyre Model — Not a Polynomial

Real tyre degradation has three physically distinct phases: a bedding phase (new rubber, lap times can improve), a plateau (roughly linear wear), and a cliff (abrupt breakdown). A polynomial forces a single smooth curve across all three — it can't represent a phase transition.

This model fits piecewise-linear segments (via pwlf) with the breakpoints optimised jointly:

Bedding  → laps 1–B₁    (often slightly negative slope)
Plateau  → laps B₁–B₂   (roughly linear degradation)
Cliff    → laps B₂+     (steep slope increase)

The cliff lap B₂ is a first-class model output with a 95% confidence interval (analytical standard error, falling back to residual bootstrap on sparse data), guarded against false positives on flat or U-shaped compounds:

model.cliff_lap("SOFT")   # → 16.4
model.cliff_ci("SOFT")    # → (14.8, 18.1)

Driver-Weighted Fitting, Not Pure Filtering

Filtering to one driver's laps alone starves the model — a single race gives ~15 clean laps per compound, borderline for a stable 3-segment fit. Instead, the selected driver's laps are up-weighted (sample_weight) 4× above the fleet, keeping the full 150+ lap fleet dataset for stability while letting the driver's own tendencies dominate the fit. Below a lap-count threshold per compound, the model automatically falls back to a simpler 2-segment fit rather than overfitting on 6 points.

Track Temperature Scaling

Degradation excess (not baseline pace) is scaled against live session weather data — hotter track accelerates the cliff, colder track flattens it — clamped to a sane range to prevent runaway extrapolation on extreme readings.

Undercut and Overcut, Side by Side

Most public strategy tools only model the undercut. This engine evaluates both directions against a configurable rival (their compound, tyre age, and gap to you) and reports mean EV, P10–P90 range, and win rate for each candidate lap — visualised as a single comparison chart showing which strategy is favoured on which lap.

EV(lap_n) = gap_to_us − gap_after_strategy(lap_n)

Both formulas were rebuilt from scratch this cycle after a real bug was found: the original implementation mixed an absolute lap time into a relative gap delta, double-counting pace on top of the pit stop cost. See docs/decisions.md for the full story.

Vectorised Monte Carlo

The simulation engine pre-computes deterministic lap-time vectors per strategy, then generates noise as a (n_laps × n_simulations) NumPy matrix in one shot — no per-iteration Python loop.

base  = build_lap_time_vector(strategy, deg_model, pit_loss)   # (n_laps,)
noise = rng.normal(0, noise_std, size=(n_laps, n_simulations)) # (n_laps, n_sims)
times = (base[:, None] + noise).sum(axis=0)                    # (n_sims,)

Backtest Mode

Reconstructs what the selected driver actually did that race — compound sequence and stint durations, pulled directly from FastF1 tyre-life data — and compares deterministic race time against the AI-optimal strategy, laid over a shared lap trace.


Quickstart

Prerequisites

  • Python 3.11+
  • ~500MB disk (FastF1 cache)

Local install

git clone https://github.com/Trixx4191/F1-Strategy-AI.git
cd f1-strategy-ai

python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate

pip install -r requirements.txt

Run the dashboard

PYTHONPATH=$(pwd) streamlit run src/visualisation/dashboard.py

Open http://localhost:8501 — select a season, race, and driver in the sidebar, then hit RUN SIMULATION. Every season 2018–2024 and every event in that season's calendar is available — nothing is hardcoded.

First run: FastF1 downloads session data (~50–200MB depending on race). Subsequent runs use the local cache.

Run with Docker

docker compose up --build

Dashboard available at http://localhost:8501. The FastF1 cache persists in a named volume across restarts.

Run tests

PYTHONPATH=$(pwd) pytest tests/ -v

91 tests across the data pipeline, the piecewise tyre model (fitting, cliff detection, temperature scaling, driver weighting), and the rival engine (undercut, overcut, and the regression tests that caught two real bugs — see below).


Configuration

All parameters are in src/utils/config.py:

N_SIMULATIONS         = 1000   # MC iterations (100–2000 via dashboard slider)
LAP_TIME_NOISE_STD    = 0.3    # Gaussian noise per lap (seconds)

DEG_MIN_LAPS_FULL     = 12     # Minimum laps for a full 3-segment piecewise fit
DEG_MIN_LAPS_FALLBACK = 6      # Minimum laps for a 2-segment fallback fit
DEG_CLIFF_THRESHOLD   = 0.12   # s/lap — minimum slope to call a segment a "cliff"

Season, race, and driver are selected live in the dashboard sidebar — pulled from FastF1's event schedule, not hardcoded.


Key Dependencies

Package Version Purpose
fastf1 ≥ 3.3 Official F1 timing, telemetry, and weather data
pandas ≥ 2.0 Lap data manipulation
numpy ≥ 1.26 Vectorised Monte Carlo
pwlf ≥ 0.5 Piecewise linear fitting for the 3-phase degradation model
scikit-learn ≥ 1.4 Pipeline utilities
scipy ≥ 1.12 Statistical distributions, confidence intervals
streamlit ≥ 1.32 Interactive dashboard
matplotlib ≥ 3.8 Strategy & tyre visualisations

Real Bugs Found & Fixed

This project has been through several rounds of adversarial testing rather than feature-only development. Two are worth calling out specifically because they were subtle, silent, and would have shipped unnoticed without deliberate regression tests:

  1. A unit-mixing bug in the undercut/overcut gap formula — an absolute lap time (~91s) was being combined with a relative gap variable (~2–10s) in the same equation, effectively double-counting pace on top of the pit stop cost. Found via a new test asserting overcut should be favourable when a rival is far behind — the assertion failed, which led to the real bug rather than a test bug.
  2. A silent column-loss bug in the cleaning pipeline — a pandas.groupby().apply(..., include_groups=False) call (added to silence a FutureWarning) was quietly dropping the Compound, Driver, and StintNumber columns from the pipeline's output entirely. Caught via an end-to-end smoke test against real FastF1 data, not a unit test — the unit tests happened to use single-driver fixtures that couldn't expose it.

Full writeup: docs/decisions.md.


Roadmap

  • Circuit cluster multi-race training — pool laps across circuits with similar degradation character to solve the underlying data-poverty problem (15–18 laps/compound/race → 300+)
  • Safety car Monte Carlo layer — sample SC events from historical TrackStatus frequency and re-evaluate pit timing when one fires
  • Strategy history tab — pit lap heatmap by year for the selected circuit, overlaid with the winning strategy
  • Telemetry analysis module — track map, speed/brake/throttle traces, delta-to-reference-lap, sector breakdown
  • Grounded race engineer — natural-language strategy/performance questions answered with evidence traced directly to telemetry, degradation model, and weather data

See docs/improvements.md for the full backlog.


Why This Project

Strategy is where races are won and lost — the undercut that works, the tyre cliff nobody saw coming, the pit call made in three seconds with imperfect information. This project exists to explore whether a rigorously-engineered data pipeline and probabilistic simulation can approximate that reasoning — and to do it in a way that's open, testable, and honest about its own limitations and past mistakes.


License

MIT — see LICENSE.


Author

Built by a computer engineering student and F1 enthusiast. If you work in motorsport data, simulation, or performance engineering — let's talk.

LinkedIn Email

About

Monte Carlo pit stop optimiser built on real F1 telemetry — tyre degradation modelling, rival undercut analysis, and a live Streamlit dashboard

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages