In [1]:
import pandas as pd
np=pd.np
from sdd_api.api import Api
from credentials import *
import matplotlib.pyplot as plt
%matplotlib inline
api = Api(username=username, password=password, client_id=client_id, client_secret=client_secret)

What We're Going to Do

  1. Get salary information
  2. Get projected points
  3. Run lineup optimizer
  4. Tweak player pool or select highest rated lineups
  5. Generate lineup csv
In [2]:
salaries=api.get_dataframe("dfs_salaries",season_start=2017)
salaries.sample(5)
Out[2]:
season week_num player_name team_name position opp_name fd_points fd_salary dk_points dk_salary yh_points yh_salary player_id
7098 2017 16 Blair Walsh SEA K DAL 3.0 4700.0 NaN NaN NaN NaN 23083
6785 2017 15 Ameer Abdullah DET RB CHI 3.6 5400.0 5.1 3800.0 3.6 13.0 9
7279 2017 16 Chad Hansen NYJ WR LAC 0.0 4500.0 0.0 3000.0 0.0 10.0 8941
5139 2017 12 Maxx Williams BAL TE HOU 1.7 4500.0 2.2 2500.0 1.7 10.0 24073
973 2017 3 Travis Kelce KC TE LAC 0.6 7100.0 1.1 6000.0 0.6 24.0 11963

We're working on creating our own projections but for this example we'll use the ones published by Fantasy Football Analytics.
For your convenience I've already downloaded week 1 for draft kings. The whole process is as follows

Go to http://apps.fantasyfootballanalytics.net

In [3]:
salaries=salaries[salaries['week_num']==1]
In [4]:
dk_projections=pd.read_csv("data/FFA-projs-2017-week1-dk.csv",index_col=False)[['player','position','points','risk','team','lower','upper']]
dk_projections.rename(columns={'player':'player_name','points':'predicted_pts',
                              'team':'team_name'},inplace=True)
dk_projections['team_name']=dk_projections['team_name'].replace("SD","LAC").replace("LA","LAR")
dk_projections['week_num']=1
dk_projections.head()
Out[4]:
player_name position predicted_pts risk team_name lower upper week_num
0 Aaron Rodgers QB 23.496123 3.593419 GB 22.630000 23.897135 1
1 Tom Brady QB 22.642342 3.253799 NE 19.827000 23.975722 1
2 David Johnson RB 22.546692 0.753315 ARI 20.610000 23.898348 1
3 LeVeon Bell RB 21.916783 1.098975 PIT 18.828765 23.708131 1
4 Matt Ryan QB 21.850069 4.874363 ATL 20.600000 23.096079 1
In [5]:
def cleanNames(row):
    name=row['player_name']
    name=name.upper()
    if row['position'] in ['Def','DST']:
        #print(row)
        return row['team_name']
    else:
        name=name.replace(" JR","").replace(".","").replace(" III","").replace("'","")
        #fix a few nicknames
        name=name.replace("PHILLY BROWN","COREY BROWN").replace("KEITH SMITH", "ROD SMITH").replace("ROBERT KELLEY", "ROB KELLEY")
        return name

salaries['merge_name']=salaries.apply(cleanNames,axis=1)
dk_projections['merge_name']=dk_projections.apply(cleanNames,axis=1)
merged=salaries[salaries['week_num']==1].merge(dk_projections, how="left", on=['merge_name','week_num'], suffixes=["","_ffa"])
merged=merged.drop_duplicates(subset=["player_name", "position","team_name"])
merged[(pd.isnull(merged['predicted_pts']))&(merged['position']!='K')]#no kickers in draft kings/dk predictions
Out[5]:
season week_num player_name team_name position opp_name fd_points fd_salary dk_points dk_salary ... yh_salary player_id merge_name player_name_ffa position_ffa predicted_pts risk team_name_ffa lower upper
237 2017 1 Eric Tomlinson NYJ TE BUF 3.5 4500.0 4.5 2500.0 ... 10.0 22300 ERIC TOMLINSON NaN NaN NaN NaN NaN NaN NaN
259 2017 1 Ben Watson BAL TE CIN 0.0 4900.0 0.0 2800.0 ... 11.0 23295 BEN WATSON NaN NaN NaN NaN NaN NaN NaN
323 2017 1 Keelan Cole JAC WR HOU 0.0 4500.0 0.0 3000.0 ... 10.0 4191 KEELAN COLE NaN NaN NaN NaN NaN NaN NaN
366 2017 1 Trey Edmunds NO RB MIN 0.0 4500.0 0.0 3000.0 ... 10.0 6221 TREY EDMUNDS NaN NaN NaN NaN NaN NaN NaN

4 rows × 21 columns

Eric Tomlinson doesn't have a prediction. Given his dk salary is 2500 we will just ignore him

Lineup Optimizer

In [14]:
from itertools import combinations,product
from collections import defaultdict
from operator import itemgetter, attrgetter

