def get_starting_squad(fpl_team_id=None): """ use the transactions table in the db """ if not fpl_team_id: # use the most recent transaction in the table most_recent = (session.query(Transaction).order_by( Transaction.id.desc()).filter_by(free_hit=0).first()) fpl_team_id = most_recent.fpl_team_id print("Getting starting squad for {}".format(fpl_team_id)) s = Squad() # Don't include free hit transfers as they only apply for the week the # chip is activated transactions = (session.query(Transaction).order_by( Transaction.gameweek, Transaction.id).filter_by( fpl_team_id=fpl_team_id).filter_by(free_hit=0).all()) for trans in transactions: if trans.bought_or_sold == -1: s.remove_player(trans.player_id, price=trans.price) else: # within an individual transfer we can violate the budget and squad # constraints, as long as the final squad for that gameweek obeys them s.add_player( trans.player_id, price=trans.price, season=trans.season, gameweek=trans.gameweek, check_budget=False, check_team=False, ) return s
def test_cant_add_same_player(fill_players): """ can't add a player thats already on the squad. """ with test_session_scope() as ts: t = Squad() added_ok = t.add_player(1, season=TEST_SEASON, dbsession=ts) assert added_ok added_ok = t.add_player(1, season=TEST_SEASON, dbsession=ts) assert not added_ok
def test_cant_add_too_many_per_squad(fill_players): """ no more than three from the same squad. """ with test_session_scope() as ts: t = Squad() assert t.add_player(1, season=TEST_SEASON, dbsession=ts) assert t.add_player(21, season=TEST_SEASON, dbsession=ts) assert t.add_player(41, season=TEST_SEASON, dbsession=ts) assert not t.add_player(61, season=TEST_SEASON, dbsession=ts)
def get_starting_squad(fpl_team_id=None, use_api=False, apifetcher=None): """ use the transactions table in the db, or the API if requested """ if use_api: if not fpl_team_id: raise RuntimeError( "Please specify fpl_team_id to get current squad from API") players_prices = get_current_squad_from_api(fpl_team_id, apifetcher=apifetcher) s = Squad(season=CURRENT_SEASON) for pp in players_prices: s.add_player( pp[0], price=pp[1], gameweek=NEXT_GAMEWEEK - 1, check_budget=False, check_team=False, ) s.budget = get_bank(fpl_team_id, season=CURRENT_SEASON) return s # otherwise, we use the Transaction table in the DB if not fpl_team_id: # use the most recent transaction in the table most_recent = (session.query(Transaction).order_by( Transaction.id.desc()).filter_by(free_hit=0).first()) if most_recent is None: raise ValueError("No transactions in database.") fpl_team_id = most_recent.fpl_team_id print("Getting starting squad for {}".format(fpl_team_id)) # Don't include free hit transfers as they only apply for the week the # chip is activated transactions = (session.query(Transaction).order_by( Transaction.gameweek, Transaction.id).filter_by( fpl_team_id=fpl_team_id).filter_by(free_hit=0).all()) if len(transactions) == 0: raise ValueError( f"No transactions in database for team ID {fpl_team_id}") s = Squad(season=transactions[0].season) for trans in transactions: if trans.bought_or_sold == -1: s.remove_player(trans.player_id, price=trans.price) else: # within an individual transfer we can violate the budget and squad # constraints, as long as the final squad for that gameweek obeys them s.add_player( trans.player_id, price=trans.price, gameweek=trans.gameweek, check_budget=False, check_team=False, ) return s
def fitness(self, player_ids): """PyGMO required function. The objective function to minimise. In this case: - 0 if the proposed squad isn't valid - weghted sum of gameweek points otherwise """ # Make squad from player IDs squad = Squad(budget=self.budget, season=self.season) for idx in player_ids: add_ok = squad.add_player( self.players[int(idx)].player_id, gameweek=self.start_gw, ) if not add_ok: return [0] # fill empty slots with dummy players (if chosen not to optimise full squad) for pos in self.positions: if self.dummy_per_position[pos] > 0: for _ in range(self.dummy_per_position[pos]): dp = DummyPlayer( self.gw_range, self.tag, pos, price=self.dummy_sub_cost ) add_ok = squad.add_player(dp) if not add_ok: return [0] # Check squad is valid, if not return fitness of zero if not squad.is_complete(): return [0] # Calc expected points for all gameweeks # - weight each gw points by its gw_weight # - weight each sub by their sub_weight score = 0.0 for i, gw in enumerate(self.gw_range): gw_weight = self.gw_weight[i] if gw == self.bench_boost_gw: score += gw_weight * squad.get_expected_points( gw, self.tag, bench_boost=True ) elif gw == self.triple_captain_gw: score += gw_weight * squad.get_expected_points( gw, self.tag, triple_captain=True ) else: score += gw_weight * squad.get_expected_points(gw, self.tag) if gw != self.bench_boost_gw: score += gw_weight * squad.total_points_for_subs( gw, self.tag, sub_weights=self.sub_weights ) return [-score]
def test_remove_player(fill_players): """ add a player then remove them. """ with test_session_scope() as ts: t = Squad() t.add_player(1, season=TEST_SEASON, dbsession=ts) assert len(t.players) == 1 assert t.num_position["GK"] == 1 t.remove_player(1, season=TEST_SEASON, use_api=False, dbsession=ts) assert len(t.players) == 0 assert t.num_position["GK"] == 0 assert t.budget == 1000
def get_lineup_from_payload(lineup): """ inverse of build_lineup_payload. Returns a squad object from get_lineup lineup is a dictionary, with the entry "picks" being a list of dictionaries like: {"element":353,"position":1,"selling_price":55,"multiplier":1,"purchase_price":55,"is_captain":false,"is_vice_captain":false} """ s = Squad() for p in lineup["picks"]: player = get_player_from_api_id(p["element"]) s.add_player(player, check_budget=False) if s.is_complete(): return s else: raise RuntimeError("Squad incomplete")
def test_add_player_by_id(fill_players): """ Should be able to add a player with integer argument """ with test_session_scope() as ts: t = Squad() added_ok = t.add_player(50, season=TEST_SEASON, dbsession=ts) assert added_ok
def test_add_player_by_name(fill_players): """ Should be able to add a player with string argument """ with test_session_scope() as ts: t = Squad() added_ok = t.add_player("Alice", season=TEST_SEASON, dbsession=ts) assert added_ok
def generate_dummy_squad(player_points_dict=None): """ Fill a squad up with dummy players. player_points_dict is a dictionary { player_id: { gw: points,...} ,...} """ if not player_points_dict: # make a simple one player_points_dict = {i: {1: 2} for i in range(15)} t = Squad() for i in range(15): if i < 2: position = "GK" elif i < 7: position = "DEF" elif i < 12: position = "MID" else: position = "FWD" t.add_player(DummyPlayer(i, position, player_points_dict[i])) return t
def get_starting_squad(): """ use the transactions table in the db """ s = Squad() # Don't include free hit transfers as they only apply for the week the chip is activated transactions = (session.query(Transaction).order_by( Transaction.id).filter_by(free_hit=0).all()) for trans in transactions: if trans.bought_or_sold == -1: s.remove_player(trans.player_id, price=trans.price) else: ## within an individual transfer we can violate the budget and team constraints, ## as long as the final team for that gameweek obeys them s.add_player( trans.player_id, price=trans.price, season=trans.season, gameweek=trans.gameweek, check_budget=False, check_team=False, ) return s
def test_cant_add_too_many_per_position(fill_players): """ no more than two keepers, 5 defenders, 5 midfielders, 3 forwards. """ with test_session_scope() as ts: t = Squad() # keepers assert t.add_player("Alice", season=TEST_SEASON, dbsession=ts) assert t.add_player("Bob", season=TEST_SEASON, dbsession=ts) assert not t.add_player("Pedro", season=TEST_SEASON, dbsession=ts) # defenders assert t.add_player("Carla", season=TEST_SEASON, dbsession=ts) assert t.add_player("Donald", season=TEST_SEASON, dbsession=ts) assert t.add_player("Erica", season=TEST_SEASON, dbsession=ts) assert t.add_player("Frank", season=TEST_SEASON, dbsession=ts) assert t.add_player("Gerry", season=TEST_SEASON, dbsession=ts) assert not t.add_player("Stefan", season=TEST_SEASON, dbsession=ts)
def validate_session_squad(session_id, dbsession=DBSESSION): """ get the list of player_ids for this session_id, and see if we can make a valid 15-player squad out of it """ budget = get_session_budget(session_id, dbsession) players = get_session_players(session_id, dbsession) if len(players) != 15: return False t = Squad(budget) for p in players: added_ok = t.add_player(p["id"], dbsession=dbsession) if not added_ok: return False return True
def test_cant_exceed_budget(): """ try and make an expensive squad """ with test_session_scope() as ts: t = Squad() added_ok = True added_ok = added_ok and t.add_player(45, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(46, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(47, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(48, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(49, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(50, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(51, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(52, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(53, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(54, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(55, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(56, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(57, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(58, season=TEST_SEASON, dbsession=ts) added_ok = added_ok and t.add_player(59, season=TEST_SEASON, dbsession=ts) assert not added_ok
def best_transfer_suggestions(n_transfer, session_id, dbsession=DBSESSION): """ Use our predicted playerscores to suggest the best transfers. """ n_transfer = int(n_transfer) if n_transfer not in range(1, 3): raise RuntimeError("Need to choose 1 or 2 transfers") if not validate_session_squad(session_id, dbsession): raise RuntimeError("Cannot suggest transfer without complete squad") budget = get_session_budget(session_id, dbsession) players = [p["id"] for p in get_session_players(session_id, dbsession)] t = Squad(budget) for p in players: added_ok = t.add_player(p) if not added_ok: raise RuntimeError("Cannot add player {}".format(p)) pred_tag = get_latest_prediction_tag() if n_transfer == 1: _, pid_out, pid_in = make_optimum_single_transfer(t, pred_tag) elif n_transfer == 2: _, pid_out, pid_in = make_optimum_double_transfer(t, pred_tag) return {"transfers_out": pid_out, "transfers_in": pid_in}
def make_new_squad_pygmo( gw_range, tag, budget=1000, players_per_position=TOTAL_PER_POSITION, season=CURRENT_SEASON, verbose=1, bench_boost_gw=None, triple_captain_gw=None, remove_zero=True, # don't consider players with predicted pts of zero sub_weights={"GK": 0.01, "Outfield": (0.4, 0.1, 0.02)}, dummy_sub_cost=45, uda=pg.sga(gen=100), population_size=100, **kwargs, ): """Optimize a full initial squad using any PyGMO-compatible algorithm. Parameters ---------- gw_range : list Gameweeks to optimize squad for tag : str Points prediction tag to use budget : int, optional Total budget for squad times 10, by default 1000 players_per_position : dict No. of players to optimize in each position, by default airsenal.framework.squad.TOTAL_PER_POSITION season : str Season to optimize for, by default airsenal.framework.utils.CURRENT_SEASON verbose : int Verbosity of optimization algorithm, by default 1 bench_boost_gw : int Gameweek to play benfh boost, by default None triple_captain_gw : int Gameweek to play triple captaiin, by default None, remove_zero : bool If True don't consider players with predicted pts of zero, by default True sub_weights : dict Weighting to give to substitutes in optimization, by default {"GK": 0.01, "Outfield": (0.4, 0.1, 0.02)}, dummy_sub_cost : int, optional If not optimizing a full squad the price of each player that is not being optimized. For example, if you are optimizing 12 out of 15 players, the effective budget for optimizinig the squad will be budget - (15 -12) * dummy_sub_cost, by default 45 uda : class, optional PyGMO compatible algorithm class, by default pg.sga(gen=100) population_size : int, optional Number of candidate solutions in each generation of the optimization, by default 100 Returns ------- airsenal.framework.squad.Squad The optimized squad """ # Build problem opt_squad = SquadOpt( gw_range, tag, budget=budget, players_per_position=players_per_position, dummy_sub_cost=dummy_sub_cost, season=season, bench_boost_gw=bench_boost_gw, triple_captain_gw=triple_captain_gw, remove_zero=remove_zero, # don't consider players with predicted pts of zero sub_weights=sub_weights, ) prob = pg.problem(opt_squad) # Create algorithm to solve problem with algo = pg.algorithm(uda=uda) algo.set_verbosity(verbose) # population of problems pop = pg.population(prob=prob, size=population_size) # solve problem pop = algo.evolve(pop) if verbose > 0: print("Best score:", -pop.champion_f[0], "pts") # construct optimal squad squad = Squad(budget=opt_squad.budget, season=season) for idx in pop.champion_x: if verbose > 0: print( opt_squad.players[int(idx)].position(season), opt_squad.players[int(idx)].name, opt_squad.players[int(idx)].team(season, 1), opt_squad.players[int(idx)].price(season, 1) / 10, ) squad.add_player( opt_squad.players[int(idx)].player_id, gameweek=opt_squad.start_gw, ) # fill empty slots with dummy players (if chosen not to optimise full squad) for pos in opt_squad.positions: if opt_squad.dummy_per_position[pos] > 0: for _ in range(opt_squad.dummy_per_position[pos]): dp = DummyPlayer( opt_squad.gw_range, opt_squad.tag, pos, price=opt_squad.dummy_sub_cost, ) squad.add_player(dp) if verbose > 0: print(dp.position, dp.name, dp.purchase_price / 10) if verbose > 0: print(f"£{squad.budget/10}m in the bank") return squad
def make_new_squad_iter( gw_range, tag, budget=1000, season=CURRENT_SEASON, num_iterations=100, update_func_and_args=None, verbose=False, bench_boost_gw=None, triple_captain_gw=None, **kwargs, ): """ Make a squad from scratch, i.e. for gameweek 1, or for wildcard, or free hit, by selecting high scoring players and then iteratively replacing them with cheaper options until we have a valid squad. """ transfer_gw = min(gw_range) # the gw we're making the new squad best_score = 0.0 best_squad = None for iteration in range(num_iterations): if verbose: print("Choosing new squad: iteration {}".format(iteration)) if update_func_and_args: # call function to update progress bar. # this was passed as a tuple (func, increment, pid) update_func_and_args[0](update_func_and_args[1], update_func_and_args[2]) predicted_points = {} t = Squad(budget, season=season) # first iteration - fill up from the front for pos in positions: predicted_points[pos] = get_predicted_points(gameweek=gw_range, position=pos, tag=tag, season=season) for pp in predicted_points[pos]: t.add_player(pp[0], gameweek=transfer_gw) if t.num_position[pos] == TOTAL_PER_POSITION[pos]: break # presumably we didn't get a complete squad now excluded_player_ids = [] while not t.is_complete(): # randomly swap out a player and replace with a cheaper one in the # same position player_to_remove = t.players[random.randint(0, len(t.players) - 1)] remove_cost = player_to_remove.purchase_price t.remove_player(player_to_remove.player_id, gameweek=transfer_gw) excluded_player_ids.append(player_to_remove.player_id) for pp in predicted_points[player_to_remove.position]: if (pp[0] not in excluded_player_ids or random.random() < 0.3): # some chance to put player back cp = CandidatePlayer(pp[0], gameweek=transfer_gw, season=season) if cp.purchase_price >= remove_cost: continue else: t.add_player(pp[0], gameweek=transfer_gw) # now try again to fill up the rest of the squad for pos in positions: num_missing = TOTAL_PER_POSITION[pos] - t.num_position[pos] if num_missing == 0: continue for pp in predicted_points[pos]: if pp[0] in excluded_player_ids: continue t.add_player(pp[0], gameweek=transfer_gw) if t.num_position[pos] == TOTAL_PER_POSITION[pos]: break # we have a complete squad score = 0.0 for gw in gw_range: if gw == bench_boost_gw: score += t.get_expected_points( gw, tag, bench_boost=True) * get_discount_factor( gw_range[0], gw) elif gw == triple_captain_gw: score += t.get_expected_points( gw, tag, triple_captain=True) * get_discount_factor( gw_range[0], gw) else: score += t.get_expected_points(gw, tag) * get_discount_factor( gw_range[0], gw) if score > best_score: best_score = score best_squad = t if verbose: print("====================================\n") print(best_squad) print(best_score) return best_squad