File size: 22,724 Bytes
158eaa8 18eaa68 67af571 158eaa8 72c43b1 158eaa8 8e462c9 bdf6978 8e462c9 05f2b9c bdf6978 05f2b9c 8e462c9 d5f1d98 8e462c9 bdf6978 8e462c9 f35aa8d 8e462c9 b9049ff 8e462c9 b9049ff 8e462c9 b9049ff 8e462c9 ad242a2 bdf6978 8e462c9 ad242a2 8e462c9 bdf6978 8e462c9 ad242a2 8e462c9 ad242a2 8e462c9 7f0bbb5 8e462c9 7f0bbb5 8e462c9 05f2b9c 8e462c9 b7931c0 67eb3a1 158eaa8 aef223f afc2563 f4f6da8 72d1643 f4f6da8 72d1643 7dd1418 f4f6da8 72d1643 abd4533 8b34fed c0ae0e3 abd4533 158eaa8 8913211 efbfb51 158eaa8 0a58221 158eaa8 2371c4f ff6fb00 3076618 158eaa8 efbfb51 3ff610a 1b89d01 6029c0b efbfb51 f93672b b452fab 38f865e 9034a8a 066fad7 afc2563 066fad7 afc2563 066fad7 38f865e 066fad7 87bb04e 066fad7 298e2c8 066fad7 298e2c8 066fad7 9034a8a 066fad7 fca5d51 066fad7 fca5d51 066fad7 fca5d51 b7931c0 7361afe b7931c0 066fad7 fca5d51 9034a8a 066fad7 b7931c0 7361afe b7931c0 066fad7 9034a8a ff6fb00 afc2563 f13359a 3a20f4f ff6fb00 afc2563 ff6fb00 9034a8a ff6fb00 efbfb51 ff6fb00 9034a8a ff6fb00 286b8a7 ff6fb00 9034a8a ce96fd8 9034a8a ff6fb00 158eaa8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
import random
import numpy as np
import math
#### Goal is to choose a player and adjust the amount of lineups that have them
#### First thing you need to do is find comparable players in the projections, so any player in the projections that is within $500 of the player and within 10% of the projection
#### Take that list of players and create a list that can be accessed for random insertion into the portfolio
#### Find the player and the amount of rows that contain them and then find an exposure rate which is the percentage of total rows
#### Use the exposure target argument and try to replace the player from as many rows as necessary to be at or just under the target
#### makes sure to check if the player is eligible for the position before replacing them
def check_nba_position_eligibility(column_name, player_positions):
if 'PG' in column_name:
return 'PG' in player_positions
elif 'SG' in column_name:
return 'SG' in player_positions
elif 'SF' in column_name:
return 'SF' in player_positions
elif 'PF' in column_name:
return 'PF' in player_positions
elif 'C' in column_name:
return 'C' in player_positions
elif 'G' in column_name:
return any(pos in ['PG', 'SG'] for pos in player_positions)
elif 'F' in column_name:
return any(pos in ['SF', 'PF'] for pos in player_positions)
elif 'UTIL' in column_name:
return True # UTIL can be any position
return False
def check_lol_position_eligibility(column_name, player_positions):
if 'TOP' in column_name:
return 'TOP' in player_positions
elif 'JNG' in column_name:
return 'JNG' in player_positions
elif 'MID' in column_name:
return 'MID' in player_positions
elif 'ADC' in column_name:
return 'ADC' in player_positions
elif 'SUP' in column_name:
return 'SUP' in player_positions
elif 'Team' in column_name:
return 'Team' in player_positions
elif 'CPT' in column_name:
return any(pos in ['TOP', 'JNG', 'MID', 'ADC', 'SUP'] for pos in player_positions)
return False
def check_mlb_position_eligibility(column_name, player_positions):
if any(pos in column_name for pos in ['P', 'SP', 'RP']):
return any(pos in ['P', 'SP', 'RP'] for pos in player_positions)
elif 'C' in column_name:
return 'C' in player_positions
elif '1B' in column_name:
return '1B' in player_positions
elif '2B' in column_name:
return '2B' in player_positions
elif '3B' in column_name:
return '3B' in player_positions
elif 'SS' in column_name:
return 'SS' in player_positions
elif 'OF' in column_name:
return 'OF' in player_positions
return False
def check_nfl_position_eligibility(column_name, player_positions):
if 'QB' in column_name:
return 'QB' in player_positions
elif 'RB' in column_name:
return 'RB' in player_positions
elif 'WR' in column_name:
return 'WR' in player_positions
elif 'TE' in column_name:
return 'TE' in player_positions
elif 'DST' in column_name:
return 'DST' in player_positions
elif 'FLEX' in column_name:
return any(pos in ['RB', 'WR', 'TE'] for pos in player_positions)
elif 'UTIL' in column_name:
return any(pos in ['RB', 'WR', 'TE'] for pos in player_positions)
return False
def check_golf_position_eligibility(column_name, player_positions):
if 'FLEX' in column_name:
return any(pos in ['G'] for pos in player_positions)
return True
def check_tennis_position_eligibility(column_name, player_positions):
if 'FLEX' in column_name:
return any(pos in ['T'] for pos in player_positions)
return True
def check_mma_position_eligibility(column_name, player_positions):
if 'FLEX' in column_name:
return any(pos in ['F'] for pos in player_positions)
return True
def check_nascar_position_eligibility(column_name, player_positions):
if 'FLEX' in column_name:
return any(pos in ['D'] for pos in player_positions)
return True
def check_ncaaf_position_eligibility(column_name, player_positions):
if 'QB' in column_name:
return 'QB' in player_positions
elif 'RB' in column_name:
return 'RB' in player_positions
elif 'WR' in column_name:
return 'WR' in player_positions
elif 'FLEX' in column_name:
return any(pos in ['RB', 'WR'] for pos in player_positions)
elif 'SFLEX' in column_name:
return any(pos in ['RB', 'WR', 'QB'] for pos in player_positions)
return False
def check_nhl_position_eligibility(column_name, player_positions):
if 'C' in column_name:
return 'C' in player_positions
elif 'W' in column_name:
return 'W' in player_positions
elif 'D' in column_name:
return 'D' in player_positions
elif 'G' in column_name:
return 'G' in player_positions
elif 'FLEX' in column_name:
return any(pos in ['C', 'W', 'D'] for pos in player_positions)
elif 'UTIL' in column_name:
return any(pos in ['C', 'W', 'D'] for pos in player_positions)
return False
def check_position_eligibility(sport, column_name, player_positions):
if sport == 'NBA':
return check_nba_position_eligibility(column_name, player_positions)
elif sport == 'MLB':
return check_mlb_position_eligibility(column_name, player_positions)
elif sport == 'NFL':
return check_nfl_position_eligibility(column_name, player_positions)
elif sport == 'NHL':
return check_nhl_position_eligibility(column_name, player_positions)
elif sport == 'MMA':
return check_mma_position_eligibility(column_name, player_positions)
elif sport == 'GOLF':
return check_golf_position_eligibility(column_name, player_positions)
elif sport == 'TENNIS':
return check_tennis_position_eligibility(column_name, player_positions)
elif sport == 'LOL':
return check_lol_position_eligibility(column_name, player_positions)
else:
# Default fallback - assume exact position match
return column_name in player_positions
def get_effective_salary(player_name, column_name, projections_df, type_var):
"""Calculate the effective salary for a player in a specific column"""
base_salary = projections_df[projections_df['player_names'] == player_name]['salary'].iloc[0]
if type_var != 'Classic' and column_name == 'CPT':
return base_salary * 1.5
return base_salary
def find_player_column(player_name, row_data, working_columns):
"""Find which column contains the specified player"""
for col in working_columns:
if row_data[col] == player_name:
return col
return None
def calculate_salary_difference(current_player, new_player, column_name, projections_df, type_var):
"""Calculate the salary difference when replacing current_player with new_player in a specific column"""
current_salary = get_effective_salary(current_player, column_name, projections_df, type_var)
new_salary = get_effective_salary(new_player, column_name, projections_df, type_var)
return new_salary - current_salary
def check_salary_eligibility(current_lineup_salary, current_player, new_player, column_name, projections_df, type_var, salary_max):
"""Check if replacing current_player with new_player in the specified column stays within salary cap"""
salary_diff = calculate_salary_difference(current_player, new_player, column_name, projections_df, type_var)
return current_lineup_salary + salary_diff <= salary_max
def exposure_spread(working_frame, exposure_player, exposure_target, comp_salary_below, comp_salary_above, ignore_stacks, remove_teams, specific_replacements, specific_exclusions, specific_columns, projections_df, sport_var, type_var, salary_max, stacking_sports):
if specific_exclusions != []:
projections_df = projections_df[~projections_df['player_names'].isin(specific_exclusions)]
comparable_players = projections_df[projections_df['player_names'] == exposure_player]
comparable_players = comparable_players.reset_index(drop=True)
comp_salary_high_base = comparable_players['salary'][0]
if type_var == 'Showdown':
comp_salary_low = comparable_players['salary'][0] + comp_salary_below
comp_salary_high = comparable_players['salary'][0] + comp_salary_above
else:
comp_salary_low = comparable_players['salary'][0] + comp_salary_below
comp_salary_high = comparable_players['salary'][0] + comp_salary_above
comp_projection_high = comparable_players['median'][0]
if type_var == 'Showdown':
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
else:
comp_projection_low = comparable_players['median'][0] - (comparable_players['median'][0] * .5)
# players can be eligible at multiple positions, so we need to find all the positions the player is eligible at
# the position column can have positions designated as 1B/OF which means they are eligible at 1B and OF
comp_player_position = comparable_players['position'].tolist()
comp_team = comparable_players['team'].tolist()
try:
comp_player_position = [pos.split('/') for pos in comp_player_position]
comp_player_position = [item for sublist in comp_player_position for item in sublist]
comp_player_position = list(set(comp_player_position))
except:
comp_player_position = comparable_players['position'].tolist()
def has_position_overlap(player_positions, target_positions):
player_pos_list = player_positions.split('/')
return any(pos in target_positions for pos in player_pos_list)
# find the exposure rate of the player in the working frame
if specific_columns != []:
player_mask = working_frame[specific_columns].apply(
lambda row: exposure_player in list(row), axis=1
)
else:
player_mask = working_frame[working_frame.columns].apply(
lambda row: exposure_player in list(row), axis=1
)
if specific_columns != []:
replace_mask = working_frame[specific_columns].apply(
lambda row: exposure_player not in list(row), axis=1
)
else:
replace_mask = working_frame[working_frame.columns].apply(
lambda row: exposure_player not in list(row), axis=1
)
player_exposure = player_mask.sum() / len(working_frame)
replace_exposure = replace_mask.sum() / len(working_frame)
# find the number of lineups that need to be removed to reach the target exposure
if exposure_target == 0:
lineups_to_remove = (player_exposure * len(working_frame))
else:
lineups_to_remove = ((player_exposure - exposure_target) * len(working_frame)) * 1.01
lineups_to_add = ((exposure_target - player_exposure) * len(working_frame)) * 1.10
# isolate the rows that contain the player
player_rows = working_frame[player_mask]
replace_rows = working_frame[replace_mask]
if ignore_stacks != []:
player_rows = player_rows[~player_rows['Stack'].isin(ignore_stacks)]
replace_rows = replace_rows[~replace_rows['Stack'].isin(ignore_stacks)]
change_counter = 0
random_row_indices_insert = list(player_rows.index)
random_row_indices_replace = list(replace_rows.index)
random.shuffle(random_row_indices_insert)
random.shuffle(random_row_indices_replace)
# for each row to the the number of lineups to remove, replace with random choice from comparable player list if they can be inserted
# we will need to use two separate functions here, one for an exposure player who has a lineups to remove above 0 and one for below 0
# key concept here is if they have a lineups to remove above 0 it means that we are trying to replace them with comparable players
# if the lineups to remove is below zero it means we want to find comparable players and replace them with the exposure player
if lineups_to_remove > 0:
# Keep trying until we've made enough successful replacements
while change_counter < math.ceil(lineups_to_remove) and random_row_indices_insert:
# Get the next row to try
row = random_row_indices_insert.pop(0)
if specific_replacements != []:
comparable_players = projections_df[(projections_df['player_names'].isin(specific_replacements)) &
(projections_df['salary'] <= comp_salary_high_base + (salary_max - working_frame['salary'][row]))
]
else:
comparable_players = projections_df[
(projections_df['salary'] >= comp_salary_low) &
(projections_df['salary'] <= comp_salary_high_base + (salary_max - working_frame['salary'][row])) &
(projections_df['median'] >= comp_projection_low) &
(projections_df['position'].apply(lambda x: has_position_overlap(x, comp_player_position)))
]
if exposure_target == 0:
comparable_players = comparable_players[comparable_players['player_names'] != exposure_player]
if remove_teams is not None:
remove_mask = comparable_players.apply(
lambda row: not any(team in list(row) for team in remove_teams), axis=1
)
comparable_players = comparable_players[remove_mask]
# Get the current row data to check for existing players
if specific_columns != []:
current_row_data = working_frame.iloc[row][specific_columns]
else:
current_row_data = working_frame.iloc[row]
# Filter out players that are already present in this row
existing_players = set(current_row_data.values)
try:
comparable_players = comparable_players[~comparable_players['player_names'].isin(existing_players)]
comparable_player_list = comparable_players['player_names'].tolist()
except:
comparable_player_list = []
if comparable_player_list:
# Find which column contains the exposure_player
if specific_columns != []:
row_data = working_frame.iloc[row][specific_columns]
working_columns = specific_columns
else:
row_data = working_frame.iloc[row]
working_columns = working_frame.columns
# Track if we successfully made a replacement
replacement_made = False
# For exposure_target == 0, replace ALL occurrences of exposure_player in this row
if exposure_target == 0:
for col in working_columns:
if row_data[col] == exposure_player:
# Try to find a suitable replacement for this specific column
suitable_replacements = []
for candidate in comparable_player_list:
# Get the replacement player's positions
replacement_player_positions = projections_df[projections_df['player_names'] == candidate]['position'].iloc[0].split('/')
# Check if the replacement player is eligible for this column
if type_var == 'Classic':
if check_position_eligibility(sport_var, col, replacement_player_positions):
suitable_replacements.append(candidate)
else:
# For non-Classic types, check salary eligibility using helper function
current_lineup_salary = working_frame.iloc[row]['salary']
if check_salary_eligibility(current_lineup_salary, exposure_player, candidate, col, projections_df, type_var, salary_max):
suitable_replacements.append(candidate)
if suitable_replacements:
insert_player = random.choice(suitable_replacements)
working_frame.at[row, col] = insert_player
replacement_made = True
# Remove this player from the list to avoid duplicates in the same row
comparable_player_list = [p for p in comparable_player_list if p != insert_player]
else:
for col in working_columns:
if row_data[col] == exposure_player:
insert_player = random.choice(comparable_player_list)
# Get the replacement player's positions
replacement_player_positions = projections_df[projections_df['player_names'] == insert_player]['position'].iloc[0].split('/')
# Check if the replacement player is eligible for this column
if type_var == 'Classic':
if check_position_eligibility(sport_var, col, replacement_player_positions):
working_frame.at[row, col] = insert_player
replacement_made = True
break
else:
# For non-Classic types, check salary eligibility using helper function
current_lineup_salary = working_frame.iloc[row]['salary']
if check_salary_eligibility(current_lineup_salary, exposure_player, insert_player, col, projections_df, type_var, salary_max):
working_frame.at[row, col] = insert_player
replacement_made = True
break
# Only increment counter if we actually made a replacement
if replacement_made:
change_counter += 1
# If we've run out of rows to try, break to avoid infinite loop
if not random_row_indices_insert:
break
else:
while change_counter < math.ceil(lineups_to_add) and random_row_indices_replace:
row = random_row_indices_replace.pop(0)
if specific_replacements != []:
comparable_players = projections_df[(projections_df['player_names'].isin(specific_replacements))
]
else:
if type_var == 'Showdown':
comparable_players = projections_df[
(projections_df['salary'] >= comp_salary_low) &
(projections_df['salary'] <= comp_salary_high_base + (salary_max - working_frame['salary'][row]))
]
else:
comparable_players = projections_df[
(projections_df['salary'] >= comp_salary_low) &
(projections_df['salary'] <= comp_salary_high_base + (salary_max - working_frame['salary'][row])) &
(projections_df['position'].apply(lambda x: has_position_overlap(x, comp_player_position)))
]
if sport_var in stacking_sports:
if working_frame.iloc[row]['Size'] == 5 and comp_team != working_frame.iloc[row]['Stack']:
remove_mask = comparable_players.apply(
lambda player_row: not any(team in list(player_row) for team in [working_frame.iloc[row]['Stack']]), axis=1
)
comparable_players = comparable_players[remove_mask]
if remove_teams is not None:
remove_mask = comparable_players.apply(
lambda row: not any(team in list(row) for team in remove_teams), axis=1
)
comparable_players = comparable_players[remove_mask]
comparable_players = comparable_players[comparable_players['player_names'] != exposure_player]
# Create a list of comparable players
comparable_player_list = comparable_players['player_names'].tolist()
if exposure_player in working_frame.iloc[row].values:
comparable_player_list = []
if comparable_player_list:
# Find which column contains the exposure_player
if specific_columns != []:
row_data = working_frame.iloc[row][specific_columns]
working_columns = specific_columns
else:
row_data = working_frame.iloc[row]
working_columns = working_frame.columns
for col in working_columns:
if row_data[col] in comparable_player_list:
current_lineup_salary = working_frame.iloc[row]['salary']
current_player = row_data[col]
# Check salary eligibility using helper function
if check_salary_eligibility(current_lineup_salary, current_player, exposure_player, col, projections_df, type_var, salary_max):
if type_var == 'Classic':
# For Classic types, also check position eligibility
exposure_player_positions = projections_df[projections_df['player_names'] == exposure_player]['position'].iloc[0].split('/')
if check_position_eligibility(sport_var, col, exposure_player_positions):
working_frame.at[row, col] = exposure_player
change_counter += 1
break
else:
# For non-Classic types, salary check is sufficient (position eligibility handled elsewhere)
working_frame.at[row, col] = exposure_player
change_counter += 1
break
return working_frame
|