def mapPos(row):
    pos=row['position']
    args=[]
    #Vectorize to make position groups easier to handle
    if pos=='QB':
        args.append(np.array((row['salary'], 1,0,0,0,0,0)))
    elif pos=='RB':
        args.append(np.array((row['salary'], 0,1,0,0,0,0)))
        args.append(np.array((row['salary'], 0,0,0,0,1,0)))
    elif pos=='WR':
        args.append(np.array((row['salary'], 0,0,1,0,0,0)))
        args.append(np.array((row['salary'], 0,0,0,0,1,0)))
    elif pos=='TE':
        args.append(np.array((row['salary'], 0,0,0,1,0,0)))
        args.append(np.array((row['salary'], 0,0,0,0,1,0)))
    elif pos=='DST':
        args.append(np.array((row['salary'], 0,0,0,0,0,1)))
    return args
class Lineup:#'W/R/T':1
    
    def __init__(self,players=[]):
        self.rem_lookup={'QB':1,'WR':3,'RB':2,'TE':1,'DST':1}#draft kings settings
        self.score=0.
        self.predictedScore=0.
        self.players=players
        self.salary=50000
        for player in players:
            self.addFull(player)
        
    def isValid(self):
        if len(self.rem_lookup.keys())<=0:
            return True
        else:
            return False
    
    def nextPos(self):
        return list(self.rem_lookup.keys())[0]
    
    def addFull(self,player):
        pos=player['position']
        self.salary-=player['salary']
        
        try:
            self.score+=player['score']
        except:
            pass
        try:
            self.predictedScore+=player['predictedScore']
        except:
            pass
        
    def add(self,player):
        self.players.append(player)
        pos=player['position']
        self.rem_lookup[pos]-=1
        if self.rem_lookup[pos]<=0:
            self.rem_lookup.pop(pos)
        self.salary-=player['salary']
        print(player['predictedScore'])
        self.score+=player['predictedScore']
    def __repr__(self):
        names=', '.join([p['player_name'] for p in self.players])
        return str(self.score)+' '+str(self.predictedScore)+" "+str(self.salary)+' '+names
    def __hash__(self):
        l=sorted([p['player_name'] for p in self.players])
        strings=','.join(l)
        return hash(strings)
In [15]:
def hashPlayer(p):
    return p['player_name']+p['position']


class lineupGenerator:

    def __init__(self,pool,locks=[],goodDef=[]):
        self.rem_lookup={'QB':1,'WR':3,'RB':2,'TE':1,'DST':1}
        self.score=0.
        keys=['QB','WR','RB','TE','DST']
        self.players=[]
        self.pool=set()
        self.lineups=[]
        self.goodDef=goodDef
        pDict={}
        self.locks=pool[pool['player_name'].isin(locks)].to_dict(orient="records")
        for lock in self.locks:
            self.rem_lookup[lock['position']]-=1
        pool=pool[~pool['player_name'].isin(locks)]
        for pos,group in pool.groupby("position"):
            pDict[pos]=group.to_dict(orient="records")
            for p in pDict[pos]:
                self.pool.add(hashPlayer(p))
        options={pos:combinations(pDict[pos],self.rem_lookup[pos]) for pos in self.rem_lookup}
        self.pDict=pDict
        self.salary=50000
        lineups=[]
        lineups=options[keys[0]]
        for idx in range(1,len(keys)):
            lineups=product(lineups,options[keys[idx]])
        self.lineups=lineups
    
    def genLineups(self):
        for l in self.lineups:
            fromTeam=defaultdict(int)
            fromSkill=defaultdict(int)
            fromPos=defaultdict(int)
            oppDST=defaultdict(int)
            attempt=list(l[1]+l[0][1]+l[0][0][1]+l[0][0][0][1]+l[0][0][0][0])
            for lock in self.locks:
                attempt.append(lock)
            sal=0
            valid=True
            for p in attempt:
                if p['position']!='DST':
                    fromTeam[p['team_name']]+=1
                    fromPos[hashPlayer(p)]+=1
                    oppDST[p['opp_name']]+=1
                    if p['position']!='QB':
                        fromSkill[p['team_name']]+=1
                else:
                    if oppDST[p['team_name']]>5:#more than 1 player from team facing defense in lineup
                        valid=False
                sal+=p['salary']
            #add flex generator that checks if in pool
            
            #unique player
            unique_player=all(fromPos[pos]<=1 for pos in fromPos)
            #3 from same team
            from_same=all(oppDST[t]<=4 for t in oppDST)
            if sal<=48000\
            and unique_player\
            and from_same\
            and all(p['opp_name'] not in self.goodDef for p in attempt) and valid:
                yield attempt
