pydfs-lineup-optimizer

repository·master·Indexed 19 days ago

https://github.com/dimakudosh/pydfs-lineup-optimizer

A tool for creating optimal daily fantasy sports (DFS) lineups. It supports multiple sports (including NFL, NBA, NHL, MLB, and more) and various sites such as DraftKings, FanDuel, FantasyDraft, and Yahoo. The library allows users to load player data via CSV, apply specific site rules and constraints, and utilize different solvers like PuLP or MIP for performance optimization.

Tokens
8.3K
Snippets
33
Records
37
Agent score
65%

What's inside pydfs-lineup-optimizer

  1. Change exposure calculation strategy

    master

    By default, the optimizer calculates exposure based on the total number of lineups requested (n). This means high-projection players might be selected in the first few lineups and then hit their exposure limit immediately.

    To spread players out more evenly, use the AfterEachExposureStrategy. This calculates exposure after every single lineup generated, which may result in unordered lineups but better distribution.

    from pydfs_lineup_optimizer import AfterEachExposureStrategy
    
    # Use the strategy to spread out player exposure
    lineups = optimizer.optimize(n=10, max_exposure=0.3, exposure_strategy=AfterEachExposureStrategy)
  2. Configure player exposure

    master

    Exposure controls how frequently a player appears across the generated lineups.

    Methods of setting exposure:

    1. CSV Columns: Add Max Exposure and Min Exposure columns to your player CSV.
    2. Player Object: Set player.max_exposure and player.min_exposure directly on the Player instance.
    3. Global Limit: Pass max_exposure to the optimize() method to set a cap for all players.

    Priority and Behavior:

    • Individual player max_exposure has higher priority than the global max_exposure passed to optimize().
    • Exposure percentages are rounded to ceil.
    • If a player is locked, exposure still applies (e.g., a locked player with 50% max exposure will appear in 50% of the generated lineups).
    # Set individual exposure
    player = optimizer.player_pool.get_player_by_name('Tom Brady')
    player.max_exposure = 0.5
    player.min_exposure = 0.3
    
    # Set global exposure for all players
    lineups = optimizer.optimize(n=10, max_exposure=0.3)
  3. Change the default PuLP solver

    master

    By default, the optimizer uses the pulp library with a free but slow default solver. You can switch to a different solver supported by pulp (like GLPK) by subclassing PuLPSolver and defining the LP_SOLVER attribute. This is useful for improving solving speed.

    Note: You must have the solver (e.g., GLPK) installed on your system before configuring it in Python.

    # install glpk: https://www.gnu.org/software/glpk/
    from pulp import GLPK_CMD  # You can find names of other solvers in pulp docs
    from pydfs_lineup_optimizer.solvers import PuLPSolver
    
    class GLPKPuLPSolver(PuLPSolver):
        LP_SOLVER = GLPK_CMD(path='<path to installed glpk solver>', msg=False)
    
    optimizer = get_optimizer(Site.DRAFTKINGS, Sport.BASEBALL, solver=GLPKPuLPSolver)
  4. Initialize the optimizer and load players

    master

    To start using pydfs-lineup-optimizer, use get_optimizer to create an optimizer instance by specifying a Site and a Sport. You can then load players using one of two methods:

    1. From a CSV file: Use load_players_from_csv(path). The CSV must match the export format of the specified DFS site. Note that this method raises NotImplementedError for the FanBall site.
    2. From a list of Player objects: Use optimizer.player_pool.load_players(players) where players is a list of Player instances.

    Once players are loaded, call optimizer.optimize(n=X) to generate X optimal lineups. This method returns a generator.

    from pydfs_lineup_optimizer import get_optimizer, Site, Sport
    
    # 1. Initialize
    optimizer = get_optimizer(Site.FANDUEL, Sport.BASKETBALL)
    
    # 2. Load players
    optimizer.load_players_from_csv("path_to_csv")
    
    # 3. Optimize
    lineups = optimizer.optimize(n=10)
    for lineup in lineups:
        print(lineup.players)
  5. Perform Late-Swap re-optimization

    master

    The optimize_lineups method allows you to re-optimize existing lineups (e.g., when games have started). This is currently supported for DRAFTKINGS and FANDUEL.

    Workflow:

    1. Load players via load_players_from_csv.
    2. Load existing lineups via load_lineups_from_csv.
    3. Call optimize_lineups(lineups).

    Note for FanDuel users: FanDuel does not provide game start time information. You must manually mark games as started by setting game.game_started = True for the relevant games in optimizer.games before calling optimize_lineups.

    # DraftKings Example
    optimizer = get_optimizer(Site.DRAFTKINGS, Sport.BASKETBALL)
    optimizer.load_players_from_csv("dk_nba.csv")
    lineups = optimizer.load_lineups_from_csv("dk_nba.csv")
    new_lineups = optimizer.optimize_lineups(lineups)
    
    # FanDuel Example (Manual game start marking)
    locked_teams = {'DET', 'MIA'}
    for game in optimizer.games:
        if game.home_team in locked_teams or game.away_team in locked_teams:
            game.game_started = True
    new_lineups = optimizer.optimize_lineups(lineups)
  6. Create custom settings for a new sport or DFS site

    master

    To support a new sport or DFS site not currently covered by the library, you must define a custom settings class. This is done by inheriting from BaseSettings and specifying the rules and constraints for your specific use case.

    Key attributes to define in your BaseSettings subclass include:

    • site: The name of the DFS site.
    • sport: The name of the sport.
    • budget: The total budget allowed for a lineup.
    • max_from_one_team: Constraint for the maximum number of players allowed from a single team.
    • min_teams: Constraint for the minimum number of different teams required in a lineup.
    • min_games: Constraint for the minimum number of different games required in a lineup.
    • csv_importer: The importer to use if players are loaded via the load_players_from_csv method.
    • positions: A list of LineupPosition objects defining the lineup structure.
    from pydfs_lineup_optimizer import LineupOptimizer
    from pydfs_lineup_optimizer.settings import BaseSettings, LineupPosition
    
    class CustomSettings(BaseSettings):
        site = 'Site Name'
        sport = 'Sport Name'
        budget = 100  # budget you want to use
        max_from_one_team = None  # if needed
        min_teams = None  # if needed
        min_games = None  # if needed
        csv_importer = None  # If player will be imported using load_players_from_csv method
        positions = [  # list of all positions
            LineupPosition('G', ('PG', 'SG')),  # First argument is name of position in lineup,
                                                # second is allowed player positions for this lineup position
        ]
    
    optimizer = LineupOptimizer(CustomSettings)
  7. Use the MIP solver for faster optimization

    master

    The library supports the mip library, which can be faster in certain scenarios, particularly when running on PyPy. To use it, you must first install the package via pip:

    pip install mip

    Then, pass MIPSolver to the get_optimizer function.

    from pydfs_lineup_optimizer.solvers.mip_solver import MIPSolver
    
    optimizer = get_optimizer(Site.DRAFTKINGS, Sport.BASEBALL, solver=MIPSolver)
  8. Decrease solving complexity by filtering players

    master

    If the optimization process is too slow (common in MLB or NFL when the player pool exceeds 100 players), you can reduce complexity by filtering the player_pool.

    Effective strategies include:

    • Removing players with low fppg (Fantasy Points Per Game).
    • Removing players with low efficiency (points/salary).
    • Removing players with high salary.
    • Excluding specific teams using exclude_teams.
    optimizer = get_optimizer(Site.DRAFTKINGS, Sport.BASEBALL)
    optimizer.load_players_from_csv('dk_mlb.csv')
    optimizer.player_pool.add_filters(
        PlayerFilter(from_value=5),  # use only players with points >= 5
        PlayerFilter(from_value=2, filter_by='efficiency'),  # and efficiency(points/salary) >= 2
        PlayerFilter(from_value=2000, filter_by='salary'),  # and salary >= 3000
    )
    optimizer.player_pool.exclude_teams(['Seattle Mariners'])
    for lineup in optimizer.optimize(100):
        print(lineup)
  9. Optimize lineups for Yahoo NBA fantasy

    master

    To find optimal lineups, use get_optimizer with the appropriate Site and Sport enums. You can load player data from a CSV file using load_players_from_csv and then call optimize(n) to retrieve the top n lineups.

    from pydfs_lineup_optimizer import Site, Sport, get_optimizer
    
    optimizer = get_optimizer(Site.YAHOO, Sport.BASKETBALL)
    optimizer.load_players_from_csv("yahoo-NBA.csv")
    for lineup in optimizer.optimize(10):
        print(lineup)
  10. Configure Game Stacks

    master

    Use GameStack with add_stack to group players from the same game, regardless of their team. This allows for stacking players from both sides of a matchup.

    optimizer.add_stack(GameStack(3))  # stack 3 players from the same game
    optimizer.add_stack(GameStack(5, min_from_team=2))  # stack 5 players from same game, min 2 from one team
  11. Restrict players from the same team

    master

    Use restrict_positions_for_same_team to prevent certain combinations of positions from being on the same team in a lineup (e.g., preventing two RBs from the same team). It accepts tuples containing two positions.

    optimizer.restrict_positions_for_same_team(('RB', 'RB'))
    optimizer.restrict_positions_for_same_team(('QB', 'DST'), ('RB', 'DST'))