This report uses the 2021-2022 season (excluding playoffs) for comparing bet results vs. the markets. The win/loss models and the xG models are tuned separately as minor parameter changes will push the model toward better performance on different metrics.
Win/Loss Model performance:
## Log Loss: 0.65301
## Accuracy: 62.31%
## AUC: 0.6605
This model was trained by reducing Log Loss
NOTE Previous editions of this had a minor issue in the model code, incorrectly normalizing the prediction of tied games.
Not using market lines with vigs as percent win/loss (instead adding 0.02 to the database odds). Calculating American odds from the percents given, with rounding (toward zero) any decimal -> so if calculated american odds were +225.4, then using +225, and if -142.9 then using -142.
Presuming we place a bet if our predictions for the winner differ by 5%, the bet results from the whole season are as follows:
| HomeBets | 95.00 |
| AwayBets | 185.00 |
| HomeBetResults | -1659.00 |
| AwayBetResults | 2856.00 |
| HomeUnits | -17.46 |
| AwayUnits | 15.44 |
| TotalBets | 280.00 |
| TotalResults | 1197.00 |
| TotalUnits | 4.28 |
The results of betting broken down by each team we would have bet on:
| BetTeam | Number_of_Bets | Total_Bet_Result |
|---|---|---|
| Anaheim Ducks | 6 | -604 |
| Arizona Coyotes | 61 | 73 |
| Boston Bruins | 13 | -171 |
| Buffalo Sabres | 13 | 42 |
| Carolina Hurricanes | 9 | 685 |
| Colorado Avalanche | 4 | -55 |
| Columbus Blue Jackets | 9 | 197 |
| Dallas Stars | 1 | -100 |
| Detroit Red Wings | 1 | -100 |
| Edmonton Oilers | 10 | 368 |
| Florida Panthers | 3 | 62 |
| Los Angeles Kings | 1 | 214 |
| Minnesota Wild | 1 | 124 |
| Montreal Canadiens | 19 | 721 |
| Nashville Predators | 11 | -238 |
| New Jersey Devils | 11 | 518 |
| New York Islanders | 10 | -553 |
| New York Rangers | 3 | 137 |
| Ottawa Senators | 33 | 661 |
| Philadelphia Flyers | 3 | -300 |
| Pittsburgh Penguins | 11 | -397 |
| San Jose Sharks | 2 | 180 |
| Seattle Kraken | 10 | -788 |
| St. Louis Blues | 7 | -92 |
| Tampa Bay Lightning | 9 | 154 |
| Vancouver Canucks | 5 | 319 |
| Vegas Golden Knights | 8 | 223 |
| Washington Capitals | 3 | -46 |
| Winnipeg Jets | 3 | -37 |
The results of betting broken down by whether we bet on favourites or underdogs:
Check if we would make more money betting with or against the favourite (based on + 100 or < -100 we bet with favourite).
| FavBet | Number_of_Bets | Total_Bet_Result |
|---|---|---|
| Bet Against Favourite | 241 | 1259 |
| Bet Favourite | 39 | -62 |
Check if there’s a bias in win/loss by confidence level. This same info is shown in graphical form below.
| BetDiffNom | Number_of_Bets | Total_Bet_Result |
|---|---|---|
| 0.05 | 46 | 1099 |
| 0.06 | 63 | -1216 |
| 0.07 | 45 | 694 |
| 0.08 | 36 | 618 |
| 0.09 | 34 | -115 |
| 0.10 | 24 | -1022 |
| 0.11 | 14 | 24 |
| 0.12 | 11 | 1071 |
| 0.13 | 2 | -200 |
| 0.14 | 3 | -300 |
| 0.17 | 2 | 544 |
Lets take a look at a few of those biggest disagreements (model vs market):
| GameID | HomeTeam | AwayTeam | HomePred | AwayPred | HomeOdds | AwayOdds | BetTeam | BetDiff |
|---|---|---|---|---|---|---|---|---|
| 2021020270 | Arizona Coyotes | Detroit Red Wings | 0.5874 | 0.4126 | 0.4137 | 0.6263 | Arizona Coyotes | 0.1738 |
| 2021021287 | Dallas Stars | Arizona Coyotes | 0.6314 | 0.3686 | 0.8413 | 0.1987 | Arizona Coyotes | 0.1700 |
| 2021020667 | Detroit Red Wings | Buffalo Sabres | 0.4971 | 0.5029 | 0.6801 | 0.3599 | Buffalo Sabres | 0.1430 |
| 2021020999 | San Jose Sharks | Arizona Coyotes | 0.5272 | 0.4728 | 0.7095 | 0.3305 | Arizona Coyotes | 0.1423 |
| 2021020814 | Arizona Coyotes | Los Angeles Kings | 0.4427 | 0.5573 | 0.3060 | 0.7340 | Arizona Coyotes | 0.1367 |
| 2021021230 | Arizona Coyotes | Chicago Blackhawks | 0.5280 | 0.4720 | 0.3956 | 0.6444 | Arizona Coyotes | 0.1324 |
| 2021020137 | Philadelphia Flyers | Arizona Coyotes | 0.5631 | 0.4369 | 0.7302 | 0.3098 | Arizona Coyotes | 0.1271 |
| 2021020908 | St. Louis Blues | Ottawa Senators | 0.6219 | 0.3781 | 0.7858 | 0.2542 | Ottawa Senators | 0.1239 |
| 2021020607 | Arizona Coyotes | Chicago Blackhawks | 0.5374 | 0.4626 | 0.4137 | 0.6263 | Arizona Coyotes | 0.1237 |
| 2021020844 | Arizona Coyotes | Winnipeg Jets | 0.4578 | 0.5422 | 0.3350 | 0.7050 | Arizona Coyotes | 0.1227 |
Plot a few things!
Total season bet results:
Bet Results by Team. The total bet results by team are listed above as well.
Check if there’s a bias in win/loss by confidence level. This same info is shown in tabular form above.
Using the model outputs from last year, run through the same code, we get the following results:
## Log Loss: 0.65173
## Accuracy: 62.96%
## AUC: 0.6627
| HomeBets | 484.00 |
| AwayBets | 424.00 |
| HomeBetResults | -233.00 |
| AwayBetResults | 1340.00 |
| HomeUnits | -0.48 |
| AwayUnits | 3.16 |
| TotalBets | 669.00 |
| TotalResults | 1107.00 |
| TotalUnits | 1.22 |
Total season bet results:
This doesn’t mean as much because we’re looking for totals, but here’s the model performance for win/loss. Naturally, a xG model would be expected to have reasonable W/L performance as a byproduct of being good at predicting goal performance.
## Log Loss: 0.66601
## Accuracy: 58.46%
## AUC: 0.6457
In addition, the model performance for total xG vs. G (this is the important part):
## Test R2: 0.0121
## Test RMSE: 2.35
We trained the model using RMSE as the optimized metric.
Setting up bet results of over/under bets placed with a difference in 0.5 xG:
| OverBets | 44.00 |
| UnderBets | 82.00 |
| OverBetResults | 88.00 |
| UnderBetResults | 198.00 |
| OverUnits | 2.00 |
| UnderUnits | 2.41 |
| TotalBets | 126.00 |
| TotalResults | 286.00 |
| TotalUnits | 2.27 |
This feels like it could be improved - very good performance to betting overs, but poor performance with unders. The betdiff of 0.5 xG was just a random number.
We’ll tune the over/under cutoffs by using the training data (2017-2018 to 2020-2021), and then use that to determine what we would have expected to get from 2021-2022
optresults<-expand.grid(
"over"=c(0.5, 0.6, 0.7, 0.8, 0.9, 1),
"under"=c(0.1, 0.2, 0.3, 0.4, 0.5)
)
optresults$TotalResults<-NA_real_
optresults$TotalUnits<-NA_real_
optresults$NumBets<-NA_integer_
optresults$Units_2122<-NA_real_
optresults$Results_2122<-NA_real_
optresults$NumBets_2122<-NA_real_
for(i in 1:nrow(optresults)){
b<-HockeyModel:::compare_market_line_xg(predictions_xg$train, overbetdiff = optresults[i,]$over, underbetdiff = optresults[i,]$under, juice = juice)
optresults[i,]$TotalResults<-b$TotalResults
optresults[i,]$TotalUnits<-round(b$TotalUnits, 2)
optresults[i,]$NumBets<-b$TotalBets
b<-HockeyModel:::compare_market_line_xg(predictions_xg$test, overbetdiff=optresults[i,]$over, underbetdiff = optresults[i,]$under, juice=juice)
optresults[i,]$Results_2122<-b$TotalResults
optresults[i,]$Units_2122<-round(b$TotalUnits,2)
optresults[i,]$NumBets_2122<-b$TotalBets
}
Our best results come from betting overs if we differ by 0.8 and betting unders by differing by 0.3, In doing so, we got final results of $8502 (or 14.97 units on 568 bets). Remember that this was a 4 season performance (a total of 4877 games) so these values are picked to ‘on average’ produce those results across a long period of time.
Using those parameters, we get a result of $-2175, or -9.62 units for the 2021-2022 season.
The top 20 mixes of over/under bet (sorted on Total profit on 2017-2021) are shown below, as are the results of those parameters applied to the 2021-2022 season.
| over | under | TotalResults | TotalUnits | NumBets | Units_2122 | Results_2122 | NumBets_2122 |
|---|---|---|---|---|---|---|---|
| 0.8 | 0.3 | 8502 | 14.97 | 568 | -9.62 | -2175 | 226 |
| 0.6 | 0.3 | 8298 | 10.73 | 773 | -6.84 | -1676 | 245 |
| 0.9 | 0.3 | 7933 | 14.91 | 532 | -9.62 | -2175 | 226 |
| 0.7 | 0.3 | 7792 | 12.35 | 631 | -8.39 | -1964 | 234 |
| 0.8 | 0.1 | 7639 | 6.14 | 1245 | -11.07 | -4241 | 383 |
| 0.5 | 0.3 | 7474 | 7.36 | 1015 | -7.73 | -2087 | 270 |
| 0.6 | 0.1 | 7435 | 5.13 | 1450 | -9.31 | -3742 | 402 |
| 1.0 | 0.3 | 7184 | 13.90 | 517 | -9.62 | -2175 | 226 |
| 0.9 | 0.1 | 7070 | 5.85 | 1209 | -11.07 | -4241 | 383 |
| 0.7 | 0.1 | 6929 | 5.30 | 1308 | -10.31 | -4030 | 391 |
| 0.5 | 0.1 | 6611 | 3.91 | 1692 | -9.73 | -4153 | 427 |
| 0.8 | 0.2 | 6326 | 7.37 | 858 | -12.29 | -3700 | 301 |
| 1.0 | 0.1 | 6321 | 5.29 | 1194 | -11.07 | -4241 | 383 |
| 0.6 | 0.2 | 6122 | 5.76 | 1063 | -10.00 | -3201 | 320 |
| 0.9 | 0.2 | 5757 | 7.00 | 822 | -12.29 | -3700 | 301 |
| 0.7 | 0.2 | 5616 | 6.10 | 921 | -11.29 | -3489 | 309 |
| 0.5 | 0.2 | 5298 | 4.06 | 1305 | -10.47 | -3612 | 345 |
| 1.0 | 0.2 | 5008 | 6.21 | 807 | -12.29 | -3700 | 301 |
| 0.8 | 0.4 | 4597 | 13.17 | 349 | -2.81 | -424 | 151 |
| 0.6 | 0.4 | 4393 | 7.93 | 554 | 0.44 | 75 | 170 |
All that’s left is to decide how many bets/season you’re looking for, and optimize for that with total performance taken into consideration.
We’ll look at over/under of 0.8/0.3 xG to direct our bets from now on.
| OverBets | 8.00 |
| UnderBets | 226.00 |
| OverBetResults | 211.00 |
| UnderBetResults | -2175.00 |
| OverUnits | 26.38 |
| UnderUnits | -9.62 |
| TotalBets | 234.00 |
| TotalResults | -1964.00 |
| TotalUnits | -8.39 |
There are multiple methods of determining the market’s xG value. This is a possible opportunity to enhance the model performance - if you can get an edge on the market here, it can make or break a model’s performance.
The market data provided contains a determined market xG using the one listed above. An alternative method could be considered using Poisson fitting. Testing the market with this to see if they’re different.
| OverBets | 37.00 |
| UnderBets | 111.00 |
| OverBetResults | 601.00 |
| UnderBetResults | 609.00 |
| OverUnits | 16.24 |
| UnderUnits | 5.49 |
| TotalBets | 148.00 |
| TotalResults | 1210.00 |
| TotalUnits | 8.18 |
Digging into the differences a bit further, we’ll plot the correlation between the supplied market xG and the poisson determined xG:
## Warning: Removed 94 rows containing missing values (geom_point).
## Warning: Removed 94 rows containing non-finite values (stat_bin).
We can dig further into each method of determination by plotting the histogram of each xG implied method:
## Warning: Removed 94 rows containing non-finite values (stat_bin).
Lets look at the difference between our suspected xG and see what we have as a cause for it. Remember from above that most differences are <0.5 xG value.
## Warning: Removed 94 rows containing missing values (geom_point).
Lets look at the highest diff values to see if a tabular form helps us understand.
| GameID | Totals | OverOdds | UnderOdds | ImpliedxG | ImpliedxGPoisson | ImpliedDiff |
|---|---|---|---|---|---|---|
| 2021020883 | 6.0 | -135 | -118 | 6.432090 | 5.552015 | 0.8800752 |
| 2021020504 | 6.0 | -125 | -133 | 6.293083 | 5.413719 | 0.8793640 |
| 2021020726 | 6.5 | -138 | -110 | 6.971515 | 6.184691 | 0.7868235 |
| 2021020934 | 5.5 | -139 | -110 | 5.984436 | 5.209304 | 0.7751323 |
| 2021020649 | 6.0 | -133 | -105 | 6.405244 | 5.828398 | 0.5768452 |
| 2021020511 | 6.5 | -125 | -111 | 6.793083 | 6.239732 | 0.5533509 |
| 2021020626 | 6.0 | -118 | -118 | 6.188190 | 5.652752 | 0.5354379 |
| 2021021023 | 6.0 | -118 | -118 | 6.188190 | 5.652752 | 0.5354379 |
| 2021020998 | 6.0 | -139 | 102 | 6.484436 | 5.952844 | 0.5315921 |
| 2021020767 | 6.0 | -118 | -115 | 6.188190 | 5.711087 | 0.4771035 |
It looks like a pretty large juice on these - that might explain the discrepancy. It also looks like these all happened in the last half of the most recent season, despite us comparing 9 season’s worth of games.
Now that we know there’s a correlation, but meaningful difference between xG methods, let’s optimize the xGPoisson betting cutoffs. This has the same format as above, so…
Our best results come from betting overs if we differ by 0.8 and betting unders by differing by 0.3, In doing so, we got final results of $8003 (or 13.82 units on 579 bets).
Using those parameters, we get a result of $1210, or 8.18 units for the 2021-2022 season.
The top 20 mixes of over/under bet (sorted on Total profit on 2017-2021) are shown below, as are the results of those parameters applied to the 2021-2022 season.
| over | under | TotalResults | TotalUnits | NumBets | Units_2122 | Results_2122 | NumBets_2122 |
|---|---|---|---|---|---|---|---|
| 0.8 | 0.3 | 8003 | 13.82 | 579 | 8.18 | 1210 | 148 |
| 0.9 | 0.3 | 7823 | 14.65 | 534 | 7.55 | 1012 | 134 |
| 0.8 | 0.1 | 7230 | 5.62 | 1287 | -2.20 | -635 | 288 |
| 0.8 | 0.2 | 7214 | 8.09 | 892 | 3.10 | 673 | 217 |
| 0.6 | 0.3 | 7059 | 8.17 | 864 | 5.53 | 1245 | 225 |
| 0.9 | 0.1 | 7050 | 5.68 | 1242 | -3.04 | -833 | 274 |
| 0.9 | 0.2 | 7034 | 8.30 | 847 | 2.34 | 475 | 203 |
| 1.0 | 0.3 | 6928 | 13.50 | 513 | 4.70 | 602 | 128 |
| 0.7 | 0.3 | 6776 | 10.07 | 673 | 3.00 | 537 | 179 |
| 0.6 | 0.1 | 6286 | 4.00 | 1572 | -1.64 | -600 | 365 |
| 0.6 | 0.2 | 6270 | 5.33 | 1177 | 2.41 | 708 | 294 |
| 1.0 | 0.1 | 6155 | 5.04 | 1221 | -4.64 | -1243 | 268 |
| 1.0 | 0.2 | 6139 | 7.43 | 826 | 0.33 | 65 | 197 |
| 0.5 | 0.3 | 6085 | 5.46 | 1114 | 2.03 | 599 | 295 |
| 0.7 | 0.1 | 6003 | 4.35 | 1381 | -4.10 | -1308 | 319 |
| 0.7 | 0.2 | 5987 | 6.07 | 986 | 0.00 | 0 | 248 |
| 0.5 | 0.1 | 5312 | 2.92 | 1822 | -2.86 | -1246 | 435 |
| 0.5 | 0.2 | 5296 | 3.71 | 1427 | 0.17 | 62 | 364 |
| 0.8 | 0.4 | 3157 | 8.77 | 360 | 19.75 | 2034 | 103 |
| 0.9 | 0.4 | 2977 | 9.45 | 315 | 20.63 | 1836 | 89 |
We’re likely happiest picking over/under cutoffs of (again) 0.8/0.3, this leaves us with the following performance for 2021-2022:
| OverBets | 37.00 |
| UnderBets | 111.00 |
| OverBetResults | 601.00 |
| UnderBetResults | 609.00 |
| OverUnits | 16.24 |
| UnderUnits | 5.49 |
| TotalBets | 148.00 |
| TotalResults | 1210.00 |
| TotalUnits | 8.18 |