BenBrostoff / draftfast

A tool to automate and optimize DraftKings and FanDuel lineup construction.
286 stars 112 forks source link

Multiple Positions on Draftkings Allows duped lineups #157

Open rfr3sh opened 4 years ago

rfr3sh commented 4 years ago

image

I believe since each player is given a unique ID based on their positional eligibility these lineups are being counted as not duplicated

BenBrostoff commented 4 years ago

This makes sense to me - optimizer assigns different player IDs based on position. Do you mind posting the minimum example you need to reproduce so I can have a failing test case to work with? I do think this might not be an easy change. In some cases you want this behavior - consider showdown where a position (captain) actually changes lineup, so two lineups w same players in different positions.

One workaround here is to test whether a lineup has the same player names before appending it to the list that goes into upload. If it does, you could reject the lineup and then ban one of the players in the next run.

On Fri, Jan 10, 2020 at 12:15 AM rfr3sh notifications@github.com wrote:

[image: image] https://user-images.githubusercontent.com/33287084/72127608-39447c80-333e-11ea-8879-e31514f35d6a.png

I believe since each player is given a unique ID based on their positional eligibility these lineups are being counted as not duplicated

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/BenBrostoff/draftfast/issues/157?email_source=notifications&email_token=ABVK35HFLYGAJBM5HKBVLG3Q4777NA5CNFSM4KFCQYBKYY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4IFH23RQ, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVK35DPWURAJILXULCQANDQ4777NANCNFSM4KFCQYBA .

rfr3sh commented 4 years ago

nba.zip

Used these PIDS anre projections

rosters, _ = run_multi( iterations=2, rule_set=rules.DK_NBA_RULE_SET, player_pool=players, verbose=False, )

Should do it

harg0055 commented 4 years ago

Preface - I am VERY new to python so I have no idea if this would work or not...

Would creating a key based on sorting the PIDS work? So after each run, sort the PIDS and concatenate them together. Then compare that against all previous lineups to see if that key exists prior to adding to the list?

EDIT - or could you do something like defining the previous runs optimal projection and then using that as a new upper bound on the next run?

BenBrostoff commented 4 years ago

Yep, this will work @harg0055 - there's still a question though of how to implement if you use this technique. The short story is at optimize time, the optimizer isn't smart enough to know this is a dupe, and the technique you're proposing would be pre-optimize time and not at optimize time.

The optimizer detects existing lineups like this https://github.com/BenBrostoff/draftfast/blob/master/draftfast/optimizer.py#L314-L329 - player.solver_id is a function of position, team and player name - https://github.com/BenBrostoff/draftfast/blob/master/draftfast/orm.py#L319-L321 . I think what will fix this is to just change self.player_to_idx_map.get(player.solver_id) to self.player_to_idx_map.get('{}{}'.format(player.name, player.team)), although I have to test it. I also need to ensure this behavior is overridable and doesn't apply for Showdown (Captain Mode), where position impacts the number of points the player scores.

rfr3sh commented 4 years ago

I think this won't return a value since the keys include the Position? ie Bradley Beal SF WAS: 0 , Bradley Beal SG WAS: 1

If I wasn't planning to use showdown, would this work?

def _add_player_to_idx_maps(self, p: Player, idx: int):
    #self.player_to_idx_map[p.solver_id] = idx
    self.player_to_idx_map[p.name] = idx

i = self.player_to_idx_map.get('{}'.format(player.name))

BenBrostoff commented 4 years ago

I think it should but haven’t tested. NBA doesn’t have this problem as much as NFL but this will break I think if two players have the same name, but it’s a corner case definitely. The way to fix that problem would probably be to assign a unique id or something to each player.

On Sun, Jan 12, 2020 at 2:33 PM rfr3sh notifications@github.com wrote:

I think this won't return a value since the keys include the Position? ie Bradley Beal SF WAS: 0 , Bradley Beal SG WAS: 1

If I wasn't planning to use showdown, would this work?

def _add_player_to_idx_maps(self, p: Player, idx: int):

self.player_to_idx_map[p.solver_id] = idx

self.player_to_idx_map[p.name] = idx

i = self.player_to_idx_map.get('{}'.format(player.name))

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/BenBrostoff/draftfast/issues/157?email_source=notifications&email_token=ABVK35HK43PAXSTXT4EGYODQ5NWBLA5CNFSM4KFCQYBKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIXCHKA#issuecomment-573449128, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABVK35HZJ3H7ADJ4V2TKWZLQ5NWBLANCNFSM4KFCQYBA .

rfr3sh commented 4 years ago