In [16]:
def prune(this):
    '''
    Prunes player pool to generate maximum expected value follows:
        if a player has a higher predicted score and lower salary, remove all players with a higher salary than the player
    Additionally
        if all position spots are unused, keep up to the remaining number of slots left
        with a lower score than the player we are on but higher predicted point total than the next best option
    Here's an example:
        Odell beckam  salary 8000 predicted 19
        Alshon Jeffrey salary 7900 predicted 20
        Mohammed sanu salary 5100 predicted 15
        
        Because there are 3+1 spots for WR (including FLEX), we can't remove odell beckham because he would contribute more
        to our point total than removing him. If there was only 1 WR slot we'd be safe to remove him.
    
    '''
    FLEX=['WR', 'RB', 'TE']
    best=pd.DataFrame()
    this=this.sort_values(by="predictedScore",ascending=False)
    
    for pos, group in this.groupby('position'):
        group=group.to_dict(orient='records')
        bad=[]
        for idx,best_player in enumerate(group):             
            salary=best_player['salary']
            rem_lookup={'QB':1,'WR':3,'RB':2,'W/R/T':1,'TE':1,'DST':1}
            rem_lookup['W/R/T']+=rem_lookup[pos]
            for player in group[idx+1:]:
                if player['salary']>=salary:
                    remove_player=False
                    if rem_lookup[pos]<=1:
                        remove_player=True
                    if pos in FLEX :
                        if rem_lookup['W/R/T']<1:
                            remove_player=True
                        else:
                            remove_player=False
                            rem_lookup['W/R/T']-=1
                    else:
                        rem_lookup[pos]-=1
                    if remove_player:
                        group.remove(player)
        best=pd.concat([best,pd.DataFrame(group)])
    best=best[this.columns]#maintain passed col order
    return best.sort_values(by='predictedScore', ascending=False)
In [17]:
dfs=merged.copy()
dfs=dfs[dfs['position']!="K"][['player_name', 'team_name', 'position',
       'opp_name', 'dk_salary',
       'player_id', 'predicted_pts', 'lower',
       'upper']]
dfs['predictedScore']=dfs['predicted_pts']#you could change this to upper or lower depending on the type of lineup your looking for
dfs['salary']=dfs['dk_salary']
dfs=dfs[['team_name', 'player_name', 'position',
       'opp_name', 'salary','predictedScore']]
dfs.head(1)
Out[17]:
team_name player_name position opp_name salary predictedScore
0 ARI Arizona Cardinals DST DET 2800.0 7.260014

Prune

In order to speed up our lineup generator we can prune out overly expensive players. Read the comments in the prune function to get the exact description

In [18]:
pruned=prune(dfs)
pruned
Out[18]:
team_name player_name position opp_name salary predictedScore
0 GB Aaron Rodgers QB SEA 7000.0 23.496123
0 ARI David Johnson RB DET 9400.0 22.546692
1 PIT Le'Veon Bell RB CLE 9800.0 21.916783
1 ATL Matt Ryan QB CHI 6900.0 21.850069
2 WAS Kirk Cousins QB PHI 6400.0 21.002631
0 PIT Antonio Brown WR CLE 8800.0 20.669101
1 ATL Julio Jones WR CHI 8500.0 20.014099
3 BUF Tyrod Taylor QB NYJ 6200.0 18.318134
4 DET Matthew Stafford QB ARI 6100.0 18.122957
2 GB Jordy Nelson WR SEA 7600.0 17.889505
5 CIN Andy Dalton QB BAL 5700.0 17.515457
2 BUF LeSean McCoy RB NYJ 8200.0 17.165540
3 CIN A.J. Green WR BAL 8000.0 16.705215
4 NO Michael Thomas WR MIN 7400.0 16.630797
3 ATL Devonta Freeman RB CHI 7000.0 16.515971
6 NYG Eli Manning QB DAL 5600.0 16.209205
4 LAC Melvin Gordon RB DEN 6600.0 15.697834
5 SEA Doug Baldwin WR GB 6700.0 15.669086
5 CHI Jordan Howard RB ATL 6300.0 15.435095
6 DAL Dez Bryant WR NYG 7500.0 15.405977
7 KC Alex Smith QB NE 5400.0 15.373665
7 OAK Amari Cooper WR TEN 7200.0 15.356374
8 NE Brandin Cooks WR KC 7700.0 15.240044
8 PHI Carson Wentz QB WAS 5300.0 15.016477
9 WAS Terrelle Pryor WR PHI 6100.0 14.843060
10 LAC Keenan Allen WR DEN 6100.0 14.780777
11 DEN Demaryius Thomas WR LAC 6300.0 14.753063
12 ARI Larry Fitzgerald WR DET 5900.0 14.708424
13 HOU DeAndre Hopkins WR JAC 5900.0 14.629065
14 OAK Michael Crabtree WR TEN 6000.0 14.621326
... ... ... ... ... ... ...
46 LAC Travis Benjamin WR DEN 3400.0 6.707451
16 ARI Jermaine Gresham TE DET 2700.0 6.691666
47 OAK Seth Roberts WR TEN 3300.0 6.659285
17 CHI Zach Miller TE ATL 2800.0 6.652618
21 IND Marlon Mack RB LAR 3400.0 6.650975
5 KC Kansas City Chiefs DST NE 2300.0 6.398443
18 LAR Tyler Higbee TE IND 2600.0 6.180053
22 NO Alvin Kamara RB MIN 3500.0 5.926684
48 BUF Andre Holmes WR NYJ 3000.0 5.878436
19 CHI Dion Sims TE ATL 2500.0 5.808481
20 OAK Clive Walford TE TEN 2500.0 5.662776
49 OAK Cordarrelle Patterson WR TEN 3000.0 5.617378
21 SF Garrett Celek TE CAR 2600.0 5.515040
6 CHI Chicago Bears DST ATL 2200.0 5.391771
7 SF San Francisco 49ers DST CAR 2100.0 5.348293
23 MIN Jerick McKinnon RB NO 3200.0 5.305845
24 CHI Tarik Cohen RB ATL 3000.0 5.062552
50 WAS Brian Quick WR PHI 3100.0 4.515122
25 BAL Javorius Allen RB CIN 3000.0 4.376774
51 HOU Bruce Ellington WR JAC 3000.0 4.280907
26 JAC Chris Ivory RB HOU 3600.0 4.245769
13 IND Scott Tolzien QB LAR 4500.0 4.053128
52 DAL Brice Butler WR NYG 3000.0 3.868858
8 CLE Cleveland Browns DST PIT 2000.0 3.625214
27 DEN De'Angelo Henderson RB LAC 3600.0 3.555085
53 SEA Luke Willson WR GB NaN 2.948455
28 SEA Chris Carson RB GB NaN 2.113196
54 DET T.J. Jones WR ARI NaN 1.645612
55 SEA Nick Vannett WR GB NaN 0.389983
14 IND Jacoby Brissett QB LAR 4000.0 0.298763

