DEV Community

Cover image for Evaluating (Historically Optimal) Fantasy F1 Teams with Linear Programming
datadr1ven
datadr1ven

Posted on

Evaluating (Historically Optimal) Fantasy F1 Teams with Linear Programming

I'm a programmer and a Formula 1 fan. When I started playing F1's fantasy league, my mind naturally went to algorithms.

The goal in the F1 fantasy game is to pick 5 drivers and 2 teams, while staying under a price cap, that based on race performance, scores a maximum number of points. This sounds like a fairly traditional computer science optimization problem with constraints, right?

More precisely, for any historical window of races, we can use Linear Programming, to find an optimal team. Strictly speaking, this solution is to a simplified version of the game (the real game lets you make changes to your team week to week, as well as has some wildcard factors), but is a useful starting point nonetheless.

Putting together our capability required minimal dependencies:

  • data: we grab points/prices data from the excellent F1 Fantasy Tools site
  • linear programming library: we use glpk.js, which is a JavaScript/WebAssembly port of the old trusty GLPK solver
  • platform: we use GitHub pages, where our code is open sourced under the MIT License and can be found here

The current capability has a simple interface, as shown in this screenshot.

screenshot of https://datadr1ven.github.io/teamtool

The crux of the capability is the behinds-the-scenes construction of the linear programs, which are then fed to the glpk.js solver running in your browser. Here is an actual linear program constructed by our tool (with many lines omitted).

{
    "name": "LP",
    "objective": {
        "direction": 2,
        "name": "obj",
        "vars": [
            {
                "name": "VER",
                "coef": 593
            },
            {
                "name": "OCO",
                "coef": 112
            },
   [...18 additional drivers, omitted for brevity]

            {
                "name": "AST",
                "coef": 360
            },
   [...9 additional teams, omitted for brevity]

        ]
    },
    "subjectTo": [
        {
            "name": "cons1",
            "vars": [
                {
                    "name": "VER",
                    "coef": 30
                },
                {
                    "name": "NOR",
                    "coef": 23
                },
[...18 additional drivers, omitted for brevity]

                {
                    "name": "MCL",
                    "coef": 23.2
                },
[...9 additional teams, omitted for brevity]

            ],
            "bnds": {
                "type": 3,
                "ub": 100,
                "lb": 0
            }
        },
        {
            "name": "cons2",
            "vars": [
                {
                    "name": "VER",
                    "coef": 1
                },
                {
                    "name": "OCO",
                    "coef": 1
                },
[...18 additional drivers, omitted for brevity]

            ],
            "bnds": {
                "type": 5,
                "ub": 5,
                "lb": 5
            }
        },
        {
            "name": "cons3",
            "vars": [
                {
                    "name": "RED",
                    "coef": 1
                },
[...9 additional teams, omitted for brevity]
Show quoted text
[...18 additional drivers, omitted for brevity]

        {
            "name": "cons29",
            "vars": [
                {
                    "name": "FER",
                    "coef": 1
                }
            ],
            "bnds": {
                "type": 4,
                "ub": 1,
                "lb": 0
            }
        },
[...9 additional teams, omitted for brevity]

    ],
    "generals": [
        "VER",
        "OCO",
[...18 additional drivers, omitted for brevity]
        "ALP",
[...9 additional teams, omitted for brevity]
    ]
}
Enter fullscreen mode Exit fullscreen mode

For those unfamiliar with the F1 naming colloquialism, drivers are referred to by the first three letters of their surname (e.g. VER is Max Verstappen), and teams each have a 3 letter mnemonic (e.g. AST is Aston Martin Motorsports).

And so in these linear programs, there is a variable per driver (named with their three letter code) and a variable per team, which must take the value of 1 (on your fantasy team) or 0 (not on your fantasy team). And the objective of the linear program is to maximize points, subject to the sum of the price not exceeding the budget threshold, and also subject to the constraint that the sum of the driver variables is 5, and the team variables is 2. Very straightforward!

A nuance is that you get to choose a "2x driver," who scores twice the points they earned that week. To accommodate this nuance, we generate 20 separate linear programs (each taking a different driver as the 2x), and run glpk.js on each of those 20 programs, to find the one with maximum points.

Disclaimer: we have no affiliation to Formula One (or any of their companies or brands). The author of this capability is simply a fan who enjoys playing the fantasy game. This information is provided with no guarantees as to its' accuracy, use at your own risk.'

Top comments (1)

Collapse
 
mayah_pattengale_7dfc0dbf profile image
Mayah Pattengale

Cool!