No luck unfortunately, I think because the constraint on the solver is based on player solver ID.

BenBrostoff commented 4 years ago

@rfr3sh Might get some time to put pen to paper on this over long weekend - to be clear I think it's solvable, just need to mess around with it.

deathdonkey-code commented 3 years ago

This is listed as an open item - wondering if anyone found a workaround / solution? I've considered checking for uniqueness of rosters after they are created and updating the number of rosters to generate by only counting the unique ones. But this gets slow for large numbers of teams (10 unique teams might be 100+ rosters that have different permutations of the same lineups)

BenBrostoff commented 3 years ago

@deathdonkey-code That workaround should work, but the solution in https://github.com/BenBrostoff/draftfast/issues/157#issuecomment-573344539 should also hypothetically work (have not tested). PRs welcome! Also the point about keeping showdown behavior the same is important, as in Showdown position impacts points scored through the multiplier.

deathdonkey-code commented 3 years ago

@deathdonkey-code That workaround should work, but the solution in #157 (comment) should also hypothetically work (have not tested). PRs welcome! Also the point about keeping showdown behavior the same is important, as in Showdown position impacts points scored through the multiplier.

Thanks for getting back to me. I am not using Showdown slates but I understand its not a good idea to modify the module and break that ability. I believe my workaround is equivalent to the suggestion you pointed to (I keep a Counter of PIDs for each run and see if it already exists) but it can be quite slow for large numbers of lineups (100 unique lineups might take 300+ runs). Unless you are referring to his second suggestion of putting an upper bound on the next run based on the projected score of the previous run with a duplicate lineup? But I am not too sure how to implement this / I didn't realize there is a way to impose an upper bound on projected score in the current code.

BenBrostoff commented 3 years ago

If you're changing how _set_no_duplicate_lineups works and redefining the .solver_id prop on player we're talking about the same thing - this will significantly slow down the optimizer as you're adding a new constraint (I literally mean solver.Constraint here) on each run.

Some speed stuff I've used in the past (final two trade off accuracy):

Let me know if this helps at all. I do think it's fair to keep this issue open because the current way of preventing duplicates still can product duplicates with different positions.

deathdonkey-code commented 3 years ago

If you're changing how _set_no_duplicate_lineups works and redefining the .solver_id prop on player we're talking about the same thing - this will significantly slow down the optimizer as you're adding a new constraint (I literally mean solver.Constraint here) on each run.

Some speed stuff I've used in the past (final two trade off accuracy):

  • multiprocessing and using multiple procs for generating lineups
  • Removing players from the pool instead of relying on Constraints
  • Randomizing projections

Let me know if this helps at all. I do think it's fair to keep this issue open because the current way of preventing duplicates still can product duplicates with different positions.

I think I am confused now or we are referencing different posts from this thread. I am following harg0055's first idea to check for uniqueness in the rosters after the optimizer completes an iteration. It works fine but slow. I have tried your suggestion to modify the _set_no_duplicate_lineups and .solver_id like so (player_id is a unique identifier from a custom CSV format):

        for player in roster.sorted_players():
            #i = self.player_to_idx_map.get(player.solver_id)
            i = self.player_to_idx_map.get('{}'.format(player.player_id))

and

def solver_id(self):
    #return '{} {} {}'.format(self.name, self.pos, self.team)
    return str(self.player_id)

However when I run this on a sample that works fine without these code changes I immediately get: WARNING: Logging before InitGoogleLogging() is written to STDERR F0307 00:31:46.264128 14264 map_util.h:147] Check failed: collection->insert(value_type(key, data)).second duplicate key: 16564212 Check failure stack trace:

I interpret this error to mean that it is failing because at least one player is duplicated (here, '16564212' refers to the player_id of Kyrie Irving who plays two positions, PG/SG). I have two Kyrie Irving's in my player_pool, one for each eligible position, with the possible_positions variable and multi_position flag set correctly I believe.

BenBrostoff commented 3 years ago

Yep, that's exactly right on why that error happens I think - if you redefine solver_id that way you're going to wind up with a repeated constraint here https://github.com/BenBrostoff/draftfast/blob/master/draftfast/optimizer.py#L49-L51 . I'd need to mess around with it but my recommendation would be to only modify _set_no_duplicate_lineups.

Some more background here - solver_id can't completely change because it's important the optimizer knows what positions a player could use. The way that's implemented right now is to have each Player in the pool unique by position and team. The benefit of this is that for sports like NBA / NFL / MLB (really, anything with multi position), optimizations look at all possible combinations instead of ones where it just takes the first position of a player in the pool.