Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion airsenal/framework/data_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,9 @@ def get_num_free_transfers(self, fpl_team_id=None):
Requires login
"""
squad_data = self.get_current_squad_data(fpl_team_id)
return squad_data["transfers"]["limit"]
return max(
0, squad_data["transfers"]["limit"] - squad_data["transfers"]["made"]
)

def get_current_bank(self, fpl_team_id=None):
"""
Expand Down
17 changes: 10 additions & 7 deletions airsenal/framework/optimization_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def next_week_transfers(
"""Given a previous strategy and some optimisation constraints, determine the valid
options for the number of transfers (or chip played) in the following gameweek.

strat is a tuple (free_transfers, hit_so_far, strat_dict)
strat is a tuple (free_transfers, total_points_hit, strat_dict)
strat_dict must have key chips_played, which is a dict indexed by gameweek with
possible values None, "wildcard", "free_hit", "bench_boost" or triple_captain"

Expand All @@ -446,7 +446,9 @@ def next_week_transfers(
max_free_transfers - maximum number of free transfers saved in the game rules
(2 before 2024/25, 5 from 2024/25 season)

Returns (new_transfers, new_ft_available, new_points_hits) tuples.
Returns (new_transfers, new_ft_available, total_points_hit, hit_this_gw) tuples.
- total_points_hit is the total points hit so far including this gw
- hit_this_gw is the points hit incurred this gameweek
"""
# check that the 'chips' dict we are given makes sense:
if chips is None:
Expand Down Expand Up @@ -524,17 +526,18 @@ def next_week_transfers(
if allow_triple_captain:
new_transfers += [f"T{nt}" for nt in ft_choices]

new_points_hits = [
hit_so_far + calc_points_hit(nt, ft_available) for nt in new_transfers
]
hit_this_gw = [calc_points_hit(nt, ft_available) for nt in new_transfers]
total_points_hit = [hit_so_far + hit for hit in hit_this_gw]
new_ft_available = [
calc_free_transfers(nt, ft_available, max_free_transfers)
for nt in new_transfers
]

# return list of (num_transfers, free_transfers, hit_so_far) tuples for each new
# strategy
return list(zip(new_transfers, new_ft_available, new_points_hits, strict=False))
return list(
zip(new_transfers, new_ft_available, total_points_hit, hit_this_gw, strict=True)
)


def count_expected_outputs(
Expand Down Expand Up @@ -590,7 +593,7 @@ def count_expected_outputs(
max_free_transfers=max_free_transfers,
)

for n_transfers, new_free_transfers, new_hit in possibilities:
for n_transfers, new_free_transfers, new_hit, _ in possibilities:
# make a copy of the strategy up to this point, then add on this gw
new_dict = deepcopy(s[2])

Expand Down
8 changes: 5 additions & 3 deletions airsenal/framework/squad.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ def __repr__(self):
"""
Display the squad
"""
print("\n=== starting 11 ===\n")
for position in ["GK", "DEF", "MID", "FWD"]:
print(f"\n== {position} ==\n")
for p in self.players:
Expand All @@ -72,7 +71,7 @@ def __repr__(self):
elif p.is_vice_captain:
player_line += "(VC)"
print(player_line)
print("\n=== subs ===\n")
print("\n=== Subs ===\n")

subs = [p for p in self.players if not p.is_starting]
subs.sort(key=lambda p: p.sub_position)
Expand Down Expand Up @@ -111,6 +110,10 @@ def add_player(
player.season = self.season
if price is not None:
player.purchase_price = price

if self.verbose:
print(f"Adding player {p}")

if player.position == "MNG":
warnings.warn(
f"Skipped adding manager {player}, assistant manager not implemented."
Expand Down Expand Up @@ -540,7 +543,6 @@ def get_current_squad_from_api(
player = get_player_from_api_id(p["element"])
if not player:
continue
print(f"Adding player {player}")
squad.add_player(
player,
price=p["purchase_price"],
Expand Down
84 changes: 49 additions & 35 deletions airsenal/scripts/fill_transfersuggestion_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import regex as re
import requests
from prettytable import PrettyTable
from tqdm import TqdmWarning, tqdm

from airsenal.framework.env import AIRSENAL_HOME
Expand All @@ -37,7 +38,6 @@
from airsenal.framework.optimization_transfers import make_best_transfers
from airsenal.framework.optimization_utils import (
MAX_FREE_TRANSFERS,
calc_points_hit,
check_tag_valid,
count_expected_outputs,
fill_suggestion_table,
Expand Down Expand Up @@ -131,7 +131,15 @@ def optimize(
else:
profiler = None

num_transfers, free_transfers, hit_so_far, squad, strat_dict, sid = status
(
num_transfers,
free_transfers,
hit_so_far,
hit_this_gw,
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).
Expand Down Expand Up @@ -203,18 +211,16 @@ def optimize(
(updater, increment, pid) if updater is not None else None,
)

points_hit = calc_points_hit(num_transfers, free_transfers)
discount_factor = get_discount_factor(root_gw, gw)
points -= points_hit * discount_factor
points -= hit_this_gw * discount_factor
strat_dict["total_score"] += points
strat_dict["points_per_gw"][gw] = points
strat_dict["free_transfers"][gw] = free_transfers
strat_dict["num_transfers"][gw] = num_transfers
strat_dict["points_hit"][gw] = points_hit
strat_dict["points_hit"][gw] = hit_this_gw
strat_dict["discount_factor"][gw] = discount_factor
strat_dict["players_in"][gw] = transfers["in"]
strat_dict["players_out"][gw] = transfers["out"]

depth += 1

if depth >= len(gameweek_range):
Expand All @@ -240,16 +246,15 @@ def optimize(
chips=chips_gw_dict[gw + 1],
max_free_transfers=max_free_transfers,
)

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

queue.put(
(
num_transfers,
free_transfers,
hit_so_far,
hit_this_gw,
new_squad,
strat_dict,
sid,
Expand Down Expand Up @@ -314,25 +319,22 @@ def print_strat(strat: dict) -> None:
"""
gameweeks_as_str = strat["points_per_gw"].keys()
gameweeks_as_int = sorted([int(gw) for gw in gameweeks_as_str])
print(" ===============================================")
print(" ========= Optimum strategy ====================")
print(" ===============================================")

for gw in gameweeks_as_int:
print(f"\n =========== Gameweek {gw} ================\n")
print(f"Chips played: {strat['chips_played'][str(gw)]}\n")
print("Players in:\t\t\tPlayers out:")
print("-----------\t\t\t------------")
for i in range(len(strat["players_in"][str(gw)])):
pin = get_player_name(strat["players_in"][str(gw)][i])
pout = get_player_name(strat["players_out"][str(gw)][i])
subs = (
f"{pin}\t\t\t{pout}"
if pin is not None and len(pin) < 20
else f"{pin}\t\t{pout}"
)
print(subs)
print("\n==========================")
print(f" Total score: {int(strat['total_score'])} \n")
print(f"\nGAMEWEEK {gw}:\n")
table = PrettyTable(["Players Out", "Players In"])
table.align = "l"
for pin, pout in zip(
strat["players_in"][str(gw)], strat["players_out"][str(gw)], strict=True
):
table.add_row([get_player_name(pout), get_player_name(pin)])
if len(table.rows) == 0:
table.add_row(["None", "None"])
print(table)
print(f"Chip Played: {strat['chips_played'][str(gw)]}")
print(f"Points Hit: {strat['points_hit'][str(gw)]}")
pred_pts = strat["points_per_gw"][str(gw)] / strat["discount_factor"][str(gw)]
print(f"Predicted Score: {pred_pts:.1f}pts")


def discord_payload(strat: dict, lineup: list[str]) -> dict:
Expand Down Expand Up @@ -382,21 +384,29 @@ def discord_payload(strat: dict, lineup: list[str]) -> dict:


def print_team_for_next_gw(
strat: dict, season: str = CURRENT_SEASON, fpl_team_id: int | None = None
strat: dict,
season: str = CURRENT_SEASON,
fpl_team_id: int | None = None,
use_api: bool = False,
) -> Squad:
"""
Display the team (inc. subs and captain) for the next gameweek
"""
gameweeks_as_str = strat["points_per_gw"].keys()
gameweeks_as_int = sorted([int(gw) for gw in gameweeks_as_str])
next_gw = gameweeks_as_int[0]
t = get_starting_squad(next_gw=next_gw, season=season, fpl_team_id=fpl_team_id)
t = get_starting_squad(
next_gw=next_gw, season=season, fpl_team_id=fpl_team_id, use_api=use_api
)
for pidout in strat["players_out"][str(next_gw)]:
t.remove_player(pidout)
for pidin in strat["players_in"][str(next_gw)]:
t.add_player(pidin)
tag = get_latest_prediction_tag(season=season)
t.get_expected_points(next_gw, tag)
print("\n--------------------------------")
print(f"Starting Lineup for Gameweek {next_gw}:")
print("--------------------------------")
print(t)
return t

Expand Down Expand Up @@ -488,6 +498,7 @@ def run_optimization(
apifetcher=fetcher,
is_replay=is_replay,
)
print(f"Starting with {num_free_transfers} free transfers")

# create the output directory for temporary json files
# giving the points prediction for each strategy
Expand Down Expand Up @@ -591,7 +602,7 @@ def update_progress(increment=1, index=None):
processor.start()
procs.append(processor)
# add starting node to the queue
squeue.put((0, num_free_transfers, 0, starting_squad, {}, "starting"))
squeue.put((0, num_free_transfers, 0, 0, starting_squad, {}, "starting"))

for i, p in enumerate(procs):
progress_bars[i].close()
Expand All @@ -610,17 +621,20 @@ def update_progress(increment=1, index=None):

for _ in range(len(procs)):
print("\n")
print("\n====================================\n")
print(f"Strategy for Team ID: {fpl_team_id}")
print(f"Baseline score: {baseline_score}")

if best_strategy is None:
msg = "Failed to find a strategy!"
raise ValueError(msg)

print(f"Best score: {best_strategy['total_score']}")
print("================")
print("OPTIMUM STRATEGY")
print("================\n")
print(f"Team ID: {fpl_team_id}")
print(f"Baseline Score: {baseline_score:.1f}pts")
print(f"Score with Transfers: {best_strategy['total_score']:.1f}pts")
print_strat(best_strategy)
best_squad = print_team_for_next_gw(
best_strategy, season=season, fpl_team_id=fpl_team_id
best_strategy, season=season, fpl_team_id=fpl_team_id, use_api=use_api
)

# If a valid discord webhook URL has been stored
Expand Down
Loading