James McCool commited on
Commit
255a179
·
1 Parent(s): 283a8e1

Refactor tab structure in app.py: remove the 'Late Swap' tab and adjust related logic to streamline user interface and improve overall functionality in portfolio management.

Browse files
Files changed (1) hide show
  1. app.py +443 -443
app.py CHANGED
@@ -21,7 +21,7 @@ freq_format = {'Finish_percentile': '{:.2%}', 'Lineup Edge': '{:.2%}', 'Win%': '
21
  player_wrong_names_mlb = ['Enrique Hernandez']
22
  player_right_names_mlb = ['Kike Hernandez']
23
 
24
- tab1, tab2, tab3 = st.tabs(["Data Load", "Late Swap", "Manage Portfolio"])
25
  with tab1:
26
  if st.button('Clear data', key='reset1'):
27
  st.session_state.clear()
@@ -171,519 +171,519 @@ with tab1:
171
 
172
  st.session_state['origin_portfolio'] = st.session_state['portfolio'].copy()
173
 
174
- with tab2:
175
- if st.button('Clear data', key='reset2'):
176
- st.session_state.clear()
177
 
178
- if 'portfolio' in st.session_state and 'projections_df' in st.session_state:
179
 
180
- optimized_df = None
181
 
182
- map_dict = {
183
- 'pos_map': dict(zip(st.session_state['projections_df']['player_names'],
184
- st.session_state['projections_df']['position'])),
185
- 'salary_map': dict(zip(st.session_state['projections_df']['player_names'],
186
- st.session_state['projections_df']['salary'])),
187
- 'proj_map': dict(zip(st.session_state['projections_df']['player_names'],
188
- st.session_state['projections_df']['median'])),
189
- 'own_map': dict(zip(st.session_state['projections_df']['player_names'],
190
- st.session_state['projections_df']['ownership'])),
191
- 'team_map': dict(zip(st.session_state['projections_df']['player_names'],
192
- st.session_state['projections_df']['team']))
193
- }
194
- # Calculate new stats for optimized lineups
195
- st.session_state['portfolio']['salary'] = st.session_state['portfolio'].apply(
196
- lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
197
- )
198
- st.session_state['portfolio']['median'] = st.session_state['portfolio'].apply(
199
- lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
200
- )
201
 
202
- st.session_state['portfolio']['Own'] = st.session_state['portfolio'].apply(
203
- lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
204
- )
205
 
206
- options_container = st.container()
207
- with options_container:
208
- col1, col2, col3, col4, col5, col6 = st.columns(6)
209
- with col1:
210
- curr_site_var = st.selectbox("Select your current site", options=['DraftKings', 'FanDuel'])
211
- with col2:
212
- curr_sport_var = st.selectbox("Select your current sport", options=['NBA', 'MLB', 'NFL', 'NHL', 'MMA'])
213
- with col3:
214
- swap_var = st.multiselect("Select late swap strategy", options=['Optimize', 'Increase volatility', 'Decrease volatility'])
215
- with col4:
216
- remove_teams_var = st.multiselect("What teams have already played?", options=st.session_state['projections_df']['team'].unique())
217
- with col5:
218
- winners_var = st.multiselect("Are there any players doing exceptionally well?", options=st.session_state['projections_df']['player_names'].unique(), max_selections=3)
219
- with col6:
220
- losers_var = st.multiselect("Are there any players doing exceptionally poorly?", options=st.session_state['projections_df']['player_names'].unique(), max_selections=3)
221
- if st.button('Clear Late Swap'):
222
- if 'optimized_df' in st.session_state:
223
- del st.session_state['optimized_df']
224
 
225
- map_dict = {
226
- 'pos_map': dict(zip(st.session_state['projections_df']['player_names'],
227
- st.session_state['projections_df']['position'])),
228
- 'salary_map': dict(zip(st.session_state['projections_df']['player_names'],
229
- st.session_state['projections_df']['salary'])),
230
- 'proj_map': dict(zip(st.session_state['projections_df']['player_names'],
231
- st.session_state['projections_df']['median'])),
232
- 'own_map': dict(zip(st.session_state['projections_df']['player_names'],
233
- st.session_state['projections_df']['ownership'])),
234
- 'team_map': dict(zip(st.session_state['projections_df']['player_names'],
235
- st.session_state['projections_df']['team']))
236
- }
237
- # Calculate new stats for optimized lineups
238
- st.session_state['portfolio']['salary'] = st.session_state['portfolio'].apply(
239
- lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
240
- )
241
- st.session_state['portfolio']['median'] = st.session_state['portfolio'].apply(
242
- lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
243
- )
244
- st.session_state['portfolio']['Own'] = st.session_state['portfolio'].apply(
245
- lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
246
- )
247
 
248
- if st.button('Run Late Swap'):
249
- st.session_state['portfolio'] = st.session_state['portfolio'].drop(columns=['salary', 'median', 'Own'])
250
- if curr_sport_var == 'NBA':
251
- if curr_site_var == 'DraftKings':
252
- st.session_state['portfolio'] = st.session_state['portfolio'].set_axis(['PG', 'SG', 'SF', 'PF', 'C', 'G', 'F', 'UTIL'], axis=1)
253
- else:
254
- st.session_state['portfolio'] = st.session_state['portfolio'].set_axis(['PG', 'PG', 'SG', 'SG', 'SF', 'SF', 'PF', 'PF', 'C'], axis=1)
255
 
256
- # Define roster position rules
257
- if curr_site_var == 'DraftKings':
258
- position_rules = {
259
- 'PG': ['PG'],
260
- 'SG': ['SG'],
261
- 'SF': ['SF'],
262
- 'PF': ['PF'],
263
- 'C': ['C'],
264
- 'G': ['PG', 'SG'],
265
- 'F': ['SF', 'PF'],
266
- 'UTIL': ['PG', 'SG', 'SF', 'PF', 'C']
267
- }
268
- else:
269
- position_rules = {
270
- 'PG': ['PG'],
271
- 'SG': ['SG'],
272
- 'SF': ['SF'],
273
- 'PF': ['PF'],
274
- 'C': ['C'],
275
- }
276
- # Create position groups from projections data
277
- position_groups = {}
278
- for _, player in st.session_state['projections_df'].iterrows():
279
- positions = player['position'].split('/')
280
- for pos in positions:
281
- if pos not in position_groups:
282
- position_groups[pos] = []
283
- position_groups[pos].append({
284
- 'player_names': player['player_names'],
285
- 'salary': player['salary'],
286
- 'median': player['median'],
287
- 'ownership': player['ownership'],
288
- 'positions': positions # Store all eligible positions
289
- })
290
 
291
- def optimize_lineup(row):
292
- current_lineup = []
293
- total_salary = 0
294
- if curr_site_var == 'DraftKings':
295
- salary_cap = 50000
296
- else:
297
- salary_cap = 60000
298
- used_players = set()
299
 
300
- # Convert row to dictionary with roster positions
301
- roster = {}
302
- for col, player in zip(row.index, row):
303
- if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
304
- roster[col] = {
305
- 'name': player,
306
- 'position': map_dict['pos_map'].get(player, '').split('/'),
307
- 'team': map_dict['team_map'].get(player, ''),
308
- 'salary': map_dict['salary_map'].get(player, 0),
309
- 'median': map_dict['proj_map'].get(player, 0),
310
- 'ownership': map_dict['own_map'].get(player, 0)
311
- }
312
- total_salary += roster[col]['salary']
313
- used_players.add(player)
314
 
315
- # Optimize each roster position in random order
316
- roster_positions = list(roster.items())
317
- random.shuffle(roster_positions)
318
 
319
- for roster_pos, current in roster_positions:
320
- # Skip optimization for players from removed teams
321
- if current['team'] in remove_teams_var:
322
- continue
323
 
324
- valid_positions = position_rules[roster_pos]
325
- better_options = []
326
 
327
- # Find valid replacements for this roster position
328
- for pos in valid_positions:
329
- if pos in position_groups:
330
- pos_options = [
331
- p for p in position_groups[pos]
332
- if p['median'] > current['median']
333
- and (total_salary - current['salary'] + p['salary']) <= salary_cap
334
- and p['player_names'] not in used_players
335
- and any(valid_pos in p['positions'] for valid_pos in valid_positions)
336
- and map_dict['team_map'].get(p['player_names']) not in remove_teams_var # Check team restriction
337
- ]
338
- better_options.extend(pos_options)
339
 
340
- if better_options:
341
- # Remove duplicates
342
- better_options = {opt['player_names']: opt for opt in better_options}.values()
343
 
344
- # Sort by median projection and take the best one
345
- best_replacement = max(better_options, key=lambda x: x['median'])
346
 
347
- # Update the lineup and tracking variables
348
- used_players.remove(current['name'])
349
- used_players.add(best_replacement['player_names'])
350
- total_salary = total_salary - current['salary'] + best_replacement['salary']
351
- roster[roster_pos] = {
352
- 'name': best_replacement['player_names'],
353
- 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
354
- 'team': map_dict['team_map'][best_replacement['player_names']],
355
- 'salary': best_replacement['salary'],
356
- 'median': best_replacement['median'],
357
- 'ownership': best_replacement['ownership']
358
- }
359
 
360
- # Return optimized lineup maintaining original column order
361
- return [roster[pos]['name'] for pos in row.index if pos in roster]
362
 
363
- def optimize_lineup_winners(row):
364
- current_lineup = []
365
- total_salary = 0
366
- if curr_site_var == 'DraftKings':
367
- salary_cap = 50000
368
- else:
369
- salary_cap = 60000
370
- used_players = set()
371
 
372
- # Check if any winners are in the lineup and count them
373
- winners_in_lineup = sum(1 for player in row if player in winners_var)
374
- changes_needed = min(winners_in_lineup, 3) if winners_in_lineup > 0 else 0
375
- changes_made = 0
376
 
377
- # Convert row to dictionary with roster positions
378
- roster = {}
379
- for col, player in zip(row.index, row):
380
- if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
381
- roster[col] = {
382
- 'name': player,
383
- 'position': map_dict['pos_map'].get(player, '').split('/'),
384
- 'team': map_dict['team_map'].get(player, ''),
385
- 'salary': map_dict['salary_map'].get(player, 0),
386
- 'median': map_dict['proj_map'].get(player, 0),
387
- 'ownership': map_dict['own_map'].get(player, 0)
388
- }
389
- total_salary += roster[col]['salary']
390
- used_players.add(player)
391
 
392
- # Only proceed with ownership-based optimization if we have winners in the lineup
393
- if changes_needed > 0:
394
- # Randomize the order of positions to optimize
395
- roster_positions = list(roster.items())
396
- random.shuffle(roster_positions)
397
 
398
- for roster_pos, current in roster_positions:
399
- # Stop if we've made enough changes
400
- if changes_made >= changes_needed:
401
- break
402
 
403
- # Skip optimization for players from removed teams or if the current player is a winner
404
- if current['team'] in remove_teams_var or current['name'] in winners_var:
405
- continue
406
 
407
- valid_positions = list(position_rules[roster_pos])
408
- random.shuffle(valid_positions)
409
- better_options = []
410
 
411
- # Find valid replacements with higher ownership
412
- for pos in valid_positions:
413
- if pos in position_groups:
414
- pos_options = [
415
- p for p in position_groups[pos]
416
- if p['ownership'] > current['ownership']
417
- and p['median'] >= current['median'] - 3
418
- and (total_salary - current['salary'] + p['salary']) <= salary_cap
419
- and (total_salary - current['salary'] + p['salary']) >= salary_cap - 1000
420
- and p['player_names'] not in used_players
421
- and any(valid_pos in p['positions'] for valid_pos in valid_positions)
422
- and map_dict['team_map'].get(p['player_names']) not in remove_teams_var
423
- ]
424
- better_options.extend(pos_options)
425
 
426
- if better_options:
427
- # Remove duplicates
428
- better_options = {opt['player_names']: opt for opt in better_options}.values()
429
 
430
- # Sort by ownership and take the highest owned option
431
- best_replacement = max(better_options, key=lambda x: x['ownership'])
432
 
433
- # Update the lineup and tracking variables
434
- used_players.remove(current['name'])
435
- used_players.add(best_replacement['player_names'])
436
- total_salary = total_salary - current['salary'] + best_replacement['salary']
437
- roster[roster_pos] = {
438
- 'name': best_replacement['player_names'],
439
- 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
440
- 'team': map_dict['team_map'][best_replacement['player_names']],
441
- 'salary': best_replacement['salary'],
442
- 'median': best_replacement['median'],
443
- 'ownership': best_replacement['ownership']
444
- }
445
- changes_made += 1
446
 
447
- # Return optimized lineup maintaining original column order
448
- return [roster[pos]['name'] for pos in row.index if pos in roster]
449
 
450
- def optimize_lineup_losers(row):
451
- current_lineup = []
452
- total_salary = 0
453
- if curr_site_var == 'DraftKings':
454
- salary_cap = 50000
455
- else:
456
- salary_cap = 60000
457
- used_players = set()
458
 
459
- # Check if any winners are in the lineup and count them
460
- losers_in_lineup = sum(1 for player in row if player in losers_var)
461
- changes_needed = min(losers_in_lineup, 3) if losers_in_lineup > 0 else 0
462
- changes_made = 0
463
 
464
- # Convert row to dictionary with roster positions
465
- roster = {}
466
- for col, player in zip(row.index, row):
467
- if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
468
- roster[col] = {
469
- 'name': player,
470
- 'position': map_dict['pos_map'].get(player, '').split('/'),
471
- 'team': map_dict['team_map'].get(player, ''),
472
- 'salary': map_dict['salary_map'].get(player, 0),
473
- 'median': map_dict['proj_map'].get(player, 0),
474
- 'ownership': map_dict['own_map'].get(player, 0)
475
- }
476
- total_salary += roster[col]['salary']
477
- used_players.add(player)
478
 
479
- # Only proceed with ownership-based optimization if we have winners in the lineup
480
- if changes_needed > 0:
481
- # Randomize the order of positions to optimize
482
- roster_positions = list(roster.items())
483
- random.shuffle(roster_positions)
484
 
485
- for roster_pos, current in roster_positions:
486
- # Stop if we've made enough changes
487
- if changes_made >= changes_needed:
488
- break
489
 
490
- # Skip optimization for players from removed teams or if the current player is a winner
491
- if current['team'] in remove_teams_var or current['name'] in losers_var:
492
- continue
493
 
494
- valid_positions = list(position_rules[roster_pos])
495
- random.shuffle(valid_positions)
496
- better_options = []
497
 
498
- # Find valid replacements with higher ownership
499
- for pos in valid_positions:
500
- if pos in position_groups:
501
- pos_options = [
502
- p for p in position_groups[pos]
503
- if p['ownership'] < current['ownership']
504
- and p['median'] >= current['median'] - 3
505
- and (total_salary - current['salary'] + p['salary']) <= salary_cap
506
- and (total_salary - current['salary'] + p['salary']) >= salary_cap - 1000
507
- and p['player_names'] not in used_players
508
- and any(valid_pos in p['positions'] for valid_pos in valid_positions)
509
- and map_dict['team_map'].get(p['player_names']) not in remove_teams_var
510
- ]
511
- better_options.extend(pos_options)
512
 
513
- if better_options:
514
- # Remove duplicates
515
- better_options = {opt['player_names']: opt for opt in better_options}.values()
516
 
517
- # Sort by ownership and take the highest owned option
518
- best_replacement = max(better_options, key=lambda x: x['ownership'])
519
 
520
- # Update the lineup and tracking variables
521
- used_players.remove(current['name'])
522
- used_players.add(best_replacement['player_names'])
523
- total_salary = total_salary - current['salary'] + best_replacement['salary']
524
- roster[roster_pos] = {
525
- 'name': best_replacement['player_names'],
526
- 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
527
- 'team': map_dict['team_map'][best_replacement['player_names']],
528
- 'salary': best_replacement['salary'],
529
- 'median': best_replacement['median'],
530
- 'ownership': best_replacement['ownership']
531
- }
532
- changes_made += 1
533
 
534
- # Return optimized lineup maintaining original column order
535
- return [roster[pos]['name'] for pos in row.index if pos in roster]
536
 
537
- # Create a progress bar
538
- progress_bar = st.progress(0)
539
- status_text = st.empty()
540
 
541
- # Process each lineup
542
- optimized_lineups = []
543
- total_lineups = len(st.session_state['portfolio'])
544
 
545
- for idx, row in st.session_state['portfolio'].iterrows():
546
- # First optimization pass
547
- first_pass = optimize_lineup(row)
548
- first_pass_series = pd.Series(first_pass, index=row.index)
549
 
550
- second_pass = optimize_lineup(first_pass_series)
551
- second_pass_series = pd.Series(second_pass, index=row.index)
552
 
553
- third_pass = optimize_lineup(second_pass_series)
554
- third_pass_series = pd.Series(third_pass, index=row.index)
555
 
556
- fourth_pass = optimize_lineup(third_pass_series)
557
- fourth_pass_series = pd.Series(fourth_pass, index=row.index)
558
 
559
- fifth_pass = optimize_lineup(fourth_pass_series)
560
- fifth_pass_series = pd.Series(fifth_pass, index=row.index)
561
 
562
- # Second optimization pass
563
- final_lineup = optimize_lineup(fifth_pass_series)
564
- optimized_lineups.append(final_lineup)
565
 
566
- if 'Optimize' in swap_var:
567
- progress = (idx + 1) / total_lineups
568
- progress_bar.progress(progress)
569
- status_text.text(f'Optimizing Lineups {idx + 1} of {total_lineups}')
570
- else:
571
- pass
572
 
573
- # Create new dataframe with optimized lineups
574
- if 'Optimize' in swap_var:
575
- st.session_state['optimized_df_medians'] = pd.DataFrame(optimized_lineups, columns=st.session_state['portfolio'].columns)
576
- else:
577
- st.session_state['optimized_df_medians'] = st.session_state['portfolio']
578
 
579
- # Create a progress bar
580
- progress_bar_winners = st.progress(0)
581
- status_text_winners = st.empty()
582
 
583
- # Process each lineup
584
- optimized_lineups_winners = []
585
- total_lineups = len(st.session_state['optimized_df_medians'])
586
 
587
- for idx, row in st.session_state['optimized_df_medians'].iterrows():
588
 
589
- final_lineup = optimize_lineup_winners(row)
590
- optimized_lineups_winners.append(final_lineup)
591
 
592
- if 'Decrease volatility' in swap_var:
593
- progress_winners = (idx + 1) / total_lineups
594
- progress_bar_winners.progress(progress_winners)
595
- status_text_winners.text(f'Lowering Volatility around Winners {idx + 1} of {total_lineups}')
596
- else:
597
- pass
598
 
599
- # Create new dataframe with optimized lineups
600
- if 'Decrease volatility' in swap_var:
601
- st.session_state['optimized_df_winners'] = pd.DataFrame(optimized_lineups_winners, columns=st.session_state['optimized_df_medians'].columns)
602
- else:
603
- st.session_state['optimized_df_winners'] = st.session_state['optimized_df_medians']
604
 
605
- # Create a progress bar
606
- progress_bar_losers = st.progress(0)
607
- status_text_losers = st.empty()
608
 
609
- # Process each lineup
610
- optimized_lineups_losers = []
611
- total_lineups = len(st.session_state['optimized_df_winners'])
612
 
613
- for idx, row in st.session_state['optimized_df_winners'].iterrows():
614
 
615
- final_lineup = optimize_lineup_losers(row)
616
- optimized_lineups_losers.append(final_lineup)
617
 
618
- if 'Increase volatility' in swap_var:
619
- progress_losers = (idx + 1) / total_lineups
620
- progress_bar_losers.progress(progress_losers)
621
- status_text_losers.text(f'Increasing Volatility around Losers {idx + 1} of {total_lineups}')
622
- else:
623
- pass
624
 
625
- # Create new dataframe with optimized lineups
626
- if 'Increase volatility' in swap_var:
627
- st.session_state['optimized_df'] = pd.DataFrame(optimized_lineups_losers, columns=st.session_state['optimized_df_winners'].columns)
628
- else:
629
- st.session_state['optimized_df'] = st.session_state['optimized_df_winners']
630
 
631
- # Calculate new stats for optimized lineups
632
- st.session_state['optimized_df']['salary'] = st.session_state['optimized_df'].apply(
633
- lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
634
- )
635
- st.session_state['optimized_df']['median'] = st.session_state['optimized_df'].apply(
636
- lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
637
- )
638
- st.session_state['optimized_df']['Own'] = st.session_state['optimized_df'].apply(
639
- lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
640
- )
641
 
642
- # Display results
643
- st.success('Optimization complete!')
644
 
645
- if 'optimized_df' in st.session_state:
646
- st.write("Increase in median highlighted in yellow, descrease in volatility highlighted in blue, increase in volatility highlighted in red:")
647
- st.dataframe(
648
- st.session_state['optimized_df'].style
649
- .apply(highlight_changes, axis=1)
650
- .apply(highlight_changes_winners, axis=1)
651
- .apply(highlight_changes_losers, axis=1)
652
- .background_gradient(axis=0)
653
- .background_gradient(cmap='RdYlGn')
654
- .format(precision=2),
655
- height=1000,
656
- use_container_width=True
657
- )
658
 
659
- # Option to download optimized lineups
660
- if st.button('Prepare Late Swap Export'):
661
- export_df = st.session_state['optimized_df'].copy()
662
 
663
- # Map player names to their export IDs for all player columns
664
- for col in export_df.columns:
665
- if col not in ['salary', 'median', 'Own']:
666
- export_df[col] = export_df[col].map(st.session_state['export_dict'])
667
 
668
- csv = export_df.to_csv(index=False)
669
- st.download_button(
670
- label="Download CSV",
671
- data=csv,
672
- file_name="optimized_lineups.csv",
673
- mime="text/csv"
674
- )
675
- else:
676
- st.write("Current Portfolio")
677
- st.dataframe(
678
- st.session_state['portfolio'].style
679
- .background_gradient(axis=0)
680
- .background_gradient(cmap='RdYlGn')
681
- .format(precision=2),
682
- height=1000,
683
- use_container_width=True
684
- )
685
 
686
- with tab3:
687
  if 'portfolio' in st.session_state and 'projections_df' in st.session_state:
688
 
689
  excluded_cols = ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Stack', 'Win%', 'Lineup Edge']
 
21
  player_wrong_names_mlb = ['Enrique Hernandez']
22
  player_right_names_mlb = ['Kike Hernandez']
23
 
24
+ tab1, tab2 = st.tabs(["Data Load", "Manage Portfolio"])
25
  with tab1:
26
  if st.button('Clear data', key='reset1'):
27
  st.session_state.clear()
 
171
 
172
  st.session_state['origin_portfolio'] = st.session_state['portfolio'].copy()
173
 
174
+ # with tab2:
175
+ # if st.button('Clear data', key='reset2'):
176
+ # st.session_state.clear()
177
 
178
+ # if 'portfolio' in st.session_state and 'projections_df' in st.session_state:
179
 
180
+ # optimized_df = None
181
 
182
+ # map_dict = {
183
+ # 'pos_map': dict(zip(st.session_state['projections_df']['player_names'],
184
+ # st.session_state['projections_df']['position'])),
185
+ # 'salary_map': dict(zip(st.session_state['projections_df']['player_names'],
186
+ # st.session_state['projections_df']['salary'])),
187
+ # 'proj_map': dict(zip(st.session_state['projections_df']['player_names'],
188
+ # st.session_state['projections_df']['median'])),
189
+ # 'own_map': dict(zip(st.session_state['projections_df']['player_names'],
190
+ # st.session_state['projections_df']['ownership'])),
191
+ # 'team_map': dict(zip(st.session_state['projections_df']['player_names'],
192
+ # st.session_state['projections_df']['team']))
193
+ # }
194
+ # # Calculate new stats for optimized lineups
195
+ # st.session_state['portfolio']['salary'] = st.session_state['portfolio'].apply(
196
+ # lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
197
+ # )
198
+ # st.session_state['portfolio']['median'] = st.session_state['portfolio'].apply(
199
+ # lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
200
+ # )
201
 
202
+ # st.session_state['portfolio']['Own'] = st.session_state['portfolio'].apply(
203
+ # lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
204
+ # )
205
 
206
+ # options_container = st.container()
207
+ # with options_container:
208
+ # col1, col2, col3, col4, col5, col6 = st.columns(6)
209
+ # with col1:
210
+ # curr_site_var = st.selectbox("Select your current site", options=['DraftKings', 'FanDuel'])
211
+ # with col2:
212
+ # curr_sport_var = st.selectbox("Select your current sport", options=['NBA', 'MLB', 'NFL', 'NHL', 'MMA'])
213
+ # with col3:
214
+ # swap_var = st.multiselect("Select late swap strategy", options=['Optimize', 'Increase volatility', 'Decrease volatility'])
215
+ # with col4:
216
+ # remove_teams_var = st.multiselect("What teams have already played?", options=st.session_state['projections_df']['team'].unique())
217
+ # with col5:
218
+ # winners_var = st.multiselect("Are there any players doing exceptionally well?", options=st.session_state['projections_df']['player_names'].unique(), max_selections=3)
219
+ # with col6:
220
+ # losers_var = st.multiselect("Are there any players doing exceptionally poorly?", options=st.session_state['projections_df']['player_names'].unique(), max_selections=3)
221
+ # if st.button('Clear Late Swap'):
222
+ # if 'optimized_df' in st.session_state:
223
+ # del st.session_state['optimized_df']
224
 
225
+ # map_dict = {
226
+ # 'pos_map': dict(zip(st.session_state['projections_df']['player_names'],
227
+ # st.session_state['projections_df']['position'])),
228
+ # 'salary_map': dict(zip(st.session_state['projections_df']['player_names'],
229
+ # st.session_state['projections_df']['salary'])),
230
+ # 'proj_map': dict(zip(st.session_state['projections_df']['player_names'],
231
+ # st.session_state['projections_df']['median'])),
232
+ # 'own_map': dict(zip(st.session_state['projections_df']['player_names'],
233
+ # st.session_state['projections_df']['ownership'])),
234
+ # 'team_map': dict(zip(st.session_state['projections_df']['player_names'],
235
+ # st.session_state['projections_df']['team']))
236
+ # }
237
+ # # Calculate new stats for optimized lineups
238
+ # st.session_state['portfolio']['salary'] = st.session_state['portfolio'].apply(
239
+ # lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
240
+ # )
241
+ # st.session_state['portfolio']['median'] = st.session_state['portfolio'].apply(
242
+ # lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
243
+ # )
244
+ # st.session_state['portfolio']['Own'] = st.session_state['portfolio'].apply(
245
+ # lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
246
+ # )
247
 
248
+ # if st.button('Run Late Swap'):
249
+ # st.session_state['portfolio'] = st.session_state['portfolio'].drop(columns=['salary', 'median', 'Own'])
250
+ # if curr_sport_var == 'NBA':
251
+ # if curr_site_var == 'DraftKings':
252
+ # st.session_state['portfolio'] = st.session_state['portfolio'].set_axis(['PG', 'SG', 'SF', 'PF', 'C', 'G', 'F', 'UTIL'], axis=1)
253
+ # else:
254
+ # st.session_state['portfolio'] = st.session_state['portfolio'].set_axis(['PG', 'PG', 'SG', 'SG', 'SF', 'SF', 'PF', 'PF', 'C'], axis=1)
255
 
256
+ # # Define roster position rules
257
+ # if curr_site_var == 'DraftKings':
258
+ # position_rules = {
259
+ # 'PG': ['PG'],
260
+ # 'SG': ['SG'],
261
+ # 'SF': ['SF'],
262
+ # 'PF': ['PF'],
263
+ # 'C': ['C'],
264
+ # 'G': ['PG', 'SG'],
265
+ # 'F': ['SF', 'PF'],
266
+ # 'UTIL': ['PG', 'SG', 'SF', 'PF', 'C']
267
+ # }
268
+ # else:
269
+ # position_rules = {
270
+ # 'PG': ['PG'],
271
+ # 'SG': ['SG'],
272
+ # 'SF': ['SF'],
273
+ # 'PF': ['PF'],
274
+ # 'C': ['C'],
275
+ # }
276
+ # # Create position groups from projections data
277
+ # position_groups = {}
278
+ # for _, player in st.session_state['projections_df'].iterrows():
279
+ # positions = player['position'].split('/')
280
+ # for pos in positions:
281
+ # if pos not in position_groups:
282
+ # position_groups[pos] = []
283
+ # position_groups[pos].append({
284
+ # 'player_names': player['player_names'],
285
+ # 'salary': player['salary'],
286
+ # 'median': player['median'],
287
+ # 'ownership': player['ownership'],
288
+ # 'positions': positions # Store all eligible positions
289
+ # })
290
 
291
+ # def optimize_lineup(row):
292
+ # current_lineup = []
293
+ # total_salary = 0
294
+ # if curr_site_var == 'DraftKings':
295
+ # salary_cap = 50000
296
+ # else:
297
+ # salary_cap = 60000
298
+ # used_players = set()
299
 
300
+ # # Convert row to dictionary with roster positions
301
+ # roster = {}
302
+ # for col, player in zip(row.index, row):
303
+ # if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
304
+ # roster[col] = {
305
+ # 'name': player,
306
+ # 'position': map_dict['pos_map'].get(player, '').split('/'),
307
+ # 'team': map_dict['team_map'].get(player, ''),
308
+ # 'salary': map_dict['salary_map'].get(player, 0),
309
+ # 'median': map_dict['proj_map'].get(player, 0),
310
+ # 'ownership': map_dict['own_map'].get(player, 0)
311
+ # }
312
+ # total_salary += roster[col]['salary']
313
+ # used_players.add(player)
314
 
315
+ # # Optimize each roster position in random order
316
+ # roster_positions = list(roster.items())
317
+ # random.shuffle(roster_positions)
318
 
319
+ # for roster_pos, current in roster_positions:
320
+ # # Skip optimization for players from removed teams
321
+ # if current['team'] in remove_teams_var:
322
+ # continue
323
 
324
+ # valid_positions = position_rules[roster_pos]
325
+ # better_options = []
326
 
327
+ # # Find valid replacements for this roster position
328
+ # for pos in valid_positions:
329
+ # if pos in position_groups:
330
+ # pos_options = [
331
+ # p for p in position_groups[pos]
332
+ # if p['median'] > current['median']
333
+ # and (total_salary - current['salary'] + p['salary']) <= salary_cap
334
+ # and p['player_names'] not in used_players
335
+ # and any(valid_pos in p['positions'] for valid_pos in valid_positions)
336
+ # and map_dict['team_map'].get(p['player_names']) not in remove_teams_var # Check team restriction
337
+ # ]
338
+ # better_options.extend(pos_options)
339
 
340
+ # if better_options:
341
+ # # Remove duplicates
342
+ # better_options = {opt['player_names']: opt for opt in better_options}.values()
343
 
344
+ # # Sort by median projection and take the best one
345
+ # best_replacement = max(better_options, key=lambda x: x['median'])
346
 
347
+ # # Update the lineup and tracking variables
348
+ # used_players.remove(current['name'])
349
+ # used_players.add(best_replacement['player_names'])
350
+ # total_salary = total_salary - current['salary'] + best_replacement['salary']
351
+ # roster[roster_pos] = {
352
+ # 'name': best_replacement['player_names'],
353
+ # 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
354
+ # 'team': map_dict['team_map'][best_replacement['player_names']],
355
+ # 'salary': best_replacement['salary'],
356
+ # 'median': best_replacement['median'],
357
+ # 'ownership': best_replacement['ownership']
358
+ # }
359
 
360
+ # # Return optimized lineup maintaining original column order
361
+ # return [roster[pos]['name'] for pos in row.index if pos in roster]
362
 
363
+ # def optimize_lineup_winners(row):
364
+ # current_lineup = []
365
+ # total_salary = 0
366
+ # if curr_site_var == 'DraftKings':
367
+ # salary_cap = 50000
368
+ # else:
369
+ # salary_cap = 60000
370
+ # used_players = set()
371
 
372
+ # # Check if any winners are in the lineup and count them
373
+ # winners_in_lineup = sum(1 for player in row if player in winners_var)
374
+ # changes_needed = min(winners_in_lineup, 3) if winners_in_lineup > 0 else 0
375
+ # changes_made = 0
376
 
377
+ # # Convert row to dictionary with roster positions
378
+ # roster = {}
379
+ # for col, player in zip(row.index, row):
380
+ # if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
381
+ # roster[col] = {
382
+ # 'name': player,
383
+ # 'position': map_dict['pos_map'].get(player, '').split('/'),
384
+ # 'team': map_dict['team_map'].get(player, ''),
385
+ # 'salary': map_dict['salary_map'].get(player, 0),
386
+ # 'median': map_dict['proj_map'].get(player, 0),
387
+ # 'ownership': map_dict['own_map'].get(player, 0)
388
+ # }
389
+ # total_salary += roster[col]['salary']
390
+ # used_players.add(player)
391
 
392
+ # # Only proceed with ownership-based optimization if we have winners in the lineup
393
+ # if changes_needed > 0:
394
+ # # Randomize the order of positions to optimize
395
+ # roster_positions = list(roster.items())
396
+ # random.shuffle(roster_positions)
397
 
398
+ # for roster_pos, current in roster_positions:
399
+ # # Stop if we've made enough changes
400
+ # if changes_made >= changes_needed:
401
+ # break
402
 
403
+ # # Skip optimization for players from removed teams or if the current player is a winner
404
+ # if current['team'] in remove_teams_var or current['name'] in winners_var:
405
+ # continue
406
 
407
+ # valid_positions = list(position_rules[roster_pos])
408
+ # random.shuffle(valid_positions)
409
+ # better_options = []
410
 
411
+ # # Find valid replacements with higher ownership
412
+ # for pos in valid_positions:
413
+ # if pos in position_groups:
414
+ # pos_options = [
415
+ # p for p in position_groups[pos]
416
+ # if p['ownership'] > current['ownership']
417
+ # and p['median'] >= current['median'] - 3
418
+ # and (total_salary - current['salary'] + p['salary']) <= salary_cap
419
+ # and (total_salary - current['salary'] + p['salary']) >= salary_cap - 1000
420
+ # and p['player_names'] not in used_players
421
+ # and any(valid_pos in p['positions'] for valid_pos in valid_positions)
422
+ # and map_dict['team_map'].get(p['player_names']) not in remove_teams_var
423
+ # ]
424
+ # better_options.extend(pos_options)
425
 
426
+ # if better_options:
427
+ # # Remove duplicates
428
+ # better_options = {opt['player_names']: opt for opt in better_options}.values()
429
 
430
+ # # Sort by ownership and take the highest owned option
431
+ # best_replacement = max(better_options, key=lambda x: x['ownership'])
432
 
433
+ # # Update the lineup and tracking variables
434
+ # used_players.remove(current['name'])
435
+ # used_players.add(best_replacement['player_names'])
436
+ # total_salary = total_salary - current['salary'] + best_replacement['salary']
437
+ # roster[roster_pos] = {
438
+ # 'name': best_replacement['player_names'],
439
+ # 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
440
+ # 'team': map_dict['team_map'][best_replacement['player_names']],
441
+ # 'salary': best_replacement['salary'],
442
+ # 'median': best_replacement['median'],
443
+ # 'ownership': best_replacement['ownership']
444
+ # }
445
+ # changes_made += 1
446
 
447
+ # # Return optimized lineup maintaining original column order
448
+ # return [roster[pos]['name'] for pos in row.index if pos in roster]
449
 
450
+ # def optimize_lineup_losers(row):
451
+ # current_lineup = []
452
+ # total_salary = 0
453
+ # if curr_site_var == 'DraftKings':
454
+ # salary_cap = 50000
455
+ # else:
456
+ # salary_cap = 60000
457
+ # used_players = set()
458
 
459
+ # # Check if any winners are in the lineup and count them
460
+ # losers_in_lineup = sum(1 for player in row if player in losers_var)
461
+ # changes_needed = min(losers_in_lineup, 3) if losers_in_lineup > 0 else 0
462
+ # changes_made = 0
463
 
464
+ # # Convert row to dictionary with roster positions
465
+ # roster = {}
466
+ # for col, player in zip(row.index, row):
467
+ # if col not in ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Lineup Edge']:
468
+ # roster[col] = {
469
+ # 'name': player,
470
+ # 'position': map_dict['pos_map'].get(player, '').split('/'),
471
+ # 'team': map_dict['team_map'].get(player, ''),
472
+ # 'salary': map_dict['salary_map'].get(player, 0),
473
+ # 'median': map_dict['proj_map'].get(player, 0),
474
+ # 'ownership': map_dict['own_map'].get(player, 0)
475
+ # }
476
+ # total_salary += roster[col]['salary']
477
+ # used_players.add(player)
478
 
479
+ # # Only proceed with ownership-based optimization if we have winners in the lineup
480
+ # if changes_needed > 0:
481
+ # # Randomize the order of positions to optimize
482
+ # roster_positions = list(roster.items())
483
+ # random.shuffle(roster_positions)
484
 
485
+ # for roster_pos, current in roster_positions:
486
+ # # Stop if we've made enough changes
487
+ # if changes_made >= changes_needed:
488
+ # break
489
 
490
+ # # Skip optimization for players from removed teams or if the current player is a winner
491
+ # if current['team'] in remove_teams_var or current['name'] in losers_var:
492
+ # continue
493
 
494
+ # valid_positions = list(position_rules[roster_pos])
495
+ # random.shuffle(valid_positions)
496
+ # better_options = []
497
 
498
+ # # Find valid replacements with higher ownership
499
+ # for pos in valid_positions:
500
+ # if pos in position_groups:
501
+ # pos_options = [
502
+ # p for p in position_groups[pos]
503
+ # if p['ownership'] < current['ownership']
504
+ # and p['median'] >= current['median'] - 3
505
+ # and (total_salary - current['salary'] + p['salary']) <= salary_cap
506
+ # and (total_salary - current['salary'] + p['salary']) >= salary_cap - 1000
507
+ # and p['player_names'] not in used_players
508
+ # and any(valid_pos in p['positions'] for valid_pos in valid_positions)
509
+ # and map_dict['team_map'].get(p['player_names']) not in remove_teams_var
510
+ # ]
511
+ # better_options.extend(pos_options)
512
 
513
+ # if better_options:
514
+ # # Remove duplicates
515
+ # better_options = {opt['player_names']: opt for opt in better_options}.values()
516
 
517
+ # # Sort by ownership and take the highest owned option
518
+ # best_replacement = max(better_options, key=lambda x: x['ownership'])
519
 
520
+ # # Update the lineup and tracking variables
521
+ # used_players.remove(current['name'])
522
+ # used_players.add(best_replacement['player_names'])
523
+ # total_salary = total_salary - current['salary'] + best_replacement['salary']
524
+ # roster[roster_pos] = {
525
+ # 'name': best_replacement['player_names'],
526
+ # 'position': map_dict['pos_map'][best_replacement['player_names']].split('/'),
527
+ # 'team': map_dict['team_map'][best_replacement['player_names']],
528
+ # 'salary': best_replacement['salary'],
529
+ # 'median': best_replacement['median'],
530
+ # 'ownership': best_replacement['ownership']
531
+ # }
532
+ # changes_made += 1
533
 
534
+ # # Return optimized lineup maintaining original column order
535
+ # return [roster[pos]['name'] for pos in row.index if pos in roster]
536
 
537
+ # # Create a progress bar
538
+ # progress_bar = st.progress(0)
539
+ # status_text = st.empty()
540
 
541
+ # # Process each lineup
542
+ # optimized_lineups = []
543
+ # total_lineups = len(st.session_state['portfolio'])
544
 
545
+ # for idx, row in st.session_state['portfolio'].iterrows():
546
+ # # First optimization pass
547
+ # first_pass = optimize_lineup(row)
548
+ # first_pass_series = pd.Series(first_pass, index=row.index)
549
 
550
+ # second_pass = optimize_lineup(first_pass_series)
551
+ # second_pass_series = pd.Series(second_pass, index=row.index)
552
 
553
+ # third_pass = optimize_lineup(second_pass_series)
554
+ # third_pass_series = pd.Series(third_pass, index=row.index)
555
 
556
+ # fourth_pass = optimize_lineup(third_pass_series)
557
+ # fourth_pass_series = pd.Series(fourth_pass, index=row.index)
558
 
559
+ # fifth_pass = optimize_lineup(fourth_pass_series)
560
+ # fifth_pass_series = pd.Series(fifth_pass, index=row.index)
561
 
562
+ # # Second optimization pass
563
+ # final_lineup = optimize_lineup(fifth_pass_series)
564
+ # optimized_lineups.append(final_lineup)
565
 
566
+ # if 'Optimize' in swap_var:
567
+ # progress = (idx + 1) / total_lineups
568
+ # progress_bar.progress(progress)
569
+ # status_text.text(f'Optimizing Lineups {idx + 1} of {total_lineups}')
570
+ # else:
571
+ # pass
572
 
573
+ # # Create new dataframe with optimized lineups
574
+ # if 'Optimize' in swap_var:
575
+ # st.session_state['optimized_df_medians'] = pd.DataFrame(optimized_lineups, columns=st.session_state['portfolio'].columns)
576
+ # else:
577
+ # st.session_state['optimized_df_medians'] = st.session_state['portfolio']
578
 
579
+ # # Create a progress bar
580
+ # progress_bar_winners = st.progress(0)
581
+ # status_text_winners = st.empty()
582
 
583
+ # # Process each lineup
584
+ # optimized_lineups_winners = []
585
+ # total_lineups = len(st.session_state['optimized_df_medians'])
586
 
587
+ # for idx, row in st.session_state['optimized_df_medians'].iterrows():
588
 
589
+ # final_lineup = optimize_lineup_winners(row)
590
+ # optimized_lineups_winners.append(final_lineup)
591
 
592
+ # if 'Decrease volatility' in swap_var:
593
+ # progress_winners = (idx + 1) / total_lineups
594
+ # progress_bar_winners.progress(progress_winners)
595
+ # status_text_winners.text(f'Lowering Volatility around Winners {idx + 1} of {total_lineups}')
596
+ # else:
597
+ # pass
598
 
599
+ # # Create new dataframe with optimized lineups
600
+ # if 'Decrease volatility' in swap_var:
601
+ # st.session_state['optimized_df_winners'] = pd.DataFrame(optimized_lineups_winners, columns=st.session_state['optimized_df_medians'].columns)
602
+ # else:
603
+ # st.session_state['optimized_df_winners'] = st.session_state['optimized_df_medians']
604
 
605
+ # # Create a progress bar
606
+ # progress_bar_losers = st.progress(0)
607
+ # status_text_losers = st.empty()
608
 
609
+ # # Process each lineup
610
+ # optimized_lineups_losers = []
611
+ # total_lineups = len(st.session_state['optimized_df_winners'])
612
 
613
+ # for idx, row in st.session_state['optimized_df_winners'].iterrows():
614
 
615
+ # final_lineup = optimize_lineup_losers(row)
616
+ # optimized_lineups_losers.append(final_lineup)
617
 
618
+ # if 'Increase volatility' in swap_var:
619
+ # progress_losers = (idx + 1) / total_lineups
620
+ # progress_bar_losers.progress(progress_losers)
621
+ # status_text_losers.text(f'Increasing Volatility around Losers {idx + 1} of {total_lineups}')
622
+ # else:
623
+ # pass
624
 
625
+ # # Create new dataframe with optimized lineups
626
+ # if 'Increase volatility' in swap_var:
627
+ # st.session_state['optimized_df'] = pd.DataFrame(optimized_lineups_losers, columns=st.session_state['optimized_df_winners'].columns)
628
+ # else:
629
+ # st.session_state['optimized_df'] = st.session_state['optimized_df_winners']
630
 
631
+ # # Calculate new stats for optimized lineups
632
+ # st.session_state['optimized_df']['salary'] = st.session_state['optimized_df'].apply(
633
+ # lambda row: sum(map_dict['salary_map'].get(player, 0) for player in row if player in map_dict['salary_map']), axis=1
634
+ # )
635
+ # st.session_state['optimized_df']['median'] = st.session_state['optimized_df'].apply(
636
+ # lambda row: sum(map_dict['proj_map'].get(player, 0) for player in row if player in map_dict['proj_map']), axis=1
637
+ # )
638
+ # st.session_state['optimized_df']['Own'] = st.session_state['optimized_df'].apply(
639
+ # lambda row: sum(map_dict['own_map'].get(player, 0) for player in row if player in map_dict['own_map']), axis=1
640
+ # )
641
 
642
+ # # Display results
643
+ # st.success('Optimization complete!')
644
 
645
+ # if 'optimized_df' in st.session_state:
646
+ # st.write("Increase in median highlighted in yellow, descrease in volatility highlighted in blue, increase in volatility highlighted in red:")
647
+ # st.dataframe(
648
+ # st.session_state['optimized_df'].style
649
+ # .apply(highlight_changes, axis=1)
650
+ # .apply(highlight_changes_winners, axis=1)
651
+ # .apply(highlight_changes_losers, axis=1)
652
+ # .background_gradient(axis=0)
653
+ # .background_gradient(cmap='RdYlGn')
654
+ # .format(precision=2),
655
+ # height=1000,
656
+ # use_container_width=True
657
+ # )
658
 
659
+ # # Option to download optimized lineups
660
+ # if st.button('Prepare Late Swap Export'):
661
+ # export_df = st.session_state['optimized_df'].copy()
662
 
663
+ # # Map player names to their export IDs for all player columns
664
+ # for col in export_df.columns:
665
+ # if col not in ['salary', 'median', 'Own']:
666
+ # export_df[col] = export_df[col].map(st.session_state['export_dict'])
667
 
668
+ # csv = export_df.to_csv(index=False)
669
+ # st.download_button(
670
+ # label="Download CSV",
671
+ # data=csv,
672
+ # file_name="optimized_lineups.csv",
673
+ # mime="text/csv"
674
+ # )
675
+ # else:
676
+ # st.write("Current Portfolio")
677
+ # st.dataframe(
678
+ # st.session_state['portfolio'].style
679
+ # .background_gradient(axis=0)
680
+ # .background_gradient(cmap='RdYlGn')
681
+ # .format(precision=2),
682
+ # height=1000,
683
+ # use_container_width=True
684
+ # )
685
 
686
+ with tab2:
687
  if 'portfolio' in st.session_state and 'projections_df' in st.session_state:
688
 
689
  excluded_cols = ['salary', 'median', 'Own', 'Finish_percentile', 'Dupes', 'Stack', 'Win%', 'Lineup Edge']