Multichem commited on
Commit
2c0c3ee
1 Parent(s): 4713c08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -195
app.py CHANGED
@@ -1,110 +1,96 @@
 
 
 
 
 
 
 
1
  import numpy as np
2
  import pandas as pd
3
  import streamlit as st
4
  import gspread
5
  import plotly.express as px
6
-
7
- scope = ['https://www.googleapis.com/auth/spreadsheets',
8
- "https://www.googleapis.com/auth/drive"]
9
-
10
- credentials = {
11
- "type": "service_account",
12
- "project_id": "sheets-api-connect-378620",
13
- "private_key_id": "1005124050c80d085e2c5b344345715978dd9cc9",
14
- "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtKa01beXwc88R\nnPZVQTNPVQuBnbwoOfc66gW3547ja/UEyIGAF112dt/VqHprRafkKGmlg55jqJNt\na4zceLKV+wTm7vBu7lDISTJfGzCf2TrxQYNqwMKE2LOjI69dBM8u4Dcb4k0wcp9v\ntW1ZzLVVuwTvmrg7JBHjiSaB+x5wxm/r3FOiJDXdlAgFlytzqgcyeZMJVKKBQHyJ\njEGg/1720A0numuOCt71w/2G0bDmijuj1e6tH32MwRWcvRNZ19K9ssyDz2S9p68s\nYDhIxX69OWxwScTIHLY6J2t8txf/XMivL/636fPlDADvBEVTdlT606n8CcKUVQeq\npUVdG+lfAgMBAAECggEAP38SUA7B69eTfRpo658ycOs3Amr0JW4H/bb1rNeAul0K\nZhwd/HnU4E07y81xQmey5kN5ZeNrD5EvqkZvSyMJHV0EEahZStwhjCfnDB/cxyix\nZ+kFhv4y9eK+kFpUAhBy5nX6T0O+2T6WvzAwbmbVsZ+X8kJyPuF9m8ldcPlD0sce\ntj8NwVq1ys52eosqs7zi2vjt+eMcaY393l4ls+vNq8Yf27cfyFw45W45CH/97/Nu\n5AmuzlCOAfFF+z4OC5g4rei4E/Qgpxa7/uom+BVfv9G0DIGW/tU6Sne0+37uoGKt\nW6DzhgtebUtoYkG7ZJ05BTXGp2lwgVcNRoPwnKJDxQKBgQDT5wYPUBDW+FHbvZSp\nd1m1UQuXyerqOTA9smFaM8sr/UraeH85DJPEIEk8qsntMBVMhvD3Pw8uIUeFNMYj\naLmZFObsL+WctepXrVo5NB6RtLB/jZYxiKMatMLUJIYtcKIp+2z/YtKiWcLnwotB\nWdCjVnPTxpkurmF2fWP/eewZ+wKBgQDRMtJg7etjvKyjYNQ5fARnCc+XsI3gkBe1\nX9oeXfhyfZFeBXWnZzN1ITgFHplDznmBdxAyYGiQdbbkdKQSghviUQ0igBvoDMYy\n1rWcy+a17Mj98uyNEfmb3X2cC6WpvOZaGHwg9+GY67BThwI3FqHIbyk6Ko09WlTX\nQpRQjMzU7QKBgAfi1iflu+q0LR+3a3vvFCiaToskmZiD7latd9AKk2ocsBd3Woy9\n+hXXecJHPOKV4oUJlJgvAZqe5HGBqEoTEK0wyPNLSQlO/9ypd+0fEnArwFHO7CMF\nycQprAKHJXM1eOOFFuZeQCaInqdPZy1UcV5Szla4UmUZWkk1m24blHzXAoGBAMcA\nyH4qdbxX9AYrC1dvsSRvgcnzytMvX05LU0uF6tzGtG0zVlub4ahvpEHCfNuy44UT\nxRWW/oFFaWjjyFxO5sWggpUqNuHEnRopg3QXx22SRRTGbN45li/+QAocTkgsiRh1\nqEcYZsO4mPCsQqAy6E2p6RcK+Xa+omxvSnVhq0x1AoGAKr8GdkCl4CF6rieLMAQ7\nLNBuuoYGaHoh8l5E2uOQpzwxVy/nMBcAv+2+KqHEzHryUv1owOi6pMLv7A9mTFoS\n18B0QRLuz5fSOsVnmldfC9fpUc6H8cH1SINZpzajqQA74bPwELJjnzrCnH79TnHG\nJuElxA33rFEjbgbzdyrE768=\n-----END PRIVATE KEY-----\n",
15
- "client_email": "gspread-connection@sheets-api-connect-378620.iam.gserviceaccount.com",
16
- "client_id": "106625872877651920064",
17
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
18
- "token_uri": "https://oauth2.googleapis.com/token",
19
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
20
- "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gspread-connection%40sheets-api-connect-378620.iam.gserviceaccount.com"
21
- }
22
-
23
- gc = gspread.service_account_from_dict(credentials)
24
-
25
- st.set_page_config(layout="wide")
26
-
27
- game_format = {'Win%': '{:.2%}', 'Vegas': '{:.2%}', 'Win% Diff': '{:.2%}'}
28
- american_format = {'First Inning Lead Percentage': '{:.2%}', 'Fifth Inning Lead Percentage': '{:.2%}'}
29
-
30
- master_hold = 'https://docs.google.com/spreadsheets/d/1I_1Ve3F4tftgfLQQoRKOJ351XfEG48s36OxXUKxmgS8/edit#gid=694077504'
31
-
32
- @st.cache_data
33
- def game_betting_model():
 
 
 