131 rows × 6 columns

I like to use this pruned set as a starting point and then tweak my final player pool using a custom web app I've developed -- not ready for release as of now

In [19]:
#example
def toIncl(s):
    return s.split(", ")
inclusions=toIncl("Le'Veon Bell, David Johnson, Antonio Brown, Pittsburgh Steelers, Julio Jones, AJ Green, Dez Bryant, Amari Cooper, Kirk Cousins, Sammy Watkins, Terrelle Pryor, Martavis Bryant, Michael Crabtree, Todd Gurley, Larry Fitzgerald, DeAndre Hopkins, Jordan Reed, Alshon Jeffery, Pierre Garcon, Allen Robinson, Lamar Miller, Jimmy Graham, Jeremy Maclin, Eric Decker, Matt Forte, Bilal Powell, John Brown, Jared Goff, Kevin White, Rishard Matthews, Marvin Jones, Sterling Shepard, Robert Woods, Eli Rogers, Jason Witten, Sammie Coates, Will Fuller, Tavon Austin, Terrance West, Jordan Matthews, Mohamed Sanu, JJ Nelson, Torrey Smith, Jaguars, Zay Jones, Zach Ertz, Jared Cook, Phillip Dorsett, Los Angeles Rams, Brian Cook, Marquess Wilson, Austin Hooper, Vernon Davis, Lance Dunbar, Evan Engram, Zach Miller, Vance McDonald, Austin Seferian-Jenkins, Jermaine Gresham, Detroit Lions, Dion Sims, Adam Shaheen, San Francisco 49ers")
player_pool=dfs[dfs['player_name'].isin(inclusions)]
print(len(player_pool))
player_pool[player_pool['position']=='WR']
54
Out[19]:
team_name player_name position opp_name salary predictedScore
35 HOU DeAndre Hopkins WR JAC 5900.0 14.629065
47 PHI Alshon Jeffery WR WAS 5800.0 14.134176
53 ATL Julio Jones WR CHI 8500.0 20.014099
54 DET Marvin Jones WR ARI 4100.0 10.560241
58 BUF Zay Jones WR NYJ 3500.0 10.168998
88 BAL Jeremy Maclin WR CIN 5000.0 12.654458
96 BUF Jordan Matthews WR NYJ 4400.0 11.564966
97 TEN Rishard Matthews WR OAK 4200.0 11.564457
156 WAS Terrelle Pryor WR PHI 6100.0 14.843060
168 JAC Allen Robinson WR HOU 5200.0 13.162698
174 PIT Eli Rogers WR CLE 3900.0 7.916191
183 ATL Mohamed Sanu WR CHI 4200.0 10.467134
188 NYG Sterling Shepard WR DAL 4100.0 10.951218
203 PHI Torrey Smith WR WAS 3700.0 8.048873
258 LAR Sammy Watkins WR IND 6200.0 12.617658
269 CHI Kevin White WR ATL 4200.0 11.486123
288 LAR Robert Woods WR IND 4000.0 9.236753
292 PIT Antonio Brown WR CLE 8800.0 20.669101
295 ARI John Brown WR DET 4800.0 10.361549
297 DAL Dez Bryant WR NYG 7500.0 15.405977
299 PIT Martavis Bryant WR CLE 6000.0 8.538977
319 CLE Sammie Coates WR PIT 3800.0 4.603813
333 OAK Amari Cooper WR TEN 7200.0 15.356374
337 OAK Michael Crabtree WR TEN 6000.0 14.621326
354 TEN Eric Decker WR OAK 5000.0 11.569751
362 NE Phillip Dorsett WR KC 3300.0 5.168265
382 ARI Larry Fitzgerald WR DET 5900.0 14.708424
395 LAR Tavon Austin WR IND 3800.0 7.921712
397 SF Pierre Garcon WR CAR 5300.0 12.918005
411 SEA Jimmy Graham WR GB NaN 12.641802

