| May 8, 2022
If you are serious about your DFS you’ve probably considered building your own Lineup Optimiser (or Cruncher) at some point. After learning that Excel isn’t the greatest tool for this you’ve probably spent 10 minutes or so googling how to build one and decided that it’s way too complicated and decided you’ll stick with the free to use options that are available. Resigned to the fact that personalised optimisers are only for those people with advanced coding skills.
I’m here to tell you that it’s not actually that complicated to build your own and below I give you the code (developed in R) for a basic single lineup optimiser on which you can expand and add any number of different criteria, including:
- generate multiple lineups
- unique number of players across lineups
- player locks
- player grouping
- team stacking
- positional stacking
- etc etc
For the purpose of this example we’ll use the Draftstars player csv file which can be downloaded from the tournament page and optimise for the team with the highest average (FPPG).
# First we need to load the packages required
library(lpSolve); library(tidyverse)
# Read the player csv file into R and store into a data.frame called stats (I've also filtered the list for players named)
# This assumes that you have stored the csv file in the same location as the R script
stats <- read.csv("players_CBHbFjzX.csv") %>%
filter(Playing.Status == "NAMED IN TEAM TO PLAY")
At this stage you should have a list of players stored in memory that looks like below. This table has all the relevant information to complete the optimiser.
## Player.ID Position Name Team Opponent Salary FPPG
## 1 8000004 MID Patrick Cripps Carlton Adelaide Crows 15520 109.50
## 2 8000005 FWD Charlie Curnow Carlton Adelaide Crows 11290 72.71
## 3 8000007 DEF Sam Docherty Carlton Adelaide Crows 15560 104.29
## 4 8000022 DEF Jacob Weitering Carlton Adelaide Crows 8380 56.00
## 5 8000056 RK Max Gawn Melbourne St Kilda 17000 114.14
## 6 8000058 MID James Harmes Melbourne St Kilda 11240 77.29
## Form Playing.Status
## 1 94.67 NAMED IN TEAM TO PLAY
## 2 79.00 NAMED IN TEAM TO PLAY
## 3 97.33 NAMED IN TEAM TO PLAY
## 4 59.67 NAMED IN TEAM TO PLAY
## 5 133.33 NAMED IN TEAM TO PLAY
## 6 72.67 NAMED IN TEAM TO PLAY
At this point we are ready to set the criteria in our optimiser, for a simple lineup optimiser we only need to define the max salary ($100,000 for Draftstars) and position criteria (2 Defenders/Forwards, 4 Midfielders and 1 Ruck). To do this we assign these values to variables.
salary <- 100000 # set maximum allowable team salary to variable 'salary'
defender <- 2 # set number of defenders to variable 'defender'
midfielder <- 4 # set number of midfielders to variable 'midfielder'
ruck <- 1 # set number of rucks to variable 'ruck'
forward <-2 # set number of forwards to variable 'forward'
Next we set up a matrix with a row for each one of these criteria (each column represents 1 player), positional criteria are binary (1 when a player is equal to the nominated position, 0 when they are not). This matrix is stored in memory as criteria_matrix.
# define optimisation criteria
criteria_matrix <- rbind(
as.numeric(stats$Position == "MID"),
as.numeric(stats$Position == "DEF"),
as.numeric(stats$Position == "RK"),
as.numeric(stats$Position == "FWD"),
stats$Salary)
# print first 10 columns of matrix for info
print(head(criteria_matrix[,c(1:10)], 6))
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,] 1 0 0 0 0 1 0 1 1 1
## [2,] 0 0 1 1 0 0 0 0 0 0
## [3,] 0 0 0 0 1 0 0 0 0 0
## [4,] 0 1 0 0 0 0 1 0 0 0
## [5,] 15520 11290 15560 8380 17000 11240 6900 16600 12940 16030
To define the limits for each of these criteria we store the direction (e.g. more than, equal to, less than, etc) in a vector called criteria_direction and the limit in a vector called criteria_limit.
criteria_direction <- c("==", # midfielders - equal to
"==", # defenders - equal to
"==", # rucks - equal to
"==", # forwards - equal to
"<=") # salary - less than or equal to
# note that we are using teh variables defined earlier
criteria_limit <- c(midfielder,
defender,
ruck,
forward,
salary)
Now we have all the criteria defined we can run the optimiser:
# set object to optimise, in this case we are looking for maximum FPPG
obj <- stats$FPPG
# run optimiser and store solution in object 'sol'
sol <- lp(direction = "max", obj, criteria_matrix, criteria_direction, criteria_limit, all.bin = TRUE)
print(stats[sol$solution==1,])
## Player.ID Position Name Team Opponent Salary
## 1 8000004 MID Patrick Cripps Carlton Adelaide Crows 15520
## 5 8000056 RK Max Gawn Melbourne St Kilda 17000
## 18 8000175 FWD Taylor Walker Adelaide Crows Carlton 11310
## 23 8000394 MID Jack Newnes Carlton Adelaide Crows 10250
## 25 8000399 DEF Jack Sinclair St Kilda Melbourne 13980
## 43 8000637 FWD Jack Silvagni Carlton Adelaide Crows 10230
## 63 8000970 DEF Harrison Petty Melbourne St Kilda 6000
## 86 8001720 MID Sam Berry Adelaide Crows Carlton 7060
## 88 8001732 MID Jack Carroll Carlton Adelaide Crows 8630
## FPPG Form Playing.Status
## 1 109.50 94.67 NAMED IN TEAM TO PLAY
## 5 114.14 133.33 NAMED IN TEAM TO PLAY
## 18 87.50 86.00 NAMED IN TEAM TO PLAY
## 23 76.67 76.67 NAMED IN TEAM TO PLAY
## 25 96.00 87.00 NAMED IN TEAM TO PLAY
## 43 72.17 66.00 NAMED IN TEAM TO PLAY
## 63 51.00 51.00 NAMED IN TEAM TO PLAY
## 86 63.67 63.67 NAMED IN TEAM TO PLAY
## 88 87.00 87.00 NAMED IN TEAM TO PLAY
print(sol)
## Success: the objective function is 757.65
Below we’ve combined all the code into one chunk that can be cut and paste and modified for your own use. Note that this does not deal with dual-position players but this can be easily factored in by expanding criteria_matrix to have binary columns for each player with dual position eligibility…..we can’t give you all the answers, you’ll need to give that a go for yourself :-).
library(lpSolve); library(tidyverse)
stats <- read.csv("players_CBHbFjzX.csv") %>%
filter(Playing.Status == "NAMED IN TEAM TO PLAY")
salary <- 100000
defender <- 2
midfielder <- 4
ruck <- 1
forward <-2
criteria_matrix <- rbind(
as.numeric(stats$Position == "MID"),
as.numeric(stats$Position == "DEF"),
as.numeric(stats$Position == "RK"),
as.numeric(stats$Position == "FWD"),
stats$Salary)
criteria_direction <- c("==",
"==",
"==",
"==",
"<=")
criteria_limit <- c(midfielder,
defender,
ruck,
forward,
salary)
obj <- stats$FPPG
sol <- lp(direction = "max", obj, criteria_matrix, criteria_direction, criteria_limit, all.bin = TRUE)