34
  sh = gc.open_by_url(master_hold)
35
- worksheet = sh.worksheet('Game_Betting')
36
  raw_display = pd.DataFrame(worksheet.get_all_records())
37
  raw_display.replace('#DIV/0!', np.nan, inplace=True)
38
- raw_display = raw_display.dropna()
39
-
40
- return raw_display
41
 
42
- @st.cache_data
43
- def player_stat_table():
44
- sh = gc.open_by_url(master_hold)
45
- worksheet = sh.worksheet('Prop_Table')
46
  raw_display = pd.DataFrame(worksheet.get_all_records())
47
  raw_display.replace('', np.nan, inplace=True)
48
- raw_display = raw_display.dropna()
49
-
50
- return raw_display
51
-
52
- @st.cache_data
53
- def timestamp_table():
54
- sh = gc.open_by_url(master_hold)
55
- worksheet = sh.worksheet('DK_ROO')
56
- raw_display = worksheet.acell('U2').value
57
-
58
- return raw_display
59
 
60
- @st.cache_data
61
- def player_prop_table():
62
- sh = gc.open_by_url(master_hold)
63
- worksheet = sh.worksheet('prop_frame')
64
  raw_display = pd.DataFrame(worksheet.get_all_records())
65
  raw_display.replace('', np.nan, inplace=True)
66
- raw_display = raw_display.dropna()
67
 
68
- return raw_display
69
 
70
- game_model = game_betting_model()
71
- overall_stats = player_stat_table()
72
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
73
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
74
- timestamp = timestamp_table()
75
- prop_frame = player_prop_table()
76
  t_stamp = f"Last Update: " + str(timestamp) + f" CST"
77
 
78
  tab1, tab2, tab3, tab4, tab5 = st.tabs(["Game Betting Model", "QB Projections", "RB/WR/TE Projections", "Player Prop Simulations", "Stat Specific Simulations"])
79
 
80
- def convert_df_to_csv(df):
81
- return df.to_csv().encode('utf-8')
82
-
83
  with tab1:
84
  st.info(t_stamp)
85
  if st.button("Reset Data", key='reset1'):
86
  st.cache_data.clear()
87
- game_model = game_betting_model()
88
- overall_stats = player_stat_table()
89
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
90
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
91
- prop_frame = player_prop_table()
92
- t_stamp = f"Last Update: " + str(prop_frame['timestamp'][0]) + f" CST"
93
  line_var1 = st.radio('How would you like to display odds?', options = ['Percentage', 'American'], key='line_var1')
94
  team_frame = game_model
95
  if line_var1 == 'Percentage':
96
- team_frame = team_frame[['team', 'Opp', 'Win%', 'Vegas', 'Win% Diff', 'PD Spread', 'Vegas Spread', 'Spread Diff']]
97
- team_frame = team_frame.set_index('team')
98
- st.dataframe(team_frame.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(game_format, precision=2), use_container_width = True)
99
  if line_var1 == 'American':