Constraints

Next, we can impose constrains on our lineups. I'll walk through the types of supported constraints:

  • ONE_OF -- All lineups must contain only 1 of the players in each added list
  • ONLY_ONE -- All lineups can contain no more than 1 of the players in each added list
  • AT_LEAST -- All lineups must have at least 1 of the players in each added list

Last season this was great for dealing with position groups like the Falcons Offense or the Redskins Receiving core. It was tough to say who will score but you can be quite confident they will have a touchdown. Constraints help you take advantage of that

In [35]:
#last years example
tweaked=pruned
ONE_OF=[]
ONE_OF.append(['Ty Montgomery'])#forces Ty Montgomery in each lineup
ONE_OF.append(["LeGarrette Blount","Dion Lewis"])
ONE_OF.append(["Devonta Freeman","Tevin Coleman"])
ONLY_ONE=[]
ONLY_ONE.append(['Devonta Freeman', 'Tevin Coleman'])
ONLY_ONE.append(['Antonio Brown','Eli Rogers'])#never more than one of these guys
ONLY_ONE.append(['Julio Jones','Mohamed Sanu'])
AT_LEAST=[]
#AT_LEAST.append()
locks=[]#["Julien Edelman"]
tweaked[tweaked['position']=='DST']
Out[35]:
team_name player_name position opp_name salary predictedScore
0 DEN Denver Broncos DST LAC 3600.0 8.655786
1 CAR Carolina Panthers DST SF 3500.0 7.520136
2 BAL Baltimore Ravens DST CIN 2800.0 7.454143
3 IND Indianapolis Colts DST LAR 2700.0 7.285907
4 OAK Oakland Raiders DST TEN 2500.0 6.774250
5 KC Kansas City Chiefs DST NE 2300.0 6.398443
6 CHI Chicago Bears DST ATL 2200.0 5.391771
7 SF San Francisco 49ers DST CAR 2100.0 5.348293
8 CLE Cleveland Browns DST PIT 2000.0 3.625214

We'll just use locks for this example. Besides that we have some standard GPP constraints like:

  • don't face your starting defense
  • don't have too many players from one team
  • too many players from the same skill group on a team
  • our max expected value pruned player pool.
In [39]:
#gen lineups without flex
tweaked=prune(dfs)
locks=["Matt Ryan","David Johnson","Baltimore Ravens", "Rob Gronkowski"]
lineGen=lineupGenerator(tweaked, locks, [])
lineups=lineGen.genLineups()
lineups=list(lineups)
HIGH_RISK_SWAPS=[]
ONE_OF=[]
AT_LEAST=[]
ONLY_ONE=[]
len(lineups)
Out[39]:
42921

Add flex player

