Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NBA FanDuel code won't generate teams if more than 7 players are listed in Exposures #173

Open
deathdonkey-code opened this issue Mar 7, 2021 · 10 comments

Comments

@deathdonkey-code
Copy link

I am trying to simulate a tournament field using exposure projections I can find online, however generating teams only works if my exposure CSV has 7 or fewer entries. I'll attach a couple CSVs that share player names to illustrate (I had to save them as .txt to get github to accept them, but they should be .csv). I use the output_DK.csv for the player projections, and the min_max_FD_short.csv for lineup exposures (it is "short" because ideally there would be a lineup projection for every player but I can demonstrate the problem with this shorter version). The lineup exposures CSV has 8 entries, running the run_multi function on this code will run without error but produce zero teams. If I simply go into the exposures CSV and remove one player (leaving 7), it will run correctly and generate teams that match the exposures.

8 players in exposure CSV:

Total iterations: 100
[Finished in 1.3s]

7 players in exposure CSV:

Total iterations: 100
Roster Exposure:
+----------+--------------------------+------+---------+--------+--------------------+-----------+--------------------+--------------------+
| Position | Player | Team | Matchup | Salary | Projection | # Lineups | Min | Max |
+----------+--------------------------+------+---------+--------+--------------------+-----------+--------------------+--------------------+
| PF | MARKIEFF MORRIS | LAL | None | 3,700 | 24.2248 | 96 | | |
| SG | TALEN HORTON-TUCKER | LAL | None | 3,400 | 27.075466666666667 | 96 | | |
| PG | LUKA DONCIC | DAL | None | 10,900 | 56.298333333333325 | 81 | | |
| C | MONTREZL HARRELL | LAL | None | 5,500 | 34.988733333333336 | 78 | | |
| SF | BRANDON INGRAM | NO | None | 8,400 | 40.814299999999996 | 59 | | |
| PF | CHRIS BOUCHER | TOR | None | 6,400 | 36.212766666666674 | 39 | 26.240000000000002 | 39.360002 |
| SF | EVAN FOURNIER | ORL | None | 6,700 | 33.0477 | 37 | | |
| PG | DAMIAN LILLARD | POR | None | 10,600 | 54.40630000000001 | 31 | 21.200001 | 31.8 |
| SG | KENTAVIOUS CALDWELL-POPE | LAL | None | 3,600 | 22.239333333333335 | 30 | | |
| SG | JAMES HARDEN | BKN | None | 11,000 | 59.22056666666666 | 29 | 19.919999999999998 | 29.880000000000003 |
| PF | ZION WILLIAMSON | NO | None | 9,100 | 43.96246666666667 | 29 | | |
| SF | GORDON HAYWARD | CHA | None | 7,300 | 35.14796666666667 | 29 | | |
| SF | NORMAN POWELL | TOR | None | 6,700 | 35.579033333333335 | 28 | 18.8 | 28.199999999999996 |
| PG | DENNIS SCHRODER | LAL | None | 5,900 | 33.0496 | 27 | 26.639998 | 39.96 |
| PG | STEPHEN CURRY | GS | None | 9,900 | 50.51603333333333 | 27 | | |
| SF | KELLY OUBRE | GS | None | 7,000 | 34.187666666666665 | 26 | | |
| PG | KYLE LOWRY | TOR | None | 8,100 | 42.484366666666666 | 23 | | |
| C | ENES KANTER | POR | None | 7,500 | 36.179966666666665 | 22 | 21.68 | 32.519999999999996 |
| SG | TERENCE DAVIS | TOR | None | 3,400 | 20.656266666666664 | 20 | 19.36 | 29.04 |
| SF | STANLEY JOHNSON | TOR | None | 3,000 | 16.82173333333333 | 17 | | |
| SG | DEANDRE BEMBRY | TOR | None | 3,700 | 22.1033 | 10 | | |
| PF | NICOLAS CLAXTON | BKN | None | 3,300 | 19.030933333333333 | 9 | | |
| SG | VICTOR OLADIPO | HOU | None | 7,900 | 38.8349 | 8 | | |
| PF | KRISTAPS PORZINGIS | DAL | None | 8,200 | 39.137933333333336 | 7 | | |
| SG | SHAI GILGEOUS-ALEXANDER | OKC | None | 8,800 | 42.60153333333333 | 6 | | |
| PG | KYRIE IRVING | BKN | None | 9,400 | 46.928566666666676 | 6 | | |
| PF | DOMANTAS SABONIS | IND | None | 9,700 | 45.441966666666666 | 5 | | |
| PF | ROBERT COVINGTON | POR | None | 6,000 | 29.439700000000002 | 5 | | |
| PG | DEAARON FOX | SAC | None | 9,000 | 44.85606666666666 | 4 | | |
| PF | PATRICK WILLIAMS | CHI | None | 4,800 | 24.197699999999998 | 3 | | |
| PF | DRAYMOND GREEN | GS | None | 7,400 | 34.568266666666666 | 2 | | |
| PF | TOBIAS HARRIS | PHI | None | 7,900 | 35.9186 | 2 | | |
| SF | DORIAN FINNEY-SMITH | DAL | None | 4,500 | 21.046566666666667 | 1 | | |
| SG | ZACH LAVINE | CHI | None | 9,500 | 44.93283333333333 | 1 | | |
| PF | JAVALE MCGEE | CLE | None | 3,500 | 17.976866666666666 | 1 | | |
| PG | ALEX CARUSO | LAL | None | 4,000 | 22.32343333333333 | 1 | | |
| PF | JOHN COLLINS | ATL | None | 6,900 | 32.214333333333336 | 1 | | |
| SF | JOSH HART | NO | None | 4,900 | 23.226466666666667 | 1 | | |
| SF | HARRISON BARNES | SAC | None | 7,000 | 31.9265 | 1 | | |
| SF | DENZEL VALENTINE | CHI | None | 3,800 | 18.676933333333334 | 1 | | |
| PF | NEMANJA BJELICA | SAC | None | 4,000 | 20.699933333333334 | 1 | | |
+----------+--------------------------+------+---------+--------+--------------------+-----------+--------------------+--------------------+
[Finished in 9.5s]

