Пример #1
0
def test_order_substitutes():
    t = Squad()

    class MockPlayer:
        def __init__(self, points, is_starting, name, squad):
            self.predicted_points = {0: {0: points}}
            self.is_starting = is_starting
            self.name = name
            self.squad = squad
            self.sub_position = None

    players = [
        MockPlayer(10, False, "a", "A"),
        MockPlayer(9, False, "b", "B"),
        MockPlayer(8, False, "c", "C"),
        MockPlayer(11, True, "d", "D"),
    ]

    t.players = players
    t.order_substitutes(0, 0)

    expected_sub_positions = [0, 1, 2, None]
    for player, sub_position in zip(players, expected_sub_positions):
        assert player.sub_position == sub_position

    # test the logic that's use in __repr__ as well
    subs = [p for p in t.players if not p.is_starting]
    subs.sort(key=lambda p: p.sub_position)
    expected_names = ["a", "b", "c"]
    for player, expected_name in zip(subs, expected_names):
        assert player.name == expected_name
Пример #2
0
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
Пример #3
0
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
Пример #4
0
def test_empty_squad(fill_players):
    """
    shouldn't be able to estimate points with
    no players.
    """
    t = Squad()
    with pytest.raises(RuntimeError) as errmsg:
        t.get_expected_points(1, "dummy")
    assert str(errmsg.value) == "Squad is incomplete"
Пример #5
0
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
Пример #6
0
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 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]
Пример #8
0
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
Пример #9
0
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")
Пример #10
0
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 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
Пример #12
0
def test_get_expected_points():
    t = Squad()

    class MockPlayer:
        def __init__(
            self,
            name,
            squad,
            position,
            points,
            is_starting,
            is_captain,
            is_vice_captain,
        ):
            self.name = name
            self.squad = squad
            self.position = position
            self.predicted_points = {0: {0: points}}
            self.is_starting = is_starting
            self.sub_position = None
            self.is_captain = is_captain
            self.is_vice_captain = is_vice_captain

        def calc_predicted_points(self, tag):
            pass

    # 3 pts captain (x2 = 6pts, or x3 = 9pts for TC)
    # 2 pts starters
    # 1 pt subs
    players = [
        MockPlayer("a", "A", "GK", 2, True, False, False),
        MockPlayer("b", "B", "GK", 1, False, False, False),  # sub 1
        MockPlayer("c", "C", "DEF", 2, True, False, False),
        MockPlayer("d", "D", "DEF", 2, True, False, False),
        MockPlayer("e", "E", "DEF", 2, True, False, False),
        MockPlayer("f", "F", "DEF", 1, False, False, False),  # sub 2
        MockPlayer("g", "G", "DEF", 1, False, False, False),  # sub 3
        MockPlayer("h", "H", "MID", 2, True, False, False),
        MockPlayer("i", "I", "MID", 2, True, False, False),
        MockPlayer("j", "J", "MID", 2, True, False, False),
        MockPlayer("k", "K", "MID", 2, True, False, False),
        MockPlayer("l", "L", "MID", 1, False, False, False),  # sub 4
        MockPlayer("m", "M", "FWD", 3, True, True, False),  # captain
        MockPlayer("n", "N", "FWD", 2, True, False, True),  # vice-captain
        MockPlayer("o", "O", "FWD", 2, True, False, False),
    ]

    t.players = players
    t.num_position = {"GK": 2, "DEF": 5, "MID": 5, "FWD": 3}

    # no chips
    assert t.get_expected_points(0, 0) == 26
    # bench boost
    assert t.get_expected_points(0, 0, bench_boost=True) == 30
    # triple captain
    assert t.get_expected_points(0, 0, triple_captain=True) == 29
Пример #13
0
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
Пример #14
0
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
Пример #15
0
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}
Пример #16
0
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
Пример #17
0
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 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
Пример #19
0
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
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