In [40]:
finals=[]
flex=tweaked[tweaked['position'].isin(["WR","TE","RB"])]
for run,l in enumerate(lineups):
    if run%1000==0:
        print(float(run)/len(lineups),len(finals))
    names=[player['player_name'] for player in l]
    for f in flex[~flex['player_name'].isin(names)].to_dict(orient="records"):
        final=l[:]
        originalPos=f['position']
        f['position']='FLEX'
        final.append(f)
        fromTeam=defaultdict(int)
        fromSkill=defaultdict(int)
        fromPos=defaultdict(int)
        fromTeamTE=defaultdict(int)
        oppDST=defaultdict(int)
        sal=0
        valid=True
        swap=0
        one_of=[0]*len(ONE_OF)
        at_least=[0]*len(AT_LEAST)
        only_one=[0]*len(ONLY_ONE)
        #ADD SWAPS. 
        ONE_OF_COPY=ONE_OF.copy()
        AT_LEAST_COPY=AT_LEAST.copy()
        ONLY_ONE_COPY=ONLY_ONE.copy()
        dst=None
        for p in final:
            if p['position']=='FLEX':
                p["position"]=originalPos
            if p['position']!='DST':
                fromTeam[p['team_name']]+=1
                if p['position']=='TE':
                    fromTeamTE[p['team_name']]+=1
                fromPos[hashPlayer(p)]+=1
                oppDST[p['opp_name'].replace('@','')]+=1
                if p['position']!='QB':
                    fromSkill[p['team_name']]+=1
            else:
                dst=p['team_name']
            sal+=p['salary']
            if p['player_name'] in HIGH_RISK_SWAPS:
                swap+=1
            for idx, players in enumerate(ONE_OF_COPY):
                if p['player_name'] in players:
                    one_of[idx]+=1
                    #ONE_OF_COPY.remove(players)
            for idx, players in enumerate(AT_LEAST_COPY):
                if p['player_name'] in players:
                    at_least[idx]+=1
                    #AT_LEAST_COPY.remove(players)
            for idx, players in enumerate(ONLY_ONE_COPY):
                if p['player_name'] in players:
                    only_one[idx]+=1
                    #ONLY_ONE_COPY.remove(players)
            #add flex generator that checks if in pool
        final[-1]["position"]="FLEX"
        if sal<=50000 and all(fromSkill[pos]<=2 for pos in fromSkill) \
            and all(fromTeamTE[team]<=1 for team in fromTeamTE)\
            and all(fromPos[pos]<=1 for pos in fromPos)\
             and sal>=40000 and valid and swap<=1 \
                and all(one_of==1 for one_of in one_of)\
                and all(at>=1 for at in at_least)\
                and all(one<=1 for one in only_one):
                if oppDST[dst]<=1:
                    #print(oppDST)
                    #print(lineup)
                    lineup=Lineup(final)
                    finals.append(lineup)
0.0 0
0.023298618391929357 6390
0.046597236783858714 19955
0.06989585517578807 30532
0.09319447356771743 44339
0.1164930919596468 55705
0.13979171035157614 70795
0.1630903287435055 82151
0.18638894713543486 103313
0.20968756552736423 116089
0.2329861839192936 129248
0.25628480231122297 143191
0.2795834207031523 155311
0.30288203909508166 176609
0.326180657487011 195047
0.3494792758789404 212601
0.3727778942708697 237882
0.3960765126627991 259376
0.41937513105472846 273899
0.4426737494466578 299379
0.4659723678385872 317347
0.4892709862305165 341994
0.5125696046224459 375628
0.5358682230143752 396803
0.5591668414063046 422707
0.5824654597982339 457043
0.6057640781901633 479318
0.6290626965820927 508150
0.652361314974022 539459
0.6756599333659514 566522
0.6989585517578808 602524
0.7222571701498102 629243
0.7455557885417394 664840
0.7688544069336688 696454
0.7921530253255982 735515
0.8154516437175275 768074
0.8387502621094569 806197
0.8620488805013863 842271
0.8853474988933157 880323
0.908646117285245 921004
0.9319447356771744 962802
0.9552433540691037 1007226
0.978541972461033 1052553
In [42]:
allHash=set()
for f in finals:
    phash=set()
    for p in f.players:
        phash.add(p["player_name"]+","+p["team_name"]+","+str(p["predictedScore"]))
    allHash.add(frozenset(phash))
len(allHash)
Out[42]:
577321

Inspect Lineups Generated

500,000 lineups! The lower number is without duplicates caused by flex vs normal spot

In [43]:
#Grab top 10 lineups
finals=sorted(list(allHash)[:10],key=lambda x: sum([float(xi.split(",")[-1]) for xi in x]),reverse=True)
In [44]:
new=[]
for idx, f in enumerate(finals):
    if idx%1000==0:
        print(idx,len(finals), idx/len(finals))
    l=[]
    posis={"WR": 3,"TE":1,"RB":2}
    for i,n in enumerate(list(f)):
        p={}
        p["player_name"]=n.split(",")[0]
        p["team_name"]=n.split(",")[1]
        p['predictedScore']=float(n.split(",")[2])
        filtered=pruned[(pruned["player_name"]==p["player_name"])&(pruned["team_name"]==p["team_name"])].iloc[0]
        p["position"]=filtered["position"]
        p["salary"]=filtered["salary"]
        if p["position"] in posis.keys():
            posis[p["position"]]-=1
        if p["position"] in posis.keys() and posis[p["position"]]<0:
            p["position"]="FLEX"
        l.append(p)
    new.append(l)
new_lineups= [Lineup(l) for l in new]
sort=sorted(new_lineups,key=attrgetter("predictedScore"),reverse=True)
playerCounts=defaultdict(int)
for l in new:
    for p in l:
        playerCounts[p["player_name"]]+=1
