Spaces:
Sleeping
Sleeping
James McCool
commited on
Commit
·
28226be
1
Parent(s):
938de6b
Lots of additions and improvements to the NHL update functions and addition of the NBA functions
Browse files- func/NBA_own_regress.py +229 -0
- func/NHL_own_regress.py +79 -94
- func/dk_nba_go/NBA_seed_frames.go +1133 -0
- func/fd_nba_go/NBA_seed_frames.go +1118 -0
- func/showdown_go/showdown_seed_frames.go +1006 -0
- requirements.txt +3 -0
- src/database.py +3 -0
- src/sports/nba_functions.py +0 -0
- src/sports/nhl_functions.py +14 -24
- src/streamlit_app.py +248 -3
func/NBA_own_regress.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pymongo
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import xgboost as xgb
|
| 5 |
+
import lightgbm as lgb
|
| 6 |
+
|
| 7 |
+
from sklearn.model_selection import train_test_split
|
| 8 |
+
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
|
| 9 |
+
from sklearn.svm import SVR
|
| 10 |
+
from sklearn.neighbors import KNeighborsRegressor
|
| 11 |
+
from sklearn.linear_model import LinearRegression
|
| 12 |
+
|
| 13 |
+
from src.database import *
|
| 14 |
+
|
| 15 |
+
collection = contest_db["NBA_reg_exposure_frames"]
|
| 16 |
+
cursor = collection.find()
|
| 17 |
+
raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
|
| 18 |
+
raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
|
| 19 |
+
raw_display = raw_display[raw_display['Actual'].between(1, 100)]
|
| 20 |
+
|
| 21 |
+
print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
|
| 22 |
+
|
| 23 |
+
collection = nba_db["Player_Range_Of_Outcomes"]
|
| 24 |
+
cursor = collection.find()
|
| 25 |
+
raw_projections = pd.DataFrame(list(cursor))
|
| 26 |
+
try:
|
| 27 |
+
raw_projections = raw_projections[['Player', 'Minutes Proj', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '4x%', '5x%', '6x%', 'GPP%',
|
| 28 |
+
'Own', 'Small_Own', 'Large_Own', 'Cash_Own', 'CPT_Own', 'LevX', 'ValX', 'site', 'version', 'slate', 'timestamp', 'player_ID']]
|
| 29 |
+
except:
|
| 30 |
+
raw_projections = raw_projections[['Player', 'Minutes Proj', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '4x%', '5x%', '6x%', 'GPP%',
|
| 31 |
+
'Own', 'Small_Own', 'Large_Own', 'Cash_Own', 'CPT_Own', 'LevX', 'ValX', 'site', 'version', 'slate', 'timestamp', 'player_id']]
|
| 32 |
+
raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
|
| 33 |
+
raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
|
| 34 |
+
|
| 35 |
+
current_projections = raw_projections[(raw_projections['slate'] == 'Main Slate') & (raw_projections['site'] == 'Draftkings')]
|
| 36 |
+
|
| 37 |
+
intcols = ['Contest ID', 'Salary']
|
| 38 |
+
floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 39 |
+
percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 40 |
+
stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
|
| 41 |
+
|
| 42 |
+
for col in intcols:
|
| 43 |
+
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
|
| 44 |
+
for col in floatcols:
|
| 45 |
+
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
|
| 46 |
+
for col in percentagecols:
|
| 47 |
+
raw_display[col] = raw_display[col] * 100.0
|
| 48 |
+
for col in stringcols:
|
| 49 |
+
raw_display[col] = raw_display[col].astype(str)
|
| 50 |
+
|
| 51 |
+
df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
|
| 52 |
+
|
| 53 |
+
df_clean['Actual'] = df_clean['Actual'] * .90
|
| 54 |
+
df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
|
| 55 |
+
df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
|
| 56 |
+
df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
|
| 57 |
+
df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 58 |
+
df_clean['base_ownership'] = 800.0 / df_clean['contest_size']
|
| 59 |
+
df_clean['value_play'] = np.where((df_clean['Salary'] <= 4000) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 6.0), 1, 0)
|
| 60 |
+
df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 61 |
+
df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 6.0), 1, 0)
|
| 62 |
+
df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 5.0), 1, 0)
|
| 63 |
+
df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
|
| 64 |
+
lambda x: x / x.sum() * 800
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
# Prepare features and target
|
| 68 |
+
feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
|
| 69 |
+
X = df_clean[feature_cols]
|
| 70 |
+
y = df_clean['ownership_share']
|
| 71 |
+
|
| 72 |
+
# Train-test split
|
| 73 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 74 |
+
|
| 75 |
+
# Create and train model
|
| 76 |
+
xgb_model = xgb.XGBRegressor(
|
| 77 |
+
n_estimators=1000,
|
| 78 |
+
learning_rate=0.10,
|
| 79 |
+
max_depth=5,
|
| 80 |
+
random_state=42,
|
| 81 |
+
base_score=10
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
xgb_model.fit(X_train, y_train)
|
| 85 |
+
|
| 86 |
+
lgb_model = lgb.LGBMRegressor(
|
| 87 |
+
n_estimators=1000, # number of boosting rounds
|
| 88 |
+
learning_rate=0.1, # learning rate
|
| 89 |
+
num_leaves=31, # max number of leaves in one tree
|
| 90 |
+
random_state=42,
|
| 91 |
+
verbose=-1 # suppress warnings during training
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
lgb_model.fit(X_train, y_train / 100)
|
| 95 |
+
|
| 96 |
+
knn_model = KNeighborsRegressor(
|
| 97 |
+
n_neighbors=5,
|
| 98 |
+
weights='distance' # or 'uniform'
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
knn_model.fit(X_train, y_train)
|
| 102 |
+
|
| 103 |
+
__all__ = ['xgb_model', 'lgb_model', 'knn_model']
|
| 104 |
+
|
| 105 |
+
if __name__ == '__main__':
|
| 106 |
+
X_full = df_clean[feature_cols]
|
| 107 |
+
y_full = df_clean['Exposure Overall']
|
| 108 |
+
|
| 109 |
+
# Get predictions from all your models on the full dataset
|
| 110 |
+
y_pred_xgb_full = np.clip(xgb_model.predict(X_full), 0, 100)
|
| 111 |
+
y_pred_lgb_full = np.clip(lgb_model.predict(X_full), 0, 100) * 100
|
| 112 |
+
y_pred_knn_full = np.clip(knn_model.predict(X_full), 0, 100)
|
| 113 |
+
|
| 114 |
+
# Create combo prediction
|
| 115 |
+
y_pred_combo_full = (y_pred_xgb_full + y_pred_lgb_full + y_pred_knn_full) / 3
|
| 116 |
+
|
| 117 |
+
# Create full comparison DataFrame
|
| 118 |
+
comparison_full = pd.DataFrame({
|
| 119 |
+
'Actual_Exposure': y_full.values,
|
| 120 |
+
'XGB': y_pred_xgb_full,
|
| 121 |
+
'LGB': y_pred_lgb_full,
|
| 122 |
+
'KNN': y_pred_knn_full,
|
| 123 |
+
'Combo': y_pred_combo_full,
|
| 124 |
+
'Abs_Error': np.abs(y_full.values - y_pred_combo_full)
|
| 125 |
+
})
|
| 126 |
+
|
| 127 |
+
# Add back the full features for context
|
| 128 |
+
comparison_full = pd.concat([
|
| 129 |
+
X_full.reset_index(drop=True),
|
| 130 |
+
comparison_full.reset_index(drop=True)
|
| 131 |
+
], axis=1)
|
| 132 |
+
|
| 133 |
+
# You can also add the original columns from df_clean for more context
|
| 134 |
+
comparison_full['Player'] = df_clean['Player'].values
|
| 135 |
+
comparison_full['Contest_Date'] = df_clean['Contest Date'].values
|
| 136 |
+
comparison_full['Pos'] = df_clean['Pos'].values
|
| 137 |
+
|
| 138 |
+
# Overall performance metrics on full dataset
|
| 139 |
+
print("\n=== Full Dataset Performance ===")
|
| 140 |
+
for model_name, predictions in [('XGB', y_pred_xgb_full), ('LGB', y_pred_lgb_full), ('KNN', y_pred_knn_full), ('Combo', y_pred_combo_full)]:
|
| 141 |
+
rmse = np.sqrt(mean_squared_error(y_full, predictions))
|
| 142 |
+
mae = mean_absolute_error(y_full, predictions)
|
| 143 |
+
r2 = r2_score(y_full, predictions)
|
| 144 |
+
print(f"{model_name:8} - RMSE: {rmse:6.2f}, MAE: {mae:6.2f}, R²: {r2:6.3f}")
|
| 145 |
+
|
| 146 |
+
# Analysis on full dataset
|
| 147 |
+
print("\n=== Highest Ownership (Full Data) ===")
|
| 148 |
+
print(comparison_full.sort_values('Actual_Exposure', ascending=False).head(10)[
|
| 149 |
+
['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
|
| 150 |
+
])
|
| 151 |
+
|
| 152 |
+
print("\n=== Highest Predicted Ownership (Full Data) ===")
|
| 153 |
+
print(comparison_full.sort_values('Combo', ascending=False).head(10)[
|
| 154 |
+
['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
|
| 155 |
+
])
|
| 156 |
+
|
| 157 |
+
print("\n=== Worst Predictions (Full Data) ===")
|
| 158 |
+
print(comparison_full.nlargest(10, 'Abs_Error')[
|
| 159 |
+
['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
|
| 160 |
+
])
|
| 161 |
+
|
| 162 |
+
print("\n=== Best Predictions (Full Data) ===")
|
| 163 |
+
print(comparison_full.nsmallest(10, 'Abs_Error')[
|
| 164 |
+
['Player', 'Pos', 'Salary', 'Actual', 'value', 'Actual_Exposure', 'XGB', 'LGB', 'KNN', 'Combo', 'Abs_Error']
|
| 165 |
+
])
|
| 166 |
+
|
| 167 |
+
# Prepare the current data with the same feature engineering
|
| 168 |
+
current_projections['Actual'] = current_projections['Median'] # Rename to match training
|
| 169 |
+
current_projections['value'] = current_projections['Actual'] / (current_projections['Salary'] / 1000)
|
| 170 |
+
current_projections['value_adv'] = current_projections['value'] - current_projections['value'].mean()
|
| 171 |
+
current_projections['actual_adv'] = current_projections['Actual'] - current_projections['Actual'].mean()
|
| 172 |
+
|
| 173 |
+
# Create the same engineered features
|
| 174 |
+
# Assuming all rows are from the same contest (current slate)
|
| 175 |
+
current_projections['contest_size'] = len(current_projections) # All players in current slate
|
| 176 |
+
|
| 177 |
+
# Create value_play feature (same logic as training)
|
| 178 |
+
current_projections['value_play'] = np.where(
|
| 179 |
+
(current_projections['Salary'] <= 4000) &
|
| 180 |
+
(current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 6.0),
|
| 181 |
+
1, 0
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
current_projections['value_density'] = current_projections['value_play'].sum() / current_projections['Player'].count()
|
| 185 |
+
|
| 186 |
+
current_projections['base_ownership'] = 800.0 / current_projections['contest_size']
|
| 187 |
+
|
| 188 |
+
current_projections['strong_play'] = np.where((current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 6.0), 1, 0)
|
| 189 |
+
current_projections['punt_play'] = np.where((current_projections['Salary'] < 3500) & (current_projections['Actual'] / (current_projections['Salary'] / 1000) >= 5.0), 1, 0)
|
| 190 |
+
|
| 191 |
+
current_projections['ownership_share'] = current_projections['Own'].sum() / current_projections['Player'].count() * 800
|
| 192 |
+
# Prepare features in the same order as training
|
| 193 |
+
X_current = current_projections[feature_cols]
|
| 194 |
+
|
| 195 |
+
# Make predictions with all your models
|
| 196 |
+
current_projections['XGB'] = np.clip(xgb_model.predict(X_current), 0, 100)
|
| 197 |
+
current_projections['LGB'] = np.clip(lgb_model.predict(X_current), 0, 100) * 100
|
| 198 |
+
current_projections['KNN'] = np.clip(knn_model.predict(X_current), 0, 100)
|
| 199 |
+
|
| 200 |
+
# Create combo prediction
|
| 201 |
+
current_projections['Combo'] = (
|
| 202 |
+
(current_projections['XGB'] * .30) +
|
| 203 |
+
(current_projections['LGB'] * .30) +
|
| 204 |
+
(current_projections['KNN'] * .40)
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
current_projections['Combo'] = np.where((current_projections['value'] < 5.0) & (current_projections['Salary'] < 9000), current_projections['Combo'] * .75, current_projections['Combo'])
|
| 208 |
+
current_projections['Combo'] = np.where((current_projections['Median'] < 18.0), current_projections['Combo'] * .33, current_projections['Combo'])
|
| 209 |
+
current_projections['Combo'] = np.where((current_projections['Salary'] > 5000) & (current_projections['value'] < 4.5), 1, current_projections['Combo'])
|
| 210 |
+
current_projections['Combo'] = np.where(current_projections['value'] > 6.0, current_projections['Combo'] * ((current_projections['value'] / 6.0)), current_projections['Combo'])
|
| 211 |
+
current_projections['Combo'] = np.where((current_projections['Salary'] > 9000), current_projections['Combo'] * .75, current_projections['Combo'])
|
| 212 |
+
current_projections['Combo'] = np.where((current_projections['Salary'] > 9000) & (current_projections['Combo'] < current_projections['value']), current_projections['value'], current_projections['Combo'])
|
| 213 |
+
current_projections['Combo'] = np.where((current_projections['Median'] > 20.0) & (current_projections['Salary'] < 3500), current_projections['Combo'] * (10 - current_projections['strong_play'].sum()).clip(0, 3), current_projections['Combo'])
|
| 214 |
+
current_projections['Combo'] = np.where(current_projections['Position'].str.contains('/'), current_projections['Combo'] * 1.25, current_projections['Combo'] * .75)
|
| 215 |
+
|
| 216 |
+
power_scale = 1.10
|
| 217 |
+
combo_powered = current_projections['Combo'] ** power_scale
|
| 218 |
+
|
| 219 |
+
norm_var = 800.0 / combo_powered.sum()
|
| 220 |
+
current_projections['Combo_powered'] = combo_powered * norm_var
|
| 221 |
+
|
| 222 |
+
# Display predictions sorted by predicted ownership
|
| 223 |
+
print("\n=== Current Slate - Predicted Ownership ===")
|
| 224 |
+
print(f'the strong_play count is {current_projections['strong_play'].sum()}')
|
| 225 |
+
display_cols = ['Player', 'Position', 'Salary', 'Median', 'value', 'Own', 'XGB', 'LGB', 'KNN', 'Combo', 'Combo_powered']
|
| 226 |
+
print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
|
| 227 |
+
print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
|
| 228 |
+
print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
| 229 |
+
print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
func/NHL_own_regress.py
CHANGED
|
@@ -1,13 +1,74 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
"""
|
| 5 |
import xgboost as xgb
|
| 6 |
import lightgbm as lgb
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
from sklearn.neighbors import KNeighborsRegressor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
# Create
|
| 10 |
-
# These will be used as-is or can be trained later if needed
|
| 11 |
xgb_model = xgb.XGBRegressor(
|
| 12 |
n_estimators=1000,
|
| 13 |
learning_rate=0.10,
|
|
@@ -16,104 +77,28 @@ xgb_model = xgb.XGBRegressor(
|
|
| 16 |
base_score=10
|
| 17 |
)
|
| 18 |
|
|
|
|
|
|
|
| 19 |
lgb_model = lgb.LGBMRegressor(
|
| 20 |
-
n_estimators=1000,
|
| 21 |
-
learning_rate=0.1,
|
| 22 |
-
num_leaves=31,
|
| 23 |
random_state=42,
|
| 24 |
-
verbose=-1
|
| 25 |
)
|
| 26 |
|
|
|
|
|
|
|
| 27 |
knn_model = KNeighborsRegressor(
|
| 28 |
n_neighbors=5,
|
| 29 |
-
weights='distance'
|
| 30 |
)
|
| 31 |
|
|
|
|
|
|
|
| 32 |
__all__ = ['xgb_model', 'lgb_model', 'knn_model']
|
| 33 |
|
| 34 |
-
# Training code moved to separate script to avoid slow imports
|
| 35 |
if __name__ == '__main__':
|
| 36 |
-
import pymongo
|
| 37 |
-
import pandas as pd
|
| 38 |
-
import numpy as np
|
| 39 |
-
from sklearn.model_selection import train_test_split
|
| 40 |
-
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
|
| 41 |
-
|
| 42 |
-
def init_conn():
|
| 43 |
-
uri = "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
|
| 44 |
-
client = pymongo.MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=500000)
|
| 45 |
-
contest_db = client["Contest_Information"]
|
| 46 |
-
nba_db = client["NHL_Database"]
|
| 47 |
-
return contest_db, nba_db
|
| 48 |
-
|
| 49 |
-
contest_db, nba_db = init_conn()
|
| 50 |
-
|
| 51 |
-
collection = contest_db["NHL_reg_exposure_frames"]
|
| 52 |
-
cursor = collection.find()
|
| 53 |
-
raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
|
| 54 |
-
raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
|
| 55 |
-
raw_display = raw_display[raw_display['Actual'].between(1, 100)]
|
| 56 |
-
|
| 57 |
-
print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
|
| 58 |
-
|
| 59 |
-
collection = nba_db["Player_Level_ROO"]
|
| 60 |
-
cursor = collection.find()
|
| 61 |
-
raw_projections = pd.DataFrame(list(cursor))
|
| 62 |
-
raw_projections = raw_projections[['Player', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '2x%', '3x%', '4x%', 'Own',
|
| 63 |
-
'Small Field Own%', 'Large Field Own%', 'Cash Own%', 'CPT_Own', 'Site', 'Type', 'Slate', 'player_id', 'timestamp']]
|
| 64 |
-
raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
|
| 65 |
-
raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
|
| 66 |
-
|
| 67 |
-
current_projections = raw_projections[(raw_projections['Slate'] == 'Main Slate') & (raw_projections['Site'] == 'Draftkings')]
|
| 68 |
-
|
| 69 |
-
intcols = ['Contest ID', 'Salary']
|
| 70 |
-
floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 71 |
-
percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 72 |
-
stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
|
| 73 |
-
|
| 74 |
-
for col in intcols:
|
| 75 |
-
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
|
| 76 |
-
for col in floatcols:
|
| 77 |
-
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
|
| 78 |
-
for col in percentagecols:
|
| 79 |
-
raw_display[col] = raw_display[col] * 100.0
|
| 80 |
-
for col in stringcols:
|
| 81 |
-
raw_display[col] = raw_display[col].astype(str)
|
| 82 |
-
|
| 83 |
-
df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
|
| 84 |
-
|
| 85 |
-
df_clean['Actual'] = df_clean['Actual'] * .90
|
| 86 |
-
df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
|
| 87 |
-
df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
|
| 88 |
-
df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
|
| 89 |
-
df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 90 |
-
df_clean['base_ownership'] = 900.0 / df_clean['contest_size']
|
| 91 |
-
df_clean['value_play'] = np.where((df_clean['Salary'] <= 4500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 92 |
-
df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 93 |
-
df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 94 |
-
df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 95 |
-
df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
|
| 96 |
-
lambda x: x / x.sum() * 900
|
| 97 |
-
)
|
| 98 |
-
|
| 99 |
-
# Prepare features and target
|
| 100 |
-
feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
|
| 101 |
-
X = df_clean[feature_cols]
|
| 102 |
-
y = df_clean['ownership_share']
|
| 103 |
-
|
| 104 |
-
# Train-test split
|
| 105 |
-
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 106 |
-
|
| 107 |
-
# Train models
|
| 108 |
-
print("Training XGBoost model...")
|
| 109 |
-
xgb_model.fit(X_train, y_train)
|
| 110 |
-
|
| 111 |
-
print("Training LightGBM model...")
|
| 112 |
-
lgb_model.fit(X_train, y_train / 100)
|
| 113 |
-
|
| 114 |
-
print("Training KNN model...")
|
| 115 |
-
knn_model.fit(X_train, y_train)
|
| 116 |
-
|
| 117 |
X_full = df_clean[feature_cols]
|
| 118 |
y_full = df_clean['Exposure Overall']
|
| 119 |
|
|
@@ -232,4 +217,4 @@ if __name__ == '__main__':
|
|
| 232 |
print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
|
| 233 |
print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
|
| 234 |
print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
| 235 |
-
print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
|
|
|
| 1 |
+
import pymongo
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
|
|
|
| 4 |
import xgboost as xgb
|
| 5 |
import lightgbm as lgb
|
| 6 |
+
|
| 7 |
+
from sklearn.model_selection import train_test_split
|
| 8 |
+
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
|
| 9 |
+
from sklearn.svm import SVR
|
| 10 |
from sklearn.neighbors import KNeighborsRegressor
|
| 11 |
+
from sklearn.linear_model import LinearRegression
|
| 12 |
+
|
| 13 |
+
from src.database import *
|
| 14 |
+
|
| 15 |
+
collection = contest_db["NHL_reg_exposure_frames"]
|
| 16 |
+
cursor = collection.find()
|
| 17 |
+
raw_display = pd.DataFrame(list(cursor)).drop_duplicates(subset=['Player', 'Contest Date', 'Contest ID'])
|
| 18 |
+
raw_display = raw_display[raw_display['Exposure Overall'].between(.0001, 1)]
|
| 19 |
+
raw_display = raw_display[raw_display['Actual'].between(1, 100)]
|
| 20 |
+
|
| 21 |
+
print(raw_display.sort_values('Exposure Overall', ascending=False).head(10))
|
| 22 |
+
|
| 23 |
+
collection = nhl_db["Player_Level_ROO"]
|
| 24 |
+
cursor = collection.find()
|
| 25 |
+
raw_projections = pd.DataFrame(list(cursor))
|
| 26 |
+
raw_projections = raw_projections[['Player', 'Position', 'Team', 'Opp', 'Salary', 'Floor', 'Median', 'Ceiling', 'Top_finish', 'Top_5_finish', 'Top_10_finish', '20+%', '2x%', '3x%', '4x%', 'Own',
|
| 27 |
+
'Small Field Own%', 'Large Field Own%', 'Cash Own%', 'CPT_Own', 'Site', 'Type', 'Slate', 'player_id', 'timestamp']]
|
| 28 |
+
raw_projections = raw_projections.rename(columns={"player_id": "player_ID"})
|
| 29 |
+
raw_projections['Median'] = raw_projections['Median'].replace('', 0).astype(float)
|
| 30 |
+
|
| 31 |
+
current_projections = raw_projections[(raw_projections['Slate'] == 'Main Slate') & (raw_projections['Site'] == 'Draftkings')]
|
| 32 |
+
|
| 33 |
+
intcols = ['Contest ID', 'Salary']
|
| 34 |
+
floatcols = ['Actual', 'Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 35 |
+
percentagecols = ['Exposure Overall', 'Exposure Top 1%', 'Exposure Top 5%', 'Exposure Top 10%', 'Exposure Top 20%']
|
| 36 |
+
stringcols = ['_id', 'Player', 'Pos', 'Contest Date']
|
| 37 |
+
|
| 38 |
+
for col in intcols:
|
| 39 |
+
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(int)
|
| 40 |
+
for col in floatcols:
|
| 41 |
+
raw_display[col] = raw_display[col].replace([np.nan, np.inf, -np.inf], 0).astype(float)
|
| 42 |
+
for col in percentagecols:
|
| 43 |
+
raw_display[col] = raw_display[col] * 100.0
|
| 44 |
+
for col in stringcols:
|
| 45 |
+
raw_display[col] = raw_display[col].astype(str)
|
| 46 |
+
|
| 47 |
+
df_clean = raw_display.dropna(subset=['Salary', 'Actual', 'Exposure Overall']).copy()
|
| 48 |
+
|
| 49 |
+
df_clean['Actual'] = df_clean['Actual'] * .90
|
| 50 |
+
df_clean['value'] = df_clean['Actual'] / (df_clean['Salary'] / 1000)
|
| 51 |
+
df_clean['value_adv'] = df_clean['value'] - df_clean['value'].mean()
|
| 52 |
+
df_clean['actual_adv'] = df_clean['Actual'] - df_clean['Actual'].mean()
|
| 53 |
+
df_clean['contest_size'] = df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 54 |
+
df_clean['base_ownership'] = 900.0 / df_clean['contest_size']
|
| 55 |
+
df_clean['value_play'] = np.where((df_clean['Salary'] <= 4500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 56 |
+
df_clean['value_density'] = df_clean.groupby('Contest ID')['value_play'].transform('sum') / df_clean.groupby('Contest ID')['Player'].transform('count')
|
| 57 |
+
df_clean['strong_play'] = np.where((df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 58 |
+
df_clean['punt_play'] = np.where((df_clean['Salary'] < 3500) & (df_clean['Actual'] / (df_clean['Salary'] / 1000) >= 2.0), 1, 0)
|
| 59 |
+
df_clean['ownership_share'] = df_clean.groupby('Contest ID')['Exposure Overall'].transform(
|
| 60 |
+
lambda x: x / x.sum() * 900
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# Prepare features and target
|
| 64 |
+
feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
|
| 65 |
+
X = df_clean[feature_cols]
|
| 66 |
+
y = df_clean['ownership_share']
|
| 67 |
+
|
| 68 |
+
# Train-test split
|
| 69 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 70 |
|
| 71 |
+
# Create and train model
|
|
|
|
| 72 |
xgb_model = xgb.XGBRegressor(
|
| 73 |
n_estimators=1000,
|
| 74 |
learning_rate=0.10,
|
|
|
|
| 77 |
base_score=10
|
| 78 |
)
|
| 79 |
|
| 80 |
+
xgb_model.fit(X_train, y_train)
|
| 81 |
+
|
| 82 |
lgb_model = lgb.LGBMRegressor(
|
| 83 |
+
n_estimators=1000, # number of boosting rounds
|
| 84 |
+
learning_rate=0.1, # learning rate
|
| 85 |
+
num_leaves=31, # max number of leaves in one tree
|
| 86 |
random_state=42,
|
| 87 |
+
verbose=-1 # suppress warnings during training
|
| 88 |
)
|
| 89 |
|
| 90 |
+
lgb_model.fit(X_train, y_train / 100)
|
| 91 |
+
|
| 92 |
knn_model = KNeighborsRegressor(
|
| 93 |
n_neighbors=5,
|
| 94 |
+
weights='distance' # or 'uniform'
|
| 95 |
)
|
| 96 |
|
| 97 |
+
knn_model.fit(X_train, y_train)
|
| 98 |
+
|
| 99 |
__all__ = ['xgb_model', 'lgb_model', 'knn_model']
|
| 100 |
|
|
|
|
| 101 |
if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
X_full = df_clean[feature_cols]
|
| 103 |
y_full = df_clean['Exposure Overall']
|
| 104 |
|
|
|
|
| 217 |
print(f'sum of Own is {current_projections['Own'].sum()} while sum of combo is {current_projections['Combo'].sum()} while combo_powered is {current_projections['Combo_powered'].sum()}')
|
| 218 |
print(f'sum of position C is {current_projections[current_projections['Position'] == 'C']['Combo_powered'].sum()}')
|
| 219 |
print(current_projections.sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
| 220 |
+
print(current_projections[current_projections['Position'] == 'C'].sort_values('Combo_powered', ascending=False)[display_cols].head(20))
|
func/dk_nba_go/NBA_seed_frames.go
ADDED
|
@@ -0,0 +1,1133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
// Script Imports
|
| 5 |
+
"context"
|
| 6 |
+
"encoding/json"
|
| 7 |
+
"fmt"
|
| 8 |
+
"io/ioutil"
|
| 9 |
+
"math/rand"
|
| 10 |
+
"os"
|
| 11 |
+
"slices"
|
| 12 |
+
"sort"
|
| 13 |
+
"strconv"
|
| 14 |
+
"strings"
|
| 15 |
+
"time"
|
| 16 |
+
|
| 17 |
+
// MongoDB Imports
|
| 18 |
+
"go.mongodb.org/mongo-driver/mongo"
|
| 19 |
+
"go.mongodb.org/mongo-driver/mongo/options"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
type LineupData struct {
|
| 23 |
+
Salary []int32
|
| 24 |
+
Projection []float64
|
| 25 |
+
Team []string
|
| 26 |
+
Team_count []int32
|
| 27 |
+
Secondary []string
|
| 28 |
+
Secondary_count []int32
|
| 29 |
+
Ownership []float64
|
| 30 |
+
Players [][]int32
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
type Player struct {
|
| 34 |
+
ID int32 `json:"id"`
|
| 35 |
+
Name string `json:"name"`
|
| 36 |
+
Team string `json:"team"`
|
| 37 |
+
Position string `json:"position"`
|
| 38 |
+
Salary int32 `json:"salary"`
|
| 39 |
+
Projection float64 `json:"projection"`
|
| 40 |
+
Ownership float64 `json:"ownership"`
|
| 41 |
+
SalaryValue float64 `json:"salary_value"`
|
| 42 |
+
ProjValue float64 `json:"proj_value"`
|
| 43 |
+
OwnValue float64 `json:"own_value"`
|
| 44 |
+
SortValue float64 `json:"sort_value"`
|
| 45 |
+
Slate string `json:"slate"`
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
type PlayerSet struct {
|
| 49 |
+
Players []Player `json:"players"`
|
| 50 |
+
Maps struct {
|
| 51 |
+
NameMap map[string]string `json:"name_map"`
|
| 52 |
+
SalaryMap map[string]int32 `json:"salary_map"`
|
| 53 |
+
ProjectionMap map[string]float64 `json:"projection_map"`
|
| 54 |
+
OwnershipMap map[string]float64 `json:"ownership_map"`
|
| 55 |
+
TeamMap map[string]string `json:"team_map"`
|
| 56 |
+
} `json:"maps"`
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
type ProcessedData struct {
|
| 60 |
+
PlayersMedian PlayerSet `json:"players_median"`
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
type PlayerData struct {
|
| 64 |
+
Players []Player
|
| 65 |
+
NameMap map[int]string
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
type StrengthResult struct {
|
| 69 |
+
Index int
|
| 70 |
+
Data LineupData
|
| 71 |
+
Error error
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
type LineupDocument struct {
|
| 75 |
+
Salary int32 `bson:"salary"`
|
| 76 |
+
Projection float64 `bson:"proj"`
|
| 77 |
+
Team string `bson:"Team"`
|
| 78 |
+
Team_count int32 `bson:"Team_count"`
|
| 79 |
+
Secondary string `bson:"Secondary"`
|
| 80 |
+
Secondary_count int32 `bson:"Secondary_count"`
|
| 81 |
+
Ownership float64 `bson:"Own"`
|
| 82 |
+
PG int32 `bson:"PG"`
|
| 83 |
+
SG int32 `bson:"SG"`
|
| 84 |
+
SF int32 `bson:"SF"`
|
| 85 |
+
PF int32 `bson:"PF"`
|
| 86 |
+
C int32 `bson:"C"`
|
| 87 |
+
G int32 `bson:"G"`
|
| 88 |
+
F int32 `bson:"F"`
|
| 89 |
+
FLEX int32 `bson:"FLEX"`
|
| 90 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
func loadPlayerData() (*ProcessedData, error) {
|
| 94 |
+
data, err := ioutil.ReadFile("dk_nba_go/player_data.json")
|
| 95 |
+
if err != nil {
|
| 96 |
+
return nil, fmt.Errorf("failed to read in data: %v", err)
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
var processedData ProcessedData
|
| 100 |
+
if err := json.Unmarshal(data, &processedData); err != nil {
|
| 101 |
+
return nil, fmt.Errorf("failed to parse json: %v", err)
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
return &processedData, nil
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
func loadOptimals() (map[string]LineupData, error) {
|
| 108 |
+
data, err := ioutil.ReadFile("dk_nba_go/optimal_lineups.json")
|
| 109 |
+
if err != nil {
|
| 110 |
+
return nil, fmt.Errorf("failed to parse optimals: %v", err)
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
type OptimalsJSON struct {
|
| 114 |
+
Slate string `json:"slate"`
|
| 115 |
+
Salary int32 `json:"salary"`
|
| 116 |
+
Projection float64 `json:"projection"`
|
| 117 |
+
Team string `json:"team"`
|
| 118 |
+
Team_count int32 `json:"team_count"`
|
| 119 |
+
Secondary string `json:"secondary"`
|
| 120 |
+
Secondary_count int32 `json:"secondary_count"`
|
| 121 |
+
Ownership float64 `json:"ownership"`
|
| 122 |
+
Players []int32 `json:"players"`
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
var allOptimals []OptimalsJSON
|
| 126 |
+
if err := json.Unmarshal(data, &allOptimals); err != nil {
|
| 127 |
+
return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
optimalsBySlate := make(map[string]LineupData)
|
| 131 |
+
|
| 132 |
+
for _, optimal := range allOptimals {
|
| 133 |
+
if _, exists := optimalsBySlate[optimal.Slate]; !exists {
|
| 134 |
+
optimalsBySlate[optimal.Slate] = LineupData{
|
| 135 |
+
Salary: []int32{},
|
| 136 |
+
Projection: []float64{},
|
| 137 |
+
Team: []string{},
|
| 138 |
+
Team_count: []int32{},
|
| 139 |
+
Secondary: []string{},
|
| 140 |
+
Secondary_count: []int32{},
|
| 141 |
+
Ownership: []float64{},
|
| 142 |
+
Players: [][]int32{},
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
slateData := optimalsBySlate[optimal.Slate]
|
| 147 |
+
slateData.Salary = append(slateData.Salary, optimal.Salary)
|
| 148 |
+
slateData.Projection = append(slateData.Projection, optimal.Projection)
|
| 149 |
+
slateData.Team = append(slateData.Team, optimal.Team)
|
| 150 |
+
slateData.Team_count = append(slateData.Team_count, optimal.Team_count)
|
| 151 |
+
slateData.Secondary = append(slateData.Secondary, optimal.Secondary)
|
| 152 |
+
slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count)
|
| 153 |
+
slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
|
| 154 |
+
slateData.Players = append(slateData.Players, optimal.Players)
|
| 155 |
+
|
| 156 |
+
optimalsBySlate[optimal.Slate] = slateData
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
return optimalsBySlate, nil
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
|
| 163 |
+
if len(optimals.Salary) == 0 {
|
| 164 |
+
return results
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
// Simply append the optimal LineupData to existing results
|
| 168 |
+
return append(results, optimals)
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
|
| 172 |
+
salaryMap := make(map[int32]int32)
|
| 173 |
+
projMap := make(map[int32]float64)
|
| 174 |
+
ownMap := make(map[int32]float64)
|
| 175 |
+
teamMap := make(map[int32]string)
|
| 176 |
+
|
| 177 |
+
for keyStr, value := range playerSet.Maps.SalaryMap {
|
| 178 |
+
key, err := strconv.Atoi(keyStr)
|
| 179 |
+
if err != nil {
|
| 180 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 181 |
+
continue
|
| 182 |
+
}
|
| 183 |
+
salaryMap[int32(key)] = value
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
for keyStr, value := range playerSet.Maps.ProjectionMap {
|
| 187 |
+
key, err := strconv.Atoi(keyStr)
|
| 188 |
+
if err != nil {
|
| 189 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 190 |
+
continue
|
| 191 |
+
}
|
| 192 |
+
projMap[int32(key)] = value
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
for keyStr, value := range playerSet.Maps.OwnershipMap {
|
| 196 |
+
key, err := strconv.Atoi(keyStr)
|
| 197 |
+
if err != nil {
|
| 198 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 199 |
+
continue
|
| 200 |
+
}
|
| 201 |
+
ownMap[int32(key)] = value
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
for keyStr, value := range playerSet.Maps.TeamMap {
|
| 205 |
+
key, err := strconv.Atoi(keyStr)
|
| 206 |
+
if err != nil {
|
| 207 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 208 |
+
continue
|
| 209 |
+
}
|
| 210 |
+
teamMap[int32(key)] = value
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
return salaryMap, projMap, ownMap, teamMap
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
|
| 217 |
+
result := make([]U, len(input))
|
| 218 |
+
|
| 219 |
+
for i, key := range input {
|
| 220 |
+
if value, exists := valueMap[key]; exists {
|
| 221 |
+
result[i] = value
|
| 222 |
+
} else {
|
| 223 |
+
var zero U
|
| 224 |
+
result[i] = zero
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
return result
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
func sortChars(strData string) string {
|
| 232 |
+
runes := []rune(strData)
|
| 233 |
+
slices.Sort(runes)
|
| 234 |
+
return string(runes)
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
func rowMostCommon(row []int) (*int, *int) {
|
| 238 |
+
if len(row) == 0 {
|
| 239 |
+
return nil, nil
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
counts := make(map[int]int)
|
| 243 |
+
for _, value := range row {
|
| 244 |
+
counts[value]++
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
if len(counts) < 2 {
|
| 248 |
+
return nil, nil
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
mostCommon := 0
|
| 252 |
+
maxCount := 0
|
| 253 |
+
secondMost := 0
|
| 254 |
+
secondMax := 0
|
| 255 |
+
|
| 256 |
+
for value, count := range counts {
|
| 257 |
+
if count > maxCount {
|
| 258 |
+
secondMax = maxCount
|
| 259 |
+
secondMost = mostCommon
|
| 260 |
+
|
| 261 |
+
maxCount = count
|
| 262 |
+
mostCommon = value
|
| 263 |
+
} else if count > secondMax && count < maxCount {
|
| 264 |
+
secondMax = count
|
| 265 |
+
secondMost = value
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
return &mostCommon, &secondMost
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
func rowBiggestAndSecond(row []int) (int, int) {
|
| 274 |
+
if len(row) == 0 {
|
| 275 |
+
return 0, 0
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
counts := make(map[int]int)
|
| 279 |
+
for _, value := range row {
|
| 280 |
+
counts[value]++
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
if len(counts) == 1 {
|
| 284 |
+
return len(row), 0
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
biggestVal := 0
|
| 288 |
+
secondBiggestVal := 0
|
| 289 |
+
|
| 290 |
+
for _, count := range counts {
|
| 291 |
+
if count > biggestVal {
|
| 292 |
+
secondBiggestVal = biggestVal
|
| 293 |
+
biggestVal = count
|
| 294 |
+
} else if count > secondBiggestVal && count < biggestVal {
|
| 295 |
+
secondBiggestVal = count
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
return biggestVal, secondBiggestVal
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
func createOverallDFs(players []Player, pos string) PlayerData {
|
| 303 |
+
var filteredPlayers []Player
|
| 304 |
+
for _, player := range players {
|
| 305 |
+
if pos == "FLEX" {
|
| 306 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 307 |
+
} else {
|
| 308 |
+
if strings.Contains(player.Position, pos) {
|
| 309 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
nameMap := make(map[int]string)
|
| 315 |
+
for i, player := range filteredPlayers {
|
| 316 |
+
nameMap[i] = player.Name
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
return PlayerData{
|
| 320 |
+
Players: filteredPlayers,
|
| 321 |
+
NameMap: nameMap,
|
| 322 |
+
}
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
func sumSalaryRows(data [][]int32) []int32 {
|
| 326 |
+
result := make([]int32, len(data))
|
| 327 |
+
|
| 328 |
+
for i, row := range data {
|
| 329 |
+
var sum int32
|
| 330 |
+
for _, value := range row {
|
| 331 |
+
sum += value
|
| 332 |
+
}
|
| 333 |
+
result[i] = sum
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
return result
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
func sumOwnRows(data [][]float64) []float64 {
|
| 340 |
+
result := make([]float64, len(data))
|
| 341 |
+
|
| 342 |
+
for i, row := range data {
|
| 343 |
+
var sum float64
|
| 344 |
+
for _, value := range row {
|
| 345 |
+
sum += value
|
| 346 |
+
}
|
| 347 |
+
result[i] = sum
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
return result
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
func sumProjRows(data [][]float64) []float64 {
|
| 354 |
+
result := make([]float64, len(data))
|
| 355 |
+
|
| 356 |
+
for i, row := range data {
|
| 357 |
+
var sum float64
|
| 358 |
+
for _, value := range row {
|
| 359 |
+
sum += value
|
| 360 |
+
}
|
| 361 |
+
result[i] = sum
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
return result
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
|
| 368 |
+
var validIndicies []int
|
| 369 |
+
|
| 370 |
+
for i, value := range values {
|
| 371 |
+
if value <= maxVal {
|
| 372 |
+
validIndicies = append(validIndicies, i)
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
return validIndicies
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
|
| 380 |
+
var validIndicies []int
|
| 381 |
+
|
| 382 |
+
for i, value := range values {
|
| 383 |
+
if value >= minVal {
|
| 384 |
+
validIndicies = append(validIndicies, i)
|
| 385 |
+
}
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
return validIndicies
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
func sliceByIndicies[T any](data []T, indicies []int) []T {
|
| 392 |
+
result := make([]T, len(indicies))
|
| 393 |
+
|
| 394 |
+
for i, idx := range indicies {
|
| 395 |
+
result[i] = data[idx]
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
return result
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
func sortDataByField(data LineupData, field string, ascending bool) LineupData {
|
| 402 |
+
indicies := make([]int, len(data.Ownership))
|
| 403 |
+
for i := range indicies {
|
| 404 |
+
indicies[i] = i
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
switch field {
|
| 408 |
+
case "salary":
|
| 409 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 410 |
+
if ascending {
|
| 411 |
+
return data.Salary[indicies[i]] < data.Salary[indicies[j]]
|
| 412 |
+
}
|
| 413 |
+
return data.Salary[indicies[i]] > data.Salary[indicies[j]]
|
| 414 |
+
})
|
| 415 |
+
case "projection":
|
| 416 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 417 |
+
if ascending {
|
| 418 |
+
return data.Projection[indicies[i]] < data.Projection[indicies[j]]
|
| 419 |
+
}
|
| 420 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 421 |
+
})
|
| 422 |
+
case "ownership":
|
| 423 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 424 |
+
if ascending {
|
| 425 |
+
return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
|
| 426 |
+
}
|
| 427 |
+
return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
|
| 428 |
+
})
|
| 429 |
+
default:
|
| 430 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 431 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 432 |
+
})
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
return LineupData{
|
| 436 |
+
Salary: sliceByIndicies(data.Salary, indicies),
|
| 437 |
+
Projection: sliceByIndicies(data.Projection, indicies),
|
| 438 |
+
Team: sliceByIndicies(data.Team, indicies),
|
| 439 |
+
Team_count: sliceByIndicies(data.Team_count, indicies),
|
| 440 |
+
Secondary: sliceByIndicies(data.Secondary, indicies),
|
| 441 |
+
Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
|
| 442 |
+
Ownership: sliceByIndicies(data.Ownership, indicies),
|
| 443 |
+
Players: sliceByIndicies(data.Players, indicies),
|
| 444 |
+
}
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
func combineArrays(pg, sg, sf, pf, c, g, f, flex []int32) [][]int32 {
|
| 448 |
+
length := len(pg)
|
| 449 |
+
|
| 450 |
+
result := make([][]int32, length)
|
| 451 |
+
|
| 452 |
+
for i := 0; i < length; i++ {
|
| 453 |
+
result[i] = []int32{
|
| 454 |
+
pg[i],
|
| 455 |
+
sg[i],
|
| 456 |
+
sf[i],
|
| 457 |
+
pf[i],
|
| 458 |
+
c[i],
|
| 459 |
+
g[i],
|
| 460 |
+
f[i],
|
| 461 |
+
flex[i],
|
| 462 |
+
}
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
return result
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
|
| 469 |
+
|
| 470 |
+
salaries := make([][]int32, len(combinedArrays))
|
| 471 |
+
projections := make([][]float64, len(combinedArrays))
|
| 472 |
+
ownership := make([][]float64, len(combinedArrays))
|
| 473 |
+
teamArrays := make([][]string, len(combinedArrays))
|
| 474 |
+
|
| 475 |
+
for i, row := range combinedArrays {
|
| 476 |
+
|
| 477 |
+
players := row[0:8]
|
| 478 |
+
|
| 479 |
+
playerSalaries := processAndFill(players, salaryMap)
|
| 480 |
+
playerProjections := processAndFill(players, projMap)
|
| 481 |
+
playerOwnership := processAndFill(players, ownMap)
|
| 482 |
+
playerTeams := processAndFill(players, teamMap)
|
| 483 |
+
|
| 484 |
+
salaries[i] = playerSalaries
|
| 485 |
+
projections[i] = playerProjections
|
| 486 |
+
ownership[i] = playerOwnership
|
| 487 |
+
teamArrays[i] = playerTeams
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
totalSalaries := sumSalaryRows(salaries)
|
| 491 |
+
totalProjections := sumProjRows(projections)
|
| 492 |
+
totalOwnership := sumOwnRows(ownership)
|
| 493 |
+
|
| 494 |
+
teamData := make([]string, len(teamArrays))
|
| 495 |
+
teamCounts := make([]int32, len(teamArrays))
|
| 496 |
+
secondaryData := make([]string, len(teamArrays))
|
| 497 |
+
secondaryCounts := make([]int32, len(teamArrays))
|
| 498 |
+
|
| 499 |
+
for i, teams := range teamArrays {
|
| 500 |
+
teamMap := make(map[string]int)
|
| 501 |
+
uniqueTeams := make([]string, 0)
|
| 502 |
+
for _, team := range teams {
|
| 503 |
+
if _, exists := teamMap[team]; !exists {
|
| 504 |
+
teamMap[team] = len(uniqueTeams)
|
| 505 |
+
uniqueTeams = append(uniqueTeams, team)
|
| 506 |
+
}
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
teamInts := make([]int, len(teams))
|
| 510 |
+
for j, team := range teams {
|
| 511 |
+
teamInts[j] = teamMap[team]
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
mostCommon, secondMost := rowMostCommon(teamInts)
|
| 515 |
+
if mostCommon != nil {
|
| 516 |
+
teamData[i] = uniqueTeams[*mostCommon]
|
| 517 |
+
teamCount, _ := rowBiggestAndSecond(teamInts)
|
| 518 |
+
teamCounts[i] = int32(teamCount)
|
| 519 |
+
}
|
| 520 |
+
if secondMost != nil {
|
| 521 |
+
secondaryData[i] = uniqueTeams[*secondMost]
|
| 522 |
+
_, secondaryCount := rowBiggestAndSecond(teamInts)
|
| 523 |
+
secondaryCounts[i] = int32(secondaryCount)
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
var validIndicies []int
|
| 528 |
+
if site == "DK" {
|
| 529 |
+
validIndicies = filterMax(totalSalaries, int32(50000))
|
| 530 |
+
} else {
|
| 531 |
+
validIndicies = filterMax(totalSalaries, int32(60000))
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
validData := LineupData{
|
| 535 |
+
Salary: sliceByIndicies(totalSalaries, validIndicies),
|
| 536 |
+
Projection: sliceByIndicies(totalProjections, validIndicies),
|
| 537 |
+
Team: sliceByIndicies(teamData, validIndicies),
|
| 538 |
+
Team_count: sliceByIndicies(teamCounts, validIndicies),
|
| 539 |
+
Secondary: sliceByIndicies(secondaryData, validIndicies),
|
| 540 |
+
Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
|
| 541 |
+
Ownership: sliceByIndicies(totalOwnership, validIndicies),
|
| 542 |
+
Players: sliceByIndicies(combinedArrays, validIndicies),
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
return sortDataByField(validData, "projection", false)
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
func calculateQuantile(values []float64, quantile float64) (float64, error) {
|
| 549 |
+
if len(values) == 0 {
|
| 550 |
+
return 0, fmt.Errorf("cannot calculate quantile of empty slice")
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
if quantile < 0 || quantile > 1 {
|
| 554 |
+
return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
sorted := make([]float64, len(values))
|
| 558 |
+
copy(sorted, values)
|
| 559 |
+
sort.Float64s(sorted)
|
| 560 |
+
|
| 561 |
+
index := int(float64(len(sorted)-1) * quantile)
|
| 562 |
+
return sorted[index], nil
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
|
| 566 |
+
if count > len(playerIDs) {
|
| 567 |
+
return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
shuffled := make([]int32, len(playerIDs))
|
| 571 |
+
copy(shuffled, playerIDs)
|
| 572 |
+
|
| 573 |
+
for i := len(shuffled) - 1; i > 0; i-- {
|
| 574 |
+
j := rng.Intn(i + 1)
|
| 575 |
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
return shuffled[:count], nil
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
func generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, gPlayers, fPlayers, flexPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
|
| 582 |
+
|
| 583 |
+
// DEBUG: Check pool sizes
|
| 584 |
+
fmt.Printf("DEBUG - Pool sizes: PG=%d, SG=%d, SF=%d, PF=%d, C=%d\n",
|
| 585 |
+
len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
|
| 586 |
+
|
| 587 |
+
if len(pgPlayers) == 0 || len(sgPlayers) == 0 || len(sfPlayers) == 0 || len(pfPlayers) == 0 || len(cPlayers) == 0 || len(gPlayers) == 0 || len(fPlayers) == 0 || len(flexPlayers) == 0 {
|
| 588 |
+
return nil, fmt.Errorf("one or more position pools is empty: PG=%d, SG=%d, SF=%d, PF=%d, C=%d, G=%d, F=%d, FLEX=%d",
|
| 589 |
+
len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers), len(gPlayers), len(fPlayers), len(flexPlayers))
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
var validArrays [][]int32
|
| 593 |
+
attempts := 0
|
| 594 |
+
maxAttempts := numRows * 10
|
| 595 |
+
|
| 596 |
+
for len(validArrays) < numRows && attempts < maxAttempts {
|
| 597 |
+
attempts++
|
| 598 |
+
|
| 599 |
+
pg := pgPlayers[rng.Intn(len(pgPlayers))]
|
| 600 |
+
sg := sgPlayers[rng.Intn(len(sgPlayers))]
|
| 601 |
+
sf := sfPlayers[rng.Intn(len(sfPlayers))]
|
| 602 |
+
pf := pfPlayers[rng.Intn(len(pfPlayers))]
|
| 603 |
+
c := cPlayers[rng.Intn(len(cPlayers))]
|
| 604 |
+
g := gPlayers[rng.Intn(len(gPlayers))]
|
| 605 |
+
f := fPlayers[rng.Intn(len(fPlayers))]
|
| 606 |
+
flex := flexPlayers[rng.Intn(len(flexPlayers))]
|
| 607 |
+
|
| 608 |
+
if pg.Name != sg.Name && pg.Name != sf.Name && pg.Name != pf.Name && pg.Name != g.Name && pg.Name != f.Name && pg.Name != flex.Name && sg.Name != sf.Name &&
|
| 609 |
+
sg.Name != pf.Name && sg.Name != g.Name && sg.Name != f.Name && sg.Name != flex.Name && sf.Name != pf.Name && sf.Name != g.Name && sf.Name != f.Name &&
|
| 610 |
+
sf.Name != flex.Name && pf.Name != c.Name && pf.Name != g.Name && pf.Name != f.Name && pf.Name != flex.Name && c.Name != f.Name && c.Name != flex.Name &&
|
| 611 |
+
g.Name != f.Name && g.Name != flex.Name && f.Name != flex.Name {
|
| 612 |
+
|
| 613 |
+
playerIDs := []int32{pg.ID, sg.ID, sf.ID, pf.ID, c.ID, g.ID, f.ID, flex.ID}
|
| 614 |
+
validArrays = append(validArrays, playerIDs)
|
| 615 |
+
}
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
if len(validArrays) == 0 {
|
| 619 |
+
return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
return validArrays, nil
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
|
| 626 |
+
if len(players) == 0 {
|
| 627 |
+
return nil, fmt.Errorf("no players provided")
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
var filteredPlayers []Player
|
| 631 |
+
for _, player := range players {
|
| 632 |
+
if pos == "FLEX" {
|
| 633 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 634 |
+
} else {
|
| 635 |
+
if strings.Contains(player.Position, pos) {
|
| 636 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 637 |
+
}
|
| 638 |
+
}
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
ownVals := make([]float64, len(filteredPlayers))
|
| 642 |
+
for i, player := range filteredPlayers {
|
| 643 |
+
ownVals[i] = player.OwnValue
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
threshold, err := calculateQuantile(ownVals, strengthStep)
|
| 647 |
+
if err != nil {
|
| 648 |
+
return nil, err
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
var filtered []Player
|
| 652 |
+
for _, player := range filteredPlayers {
|
| 653 |
+
if player.OwnValue >= threshold {
|
| 654 |
+
filtered = append(filtered, player)
|
| 655 |
+
}
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
if len(filtered) == 0 {
|
| 659 |
+
return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
return filtered, nil
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) (LineupData, error) {
|
| 666 |
+
|
| 667 |
+
pgPlayers, err := filterPosPlayersInQuantile(players, "PG", strengthStep)
|
| 668 |
+
if err != nil {
|
| 669 |
+
return LineupData{}, fmt.Errorf("failed to filter PG players: %v", err)
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
sgPlayers, err := filterPosPlayersInQuantile(players, "SG", strengthStep)
|
| 673 |
+
if err != nil {
|
| 674 |
+
return LineupData{}, fmt.Errorf("failed to filter SG players: %v", err)
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
sfPlayers, err := filterPosPlayersInQuantile(players, "SF", strengthStep)
|
| 678 |
+
if err != nil {
|
| 679 |
+
return LineupData{}, fmt.Errorf("failed to filter SF players: %v", err)
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
pfPlayers, err := filterPosPlayersInQuantile(players, "PF", strengthStep)
|
| 683 |
+
if err != nil {
|
| 684 |
+
return LineupData{}, fmt.Errorf("failed to filter PF players: %v", err)
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
cPlayers, err := filterPosPlayersInQuantile(players, "C", strengthStep)
|
| 688 |
+
if err != nil {
|
| 689 |
+
return LineupData{}, fmt.Errorf("failed to filter C players: %v", err)
|
| 690 |
+
}
|
| 691 |
+
|
| 692 |
+
gPlayers, err := filterPosPlayersInQuantile(players, "G", strengthStep)
|
| 693 |
+
if err != nil {
|
| 694 |
+
return LineupData{}, fmt.Errorf("failed to filter G players: %v", err)
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
fPlayers, err := filterPosPlayersInQuantile(players, "F", strengthStep)
|
| 698 |
+
if err != nil {
|
| 699 |
+
return LineupData{}, fmt.Errorf("failed to filter F players: %v", err)
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
flexPlayers, err := filterPosPlayersInQuantile(players, "FLEX", strengthStep)
|
| 703 |
+
if err != nil {
|
| 704 |
+
return LineupData{}, fmt.Errorf("failed to filter FLEX players: %v", err)
|
| 705 |
+
}
|
| 706 |
+
|
| 707 |
+
overallArrays, err := generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, gPlayers, fPlayers, flexPlayers, numRows, strengthStep, rng)
|
| 708 |
+
if err != nil {
|
| 709 |
+
return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
|
| 713 |
+
|
| 714 |
+
return result, nil
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) ([]LineupData, error) {
|
| 718 |
+
resultsChan := make(chan StrengthResult, len(strengthVars))
|
| 719 |
+
|
| 720 |
+
for i, strengthStep := range strengthVars {
|
| 721 |
+
go func(step float64, rows int, index int) {
|
| 722 |
+
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
|
| 723 |
+
|
| 724 |
+
result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
|
| 725 |
+
resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
|
| 726 |
+
|
| 727 |
+
if err != nil {
|
| 728 |
+
fmt.Printf("Error in strength level %.2f: %v\n", step, err)
|
| 729 |
+
} else {
|
| 730 |
+
fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
|
| 731 |
+
}
|
| 732 |
+
}(strengthStep, rowsPerLevel[i], i)
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
allResults := make([]LineupData, len(strengthVars))
|
| 736 |
+
var errors []error
|
| 737 |
+
successCount := 0
|
| 738 |
+
|
| 739 |
+
for i := 0; i < len(strengthVars); i++ {
|
| 740 |
+
result := <-resultsChan
|
| 741 |
+
if result.Error != nil {
|
| 742 |
+
errors = append(errors, result.Error)
|
| 743 |
+
} else {
|
| 744 |
+
allResults[result.Index] = result.Data
|
| 745 |
+
successCount++
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
|
| 749 |
+
if successCount == 0 {
|
| 750 |
+
return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
|
| 751 |
+
}
|
| 752 |
+
|
| 753 |
+
var validResults []LineupData
|
| 754 |
+
for i, result := range allResults {
|
| 755 |
+
if len(result.Salary) > 0 {
|
| 756 |
+
validResults = append(validResults, result)
|
| 757 |
+
} else {
|
| 758 |
+
fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
|
| 759 |
+
}
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
|
| 763 |
+
return validResults, nil
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
func printResults(results []LineupData, nameMap map[int32]string) {
|
| 767 |
+
fmt.Printf("Generated %d strength levels:\n", len(results))
|
| 768 |
+
|
| 769 |
+
// Combine all results into one big dataset
|
| 770 |
+
var allSalaries []int32
|
| 771 |
+
var allProjections []float64
|
| 772 |
+
var allOwnership []float64
|
| 773 |
+
var allPlayers [][]int32
|
| 774 |
+
|
| 775 |
+
for _, result := range results {
|
| 776 |
+
allSalaries = append(allSalaries, result.Salary...)
|
| 777 |
+
allProjections = append(allProjections, result.Projection...)
|
| 778 |
+
allOwnership = append(allOwnership, result.Ownership...)
|
| 779 |
+
allPlayers = append(allPlayers, result.Players...)
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
|
| 783 |
+
|
| 784 |
+
if len(allSalaries) > 0 {
|
| 785 |
+
// Print top 5 lineups (highest projection)
|
| 786 |
+
fmt.Printf("\nTop 5 lineups (by projection):\n")
|
| 787 |
+
for i := 0; i < 5 && i < len(allSalaries); i++ {
|
| 788 |
+
playerNames := []string{
|
| 789 |
+
getPlayerName(allPlayers[i][0], nameMap, "PG"),
|
| 790 |
+
getPlayerName(allPlayers[i][1], nameMap, "SG"),
|
| 791 |
+
getPlayerName(allPlayers[i][2], nameMap, "SF"),
|
| 792 |
+
getPlayerName(allPlayers[i][3], nameMap, "PF"),
|
| 793 |
+
getPlayerName(allPlayers[i][4], nameMap, "C"),
|
| 794 |
+
getPlayerName(allPlayers[i][5], nameMap, "G"),
|
| 795 |
+
getPlayerName(allPlayers[i][6], nameMap, "F"),
|
| 796 |
+
getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
|
| 800 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i])
|
| 801 |
+
fmt.Printf(" Players: PG=%s, SG=%s, SF=%s, PF=%s, C=%s, G=%s, F=%s, FLEX=%s\n",
|
| 802 |
+
playerNames[0], playerNames[1], playerNames[2], playerNames[3],
|
| 803 |
+
playerNames[4], playerNames[5], playerNames[6], playerNames[7])
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
// Print bottom 5 lineups (lowest projection)
|
| 807 |
+
if len(allSalaries) > 5 {
|
| 808 |
+
fmt.Printf("\nBottom 5 lineups (by projection):\n")
|
| 809 |
+
start := len(allSalaries) - 5
|
| 810 |
+
for i := start; i < len(allSalaries); i++ {
|
| 811 |
+
// Convert player IDs to names
|
| 812 |
+
playerNames := []string{
|
| 813 |
+
getPlayerName(allPlayers[i][0], nameMap, "PG"),
|
| 814 |
+
getPlayerName(allPlayers[i][1], nameMap, "SG"),
|
| 815 |
+
getPlayerName(allPlayers[i][2], nameMap, "SF"),
|
| 816 |
+
getPlayerName(allPlayers[i][3], nameMap, "PF"),
|
| 817 |
+
getPlayerName(allPlayers[i][4], nameMap, "C"),
|
| 818 |
+
getPlayerName(allPlayers[i][5], nameMap, "G"),
|
| 819 |
+
getPlayerName(allPlayers[i][6], nameMap, "F"),
|
| 820 |
+
getPlayerName(allPlayers[i][7], nameMap, "FLEX"),
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
|
| 824 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i])
|
| 825 |
+
fmt.Printf(" Players: PG=%s, SG=%s, SF=%s, PF=%s, C=%s, G=%s, F=%s, FLEX=%s\n",
|
| 826 |
+
playerNames[0], playerNames[1], playerNames[2], playerNames[3],
|
| 827 |
+
playerNames[4], playerNames[5], playerNames[6], playerNames[7])
|
| 828 |
+
}
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
func removeDuplicates(results []LineupData) []LineupData {
|
| 834 |
+
seen := make(map[string]bool)
|
| 835 |
+
var uniqueLineups []LineupData
|
| 836 |
+
|
| 837 |
+
for _, result := range results {
|
| 838 |
+
for i := 0; i < len(result.Players); i++ {
|
| 839 |
+
// Create combo string like Python
|
| 840 |
+
combo := fmt.Sprintf("%d%d%d%d%d%d%d%d",
|
| 841 |
+
result.Players[i][0], result.Players[i][1], result.Players[i][2],
|
| 842 |
+
result.Players[i][3], result.Players[i][4], result.Players[i][5],
|
| 843 |
+
result.Players[i][6], result.Players[i][7])
|
| 844 |
+
|
| 845 |
+
// Sort combo like Python
|
| 846 |
+
sortedCombo := sortChars(combo)
|
| 847 |
+
|
| 848 |
+
if !seen[sortedCombo] {
|
| 849 |
+
seen[sortedCombo] = true
|
| 850 |
+
uniqueLineups = append(uniqueLineups, LineupData{
|
| 851 |
+
Salary: []int32{result.Salary[i]},
|
| 852 |
+
Projection: []float64{result.Projection[i]},
|
| 853 |
+
Team: []string{result.Team[i]},
|
| 854 |
+
Team_count: []int32{result.Team_count[i]},
|
| 855 |
+
Secondary: []string{result.Secondary[i]},
|
| 856 |
+
Secondary_count: []int32{result.Secondary_count[i]},
|
| 857 |
+
Ownership: []float64{result.Ownership[i]},
|
| 858 |
+
Players: [][]int32{result.Players[i]},
|
| 859 |
+
})
|
| 860 |
+
}
|
| 861 |
+
}
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
if len(uniqueLineups) == 0 {
|
| 865 |
+
return []LineupData{}
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
var allSalary []int32
|
| 869 |
+
var allProjection []float64
|
| 870 |
+
var allTeam []string
|
| 871 |
+
var allTeamCount []int32
|
| 872 |
+
var allSecondary []string
|
| 873 |
+
var allSecondaryCount []int32
|
| 874 |
+
var allOwnership []float64
|
| 875 |
+
var allPlayers [][]int32
|
| 876 |
+
|
| 877 |
+
for _, lineup := range uniqueLineups {
|
| 878 |
+
allSalary = append(allSalary, lineup.Salary[0])
|
| 879 |
+
allProjection = append(allProjection, lineup.Projection[0])
|
| 880 |
+
allTeam = append(allTeam, lineup.Team[0])
|
| 881 |
+
allTeamCount = append(allTeamCount, lineup.Team_count[0])
|
| 882 |
+
allSecondary = append(allSecondary, lineup.Secondary[0])
|
| 883 |
+
allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
|
| 884 |
+
allOwnership = append(allOwnership, lineup.Ownership[0])
|
| 885 |
+
allPlayers = append(allPlayers, lineup.Players[0])
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
+
return []LineupData{{
|
| 889 |
+
Salary: allSalary,
|
| 890 |
+
Projection: allProjection,
|
| 891 |
+
Team: allTeam,
|
| 892 |
+
Team_count: allTeamCount,
|
| 893 |
+
Secondary: allSecondary,
|
| 894 |
+
Secondary_count: allSecondaryCount,
|
| 895 |
+
Ownership: allOwnership,
|
| 896 |
+
Players: allPlayers,
|
| 897 |
+
}}
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
func connectToMongoDB() (*mongo.Client, error) {
|
| 901 |
+
uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
|
| 902 |
+
|
| 903 |
+
clientOptions := options.Client().
|
| 904 |
+
ApplyURI(uri).
|
| 905 |
+
SetRetryWrites(true).
|
| 906 |
+
SetServerSelectionTimeout(10 * time.Second).
|
| 907 |
+
SetMaxPoolSize(100).
|
| 908 |
+
SetMinPoolSize(10).
|
| 909 |
+
SetMaxConnIdleTime(30 * time.Second).
|
| 910 |
+
SetRetryReads(true)
|
| 911 |
+
|
| 912 |
+
client, err := mongo.Connect(context.TODO(), clientOptions)
|
| 913 |
+
if err != nil {
|
| 914 |
+
return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
err = client.Ping(context.TODO(), nil)
|
| 918 |
+
if err != nil {
|
| 919 |
+
return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
fmt.Printf("Connected to MongoDB!")
|
| 923 |
+
return client, nil
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
|
| 927 |
+
db := client.Database(fmt.Sprintf("%s_Database", sport))
|
| 928 |
+
|
| 929 |
+
collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
|
| 930 |
+
collection := db.Collection(collectionName)
|
| 931 |
+
|
| 932 |
+
err := collection.Drop(context.TODO())
|
| 933 |
+
if err != nil {
|
| 934 |
+
fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
var documents []interface{}
|
| 938 |
+
|
| 939 |
+
for _, result := range results {
|
| 940 |
+
|
| 941 |
+
if len(result.Salary) == 0 || len(result.Players) == 0 {
|
| 942 |
+
fmt.Printf("Warning: Empty result found, skipping\n")
|
| 943 |
+
continue
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
for i := 0; i < len(result.Salary); i++ {
|
| 947 |
+
if len(result.Players[i]) < 8 {
|
| 948 |
+
fmt.Printf("Warning: Lineup %d has only %d players, expected 8\n", i, len(result.Players[i]))
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
doc := LineupDocument{
|
| 952 |
+
Salary: result.Salary[i],
|
| 953 |
+
Projection: result.Projection[i],
|
| 954 |
+
Team: result.Team[i],
|
| 955 |
+
Team_count: result.Team_count[i],
|
| 956 |
+
Secondary: result.Secondary[i],
|
| 957 |
+
Secondary_count: result.Secondary_count[i],
|
| 958 |
+
Ownership: result.Ownership[i],
|
| 959 |
+
PG: result.Players[i][0],
|
| 960 |
+
SG: result.Players[i][1],
|
| 961 |
+
SF: result.Players[i][2],
|
| 962 |
+
PF: result.Players[i][3],
|
| 963 |
+
C: result.Players[i][4],
|
| 964 |
+
G: result.Players[i][5],
|
| 965 |
+
F: result.Players[i][6],
|
| 966 |
+
FLEX: result.Players[i][7],
|
| 967 |
+
CreatedAt: time.Now(),
|
| 968 |
+
}
|
| 969 |
+
|
| 970 |
+
documents = append(documents, doc)
|
| 971 |
+
}
|
| 972 |
+
}
|
| 973 |
+
|
| 974 |
+
if len(documents) == 0 {
|
| 975 |
+
fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
|
| 976 |
+
}
|
| 977 |
+
|
| 978 |
+
if len(documents) > 500000 {
|
| 979 |
+
documents = documents[:500000]
|
| 980 |
+
}
|
| 981 |
+
|
| 982 |
+
chunkSize := 250000
|
| 983 |
+
for i := 0; i < len(documents); i += chunkSize {
|
| 984 |
+
end := i + chunkSize
|
| 985 |
+
if end > len(documents) {
|
| 986 |
+
end = len(documents)
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
chunk := documents[i:end]
|
| 990 |
+
|
| 991 |
+
for attempt := 0; attempt < 5; attempt++ {
|
| 992 |
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
| 993 |
+
|
| 994 |
+
opts := options.InsertMany().SetOrdered(false)
|
| 995 |
+
_, err := collection.InsertMany(ctx, chunk, opts)
|
| 996 |
+
cancel()
|
| 997 |
+
|
| 998 |
+
if err == nil {
|
| 999 |
+
fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
|
| 1000 |
+
break
|
| 1001 |
+
}
|
| 1002 |
+
|
| 1003 |
+
fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
|
| 1004 |
+
if attempt < 4 {
|
| 1005 |
+
time.Sleep(1 * time.Second)
|
| 1006 |
+
}
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
if err != nil {
|
| 1010 |
+
return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
|
| 1011 |
+
}
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
|
| 1015 |
+
return nil
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
func groupPlayersBySlate(players []Player) map[string][]Player {
|
| 1019 |
+
slateGroups := make(map[string][]Player)
|
| 1020 |
+
|
| 1021 |
+
for _, player := range players {
|
| 1022 |
+
slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
return slateGroups
|
| 1026 |
+
}
|
| 1027 |
+
|
| 1028 |
+
func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
|
| 1029 |
+
if name, exists := nameMap[playerID]; exists && name != "" {
|
| 1030 |
+
return name
|
| 1031 |
+
}
|
| 1032 |
+
return fmt.Sprintf("Unknown_%s_%d", position, playerID)
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
|
| 1036 |
+
nameMap := make(map[int32]string)
|
| 1037 |
+
|
| 1038 |
+
for keyStr, value := range playerSet.Maps.NameMap {
|
| 1039 |
+
key, err := strconv.Atoi(keyStr)
|
| 1040 |
+
if err != nil {
|
| 1041 |
+
fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
|
| 1042 |
+
continue
|
| 1043 |
+
}
|
| 1044 |
+
nameMap[int32(key)] = value
|
| 1045 |
+
}
|
| 1046 |
+
|
| 1047 |
+
return nameMap
|
| 1048 |
+
}
|
| 1049 |
+
|
| 1050 |
+
func main() {
|
| 1051 |
+
site := "DK"
|
| 1052 |
+
sport := "NBA"
|
| 1053 |
+
if len(os.Args) > 1 {
|
| 1054 |
+
site = os.Args[1]
|
| 1055 |
+
}
|
| 1056 |
+
if len(os.Args) > 2 {
|
| 1057 |
+
sport = os.Args[2]
|
| 1058 |
+
}
|
| 1059 |
+
processedData, err := loadPlayerData()
|
| 1060 |
+
if err != nil {
|
| 1061 |
+
fmt.Printf("Error loading data: %v\n", err)
|
| 1062 |
+
return
|
| 1063 |
+
}
|
| 1064 |
+
|
| 1065 |
+
start := time.Now()
|
| 1066 |
+
strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
|
| 1067 |
+
rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
|
| 1068 |
+
|
| 1069 |
+
SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
|
| 1070 |
+
|
| 1071 |
+
salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
|
| 1072 |
+
|
| 1073 |
+
nameMap := convertNamesToMaps(&processedData.PlayersMedian)
|
| 1074 |
+
|
| 1075 |
+
mongoClient, err := connectToMongoDB()
|
| 1076 |
+
if err != nil {
|
| 1077 |
+
fmt.Printf("Error connecting to MongoDB: %v\n", err)
|
| 1078 |
+
return
|
| 1079 |
+
}
|
| 1080 |
+
defer func() {
|
| 1081 |
+
if err := mongoClient.Disconnect(context.TODO()); err != nil {
|
| 1082 |
+
fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
|
| 1083 |
+
}
|
| 1084 |
+
}()
|
| 1085 |
+
|
| 1086 |
+
optimalsBySlate, err := loadOptimals()
|
| 1087 |
+
if err != nil {
|
| 1088 |
+
fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
|
| 1089 |
+
optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
|
| 1090 |
+
} else {
|
| 1091 |
+
totalOptimals := 0
|
| 1092 |
+
for _, optimals := range optimalsBySlate {
|
| 1093 |
+
totalOptimals += len(optimals.Salary)
|
| 1094 |
+
}
|
| 1095 |
+
fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
for slate, players := range SlateGroups {
|
| 1099 |
+
|
| 1100 |
+
fmt.Printf("Processing slate: %s\n", slate)
|
| 1101 |
+
|
| 1102 |
+
results, err := runSeedframeRoutines(
|
| 1103 |
+
players, strengthVars, rowsPerLevel,
|
| 1104 |
+
salaryMapJSON, projectionMapJSON,
|
| 1105 |
+
ownershipMapJSON, teamMapJSON, site)
|
| 1106 |
+
|
| 1107 |
+
if err != nil {
|
| 1108 |
+
fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
|
| 1109 |
+
continue
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
// Get optimal lineups for this specific slate
|
| 1113 |
+
slateOptimals := optimalsBySlate[slate]
|
| 1114 |
+
|
| 1115 |
+
// Append optimal lineups for this slate
|
| 1116 |
+
finalResults := appendOptimalLineups(results, slateOptimals)
|
| 1117 |
+
|
| 1118 |
+
exportResults := removeDuplicates(finalResults)
|
| 1119 |
+
|
| 1120 |
+
exportResults[0] = sortDataByField(exportResults[0], "projection", false)
|
| 1121 |
+
|
| 1122 |
+
err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
|
| 1123 |
+
if err != nil {
|
| 1124 |
+
fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
|
| 1125 |
+
continue
|
| 1126 |
+
}
|
| 1127 |
+
|
| 1128 |
+
printResults(exportResults, nameMap)
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
// Add this line at the end
|
| 1132 |
+
fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
|
| 1133 |
+
}
|
func/fd_nba_go/NBA_seed_frames.go
ADDED
|
@@ -0,0 +1,1118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
// Script Imports
|
| 5 |
+
"context"
|
| 6 |
+
"encoding/json"
|
| 7 |
+
"fmt"
|
| 8 |
+
"io/ioutil"
|
| 9 |
+
"math/rand"
|
| 10 |
+
"os"
|
| 11 |
+
"slices"
|
| 12 |
+
"sort"
|
| 13 |
+
"strconv"
|
| 14 |
+
"strings"
|
| 15 |
+
"time"
|
| 16 |
+
|
| 17 |
+
// MongoDB Imports
|
| 18 |
+
"go.mongodb.org/mongo-driver/mongo"
|
| 19 |
+
"go.mongodb.org/mongo-driver/mongo/options"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
type LineupData struct {
|
| 23 |
+
Salary []int32
|
| 24 |
+
Projection []float64
|
| 25 |
+
Team []string
|
| 26 |
+
Team_count []int32
|
| 27 |
+
Secondary []string
|
| 28 |
+
Secondary_count []int32
|
| 29 |
+
Ownership []float64
|
| 30 |
+
Players [][]int32
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
type Player struct {
|
| 34 |
+
ID int32 `json:"id"`
|
| 35 |
+
Name string `json:"name"`
|
| 36 |
+
Team string `json:"team"`
|
| 37 |
+
Position string `json:"position"`
|
| 38 |
+
Salary int32 `json:"salary"`
|
| 39 |
+
Projection float64 `json:"projection"`
|
| 40 |
+
Ownership float64 `json:"ownership"`
|
| 41 |
+
SalaryValue float64 `json:"salary_value"`
|
| 42 |
+
ProjValue float64 `json:"proj_value"`
|
| 43 |
+
OwnValue float64 `json:"own_value"`
|
| 44 |
+
SortValue float64 `json:"sort_value"`
|
| 45 |
+
Slate string `json:"slate"`
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
type PlayerSet struct {
|
| 49 |
+
Players []Player `json:"players"`
|
| 50 |
+
Maps struct {
|
| 51 |
+
NameMap map[string]string `json:"name_map"`
|
| 52 |
+
SalaryMap map[string]int32 `json:"salary_map"`
|
| 53 |
+
ProjectionMap map[string]float64 `json:"projection_map"`
|
| 54 |
+
OwnershipMap map[string]float64 `json:"ownership_map"`
|
| 55 |
+
TeamMap map[string]string `json:"team_map"`
|
| 56 |
+
} `json:"maps"`
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
type ProcessedData struct {
|
| 60 |
+
PlayersMedian PlayerSet `json:"players_median"`
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
type PlayerData struct {
|
| 64 |
+
Players []Player
|
| 65 |
+
NameMap map[int]string
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
type StrengthResult struct {
|
| 69 |
+
Index int
|
| 70 |
+
Data LineupData
|
| 71 |
+
Error error
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
type LineupDocument struct {
|
| 75 |
+
Salary int32 `bson:"salary"`
|
| 76 |
+
Projection float64 `bson:"proj"`
|
| 77 |
+
Team string `bson:"Team"`
|
| 78 |
+
Team_count int32 `bson:"Team_count"`
|
| 79 |
+
Secondary string `bson:"Secondary"`
|
| 80 |
+
Secondary_count int32 `bson:"Secondary_count"`
|
| 81 |
+
Ownership float64 `bson:"Own"`
|
| 82 |
+
PG1 int32 `bson:"PG1"`
|
| 83 |
+
PG2 int32 `bson:"PG2"`
|
| 84 |
+
SG1 int32 `bson:"SG1"`
|
| 85 |
+
SG2 int32 `bson:"SG2"`
|
| 86 |
+
SF1 int32 `bson:"SF1"`
|
| 87 |
+
SF2 int32 `bson:"SF2"`
|
| 88 |
+
PF1 int32 `bson:"PF1"`
|
| 89 |
+
PF2 int32 `bson:"PF2"`
|
| 90 |
+
C int32 `bson:"C"`
|
| 91 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
func loadPlayerData() (*ProcessedData, error) {
|
| 95 |
+
data, err := ioutil.ReadFile("fd_nba_go/player_data.json")
|
| 96 |
+
if err != nil {
|
| 97 |
+
return nil, fmt.Errorf("failed to read in data: %v", err)
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
var processedData ProcessedData
|
| 101 |
+
if err := json.Unmarshal(data, &processedData); err != nil {
|
| 102 |
+
return nil, fmt.Errorf("failed to parse json: %v", err)
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
return &processedData, nil
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
func loadOptimals() (map[string]LineupData, error) {
|
| 109 |
+
data, err := ioutil.ReadFile("fd_nba_go/optimal_lineups.json")
|
| 110 |
+
if err != nil {
|
| 111 |
+
return nil, fmt.Errorf("failed to parse optimals: %v", err)
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
type OptimalsJSON struct {
|
| 115 |
+
Slate string `json:"slate"`
|
| 116 |
+
Salary int32 `json:"salary"`
|
| 117 |
+
Projection float64 `json:"projection"`
|
| 118 |
+
Team string `json:"team"`
|
| 119 |
+
Team_count int32 `json:"team_count"`
|
| 120 |
+
Secondary string `json:"secondary"`
|
| 121 |
+
Secondary_count int32 `json:"secondary_count"`
|
| 122 |
+
Ownership float64 `json:"ownership"`
|
| 123 |
+
Players []int32 `json:"players"`
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
var allOptimals []OptimalsJSON
|
| 127 |
+
if err := json.Unmarshal(data, &allOptimals); err != nil {
|
| 128 |
+
return nil, fmt.Errorf("failed to parse optimals JSON: %v", err)
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
optimalsBySlate := make(map[string]LineupData)
|
| 132 |
+
|
| 133 |
+
for _, optimal := range allOptimals {
|
| 134 |
+
if _, exists := optimalsBySlate[optimal.Slate]; !exists {
|
| 135 |
+
optimalsBySlate[optimal.Slate] = LineupData{
|
| 136 |
+
Salary: []int32{},
|
| 137 |
+
Projection: []float64{},
|
| 138 |
+
Team: []string{},
|
| 139 |
+
Team_count: []int32{},
|
| 140 |
+
Secondary: []string{},
|
| 141 |
+
Secondary_count: []int32{},
|
| 142 |
+
Ownership: []float64{},
|
| 143 |
+
Players: [][]int32{},
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
slateData := optimalsBySlate[optimal.Slate]
|
| 148 |
+
slateData.Salary = append(slateData.Salary, optimal.Salary)
|
| 149 |
+
slateData.Projection = append(slateData.Projection, optimal.Projection)
|
| 150 |
+
slateData.Team = append(slateData.Team, optimal.Team)
|
| 151 |
+
slateData.Team_count = append(slateData.Team_count, optimal.Team_count)
|
| 152 |
+
slateData.Secondary = append(slateData.Secondary, optimal.Secondary)
|
| 153 |
+
slateData.Secondary_count = append(slateData.Secondary_count, optimal.Secondary_count)
|
| 154 |
+
slateData.Ownership = append(slateData.Ownership, optimal.Ownership)
|
| 155 |
+
slateData.Players = append(slateData.Players, optimal.Players)
|
| 156 |
+
|
| 157 |
+
optimalsBySlate[optimal.Slate] = slateData
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
return optimalsBySlate, nil
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
func appendOptimalLineups(results []LineupData, optimals LineupData) []LineupData {
|
| 164 |
+
if len(optimals.Salary) == 0 {
|
| 165 |
+
return results
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
// Simply append the optimal LineupData to existing results
|
| 169 |
+
return append(results, optimals)
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
|
| 173 |
+
salaryMap := make(map[int32]int32)
|
| 174 |
+
projMap := make(map[int32]float64)
|
| 175 |
+
ownMap := make(map[int32]float64)
|
| 176 |
+
teamMap := make(map[int32]string)
|
| 177 |
+
|
| 178 |
+
for keyStr, value := range playerSet.Maps.SalaryMap {
|
| 179 |
+
key, err := strconv.Atoi(keyStr)
|
| 180 |
+
if err != nil {
|
| 181 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 182 |
+
continue
|
| 183 |
+
}
|
| 184 |
+
salaryMap[int32(key)] = value
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
for keyStr, value := range playerSet.Maps.ProjectionMap {
|
| 188 |
+
key, err := strconv.Atoi(keyStr)
|
| 189 |
+
if err != nil {
|
| 190 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 191 |
+
continue
|
| 192 |
+
}
|
| 193 |
+
projMap[int32(key)] = value
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
for keyStr, value := range playerSet.Maps.OwnershipMap {
|
| 197 |
+
key, err := strconv.Atoi(keyStr)
|
| 198 |
+
if err != nil {
|
| 199 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 200 |
+
continue
|
| 201 |
+
}
|
| 202 |
+
ownMap[int32(key)] = value
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
for keyStr, value := range playerSet.Maps.TeamMap {
|
| 206 |
+
key, err := strconv.Atoi(keyStr)
|
| 207 |
+
if err != nil {
|
| 208 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 209 |
+
continue
|
| 210 |
+
}
|
| 211 |
+
teamMap[int32(key)] = value
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
return salaryMap, projMap, ownMap, teamMap
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
|
| 218 |
+
result := make([]U, len(input))
|
| 219 |
+
|
| 220 |
+
for i, key := range input {
|
| 221 |
+
if value, exists := valueMap[key]; exists {
|
| 222 |
+
result[i] = value
|
| 223 |
+
} else {
|
| 224 |
+
var zero U
|
| 225 |
+
result[i] = zero
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
return result
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
func sortChars(strData string) string {
|
| 233 |
+
runes := []rune(strData)
|
| 234 |
+
slices.Sort(runes)
|
| 235 |
+
return string(runes)
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
func rowMostCommon(row []int) (*int, *int) {
|
| 239 |
+
if len(row) == 0 {
|
| 240 |
+
return nil, nil
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
counts := make(map[int]int)
|
| 244 |
+
for _, value := range row {
|
| 245 |
+
counts[value]++
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
if len(counts) < 2 {
|
| 249 |
+
return nil, nil
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
mostCommon := 0
|
| 253 |
+
maxCount := 0
|
| 254 |
+
secondMost := 0
|
| 255 |
+
secondMax := 0
|
| 256 |
+
|
| 257 |
+
for value, count := range counts {
|
| 258 |
+
if count > maxCount {
|
| 259 |
+
secondMax = maxCount
|
| 260 |
+
secondMost = mostCommon
|
| 261 |
+
|
| 262 |
+
maxCount = count
|
| 263 |
+
mostCommon = value
|
| 264 |
+
} else if count > secondMax && count < maxCount {
|
| 265 |
+
secondMax = count
|
| 266 |
+
secondMost = value
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
return &mostCommon, &secondMost
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
func rowBiggestAndSecond(row []int) (int, int) {
|
| 275 |
+
if len(row) == 0 {
|
| 276 |
+
return 0, 0
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
counts := make(map[int]int)
|
| 280 |
+
for _, value := range row {
|
| 281 |
+
counts[value]++
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
if len(counts) == 1 {
|
| 285 |
+
return len(row), 0
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
biggestVal := 0
|
| 289 |
+
secondBiggestVal := 0
|
| 290 |
+
|
| 291 |
+
for _, count := range counts {
|
| 292 |
+
if count > biggestVal {
|
| 293 |
+
secondBiggestVal = biggestVal
|
| 294 |
+
biggestVal = count
|
| 295 |
+
} else if count > secondBiggestVal && count < biggestVal {
|
| 296 |
+
secondBiggestVal = count
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
return biggestVal, secondBiggestVal
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
func createOverallDFs(players []Player, pos string) PlayerData {
|
| 304 |
+
var filteredPlayers []Player
|
| 305 |
+
for _, player := range players {
|
| 306 |
+
if strings.Contains(player.Position, pos) {
|
| 307 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
nameMap := make(map[int]string)
|
| 312 |
+
for i, player := range filteredPlayers {
|
| 313 |
+
nameMap[i] = player.Name
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
return PlayerData{
|
| 317 |
+
Players: filteredPlayers,
|
| 318 |
+
NameMap: nameMap,
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
func sumSalaryRows(data [][]int32) []int32 {
|
| 323 |
+
result := make([]int32, len(data))
|
| 324 |
+
|
| 325 |
+
for i, row := range data {
|
| 326 |
+
var sum int32
|
| 327 |
+
for _, value := range row {
|
| 328 |
+
sum += value
|
| 329 |
+
}
|
| 330 |
+
result[i] = sum
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
return result
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
func sumOwnRows(data [][]float64) []float64 {
|
| 337 |
+
result := make([]float64, len(data))
|
| 338 |
+
|
| 339 |
+
for i, row := range data {
|
| 340 |
+
var sum float64
|
| 341 |
+
for _, value := range row {
|
| 342 |
+
sum += value
|
| 343 |
+
}
|
| 344 |
+
result[i] = sum
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
return result
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
func sumProjRows(data [][]float64) []float64 {
|
| 351 |
+
result := make([]float64, len(data))
|
| 352 |
+
|
| 353 |
+
for i, row := range data {
|
| 354 |
+
var sum float64
|
| 355 |
+
for _, value := range row {
|
| 356 |
+
sum += value
|
| 357 |
+
}
|
| 358 |
+
result[i] = sum
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
return result
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
|
| 365 |
+
var validIndicies []int
|
| 366 |
+
|
| 367 |
+
for i, value := range values {
|
| 368 |
+
if value <= maxVal {
|
| 369 |
+
validIndicies = append(validIndicies, i)
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
return validIndicies
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
|
| 377 |
+
var validIndicies []int
|
| 378 |
+
|
| 379 |
+
for i, value := range values {
|
| 380 |
+
if value >= minVal {
|
| 381 |
+
validIndicies = append(validIndicies, i)
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
return validIndicies
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
func sliceByIndicies[T any](data []T, indicies []int) []T {
|
| 389 |
+
result := make([]T, len(indicies))
|
| 390 |
+
|
| 391 |
+
for i, idx := range indicies {
|
| 392 |
+
result[i] = data[idx]
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
return result
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
func sortDataByField(data LineupData, field string, ascending bool) LineupData {
|
| 399 |
+
indicies := make([]int, len(data.Ownership))
|
| 400 |
+
for i := range indicies {
|
| 401 |
+
indicies[i] = i
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
switch field {
|
| 405 |
+
case "salary":
|
| 406 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 407 |
+
if ascending {
|
| 408 |
+
return data.Salary[indicies[i]] < data.Salary[indicies[j]]
|
| 409 |
+
}
|
| 410 |
+
return data.Salary[indicies[i]] > data.Salary[indicies[j]]
|
| 411 |
+
})
|
| 412 |
+
case "projection":
|
| 413 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 414 |
+
if ascending {
|
| 415 |
+
return data.Projection[indicies[i]] < data.Projection[indicies[j]]
|
| 416 |
+
}
|
| 417 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 418 |
+
})
|
| 419 |
+
case "ownership":
|
| 420 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 421 |
+
if ascending {
|
| 422 |
+
return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
|
| 423 |
+
}
|
| 424 |
+
return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
|
| 425 |
+
})
|
| 426 |
+
default:
|
| 427 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 428 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 429 |
+
})
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
return LineupData{
|
| 433 |
+
Salary: sliceByIndicies(data.Salary, indicies),
|
| 434 |
+
Projection: sliceByIndicies(data.Projection, indicies),
|
| 435 |
+
Team: sliceByIndicies(data.Team, indicies),
|
| 436 |
+
Team_count: sliceByIndicies(data.Team_count, indicies),
|
| 437 |
+
Secondary: sliceByIndicies(data.Secondary, indicies),
|
| 438 |
+
Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
|
| 439 |
+
Ownership: sliceByIndicies(data.Ownership, indicies),
|
| 440 |
+
Players: sliceByIndicies(data.Players, indicies),
|
| 441 |
+
}
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
func combineArrays(pg1, pg2, sg1, sg2, sf1, sf2, pf1, pf2, c []int32) [][]int32 {
|
| 445 |
+
length := len(pg1)
|
| 446 |
+
|
| 447 |
+
result := make([][]int32, length)
|
| 448 |
+
|
| 449 |
+
for i := 0; i < length; i++ {
|
| 450 |
+
result[i] = []int32{
|
| 451 |
+
pg1[i],
|
| 452 |
+
pg2[i],
|
| 453 |
+
sg1[i],
|
| 454 |
+
sg2[i],
|
| 455 |
+
sf1[i],
|
| 456 |
+
sf2[i],
|
| 457 |
+
pf1[i],
|
| 458 |
+
pf2[i],
|
| 459 |
+
c[i],
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
return result
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
func createSeedFrames(combinedArrays [][]int32, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) LineupData {
|
| 467 |
+
|
| 468 |
+
salaries := make([][]int32, len(combinedArrays))
|
| 469 |
+
projections := make([][]float64, len(combinedArrays))
|
| 470 |
+
ownership := make([][]float64, len(combinedArrays))
|
| 471 |
+
teamArrays := make([][]string, len(combinedArrays))
|
| 472 |
+
|
| 473 |
+
for i, row := range combinedArrays {
|
| 474 |
+
|
| 475 |
+
players := row[0:9]
|
| 476 |
+
|
| 477 |
+
playerSalaries := processAndFill(players, salaryMap)
|
| 478 |
+
playerProjections := processAndFill(players, projMap)
|
| 479 |
+
playerOwnership := processAndFill(players, ownMap)
|
| 480 |
+
playerTeams := processAndFill(players, teamMap)
|
| 481 |
+
|
| 482 |
+
salaries[i] = playerSalaries
|
| 483 |
+
projections[i] = playerProjections
|
| 484 |
+
ownership[i] = playerOwnership
|
| 485 |
+
teamArrays[i] = playerTeams
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
totalSalaries := sumSalaryRows(salaries)
|
| 489 |
+
totalProjections := sumProjRows(projections)
|
| 490 |
+
totalOwnership := sumOwnRows(ownership)
|
| 491 |
+
|
| 492 |
+
teamData := make([]string, len(teamArrays))
|
| 493 |
+
teamCounts := make([]int32, len(teamArrays))
|
| 494 |
+
secondaryData := make([]string, len(teamArrays))
|
| 495 |
+
secondaryCounts := make([]int32, len(teamArrays))
|
| 496 |
+
|
| 497 |
+
for i, teams := range teamArrays {
|
| 498 |
+
teamMap := make(map[string]int)
|
| 499 |
+
uniqueTeams := make([]string, 0)
|
| 500 |
+
for _, team := range teams {
|
| 501 |
+
if _, exists := teamMap[team]; !exists {
|
| 502 |
+
teamMap[team] = len(uniqueTeams)
|
| 503 |
+
uniqueTeams = append(uniqueTeams, team)
|
| 504 |
+
}
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
teamInts := make([]int, len(teams))
|
| 508 |
+
for j, team := range teams {
|
| 509 |
+
teamInts[j] = teamMap[team]
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
mostCommon, secondMost := rowMostCommon(teamInts)
|
| 513 |
+
if mostCommon != nil {
|
| 514 |
+
teamData[i] = uniqueTeams[*mostCommon]
|
| 515 |
+
teamCount, _ := rowBiggestAndSecond(teamInts)
|
| 516 |
+
teamCounts[i] = int32(teamCount)
|
| 517 |
+
}
|
| 518 |
+
if secondMost != nil {
|
| 519 |
+
secondaryData[i] = uniqueTeams[*secondMost]
|
| 520 |
+
_, secondaryCount := rowBiggestAndSecond(teamInts)
|
| 521 |
+
secondaryCounts[i] = int32(secondaryCount)
|
| 522 |
+
}
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
var validIndicies []int
|
| 526 |
+
if site == "DK" {
|
| 527 |
+
validIndicies = filterMax(totalSalaries, int32(50000))
|
| 528 |
+
} else {
|
| 529 |
+
validIndicies = filterMax(totalSalaries, int32(60000))
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
validData := LineupData{
|
| 533 |
+
Salary: sliceByIndicies(totalSalaries, validIndicies),
|
| 534 |
+
Projection: sliceByIndicies(totalProjections, validIndicies),
|
| 535 |
+
Team: sliceByIndicies(teamData, validIndicies),
|
| 536 |
+
Team_count: sliceByIndicies(teamCounts, validIndicies),
|
| 537 |
+
Secondary: sliceByIndicies(secondaryData, validIndicies),
|
| 538 |
+
Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
|
| 539 |
+
Ownership: sliceByIndicies(totalOwnership, validIndicies),
|
| 540 |
+
Players: sliceByIndicies(combinedArrays, validIndicies),
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
return sortDataByField(validData, "projection", false)
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
func calculateQuantile(values []float64, quantile float64) (float64, error) {
|
| 547 |
+
if len(values) == 0 {
|
| 548 |
+
return 0, fmt.Errorf("cannot calculate quantile of empty slice")
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
if quantile < 0 || quantile > 1 {
|
| 552 |
+
return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
sorted := make([]float64, len(values))
|
| 556 |
+
copy(sorted, values)
|
| 557 |
+
sort.Float64s(sorted)
|
| 558 |
+
|
| 559 |
+
index := int(float64(len(sorted)-1) * quantile)
|
| 560 |
+
return sorted[index], nil
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
|
| 564 |
+
if count > len(playerIDs) {
|
| 565 |
+
return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
shuffled := make([]int32, len(playerIDs))
|
| 569 |
+
copy(shuffled, playerIDs)
|
| 570 |
+
|
| 571 |
+
for i := len(shuffled) - 1; i > 0; i-- {
|
| 572 |
+
j := rng.Intn(i + 1)
|
| 573 |
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
return shuffled[:count], nil
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
func generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers []Player, numRows int, strengthStep float64, rng *rand.Rand) ([][]int32, error) {
|
| 580 |
+
|
| 581 |
+
// DEBUG: Check pool sizes
|
| 582 |
+
fmt.Printf("DEBUG - Pool sizes: PG=%d, SG=%d, SF=%d, PF=%d, C=%d\n",
|
| 583 |
+
len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
|
| 584 |
+
|
| 585 |
+
if len(pgPlayers) == 0 || len(sgPlayers) == 0 || len(sfPlayers) == 0 || len(pfPlayers) == 0 || len(cPlayers) == 0 {
|
| 586 |
+
return nil, fmt.Errorf("one or more position pools is empty: PG=%d, SG=%d, SF=%d, PF=%d, C=%d",
|
| 587 |
+
len(pgPlayers), len(sgPlayers), len(sfPlayers), len(pfPlayers), len(cPlayers))
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
var validArrays [][]int32
|
| 591 |
+
attempts := 0
|
| 592 |
+
maxAttempts := numRows * 10
|
| 593 |
+
|
| 594 |
+
for len(validArrays) < numRows && attempts < maxAttempts {
|
| 595 |
+
attempts++
|
| 596 |
+
|
| 597 |
+
pg1 := pgPlayers[rng.Intn(len(pgPlayers))]
|
| 598 |
+
pg2 := pgPlayers[rng.Intn(len(pgPlayers))]
|
| 599 |
+
sg1 := sgPlayers[rng.Intn(len(sgPlayers))]
|
| 600 |
+
sg2 := sgPlayers[rng.Intn(len(sgPlayers))]
|
| 601 |
+
sf1 := sfPlayers[rng.Intn(len(sfPlayers))]
|
| 602 |
+
sf2 := sfPlayers[rng.Intn(len(sfPlayers))]
|
| 603 |
+
pf1 := pfPlayers[rng.Intn(len(pfPlayers))]
|
| 604 |
+
pf2 := pfPlayers[rng.Intn(len(pfPlayers))]
|
| 605 |
+
c := cPlayers[rng.Intn(len(cPlayers))]
|
| 606 |
+
|
| 607 |
+
if pg1.Name != pg2.Name && pg1.Name != sg1.Name && pg1.Name != sg2.Name && pg1.Name != sf1.Name && pg1.Name != sf2.Name && pg1.Name != pf1.Name && pg1.Name != pf2.Name &&
|
| 608 |
+
pg2.Name != sg1.Name && pg2.Name != sg2.Name && pg2.Name != sf1.Name && pg2.Name != sf2.Name && pg2.Name != pf1.Name && pg2.Name != pf2.Name &&
|
| 609 |
+
sg1.Name != sg2.Name && sg1.Name != sf1.Name && sg1.Name != sf2.Name && sg1.Name != pf1.Name && sg1.Name != pf2.Name &&
|
| 610 |
+
sg2.Name != sf1.Name && sg2.Name != sf2.Name && sg2.Name != pf1.Name && sg2.Name != pf2.Name &&
|
| 611 |
+
sf1.Name != sf2.Name && sf1.Name != pf1.Name && sf1.Name != pf2.Name && sf2.Name != pf1.Name &&
|
| 612 |
+
sf2.Name != pf2.Name && pf1.Name != pf2.Name && pf1.Name != c.Name && pf2.Name != c.Name {
|
| 613 |
+
|
| 614 |
+
playerIDs := []int32{pg1.ID, pg2.ID, sg1.ID, sg2.ID, sf1.ID, sf2.ID, pf1.ID, pf2.ID, c.ID}
|
| 615 |
+
validArrays = append(validArrays, playerIDs)
|
| 616 |
+
}
|
| 617 |
+
}
|
| 618 |
+
|
| 619 |
+
if len(validArrays) == 0 {
|
| 620 |
+
return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
return validArrays, nil
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
func filterPosPlayersInQuantile(players []Player, pos string, strengthStep float64) ([]Player, error) {
|
| 627 |
+
if len(players) == 0 {
|
| 628 |
+
return nil, fmt.Errorf("no players provided")
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
var filteredPlayers []Player
|
| 632 |
+
for _, player := range players {
|
| 633 |
+
if strings.Contains(player.Position, pos) {
|
| 634 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 635 |
+
}
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
ownVals := make([]float64, len(filteredPlayers))
|
| 639 |
+
for i, player := range filteredPlayers {
|
| 640 |
+
ownVals[i] = player.OwnValue
|
| 641 |
+
}
|
| 642 |
+
|
| 643 |
+
threshold, err := calculateQuantile(ownVals, strengthStep)
|
| 644 |
+
if err != nil {
|
| 645 |
+
return nil, err
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
var filtered []Player
|
| 649 |
+
for _, player := range filteredPlayers {
|
| 650 |
+
if player.OwnValue >= threshold {
|
| 651 |
+
filtered = append(filtered, player)
|
| 652 |
+
}
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
if len(filtered) == 0 {
|
| 656 |
+
return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
return filtered, nil
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
func processStrengthLevels(players []Player, strengthStep float64, numRows int, rng *rand.Rand, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) (LineupData, error) {
|
| 663 |
+
|
| 664 |
+
pgPlayers, err := filterPosPlayersInQuantile(players, "PG", strengthStep)
|
| 665 |
+
if err != nil {
|
| 666 |
+
return LineupData{}, fmt.Errorf("failed to filter PG players: %v", err)
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
sgPlayers, err := filterPosPlayersInQuantile(players, "SG", strengthStep)
|
| 670 |
+
if err != nil {
|
| 671 |
+
return LineupData{}, fmt.Errorf("failed to filter SG players: %v", err)
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
sfPlayers, err := filterPosPlayersInQuantile(players, "SF", strengthStep)
|
| 675 |
+
if err != nil {
|
| 676 |
+
return LineupData{}, fmt.Errorf("failed to filter SF players: %v", err)
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
pfPlayers, err := filterPosPlayersInQuantile(players, "PF", strengthStep)
|
| 680 |
+
if err != nil {
|
| 681 |
+
return LineupData{}, fmt.Errorf("failed to filter PF players: %v", err)
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
cPlayers, err := filterPosPlayersInQuantile(players, "C", strengthStep)
|
| 685 |
+
if err != nil {
|
| 686 |
+
return LineupData{}, fmt.Errorf("failed to filter C players: %v", err)
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
overallArrays, err := generateBaseArrays(pgPlayers, sgPlayers, sfPlayers, pfPlayers, cPlayers, numRows, strengthStep, rng)
|
| 690 |
+
if err != nil {
|
| 691 |
+
return LineupData{}, fmt.Errorf("failed to generate base arrays: %v", err)
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
result := createSeedFrames(overallArrays, salaryMap, projMap, ownMap, teamMap, site)
|
| 695 |
+
|
| 696 |
+
return result, nil
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
func runSeedframeRoutines(players []Player, strengthVars []float64, rowsPerLevel []int, salaryMap map[int32]int32, projMap map[int32]float64, ownMap map[int32]float64, teamMap map[int32]string, site string) ([]LineupData, error) {
|
| 700 |
+
resultsChan := make(chan StrengthResult, len(strengthVars))
|
| 701 |
+
|
| 702 |
+
for i, strengthStep := range strengthVars {
|
| 703 |
+
go func(step float64, rows int, index int) {
|
| 704 |
+
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
|
| 705 |
+
|
| 706 |
+
result, err := processStrengthLevels(players, step, rows, rng, salaryMap, projMap, ownMap, teamMap, site)
|
| 707 |
+
resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
|
| 708 |
+
|
| 709 |
+
if err != nil {
|
| 710 |
+
fmt.Printf("Error in strength level %.2f: %v\n", step, err)
|
| 711 |
+
} else {
|
| 712 |
+
fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
|
| 713 |
+
}
|
| 714 |
+
}(strengthStep, rowsPerLevel[i], i)
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
allResults := make([]LineupData, len(strengthVars))
|
| 718 |
+
var errors []error
|
| 719 |
+
successCount := 0
|
| 720 |
+
|
| 721 |
+
for i := 0; i < len(strengthVars); i++ {
|
| 722 |
+
result := <-resultsChan
|
| 723 |
+
if result.Error != nil {
|
| 724 |
+
errors = append(errors, result.Error)
|
| 725 |
+
} else {
|
| 726 |
+
allResults[result.Index] = result.Data
|
| 727 |
+
successCount++
|
| 728 |
+
}
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
if successCount == 0 {
|
| 732 |
+
return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
var validResults []LineupData
|
| 736 |
+
for i, result := range allResults {
|
| 737 |
+
if len(result.Salary) > 0 {
|
| 738 |
+
validResults = append(validResults, result)
|
| 739 |
+
} else {
|
| 740 |
+
fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
|
| 741 |
+
}
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
|
| 745 |
+
return validResults, nil
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
func printResults(results []LineupData, nameMap map[int32]string) {
|
| 749 |
+
fmt.Printf("Generated %d strength levels:\n", len(results))
|
| 750 |
+
|
| 751 |
+
// Combine all results into one big dataset
|
| 752 |
+
var allSalaries []int32
|
| 753 |
+
var allProjections []float64
|
| 754 |
+
var allOwnership []float64
|
| 755 |
+
var allPlayers [][]int32
|
| 756 |
+
|
| 757 |
+
for _, result := range results {
|
| 758 |
+
allSalaries = append(allSalaries, result.Salary...)
|
| 759 |
+
allProjections = append(allProjections, result.Projection...)
|
| 760 |
+
allOwnership = append(allOwnership, result.Ownership...)
|
| 761 |
+
allPlayers = append(allPlayers, result.Players...)
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
|
| 765 |
+
|
| 766 |
+
if len(allSalaries) > 0 {
|
| 767 |
+
// Print top 5 lineups (highest projection)
|
| 768 |
+
fmt.Printf("\nTop 5 lineups (by projection):\n")
|
| 769 |
+
for i := 0; i < 5 && i < len(allSalaries); i++ {
|
| 770 |
+
playerNames := []string{
|
| 771 |
+
getPlayerName(allPlayers[i][0], nameMap, "PG1"),
|
| 772 |
+
getPlayerName(allPlayers[i][1], nameMap, "PG2"),
|
| 773 |
+
getPlayerName(allPlayers[i][2], nameMap, "SG1"),
|
| 774 |
+
getPlayerName(allPlayers[i][3], nameMap, "SG2"),
|
| 775 |
+
getPlayerName(allPlayers[i][4], nameMap, "SF1"),
|
| 776 |
+
getPlayerName(allPlayers[i][5], nameMap, "SF2"),
|
| 777 |
+
getPlayerName(allPlayers[i][6], nameMap, "PF1"),
|
| 778 |
+
getPlayerName(allPlayers[i][7], nameMap, "PF2"),
|
| 779 |
+
getPlayerName(allPlayers[i][8], nameMap, "C"),
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
|
| 783 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i])
|
| 784 |
+
fmt.Printf(" Players: PG1=%s, PG2=%s, SG1=%s, SG2=%s, SF1=%s, SF2=%s, PF1=%s, PF2=%s, C=%s\n",
|
| 785 |
+
playerNames[0], playerNames[1], playerNames[2], playerNames[3],
|
| 786 |
+
playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
// Print bottom 5 lineups (lowest projection)
|
| 790 |
+
if len(allSalaries) > 5 {
|
| 791 |
+
fmt.Printf("\nBottom 5 lineups (by projection):\n")
|
| 792 |
+
start := len(allSalaries) - 5
|
| 793 |
+
for i := start; i < len(allSalaries); i++ {
|
| 794 |
+
// Convert player IDs to names
|
| 795 |
+
playerNames := []string{
|
| 796 |
+
getPlayerName(allPlayers[i][0], nameMap, "PG1"),
|
| 797 |
+
getPlayerName(allPlayers[i][1], nameMap, "PG2"),
|
| 798 |
+
getPlayerName(allPlayers[i][2], nameMap, "SG1"),
|
| 799 |
+
getPlayerName(allPlayers[i][3], nameMap, "SG2"),
|
| 800 |
+
getPlayerName(allPlayers[i][4], nameMap, "SF1"),
|
| 801 |
+
getPlayerName(allPlayers[i][5], nameMap, "SF2"),
|
| 802 |
+
getPlayerName(allPlayers[i][6], nameMap, "PF1"),
|
| 803 |
+
getPlayerName(allPlayers[i][7], nameMap, "PF2"),
|
| 804 |
+
getPlayerName(allPlayers[i][8], nameMap, "C"),
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f\n",
|
| 808 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i])
|
| 809 |
+
fmt.Printf(" Players: PG1=%s, PG2=%s, SG1=%s, SG2=%s, SF1=%s, SF2=%s, PF1=%s, PF2=%s, C=%s\n",
|
| 810 |
+
playerNames[0], playerNames[1], playerNames[2], playerNames[3],
|
| 811 |
+
playerNames[4], playerNames[5], playerNames[6], playerNames[7], playerNames[8])
|
| 812 |
+
}
|
| 813 |
+
}
|
| 814 |
+
}
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
func removeDuplicates(results []LineupData) []LineupData {
|
| 818 |
+
seen := make(map[string]bool)
|
| 819 |
+
var uniqueLineups []LineupData
|
| 820 |
+
|
| 821 |
+
for _, result := range results {
|
| 822 |
+
for i := 0; i < len(result.Players); i++ {
|
| 823 |
+
// Create combo string like Python
|
| 824 |
+
combo := fmt.Sprintf("%d%d%d%d%d%d%d%d%d",
|
| 825 |
+
result.Players[i][0], result.Players[i][1], result.Players[i][2],
|
| 826 |
+
result.Players[i][3], result.Players[i][4], result.Players[i][5],
|
| 827 |
+
result.Players[i][6], result.Players[i][7], result.Players[i][8])
|
| 828 |
+
|
| 829 |
+
// Sort combo like Python
|
| 830 |
+
sortedCombo := sortChars(combo)
|
| 831 |
+
|
| 832 |
+
if !seen[sortedCombo] {
|
| 833 |
+
seen[sortedCombo] = true
|
| 834 |
+
uniqueLineups = append(uniqueLineups, LineupData{
|
| 835 |
+
Salary: []int32{result.Salary[i]},
|
| 836 |
+
Projection: []float64{result.Projection[i]},
|
| 837 |
+
Team: []string{result.Team[i]},
|
| 838 |
+
Team_count: []int32{result.Team_count[i]},
|
| 839 |
+
Secondary: []string{result.Secondary[i]},
|
| 840 |
+
Secondary_count: []int32{result.Secondary_count[i]},
|
| 841 |
+
Ownership: []float64{result.Ownership[i]},
|
| 842 |
+
Players: [][]int32{result.Players[i]},
|
| 843 |
+
})
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
if len(uniqueLineups) == 0 {
|
| 849 |
+
return []LineupData{}
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
var allSalary []int32
|
| 853 |
+
var allProjection []float64
|
| 854 |
+
var allTeam []string
|
| 855 |
+
var allTeamCount []int32
|
| 856 |
+
var allSecondary []string
|
| 857 |
+
var allSecondaryCount []int32
|
| 858 |
+
var allOwnership []float64
|
| 859 |
+
var allPlayers [][]int32
|
| 860 |
+
|
| 861 |
+
for _, lineup := range uniqueLineups {
|
| 862 |
+
allSalary = append(allSalary, lineup.Salary[0])
|
| 863 |
+
allProjection = append(allProjection, lineup.Projection[0])
|
| 864 |
+
allTeam = append(allTeam, lineup.Team[0])
|
| 865 |
+
allTeamCount = append(allTeamCount, lineup.Team_count[0])
|
| 866 |
+
allSecondary = append(allSecondary, lineup.Secondary[0])
|
| 867 |
+
allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
|
| 868 |
+
allOwnership = append(allOwnership, lineup.Ownership[0])
|
| 869 |
+
allPlayers = append(allPlayers, lineup.Players[0])
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
return []LineupData{{
|
| 873 |
+
Salary: allSalary,
|
| 874 |
+
Projection: allProjection,
|
| 875 |
+
Team: allTeam,
|
| 876 |
+
Team_count: allTeamCount,
|
| 877 |
+
Secondary: allSecondary,
|
| 878 |
+
Secondary_count: allSecondaryCount,
|
| 879 |
+
Ownership: allOwnership,
|
| 880 |
+
Players: allPlayers,
|
| 881 |
+
}}
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
func connectToMongoDB() (*mongo.Client, error) {
|
| 885 |
+
uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
|
| 886 |
+
|
| 887 |
+
clientOptions := options.Client().
|
| 888 |
+
ApplyURI(uri).
|
| 889 |
+
SetRetryWrites(true).
|
| 890 |
+
SetServerSelectionTimeout(10 * time.Second).
|
| 891 |
+
SetMaxPoolSize(100).
|
| 892 |
+
SetMinPoolSize(10).
|
| 893 |
+
SetMaxConnIdleTime(30 * time.Second).
|
| 894 |
+
SetRetryReads(true)
|
| 895 |
+
|
| 896 |
+
client, err := mongo.Connect(context.TODO(), clientOptions)
|
| 897 |
+
if err != nil {
|
| 898 |
+
return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
err = client.Ping(context.TODO(), nil)
|
| 902 |
+
if err != nil {
|
| 903 |
+
return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
fmt.Printf("Connected to MongoDB!")
|
| 907 |
+
return client, nil
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, nameMap map[int32]string, site string, sport string) error {
|
| 911 |
+
db := client.Database(fmt.Sprintf("%s_Database", sport))
|
| 912 |
+
|
| 913 |
+
collectionName := fmt.Sprintf("%s_%s_seed_frame_%s", site, sport, slate) // NOTE: change the database here
|
| 914 |
+
collection := db.Collection(collectionName)
|
| 915 |
+
|
| 916 |
+
err := collection.Drop(context.TODO())
|
| 917 |
+
if err != nil {
|
| 918 |
+
fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
var documents []interface{}
|
| 922 |
+
|
| 923 |
+
for _, result := range results {
|
| 924 |
+
|
| 925 |
+
if len(result.Salary) == 0 || len(result.Players) == 0 {
|
| 926 |
+
fmt.Printf("Warning: Empty result found, skipping\n")
|
| 927 |
+
continue
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
for i := 0; i < len(result.Salary); i++ {
|
| 931 |
+
if len(result.Players[i]) < 9 {
|
| 932 |
+
fmt.Printf("Warning: Lineup %d has only %d players, expected 8\n", i, len(result.Players[i]))
|
| 933 |
+
}
|
| 934 |
+
|
| 935 |
+
doc := LineupDocument{
|
| 936 |
+
Salary: result.Salary[i],
|
| 937 |
+
Projection: result.Projection[i],
|
| 938 |
+
Team: result.Team[i],
|
| 939 |
+
Team_count: result.Team_count[i],
|
| 940 |
+
Secondary: result.Secondary[i],
|
| 941 |
+
Secondary_count: result.Secondary_count[i],
|
| 942 |
+
Ownership: result.Ownership[i],
|
| 943 |
+
PG1: result.Players[i][0],
|
| 944 |
+
PG2: result.Players[i][1],
|
| 945 |
+
SG1: result.Players[i][2],
|
| 946 |
+
SG2: result.Players[i][3],
|
| 947 |
+
SF1: result.Players[i][4],
|
| 948 |
+
SF2: result.Players[i][5],
|
| 949 |
+
PF1: result.Players[i][6],
|
| 950 |
+
PF2: result.Players[i][7],
|
| 951 |
+
C: result.Players[i][8],
|
| 952 |
+
CreatedAt: time.Now(),
|
| 953 |
+
}
|
| 954 |
+
|
| 955 |
+
documents = append(documents, doc)
|
| 956 |
+
}
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
if len(documents) == 0 {
|
| 960 |
+
fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
|
| 961 |
+
}
|
| 962 |
+
|
| 963 |
+
if len(documents) > 500000 {
|
| 964 |
+
documents = documents[:500000]
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
chunkSize := 250000
|
| 968 |
+
for i := 0; i < len(documents); i += chunkSize {
|
| 969 |
+
end := i + chunkSize
|
| 970 |
+
if end > len(documents) {
|
| 971 |
+
end = len(documents)
|
| 972 |
+
}
|
| 973 |
+
|
| 974 |
+
chunk := documents[i:end]
|
| 975 |
+
|
| 976 |
+
for attempt := 0; attempt < 5; attempt++ {
|
| 977 |
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
| 978 |
+
|
| 979 |
+
opts := options.InsertMany().SetOrdered(false)
|
| 980 |
+
_, err := collection.InsertMany(ctx, chunk, opts)
|
| 981 |
+
cancel()
|
| 982 |
+
|
| 983 |
+
if err == nil {
|
| 984 |
+
fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
|
| 985 |
+
break
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
|
| 989 |
+
if attempt < 4 {
|
| 990 |
+
time.Sleep(1 * time.Second)
|
| 991 |
+
}
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
if err != nil {
|
| 995 |
+
return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
|
| 996 |
+
}
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
+
fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
|
| 1000 |
+
return nil
|
| 1001 |
+
}
|
| 1002 |
+
|
| 1003 |
+
func groupPlayersBySlate(players []Player) map[string][]Player {
|
| 1004 |
+
slateGroups := make(map[string][]Player)
|
| 1005 |
+
|
| 1006 |
+
for _, player := range players {
|
| 1007 |
+
slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
|
| 1008 |
+
}
|
| 1009 |
+
|
| 1010 |
+
return slateGroups
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
|
| 1014 |
+
if name, exists := nameMap[playerID]; exists && name != "" {
|
| 1015 |
+
return name
|
| 1016 |
+
}
|
| 1017 |
+
return fmt.Sprintf("Unknown_%s_%d", position, playerID)
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
+
func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
|
| 1021 |
+
nameMap := make(map[int32]string)
|
| 1022 |
+
|
| 1023 |
+
for keyStr, value := range playerSet.Maps.NameMap {
|
| 1024 |
+
key, err := strconv.Atoi(keyStr)
|
| 1025 |
+
if err != nil {
|
| 1026 |
+
fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
|
| 1027 |
+
continue
|
| 1028 |
+
}
|
| 1029 |
+
nameMap[int32(key)] = value
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
return nameMap
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
func main() {
|
| 1036 |
+
site := "FD"
|
| 1037 |
+
sport := "NBA"
|
| 1038 |
+
if len(os.Args) > 1 {
|
| 1039 |
+
site = os.Args[1]
|
| 1040 |
+
}
|
| 1041 |
+
if len(os.Args) > 2 {
|
| 1042 |
+
sport = os.Args[2]
|
| 1043 |
+
}
|
| 1044 |
+
processedData, err := loadPlayerData()
|
| 1045 |
+
if err != nil {
|
| 1046 |
+
fmt.Printf("Error loading data: %v\n", err)
|
| 1047 |
+
return
|
| 1048 |
+
}
|
| 1049 |
+
|
| 1050 |
+
start := time.Now()
|
| 1051 |
+
strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
|
| 1052 |
+
rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
|
| 1053 |
+
|
| 1054 |
+
SlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
|
| 1055 |
+
|
| 1056 |
+
salaryMapJSON, projectionMapJSON, ownershipMapJSON, teamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
|
| 1057 |
+
|
| 1058 |
+
nameMap := convertNamesToMaps(&processedData.PlayersMedian)
|
| 1059 |
+
|
| 1060 |
+
mongoClient, err := connectToMongoDB()
|
| 1061 |
+
if err != nil {
|
| 1062 |
+
fmt.Printf("Error connecting to MongoDB: %v\n", err)
|
| 1063 |
+
return
|
| 1064 |
+
}
|
| 1065 |
+
defer func() {
|
| 1066 |
+
if err := mongoClient.Disconnect(context.TODO()); err != nil {
|
| 1067 |
+
fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
|
| 1068 |
+
}
|
| 1069 |
+
}()
|
| 1070 |
+
|
| 1071 |
+
optimalsBySlate, err := loadOptimals()
|
| 1072 |
+
if err != nil {
|
| 1073 |
+
fmt.Printf("Warning: Could not load optimal lineups: %v\n", err)
|
| 1074 |
+
optimalsBySlate = make(map[string]LineupData) // Continue with empty optimals
|
| 1075 |
+
} else {
|
| 1076 |
+
totalOptimals := 0
|
| 1077 |
+
for _, optimals := range optimalsBySlate {
|
| 1078 |
+
totalOptimals += len(optimals.Salary)
|
| 1079 |
+
}
|
| 1080 |
+
fmt.Printf("Loaded %d optimal lineups across all slates\n", totalOptimals)
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
for slate, players := range SlateGroups {
|
| 1084 |
+
|
| 1085 |
+
fmt.Printf("Processing slate: %s\n", slate)
|
| 1086 |
+
|
| 1087 |
+
results, err := runSeedframeRoutines(
|
| 1088 |
+
players, strengthVars, rowsPerLevel,
|
| 1089 |
+
salaryMapJSON, projectionMapJSON,
|
| 1090 |
+
ownershipMapJSON, teamMapJSON, site)
|
| 1091 |
+
|
| 1092 |
+
if err != nil {
|
| 1093 |
+
fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
|
| 1094 |
+
continue
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
// Get optimal lineups for this specific slate
|
| 1098 |
+
slateOptimals := optimalsBySlate[slate]
|
| 1099 |
+
|
| 1100 |
+
// Append optimal lineups for this slate
|
| 1101 |
+
finalResults := appendOptimalLineups(results, slateOptimals)
|
| 1102 |
+
|
| 1103 |
+
exportResults := removeDuplicates(finalResults)
|
| 1104 |
+
|
| 1105 |
+
exportResults[0] = sortDataByField(exportResults[0], "projection", false)
|
| 1106 |
+
|
| 1107 |
+
err = insertLineupsToMongoDB(mongoClient, exportResults, slate, nameMap, site, sport)
|
| 1108 |
+
if err != nil {
|
| 1109 |
+
fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
|
| 1110 |
+
continue
|
| 1111 |
+
}
|
| 1112 |
+
|
| 1113 |
+
printResults(exportResults, nameMap)
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
// Add this line at the end
|
| 1117 |
+
fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
|
| 1118 |
+
}
|
func/showdown_go/showdown_seed_frames.go
ADDED
|
@@ -0,0 +1,1006 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
// Script Imports
|
| 5 |
+
"context"
|
| 6 |
+
"encoding/json"
|
| 7 |
+
"fmt"
|
| 8 |
+
"io/ioutil"
|
| 9 |
+
"math/rand"
|
| 10 |
+
"os"
|
| 11 |
+
"slices"
|
| 12 |
+
"sort"
|
| 13 |
+
"strconv"
|
| 14 |
+
"strings"
|
| 15 |
+
"time"
|
| 16 |
+
|
| 17 |
+
// MongoDB Imports
|
| 18 |
+
"go.mongodb.org/mongo-driver/mongo"
|
| 19 |
+
"go.mongodb.org/mongo-driver/mongo/options"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
type LineupData struct {
|
| 23 |
+
Salary []int32
|
| 24 |
+
Projection []float64
|
| 25 |
+
Team []string
|
| 26 |
+
Team_count []int32
|
| 27 |
+
Secondary []string
|
| 28 |
+
Secondary_count []int32
|
| 29 |
+
Ownership []float64
|
| 30 |
+
Players [][]int32
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
type Player struct {
|
| 34 |
+
ID int32 `json:"id"`
|
| 35 |
+
Name string `json:"name"`
|
| 36 |
+
Team string `json:"team"`
|
| 37 |
+
Position string `json:"position"`
|
| 38 |
+
Salary int32 `json:"salary"`
|
| 39 |
+
Projection float64 `json:"projection"`
|
| 40 |
+
Ownership float64 `json:"ownership"`
|
| 41 |
+
SalaryValue float64 `json:"salary_value"`
|
| 42 |
+
ProjValue float64 `json:"proj_value"`
|
| 43 |
+
OwnValue float64 `json:"own_value"`
|
| 44 |
+
SortValue float64 `json:"sort_value"`
|
| 45 |
+
Slate string `json:"slate"`
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
type PlayerSet struct {
|
| 49 |
+
Players []Player `json:"players"`
|
| 50 |
+
Maps struct {
|
| 51 |
+
NameMap map[string]string `json:"name_map"`
|
| 52 |
+
SalaryMap map[string]int32 `json:"salary_map"`
|
| 53 |
+
ProjectionMap map[string]float64 `json:"projection_map"`
|
| 54 |
+
OwnershipMap map[string]float64 `json:"ownership_map"`
|
| 55 |
+
TeamMap map[string]string `json:"team_map"`
|
| 56 |
+
} `json:"maps"`
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
type ProcessedData struct {
|
| 60 |
+
PlayersMedian PlayerSet `json:"players_median"`
|
| 61 |
+
CptMedian PlayerSet `json:"cpt_median"`
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
type PlayerData struct {
|
| 65 |
+
Players []Player
|
| 66 |
+
NameMap map[int]string
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
type StrengthResult struct {
|
| 70 |
+
Index int
|
| 71 |
+
Data LineupData
|
| 72 |
+
Error error
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
type LineupDocument struct {
|
| 76 |
+
Salary int32 `bson:"salary"`
|
| 77 |
+
Projection float64 `bson:"proj"`
|
| 78 |
+
Team string `bson:"Team"`
|
| 79 |
+
Team_count int32 `bson:"Team_count"`
|
| 80 |
+
Secondary string `bson:"Secondary"`
|
| 81 |
+
Secondary_count int32 `bson:"Secondary_count"`
|
| 82 |
+
Ownership float64 `bson:"Own"`
|
| 83 |
+
CPT string `bson:"CPT"`
|
| 84 |
+
FLEX1 string `bson:"FLEX1"`
|
| 85 |
+
FLEX2 string `bson:"FLEX2"`
|
| 86 |
+
FLEX3 string `bson:"FLEX3"`
|
| 87 |
+
FLEX4 string `bson:"FLEX4"`
|
| 88 |
+
FLEX5 string `bson:"FLEX5"`
|
| 89 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
func loadPlayerData() (*ProcessedData, error) {
|
| 93 |
+
data, err := ioutil.ReadFile("showdown_go/player_data.json")
|
| 94 |
+
if err != nil {
|
| 95 |
+
return nil, fmt.Errorf("failed to read in data: %v", err)
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
var processedData ProcessedData
|
| 99 |
+
if err := json.Unmarshal(data, &processedData); err != nil {
|
| 100 |
+
return nil, fmt.Errorf("failed to parse json: %v", err)
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
return &processedData, nil
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
func convertMapsToInt32Keys(playerSet *PlayerSet) (map[int32]int32, map[int32]float64, map[int32]float64, map[int32]string) {
|
| 107 |
+
salaryMap := make(map[int32]int32)
|
| 108 |
+
projMap := make(map[int32]float64)
|
| 109 |
+
ownMap := make(map[int32]float64)
|
| 110 |
+
teamMap := make(map[int32]string)
|
| 111 |
+
|
| 112 |
+
for keyStr, value := range playerSet.Maps.SalaryMap {
|
| 113 |
+
key, err := strconv.Atoi(keyStr)
|
| 114 |
+
if err != nil {
|
| 115 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 116 |
+
continue
|
| 117 |
+
}
|
| 118 |
+
salaryMap[int32(key)] = value
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
for keyStr, value := range playerSet.Maps.ProjectionMap {
|
| 122 |
+
key, err := strconv.Atoi(keyStr)
|
| 123 |
+
if err != nil {
|
| 124 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 125 |
+
continue
|
| 126 |
+
}
|
| 127 |
+
projMap[int32(key)] = value
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
for keyStr, value := range playerSet.Maps.OwnershipMap {
|
| 131 |
+
key, err := strconv.Atoi(keyStr)
|
| 132 |
+
if err != nil {
|
| 133 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 134 |
+
continue
|
| 135 |
+
}
|
| 136 |
+
ownMap[int32(key)] = value
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
for keyStr, value := range playerSet.Maps.TeamMap {
|
| 140 |
+
key, err := strconv.Atoi(keyStr)
|
| 141 |
+
if err != nil {
|
| 142 |
+
fmt.Printf("Error converting key %s: %v\n", keyStr, err)
|
| 143 |
+
continue
|
| 144 |
+
}
|
| 145 |
+
teamMap[int32(key)] = value
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
return salaryMap, projMap, ownMap, teamMap
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
func processAndFill[T comparable, U any](input []T, valueMap map[T]U) []U {
|
| 152 |
+
result := make([]U, len(input))
|
| 153 |
+
|
| 154 |
+
for i, key := range input {
|
| 155 |
+
if value, exists := valueMap[key]; exists {
|
| 156 |
+
result[i] = value
|
| 157 |
+
} else {
|
| 158 |
+
var zero U
|
| 159 |
+
result[i] = zero
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
return result
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
func sortChars(strData string) string {
|
| 167 |
+
runes := []rune(strData)
|
| 168 |
+
slices.Sort(runes)
|
| 169 |
+
return string(runes)
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
func rowMostCommon(row []int) (*int, *int) {
|
| 173 |
+
if len(row) == 0 {
|
| 174 |
+
return nil, nil
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
counts := make(map[int]int)
|
| 178 |
+
for _, value := range row {
|
| 179 |
+
counts[value]++
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
if len(counts) < 2 {
|
| 183 |
+
return nil, nil
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
mostCommon := 0
|
| 187 |
+
maxCount := 0
|
| 188 |
+
secondMost := 0
|
| 189 |
+
secondMax := 0
|
| 190 |
+
|
| 191 |
+
for value, count := range counts {
|
| 192 |
+
if count > maxCount {
|
| 193 |
+
secondMax = maxCount
|
| 194 |
+
secondMost = mostCommon
|
| 195 |
+
|
| 196 |
+
maxCount = count
|
| 197 |
+
mostCommon = value
|
| 198 |
+
} else if count > secondMax && count < maxCount {
|
| 199 |
+
secondMax = count
|
| 200 |
+
secondMost = value
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
return &mostCommon, &secondMost
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
func rowBiggestAndSecond(row []int) (int, int) {
|
| 209 |
+
if len(row) == 0 {
|
| 210 |
+
return 0, 0
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
counts := make(map[int]int)
|
| 214 |
+
for _, value := range row {
|
| 215 |
+
counts[value]++
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
if len(counts) == 1 {
|
| 219 |
+
return len(row), 0
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
biggestVal := 0
|
| 223 |
+
secondBiggestVal := 0
|
| 224 |
+
|
| 225 |
+
for _, count := range counts {
|
| 226 |
+
if count > biggestVal {
|
| 227 |
+
secondBiggestVal = biggestVal
|
| 228 |
+
biggestVal = count
|
| 229 |
+
} else if count > secondBiggestVal && count < biggestVal {
|
| 230 |
+
secondBiggestVal = count
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
return biggestVal, secondBiggestVal
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
func createOverallDFs(players []Player, pos string) PlayerData {
|
| 238 |
+
var filteredPlayers []Player
|
| 239 |
+
for _, player := range players {
|
| 240 |
+
if strings.Contains(player.Position, pos) {
|
| 241 |
+
filteredPlayers = append(filteredPlayers, player)
|
| 242 |
+
}
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
nameMap := make(map[int]string)
|
| 246 |
+
for i, player := range filteredPlayers {
|
| 247 |
+
nameMap[i] = player.Name
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
return PlayerData{
|
| 251 |
+
Players: filteredPlayers,
|
| 252 |
+
NameMap: nameMap,
|
| 253 |
+
}
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
func sumSalaryRows(data [][]int32) []int32 {
|
| 257 |
+
result := make([]int32, len(data))
|
| 258 |
+
|
| 259 |
+
for i, row := range data {
|
| 260 |
+
var sum int32
|
| 261 |
+
for _, value := range row {
|
| 262 |
+
sum += value
|
| 263 |
+
}
|
| 264 |
+
result[i] = sum
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
return result
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
func sumOwnRows(data [][]float64) []float64 {
|
| 271 |
+
result := make([]float64, len(data))
|
| 272 |
+
|
| 273 |
+
for i, row := range data {
|
| 274 |
+
var sum float64
|
| 275 |
+
for _, value := range row {
|
| 276 |
+
sum += value
|
| 277 |
+
}
|
| 278 |
+
result[i] = sum
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
return result
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
func sumProjRows(data [][]float64) []float64 {
|
| 285 |
+
result := make([]float64, len(data))
|
| 286 |
+
|
| 287 |
+
for i, row := range data {
|
| 288 |
+
var sum float64
|
| 289 |
+
for _, value := range row {
|
| 290 |
+
sum += value
|
| 291 |
+
}
|
| 292 |
+
result[i] = sum
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
return result
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
func filterMax[T ~int32 | ~float64](values []T, maxVal T) []int {
|
| 299 |
+
var validIndicies []int
|
| 300 |
+
|
| 301 |
+
for i, value := range values {
|
| 302 |
+
if value <= maxVal {
|
| 303 |
+
validIndicies = append(validIndicies, i)
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
return validIndicies
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
func filterMin[T ~int32 | ~float64](values []T, minVal T) []int {
|
| 311 |
+
var validIndicies []int
|
| 312 |
+
|
| 313 |
+
for i, value := range values {
|
| 314 |
+
if value >= minVal {
|
| 315 |
+
validIndicies = append(validIndicies, i)
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
return validIndicies
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
func sliceByIndicies[T any](data []T, indicies []int) []T {
|
| 323 |
+
result := make([]T, len(indicies))
|
| 324 |
+
|
| 325 |
+
for i, idx := range indicies {
|
| 326 |
+
result[i] = data[idx]
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
return result
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
func sortDataByField(data LineupData, field string, ascending bool) LineupData {
|
| 333 |
+
indicies := make([]int, len(data.Ownership))
|
| 334 |
+
for i := range indicies {
|
| 335 |
+
indicies[i] = i
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
switch field {
|
| 339 |
+
case "salary":
|
| 340 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 341 |
+
if ascending {
|
| 342 |
+
return data.Salary[indicies[i]] < data.Salary[indicies[j]]
|
| 343 |
+
}
|
| 344 |
+
return data.Salary[indicies[i]] > data.Salary[indicies[j]]
|
| 345 |
+
})
|
| 346 |
+
case "projection":
|
| 347 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 348 |
+
if ascending {
|
| 349 |
+
return data.Projection[indicies[i]] < data.Projection[indicies[j]]
|
| 350 |
+
}
|
| 351 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 352 |
+
})
|
| 353 |
+
case "ownership":
|
| 354 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 355 |
+
if ascending {
|
| 356 |
+
return data.Ownership[indicies[i]] < data.Ownership[indicies[j]]
|
| 357 |
+
}
|
| 358 |
+
return data.Ownership[indicies[i]] > data.Ownership[indicies[j]]
|
| 359 |
+
})
|
| 360 |
+
default:
|
| 361 |
+
sort.Slice(indicies, func(i, j int) bool {
|
| 362 |
+
return data.Projection[indicies[i]] > data.Projection[indicies[j]]
|
| 363 |
+
})
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
return LineupData{
|
| 367 |
+
Salary: sliceByIndicies(data.Salary, indicies),
|
| 368 |
+
Projection: sliceByIndicies(data.Projection, indicies),
|
| 369 |
+
Team: sliceByIndicies(data.Team, indicies),
|
| 370 |
+
Team_count: sliceByIndicies(data.Team_count, indicies),
|
| 371 |
+
Secondary: sliceByIndicies(data.Secondary, indicies),
|
| 372 |
+
Secondary_count: sliceByIndicies(data.Secondary_count, indicies),
|
| 373 |
+
Ownership: sliceByIndicies(data.Ownership, indicies),
|
| 374 |
+
Players: sliceByIndicies(data.Players, indicies),
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
func combineArrays(flex1, flex2, flex3, flex4, flex5, flex6 []int32) [][]int32 {
|
| 379 |
+
length := len(flex1)
|
| 380 |
+
|
| 381 |
+
result := make([][]int32, length)
|
| 382 |
+
|
| 383 |
+
for i := 0; i < length; i++ {
|
| 384 |
+
result[i] = []int32{
|
| 385 |
+
flex1[i],
|
| 386 |
+
flex2[i],
|
| 387 |
+
flex3[i],
|
| 388 |
+
flex4[i],
|
| 389 |
+
flex5[i],
|
| 390 |
+
flex6[i],
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
return result
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
func createSeedFrames(combinedArrays [][]int32, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) LineupData {
|
| 398 |
+
|
| 399 |
+
salaries := make([][]int32, len(combinedArrays))
|
| 400 |
+
projections := make([][]float64, len(combinedArrays))
|
| 401 |
+
ownership := make([][]float64, len(combinedArrays))
|
| 402 |
+
teamArrays := make([][]string, len(combinedArrays))
|
| 403 |
+
|
| 404 |
+
for i, row := range combinedArrays {
|
| 405 |
+
|
| 406 |
+
cptPlayers := []int32{row[0]}
|
| 407 |
+
flexPlayers := row[1:]
|
| 408 |
+
|
| 409 |
+
cptSalaries := processAndFill(cptPlayers, cptSalaryMap)
|
| 410 |
+
cptProjections := processAndFill(cptPlayers, cptProjMap)
|
| 411 |
+
cptOwnership := processAndFill(cptPlayers, cptOwnMap)
|
| 412 |
+
cptTeams := processAndFill(cptPlayers, cptTeamMap)
|
| 413 |
+
|
| 414 |
+
flexSalaries := processAndFill(flexPlayers, flexSalaryMap)
|
| 415 |
+
flexProjections := processAndFill(flexPlayers, flexProjMap)
|
| 416 |
+
flexOwnership := processAndFill(flexPlayers, flexOwnMap)
|
| 417 |
+
flexTeams := processAndFill(flexPlayers, flexTeamMap)
|
| 418 |
+
|
| 419 |
+
salaries[i] = append(cptSalaries, flexSalaries...)
|
| 420 |
+
projections[i] = append(cptProjections, flexProjections...)
|
| 421 |
+
ownership[i] = append(cptOwnership, flexOwnership...)
|
| 422 |
+
teamArrays[i] = append(cptTeams, flexTeams...)
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
totalSalaries := sumSalaryRows(salaries)
|
| 426 |
+
totalProjections := sumProjRows(projections)
|
| 427 |
+
totalOwnership := sumOwnRows(ownership)
|
| 428 |
+
|
| 429 |
+
teamData := make([]string, len(teamArrays))
|
| 430 |
+
teamCounts := make([]int32, len(teamArrays))
|
| 431 |
+
secondaryData := make([]string, len(teamArrays))
|
| 432 |
+
secondaryCounts := make([]int32, len(teamArrays))
|
| 433 |
+
|
| 434 |
+
for i, teams := range teamArrays {
|
| 435 |
+
teamMap := make(map[string]int)
|
| 436 |
+
uniqueTeams := make([]string, 0)
|
| 437 |
+
for _, team := range teams {
|
| 438 |
+
if _, exists := teamMap[team]; !exists {
|
| 439 |
+
teamMap[team] = len(uniqueTeams)
|
| 440 |
+
uniqueTeams = append(uniqueTeams, team)
|
| 441 |
+
}
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
teamInts := make([]int, len(teams))
|
| 445 |
+
for j, team := range teams {
|
| 446 |
+
teamInts[j] = teamMap[team]
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
mostCommon, secondMost := rowMostCommon(teamInts)
|
| 450 |
+
if mostCommon != nil {
|
| 451 |
+
teamData[i] = uniqueTeams[*mostCommon]
|
| 452 |
+
teamCount, _ := rowBiggestAndSecond(teamInts)
|
| 453 |
+
teamCounts[i] = int32(teamCount)
|
| 454 |
+
}
|
| 455 |
+
if secondMost != nil {
|
| 456 |
+
secondaryData[i] = uniqueTeams[*secondMost]
|
| 457 |
+
_, secondaryCount := rowBiggestAndSecond(teamInts)
|
| 458 |
+
secondaryCounts[i] = int32(secondaryCount)
|
| 459 |
+
}
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
var validIndicies []int
|
| 463 |
+
if site == "DK" {
|
| 464 |
+
validIndicies = filterMax(totalSalaries, int32(50000))
|
| 465 |
+
} else {
|
| 466 |
+
validIndicies = filterMax(totalSalaries, int32(60000))
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
validData := LineupData{
|
| 470 |
+
Salary: sliceByIndicies(totalSalaries, validIndicies),
|
| 471 |
+
Projection: sliceByIndicies(totalProjections, validIndicies),
|
| 472 |
+
Team: sliceByIndicies(teamData, validIndicies),
|
| 473 |
+
Team_count: sliceByIndicies(teamCounts, validIndicies),
|
| 474 |
+
Secondary: sliceByIndicies(secondaryData, validIndicies),
|
| 475 |
+
Secondary_count: sliceByIndicies(secondaryCounts, validIndicies),
|
| 476 |
+
Ownership: sliceByIndicies(totalOwnership, validIndicies),
|
| 477 |
+
Players: sliceByIndicies(combinedArrays, validIndicies),
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
return sortDataByField(validData, "ownership", false)
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
func calculateQuantile(values []float64, quantile float64) (float64, error) {
|
| 484 |
+
if len(values) == 0 {
|
| 485 |
+
return 0, fmt.Errorf("cannot calculate quantile of empty slice")
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
if quantile < 0 || quantile > 1 {
|
| 489 |
+
return 0, fmt.Errorf("quantile must be between 0 and 1, got %.2f", quantile)
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
sorted := make([]float64, len(values))
|
| 493 |
+
copy(sorted, values)
|
| 494 |
+
sort.Float64s(sorted)
|
| 495 |
+
|
| 496 |
+
index := int(float64(len(sorted)-1) * quantile)
|
| 497 |
+
return sorted[index], nil
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
func generateUniqueRow(playerIDs []int32, count int, rng *rand.Rand) ([]int32, error) {
|
| 501 |
+
if count > len(playerIDs) {
|
| 502 |
+
return nil, fmt.Errorf("cannot generate %d unique values from %d players", count, len(playerIDs))
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
shuffled := make([]int32, len(playerIDs))
|
| 506 |
+
copy(shuffled, playerIDs)
|
| 507 |
+
|
| 508 |
+
for i := len(shuffled) - 1; i > 0; i-- {
|
| 509 |
+
j := rng.Intn(i + 1)
|
| 510 |
+
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
return shuffled[:count], nil
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
func generateBaseArrays(cptPlayers, flexPlayers []Player, numRows int, rng *rand.Rand) ([][]int32, error) {
|
| 517 |
+
|
| 518 |
+
if len(cptPlayers) < 1 || len(flexPlayers) < 5 {
|
| 519 |
+
return nil, fmt.Errorf("need at least 1 cpt and 5 flex, got %d cpt and %d flex", len(cptPlayers), len(flexPlayers))
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
var validArrays [][]int32
|
| 523 |
+
attempts := 0
|
| 524 |
+
maxAttempts := numRows * 10
|
| 525 |
+
|
| 526 |
+
for len(validArrays) < numRows && attempts < maxAttempts {
|
| 527 |
+
attempts++
|
| 528 |
+
|
| 529 |
+
cptPlayer := cptPlayers[rng.Intn(len(cptPlayers))]
|
| 530 |
+
flexPlayers_selected := make([]Player, 5)
|
| 531 |
+
|
| 532 |
+
for i := 0; i < 5; i++ {
|
| 533 |
+
flexPlayers_selected[i] = flexPlayers[rng.Intn(len(flexPlayers))]
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
flexNames := make([]string, 5)
|
| 537 |
+
for i, player := range flexPlayers_selected {
|
| 538 |
+
flexNames[i] = player.Name
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
if flexNames[0] != flexNames[1] && flexNames[0] != flexNames[2] && flexNames[0] != flexNames[3] && flexNames[0] != flexNames[4] &&
|
| 542 |
+
flexNames[1] != flexNames[2] && flexNames[1] != flexNames[3] && flexNames[1] != flexNames[4] &&
|
| 543 |
+
flexNames[2] != flexNames[3] && flexNames[2] != flexNames[4] &&
|
| 544 |
+
flexNames[3] != flexNames[4] {
|
| 545 |
+
|
| 546 |
+
cptUnique := true
|
| 547 |
+
for _, flexName := range flexNames {
|
| 548 |
+
if cptPlayer.Name == flexName {
|
| 549 |
+
cptUnique = false
|
| 550 |
+
break
|
| 551 |
+
}
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
if cptUnique {
|
| 555 |
+
flexIDs := make([]int32, 5)
|
| 556 |
+
for i, player := range flexPlayers_selected {
|
| 557 |
+
flexIDs[i] = player.ID
|
| 558 |
+
}
|
| 559 |
+
combinedRow := append([]int32{cptPlayer.ID}, flexIDs...)
|
| 560 |
+
validArrays = append(validArrays, combinedRow)
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
if len(validArrays) == 0 {
|
| 566 |
+
return nil, fmt.Errorf("only generated %d valid lineups out of %d requested", len(validArrays), numRows)
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
return validArrays, nil
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
func filterPlayersInQuantile(players []Player, strengthStep float64) ([]Player, error) {
|
| 573 |
+
if len(players) == 0 {
|
| 574 |
+
return nil, fmt.Errorf("no players provided")
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
ownVals := make([]float64, len(players))
|
| 578 |
+
for i, player := range players {
|
| 579 |
+
ownVals[i] = player.OwnValue
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
threshold, err := calculateQuantile(ownVals, strengthStep)
|
| 583 |
+
if err != nil {
|
| 584 |
+
return nil, err
|
| 585 |
+
}
|
| 586 |
+
|
| 587 |
+
var filtered []Player
|
| 588 |
+
for _, player := range players {
|
| 589 |
+
if player.OwnValue >= threshold {
|
| 590 |
+
filtered = append(filtered, player)
|
| 591 |
+
}
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
if len(filtered) == 0 {
|
| 595 |
+
return nil, fmt.Errorf("no players meet ownership threshold %.2f", threshold)
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
return filtered, nil
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
func processStrengthLevels(cptPlayers, flexPlayers []Player, strengthStep float64, numRows int, rng *rand.Rand, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) (LineupData, error) {
|
| 602 |
+
|
| 603 |
+
filteredCptPlayers, err := filterPlayersInQuantile(cptPlayers, strengthStep)
|
| 604 |
+
if err != nil {
|
| 605 |
+
return LineupData{}, err
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
filteredFlexPlayers, err := filterPlayersInQuantile(flexPlayers, strengthStep)
|
| 609 |
+
if err != nil {
|
| 610 |
+
return LineupData{}, err
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
overallArrays, err := generateBaseArrays(filteredCptPlayers, filteredFlexPlayers, numRows, rng)
|
| 614 |
+
if err != nil {
|
| 615 |
+
return LineupData{}, err
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
result := createSeedFrames(overallArrays, cptSalaryMap, flexSalaryMap, cptProjMap, flexProjMap, cptOwnMap, flexOwnMap, cptTeamMap, flexTeamMap, site)
|
| 619 |
+
|
| 620 |
+
return result, nil
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
func runSeedframeRoutines(cptPlayers, flexPlayers []Player, strengthVars []float64, rowsPerLevel []int, cptSalaryMap, flexSalaryMap map[int32]int32, cptProjMap, flexProjMap map[int32]float64, cptOwnMap, flexOwnMap map[int32]float64, cptTeamMap, flexTeamMap map[int32]string, site string) ([]LineupData, error) {
|
| 624 |
+
resultsChan := make(chan StrengthResult, len(strengthVars))
|
| 625 |
+
|
| 626 |
+
for i, strengthStep := range strengthVars {
|
| 627 |
+
go func(step float64, rows int, index int) {
|
| 628 |
+
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(index)))
|
| 629 |
+
|
| 630 |
+
result, err := processStrengthLevels(cptPlayers, flexPlayers, step, rows, rng, cptSalaryMap, flexSalaryMap, cptProjMap, flexProjMap, cptOwnMap, flexOwnMap, cptTeamMap, flexTeamMap, site)
|
| 631 |
+
resultsChan <- StrengthResult{Index: index, Data: result, Error: err}
|
| 632 |
+
|
| 633 |
+
if err != nil {
|
| 634 |
+
fmt.Printf("Error in strength level %.2f: %v\n", step, err)
|
| 635 |
+
} else {
|
| 636 |
+
fmt.Printf("Completed strength level %.2f with %d lineups\n", step, len(result.Salary))
|
| 637 |
+
}
|
| 638 |
+
}(strengthStep, rowsPerLevel[i], i)
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
allResults := make([]LineupData, len(strengthVars))
|
| 642 |
+
var errors []error
|
| 643 |
+
successCount := 0
|
| 644 |
+
|
| 645 |
+
for i := 0; i < len(strengthVars); i++ {
|
| 646 |
+
result := <-resultsChan
|
| 647 |
+
if result.Error != nil {
|
| 648 |
+
errors = append(errors, result.Error)
|
| 649 |
+
} else {
|
| 650 |
+
allResults[result.Index] = result.Data
|
| 651 |
+
successCount++
|
| 652 |
+
}
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
if successCount == 0 {
|
| 656 |
+
return nil, fmt.Errorf("all %d strength levels failed: %v", len(strengthVars), errors)
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
var validResults []LineupData
|
| 660 |
+
for i, result := range allResults {
|
| 661 |
+
if len(result.Salary) > 0 {
|
| 662 |
+
validResults = append(validResults, result)
|
| 663 |
+
} else {
|
| 664 |
+
fmt.Printf("skipping empty result from strength level %.2f\n", strengthVars[i])
|
| 665 |
+
}
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
fmt.Printf("📊 Successfully processed %d out of %d strength levels\n", len(validResults), len(strengthVars))
|
| 669 |
+
return validResults, nil
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
func printResults(results []LineupData) {
|
| 673 |
+
fmt.Printf("Generated %d strength levels:\n", len(results))
|
| 674 |
+
|
| 675 |
+
// Combine all results into one big dataset
|
| 676 |
+
var allSalaries []int32
|
| 677 |
+
var allProjections []float64
|
| 678 |
+
var allOwnership []float64
|
| 679 |
+
var allPlayers [][]int32
|
| 680 |
+
|
| 681 |
+
for _, result := range results {
|
| 682 |
+
allSalaries = append(allSalaries, result.Salary...)
|
| 683 |
+
allProjections = append(allProjections, result.Projection...)
|
| 684 |
+
allOwnership = append(allOwnership, result.Ownership...)
|
| 685 |
+
allPlayers = append(allPlayers, result.Players...)
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
fmt.Printf("Total lineups generated: %d\n", len(allSalaries))
|
| 689 |
+
|
| 690 |
+
if len(allSalaries) > 0 {
|
| 691 |
+
// Print top 5 lineups (highest projection)
|
| 692 |
+
fmt.Printf("\nTop 5 lineups (by projection):\n")
|
| 693 |
+
for i := 0; i < 5 && i < len(allSalaries); i++ {
|
| 694 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f, Players=%v\n",
|
| 695 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i], allPlayers[i])
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
// Print bottom 5 lineups (lowest projection)
|
| 699 |
+
if len(allSalaries) > 5 {
|
| 700 |
+
fmt.Printf("\nBottom 5 lineups (by projection):\n")
|
| 701 |
+
start := len(allSalaries) - 5
|
| 702 |
+
for i := start; i < len(allSalaries); i++ {
|
| 703 |
+
fmt.Printf(" Lineup %d: Salary=%d, Projection=%.2f, Ownership=%.3f, Players=%v\n",
|
| 704 |
+
i+1, allSalaries[i], allProjections[i], allOwnership[i], allPlayers[i])
|
| 705 |
+
}
|
| 706 |
+
}
|
| 707 |
+
}
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
func removeDuplicates(results []LineupData) []LineupData {
|
| 711 |
+
seen := make(map[string]bool)
|
| 712 |
+
var uniqueLineups []LineupData
|
| 713 |
+
|
| 714 |
+
for _, result := range results {
|
| 715 |
+
for i := 0; i < len(result.Players); i++ {
|
| 716 |
+
// Create combo string like Python
|
| 717 |
+
combo := fmt.Sprintf("%d%d%d%d%d%d",
|
| 718 |
+
result.Players[i][0], result.Players[i][1], result.Players[i][2],
|
| 719 |
+
result.Players[i][3], result.Players[i][4], result.Players[i][5])
|
| 720 |
+
|
| 721 |
+
// Sort combo like Python
|
| 722 |
+
sortedCombo := sortChars(combo)
|
| 723 |
+
|
| 724 |
+
if !seen[sortedCombo] {
|
| 725 |
+
seen[sortedCombo] = true
|
| 726 |
+
uniqueLineups = append(uniqueLineups, LineupData{
|
| 727 |
+
Salary: []int32{result.Salary[i]},
|
| 728 |
+
Projection: []float64{result.Projection[i]},
|
| 729 |
+
Team: []string{result.Team[i]},
|
| 730 |
+
Team_count: []int32{result.Team_count[i]},
|
| 731 |
+
Secondary: []string{result.Secondary[i]},
|
| 732 |
+
Secondary_count: []int32{result.Secondary_count[i]},
|
| 733 |
+
Ownership: []float64{result.Ownership[i]},
|
| 734 |
+
Players: [][]int32{result.Players[i]},
|
| 735 |
+
})
|
| 736 |
+
}
|
| 737 |
+
}
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
if len(uniqueLineups) == 0 {
|
| 741 |
+
return []LineupData{}
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
var allSalary []int32
|
| 745 |
+
var allProjection []float64
|
| 746 |
+
var allTeam []string
|
| 747 |
+
var allTeamCount []int32
|
| 748 |
+
var allSecondary []string
|
| 749 |
+
var allSecondaryCount []int32
|
| 750 |
+
var allOwnership []float64
|
| 751 |
+
var allPlayers [][]int32
|
| 752 |
+
|
| 753 |
+
for _, lineup := range uniqueLineups {
|
| 754 |
+
allSalary = append(allSalary, lineup.Salary[0])
|
| 755 |
+
allProjection = append(allProjection, lineup.Projection[0])
|
| 756 |
+
allTeam = append(allTeam, lineup.Team[0])
|
| 757 |
+
allTeamCount = append(allTeamCount, lineup.Team_count[0])
|
| 758 |
+
allSecondary = append(allSecondary, lineup.Secondary[0])
|
| 759 |
+
allSecondaryCount = append(allSecondaryCount, lineup.Secondary_count[0])
|
| 760 |
+
allOwnership = append(allOwnership, lineup.Ownership[0])
|
| 761 |
+
allPlayers = append(allPlayers, lineup.Players[0])
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
return []LineupData{{
|
| 765 |
+
Salary: allSalary,
|
| 766 |
+
Projection: allProjection,
|
| 767 |
+
Team: allTeam,
|
| 768 |
+
Team_count: allTeamCount,
|
| 769 |
+
Secondary: allSecondary,
|
| 770 |
+
Secondary_count: allSecondaryCount,
|
| 771 |
+
Ownership: allOwnership,
|
| 772 |
+
Players: allPlayers,
|
| 773 |
+
}}
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
func connectToMongoDB() (*mongo.Client, error) {
|
| 777 |
+
uri := "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcluster.lgwtp5i.mongodb.net/?retryWrites=true&w=majority"
|
| 778 |
+
|
| 779 |
+
clientOptions := options.Client().
|
| 780 |
+
ApplyURI(uri).
|
| 781 |
+
SetRetryWrites(true).
|
| 782 |
+
SetServerSelectionTimeout(10 * time.Second).
|
| 783 |
+
SetMaxPoolSize(100).
|
| 784 |
+
SetMinPoolSize(10).
|
| 785 |
+
SetMaxConnIdleTime(30 * time.Second).
|
| 786 |
+
SetRetryReads(true)
|
| 787 |
+
|
| 788 |
+
client, err := mongo.Connect(context.TODO(), clientOptions)
|
| 789 |
+
if err != nil {
|
| 790 |
+
return nil, fmt.Errorf("failed to connect to MongoDB: %v", err)
|
| 791 |
+
}
|
| 792 |
+
|
| 793 |
+
err = client.Ping(context.TODO(), nil)
|
| 794 |
+
if err != nil {
|
| 795 |
+
return nil, fmt.Errorf("failed to ping mMongoDB %v", err)
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
fmt.Printf("Connected to MongoDB!")
|
| 799 |
+
return client, nil
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
func insertLineupsToMongoDB(client *mongo.Client, results []LineupData, slate string, cptNameMap, flexNameMap map[int32]string, site string, sport string) error {
|
| 803 |
+
db := client.Database(fmt.Sprintf("%s_Database", sport))
|
| 804 |
+
|
| 805 |
+
collectionName := fmt.Sprintf("%s_%s_SD_seed_frame_%s", site, sport, slate)
|
| 806 |
+
collection := db.Collection(collectionName)
|
| 807 |
+
|
| 808 |
+
err := collection.Drop(context.TODO())
|
| 809 |
+
if err != nil {
|
| 810 |
+
fmt.Printf("Warning: Could not drop collection %s: %v\n", collectionName, err)
|
| 811 |
+
}
|
| 812 |
+
|
| 813 |
+
var documents []interface{}
|
| 814 |
+
|
| 815 |
+
for _, result := range results {
|
| 816 |
+
|
| 817 |
+
if len(result.Salary) == 0 || len(result.Players) == 0 {
|
| 818 |
+
fmt.Printf("Warning: Empty result found, skipping\n")
|
| 819 |
+
continue
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
for i := 0; i < len(result.Salary); i++ {
|
| 823 |
+
if len(result.Players[i]) < 6 {
|
| 824 |
+
fmt.Printf("Warning: Lineup %d has only %d players, expected 6\n", i, len(result.Players[i]))
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
playerNames := []string{
|
| 828 |
+
getPlayerName(result.Players[i][0], cptNameMap, "CPT"), // CPT
|
| 829 |
+
getPlayerName(result.Players[i][1], flexNameMap, "FLEX"), // FLEX1
|
| 830 |
+
getPlayerName(result.Players[i][2], flexNameMap, "FLEX"), // FLEX2
|
| 831 |
+
getPlayerName(result.Players[i][3], flexNameMap, "FLEX"), // FLEX3
|
| 832 |
+
getPlayerName(result.Players[i][4], flexNameMap, "FLEX"), // FLEX4
|
| 833 |
+
getPlayerName(result.Players[i][5], flexNameMap, "FLEX"), // FLEX5
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
doc := LineupDocument{
|
| 837 |
+
Salary: result.Salary[i],
|
| 838 |
+
Projection: result.Projection[i],
|
| 839 |
+
Team: result.Team[i],
|
| 840 |
+
Team_count: result.Team_count[i],
|
| 841 |
+
Secondary: result.Secondary[i],
|
| 842 |
+
Secondary_count: result.Secondary_count[i],
|
| 843 |
+
Ownership: result.Ownership[i],
|
| 844 |
+
CPT: playerNames[0],
|
| 845 |
+
FLEX1: playerNames[1],
|
| 846 |
+
FLEX2: playerNames[2],
|
| 847 |
+
FLEX3: playerNames[3],
|
| 848 |
+
FLEX4: playerNames[4],
|
| 849 |
+
FLEX5: playerNames[5],
|
| 850 |
+
CreatedAt: time.Now(),
|
| 851 |
+
}
|
| 852 |
+
documents = append(documents, doc)
|
| 853 |
+
}
|
| 854 |
+
}
|
| 855 |
+
|
| 856 |
+
if len(documents) == 0 {
|
| 857 |
+
fmt.Printf("Warning: No documents to insert for slate %s\n", slate)
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
if len(documents) > 500000 {
|
| 861 |
+
documents = documents[:500000]
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
chunkSize := 250000
|
| 865 |
+
for i := 0; i < len(documents); i += chunkSize {
|
| 866 |
+
end := i + chunkSize
|
| 867 |
+
if end > len(documents) {
|
| 868 |
+
end = len(documents)
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
chunk := documents[i:end]
|
| 872 |
+
|
| 873 |
+
for attempt := 0; attempt < 5; attempt++ {
|
| 874 |
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
| 875 |
+
|
| 876 |
+
opts := options.InsertMany().SetOrdered(false)
|
| 877 |
+
_, err := collection.InsertMany(ctx, chunk, opts)
|
| 878 |
+
cancel()
|
| 879 |
+
|
| 880 |
+
if err == nil {
|
| 881 |
+
fmt.Printf("Successfully inserted chunk %d-%d to %s\n", i, end, collectionName)
|
| 882 |
+
break
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
fmt.Printf("Retry %d due to error: %v\n", attempt+1, err)
|
| 886 |
+
if attempt < 4 {
|
| 887 |
+
time.Sleep(1 * time.Second)
|
| 888 |
+
}
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
if err != nil {
|
| 892 |
+
return fmt.Errorf("failed to insert chunk %d-%d after 5 attempts: %v", i, end, err)
|
| 893 |
+
}
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
fmt.Printf("All documents inserted successfully to %s!\n", collectionName)
|
| 897 |
+
return nil
|
| 898 |
+
}
|
| 899 |
+
|
| 900 |
+
func groupPlayersBySlate(players []Player) map[string][]Player {
|
| 901 |
+
slateGroups := make(map[string][]Player)
|
| 902 |
+
|
| 903 |
+
for _, player := range players {
|
| 904 |
+
slateGroups[player.Slate] = append(slateGroups[player.Slate], player)
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
return slateGroups
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
func getPlayerName(playerID int32, nameMap map[int32]string, position string) string {
|
| 911 |
+
if name, exists := nameMap[playerID]; exists && name != "" {
|
| 912 |
+
return name
|
| 913 |
+
}
|
| 914 |
+
return fmt.Sprintf("Unknown_%s_%d", position, playerID)
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
func convertNamesToMaps(playerSet *PlayerSet) map[int32]string {
|
| 918 |
+
nameMap := make(map[int32]string)
|
| 919 |
+
|
| 920 |
+
for keyStr, value := range playerSet.Maps.NameMap {
|
| 921 |
+
key, err := strconv.Atoi(keyStr)
|
| 922 |
+
if err != nil {
|
| 923 |
+
fmt.Printf("Error coinverting name key %s: %v\n", keyStr, err)
|
| 924 |
+
continue
|
| 925 |
+
}
|
| 926 |
+
nameMap[int32(key)] = value
|
| 927 |
+
}
|
| 928 |
+
|
| 929 |
+
return nameMap
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
func main() {
|
| 933 |
+
site := "DK"
|
| 934 |
+
sport := "NFL"
|
| 935 |
+
if len(os.Args) > 1 {
|
| 936 |
+
site = os.Args[1]
|
| 937 |
+
}
|
| 938 |
+
if len(os.Args) > 2 {
|
| 939 |
+
sport = os.Args[2]
|
| 940 |
+
}
|
| 941 |
+
|
| 942 |
+
processedData, err := loadPlayerData()
|
| 943 |
+
if err != nil {
|
| 944 |
+
fmt.Printf("Error loading data: %v\n", err)
|
| 945 |
+
return
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
start := time.Now()
|
| 949 |
+
strengthVars := []float64{0.01, 0.20, 0.40, 0.60, 0.80}
|
| 950 |
+
rowsPerLevel := []int{1000000, 1000000, 1000000, 1000000, 1000000}
|
| 951 |
+
|
| 952 |
+
cptSlateGroups := groupPlayersBySlate(processedData.CptMedian.Players)
|
| 953 |
+
flexSlateGroups := groupPlayersBySlate(processedData.PlayersMedian.Players)
|
| 954 |
+
|
| 955 |
+
cptSalaryMapJSON, cptProjectionMapJSON, cptOwnershipMapJSON, cptTeamMapJSON := convertMapsToInt32Keys(&processedData.CptMedian)
|
| 956 |
+
flexSalaryMapJSON, flexProjectionMapJSON, flexOwnershipMapJSON, flexTeamMapJSON := convertMapsToInt32Keys(&processedData.PlayersMedian)
|
| 957 |
+
|
| 958 |
+
cptNameMap := convertNamesToMaps(&processedData.CptMedian)
|
| 959 |
+
flexNameMap := convertNamesToMaps(&processedData.PlayersMedian)
|
| 960 |
+
|
| 961 |
+
mongoClient, err := connectToMongoDB()
|
| 962 |
+
if err != nil {
|
| 963 |
+
fmt.Printf("Error connecting to MongoDB: %v\n", err)
|
| 964 |
+
return
|
| 965 |
+
}
|
| 966 |
+
defer func() {
|
| 967 |
+
if err := mongoClient.Disconnect(context.TODO()); err != nil {
|
| 968 |
+
fmt.Printf("Error disconnecting from MongoDB: %v\n", err)
|
| 969 |
+
}
|
| 970 |
+
}()
|
| 971 |
+
|
| 972 |
+
for slate, cptPlayers := range cptSlateGroups {
|
| 973 |
+
flexPlayers, exists := flexSlateGroups[slate]
|
| 974 |
+
if !exists {
|
| 975 |
+
fmt.Printf("Warning: no FLEX players found for slate %s\n", slate)
|
| 976 |
+
continue
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
fmt.Printf("Processing slate: %s\n", slate)
|
| 980 |
+
|
| 981 |
+
results, err := runSeedframeRoutines(
|
| 982 |
+
cptPlayers, flexPlayers,
|
| 983 |
+
strengthVars, rowsPerLevel,
|
| 984 |
+
cptSalaryMapJSON, flexSalaryMapJSON,
|
| 985 |
+
cptProjectionMapJSON, flexProjectionMapJSON,
|
| 986 |
+
cptOwnershipMapJSON, flexOwnershipMapJSON,
|
| 987 |
+
cptTeamMapJSON, flexTeamMapJSON, site)
|
| 988 |
+
|
| 989 |
+
if err != nil {
|
| 990 |
+
fmt.Printf("Error generating mixed lineups for slate %s: %v\n", slate, err)
|
| 991 |
+
continue
|
| 992 |
+
}
|
| 993 |
+
|
| 994 |
+
uniqueResults := removeDuplicates(results)
|
| 995 |
+
fmt.Printf("Generated %d unique lineups for slate %s\n", len(uniqueResults), slate)
|
| 996 |
+
|
| 997 |
+
err = insertLineupsToMongoDB(mongoClient, uniqueResults, slate, cptNameMap, flexNameMap, site, sport)
|
| 998 |
+
if err != nil {
|
| 999 |
+
fmt.Printf("Error inserting to MongoDB for slate %s: %v\n", slate, err)
|
| 1000 |
+
continue
|
| 1001 |
+
}
|
| 1002 |
+
}
|
| 1003 |
+
|
| 1004 |
+
// Add this line at the end
|
| 1005 |
+
fmt.Printf("This took %.2f seconds\n", time.Since(start).Seconds())
|
| 1006 |
+
}
|
requirements.txt
CHANGED
|
@@ -6,3 +6,6 @@ ortools
|
|
| 6 |
gspread
|
| 7 |
discordwebhook
|
| 8 |
pymongo
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
gspread
|
| 7 |
discordwebhook
|
| 8 |
pymongo
|
| 9 |
+
xgboost
|
| 10 |
+
lightgbm
|
| 11 |
+
scikit-learn
|
src/database.py
CHANGED
|
@@ -52,9 +52,12 @@ uri = get_secret("MONGODB_URI", "mongodb+srv://multichem:Xr1q5wZdXPbxdUmJ@testcl
|
|
| 52 |
client = MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=10000, w=0)
|
| 53 |
|
| 54 |
nhl_db = client['NHL_Database']
|
|
|
|
|
|
|
| 55 |
|
| 56 |
# Google Sheets URL
|
| 57 |
NHL_Master_hold = get_secret("MASTER_SHEET_URL", 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863')
|
|
|
|
| 58 |
|
| 59 |
# Discord Webhook
|
| 60 |
discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
|
|
|
|
| 52 |
client = MongoClient(uri, retryWrites=True, serverSelectionTimeoutMS=10000, w=0)
|
| 53 |
|
| 54 |
nhl_db = client['NHL_Database']
|
| 55 |
+
nba_db = client['NBA_Database']
|
| 56 |
+
contest_db = client["Contest_Information"]
|
| 57 |
|
| 58 |
# Google Sheets URL
|
| 59 |
NHL_Master_hold = get_secret("MASTER_SHEET_URL", 'https://docs.google.com/spreadsheets/d/1NmKa-b-2D3w7rRxwMPSchh31GKfJ1XcDI2GU8rXWnHI/edit#gid=578660863')
|
| 60 |
+
NBA_Master_hold: str = 'https://docs.google.com/spreadsheets/d/1Yq0vGriWK-bS79e-bD6_u9pqrYE6Yrlbb_wEkmH-ot0/edit?gid=172632260#gid=172632260'
|
| 61 |
|
| 62 |
# Discord Webhook
|
| 63 |
discord = Discord(url=get_secret("DISCORD_WEBHOOK_URL", "https://discord.com/api/webhooks/1244687568394780672/COng4Gz1JFdoS-zCCcB24tQWo1upansxWFdfIv16_HZIb_j7-glZoGd4TXAGJDLIRiIJ"))
|
src/sports/nba_functions.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/sports/nhl_functions.py
CHANGED
|
@@ -1,22 +1,10 @@
|
|
| 1 |
import time
|
| 2 |
-
import sys
|
| 3 |
-
import io
|
| 4 |
-
from contextlib import redirect_stdout
|
| 5 |
import re
|
| 6 |
|
| 7 |
# Numpy
|
| 8 |
from numpy import where as np_where
|
| 9 |
-
from numpy import newaxis as np_newaxis
|
| 10 |
from numpy import random as np_random
|
| 11 |
-
from numpy import hstack as np_hstack
|
| 12 |
from numpy import array as np_array
|
| 13 |
-
from numpy import apply_along_axis as np_apply_along_axis
|
| 14 |
-
from numpy import bincount as np_bincount
|
| 15 |
-
from numpy import partition as np_partition
|
| 16 |
-
from numpy import count_nonzero as np_count_nonzero
|
| 17 |
-
from numpy import argsort as np_argsort
|
| 18 |
-
from numpy import unique as np_unique
|
| 19 |
-
from numpy import vectorize as np_vectorize
|
| 20 |
from numpy import nan as np_nan
|
| 21 |
from numpy import inf as np_inf
|
| 22 |
from numpy import zeros as np_zeros
|
|
@@ -32,7 +20,6 @@ from pandas import errors as pd_errors
|
|
| 32 |
from pandas import merge, to_numeric
|
| 33 |
from pandas import options as poptions
|
| 34 |
from pandas import set_option
|
| 35 |
-
from pandas import notnull
|
| 36 |
|
| 37 |
# Time
|
| 38 |
import time
|
|
@@ -48,24 +35,20 @@ import os
|
|
| 48 |
import subprocess
|
| 49 |
from ortools.linear_solver import pywraplp
|
| 50 |
from random import random
|
| 51 |
-
from random import randint
|
| 52 |
from random import choice
|
| 53 |
|
| 54 |
# Ownership Models
|
| 55 |
-
|
| 56 |
|
| 57 |
pd_options.mode.chained_assignment = None # default='warn'
|
| 58 |
from warnings import simplefilter
|
| 59 |
simplefilter(action="ignore", category=pd_errors.PerformanceWarning)
|
| 60 |
poptions.mode.chained_assignment = None # default='warn'
|
| 61 |
set_option('future.no_silent_downcasting', True)
|
| 62 |
-
from gspread.auth import service_account_from_dict
|
| 63 |
-
from discordwebhook import Discord
|
| 64 |
-
from pymongo import MongoClient, UpdateOne
|
| 65 |
|
| 66 |
import streamlit as st
|
| 67 |
|
| 68 |
-
from database import *
|
| 69 |
|
| 70 |
nan_value = float("NaN")
|
| 71 |
sim_teams = []
|
|
@@ -731,12 +714,19 @@ def build_dk_player_level_basic_outcomes(slate_info, dk_player_hold, fd_player_h
|
|
| 731 |
feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
|
| 732 |
X_current = basic_own_df[feature_cols]
|
| 733 |
|
| 734 |
-
|
| 735 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
basic_own_df['Combo'] = (
|
| 737 |
-
(basic_own_df['
|
| 738 |
-
(
|
| 739 |
-
|
|
|
|
| 740 |
|
| 741 |
basic_own_df['Combo'] = np_where((basic_own_df['value'] < 1.5) & (basic_own_df['Salary'] < 7500), basic_own_df['Combo'] * .75, basic_own_df['Combo'])
|
| 742 |
basic_own_df['Combo'] = np_where((basic_own_df['Salary'] > 5000) & (basic_own_df['value'] < 1.5), basic_own_df['value'], basic_own_df['Combo'])
|
|
|
|
| 1 |
import time
|
|
|
|
|
|
|
|
|
|
| 2 |
import re
|
| 3 |
|
| 4 |
# Numpy
|
| 5 |
from numpy import where as np_where
|
|
|
|
| 6 |
from numpy import random as np_random
|
|
|
|
| 7 |
from numpy import array as np_array
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from numpy import nan as np_nan
|
| 9 |
from numpy import inf as np_inf
|
| 10 |
from numpy import zeros as np_zeros
|
|
|
|
| 20 |
from pandas import merge, to_numeric
|
| 21 |
from pandas import options as poptions
|
| 22 |
from pandas import set_option
|
|
|
|
| 23 |
|
| 24 |
# Time
|
| 25 |
import time
|
|
|
|
| 35 |
import subprocess
|
| 36 |
from ortools.linear_solver import pywraplp
|
| 37 |
from random import random
|
|
|
|
| 38 |
from random import choice
|
| 39 |
|
| 40 |
# Ownership Models
|
| 41 |
+
from func.NHL_own_regress import xgb_model, lgb_model, knn_model
|
| 42 |
|
| 43 |
pd_options.mode.chained_assignment = None # default='warn'
|
| 44 |
from warnings import simplefilter
|
| 45 |
simplefilter(action="ignore", category=pd_errors.PerformanceWarning)
|
| 46 |
poptions.mode.chained_assignment = None # default='warn'
|
| 47 |
set_option('future.no_silent_downcasting', True)
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
import streamlit as st
|
| 50 |
|
| 51 |
+
from src.database import *
|
| 52 |
|
| 53 |
nan_value = float("NaN")
|
| 54 |
sim_teams = []
|
|
|
|
| 714 |
feature_cols = ['Salary', 'Actual', 'actual_adv', 'value', 'value_adv', 'contest_size', 'base_ownership', 'value_play', 'value_density', 'strong_play', 'punt_play']
|
| 715 |
X_current = basic_own_df[feature_cols]
|
| 716 |
|
| 717 |
+
print(X_current)
|
| 718 |
+
|
| 719 |
+
# Make predictions with all your models
|
| 720 |
+
basic_own_df['XGB'] = np_clip(xgb_model.predict(X_current), 0, 100)
|
| 721 |
+
basic_own_df['LGB'] = np_clip(lgb_model.predict(X_current), 0, 100) * 100
|
| 722 |
+
basic_own_df['KNN'] = np_clip(knn_model.predict(X_current), 0, 100)
|
| 723 |
+
|
| 724 |
+
# Create combo prediction
|
| 725 |
basic_own_df['Combo'] = (
|
| 726 |
+
(basic_own_df['XGB'] * .30) +
|
| 727 |
+
(basic_own_df['LGB'] * .30) +
|
| 728 |
+
(basic_own_df['KNN'] * .40)
|
| 729 |
+
)
|
| 730 |
|
| 731 |
basic_own_df['Combo'] = np_where((basic_own_df['value'] < 1.5) & (basic_own_df['Salary'] < 7500), basic_own_df['Combo'] * .75, basic_own_df['Combo'])
|
| 732 |
basic_own_df['Combo'] = np_where((basic_own_df['Salary'] > 5000) & (basic_own_df['value'] < 1.5), basic_own_df['value'], basic_own_df['Combo'])
|
src/streamlit_app.py
CHANGED
|
@@ -17,7 +17,8 @@ from pandas import set_option
|
|
| 17 |
# Time
|
| 18 |
import time
|
| 19 |
from time import sleep as time_sleep
|
| 20 |
-
from datetime import datetime
|
|
|
|
| 21 |
|
| 22 |
from database import *
|
| 23 |
from sports.nhl_functions import *
|
|
@@ -237,8 +238,252 @@ if selected_tab == "NFL Updates":
|
|
| 237 |
st.write("NFL functionality will be added later on.")
|
| 238 |
|
| 239 |
if selected_tab == "NBA Updates":
|
| 240 |
-
|
| 241 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
if selected_tab == "MLB Updates":
|
| 244 |
st.info("MLB updates coming soon!")
|
|
|
|
| 17 |
# Time
|
| 18 |
import time
|
| 19 |
from time import sleep as time_sleep
|
| 20 |
+
from datetime import datetime, date
|
| 21 |
+
from pytz import timezone as pytz_timezone
|
| 22 |
|
| 23 |
from database import *
|
| 24 |
from sports.nhl_functions import *
|
|
|
|
| 238 |
st.write("NFL functionality will be added later on.")
|
| 239 |
|
| 240 |
if selected_tab == "NBA Updates":
|
| 241 |
+
|
| 242 |
+
if st.button(f"🏀 Update NBA models and generate seed frames", type="primary", use_container_width=True):
|
| 243 |
+
x: int = 1
|
| 244 |
+
high_end: int = 1
|
| 245 |
+
|
| 246 |
+
while x <= high_end:
|
| 247 |
+
try:
|
| 248 |
+
try:
|
| 249 |
+
sh = gc.open_by_url(NBA_Master_hold)
|
| 250 |
+
worksheet = sh.worksheet('Game_Adj')
|
| 251 |
+
except:
|
| 252 |
+
sh = gc2.open_by_url(NBA_Master_hold)
|
| 253 |
+
worksheet = sh.worksheet('Game_Adj')
|
| 254 |
+
|
| 255 |
+
t_range = worksheet.range('T2:T32')
|
| 256 |
+
# Create a list of zeros with the same length as t_range
|
| 257 |
+
values = [0] * len(t_range)
|
| 258 |
+
worksheet.update('T2:T32', [[0] for _ in range(len(t_range))])
|
| 259 |
+
|
| 260 |
+
# Sleep for 2 seconds
|
| 261 |
+
time.sleep(2)
|
| 262 |
+
|
| 263 |
+
# Get values from Z2:Z32
|
| 264 |
+
z_values = worksheet.range('Z2:Z32')
|
| 265 |
+
|
| 266 |
+
z_data = [cell.value for cell in z_values]
|
| 267 |
+
worksheet.update([[val] for val in z_data], 'T2:T32')
|
| 268 |
+
except:
|
| 269 |
+
pass
|
| 270 |
+
|
| 271 |
+
ROO_creation_var = 0
|
| 272 |
+
SD_ROO_creation_var = 0
|
| 273 |
+
DK_SD_seed_frame_var = 0
|
| 274 |
+
FD_SD_seed_frame_var = 0
|
| 275 |
+
DK_seed_frame_var = 0
|
| 276 |
+
FD_seed_frame_var = 0
|
| 277 |
+
upload_mongo_dfs_var = 0
|
| 278 |
+
upload_mongo_bets_var = 0
|
| 279 |
+
|
| 280 |
+
if ROO_creation_var == 0:
|
| 281 |
+
try:
|
| 282 |
+
DK_ROO = DK_NBA_ROO_Build(dk_roo_player_hold)
|
| 283 |
+
FD_ROO = FD_NBA_ROO_Build(fd_roo_player_hold)
|
| 284 |
+
|
| 285 |
+
solver_DK = DK_ROO.copy()
|
| 286 |
+
solver_FD = FD_ROO.copy()
|
| 287 |
+
solver_DK = solver_DK.drop_duplicates(subset=['Player'])
|
| 288 |
+
solver_DK['Player'] = solver_DK['Player'].replace('Ron Holland', 'Ronald Holland II')
|
| 289 |
+
solver_FD = solver_FD.drop_duplicates(subset=['Player'])
|
| 290 |
+
solver_FD['Player'] = solver_FD['Player'].replace('Ron Holland', 'Ronald Holland II')
|
| 291 |
+
|
| 292 |
+
try:
|
| 293 |
+
solver_DK['Timestamp'] = str(date.today())
|
| 294 |
+
sh = gc.open_by_url('https://docs.google.com/spreadsheets/d/1H7kdaxVF7Bv3kb1DSa_3Dq6OaC9ajq9UAQfVyDluXzk/edit?gid=0#gid=0')
|
| 295 |
+
worksheet = sh.worksheet('NBA DK')
|
| 296 |
+
worksheet.batch_clear(['A:AB'])
|
| 297 |
+
worksheet.update([solver_DK.columns.values.tolist()] + solver_DK.values.tolist())
|
| 298 |
+
except:
|
| 299 |
+
sh = gc2.open_by_url(NBA_Master_hold)
|
| 300 |
+
worksheet = sh.worksheet('NBA DK')
|
| 301 |
+
worksheet.batch_clear(['A:AB'])
|
| 302 |
+
worksheet.update([solver_DK.columns.values.tolist()] + solver_DK.values.tolist())
|
| 303 |
+
|
| 304 |
+
try:
|
| 305 |
+
solver_FD['Timestamp'] = str(date.today())
|
| 306 |
+
sh = gc.open_by_url('https://docs.google.com/spreadsheets/d/1H7kdaxVF7Bv3kb1DSa_3Dq6OaC9ajq9UAQfVyDluXzk/edit?gid=0#gid=0')
|
| 307 |
+
worksheet = sh.worksheet('NBA FD')
|
| 308 |
+
worksheet.batch_clear(['A:AB'])
|
| 309 |
+
worksheet.update([solver_FD.columns.values.tolist()] + solver_FD.values.tolist())
|
| 310 |
+
except:
|
| 311 |
+
sh = gc2.open_by_url(NBA_Master_hold)
|
| 312 |
+
worksheet = sh.worksheet('NBA FD')
|
| 313 |
+
worksheet.batch_clear(['A:AB'])
|
| 314 |
+
worksheet.update([solver_FD.columns.values.tolist()] + solver_FD.values.tolist())
|
| 315 |
+
|
| 316 |
+
time.sleep(3)
|
| 317 |
+
|
| 318 |
+
roo_final = pd_concat([DK_ROO, FD_ROO])
|
| 319 |
+
roo_final['Salary'] = roo_final['Salary'].astype(int)
|
| 320 |
+
|
| 321 |
+
tz = pytz_timezone('US/Central')
|
| 322 |
+
central_tz = datetime.now(tz)
|
| 323 |
+
current_time = central_tz.strftime("%H:%M:%S")
|
| 324 |
+
|
| 325 |
+
roo_final['timestamp'] = current_time
|
| 326 |
+
|
| 327 |
+
try:
|
| 328 |
+
sh = gc.open_by_url(NBA_Master_hold)
|
| 329 |
+
worksheet = sh.worksheet('Player_Level_ROO')
|
| 330 |
+
worksheet.batch_clear(['A:AB'])
|
| 331 |
+
worksheet.update([roo_final.columns.values.tolist()] + roo_final.values.tolist())
|
| 332 |
+
except:
|
| 333 |
+
sh = gc2.open_by_url(NBA_Master_hold)
|
| 334 |
+
worksheet = sh.worksheet('Player_Level_ROO')
|
| 335 |
+
worksheet.batch_clear(['A:AB'])
|
| 336 |
+
worksheet.update([roo_final.columns.values.tolist()] + roo_final.values.tolist())
|
| 337 |
+
|
| 338 |
+
ROO_creation_var = 1
|
| 339 |
+
except:
|
| 340 |
+
pass
|
| 341 |
+
|
| 342 |
+
if ROO_creation_var == 1:
|
| 343 |
+
try:
|
| 344 |
+
discord.post(content="NBA ROO structure refreshed")
|
| 345 |
+
except:
|
| 346 |
+
pass
|
| 347 |
+
else:
|
| 348 |
+
try:
|
| 349 |
+
discord.post(content="NBA ROO structure script broke")
|
| 350 |
+
except:
|
| 351 |
+
pass
|
| 352 |
+
|
| 353 |
+
time_sleep(1)
|
| 354 |
+
|
| 355 |
+
upload_dfs_data(client, roo_final)
|
| 356 |
+
discord.post(content="NBA DFS database refreshed")
|
| 357 |
+
|
| 358 |
+
upload_betting_data(client)
|
| 359 |
+
discord.post(content="NBA betting database refreshed")
|
| 360 |
+
|
| 361 |
+
try:
|
| 362 |
+
trending_script()
|
| 363 |
+
discord.post(content="NBA Trending Tables refreshed")
|
| 364 |
+
except:
|
| 365 |
+
discord.post(content="NBA Trending Tables broke")
|
| 366 |
+
pass
|
| 367 |
+
|
| 368 |
+
if DK_seed_frame_var == 0:
|
| 369 |
+
DK_NBA_seed_frame(roo_final, client)
|
| 370 |
+
DK_seed_frame_var = 1
|
| 371 |
+
|
| 372 |
+
if DK_seed_frame_var == 1:
|
| 373 |
+
try:
|
| 374 |
+
discord.post(content="NBA Draftkings Seed Frames refreshed")
|
| 375 |
+
except:
|
| 376 |
+
pass
|
| 377 |
+
else:
|
| 378 |
+
try:
|
| 379 |
+
discord.post(content="NBA Draftkings Seed Frames script broke")
|
| 380 |
+
except:
|
| 381 |
+
pass
|
| 382 |
+
|
| 383 |
+
time_sleep(1)
|
| 384 |
+
|
| 385 |
+
if FD_seed_frame_var == 0:
|
| 386 |
+
FD_NBA_seed_frame(roo_final, client)
|
| 387 |
+
FD_seed_frame_var = 1
|
| 388 |
+
|
| 389 |
+
if FD_seed_frame_var == 1:
|
| 390 |
+
try:
|
| 391 |
+
discord.post(content="NBA Fanduel Seed Frames refreshed")
|
| 392 |
+
except:
|
| 393 |
+
pass
|
| 394 |
+
else:
|
| 395 |
+
try:
|
| 396 |
+
discord.post(content="NBA Fanduel Seed Frames script broke")
|
| 397 |
+
except:
|
| 398 |
+
pass
|
| 399 |
+
|
| 400 |
+
time_sleep(1)
|
| 401 |
+
|
| 402 |
+
if SD_ROO_creation_var == 0:
|
| 403 |
+
DK_SD_ROO = DK_SD_NBA_ROO_Build(dk_sd_player_hold, dk_showdown_options, dk_sd_projections)
|
| 404 |
+
FD_SD_ROO = FD_SD_NBA_ROO_Build(fd_sd_player_hold, fd_showdown_options, fd_sd_projections)
|
| 405 |
+
|
| 406 |
+
sd_roo_final = pd_concat([DK_SD_ROO, FD_SD_ROO])
|
| 407 |
+
sd_roo_final['Salary'] = sd_roo_final['Salary'].astype(int)
|
| 408 |
+
|
| 409 |
+
tz = pytz_timezone('US/Central')
|
| 410 |
+
central_tz = datetime.now(tz)
|
| 411 |
+
current_time = central_tz.strftime("%H:%M:%S")
|
| 412 |
+
|
| 413 |
+
sd_roo_final['timestamp'] = current_time
|
| 414 |
+
|
| 415 |
+
try:
|
| 416 |
+
sh = gc.open_by_url(NBA_Master_hold)
|
| 417 |
+
worksheet = sh.worksheet('Player_Level_SD_ROO')
|
| 418 |
+
worksheet.batch_clear(['A:AB'])
|
| 419 |
+
worksheet.update([sd_roo_final.columns.values.tolist()] + sd_roo_final.values.tolist())
|
| 420 |
+
except:
|
| 421 |
+
sh = gc2.open_by_url(NBA_Master_hold)
|
| 422 |
+
worksheet = sh.worksheet('Player_Level_SD_ROO')
|
| 423 |
+
worksheet.batch_clear(['A:AB'])
|
| 424 |
+
worksheet.update([sd_roo_final.columns.values.tolist()] + sd_roo_final.values.tolist())
|
| 425 |
+
|
| 426 |
+
ROO_creation_var = 1
|
| 427 |
+
|
| 428 |
+
if ROO_creation_var == 1:
|
| 429 |
+
try:
|
| 430 |
+
discord.post(content="NBA SD ROO structure refreshed")
|
| 431 |
+
except:
|
| 432 |
+
pass
|
| 433 |
+
else:
|
| 434 |
+
try:
|
| 435 |
+
discord.post(content="NBA SD ROO structure script broke")
|
| 436 |
+
except:
|
| 437 |
+
pass
|
| 438 |
+
|
| 439 |
+
time_sleep(1)
|
| 440 |
+
|
| 441 |
+
if DK_SD_seed_frame_var == 0:
|
| 442 |
+
DK_NBA_SD_seed_frame(dk_showdown_options, dk_sd_projections)
|
| 443 |
+
DK_SD_seed_frame_var = 1
|
| 444 |
+
|
| 445 |
+
if DK_SD_seed_frame_var == 1:
|
| 446 |
+
try:
|
| 447 |
+
discord.post(content="NBA Draftkings SD Seed Frames refreshed")
|
| 448 |
+
except:
|
| 449 |
+
pass
|
| 450 |
+
else:
|
| 451 |
+
try:
|
| 452 |
+
discord.post(content="NBA Draftkings SD Seed Frames script broke")
|
| 453 |
+
except:
|
| 454 |
+
pass
|
| 455 |
+
|
| 456 |
+
time_sleep(1)
|
| 457 |
+
|
| 458 |
+
if FD_SD_seed_frame_var == 0:
|
| 459 |
+
FD_NBA_SD_seed_frame(fd_showdown_options, fd_sd_projections)
|
| 460 |
+
FD_SD_seed_frame_var = 1
|
| 461 |
+
|
| 462 |
+
if FD_SD_seed_frame_var == 1:
|
| 463 |
+
try:
|
| 464 |
+
discord.post(content="NBA Fanduel SD Seed Frames refreshed")
|
| 465 |
+
except:
|
| 466 |
+
pass
|
| 467 |
+
else:
|
| 468 |
+
try:
|
| 469 |
+
discord.post(content="NBA Fanduel SD Seed Frames script broke")
|
| 470 |
+
except:
|
| 471 |
+
pass
|
| 472 |
+
|
| 473 |
+
time_sleep(1)
|
| 474 |
+
|
| 475 |
+
upload_sd_dfs_data(client, sd_roo_final)
|
| 476 |
+
discord.post(content="NBA SD DFS database refreshed")
|
| 477 |
+
|
| 478 |
+
print (f'currently on run {x}, {high_end - x} runs remaining')
|
| 479 |
+
|
| 480 |
+
x += 1
|
| 481 |
+
|
| 482 |
+
if high_end > 1:
|
| 483 |
+
time_sleep(600)
|
| 484 |
+
|
| 485 |
+
st.success("✅ NBA updates completed successfully!")
|
| 486 |
+
st.balloons()
|
| 487 |
|
| 488 |
if selected_tab == "MLB Updates":
|
| 489 |
st.info("MLB updates coming soon!")
|