<jupyter_start><jupyter_text>In this noteboook we do a rudimentary betting strategy. There was some struggle to even get a even positive ROI with different attempts so this is a reasonable baseline of 5% and 10% ROI in 2016-17 and 2017-18 respectively for logistic regression I will attempt a more sophisticated approach which considers: 1. the home/away, favorite/not favorite situation (in our calculation below we can see that away/underdog is a losing bet in general, for example2. the model predicted probability vs the betting implied probability (strictly speaking, breaking even or better long term is impossible unless pred_proba >= implied_proba) 3. one can also look at model confidence as a measure of distance of probabaility from 0.5 (eg 0.6 high confifdence, 0.51 low confidence)I attempted these already, but there was something wrong in the calculations so I dropped them for the time being in favour of this simpler approach.Certainly, one expectes to do better using a more sophisticated approach.Then as usual there is model selection which may improve the betting results (this betting result is just for logisistic regressio at the moment)... and the various ways we expect to improve the models in the model development stage (tuning, feature selection, introducing neural net model)<jupyter_code>import pandas as pd
import numpy as np
##note KNN or other clusters might be helpful group the teams in smart way ... but not now.
#models


from sklearn.preprocessing import StandardScaler, MinMaxScaler 
std_scal = StandardScaler()
mm_scal = MinMaxScaler()


##no tuning 
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score, confusion_matrix
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, f1_score


from sklearn.model_selection import train_test_split



##regression
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor

#classifiers (non-tree)
from sklearn.linear_model import RidgeClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression, SGDRegressor, SGDClassifier
from sklearn.svm import SVC


#tree-based classifiers
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import GradientBoostingClassifier
from xgboost import XGBClassifier
from xgboost import XGBRegressor



##helper functions

def fav_pred(x):
    if x < 0:
        return 1
    if x>0:
        return 0
    if x==0:
        print("O odds detected, nan returned")
        return np.NaN
v_fav_pred = np.vectorize(fav_pred)


def implied_proba(odds):
    if odds > 0: 
        return 100/(odds+100)    #bet 100 to get 100+odds; profit = odds
    
#def fav_implied_proba(odds):
    if odds < 0:
        return (-odds)/(-odds + 100)   #bet |odds| to get 100+|odds|; profit = 100
    
v_impl_proba = np.vectorize(implied_proba)

def are_equal(x1,x2):  # 1 if nums =1, 0, o/w  #delta 
    return int(x1 == x2)
v_are_equal = np.vectorize(are_equal)
X = pd.read_csv('/Users/joejohns/data_bootcamp/GitHub/nhl_prediction_Aug_2021B/Data/Processed_Data/Approach_3_bulk_Leung_data/data_LJ.csv')
X.shape
X['goal_diff_target'] = X['home_goals'] - X['away_goals']

#we will use this for correlations etc at the bottom

data = X.copy()
X.columns
 
X['season'] = X['season'].apply(int)
X['game_id'] = X['game_id'].apply(int)
X['mp_date'] = X['mp_date'].apply(int)
filt_no_early  = (X['mp_date'].apply(lambda x : x% 10**4) < 900) | (1100 < X['mp_date'].apply(lambda x : x% 10**4))

X = X.loc[filt_no_early, : ].copy()  ## keep games < 900 and > 1100
X.shape
X['season'].value_counts()
print(X.shape)

filt_no_early  = (X['mp_date'].apply(lambda x : x% 10**4) < 900) | (1100 < X['mp_date'].apply(lambda x : x% 10**4))
X= X.loc[filt_no_early, : ].copy()  ## keep games < 900 and > 1100
print(X.shape)
filt_no_fav = (v_fav_pred(X['home_odds']) != v_fav_pred(X['away_odds'])).copy() 
X.loc[filt_no_fav, :]['season'].value_counts()
X = X.loc[ filt_no_fav , : ].copy()  #1000 plus rows across all seasons 
print(X.shape)
##below we see we lose approx 10% dropping games before nov 1 and another 10% where the betting odds are messed up (both negative)
##could use different betting odds or try to fix these ones later
#woof two 10% loses
X.reset_index(inplace = True)    

y_cols = ['Unnamed: 0', 'game_id', 'mp_date', 'season', 'home_team', 'away_team',
       'home_odds', 'away_odds', 'home_goals', 'away_goals', 'goal_diff_target', 'home_win',
       'settled_in', ]

x_cols = ['CF%', 'CSh%', 'CSv%', 'FF%', 'FSh%', 'FSv%', 'GDiff',
       'GF%', 'PDO', 'PENDiff', 'SF%', 'SDiff', 'Sh%', 'Sv']
columns_to_scale = ['CF%', 'CSh%', 'CSv%', 'FF%', 'FSh%', 'FSv%', 'GDiff',
       'GF%', 'PDO', 'PENDiff', 'SF%', 'SDiff', 'Sh%', 'Sv']  ##same


x = np.array(X.loc[(X['season'] <= 20152016), x_cols].copy())
  #features test-train
Y = X.loc[(X['season'] <= 20152016), y_cols].copy()
   #targets
y = np.array(Y['home_win']).reshape(-1,1)


              
x_16 = X.loc[(X['season'] == 20162017), x_cols].copy()  #features test-train
Y_16 = X.loc[(X['season'] == 20162017), y_cols].copy()   #targets
y_16 = np.array(Y_16['home_win']).reshape(-1,1)

x_17 = X.loc[(X['season'] == 20172018), x_cols].copy()  #features test-train
Y_17 = X.loc[(X['season'] == 20172018), y_cols].copy()   #targets
y_17 = np.array(Y_17['home_win']).reshape(-1,1)



X.loc[(X['season'] >= 20162017), :]['season'].value_counts()
X.loc[(X['season'] <= 20152016), :]['season'].value_counts()
X.loc[(X['season'] <= 20152016), :]['season'].value_counts().sum()
print(X.shape)
print(x.shape, Y.shape, y.shape)
print(x_16.shape, y_16.shape)
print(x_17.shape, y_17.shape)
print('sum of ', y.shape[0],y_16.shape[0], y_17.shape[0], ' is: ', y.shape[0] + y_16.shape[0]+y_17.shape[0])
Y['season'].value_counts()
Y_16['season'].value_counts()
Y_17['season'].value_counts()


from sklearn.preprocessing import StandardScaler, MinMaxScaler 
std_scal = StandardScaler()
mm_scal = MinMaxScaler()
###NOT tuned 


from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=314, stratify=y)

