You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
larp/draft_kings_lineup.py

433 lines
15 KiB
Python

#!/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()