100
- team_frame = team_frame[['team', 'Opp', 'Win Line', 'Vegas Line', 'Line Diff', 'PD Spread', 'Vegas Spread', 'Spread Diff']]
101
- team_frame = team_frame.set_index('team')
102
  st.dataframe(team_frame.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
103
 
104
  st.download_button(
105
  label="Export Team Model",
106
  data=convert_df_to_csv(team_frame),
107
- file_name='NFL_team_betting_export.csv',
108
  mime='text/csv',
109
  key='team_export',
110
  )
@@ -113,66 +99,31 @@ with tab2:
113
  st.info(t_stamp)
114
  if st.button("Reset Data", key='reset2'):
115
  st.cache_data.clear()
116
- game_model = game_betting_model()
117
- overall_stats = player_stat_table()
118
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
119
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
120
- prop_frame = player_prop_table()
121
- t_stamp = f"Last Update: " + str(prop_frame['timestamp'][0]) + f" CST"
122
  split_var1 = st.radio("Would you like to view all teams or specific ones?", ('All', 'Specific Teams'), key='split_var1')
123
  if split_var1 == 'Specific Teams':
124
- team_var1 = st.multiselect('Which teams would you like to include in the tables?', options = qb_stats['Team'].unique(), key='team_var1')
125
  elif split_var1 == 'All':
126
- team_var1 = qb_stats.Team.values.tolist()
127
- qb_stats = qb_stats[qb_stats['Team'].isin(team_var1)]
128
- qb_stats_disp = qb_stats.set_index('Player')
129
- qb_stats_disp = qb_stats_disp.sort_values(by='PPR', ascending=False)
130
- st.dataframe(qb_stats_disp.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
131
  st.download_button(
132
  label="Export Prop Model",
133
- data=convert_df_to_csv(qb_stats_disp),
134
- file_name='NFL_qb_stats_export.csv',
135
  mime='text/csv',
136
  key='pitcher_prop_export',
137
  )
138
-
139
  with tab3:
140
- st.info(t_stamp)
141
- if st.button("Reset Data", key='reset3'):
142
- st.cache_data.clear()
143
- game_model = game_betting_model()
144
- overall_stats = player_stat_table()
145
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
146
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
147
- prop_frame = player_prop_table()
148
- t_stamp = f"Last Update: " + str(prop_frame['timestamp'][0]) + f" CST"
149
- split_var2 = st.radio("Would you like to view all teams or specific ones?", ('All', 'Specific Teams'), key='split_var2')
150
- if split_var2 == 'Specific Teams':
151
- team_var2 = st.multiselect('Which teams would you like to include in the tables?', options = non_qb_stats['Team'].unique(), key='team_var2')
152
- elif split_var2 == 'All':
153
- team_var2 = non_qb_stats.Team.values.tolist()
154
- non_qb_stats = non_qb_stats[non_qb_stats['Team'].isin(team_var2)]
155
- non_qb_stats_disp = non_qb_stats.set_index('Player')
156
- non_qb_stats_disp = non_qb_stats_disp.sort_values(by='PPR', ascending=False)
157
- st.dataframe(non_qb_stats_disp.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
158
- st.download_button(
159
- label="Export Prop Model",
160
- data=convert_df_to_csv(non_qb_stats_disp),
161
- file_name='NFL_nonqb_stats_export.csv',
162
- mime='text/csv',
163
- key='hitter_prop_export',
164
- )
165
-
166
- with tab4:
167
  st.info(t_stamp)
168
  if st.button("Reset Data", key='reset4'):
169
  st.cache_data.clear()
170
- game_model = game_betting_model()
171
- overall_stats = player_stat_table()
172
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
173
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
174
- prop_frame = player_prop_table()
175
- t_stamp = f"Last Update: " + str(prop_frame['timestamp'][0]) + f" CST"
176
  col1, col2 = st.columns([1, 5])
177
 
178
  with col2:
@@ -181,31 +132,30 @@ with tab4:
181
  plot_hold_container = st.empty()
182
 
183
  with col1:
184
- player_check = st.selectbox('Select player to simulate props', options = overall_stats['Player'].unique())
185
- prop_type_var = st.selectbox('Select type of prop to simulate', options = ['Pass Yards', 'Pass TDs', 'Rush Yards', 'Rush TDs', 'Receptions', 'Rec Yards', 'Rec TDs', 'Fantasy', 'FD Fantasy', 'PrizePicks'])
 
186
 
187
  ou_var = st.selectbox('Select wether it is an over or under', options = ['Over', 'Under'])
188
- if prop_type_var == 'Pass Yards':
189
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 100.0, max_value = 400.5, value = 250.5, step = .5)
190
- elif prop_type_var == 'Pass TDs':
 
 
 
 
191
  prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 5.5, value = 1.5, step = .5)
192
- elif prop_type_var == 'Rush Yards':
193
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 155.5, value = 25.5, step = .5)
194
- elif prop_type_var == 'Rush TDs':
195
  prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 5.5, value = 1.5, step = .5)
196
- elif prop_type_var == 'Receptions':
197
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 15.5, value = 5.5, step = .5)
198
- elif prop_type_var == 'Rec Yards':
199
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 155.5, value = 25.5, step = .5)
200
- elif prop_type_var == 'Rec TDs':
201
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 5.5, value = 1.5, step = .5)
202
- elif prop_type_var == 'Fantasy':
203
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 50.5, value = 10.5, step = .5)
204
- elif prop_type_var == 'FD Fantasy':
205
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 50.5, value = 10.5, step = .5)
206
- elif prop_type_var == 'PrizePicks':
207
- prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 50.5, value = 10.5, step = .5)
208
- line_var = st.number_input('Type in the line on the prop (i.e. -120)', min_value = -1000, max_value = 1000, value = -150, step = 1)
209
  line_var = line_var + 1
210
 
211
  if st.button('Simulate Prop'):
@@ -213,7 +163,7 @@ with tab4:
213
 
214
  with df_hold_container.container():
215
 
216
- df = overall_stats
217
 
218
  total_sims = 5000
219
 
@@ -222,31 +172,29 @@ with tab4:
222
  player_var = df.loc[df['Player'] == player_check]
223
  player_var = player_var.reset_index()
224
 