output_FD.txt
min_max_FD_short.txt

@BenBrostoff
Copy link
Owner

Can you add verbose=True on run_multi and post the output in the case it doesn't work? Also does removing any arbitrary row resolve the issue, or just a specific row?

@deathdonkey-code
Copy link
Author

deathdonkey-code commented Mar 7, 2021

It appears its more complex than just "7 players are ok, 8 aren't", it does indeed depend on which players I remove. I think this is because several of the players in the example I gave here are from the same team (TOR) but unsure. Maybe its just failing to find valid configurations that use players from the same team? But then how would this work with using lineup projections for the entire slate? Here I simplified the min_max CSV and it still fails:

name,min,max
DENNIS SCHRODER,0.1,0.7
CHRIS BOUCHER,0.1,0.9
ENES KANTER,0.1,0.9
DAMIAN LILLARD,0.1,0.9
JAMES HARDEN,0.1,0.9
TERENCE DAVIS,0.1,0.9
KYLE LOWRY,0.1,0.9

Building 100 tournament teams...

No solution found.
Try adjusting your query by taking away constraints.

OPTIMIZER CONSTRAINTS:

Min teams: 2

LINEUP CONSTRAINTS:

None

PLAYER POOL SETTINGS:

None

PLAYER COUNT: 192

Total iterations: 100
[Finished in 1.3s]

@BenBrostoff
Copy link
Owner

I'm guessing it's the locked and banned settings (which I can add to the debug output - it really should be in there). Do you mind printing locked and banned players in the failure case? What could be happening is that the optimizer is unable to satisfy required locks in addition to all the other constraints it has to satisfy. Sorry for the continued info requests - it can be tricky figuring out why the opto can't meet certain criteria unless you have all the variables.

@deathdonkey-code
Copy link
Author