print("Player count", len(playerCounts.keys()))
print("Players used",sorted(tweaked['player_name'].unique()))
print(len(tweaked['player_name'].unique()))
{key: playerCounts[key]/float(len(new)) for key in playerCounts}
0 10 0.0
Player count 32
Players used ['A.J. Green', 'Aaron Rodgers', 'Alex Smith', 'Alshon Jeffery', 'Alvin Kamara', 'Andre Holmes', 'Andy Dalton', 'Antonio Brown', 'Baltimore Ravens', 'Bilal Powell', 'Brandon LaFell', 'Brandon Marshall', 'Brian Hoyer', 'C.J. Anderson', 'C.J. Fiedorowicz', 'Carlos Hyde', 'Carolina Panthers', 'Carson Wentz', 'Chicago Bears', 'Chris Thompson', 'Cleveland Browns', 'Clive Walford', 'Coby Fleener', 'Cordarrelle Patterson', 'Davante Adams', 'David Johnson', 'DeAndre Hopkins', 'DeShone Kizer', 'Denver Broncos', 'Devonta Freeman', 'Dez Bryant', 'Donte Moncrief', 'Doug Baldwin', 'Eli Manning', 'Indianapolis Colts', 'James White', 'Jared Cook', 'Jarvis Landry', 'Javorius Allen', 'Jay Ajayi', 'Jeremy Maclin', 'Jerick McKinnon', 'Jermaine Gresham', 'Jordan Howard', 'Jordy Nelson', 'Josh McCown', 'Julio Jones', 'Kansas City Chiefs', 'Keenan Allen', 'Kendall Wright', 'Kenny Britt', 'Kirk Cousins', 'Kyle Rudolph', 'Larry Fitzgerald', "Le'Veon Bell", 'LeSean McCoy', 'Marlon Mack', 'Marquise Goodwin', 'Matt Ryan', 'Matthew Stafford', 'Melvin Gordon', 'Michael Thomas', 'Mike Glennon', 'Mike Wallace', 'Oakland Raiders', 'Odell Beckham', 'Paul Perkins', 'Rob Gronkowski', 'Robby Anderson', 'Robert Kelley', 'San Francisco 49ers', 'Scott Tolzien', 'Shane Vereen', 'Sterling Shepard', 'Tarik Cohen', 'Ted Ginn', 'Terrelle Pryor', 'Todd Gurley', 'Travis Kelce', 'Tyler Higbee', 'Tyrod Taylor', 'Zach Ertz', 'Zay Jones']
83
Out[44]:
{'Alshon Jeffery': 0.1,
 'Alvin Kamara': 0.2,
 'Andre Holmes': 0.3,
 'Baltimore Ravens': 1.0,
 'Brandon LaFell': 0.1,
 'Brandon Marshall': 0.3,
 'C.J. Fiedorowicz': 0.1,
 'Chris Thompson': 0.2,
 'Clive Walford': 0.3,
 'Davante Adams': 0.1,
 'David Johnson': 1.0,
 'DeAndre Hopkins': 0.2,
 'Devonta Freeman': 0.1,
 'Dez Bryant': 0.1,
 'Donte Moncrief': 0.2,
 'James White': 0.2,
 'Jermaine Gresham': 0.1,
 'Julio Jones': 0.1,
 'Keenan Allen': 0.1,
 'Kenny Britt': 0.2,
 'Larry Fitzgerald': 0.2,
 'Marlon Mack': 0.1,
 'Marquise Goodwin': 0.4,
 'Matt Ryan': 1.0,
 'Mike Wallace': 0.1,
 'Rob Gronkowski': 1.0,
 'Robby Anderson': 0.2,
 'Shane Vereen': 0.2,
 'Sterling Shepard': 0.2,
 'Tarik Cohen': 0.2,
 'Terrelle Pryor': 0.2,
 'Zay Jones': 0.2}

Our constraints we're honored. Matt Ryan, Rob Gronkowski and the Baltimore Ravens are in all of our lineups