225
- if prop_type_var == 'Pass Yards':
226
- df['Median'] = df['pass_yards']
227
- elif prop_type_var == 'Pass TDs':
228
- df['Median'] = df['pass_tds']
229
- elif prop_type_var == 'Rush Yards':
230
- df['Median'] = df['rush_yards']
231
- elif prop_type_var == 'Rush TDs':
232
- df['Median'] = df['rush_tds']
233
- elif prop_type_var == 'Receptions':
234
- df['Median'] = df['rec']
235
- elif prop_type_var == 'Rec Yards':
236
- df['Median'] = df['rec_yards']
237
- elif prop_type_var == 'Rec TDs':
238
- df['Median'] = df['rec_tds']
239
- elif prop_type_var == 'Fantasy':
240
- df['Median'] = df['PPR']
241
- elif prop_type_var == 'FD Fantasy':
242
- df['Median'] = df['Half_PPF']
243
- elif prop_type_var == 'PrizePicks':
244
- df['Median'] = df['Half_PPF']
245
 
246
  flex_file = df
247
- flex_file['Floor'] = flex_file['Median'] * .20
248
- flex_file['Ceiling'] = flex_file['Median'] + (flex_file['Median'] * .80)
249
- flex_file['STD'] = flex_file['Median'] / 4
250
  flex_file = flex_file[['Player', 'Floor', 'Median', 'Ceiling', 'STD']]
251
 
252
  hold_file = flex_file
@@ -310,17 +258,13 @@ with tab4:
310
  plot_hold_container = st.empty()
311
  st.plotly_chart(fig, use_container_width=True)
312
 
313
- with tab5:
314
  st.info(t_stamp)
315
  st.info('The Over and Under percentages are a compositve percentage based on simulations, historical performance, and implied probabilities, and may be different than you would expect based purely on the median projection. Likewise, the Edge of a bet is not the only indicator of if you should make the bet or not as the suggestion is using a base acceptable threshold to determine how much edge you should have for each stat category.')
316
  if st.button("Reset Data/Load Data", key='reset5'):
317
  st.cache_data.clear()
318
- game_model = game_betting_model()
319
- overall_stats = player_stat_table()
320
- qb_stats = overall_stats.loc[overall_stats['Position'] == 'QB']
321
- non_qb_stats = overall_stats.loc[overall_stats['Position'] != 'QB']
322
- prop_frame = player_prop_table()
323
- t_stamp = f"Last Update: " + str(prop_frame['timestamp'][0]) + f" CST"
324
  col1, col2 = st.columns([1, 5])
325
 
326
  with col2:
@@ -330,63 +274,111 @@ with tab5:
330
  export_container = st.empty()
331
 
332
  with col1:
333
- prop_type_var = st.selectbox('Select prop category', options = ['Pass Yards', 'Rush Yards', 'Receiving Yards'])
334
 
335
  if st.button('Simulate Prop Category'):
336
  with col2:
337
 
338
  with df_hold_container.container():
339
 
340
- if prop_type_var == "Pass Yards":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
342
- prop_df = prop_df.loc[prop_df['prop_type'] == 'pass_yards']
343
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
344
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
345
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
346
  st.table(prop_df)
