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.
433 lines
15 KiB
Python
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()
|