Beispiel #1
0
def test_get_discount_factor():
    """
    Discount factor discounts future gameweek score predictions based on the number of gameweeks ahead.
    It uses two discount types based on a discount of 14/15, exponential ({14/15}^{weeks ahead}) and constant (1-{14/15}*weeks ahead)
    """

    assert get_discount_factor(1, 4) == (14 / 15) ** (4 - 1)
    assert get_discount_factor(1, 4, "constant") == 1 - ((1 / 15) * (4 - 1))
    assert get_discount_factor(1, 20, "const") == 0
    assert get_discount_factor(1, 1, "const") == 1
    assert get_discount_factor(1, 1, "exp") == 1
Beispiel #2
0
def save_baseline_score(squad, gameweeks, tag, season=CURRENT_SEASON):
    """When strategies with unused transfers are excluded the baseline strategy will
    normally not be part of the tree. In that case save it first with this function.
    """
    # TODO: use season argument
    root_gw = gameweeks[0]
    strat_dict = {
        "total_score": 0,
        "points_per_gw": {},
        "players_in": {},
        "players_out": {},
        "chips_played": {},
        "root_gw": root_gw,
    }
    for gw in gameweeks:
        gw_score = squad.get_expected_points(gw, tag) * get_discount_factor(
            root_gw, gw)
        strat_dict["total_score"] += gw_score
        strat_dict["points_per_gw"][gw] = gw_score
        strat_dict["players_in"][gw] = []
        strat_dict["players_out"][gw] = []
        strat_dict["chips_played"][gw] = None

    num_gameweeks = len(gameweeks)
    zeros = ("0-" * num_gameweeks)[:-1]
    filename = os.path.join(OUTPUT_DIR,
                            "strategy_{}_{}.json".format(tag, zeros))
    with open(filename, "w") as f:
        json.dump(strat_dict, f)
 def _get_gw_weight(self, **kwargs):
     """Weight for each gameweek (discount weeks further in the future). **kwargs
     passed to get_discount_factor
     """
     return [
         get_discount_factor(min(self.gw_range), gw, **kwargs)
         for gw in self.gw_range
     ]