#do standard/minmax scaling on X_train numeric columns ... better to do pipeline? 
x_train_sc = std_scal.fit_transform(x_train)
    
#fit the scaler from train portion to the test portion 
x_test_sc = std_scal.transform(x_test)
##classifier models
lrc2 = RidgeClassifier(alpha =0.001)
gnb2 = GaussianNB()
lgr2 = LogisticRegression(random_state = 0, max_iter = 500)
svc2 = SVC(kernel = 'rbf')

#tree-based classifiers
rfc2 =  RandomForestClassifier( random_state=0)
bc2 = BaggingClassifier()
gbc2 = GradientBoostingClassifier()
xgbc2 = XGBClassifier()
##quick checks 
for model in [svc2, lrc2, rfc2, gnb2, lgr2, bc2, gbc2, xgbc2]:
    model.fit(x_train_sc, y_train.ravel())
      #model.fit(x_train_sc, y_train.ravel())
    y_pred= model.predict(x_test_sc)
    y_predt= model.predict(x_train_sc)  
    acc = accuracy_score(y_test, y_pred)
   # f1 = f1_score(y_test,y_pred)
    acct = accuracy_score(y_train, y_predt)
    #f1t = f1_score(y_train,y_predt)
  
  
    print(str(model)[0:12], 'TEST: ', acc ) #, f1, 'training : ', acct, f1t)
#check on 2016
x_16_sc = std_scal.transform(x_16).copy()
x_test2 = std_scal.transform(x_16).copy()
y_test2 = y_16.copy()


##quick checks 
for model in [svc2, lrc2, rfc2, gnb2, lgr2, bc2, gbc2, xgbc2]:
    #model.fit(x_train)
      #model.fit(x_train_sc, y_train.ravel())
    y_pred= model.predict(x_test2)
  
    acc = accuracy_score(y_test2, y_pred)
    f1 = f1_score(y_test2,y_pred)
   
   
    #acct = accuracy_score(y_train, y_predt)
    #f1t = f1_score(y_train,y_predt)
  
  
    print(str(model)[0:12], '20162017, no tuning, TEST: ', acc, f1) #, 'training : ', acct, f1t)
#check on 2017

x_test2 = std_scal.transform(x_17).copy()
y_test2 = y_17.copy()