In [45]:
new_lineups
Out[45]:
[0.0 127.83853357199999 300.0 David Johnson, Zay Jones, Terrelle Pryor, Matt Ryan, C.J. Fiedorowicz, Baltimore Ravens, Devonta Freeman, Sterling Shepard, Rob Gronkowski,
 0.0 122.81643964300002 600.0 Larry Fitzgerald, David Johnson, Matt Ryan, Marquise Goodwin, Alshon Jeffery, Kenny Britt, Baltimore Ravens, Shane Vereen, Rob Gronkowski,
 0.0 121.79694265100001 2500.0 David Johnson, Brandon Marshall, Matt Ryan, Kenny Britt, Baltimore Ravens, Chris Thompson, Sterling Shepard, Rob Gronkowski, James White,
 0.0 120.00725186100001 2700.0 David Johnson, Marlon Mack, Matt Ryan, Marquise Goodwin, Baltimore Ravens, Brandon Marshall, Robby Anderson, Rob Gronkowski, DeAndre Hopkins,
 0.0 118.747398385 1100.0 David Johnson, Dez Bryant, Alvin Kamara, Matt Ryan, Marquise Goodwin, Brandon LaFell, Baltimore Ravens, Brandon Marshall, Rob Gronkowski,
 0.0 117.52101673800001 2500.0 David Johnson, Clive Walford, Tarik Cohen, Matt Ryan, Davante Adams, Baltimore Ravens, Rob Gronkowski, Donte Moncrief, DeAndre Hopkins,
 0.0 117.518967094 1200.0 Larry Fitzgerald, David Johnson, Clive Walford, Julio Jones, Tarik Cohen, Matt Ryan, Baltimore Ravens, Andre Holmes, Rob Gronkowski,
 0.0 117.447469314 2800.0 David Johnson, Terrelle Pryor, Matt Ryan, Mike Wallace, Baltimore Ravens, Andre Holmes, Shane Vereen, Rob Gronkowski, James White,
 0.0 112.57136247100001 6000.0 David Johnson, Jermaine Gresham, Zay Jones, Matt Ryan, Marquise Goodwin, Baltimore Ravens, Chris Thompson, Rob Gronkowski, Donte Moncrief,
 0.0 109.646602266 5200.0 David Johnson, Clive Walford, Alvin Kamara, Matt Ryan, Baltimore Ravens, Keenan Allen, Andre Holmes, Robby Anderson, Rob Gronkowski]

Our highest expected value with our constraints is 127.838 points

Output Lineup CSV

Download the draft kings lineup template by going to the lineups tab on Draft Kings.com. We've included the all game template for week 1 for your convenience

In [53]:
id_table=pd.read_csv("data/dk-lineup-template-week1.csv",skiprows=7)
id_table.index=range(len(id_table))
id_table.columns=range(len(id_table.columns))
id_table=id_table[[9,10,11,12,13,14,15]]
id_table.columns=["position","Name + ID","player_name","ID","Salary","Game Info","team_name"]
def cleanName(name):
    #sites handle surnames/nicknames differently. We will remove them and have verified this is ok at the time of writing
    return name.replace(' Jr.', '').replace("'","").replace(" Sr.","")
id_table['player_name']=id_table['player_name'].apply(cleanName)
id_table.head(3)
Out[53]:
position Name + ID player_name ID Salary Game Info team_name
0 RB Le'Veon Bell (9359211) LeVeon Bell 9359211 9800 [email protected] 01:00PM ET PIT
1 RB David Johnson (9359235) David Johnson 9359235 9400 [email protected] 01:00PM ET ARI
2 WR Antonio Brown (9358987) Antonio Brown 9358987 8800 [email protected] 01:00PM ET PIT
In [54]:
rows=[]
POS_START={"QB":0,"RB":1,"WR":3,"TE":6,"FLEX":7,"DST":8}
for l in new_lineups:
    row=[0]*9
    for p in l.players[:]:
        idx=POS_START[p['position']]
        if(idx==8):
            name=p["player_name"].split(" ")[-1]+" "#Draft kings DST's have a space after them for some reason.
        else:
            name=p["player_name"]
        player=id_table[(id_table['player_name']==name)&(id_table['team_name']==p['team_name'])].iloc[0]
        if row[idx]<1:
            row[idx]=player['ID']
        else:
            idx+=1
            if row[idx]<1:
                row[idx]=player['ID']
            else:
                idx+=1
                row[idx]=player['ID']

    rows.append(row)
output_lineups=pd.DataFrame(rows, columns=["QB","RB","RB","WR","WR","WR","TE","FLEX","DST"])
output_lineups
Out[54]:
QB RB RB WR WR WR TE FLEX DST
0 9358787 9359235 9359384 9359993 9359005 9359688 9359251 9358612 9358762
1 9358787 9359235 9358968 9358814 9359127 9359122 9358612 9358910 9358762
2 9358787 9359235 9359150 9358773 9358910 9359688 9358612 9358576 9358762
3 9358787 9359235 9360142 9359127 9358773 9359545 9358612 9359331 9358762
4 9358787 9359235 9358631 9358936 9359127 9358874 9358612 9358773 9358762
5 9358787 9359235 9360065 9359572 9359514 9359331 9359249 9358612 9358762
6 9358787 9359235 9360065 9358814 9359003 9359310 9359249 9358612 9358762
7 9358787 9359235 9358968 9359005 9358914 9359310 9358612 9358576 9358762
8 9358787 9359235 9359150 9359993 9359127 9359514 9358900 9358612 9358762
9 9358787 9359235 9358631 9358696 9359310 9359545 9359249 9358612 9358762
In [55]:
output_lineups.to_csv("data/lineupsweek1-example.csv",index=False)

There you have it. All you have to do now is go to the lineups tab on draft kings and upload this sheet.
I hope this served as a good example. The code needs serious refactoring, probably should just be scrapped, but this should give you a great starting point for your own lineup optimizer