Beispiel #4
0
def optimize(
    queue,
    pid,
    num_expected_outputs,
    gameweek_range,
    season,
    pred_tag,
    chips_gw_dict,
    max_total_hit=None,
    allow_unused_transfers=True,
    max_transfers=2,
    num_iterations=100,
    updater=None,
    resetter=None,
    profile=False,
):
    """
    Queue is the multiprocessing queue,
    pid is the Process that will execute this func,
    gameweeks will be a list of gameweeks to consider,
    season and prediction_tag are hopefully self-explanatory.

    The rest of the parameters needed for prediction are from the queue.

    Things on the queue will either be "FINISHED", or a tuple:
    (
     num_transfers,
     free_transfers,
     hit_so_far,
     current_team,
     strat_dict,
     strat_id
    )
    """
    while True:
        if queue.qsize() > 0:
            status = queue.get()
        else:
            if is_finished(num_expected_outputs):
                break
            time.sleep(5)
            continue

        # now assume we have set of parameters to do an optimization
        # from the queue.

        # turn on the profiler if requested
        if profile:
            profiler = cProfile.Profile()
            profiler.enable()

        num_transfers, free_transfers, hit_so_far, squad, strat_dict, sid = status
        # num_transfers will be 0, 1, 2, OR 'W' or 'F', OR 'T0', T1', 'T2',
        # OR 'B0', 'B1', or 'B2' (the latter six represent triple captain or
        # bench boost along with 0, 1, or 2 transfers).

        # sid (status id) is just a string e.g. "0-0-2" representing how many
        # transfers to be made in each gameweek.
        # Only exception is the root node, where sid is "starting" - this
        # node only exists to add children to the queue.

        if sid == "starting":
            sid = ""
            depth = 0
            strat_dict["total_score"] = 0
            strat_dict["points_per_gw"] = {}
            strat_dict["players_in"] = {}
            strat_dict["players_out"] = {}
            strat_dict["chips_played"] = {}
            new_squad = squad
            gw = gameweek_range[0] - 1
            strat_dict["root_gw"] = gameweek_range[0]
        else:
            if len(sid) > 0:
                sid += "-"
            sid += str(num_transfers)
            resetter(pid, sid)

            # work out what gameweek we're in and how far down the tree we are.
            depth = len(strat_dict["points_per_gw"])

            # gameweeks from this point in strategy to end of window
            gameweeks = gameweek_range[depth:]

            # upcoming gameweek:
            gw = gameweeks[0]
            root_gw = strat_dict["root_gw"]

            # check whether we're playing a chip this gameweek
            if isinstance(num_transfers, str):
                if num_transfers.startswith("T"):
                    strat_dict["chips_played"][gw] = "triple_captain"
                elif num_transfers.startswith("B"):
                    strat_dict["chips_played"][gw] = "bench_boost"
                elif num_transfers == "W":
                    strat_dict["chips_played"][gw] = "wildcard"
                elif num_transfers == "F":
                    strat_dict["chips_played"][gw] = "free_hit"
            else:
                strat_dict["chips_played"][gw] = None

            # calculate best transfers to make this gameweek (to maximise points across
            # remaining gameweeks)
            num_increments_for_updater = get_num_increments(
                num_transfers, num_iterations)
            increment = 100 / num_increments_for_updater
            new_squad, transfers, points = make_best_transfers(
                num_transfers,
                squad,
                pred_tag,
                gameweeks,
                root_gw,
                season,
                num_iterations,
                (updater, increment, pid),
            )

            points -= calc_points_hit(num_transfers,
                                      free_transfers) * get_discount_factor(
                                          root_gw, gw)
            strat_dict["total_score"] += points
            strat_dict["points_per_gw"][gw] = points

            strat_dict["players_in"][gw] = transfers["in"]
            strat_dict["players_out"][gw] = transfers["out"]
            free_transfers = calc_free_transfers(num_transfers, free_transfers)

            depth += 1

        if depth >= len(gameweek_range):
            with open(
                    os.path.join(OUTPUT_DIR,
                                 "strategy_{}_{}.json".format(pred_tag, sid)),
                    "w",
            ) as outfile:
                json.dump(strat_dict, outfile)
            # call function to update the main progress bar
            updater()

            if profile:
                profiler.dump_stats(f"process_strat_{pred_tag}_{sid}.pstat")

        else:
            # add children to the queue
            strategies = next_week_transfers(
                (free_transfers, hit_so_far, strat_dict),
                max_total_hit=max_total_hit,
                allow_unused_transfers=allow_unused_transfers,
                max_transfers=max_transfers,
                chips=chips_gw_dict[gw + 1],
            )

            for strat in strategies:
                # strat: (num_transfers, free_transfers, hit_so_far)
                num_transfers, free_transfers, hit_so_far = strat

                queue.put((
                    num_transfers,
                    free_transfers,
                    hit_so_far,
                    new_squad,
                    strat_dict,
                    sid,
                ))