##quick checks 
for model in [svc2, lrc2, rfc2, gnb2, lgr2, bc2, gbc2, xgbc2]:
    #model.fit(x_train)
      #model.fit(x_train_sc, y_train.ravel())
    y_pred= model.predict(x_test2)
  
    acc = accuracy_score(y_test2, y_pred)
    f1 = f1_score(y_test2,y_pred)
    acc = accuracy_score(y_test2, y_pred)
    f1 = f1_score(y_test2,y_pred)
   
    #acct = accuracy_score(y_train, y_predt)
    #f1t = f1_score(y_train,y_predt)
  
  
    print(str(model)[0:12], '20172018, no tuning, TEST: ', acc, f1) #, 'training : ', acct, f1t)
def find_pay_off(df_test):

    df = df_test.copy()
    
    ##bet fav_home 
    df2 = df.loc[ (df_test['fav_pred'] !=  df_test['model_pred'])&(df_test['model_pred'] ==1), :]
    df2['bet'] = 100
    df2['win'] = df2['actual_res']*(  100*(1/df2['home_impl_proba']) + 100    )
    df2['profit'] = df2['win'] - df2['bet']
    
    ##bet fav_away
    df3= df.loc[ (df_test['fav_pred'] !=  df_test['model_pred'])&(df_test['model_pred'] == 0), :]
    df3['bet'] = 100
    df3['win'] = (1-df3['actual_res'])*(100*(1/df3['away_impl_proba']) + 100 )
    df3['profit'] = df3['win'] - df3['bet']
    
    ##bet und_home 
    df4 = df.loc[ (df_test['fav_pred'] ==  df_test['model_pred'])&(df_test['model_pred'] ==1), :]
    df4['bet'] = -df4['home_odds']
    df4['win'] = df4['actual_res']*(-df['home_odds'] + 100) 
    df4['profit'] = df4['win'] - df4['bet']
    
    df5 =df.loc[ (df_test['fav_pred'] ==  df_test['model_pred'])&(df_test['model_pred'] == 0), :]
    df5['bet'] =  -df5['away_odds']
    df5['win'] = (1-df5['actual_res'])*(-df5['away_odds'] + 100) 
    df5['profit'] = df5['win'] - df5['bet']
    
    print(df5['profit'].sum(), (df5['profit'].sum() +   df5['bet'].sum())/df5['bet'].sum()        )
    print(df2['profit'].sum(), (df2['profit'].sum() +   df2['bet'].sum())/df2['bet'].sum()        )
    print(df3['profit'].sum(), (df3['profit'].sum() +   df3['bet'].sum())/df3['bet'].sum()        )                                    
    print(df4['profit'].sum(), (df4['profit'].sum() +   df4['bet'].sum())/df4['bet'].sum()        )
    profit_t = df2['profit'].sum()+df3['profit'].sum()+df4['profit'].sum() +df5['profit'].sum()
    bet_t = df2['bet'].sum()+df3['bet'].sum() +df4['bet'].sum()+df5['bet'].sum()
    
    print(bet_t, profit_t, (profit_t+bet_t)/bet_t    )                                 
                                    
  
    
###baseline pretraining accuracy when model says to bet fav and bet und
###find some baselines from Y"
df_base = Y.copy()
df_base['fav_pred'] = v_fav_pred(df_base['home_odds'] ).copy()
df_base.shape
fav_home_acc = ((df_base['home_win'] == 1)&(df_base['fav_pred'] == 1)).sum()/(df_base['fav_pred'] == 1).sum()
und_home_acc = ((df_base['home_win'] == 0)&(df_base['fav_pred'] == 0)).sum()/(df_base['fav_pred'] == 0).sum()
print(fav_home_acc, fav_away_acc)

#accuracy if you bet for favorite home team, or favorite away team

#select model lgr2 logistic regression
model = lgr2

##quick check on model accuracy ... decent at 58.4%


y_pred= model.predict(x_test_sc)  #this is <= 20152016 data
acc_pre = accuracy_score(y_test, y_pred)  #this x_test_sc needs to be defined already above during training session on <=2015
acc_pre