347
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
348
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
349
- df = pd.merge(overall_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
350
- elif prop_type_var == "Rush Yards":
351
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
352
- prop_df = prop_df.loc[prop_df['prop_type'] == 'rush_yards']
353
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
354
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
355
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
356
  st.table(prop_df)
357
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
358
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
359
- df = pd.merge(overall_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
360
- elif prop_type_var == "Receiving Yards":
361
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
362
- prop_df = prop_df.loc[prop_df['prop_type'] == 'rec_yards']
363
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
364
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
365
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
366
  st.table(prop_df)
367
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
368
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
369
- df = pd.merge(overall_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
 
 
 
 
 
 
 
 
 
 
370
 
371
  prop_dict = dict(zip(df.Player, df.Prop))
372
  over_dict = dict(zip(df.Player, df.Over))
373
  under_dict = dict(zip(df.Player, df.Under))
374
 
375
- total_sims = 1000
376
 
377
  df.replace("", 0, inplace=True)
378
 
379
- if prop_type_var == "Pass Yards":
380
- df['Median'] = df['pass_yards']
381
- elif prop_type_var == "Rush Yards":
382
- df['Median'] = df['rush_yards']
383
- elif prop_type_var == "Receiving Yards":
384
- df['Median'] = df['rec_yards']
 
 
 
 
 
 
 
 
385
 
386
  flex_file = df
387
- flex_file['Floor'] = flex_file['Median'] * .20
388
- flex_file['Ceiling'] = flex_file['Median'] + (flex_file['Median'] * .80)
389
- flex_file['STD'] = flex_file['Median'] / 4
390
  flex_file['Prop'] = flex_file['Player'].map(prop_dict)
391
  flex_file = flex_file[['Player', 'Prop', 'Floor', 'Median', 'Ceiling', 'STD']]
392
 
@@ -448,7 +440,7 @@ with tab5:
448
  st.download_button(
449
  label="Export Projections",
450
  data=convert_df_to_csv(final_outcomes),
451
- file_name='NFL_prop_proj.csv',
452
  mime='text/csv',
453
  key='prop_proj',
454
  )
 
1
+ import streamlit as st
2
+ st.set_page_config(layout="wide")
3
+
4
+ for name in dir():
5
+ if not name.startswith('_'):
6
+ del globals()[name]
7
+
8
  import numpy as np
9
  import pandas as pd
10
  import streamlit as st
11
  import gspread
12
  import plotly.express as px
13
+ import random
14
+ import gc
15
+
16
+ @st.cache_resource
17
+ def init_conn():
18
+ scope = ['https://www.googleapis.com/auth/spreadsheets',
19
+ "https://www.googleapis.com/auth/drive"]
20
+
21
+ credentials = {
22
+ "type": "service_account",
23
+ "project_id": "model-sheets-connect",
24
+ "private_key_id": "0e0bc2fdef04e771172fe5807392b9d6639d945e",
25
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDiu1v/e6KBKOcK\ncx0KQ23nZK3ZVvADYy8u/RUn/EDI82QKxTd/DizRLIV81JiNQxDJXSzgkbwKYEDm\n48E8zGvupU8+Nk76xNPakrQKy2Y8+VJlq5psBtGchJTuUSHcXU5Mg2JhQsB376PJ\nsCw552K6Pw8fpeMDJDZuxpKSkaJR6k9G5Dhf5q8HDXnC5Rh/PRFuKJ2GGRpX7n+2\nhT/sCax0J8jfdTy/MDGiDfJqfQrOPrMKELtsGHR9Iv6F4vKiDqXpKfqH+02E9ptz\nBk+MNcbZ3m90M8ShfRu28ebebsASfarNMzc3dk7tb3utHOGXKCf4tF8yYKo7x8BZ\noO9X4gSfAgMBAAECggEAU8ByyMpSKlTCF32TJhXnVJi/kS+IhC/Qn5JUDMuk4LXr\naAEWsWO6kV/ZRVXArjmuSzuUVrXumISapM9Ps5Ytbl95CJmGDiLDwRL815nvv6k3\nUyAS8EGKjz74RpoIoH6E7EWCAzxlnUgTn+5oP9Flije97epYk3H+e2f1f5e1Nn1d\nYNe8U+1HqJgILcxA1TAUsARBfoD7+K3z/8DVPHI8IpzAh6kTHqhqC23Rram4XoQ6\nzj/ZdVBjvnKuazETfsD+Vl3jGLQA8cKQVV70xdz3xwLcNeHsbPbpGBpZUoF73c65\nkAXOrjYl0JD5yAk+hmYhXr6H9c6z5AieuZGDrhmlFQKBgQDzV6LRXmjn4854DP/J\nI82oX2GcI4eioDZPRukhiQLzYerMQBmyqZIRC+/LTCAhYQSjNgMa+ZKyvLqv48M0\n/x398op/+n3xTs+8L49SPI48/iV+mnH7k0WI/ycd4OOKh8rrmhl/0EWb9iitwJYe\nMjTV/QxNEpPBEXfR1/mvrN/lVQKBgQDuhomOxUhWVRVH6x03slmyRBn0Oiw4MW+r\nrt1hlNgtVmTc5Mu+4G0USMZwYuOB7F8xG4Foc7rIlwS7Ic83jMJxemtqAelwOLdV\nXRLrLWJfX8+O1z/UE15l2q3SUEnQ4esPHbQnZowHLm0mdL14qSVMl1mu1XfsoZ3z\nJZTQb48CIwKBgEWbzQRtKD8lKDupJEYqSrseRbK/ax43DDITS77/DWwHl33D3FYC\nMblUm8ygwxQpR4VUfwDpYXBlklWcJovzamXpSnsfcYVkkQH47NuOXPXPkXQsw+w+\nDYcJzeu7F/vZqk9I7oBkWHUrrik9zPNoUzrfPvSRGtkAoTDSwibhoc5dAoGBAMHE\nK0T/ANeZQLNuzQps6S7G4eqjwz5W8qeeYxsdZkvWThOgDd/ewt3ijMnJm5X05hOn\ni4XF1euTuvUl7wbqYx76Wv3/1ZojiNNgy7ie4rYlyB/6vlBS97F4ZxJdxMlabbCW\n6b3EMWa4EVVXKoA1sCY7IVDE+yoQ1JYsZmq45YzPAoGBANWWHuVueFGZRDZlkNlK\nh5OmySmA0NdNug3G1upaTthyaTZ+CxGliwBqMHAwpkIRPwxUJpUwBTSEGztGTAxs\nWsUOVWlD2/1JaKSmHE8JbNg6sxLilcG6WEDzxjC5dLL1OrGOXj9WhC9KX3sq6qb6\nF/j9eUXfXjAlb042MphoF3ZC\n-----END PRIVATE KEY-----\n",
26
+ "client_email": "gspread-connection@model-sheets-connect.iam.gserviceaccount.com",
27
+ "client_id": "100369174533302798535",
28
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
29
+ "token_uri": "https://oauth2.googleapis.com/token",
30
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
31
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gspread-connection%40model-sheets-connect.iam.gserviceaccount.com"
32
+ }
33
+
34
+ gc_con = gspread.service_account_from_dict(credentials)
35
+
36
+ return gc_con
37
+
38
+ gcservice_account = init_conn()
39
+
40
+ master_hold = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-bD6_u9pqrYE6Yrlbb_wEkmH-ot0/edit#gid=853878325'
41
+
42
+ @st.cache_resource(ttl = 300)
43
+ def init_baselines():
44
  sh = gc.open_by_url(master_hold)
45
+ worksheet = sh.worksheet('Betting Model Clean')
46
  raw_display = pd.DataFrame(worksheet.get_all_records())
47
  raw_display.replace('#DIV/0!', np.nan, inplace=True)
48
+ game_model = raw_display.dropna()
 
 
49
 
50
+ worksheet = sh.worksheet('DK_Build_Up')
 
 
 
51
  raw_display = pd.DataFrame(worksheet.get_all_records())
52
  raw_display.replace('', np.nan, inplace=True)
53
+ player_stats = raw_display.dropna()
54
+
55
+ worksheet = sh.worksheet('Timestamp')
56
+ timestamp = worksheet.acell('A1').value
 
 
 
 
 
 
 
57
 
58
+ worksheet = sh.worksheet('Prop_Frame')
 
 
 
59
  raw_display = pd.DataFrame(worksheet.get_all_records())
60
  raw_display.replace('', np.nan, inplace=True)
61
+ prop_frame = raw_display.dropna()
62
 
63
+ return game_model, player_stats, prop_frame, timestamp
64
 
65
+ def convert_df_to_csv(df):
66
+ return df.to_csv().encode('utf-8')
67
+
68
+ game_model, player_stats, prop_frame, timestamp = init_baselines()
 
 
69
  t_stamp = f"Last Update: " + str(timestamp) + f" CST"
70
 
71
  tab1, tab2, tab3, tab4, tab5 = st.tabs(["Game Betting Model", "QB Projections", "RB/WR/TE Projections", "Player Prop Simulations", "Stat Specific Simulations"])
72
 
 
 
 
73
  with tab1:
74
  st.info(t_stamp)
75
  if st.button("Reset Data", key='reset1'):
76
  st.cache_data.clear()
77
+ game_model, player_stats, prop_frame, timestamp = init_baselines()
78
+ t_stamp = f"Last Update: " + str(timestamp) + f" CST"
 
 
 
 
79
  line_var1 = st.radio('How would you like to display odds?', options = ['Percentage', 'American'], key='line_var1')
80
  team_frame = game_model
81
  if line_var1 == 'Percentage':
82
+ team_frame = team_frame[['Team', 'Opp', 'Team Points', 'Opp Points', 'Proj Total', 'Proj Spread', 'Proj Winner', 'Win%']]
83
+ team_frame = team_frame.set_index('Team')
84
+ st.dataframe(team_frame.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
85
  if line_var1 == 'American':
86
+ team_frame = team_frame[['Team', 'Opp', 'Team Points', 'Opp Points', 'Proj Total', 'Proj Spread', 'Proj Winner', 'Odds Line']]
87
+ team_frame = team_frame.set_index('Team')
88
  st.dataframe(team_frame.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
89
 
90
  st.download_button(
91
  label="Export Team Model",
92
  data=convert_df_to_csv(team_frame),
93
+ file_name='NBA_team_betting_export.csv',
94
  mime='text/csv',
95
  key='team_export',
96
  )
 
99
  st.info(t_stamp)
100
  if st.button("Reset Data", key='reset2'):
101
  st.cache_data.clear()
102
+ game_model, player_stats, prop_frame, timestamp = init_baselines()
103
+ t_stamp = f"Last Update: " + str(timestamp) + f" CST"
 
 
 
 
104
  split_var1 = st.radio("Would you like to view all teams or specific ones?", ('All', 'Specific Teams'), key='split_var1')
105
  if split_var1 == 'Specific Teams':
106
+ team_var1 = st.multiselect('Which teams would you like to include in the tables?', options = player_stats['Team'].unique(), key='team_var1')
107
  elif split_var1 == 'All':
108
+ team_var1 = player_stats.Team.values.tolist()
109
+ player_stats = player_stats[player_stats['Team'].isin(team_var1)]
110
+ player_stats = player_stats.set_index('Player')
111
+ player_stats = player_stats.sort_values(by='Fantasy', ascending=False)
112
+ st.dataframe(player_stats.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
113
  st.download_button(
114
  label="Export Prop Model",
115
+ data=convert_df_to_csv(player_stats),
116
+ file_name='NBA_stats_export.csv',
117
  mime='text/csv',
118
  key='pitcher_prop_export',
119
  )
120
+
121
  with tab3:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  st.info(t_stamp)
123
  if st.button("Reset Data", key='reset4'):
124
  st.cache_data.clear()
125
+ game_model, player_stats, prop_frame, timestamp = init_baselines()
126
+ t_stamp = f"Last Update: " + str(timestamp) + f" CST"
 
 
 
 
127
  col1, col2 = st.columns([1, 5])
128
 
129
  with col2:
 
132
  plot_hold_container = st.empty()
133
 
134
  with col1:
135
+ player_check = st.selectbox('Select player to simulate props', options = player_stats['Player'].unique())
136
+ prop_type_var = st.selectbox('Select type of prop to simulate', options = ['points', 'rebounds', 'assists', 'blocks', 'steals',
137
+ 'PRA', 'points+rebounds', 'points+assists', 'rebounds+assists'])
138
 
139
  ou_var = st.selectbox('Select wether it is an over or under', options = ['Over', 'Under'])
140
+ if prop_type_var == 'points':
141
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 50.5, value = 15.5, step = .5)
142
+ elif prop_type_var == 'rebounds':
143
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 25.5, value = 5.5, step = .5)
144
+ elif prop_type_var == 'assists':
145
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 25.5, value = 5.5, step = .5)
146
+ elif prop_type_var == 'blocks':
147
  prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 5.5, value = 1.5, step = .5)
148
+ elif prop_type_var == 'steals':
 
 
149
  prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 5.5, value = 1.5, step = .5)
150
+ elif prop_type_var == 'PRA':
151
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 65.5, value = 20.5, step = .5)
152
+ elif prop_type_var == 'points+rebounds':
153
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 45.5, value = 10.5, step = .5)
154
+ elif prop_type_var == 'points+assists':
155
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 45.5, value = 10.5, step = .5)
156
+ elif prop_type_var == 'rebounds+assists':
157
+ prop_var = st.number_input('Type in the prop offered (i.e 5.5)', min_value = 0.0, max_value = 45.5, value = 10.5, step = .5)
158
+ line_var = st.number_input('Type in the line on the prop (i.e. -120)', min_value = -1500, max_value = 1500, value = -150, step = 1)
 
 
 
 
159
  line_var = line_var + 1