Beispiel #5
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 make_optimum_double_transfer(
    squad,
    tag,
    gameweek_range=None,
    root_gw=None,
    season=CURRENT_SEASON,
    update_func_and_args=None,
    bench_boost_gw=None,
    triple_captain_gw=None,
    verbose=False,
):
    """
    If we want to just make two transfers, it's not unfeasible to try all
    possibilities in turn.
    We will order the list of potential subs via the sum of expected points
    over a specified range of gameweeks.
    """
    if not gameweek_range:
        gameweek_range = [NEXT_GAMEWEEK]
        root_gw = NEXT_GAMEWEEK

    transfer_gw = min(gameweek_range)  # the week we're making the transfer
    best_score = 0.0
    best_pid_out, best_pid_in = 0, 0
    ordered_player_lists = {
        pos: get_predicted_points(gameweek=gameweek_range,
                                  position=pos,
                                  tag=tag)
        for pos in ["GK", "DEF", "MID", "FWD"]
    }
    for i in range(len(squad.players) - 1):
        positions_needed = []
        pout_1 = squad.players[i]

        new_squad_remove_1 = fastcopy(squad)
        new_squad_remove_1.remove_player(pout_1.player_id,
                                         gameweek=transfer_gw)
        for j in range(i + 1, len(squad.players)):
            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])

            pout_2 = squad.players[j]
            new_squad_remove_2 = fastcopy(new_squad_remove_1)
            new_squad_remove_2.remove_player(pout_2.player_id,
                                             gameweek=transfer_gw)
            if verbose:
                print("Removing players {} {}".format(i, j))
            # what positions do we need to fill?
            positions_needed = [pout_1.position, pout_2.position]

            # now loop over lists of players and add players back in
            for pin_1 in ordered_player_lists[positions_needed[0]]:
                if pin_1[0].player_id in [pout_1.player_id, pout_2.player_id]:
                    continue  # no point in adding same player back in
                new_squad_add_1 = fastcopy(new_squad_remove_2)
                added_1_ok = new_squad_add_1.add_player(pin_1[0],
                                                        gameweek=transfer_gw)
                if not added_1_ok:
                    continue
                for pin_2 in ordered_player_lists[positions_needed[1]]:
                    new_squad_add_2 = fastcopy(new_squad_add_1)
                    if (pin_2[0] == pin_1[0]
                            or pin_2[0].player_id == pout_1.player_id
                            or pin_2[0].player_id == pout_2.player_id):
                        continue  # no point in adding same player back in
                    added_2_ok = new_squad_add_2.add_player(
                        pin_2[0], gameweek=transfer_gw)
                    if added_2_ok:
                        # calculate the score
                        total_points = 0.0
                        for gw in gameweek_range:
                            if gw == bench_boost_gw:
                                total_points += new_squad_add_2.get_expected_points(
                                    gw, tag,
                                    bench_boost=True) * get_discount_factor(
                                        root_gw, gw)
                            elif gw == triple_captain_gw:
                                total_points += new_squad_add_2.get_expected_points(
                                    gw, tag,
                                    triple_captain=True) * get_discount_factor(
                                        root_gw, gw)
                            else:
                                total_points += new_squad_add_2.get_expected_points(
                                    gw, tag) * get_discount_factor(
                                        root_gw, gw)
                        if total_points > best_score:
                            best_score = total_points
                            best_pid_out = [pout_1.player_id, pout_2.player_id]
                            best_pid_in = [
                                pin_1[0].player_id, pin_2[0].player_id
                            ]
                            best_squad = new_squad_add_2
                        break

    return best_squad, best_pid_out, best_pid_in
