def test_count_expected_outputs_no_chips_zero_hit(): count = count_expected_outputs( 3, free_transfers=1, max_total_hit=0, next_gw=1, max_transfers=2, chip_gw_dict={}, ) assert count == 13
def test_count_expected_outputs_no_chips_2ft_no_unused(): count = count_expected_outputs( 3, free_transfers=2, max_total_hit=None, allow_unused_transfers=False, next_gw=1, max_transfers=2, ) assert count == 14
def test_count_expected_outputs_no_chips_no_constraints(): # No constraints or chips, expect 3**num_gameweeks strategies count = count_expected_outputs( 3, free_transfers=1, max_total_hit=None, allow_unused_transfers=True, next_gw=1, max_transfers=2, chip_gw_dict={}, ) assert count == 3**3
def count_expected_play_wildcard_no_constraints(): count = count_expected_outputs( 2, free_transfers=1, max_total_hit=None, allow_unused_transfers=True, next_gw=1, max_transfers=2, chip_gw_dict={ 1: {"chip_to_play": "wildcard", "chips_allowed": []}, 2: {"chip_to_play": None, "chips_allowed": []}, }, ) assert count == 3
def count_expected_play_free_hit_no_unused(): count = count_expected_outputs( 2, free_transfers=2, max_total_hit=None, allow_unused_transfers=False, next_gw=1, max_transfers=2, chip_gw_dict={ 1: {"chip_to_play": "free_hit", "chips_allowed": []}, 2: {"chip_to_play": None, "chips_allowed": []}, }, ) assert count == 3
def count_expected_bench_boost_allowed_no_constraints(): count = count_expected_outputs( 2, free_transfers=1, max_total_hit=None, allow_unused_transfers=True, next_gw=1, max_transfers=2, chip_gw_dict={ 1: {"chips_allowed": ["bench_boost"]}, 2: {"chips_allowed": ["bench_boost"]}, 3: {"chips_allowed": ["bench_boost"]}, }, ) assert count == 27
def test_count_expected_wildcard_allowed_no_constraints(): count = count_expected_outputs( 2, free_transfers=1, max_total_hit=None, allow_unused_transfers=True, next_gw=1, max_transfers=2, chip_gw_dict={ 1: {"chips_allowed": ["wildcard"]}, 2: {"chips_allowed": ["wildcard"]}, 3: {"chips_allowed": ["wildcard"]}, }, ) assert count == 15
def is_finished( num_gameweeks, wildcard=False, free_hit=False, triple_captain=False, bench_boost=False, ): """ Count the number of json files in the output directory, and see if the number matches the final expected number, calculated based on number of gameweeks, and cards played Return True if output files are all there, False otherwise. """ final_expected_num = count_expected_outputs(0, num_gameweeks, wildcard, free_hit, triple_captain, bench_boost) # count the json files in the output dir json_count = len(os.listdir(OUTPUT_DIR)) if json_count == final_expected_num: return True return False
def run_optimization( gameweeks, tag, season=CURRENT_SEASON, fpl_team_id=None, chip_gameweeks={}, num_free_transfers=None, max_total_hit=None, allow_unused_transfers=True, max_transfers=2, num_iterations=100, num_thread=4, profile=False, ): """ This is the actual main function that sets up the multiprocessing and calls the optimize function for every num_transfers/gameweek combination, to find the best strategy. The chip-related variables e.g. wildcard_week are -1 if that chip is not to be played, 0 for 'play it any week', or the gw in which it should be played. """ discord_webhook = fetcher.DISCORD_WEBHOOK if fpl_team_id is None: fpl_team_id = fetcher.FPL_TEAM_ID # give the user the option to login fetcher.login() print("Running optimization with fpl_team_id {}".format(fpl_team_id)) # How many free transfers are we starting with? if not num_free_transfers: num_free_transfers = get_free_transfers(fpl_team_id, gameweeks[0], apifetcher=fetcher) # create the output directory for temporary json files # giving the points prediction for each strategy shutil.rmtree(OUTPUT_DIR, ignore_errors=True) os.makedirs(OUTPUT_DIR, exist_ok=True) # first get a baseline prediction # baseline_score, baseline_dict = get_baseline_prediction(num_weeks_ahead, tag) # Get a dict of what chips we definitely or possibly will play # in each gw chip_gw_dict = construct_chip_dict(gameweeks, chip_gameweeks) # create a queue that we will add nodes to, and some processes to take # things off it squeue = CustomQueue() procs = [] # create one progress bar for each thread progress_bars = [] for i in range(num_thread): progress_bars.append(tqdm(total=100)) # number of nodes in tree will be something like 3^num_weeks unless we allow # a "chip" such as wildcard or free hit, in which case it gets complicated num_weeks = len(gameweeks) num_expected_outputs = count_expected_outputs( num_weeks, next_gw=gameweeks[0], free_transfers=num_free_transfers, max_total_hit=max_total_hit, allow_unused_transfers=allow_unused_transfers, max_transfers=max_transfers, chip_gw_dict=chip_gw_dict, ) total_progress = tqdm(total=num_expected_outputs, desc="Total progress") # functions to be passed to subprocess to update or reset progress bars def reset_progress(index, strategy_string): if strategy_string == "DONE": progress_bars[index].close() else: progress_bars[index].n = 0 progress_bars[index].desc = "strategy: " + strategy_string progress_bars[index].refresh() def update_progress(increment=1, index=None): if index is None: # outer progress bar nfiles = len(os.listdir(OUTPUT_DIR)) total_progress.n = nfiles total_progress.refresh() if nfiles == num_expected_outputs: total_progress.close() for pb in progress_bars: pb.close() else: progress_bars[index].update(increment) progress_bars[index].refresh() use_api = fetcher.logged_in starting_squad = get_starting_squad(fpl_team_id=fpl_team_id, use_api=use_api, apifetcher=fetcher) if not allow_unused_transfers and (num_weeks > 1 or (num_weeks == 1 and num_free_transfers == 2)): # if we are excluding unused transfers the tree may not include the baseline # strategy. In those cases quickly calculate and save it here first. save_baseline_score(starting_squad, gameweeks, tag, season=season) update_progress() # Add Processes to run the the target 'optimize' function. # This target function needs to know: # num_transfers # current_team (list of player_ids) # transfer_dict {"gw":<gw>,"in":[],"out":[]} # total_score # num_free_transfers # budget for i in range(num_thread): processor = Process( target=optimize, args=( squeue, i, num_expected_outputs, gameweeks, season, tag, chip_gw_dict, max_total_hit, allow_unused_transfers, max_transfers, num_iterations, update_progress, reset_progress, profile, ), ) processor.daemon = True processor.start() procs.append(processor) # add starting node to the queue squeue.put((0, num_free_transfers, 0, starting_squad, {}, "starting")) for i, p in enumerate(procs): progress_bars[i].close() progress_bars[i] = None p.join() # find the best from all the strategies tried best_strategy = find_best_strat_from_json(tag) baseline_score = find_baseline_score_from_json(tag, num_weeks) fill_suggestion_table(baseline_score, best_strategy, season, fpl_team_id) for i in range(len(procs)): print("\n") print("\n====================================\n") print("Strategy for Team ID: {}".format(fpl_team_id)) print("Baseline score: {}".format(baseline_score)) print("Best score: {}".format(best_strategy["total_score"])) print_strat(best_strategy) t = print_team_for_next_gw(best_strategy, fpl_team_id) # If a valid discord webhook URL has been stored # in env variables, send a webhook message if discord_webhook != "MISSING_ID": # Use regex to check the discord webhook url is correctly formatted if re.match( r"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-zA-Z0-9_-]+)$", discord_webhook, ): # create a formatted team lineup message for the discord webhook lineup_strings = [ "__Strategy for Team ID: **{}**__".format(fpl_team_id), "Baseline score: *{}*".format(int(baseline_score)), "Best score: *{}*".format(int(best_strategy["total_score"])), "\n__starting 11__", ] for position in ["GK", "DEF", "MID", "FWD"]: lineup_strings.append("== **{}** ==\n```".format(position)) for p in t.players: if p.position == position and p.is_starting: player_line = "{} ({})".format(p.name, p.team) if p.is_captain: player_line += "(C)" elif p.is_vice_captain: player_line += "(VC)" lineup_strings.append(player_line) lineup_strings.append("```\n") lineup_strings.append("__subs__") lineup_strings.append("```") subs = [p for p in t.players if not p.is_starting] subs.sort(key=lambda p: p.sub_position) for p in subs: lineup_strings.append("{} ({})".format(p.name, p.team)) lineup_strings.append("```\n") # generate a discord embed json and send to webhook payload = discord_payload(best_strategy, lineup_strings) result = requests.post(discord_webhook, json=payload) if 200 <= result.status_code < 300: print( f"Discord webhook sent, status code: {result.status_code}") else: print( f"Not sent with {result.status_code}, response:\n{result.json()}" ) else: print("Warning: Discord webhook url is malformed!\n", discord_webhook) shutil.rmtree(OUTPUT_DIR, ignore_errors=True) return
def run_optimization( gameweeks, tag, season=CURRENT_SEASON, fpl_team_id=None, chip_gameweeks={}, num_free_transfers=None, max_total_hit=None, allow_unused_transfers=True, max_transfers=2, num_iterations=100, num_thread=4, profile=False, ): """ This is the actual main function that sets up the multiprocessing and calls the optimize function for every num_transfers/gameweek combination, to find the best strategy. The chip-related variables e.g. wildcard_week are -1 if that chip is not to be played, 0 for 'play it any week', or the gw in which it should be played. """ if fpl_team_id is None: fpl_team_id = fetcher.FPL_TEAM_ID print("Running optimization with fpl_team_id {}".format(fpl_team_id)) # How many free transfers are we starting with? if not num_free_transfers: num_free_transfers = get_free_transfers(gameweeks[0], fpl_team_id) # create the output directory for temporary json files # giving the points prediction for each strategy shutil.rmtree(OUTPUT_DIR, ignore_errors=True) os.makedirs(OUTPUT_DIR, exist_ok=True) # first get a baseline prediction # baseline_score, baseline_dict = get_baseline_prediction(num_weeks_ahead, tag) # Get a dict of what chips we definitely or possibly will play # in each gw chip_gw_dict = construct_chip_dict(gameweeks, chip_gameweeks) # create a queue that we will add nodes to, and some processes to take # things off it squeue = CustomQueue() procs = [] # create one progress bar for each thread progress_bars = [] for i in range(num_thread): progress_bars.append(tqdm(total=100)) # number of nodes in tree will be something like 3^num_weeks unless we allow # a "chip" such as wildcard or free hit, in which case it gets complicated num_weeks = len(gameweeks) num_expected_outputs = count_expected_outputs( num_weeks, next_gw=gameweeks[0], free_transfers=num_free_transfers, max_total_hit=max_total_hit, allow_unused_transfers=allow_unused_transfers, max_transfers=max_transfers, chip_gw_dict=chip_gw_dict, ) total_progress = tqdm(total=num_expected_outputs, desc="Total progress") # functions to be passed to subprocess to update or reset progress bars def reset_progress(index, strategy_string): if strategy_string == "DONE": progress_bars[index].close() else: progress_bars[index].n = 0 progress_bars[index].desc = "strategy: " + strategy_string progress_bars[index].refresh() def update_progress(increment=1, index=None): if index is None: # outer progress bar nfiles = len(os.listdir(OUTPUT_DIR)) total_progress.n = nfiles total_progress.refresh() if nfiles == num_expected_outputs: total_progress.close() for pb in progress_bars: pb.close() else: progress_bars[index].update(increment) progress_bars[index].refresh() starting_squad = get_starting_squad(fpl_team_id=fpl_team_id) if not allow_unused_transfers and ( num_weeks > 1 or (num_weeks == 1 and num_free_transfers == 2) ): # if we are excluding unused transfers the tree may not include the baseline # strategy. In those cases quickly calculate and save it here first. save_baseline_score(starting_squad, gameweeks, tag, season=season) update_progress() # Add Processes to run the the target 'optimize' function. # This target function needs to know: # num_transfers # current_team (list of player_ids) # transfer_dict {"gw":<gw>,"in":[],"out":[]} # total_score # num_free_transfers # budget for i in range(num_thread): processor = Process( target=optimize, args=( squeue, i, num_expected_outputs, gameweeks, season, tag, chip_gw_dict, max_total_hit, allow_unused_transfers, max_transfers, num_iterations, update_progress, reset_progress, profile, ), ) processor.daemon = True processor.start() procs.append(processor) # add starting node to the queue squeue.put((0, num_free_transfers, 0, starting_squad, {}, "starting")) for i, p in enumerate(procs): progress_bars[i].close() progress_bars[i] = None p.join() # find the best from all the strategies tried best_strategy = find_best_strat_from_json(tag) baseline_score = find_baseline_score_from_json(tag, num_weeks) fill_suggestion_table(baseline_score, best_strategy, season, fpl_team_id) for i in range(len(procs)): print("\n") print("\n====================================\n") print("Strategy for Team ID: {}".format(fpl_team_id)) print("Baseline score: {}".format(baseline_score)) print("Best score: {}".format(best_strategy["total_score"])) print_strat(best_strategy) print_team_for_next_gw(best_strategy, fpl_team_id) shutil.rmtree(OUTPUT_DIR, ignore_errors=True) return
def run_optimization( gameweeks, tag, season=CURRENT_SEASON, wildcard=False, free_hit=False, triple_captain=False, bench_boost=False, num_free_transfers=None, num_iterations=100, num_thread=4, profile=False, ): """ This is the actual main function that sets up the multiprocessing and calls the optimize function for every num_transfers/gameweek combination, to find the best strategy. """ ## How many free transfers are we starting with? if not num_free_transfers: num_free_transfers = get_free_transfers(gameweeks[0]) ## create the output directory for temporary json files ## giving the points prediction for each strategy shutil.rmtree(OUTPUT_DIR, ignore_errors=True) os.makedirs(OUTPUT_DIR, exist_ok=True) ## first get a baseline prediction # baseline_score, baseline_dict = get_baseline_prediction(num_weeks_ahead, tag) ## create a queue that we will add nodes to, and some processes to take ## things off it squeue = CustomQueue() procs = [] ## create one progress bar for each thread progress_bars = [] for i in range(num_thread): progress_bars.append(tqdm(total=100)) ## number of nodes in tree will be something like 3^num_weeks unless we allow ## a "card" such as wildcard or free hit, in which case it gets complicated num_weeks = len(gameweeks) expected_num = count_expected_outputs(0, num_weeks, wildcard, free_hit, triple_captain, bench_boost) total_progress = tqdm(total=expected_num, desc="Total progress") ## functions to be passed to subprocess to update or reset progress bars def reset_progress(index, strategy_string): if strategy_string == "DONE": progress_bars[index].close() else: progress_bars[index].n = 0 progress_bars[index].desc = "strategy: " + strategy_string progress_bars[index].refresh() def update_progress(increment=1, index=None): if index == None: ## outer progress bar nfiles = len(os.listdir(OUTPUT_DIR)) total_progress.n = nfiles total_progress.refresh() if nfiles == expected_num: total_progress.close() for pb in progress_bars: pb.close() else: progress_bars[index].update(increment) progress_bars[index].refresh() ## Add Processes to run the the target 'optimize' function. ## This target function needs to know: ## num_transfers ## current_team (list of player_ids) ## transfer_dict {"gw":<gw>,"in":[],"out":[]} ## total_score ## num_free_transfers ## budget cards = [] if wildcard: cards.append("wildcard") if free_hit: cards.append("free_hit") if triple_captain: cards.append("triple_captain") if bench_boost: cards.append("bench_boost") for i in range(num_thread): processor = Process( target=optimize, args=( squeue, i, gameweeks, season, tag, cards, num_iterations, update_progress, reset_progress, profile, ), ) processor.daemon = True processor.start() procs.append(processor) ## add starting node to the queue starting_squad = get_starting_squad() squeue.put((0, num_free_transfers, starting_squad, {}, "starting")) for i, p in enumerate(procs): progress_bars[i].close() progress_bars[i] = None p.join() ### find the best from all the strategies tried best_strategy = find_best_strat_from_json(tag) baseline_score = find_baseline_score_from_json(tag, num_weeks) fill_suggestion_table(baseline_score, best_strategy, season) for i in range(len(procs)): print("\n") print("\n====================================\n") print("Baseline score: {}".format(baseline_score)) print("Best score: {}".format(best_strategy["total_score"])) print_strat(best_strategy) print_team_for_next_gw(best_strategy) shutil.rmtree(OUTPUT_DIR, ignore_errors=True) return