160
 
161
  if st.button('Simulate Prop'):
 
163
 
164
  with df_hold_container.container():
165
 
166
+ df = player_stats
167
 
168
  total_sims = 5000
169
 
 
172
  player_var = df.loc[df['Player'] == player_check]
173
  player_var = player_var.reset_index()
174
 
175
+ if prop_type_var == 'points':
176
+ df['Median'] = df['Points']
177
+ elif prop_type_var == 'rebounds':
178
+ df['Median'] = df['Rebounds']
179
+ elif prop_type_var == 'assists':
180
+ df['Median'] = df['Assists']
181
+ elif prop_type_var == 'blocks':
182
+ df['Median'] = df['Blocks']
183
+ elif prop_type_var == 'steals':
184
+ df['Median'] = df['Steals']
185
+ elif prop_type_var == 'PRA':
186
+ df['Median'] = df['Points'] + df['Rebounds'] + df['Assists']
187
+ elif prop_type_var == 'points+rebounds':
188
+ df['Median'] = df['Points'] + df['Rebounds']
189
+ elif prop_type_var == 'points+assists':
190
+ df['Median'] = df['Points'] + df['Assists']
191
+ elif prop_type_var == 'rebounds+assists':
192
+ df['Median'] = df['Assists'] + df['Rebounds']
 
 
193
 
194
  flex_file = df
195
+ flex_file['Floor'] = (flex_file['Median'] * .25) + (flex_file['Minutes'] * .25)
196
+ flex_file['Ceiling'] = flex_file['Median'] + 10 + (flex_file['Minutes'] * .25)
197
+ flex_file['STD'] = (flex_file['Median']/4)
198
  flex_file = flex_file[['Player', 'Floor', 'Median', 'Ceiling', 'STD']]
199
 
200
  hold_file = flex_file
 
258
  plot_hold_container = st.empty()
259
  st.plotly_chart(fig, use_container_width=True)
260
 
261
+ with tab4:
262
  st.info(t_stamp)
263
  st.info('The Over and Under percentages are a compositve percentage based on simulations, historical performance, and implied probabilities, and may be different than you would expect based purely on the median projection. Likewise, the Edge of a bet is not the only indicator of if you should make the bet or not as the suggestion is using a base acceptable threshold to determine how much edge you should have for each stat category.')
264
  if st.button("Reset Data/Load Data", key='reset5'):
265
  st.cache_data.clear()
266
+ game_model, player_stats, prop_frame, timestamp = init_baselines()
267
+ t_stamp = f"Last Update: " + str(timestamp) + f" CST"
 
 
 
 
268
  col1, col2 = st.columns([1, 5])
269
 
270
  with col2:
 
274
  export_container = st.empty()
275
 
276
  with col1:
277
+ prop_type_var = st.selectbox('Select prop category', options = ['points', 'rebounds', 'assists', 'PRA', 'points+rebounds', 'points+assists', 'rebounds+assists'])
278
 
279
  if st.button('Simulate Prop Category'):
280
  with col2:
281
 
282
  with df_hold_container.container():
283
 
284
+ if prop_type_var == "points":
285
+ prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
286
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'points']
287
+ prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
288
+ prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
289
+ prop_df = prop_df.loc[prop_df['Prop'] != 0]
290
+ st.table(prop_df)
291
+ prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
292
+ prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
293
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
294
+ elif prop_type_var == "rebounds":
295
+ prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
296
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'rebounds']
297
+ prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
298
+ prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
299
+ prop_df = prop_df.loc[prop_df['Prop'] != 0]
300
+ st.table(prop_df)
301
+ prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
302
+ prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
303
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
304
+ elif prop_type_var == "assists":
305
+ prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
306
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'assists']
307
+ prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
308
+ prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
309
+ prop_df = prop_df.loc[prop_df['Prop'] != 0]
310
+ st.table(prop_df)
311
+ prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
312
+ prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
313
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
314
+ elif prop_type_var == "PRA":
315
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
316
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'PRA']
317
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
318
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
319
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
320
  st.table(prop_df)
321
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
322
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
323
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
324
+ elif prop_type_var == "points+rebounds":
325
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
326
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'points+rebounds']
327
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
328
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
329
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
330
  st.table(prop_df)
331
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
332
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
333
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
334
+ elif prop_type_var == "points+assists":
335
  prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
336
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'points+assists']
337
  prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
338
  prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
339
  prop_df = prop_df.loc[prop_df['Prop'] != 0]
340
  st.table(prop_df)
341
  prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
342
  prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
343
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
344
+ elif prop_type_var == "rebounds+assists":
345
+ prop_df = prop_frame[['Player', 'over_prop', 'over_line', 'under_line', 'prop_type']]
346
+ prop_df = prop_df.loc[prop_df['prop_type'] == 'rebounds+assists']
347
+ prop_df = prop_df[['Player', 'over_prop', 'over_line', 'under_line']]
348
+ prop_df.rename(columns={"over_prop": "Prop"}, inplace = True)
349
+ prop_df = prop_df.loc[prop_df['Prop'] != 0]
350
+ st.table(prop_df)
351
+ prop_df['Over'] = np.where(prop_df['over_line'] < 0, (-(prop_df['over_line'])/((-(prop_df['over_line']))+101)), 101/(prop_df['over_line']+101))
352
+ prop_df['Under'] = np.where(prop_df['under_line'] < 0, (-(prop_df['under_line'])/((-(prop_df['under_line']))+101)), 101/(prop_df['under_line']+101))
353
+ df = pd.merge(player_stats, prop_df, how='left', left_on=['Player'], right_on = ['Player'])
354
 
355
  prop_dict = dict(zip(df.Player, df.Prop))
356
  over_dict = dict(zip(df.Player, df.Over))
357
  under_dict = dict(zip(df.Player, df.Under))
358
 
359
+ total_sims = 5000
360
 
361
  df.replace("", 0, inplace=True)
362
 
363
+ if prop_type_var == 'points':
364
+ df['Median'] = df['Points']
365
+ elif prop_type_var == 'rebounds':
366
+ df['Median'] = df['Rebounds']
367
+ elif prop_type_var == 'assists':
368
+ df['Median'] = df['Assists']
369
+ elif prop_type_var == 'PRA':
370
+ df['Median'] = df['Points'] + df['Rebounds'] + df['Assists']
371
+ elif prop_type_var == 'points+rebounds':
372
+ df['Median'] = df['Points'] + df['Rebounds']
373
+ elif prop_type_var == 'points+assists':
374
+ df['Median'] = df['Points'] + df['Assists']
375
+ elif prop_type_var == 'rebounds+assists':
376
+ df['Median'] = df['Assists'] + df['Rebounds']
377
 
378
  flex_file = df
379
+ flex_file['Floor'] = (flex_file['Median'] * .25) + (flex_file['Minutes'] * .25)
380
+ flex_file['Ceiling'] = flex_file['Median'] + 10 + (flex_file['Minutes'] * .25)
381
+ flex_file['STD'] = (flex_file['Median']/4)
382
  flex_file['Prop'] = flex_file['Player'].map(prop_dict)
383
  flex_file = flex_file[['Player', 'Prop', 'Floor', 'Median', 'Ceiling', 'STD']]
384
 
 
440
  st.download_button(
441
  label="Export Projections",
442
  data=convert_df_to_csv(final_outcomes),
443
+ file_name='Nba_prop_proj.csv',
444
  mime='text/csv',
445
  key='prop_proj',
446
  )