def make_best_transfers(
    num_transfers,
    squad,
    tag,
    gameweeks,
    root_gw,
    season,
    num_iter=100,
    update_func_and_args=None,
    algorithm="genetic",
):
    """
    Return a new squad and a dictionary {"in": [player_ids],
                                        "out":[player_ids]}
    """
    transfer_dict = {}
    # deal with triple_captain or free_hit
    triple_captain_gw = None
    bench_boost_gw = None
    if isinstance(num_transfers, str):
        if num_transfers.startswith("T"):
            num_transfers = int(num_transfers[1])
            triple_captain_gw = gameweeks[0]
        elif num_transfers.startswith("B"):
            num_transfers = int(num_transfers[1])
            bench_boost_gw = gameweeks[0]

    if num_transfers == 0:
        # 0 or 'T0' or 'B0' (i.e. zero transfers, possibly with chip)
        new_squad = squad
        transfer_dict = {"in": [], "out": []}
        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])

    elif num_transfers == 1:
        # 1 or 'T1' or 'B1' (i.e. 1 transfer, possibly with chip)
        new_squad, players_out, players_in = make_optimum_single_transfer(
            squad,
            tag,
            gameweeks,
            root_gw,
            season,
            triple_captain_gw=triple_captain_gw,
            bench_boost_gw=bench_boost_gw,
            update_func_and_args=update_func_and_args,
        )
        transfer_dict = {"in": players_in, "out": players_out}

    elif num_transfers == 2:
        # 2 or 'T2' or 'B2' (i.e. 2 transfers, possibly with chip)
        new_squad, players_out, players_in = make_optimum_double_transfer(
            squad,
            tag,
            gameweeks,
            root_gw,
            season,
            triple_captain_gw=triple_captain_gw,
            bench_boost_gw=bench_boost_gw,
            update_func_and_args=update_func_and_args,
        )
        transfer_dict = {"in": players_in, "out": players_out}

    elif num_transfers in ["W", "F"]:
        _out = [p.player_id for p in squad.players]
        budget = get_squad_value(squad)
        if num_transfers == "F":
            gameweeks = [gameweeks[0]
                         ]  # for free hit, only need to optimize this week
        new_squad = make_new_squad(
            gameweeks,
            tag=tag,
            budget=budget,
            season=season,
            verbose=0,
            bench_boost_gw=bench_boost_gw,
            triple_captain_gw=triple_captain_gw,
            algorithm=algorithm,
            population_size=num_iter,
            num_iter=num_iter,
            update_func_and_args=update_func_and_args,
        )
        _in = [p.player_id for p in new_squad.players]
        players_in = [p for p in _in if p not in _out]  # remove duplicates
        players_out = [p for p in _out if p not in _in]  # remove duplicates
        transfer_dict = {"in": players_in, "out": players_out}

    else:
        raise RuntimeError(
            "Unrecognized value for num_transfers: {}".format(num_transfers))

    # get the expected points total for next gameweek
    points = (new_squad.get_expected_points(
        gameweeks[0],
        tag,
        triple_captain=(triple_captain_gw is not None),
        bench_boost=(bench_boost_gw is not None),
    ) * get_discount_factor(root_gw, gameweeks[0]))

    if num_transfers == "F":
        # Free Hit changes don't apply to next gameweek, so return the original squad
        return squad, transfer_dict, points
    else:
        return new_squad, transfer_dict, points
def make_random_transfers(
    squad,
    tag,
    nsubs=1,
    gw_range=None,
    root_gw=None,
    num_iter=1,
    update_func_and_args=None,
    season=CURRENT_SEASON,
    bench_boost_gw=None,
    triple_captain_gw=None,
):
    """
    choose nsubs random players to sub out, and then select players
    using a triangular PDF to preferentially select  the replacements with
    the best expected score to fill their place.
    Do this num_iter times and choose the best total score over gw_range gameweeks.
    """
    best_score = 0.0
    best_squad = None
    best_pid_out = []
    best_pid_in = []
    max_tries = 100
    for _ in range(num_iter):
        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])

        new_squad = fastcopy(squad)

        if not gw_range:
            gw_range = [NEXT_GAMEWEEK]
            root_gw = NEXT_GAMEWEEK

        transfer_gw = min(gw_range)  # the week we're making the transfer
        players_to_remove = []  # this is the index within the squad
        removed_players = []  # this is the player_ids
        # order the players in the squad by predicted_points - least-to-most
        player_list = []
        for p in squad.players:
            p.calc_predicted_points(tag)
            player_list.append(
                (p.player_id, p.predicted_points[tag][gw_range[0]]))
        player_list.sort(key=itemgetter(1), reverse=False)
        while len(players_to_remove) < nsubs:
            index = int(random.triangular(0, len(player_list), 0))
            if index not in players_to_remove:
                players_to_remove.append(index)

        positions_needed = []
        for p in players_to_remove:
            positions_needed.append(squad.players[p].position)
            removed_players.append(squad.players[p].player_id)
            new_squad.remove_player(removed_players[-1], gameweek=transfer_gw)
        predicted_points = {
            pos: get_predicted_points(position=pos, gameweek=gw_range, tag=tag)
            for pos in set(positions_needed)
        }
        complete_squad = False
        added_players = []
        attempt = 0
        while not complete_squad:
            # sample with a triangular PDF - preferentially select players near
            # the start
            added_players = []
            for pos in positions_needed:
                index = int(random.triangular(0, len(predicted_points[pos]),
                                              0))
                pid_to_add = predicted_points[pos][index][0]
                added_ok = new_squad.add_player(pid_to_add,
                                                gameweek=transfer_gw)
                if added_ok:
                    added_players.append(pid_to_add)
            complete_squad = new_squad.is_complete()
            if not complete_squad:
                # try to avoid getting stuck in a loop
                attempt += 1
                if attempt > max_tries:
                    new_squad = fastcopy(squad)
                    break
                # take those players out again.
                for ap in added_players:
                    removed_ok = new_squad.remove_player(ap.player_id,
                                                         gameweek=transfer_gw)
                    if not removed_ok:
                        print("Problem removing {}".format(ap.name))
                added_players = []

        # calculate the score
        total_points = 0.0
        for gw in gw_range:
            if gw == bench_boost_gw:
                total_points += new_squad.get_expected_points(
                    gw, tag, bench_boost=True) * get_discount_factor(
                        root_gw, gw)
            elif gw == triple_captain_gw:
                total_points += new_squad.get_expected_points(
                    gw, tag, triple_captain=True) * get_discount_factor(
                        root_gw, gw)
            else:
                total_points += new_squad.get_expected_points(
                    gw, tag) * get_discount_factor(root_gw, gw)
        if total_points > best_score:
            best_score = total_points
            best_pid_out = removed_players
            best_pid_in = [ap.player_id for ap in added_players]
            best_squad = new_squad
            # end of loop over n_iter
    return best_squad, best_pid_out, best_pid_in