deathdonkey-code commented Mar 7, 2021 via email

@BenBrostoff
Copy link
Owner

Right - but run_multi will automatically lock and ban behind the scenes.

@deathdonkey-code
Copy link
Author

I printed Locked and Banned lists within the Run() function like this:

LINEUP CONSTRAINTS:

{}

LOCKED LIST:

{}

BANNED LIST:

{}

PLAYER POOL SETTINGS:

{}

PLAYER COUNT: {}
        '''.format(
                optimizer_settings,
                constraints, constraints._locked, constraints._banned,
                player_settings,
                len(players or [])
            )
        )

but they are just empty sets:

No solution found.
Try adjusting your query by taking away constraints.

OPTIMIZER CONSTRAINTS:

Min teams: 2

LINEUP CONSTRAINTS:

None

LOCKED LIST:

set()

BANNED LIST:

set()

PLAYER POOL SETTINGS:

None

PLAYER COUNT: 192

Total iterations: 100
[Finished in 1.4s]

@BenBrostoff
Copy link
Owner

Alrighty, one more test and then I can pull down and try to repro - what about exposure_dict?

Check out https://github.com/BenBrostoff/draftfast/blob/master/draftfast/optimizer.py#L38-L39 in the source. _is_banned and _is_locked both look at the values in this dictionary to determine locking and banning.

All run_multi does is just run run the number of iterations you specify with different params, so if I know the values of the params when it effectively runs, I can debug it.

@deathdonkey-code
Copy link
Author

Here you go - it looks like its locking every player in the exposures CSV?

No solution found.
Try adjusting your query by taking away constraints.

OPTIMIZER CONSTRAINTS:

Min teams: 2

LINEUP CONSTRAINTS:

None

EXPOSURE DICT:

{'banned': [], 'locked': ['DENNIS SCHRODER', 'CHRIS BOUCHER', 'ENES KANTER', 'DAMIAN LILLARD', 'JAMES HARDEN', 'TERENCE DAVIS', 'KYLE LOWRY']}

PLAYER POOL SETTINGS:

None

PLAYER COUNT: 192

Total iterations: 100
[Finished in 1.3s]

@BenBrostoff
Copy link
Owner

Yep, that's exactly the issue. run_multi doesn't look like it is selective at all with who it locks on the first iteration:

def get_exposure_args_deterministic(exposures, existing_rosters,
exposure_bounds) -> dict:
banned = []
locked = []
for bound in exposure_bounds:
name = bound['name']
total = float(len(existing_rosters) + 1)
min_lines = bound['min'] * total
max_lines = math.floor(bound['max'] * total)
lineups = exposures.get(name, 0)
if lineups < min_lines:
# TODO - downsize locked so solution is not impossible
locked.append(name)
elif lineups >= max_lines:
banned.append(name)
return {
'banned': banned,
'locked': locked,
}

You can actually see in the TODO that this is a known issue. I think an easy-ish fix is to allow this to be settable - in your case you could max out the number of locks at 4 to be conservative. It may take me a little bit to push out a fix but in the meantime this workaround I think should do it:

MAX_LOCKS = 4 # or some number you think is reasonable
if lineups < min_lines or len(locked) < MAX_LOCKS:
  # TODO - downsize locked so solution is not impossible
   locked.append(name)

@deathdonkey-code
Copy link
Author

That almost works - I think this is correct?

        MAX_LOCKS = 3 # or some number you think is reasonable
        if lineups < min_lines and len(locked) < MAX_LOCKS:
            # TODO - downsize locked so solution is not impossible
            locked.append(name)

the only problem I've noticed so far is that the code doesn't check for the locked players to make sense - so 3 centers can be locked and then it won't find a valid team and break. For FanDuel it looks like just using MAX_LOCKS = 1 is fine, for DraftKings I can use a higher MAX_LOCKS and it works because it can put those players in the UTIL / G / F slots, but it lowers the average projection a bit and makes some funny lineups. Still, seems to generate lists of teams that attempts to match the exposures and that is a lot of progress, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants