James McCool commited on
Commit
88c5476
·
1 Parent(s): c751a44

Implement initial Streamlit application with MongoDB integration, including player statistics analysis and stack generation features. Add configuration files for deployment and specify required packages in requirements.txt.

Browse files
Files changed (3) hide show
  1. app.py +278 -0
  2. app.yaml +10 -0
  3. requirements.txt +9 -0
app.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ import streamlit as st
4
+ from itertools import combinations
5
+ import pymongo
6
+
7
+ st.set_page_config(layout="wide")
8
+
9
+ @st.cache_resource
10
+ def init_conn():
11
+
12
+ uri = st.secrets['mongo_uri']
13
+ client = pymongo.MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=500000)
14
+ db = client["MLB_Database"]
15
+
16
+ return db
17
+
18
+ db = init_conn()
19
+
20
+ game_format = {'Win Percentage': '{:.2%}','First Inning Lead Percentage': '{:.2%}',
21
+ 'Fifth Inning Lead Percentage': '{:.2%}', '8+ runs': '{:.2%}', 'DK LevX': '{:.2%}', 'FD LevX': '{:.2%}'}
22
+
23
+ team_roo_format = {'Top Score%': '{:.2%}','0 Runs': '{:.2%}', '1 Run': '{:.2%}', '2 Runs': '{:.2%}', '3 Runs': '{:.2%}', '4 Runs': '{:.2%}',
24
+ '5 Runs': '{:.2%}','6 Runs': '{:.2%}', '7 Runs': '{:.2%}', '8 Runs': '{:.2%}', '9 Runs': '{:.2%}', '10 Runs': '{:.2%}'}
25
+
26
+ wrong_acro = ['WSH', 'AZ', 'CHW']
27
+ right_acro = ['WAS', 'ARI', 'CWS']
28
+
29
+ st.markdown("""
30
+ <style>
31
+ /* Tab styling */
32
+ .stTabs [data-baseweb="tab-list"] {
33
+ gap: 8px;
34
+ padding: 4px;
35
+ }
36
+ .stTabs [data-baseweb="tab"] {
37
+ height: 50px;
38
+ white-space: pre-wrap;
39
+ background-color: #FFD700;
40
+ color: white;
41
+ border-radius: 10px;
42
+ gap: 1px;
43
+ padding: 10px 20px;
44
+ font-weight: bold;
45
+ transition: all 0.3s ease;
46
+ }
47
+ .stTabs [aria-selected="true"] {
48
+ background-color: #DAA520;
49
+ color: white;
50
+ }
51
+ .stTabs [data-baseweb="tab"]:hover {
52
+ background-color: #DAA520;
53
+ cursor: pointer;
54
+ }
55
+ </style>""", unsafe_allow_html=True)
56
+
57
+ @st.cache_resource(ttl = 60)
58
+ def init_stat_load():
59
+
60
+ collection = db["Player_Range_Of_Outcomes"]
61
+ cursor = collection.find()
62
+
63
+ raw_display = pd.DataFrame(list(cursor))
64
+ raw_display = raw_display[['Player', 'Position', 'Team', 'Salary', 'Floor', 'Median', 'Ceiling', 'Own%', 'Site', 'Slate']]
65
+ raw_display = raw_display.rename(columns={'Own%': 'Own'})
66
+ initial_concat = raw_display.sort_values(by='Own', ascending=False)
67
+
68
+ return initial_concat
69
+
70
+ @st.cache_data
71
+ def convert_df_to_csv(df):
72
+ return df.to_csv().encode('utf-8')
73
+
74
+ proj_raw = init_stat_load()
75
+
76
+ col1, col2 = st.columns([1, 5])
77
+
78
+ with col1:
79
+ with st.expander("Info and Filters"):
80
+ if st.button("Load/Reset Data", key='reset1'):
81
+ st.cache_data.clear()
82
+ proj_raw, timestamp = init_stat_load()
83
+ t_stamp = f"Last Update: " + str(timestamp) + f" CST"
84
+ for key in st.session_state.keys():
85
+ del st.session_state[key]
86
+ site_var1 = st.radio("What site are you working with?", ('Draftkings', 'Fanduel'), key='site_var1')
87
+ slate_var1 = st.radio("What slate are you working with?", ('Main Slate', 'Secondary Slate'), key='slate_var1')
88
+ if site_var1 == 'Draftkings':
89
+ raw_baselines = proj_raw[proj_raw['Site'] == 'Draftkings']
90
+ if slate_var1 == 'Main Slate':
91
+ raw_baselines = raw_baselines[raw_baselines['Slate'] == 'main_slate']
92
+ elif slate_var1 == 'Secondary Slate':
93
+ raw_baselines = raw_baselines[raw_baselines['Slate'] == 'secondary_slate']
94
+ raw_baselines = raw_baselines.sort_values(by='Own', ascending=False)
95
+ elif site_var1 == 'Fanduel':
96
+ raw_baselines = proj_raw[proj_raw['Site'] == 'Fanduel']
97
+ if slate_var1 == 'Main Slate':
98
+ raw_baselines = raw_baselines[raw_baselines['Slate'] == 'main_slate']
99
+ elif slate_var1 == 'Secondary Slate':
100
+ raw_baselines = raw_baselines[raw_baselines['Slate'] == 'secondary_slate']
101
+ raw_baselines = raw_baselines.sort_values(by='Own', ascending=False)
102
+ split_var2 = st.radio("Would you like to run stack analysis for the full slate or individual teams?", ('Full Slate Run', 'Specific Teams'), key='split_var2')
103
+ if split_var2 == 'Specific Teams':
104
+ team_var2 = st.multiselect('Which teams would you like to include in the analysis?', options = raw_baselines['Team'].unique(), key='team_var2')
105
+ elif split_var2 == 'Full Slate Run':
106
+ team_var2 = raw_baselines.Team.unique().tolist()
107
+ pos_split2 = st.radio("Are you viewing all positions, specific groups, or specific positions?", ('All Positions', 'Specific Positions'), key='pos_split2')
108
+ if pos_split2 == 'Specific Positions':
109
+ pos_var2 = st.multiselect('What Positions would you like to view?', options = ['SP', 'P', 'C', '1B', '2B', '3B', 'SS', 'OF'])
110
+ elif pos_split2 == 'All Positions':
111
+ pos_var2 = 'All'
112
+ if site_var1 == 'Draftkings':
113
+ max_sal2 = st.number_input('Max Salary', min_value = 5000, max_value = 50000, value = 35000, step = 100, key='max_sal2')
114
+ elif site_var1 == 'Fanduel':
115
+ max_sal2 = st.number_input('Max Salary', min_value = 5000, max_value = 35000, value = 25000, step = 100, key='max_sal2')
116
+ size_var2 = st.selectbox('What size of stacks are you analyzing?', options = ['3-man', '4-man', '5-man'])
117
+ if size_var2 == '3-man':
118
+ stack_size = 3
119
+ if size_var2 == '4-man':
120
+ stack_size = 4
121
+ if size_var2 == '5-man':
122
+ stack_size = 5
123
+
124
+ team_dict = dict(zip(raw_baselines.Player, raw_baselines.Team))
125
+ proj_dict = dict(zip(raw_baselines.Player, raw_baselines.Median))
126
+ own_dict = dict(zip(raw_baselines.Player, raw_baselines.Own))
127
+ cost_dict = dict(zip(raw_baselines.Player, raw_baselines.Salary))
128
+
129
+ with col2:
130
+ stack_hold_container = st.empty()
131
+ if st.button('Run stack analysis'):
132
+ comb_list = []
133
+ if pos_split2 == 'All Positions':
134
+ raw_baselines = raw_baselines
135
+ elif pos_split2 != 'All Positions':
136
+ raw_baselines = raw_baselines[raw_baselines['Position'].str.contains('|'.join(pos_var2))]
137
+
138
+ for cur_team in team_var2:
139
+ working_baselines = raw_baselines
140
+ working_baselines = working_baselines[working_baselines['Team'] == cur_team]
141
+ working_baselines = working_baselines[working_baselines['Position'] != 'SP']
142
+ working_baselines = working_baselines[working_baselines['Position'] != 'P']
143
+ order_list = working_baselines['Player']
144
+
145
+ comb = combinations(order_list, stack_size)
146
+
147
+ for i in list(comb):
148
+ comb_list.append(i)
149
+
150
+ comb_DF = pd.DataFrame(comb_list)
151
+
152
+ if stack_size == 3:
153
+ comb_DF['Team'] = comb_DF[0].map(team_dict)
154
+
155
+ comb_DF['Proj'] = sum([comb_DF[0].map(proj_dict),
156
+ comb_DF[1].map(proj_dict),
157
+ comb_DF[2].map(proj_dict)])
158
+
159
+ comb_DF['Salary'] = sum([comb_DF[0].map(cost_dict),
160
+ comb_DF[1].map(cost_dict),
161
+ comb_DF[2].map(cost_dict)])
162
+
163
+ comb_DF['Own%'] = sum([comb_DF[0].map(own_dict),
164
+ comb_DF[1].map(own_dict),
165
+ comb_DF[2].map(own_dict)])
166
+ elif stack_size == 4:
167
+ comb_DF['Team'] = comb_DF[0].map(team_dict)
168
+
169
+ comb_DF['Proj'] = sum([comb_DF[0].map(proj_dict),
170
+ comb_DF[1].map(proj_dict),
171
+ comb_DF[2].map(proj_dict),
172
+ comb_DF[3].map(proj_dict)])
173
+
174
+ comb_DF['Salary'] = sum([comb_DF[0].map(cost_dict),
175
+ comb_DF[1].map(cost_dict),
176
+ comb_DF[2].map(cost_dict),
177
+ comb_DF[3].map(cost_dict)])
178
+
179
+ comb_DF['Own%'] = sum([comb_DF[0].map(own_dict),
180
+ comb_DF[1].map(own_dict),
181
+ comb_DF[2].map(own_dict),
182
+ comb_DF[3].map(own_dict)])
183
+ elif stack_size == 5:
184
+ comb_DF['Team'] = comb_DF[0].map(team_dict)
185
+
186
+ comb_DF['Proj'] = sum([comb_DF[0].map(proj_dict),
187
+ comb_DF[1].map(proj_dict),
188
+ comb_DF[2].map(proj_dict),
189
+ comb_DF[3].map(proj_dict),
190
+ comb_DF[4].map(proj_dict)])
191
+
192
+ comb_DF['Salary'] = sum([comb_DF[0].map(cost_dict),
193
+ comb_DF[1].map(cost_dict),
194
+ comb_DF[2].map(cost_dict),
195
+ comb_DF[3].map(cost_dict),
196
+ comb_DF[4].map(cost_dict)])
197
+
198
+ comb_DF['Own%'] = sum([comb_DF[0].map(own_dict),
199
+ comb_DF[1].map(own_dict),
200
+ comb_DF[2].map(own_dict),
201
+ comb_DF[3].map(own_dict),
202
+ comb_DF[4].map(own_dict)])
203
+
204
+ comb_DF = comb_DF.sort_values(by='Proj', ascending=False)
205
+ comb_DF = comb_DF.loc[comb_DF['Salary'] <= max_sal2]
206
+
207
+ cut_var = 0
208
+
209
+ if stack_size == 3:
210
+ while cut_var <= int(len(comb_DF)):
211
+ try:
212
+ if int(cut_var) == 0:
213
+ cur_proj = float(comb_DF.iat[cut_var,4])
214
+ cur_own = float(comb_DF.iat[cut_var,6])
215
+ elif int(cut_var) >= 1:
216
+ check_own = float(comb_DF.iat[cut_var,6])
217
+ if check_own > cur_own:
218
+ comb_DF = comb_DF.drop([cut_var])
219
+ cur_own = cur_own
220
+ cut_var = cut_var - 1
221
+ comb_DF = comb_DF.reset_index()
222
+ comb_DF = comb_DF.drop(['index'], axis=1)
223
+ elif check_own <= cur_own:
224
+ cur_own = float(comb_DF.iat[cut_var,6])
225
+ cut_var = cut_var
226
+ cut_var += 1
227
+ except:
228
+ cut_var += 1
229
+ elif stack_size == 4:
230
+ while cut_var <= int(len(comb_DF)):
231
+ try:
232
+ if int(cut_var) == 0:
233
+ cur_proj = float(comb_DF.iat[cut_var,5])
234
+ cur_own = float(comb_DF.iat[cut_var,7])
235
+ elif int(cut_var) >= 1:
236
+ check_own = float(comb_DF.iat[cut_var,7])
237
+ if check_own > cur_own:
238
+ comb_DF = comb_DF.drop([cut_var])
239
+ cur_own = cur_own
240
+ cut_var = cut_var - 1
241
+ comb_DF = comb_DF.reset_index()
242
+ comb_DF = comb_DF.drop(['index'], axis=1)
243
+ elif check_own <= cur_own:
244
+ cur_own = float(comb_DF.iat[cut_var,7])
245
+ cut_var = cut_var
246
+ cut_var += 1
247
+ except:
248
+ cut_var += 1
249
+ elif stack_size == 5:
250
+ while cut_var <= int(len(comb_DF)):
251
+ try:
252
+ if int(cut_var) == 0:
253
+ cur_proj = float(comb_DF.iat[cut_var,6])
254
+ cur_own = float(comb_DF.iat[cut_var,8])
255
+ elif int(cut_var) >= 1:
256
+ check_own = float(comb_DF.iat[cut_var,8])
257
+ if check_own > cur_own:
258
+ comb_DF = comb_DF.drop([cut_var])
259
+ cur_own = cur_own
260
+ cut_var = cut_var - 1
261
+ comb_DF = comb_DF.reset_index()
262
+ comb_DF = comb_DF.drop(['index'], axis=1)
263
+ elif check_own <= cur_own:
264
+ cur_own = float(comb_DF.iat[cut_var,8])
265
+ cut_var = cut_var
266
+ cut_var += 1
267
+ except:
268
+ cut_var += 1
269
+
270
+ with stack_hold_container:
271
+ stack_hold_container = st.empty()
272
+ st.dataframe(comb_DF.style.background_gradient(axis=0).background_gradient(cmap='RdYlGn').format(precision=2), use_container_width = True)
273
+ st.download_button(
274
+ label="Export Tables",
275
+ data=convert_df_to_csv(comb_DF),
276
+ file_name='MLB_Stack_Options_export.csv',
277
+ mime='text/csv',
278
+ )
app.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ runtime: python
2
+ env: flex
3
+
4
+ runtime_config:
5
+ python_version: 3
6
+
7
+ entrypoint: streamlit run streamlit-app.py --server.port $PORT
8
+
9
+ automatic_scaling:
10
+ max_num_instances: 200
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ gspread
3
+ openpyxl
4
+ matplotlib
5
+ pymongo
6
+ pulp
7
+ docker
8
+ plotly
9
+ scipy