def make_bet_df(model=model, x = x_16, y = y_16, Y = Y_16):
    dic = {}  #x_16, lgr 0.55 ... Request rfc 0.58 x_16 and 0.6 on x_17 (later)
    
    dic['model_name'] = str(model)[0:10]
    dic['actual_res'] = [ t[0] for t in list(y)]
    dic['model_pred'] = list(model.predict(x))
    
    
    dic['model_conf_1'] = [round(t,4) for t in model.predict_proba(x)[0:, 1]]
    dic['model_conf_0'] =[round(t,4) for t in model.predict_proba(x)[0:, 0]]
    
    dic['home_odds'] = list(Y['home_odds'])
    dic['away_odds'] = list(Y['away_odds'])
    
    df= pd.DataFrame(dic)
    
    
    df['home_impl_proba'] = v_impl_proba(df['home_odds'])
    df['away_impl_proba'] = v_impl_proba(df['away_odds'])
    
    df['conf_1_sub_home_impl'] = df['model_conf_1'] - df['home_impl_proba']
    df['conf_0_sub_away_impl'] = df['model_conf_0'] - df['away_impl_proba']
    
    df['pre_acc_sub_home_impl'] = (acc_pre - df['home_impl_proba']).copy()
    df['pre_acc_sub_away_impl'] = (acc_pre  - df['away_impl_proba']).copy()
    
    df['fav_pred'] = v_fav_pred(df['home_odds'] )
    df['model_conf'] = (0.5)*(df['model_conf_1'] - df['model_conf_0'])
    df['model_conf'] = np.abs(df['model_conf'])
    return df   #df_bet_info
df_test17 = make_bet_df(model=model, x = x_17, y = y_17, Y = Y_17)
df_test  = make_bet_df(model=model, x = x_16, y = y_16, Y = Y_16)   ##this is for model = rfc and season = 20162017; rfc dif well on both seasons<jupyter_output><empty_output><jupyter_text>We investigate a few baselines for the model ... betting against or for the betting favorite, and  in the case of both home and away.<jupyter_code>bet_fav = ((df_test['fav_pred'] ==  df_test['model_pred'])&(df_test['actual_res'] == df_test['model_pred'])).sum()/(df_test['fav_pred'] ==  df_test['model_pred']).sum()
bet_und =((df_test['fav_pred'] !=  df_test['model_pred'])&(df_test['actual_res'] == df_test['model_pred'])).sum()/(df_test['fav_pred'] !=  df_test['model_pred']).sum()
print(bet_fav)
print(bet_und)
bet_away_fav = ((df_test['fav_pred'] == 0)&(df_test['model_pred'] == 0)&(df_test['actual_res'] == 0)).sum()/((df_test['fav_pred'] == 0)&(df_test['model_pred'] == 0)).sum()
bet_away_und = ((df_test['fav_pred'] == 1)&(df_test['model_pred'] == 0)&(df_test['actual_res'] == 0)).sum()/((df_test['fav_pred'] == 1)&(df_test['model_pred'] == 0)).sum()
bet_home_fav = ((df_test['fav_pred'] == 1)&(df_test['model_pred'] == 1)&(df_test['actual_res'] == 1)).sum()/((df_test['fav_pred'] == 1)&(df_test['model_pred'] == 1)).sum()
bet_home_und = ((df_test['fav_pred'] == 0)&(df_test['model_pred'] == 1)&(df_test['actual_res'] == 1)).sum()/((df_test['fav_pred'] ==0)&(df_test['model_pred'] == 1)).sum()

print(bet_away_fav)
print(bet_away_und)
print(bet_home_fav)
print(bet_home_und)

##can use these in x_17 betting model but not in the x_16 one ... except I'kk just say maybe don'tbet on away und

cols = ['model_conf','home_impl_proba', 'model_conf_1', 'conf_1_sub_home_impl', 'pre_acc_sub_home_impl' 
                      ,'away_impl_proba', 'model_conf_0', 'conf_0_sub_away_impl',   'pre_acc_sub_away_impl' ,  'model_pred', 'fav_pred', 'actual_res', 'home_odds', 'away_odds',]
df_test.columns
find_pay_off(df_test17)
find_pay_off(df_test)
##ROI of 5.3% and 10.1% respectively, avg of 7.5%<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17094491466629666, 'fraction_numerical': 0.0397530178992646, 'mean_word_length': 4.559197840339375, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '374484', 'license': 'MIT', 'n_tokens_mistral': 5962, 'n_tokens_neox': 5504, 'n_words': 1212, 'path': 'Betting_stratgy_clean_Aug_2021_version-Copy1.ipynb', 'repo_name': 'joeyamosjohns/nhl_prediction_Aug_2021', 'size': '14414'}