def make_optimum_single_transfer(
    squad,
    tag,
    gameweek_range=None,
    root_gw=None,
    season=CURRENT_SEASON,
    update_func_and_args=None,
    bench_boost_gw=None,
    triple_captain_gw=None,
    verbose=False,
):
    """
    If we want to just make one transfer, it's not unfeasible to try all
    possibilities in turn.


    We will order the list of potential transfers via the sum of
    expected points over a specified range of gameweeks.
    """
    if not gameweek_range:
        gameweek_range = [NEXT_GAMEWEEK]
        root_gw = NEXT_GAMEWEEK

    transfer_gw = min(gameweek_range)  # the week we're making the transfer
    best_score = -1.0
    best_pid_out, best_pid_in = 0, 0
    if verbose:
        print("Creating ordered player lists")
    ordered_player_lists = {
        pos: get_predicted_points(gameweek=gameweek_range,
                                  position=pos,
                                  tag=tag)
        for pos in ["GK", "DEF", "MID", "FWD"]
    }
    for p_out in squad.players:
        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])

        new_squad = fastcopy(squad)
        position = p_out.position
        if verbose:
            print("Removing player {}".format(p_out.player_id))
        new_squad.remove_player(p_out.player_id, gameweek=transfer_gw)
        for p_in in ordered_player_lists[position]:
            if p_in[0].player_id == p_out.player_id:
                continue  # no point in adding the same player back in
            added_ok = new_squad.add_player(p_in[0], gameweek=transfer_gw)
            if added_ok:
                if verbose:
                    print("Added player {}".format(p_in[0].name))
                break
            else:
                if verbose:
                    print("Failed to add {}".format(p_in[0].name))
        total_points = 0.0
        for gw in gameweek_range:
            if gw == bench_boost_gw:
                total_points += new_squad.get_expected_points(
                    gw, tag, bench_boost=True) * get_discount_factor(
                        root_gw, gw)
            elif gw == triple_captain_gw:
                total_points += new_squad.get_expected_points(
                    gw, tag, triple_captain=True) * get_discount_factor(
                        root_gw, gw)
            else:
                total_points += new_squad.get_expected_points(
                    gw, tag) * get_discount_factor(root_gw, gw)
        if total_points > best_score:
            best_score = total_points
            best_pid_out = p_out.player_id
            best_pid_in = p_in[0].player_id
            best_squad = new_squad
    return best_squad, [best_pid_out], [best_pid_in]