#!/usr/bin/python3 import re from threading import Thread, Semaphore from draft_kings import Client from fantasypros_proj import FantasyProsProjections MIN_PERPOINT = 50 MAX_PERPOINT = 2000 # MIN_PERPOINT = 50 # MAX_PERPOINT = 1000 # MIN_PERPOINT = 200 # MAX_PERPOINT = 800 MAX_SALARY = 50000 LINEUP_POS = ['qb', 'rb', 'wr', 'te', 'dst'] MAX_THREADS = 3 lock = Semaphore(value=MAX_THREADS) class DraftKingsDraft: def __init__(self, draft_group_id): self._draft_group_id = draft_group_id self._proj = {} self._get_fpros_proj() self._players = ( Client(). available_players(draft_group_id=draft_group_id). players) self._pos = { 'qb': DraftKingsDraftPosition([], 'qb', num_in_lineup=1), 'rb': DraftKingsDraftPosition([], 'rb', num_in_lineup=3), 'wr': DraftKingsDraftPosition([], 'wr', num_in_lineup=3), 'te': DraftKingsDraftPosition([], 'te', num_in_lineup=1), 'dst': DraftKingsDraftPosition([], 'dst', num_in_lineup=1), 'k': DraftKingsDraftPosition([], 'k', num_in_lineup=0)} print('DEBUG: Sorting players by position.') self._sort_players_by_pos() # Initialize position groups for making lineups. self._lineup_pos_combo_index = {} threads = {} for pos in LINEUP_POS: threads[pos] = Thread(target=self.run_init_lineup, args=(pos,)) threads[pos].start() self._lineup_pos_combo_index[pos] = 0 for pos in LINEUP_POS: threads[pos].join() # self._find_good_subsets() self._gen_lineup() prev_lineup = self._lineup while self._lineup.salary <= MAX_SALARY: prev_lineup = self._lineup print(self._lineup.pretty_str()) success = self._improve_lineup() if not success: print('**** Failed') break self._gen_lineup() self._lineup = prev_lineup def run_init_lineup(self, pos): lock.acquire() print(f'DEBUG: Start \'{pos}\' thread.') self._pos[pos].init_lineup() lock.release() print(f'DEBUG: \'{pos}\' thread ended.') def draft_kings_players(self): """ Returns all players in the draft_kings module format. """ return self._players def quarter_backs(self): return self.qbs() def qbs(self): return self._get_pos_players('qb') def running_backs(self): return self.rbs() def rbs(self): return self._get_pos_players('rb') def wide_reveivers(self): return self.wrs() def wrs(self): return self._get_pos_players('wr') def tight_ends(self): return self.tes() def tes(self): return self._get_pos_players('te') def defenses(self): return self.dsts() def dsts(self): return self._get_pos_players('dst') def kickers(self): return self.ks() def ks(self): return self._get_pos_players('k') def _get_pos_players(self, pos): """ TODO REM is this useful? """ return self._pos[pos]._players def _get_fpros_proj(self): for pos in LINEUP_POS: self._proj[pos] = FantasyProsProjections(pos) def _sort_players_by_pos(self): for player in self._players: pos = player.position_details.name.lower() if pos not in self._pos: raise Exception("Can't handle position: " + player.position_details.name) fpros_proj = self._proj[pos].get_projection(player.first_name, player.last_name) self._pos[pos]._players.append(DraftKingsDraftPlayer(player, fpros_proj=fpros_proj)) def _get_player_for_lineup(self, pos, lineup_slot): return self._get_pos_players(pos)[self._pos[pos]. lineup_indexes[lineup_slot]] def _gen_lineup(self): players = {} for pos in LINEUP_POS: players[pos] = ( list(self._pos[pos] .pos_combos[self._lineup_pos_combo_index[pos]] .players)) self._lineup = DraftKingsDraftLineup( qb=players['qb'][0], rb1=players['rb'][0], rb2=players['rb'][1], wr1=players['wr'][0], wr2=players['wr'][1], wr3=players['wr'][2], te=players['te'][0], flex=players['rb'][2], dst=players['dst'][0]) def lineup(self): return self._lineup def _improve_lineup(self): lowest = {'pos': LINEUP_POS[0], 'perpoint': MAX_PERPOINT} # Find lowest perpoint for a position group. for pos_key in LINEUP_POS: pos = self._pos[pos_key] # Make sure this position isn't already the highest possible. if pos.highest: continue # lineup_indexes = pos.lineup_indexes # for player in pos._players[lineup_indexes[-1]:]: # if player.perpoint < lowest['perpoint']: # lowest['pos'] = pos_key # lowest['perpoint'] = player.perpoint index = self._lineup_pos_combo_index[pos_key] print("{} perpoint {}".format(pos_key, pos.pos_combos[index].fpros_perpoint)) if pos.pos_combos[index].fpros_perpoint < lowest['perpoint']: lowest['perpoint'] = pos.pos_combos[index].fpros_perpoint lowest['pos'] = pos_key # If we didn't find a pos group that isn't allready highest then we # can't improve. This is, however, very unlikely so we log a warning. if lowest['perpoint'] == MAX_PERPOINT: print("WARNING: All position groups are already maxxed! " + "Something probably went wrong.") return False # Bump up lowest pos. index = self._lineup_pos_combo_index[lowest['pos']] if (index + 1) == len(self._pos[lowest['pos']].pos_combos): self._pos[lowest['pos']].highest = True return self._improve_lineup() else: self._lineup_pos_combo_index[lowest['pos']] += 1 return True class DraftKingsDraftPositionCombo: def __init__(self, players): self.players = players self.salary = 0 self.points = 0.0 self.fpros_points = 0.0 for player in players: self.salary += player.salary self.points += player.points self.fpros_points += player.fpros_proj self.perpoint = self.salary / self.points if self.points != 0.0 else 0.0 self.fpros_perpoint = self.salary / self.fpros_points if self.fpros_points != 0.0 else 0.0 def __str__(self): combo = (f'{{ fpros_perpoint: {self.fpros_perpoint}, ' + f'fpros_points: {self.fpros_points}, ' + f'perpoint: {self.perpoint}, points: {self.points}, ' + f'salary: {self.salary}, players: [ ') players = [] for player in self.players: players.append(player.last_name) combo += ', '.join(players) + ' ] }' return combo class DraftKingsDraftPosition: def __init__(self, players, pos_str, start_index=0, stop_index=0, num_in_lineup=1): self._players = players self._sort_players(lambda p: p.perpoint) self.pos = pos_str # If start_index or stop_index was manually set then use those values, # if not, then calculate the indexes based on MIN_PERPOINT and # MAX_PERPOINT. if start_index != 0 or stop_index != 0: self.start_index = start_index self.stop_index = stop_index else: self.start_index = 0 self.stop_index = 0 self._calc_start_index() self._calc_stop_index() print(f'DEBUG: Using {self.stop_index-self.start_index} players in ' + f'the \'{pos_str}\' group.') self.num_in_lineup = num_in_lineup self.highest = False self.lineup_indexes = [] self.pos_combos = [] self.pos_combos_sets = set() # TODO This is where we initialized lineup indexes, but its not clear # if thats needed going forward. # self. # if len(players) == 0: # self._init_lineup_indexes() # def players(self, new_players=None): # if new_players is None: # return self._players # else: # self._players = new_players # self._calc_start_index() # self._calc_stop_index() def init_lineup(self): self._sort_players(lambda p: p.fpros_perpoint) self._calc_start_index() self._calc_stop_index() self.lineup_indexes = [] for i in range(self.num_in_lineup): self.lineup_indexes.append(self.start_index + i) self.pos_combos_sets = set() self.pos_combos = [] print(f'DEBUG: Calculating position combo sets for {self.pos}.') self._calc_pos_combo_sets(set(), self.num_in_lineup) for combo_set in self.pos_combos_sets: self.pos_combos.append(DraftKingsDraftPositionCombo(combo_set)) # self.pos_combos = sorted(self.pos_combos, key=lambda c: c.perpoint) print(f'DEBUG: Sorting position combo sets for {self.pos}.') self.pos_combos = sorted(self.pos_combos, key=lambda c: c.fpros_perpoint) self.pos_combos_sets = set() # Strip out all entries in pos_combos that aren't increasing in points. stripped_pos_combos = [] points_to_beat = 0 print(f'DEBUG: Stripping position combo sets for {self.pos}.') for combo in self.pos_combos: if combo.fpros_perpoint > MIN_PERPOINT and combo.fpros_points > points_to_beat: stripped_pos_combos.append(combo) points_to_beat = combo.fpros_points self.pos_combos = stripped_pos_combos def _calc_pos_combo_sets(self, starting_perm, final_size): players_avail = self._players[self.start_index:self.stop_index] for player in starting_perm: players_avail = ( list(filter( lambda p: p.object.player_id != player.object.player_id, players_avail))) done_recursing = len(starting_perm) == (final_size - 1) for player in players_avail: new_perm = starting_perm.copy() new_perm.add(player) if done_recursing: self.pos_combos_sets.add(frozenset(new_perm)) else: self._calc_pos_combo_sets(new_perm, final_size) # def get_next_best_players_index(self, starting_index): # player1 = self._players[starting_index] # for i in range(starting_index + 1, len(self._players)): # tmp_player = self._players[i] # if tmp_player.points > player1.points: # return i def _calc_start_index(self): """ Assumes players are already sorted. """ self.start_index = 0 for i, player in enumerate(self._players): if player.fpros_perpoint > MIN_PERPOINT: self.start_index = i break print(f'DEBUG: Set \'{self.pos}\' start_index to {self.start_index}.' + f' Using {self.stop_index-self.start_index} players.') def _calc_stop_index(self): """ Assumes players are already sorted. """ self.stop_index = len(self._players) for i, player in enumerate(self._players): if player.fpros_perpoint > MAX_PERPOINT: self.stop_index = i break print(f'DEBUG: Set \'{self.pos}\' stop_index to {self.stop_index}.' + f' Using {self.stop_index-self.start_index} players.') def _sort_players(self, key): self._players = sorted(self._players, key=key) class DraftKingsDraftPlayer: def __init__(self, player, fpros_proj=0.0): self.first_name = player.first_name self.last_name = player.last_name self.fpros_proj = fpros_proj self.salary = player.draft_details.salary self.points = player.points_per_game self.perpoint = self.salary / self.points if self.points != 0 else 0 self.fpros_perpoint = self.salary / self.fpros_proj if self.fpros_proj != 0 else 0 self.pos = player.position_details.name self.object = player def name(self): return "%s %s" % (self.first_name, self.last_name) def __str__(self): return ("{ fpros_perpoint: %f, perpoint: %f, name: \"%s\", salary: %d, fpros_proj: %f, points: %f, " + "pos: %s}") % ( self.fpros_perpoint, self.perpoint, self.name(), self.salary, self.fpros_proj, self.points, self.pos) class DraftKingsDraftLineup: def __init__(self, qb, rb1, rb2, wr1, wr2, wr3, te, flex, dst): self._qb = qb self._rb1 = rb1 self._rb2 = rb2 self._wr1 = wr1 self._wr2 = wr2 self._wr3 = wr3 self._te = te self._flex = flex self._dst = dst self.points = self.calc_points() self.salary = self.calc_salary() def __str__(self): return "lineup: { points: %f, salary: %d }" % ( self.points, self.salary) def pretty_str(self): col_width = [0, 0, 0, 0, 0] rows = [] lineup_str = (f'Lineup Total-Points: {self.points} ' + f'Total-Salary: {self.salary}\n') for player in [self._qb, self._rb1, self._rb2, self._wr1, self._wr2, self._wr3, self._te, self._flex, self._dst]: row = [player.pos.upper(), player.name(), str(player.fpros_proj), str(player.salary), str(player.fpros_perpoint)] for i, val in enumerate(row): if len(val) > col_width[i]: col_width[i] = len(val) rows.append(row) for row in rows: for i, val in enumerate(row): lineup_str += val + ' ' + (' ' * (col_width[i]-len(val))) lineup_str += '\n' return lineup_str def calc_dk_points(self): return (self._qb.points + self._rb1.points + self._rb2.points + self._wr1.points + self._wr2.points + self._wr3.points + self._te.points + self._flex.points + self._dst.points) def calc_points(self): return (self._qb.fpros_proj + self._rb1.fpros_proj + self._rb2.fpros_proj + self._wr1.fpros_proj + self._wr2.fpros_proj + self._wr3.fpros_proj + self._te.fpros_proj + self._flex.fpros_proj + self._dst.fpros_proj) def calc_salary(self): return (self._qb.salary + self._rb1.salary + self._rb2.salary + self._wr1.salary + self._wr2.salary + self._wr3.salary + self._te.salary + self._flex.salary + self._dst.salary) def run(): draft = DraftKingsDraft(74624) for i, wr, in enumerate(draft.wrs()): print("%s %s" % (wr.perpoint, wr.points)) # TODO REM STARTHERE we need to make all 'points' and 'perpoint' to have # 'fpros' or 'dk' prefix. def debug(): draft = DraftKingsDraft(74624) return draft if __name__ == '__main__': # run() debug()