A web-based Formula 1 race strategy tool that predicts optimal pit stop strategies from real practice session data and tracks live races with real-time telemetry. Built with Python/FastAPI and React.
Select any Grand Prix from 2023-2025, and the engine analyzes real practice and qualifying data to predict optimal pit stop strategies:
- Tyre degradation analysis — Fuel-corrected degradation curves per compound (SOFT, MEDIUM, HARD) built from FP1/FP2/FP3 and Sprint session lap times
- Lap-by-lap simulation — Models every lap accounting for tyre wear, fuel burn-off, cold tyre penalty, and track position effects
- Multi-stop optimization — Generates and ranks all valid 1/2/3-stop strategies across compound permutations, filtered by FIA tyre regulations
- Mixed-weather strategies — Define weather windows (dry/intermediate/wet) and the engine forces compound changes at transitions
- Per-circuit pit loss — Circuit-specific pit lane time loss (17s Austria to 27s Singapore) instead of a flat default
- Historical stabilization — Uses 3 years of race data at the same circuit to stabilize degradation curve shapes
Switch to the Live tab during a race weekend. The dashboard auto-detects whether a race is happening:
- Auto-detection — Fetches the current season's schedule and checks if a race is live (within 3 hours of start time). If yes, shows "Race In Progress" with a one-click connect button. If no race is live, shows a countdown timer to the next upcoming race with track name and local time
- Car telemetry (sponsor tier) — Speed and RPM circular gauges, gear indicator with shift light strip, DRS status, throttle/brake bar gauges, and a speed-over-time chart per driver
- Track map (sponsor tier) — SVG visualization of all 20 driver positions using real OpenF1 coordinates, with the selected team highlighted. Track shape reveals itself naturally from driver positions — no static circuit outlines needed
- Team focus — Select your team and see both drivers side-by-side with position, gap, interval, tyre compound, tyre age, and pit stop history
- Mid-race strategy recalculation — Engine recalculates optimal remaining strategies on every pit stop and safety car change, showing top 3 recommendations per driver
- Safety car detection — SC/VSC/red flag status tracked from race control messages with pulsing visual indicators
- Race control log — Collapsible live feed of flags, safety cars, and race director messages
- Graceful degradation — Without sponsor credentials, telemetry gauges and track map are hidden; the dashboard still shows all positional data, tyre info, and strategy recommendations
Switch to the Replay tab to watch any past race played back through the same telemetry dashboard:
- Adjustable speed — Play back at 1x, 2x, 4x, or 8x real-time speed, adjustable mid-replay
- Full telemetry — Same driver panels, track map, and race control log as the live dashboard
- Pause/resume — Pause playback to study a moment, then resume at any speed
- Progress bar — Visual indicator of replay progress through the race
- Strategy recalculation — Engine recalculates on pit events during replay, just like live
The strategy engine simulates each lap of a race:
lap_time = base_lap_time
+ (deg_rate × tyre_age)
- (fuel_correction × laps_completed)
+ warmup_penalty (first lap on new tyres)
+ position_loss (escalating per pit stop)
- first_stint_bonus (softer compounds = better track position)
Data pipeline:
- Load all practice session laps (FP1, FP2, FP3, Sprint) via FastF1
- Filter to green-flag laps only (TrackStatus == "1"), skip out-laps, remove within-stint outliers
- Weight laps by track temperature proximity to race conditions (Gaussian, sigma=10C)
- Compute fuel-corrected degradation rates per compound using median averaging
- Stabilize curvature with 3 years of historical race stint data at the same circuit
- Apply practice-to-race scaling (0.85x) since practice systematically overestimates degradation
- Brute-force search all valid pit lap combinations for each compound sequence
- Rank strategies by total predicted race time
Key engine parameters:
| Parameter | Default | Description |
|---|---|---|
deg_scaling |
0.85 | Practice-to-race degradation multiplier |
fuel_correction |
0.055 s/lap | Fuel burn-off time gain per lap |
tyre_warmup_loss |
1.0s | Cold tyre penalty per pit stop |
position_loss |
3.0s | Escalating traffic penalty: stop 1 = 3s, stop 2 = 6s, stop 3 = 9s |
history_years |
3 | Years of historical race data for curve stabilization |
Validation results (2025 season, 21 races, 62 team comparisons across McLaren/Mercedes/Red Bull):
- Stop count match: 53%
- Compound set match: 57%
- Compound balance: HARD +6, MEDIUM +1, SOFT -22 (slight over-prediction of harder compounds)
- Pit timing bias: +0.2 laps (nearly neutral)
- Backend: Python 3.12, FastAPI, FastF1 (historical F1 data), OpenF1 (live timing), httpx, sse-starlette
- Frontend: React 19, Vite, Tailwind CSS v4, Recharts, Lucide React, Deno 2.x
- Deployment: Single Docker container — nginx serves the React build and reverse-proxies
/api/*to uvicorn
# Build the image
docker build -t f1-strat .
# Run — mount a volume so FastF1 cache persists across restarts
docker run -d --name f1-strat -p 3000:80 -v f1_cache:/app/backend/.fastf1_cache f1-strat
# Open in browser
open http://localhost:3000Live race tracking works on the free OpenF1 tier (historical data, 8-second polling). For real-time data during active sessions, sign up for the OpenF1 sponsor tier and pass your credentials as environment variables:
docker run -d --name f1-strat -p 3000:80 \
-e OPENF1_USERNAME=your_user \
-e OPENF1_PASSWORD=your_pass \
-v f1_cache:/app/backend/.fastf1_cache f1-stratWith credentials set, the backend exchanges them for a bearer token (valid 1 hour, auto-refreshed) and polls at 4-second intervals instead of 8. It also enables car telemetry and location endpoints for the speed/RPM gauges and track map. If authentication fails, it falls back to the free tier automatically — wrong credentials won't crash anything.
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Start the API server
PYTHONPATH=backend uvicorn f1_strat.api:app --reload
# API docs at http://localhost:8000/docsRequires Deno 2.x.
cd frontend
deno install # Install dependencies
deno task dev # Vite dev server at http://localhost:5173The frontend dev server proxies API calls to http://localhost:8000 (configured in .env.development).
# Backend tests (first run downloads F1 data, ~2-4 min; cached after that)
PYTHONPATH=backend backend/venv/bin/pytest backend/tests/ -v -s
# Frontend type check + build
cd frontend && deno task build| Method | Endpoint | Description |
|---|---|---|
| GET | /api/schedule/{year} |
Race calendar for a season |
| GET | /api/degradation/{year}/{grand_prix} |
Tyre degradation curves from practice data |
| GET | /api/strategy/{year}/{grand_prix}?race_laps=N |
Ranked pit stop strategies (dry) |
| POST | /api/strategy/{year}/{grand_prix} |
Strategies with weather windows (mixed conditions) |
| GET | /api/qualifying/{year}/{grand_prix} |
Q2 tyre compounds for top-10 qualifiers |
| GET | /api/weather/{year}/{grand_prix} |
Practice session weather summary |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/live/status/{year}/{grand_prix} |
Check if a session is available for tracking |
| POST | /api/live/start/{session_key}?total_laps=N&year=Y&grand_prix=GP |
Start polling OpenF1 with strategy recalculation |
| GET | /api/live/drivers/{year}/{grand_prix} |
Teams and drivers for the team selector |
| GET | /api/live/stream/{session_key} |
SSE stream of full race state snapshots |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/replay/start/{session_key}?total_laps=N&year=Y&grand_prix=GP&speed=4 |
Start replaying a historical race |
| POST | /api/replay/speed |
Change playback speed (body: {"speed": 4}) |
| POST | /api/replay/stop |
Stop replay and reset state |
f1_strat/
├── Dockerfile # Multi-stage build (Deno → Python + nginx)
├── nginx.conf # Reverse proxy with SSE support
├── start.sh # Entrypoint: uvicorn + nginx
├── backend/
│ ├── requirements.txt
│ ├── f1_strat/
│ │ ├── api.py # FastAPI REST + SSE endpoints
│ │ ├── cache.py # FastF1 cache setup
│ │ ├── degradation.py # Tyre degradation analysis
│ │ ├── session_service.py # Core data service (FastF1 wrapper)
│ │ ├── strategy.py # Race strategy engine
│ │ ├── live_race.py # OpenF1 polling + SSE state management
│ │ ├── replay.py # Historical race replay engine
│ │ └── validation.py # Backtesting engine vs actual race results
│ └── tests/
│ ├── test_degradation.py
│ ├── test_strategy.py
│ ├── test_live_race.py # 24 tests with mock OpenF1 fixtures
│ ├── test_validation.py
│ ├── test_session_service.py
│ └── fixtures/openf1/ # Recorded API responses for tests
└── frontend/
├── deno.json # Deno config (tasks, nodeModulesDir)
├── package.json # Dependencies
└── src/
├── App.tsx # Main app with Analysis/Live/Replay mode toggle
├── api.ts # Backend API client functions
├── types.ts # TypeScript interfaces for all API shapes
├── hooks/
│ └── useLiveRace.ts # SSE hook (useSyncExternalStore)
└── components/
├── RaceSelector.tsx # Year + GP dropdown
├── DegradationChart.tsx # Tyre degradation curves
├── StrategyControls.tsx # Strategy parameter inputs
├── StrategyList.tsx # Ranked strategy results
├── StrategyTimeline.tsx # Visual stint timeline
├── WeatherScenarioBuilder.tsx # Weather window editor
└── telemetry/ # Live + replay telemetry dashboard (Tailwind)
├── TelemetryDashboard.tsx # Live view (auto-detect + countdown + connected)
├── ReplayDashboard.tsx # Replay view (GP selection + playback controls)
├── ConnectedDashboard.tsx # Shared connected state (driver panels, track map)
├── DriverPanel.tsx # Per-driver gauges + positional data
├── CircularGauge.tsx # SVG arc gauge (speed, RPM)
├── BarGauge.tsx # Skewed bar gauge (throttle, brake)
├── GearDisplay.tsx # Gear number + shift lights
├── DRSIndicator.tsx # DRS open/eligible/off badge
├── TyreCompound.tsx # Compound badge + tyre age
├── TelemetryChart.tsx # Speed-over-time line chart
├── TrackMap.tsx # SVG driver positions map
├── LapDelta.tsx # Gap between team drivers
├── RaceStatusBadge.tsx # Green/SC/VSC/red flag
└── TeamSelector.tsx # Team dropdown with colour swatch
The live tracking system polls the OpenF1 API and pushes state updates to the browser via Server-Sent Events:
OpenF1 API → Backend polling loop (4s sponsor / 8s free) → Module-level state dict
positions, pits, stints, race control, intervals ↓
+ car_data, location (sponsor tier only) Strategy recalculation
(triggered by pit events
and SC/VSC changes)
↓
Browser ← SSE (EventSourceResponse) ← Full state snapshot + strategies + telemetry
↓
Telemetry history accumulated client-side (ring buffer, max 60 samples/driver)
- Single worker —
--workers 1because race state lives in module-level memory - Automatic lifecycle — Polling starts on first SSE client, stops after 5 minutes with no clients or when the race ends
- Mid-race recalculation —
calculate_remaining()runs on pit events and safety car changes, using the driver's current compound, tyre age, and stops completed to find optimal strategies for the remaining laps - Coalesced triggers — Rapid events (e.g., multiple cars pitting on the same lap) are coalesced into a single recalculation to avoid redundant work
- Rate limit handling — 429 responses trigger exponential backoff; 401/403 attempts one token refresh before stopping polling
- Reconnection — EventSource auto-reconnects; each SSE message is a full snapshot so no data is lost
- Keepalive — Server sends SSE comments every 15s during quiet periods to keep the connection alive through nginx
Compare engine predictions against what leading teams actually did:
# Full season
PYTHONPATH=backend backend/venv/bin/python -m f1_strat.validation --years 2025
# Single race
PYTHONPATH=backend backend/venv/bin/python -m f1_strat.validation --race "Spanish Grand Prix" --years 2025
# Resume after API rate limit
PYTHONPATH=backend backend/venv/bin/python -m f1_strat.validation --years 2025 --resumeCompares against McLaren, Mercedes, and Red Bull strategies across all dry races. Key metrics: stop count match, compound sequence/set match, strategy rank, pit timing accuracy, compound balance.