├── NAMESPACE ├── .gitignore ├── data ├── ..rda ├── mlb_odds_2014.rda ├── mlb_odds_2015.rda ├── mlb_odds_2016.rda ├── mlb_odds_2017.rda ├── mlb_odds_2018.rda ├── mlb_odds_2019.rda ├── nba_odds_2018.rda ├── nba_odds_2019.rda └── nba_odds_2020.rda ├── .Rbuildignore ├── mlb_2019_ml_chart.jpg ├── man ├── figures │ └── README-unnamed-chunk-2-1.png ├── Dec2US.Rd ├── US2Dec.Rd ├── Implied2US.Rd ├── Implied2Dec.Rd ├── US2Implied.Rd ├── Dec2Implied.Rd ├── US2All.Rd ├── US2Fair.Rd ├── Dec2All.Rd ├── Dec2Fair.Rd ├── Implied2All.Rd ├── calculateWinRanges.Rd ├── calculateImpliedProbPair.Rd ├── calculateZeroVigProb.Rd ├── calculateTheoreticalHold.Rd ├── nba_odds_2020.Rd ├── calculateKellyStake.Rd ├── mlb_odds_2014.Rd ├── mlb_odds_2015.Rd ├── mlb_odds_2016.Rd ├── mlb_odds_2017.Rd ├── mlb_odds_2018.Rd └── mlb_odds_2019.Rd ├── LICENSE ├── bettingtools.Rproj ├── DESCRIPTION ├── R ├── nba_data.R ├── betting_tools.R ├── odds_converter.R └── mlb_data.R ├── data-raw ├── nba_datasets.R └── MLB_Datasets.R ├── README.Rmd └── README.md /NAMESPACE: -------------------------------------------------------------------------------- 1 | exportPattern("^[[:alpha:]]+") 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /data/..rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/..rda -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^data-raw$ 4 | ^README\.Rmd$ 5 | -------------------------------------------------------------------------------- /data/mlb_odds_2014.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2014.rda -------------------------------------------------------------------------------- /data/mlb_odds_2015.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2015.rda -------------------------------------------------------------------------------- /data/mlb_odds_2016.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2016.rda -------------------------------------------------------------------------------- /data/mlb_odds_2017.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2017.rda -------------------------------------------------------------------------------- /data/mlb_odds_2018.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2018.rda -------------------------------------------------------------------------------- /data/mlb_odds_2019.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/mlb_odds_2019.rda -------------------------------------------------------------------------------- /data/nba_odds_2018.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/nba_odds_2018.rda -------------------------------------------------------------------------------- /data/nba_odds_2019.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/nba_odds_2019.rda -------------------------------------------------------------------------------- /data/nba_odds_2020.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/data/nba_odds_2020.rda -------------------------------------------------------------------------------- /mlb_2019_ml_chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/mlb_2019_ml_chart.jpg -------------------------------------------------------------------------------- /man/figures/README-unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pwu97/bettingtools/HEAD/man/figures/README-unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Everyone can use this package so long as you give credit to the package and its author. The historical betting lines were scraped, downloaded, and cleaned from 2 | https://www.sportsbookreviewsonline.com/. 3 | -------------------------------------------------------------------------------- /bettingtools.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: bettingtools 2 | Type: Package 3 | Title: Betting Tools 4 | Version: 0.0.0.9000 5 | Authors@R: c(person("Peter", "Wu", email = "wu.peter97@gmail.com", role = c("aut", "cre"))) 6 | Maintainer: Peter Wu 7 | Description: Sports Betting Tools 8 | License: see LICENSE 9 | Encoding: UTF-8 10 | LazyData: true 11 | Suggests: 12 | dplyr, 13 | tibble, 14 | readr, 15 | stringr, 16 | lubridate, 17 | teamcolors, 18 | ggplot2 19 | RoxygenNote: 7.0.2 20 | -------------------------------------------------------------------------------- /man/Dec2US.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Dec2US} 4 | \alias{Dec2US} 5 | \title{Convert Decimal to American odds} 6 | \usage{ 7 | Dec2US(decimal, precision = 4) 8 | } 9 | \arguments{ 10 | \item{decimal}{A vector of Decimal odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector of American odds. 17 | } 18 | \description{ 19 | Returns a vector of American odds 20 | } 21 | \examples{ 22 | Dec2US(c(3.17, 2.14, 2.01, 1.67)) 23 | } 24 | -------------------------------------------------------------------------------- /man/US2Dec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{US2Dec} 4 | \alias{US2Dec} 5 | \title{Convert American to Decimal odds} 6 | \usage{ 7 | US2Dec(american, precision = 2) 8 | } 9 | \arguments{ 10 | \item{american}{A vector of American odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector of Decimal odds. 17 | } 18 | \description{ 19 | Returns a vector of Decimal odds 20 | } 21 | \examples{ 22 | US2Dec(c(-250, 600, 137, -110)) 23 | } 24 | -------------------------------------------------------------------------------- /man/Implied2US.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Implied2US} 4 | \alias{Implied2US} 5 | \title{Convert Implied to American odds} 6 | \usage{ 7 | Implied2US(implied, precision = 4) 8 | } 9 | \arguments{ 10 | \item{precision}{A numerical value representing the precision. The default precision 11 | is set to 4 digits.} 12 | 13 | \item{decimal}{A vector of Implied odds.} 14 | } 15 | \value{ 16 | A vector of American odds. 17 | } 18 | \description{ 19 | Returns a vector of American odds 20 | } 21 | \examples{ 22 | Implied2US(c(.34, .54, .88, .12)) 23 | } 24 | -------------------------------------------------------------------------------- /man/Implied2Dec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Implied2Dec} 4 | \alias{Implied2Dec} 5 | \title{Convert Implied to Decimal odds} 6 | \usage{ 7 | Implied2Dec(implied, precision = 2) 8 | } 9 | \arguments{ 10 | \item{precision}{A numerical value representing the precision. The default precision 11 | is set to 4 digits.} 12 | 13 | \item{decimal}{A vector of Implied odds.} 14 | } 15 | \value{ 16 | A vector of Decimal odds. 17 | } 18 | \description{ 19 | Returns a vector of Decimal odds 20 | } 21 | \examples{ 22 | Implied2Dec(c(.34, .54, .88, .12)) 23 | } 24 | -------------------------------------------------------------------------------- /man/US2Implied.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{US2Implied} 4 | \alias{US2Implied} 5 | \title{Convert American to Implied odds} 6 | \usage{ 7 | US2Implied(american, precision = 4) 8 | } 9 | \arguments{ 10 | \item{american}{A vector of American odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector of Implied odds. 17 | } 18 | \description{ 19 | Returns a vector of Implied odds 20 | } 21 | \examples{ 22 | US2Implied(c(-250, 600, 137, -110)) 23 | } 24 | -------------------------------------------------------------------------------- /man/Dec2Implied.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Dec2Implied} 4 | \alias{Dec2Implied} 5 | \title{Convert Decimal to Implied odds} 6 | \usage{ 7 | Dec2Implied(decimal, precision = 4) 8 | } 9 | \arguments{ 10 | \item{decimal}{A vector of Decimal odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector of Implied odds. 17 | } 18 | \description{ 19 | Returns a vector of Implied odds 20 | } 21 | \examples{ 22 | Dec2Implied(c(3.17, 2.14, 2.01, 1.67)) 23 | } 24 | -------------------------------------------------------------------------------- /man/US2All.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{US2All} 4 | \alias{US2All} 5 | \title{Convert American to Decimal and Implied odds} 6 | \usage{ 7 | US2All(american, precision = 4) 8 | } 9 | \arguments{ 10 | \item{american}{A vector of American odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A tibble of American, Decimal, and Implied odds 17 | } 18 | \description{ 19 | Returns a tibble where each row has American, Decimal, and Implied 20 | odds 21 | } 22 | \examples{ 23 | US2All(c(-250, 600, 137, -110)) 24 | } 25 | -------------------------------------------------------------------------------- /man/US2Fair.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{US2Fair} 4 | \alias{US2Fair} 5 | \title{Calculate fair lines from a pair of American odds} 6 | \usage{ 7 | US2Fair(pair, precision = 4) 8 | } 9 | \arguments{ 10 | \item{pair}{A vector representing the two outcomes in American odds format.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector representing the fair line 17 | } 18 | \description{ 19 | Returns a vector of fair lines 20 | } 21 | \examples{ 22 | US2Fair(c(265, -375)) 23 | 24 | US2Fair(c(150, -200)) 25 | } 26 | -------------------------------------------------------------------------------- /man/Dec2All.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Dec2All} 4 | \alias{Dec2All} 5 | \title{Convert Decimal to American and Implied odds} 6 | \usage{ 7 | Dec2All(decimal, precision = 4) 8 | } 9 | \arguments{ 10 | \item{precision}{A numerical value representing the precision. The default precision 11 | is set to 4 digits.} 12 | 13 | \item{american}{A vector of Decimal odds.} 14 | } 15 | \value{ 16 | A tibble of American, Decimal, and Implied odds 17 | } 18 | \description{ 19 | Returns a tibble where each row has American, Decimal, and Implied 20 | odds 21 | } 22 | \examples{ 23 | Dec2All(c(3.17, 2.14, 2.01, 1.67)) 24 | } 25 | -------------------------------------------------------------------------------- /man/Dec2Fair.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Dec2Fair} 4 | \alias{Dec2Fair} 5 | \title{Calculate fair lines from a pair of Decimal odds} 6 | \usage{ 7 | Dec2Fair(pair, precision = 4) 8 | } 9 | \arguments{ 10 | \item{pair}{A vector representing the two outcomes in Decimal odds format.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector representing the fair line 17 | } 18 | \description{ 19 | Returns a vector of fair lines 20 | } 21 | \examples{ 22 | Dec2Fair(c(2.14, 1.86)) 23 | 24 | Dec2Fair(c(4.00, 1.51)) 25 | } 26 | -------------------------------------------------------------------------------- /man/Implied2All.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/odds_converter.R 3 | \name{Implied2All} 4 | \alias{Implied2All} 5 | \title{Convert Implied to Decimal and Implied odds} 6 | \usage{ 7 | Implied2All(implied, precision = 4) 8 | } 9 | \arguments{ 10 | \item{implied}{A vector of Implied odds.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A tibble of American, Decimal, and Implied odds 17 | } 18 | \description{ 19 | Returns a tibble where each row has American, Decimal, and Implied 20 | odds 21 | } 22 | \examples{ 23 | Implied2All(c(.34, .54, .88, .12)) 24 | } 25 | -------------------------------------------------------------------------------- /man/calculateWinRanges.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/betting_tools.R 3 | \name{calculateWinRanges} 4 | \alias{calculateWinRanges} 5 | \title{Calculate probabilities of all win possibilities} 6 | \usage{ 7 | calculateWinRanges(probabilities) 8 | } 9 | \arguments{ 10 | \item{probabilites}{A vector representing a vector of win probabilities.} 11 | } 12 | \value{ 13 | A tibble where each row represents one possible win-loss combination 14 | along with the probability of that combination occurring. 15 | } 16 | \description{ 17 | Returns a tibble of all possible win-loss combinations. 18 | } 19 | \examples{ 20 | calculateWinRanges(c(.1, .4, .88, .47)) 21 | 22 | calculateWinRanges(c(.12, .462, .29)) 23 | 24 | calculateWinRanges(c(.6, .6, .6, .6)) 25 | } 26 | -------------------------------------------------------------------------------- /man/calculateImpliedProbPair.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/betting_tools.R 3 | \name{calculateImpliedProbPair} 4 | \alias{calculateImpliedProbPair} 5 | \title{Calculate implied probabilities} 6 | \usage{ 7 | calculateImpliedProbPair(pair, precision = 4) 8 | } 9 | \arguments{ 10 | \item{pair}{A vector representing the two outcomes in American odds format.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector representing the implied probabilites 17 | } 18 | \description{ 19 | Returns a vector of implied probabilities 20 | } 21 | \examples{ 22 | calculateImpliedProbPair(c(200, -220)) 23 | 24 | calculateImpliedProbPair(c(1000, -800)) 25 | 26 | calculateImpliedProbPair(c(1000, -800), precision = 7) 27 | } 28 | -------------------------------------------------------------------------------- /man/calculateZeroVigProb.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/betting_tools.R 3 | \name{calculateZeroVigProb} 4 | \alias{calculateZeroVigProb} 5 | \title{Calculate zero-vig implied probabilities} 6 | \usage{ 7 | calculateZeroVigProb(lines, precision = 4) 8 | } 9 | \arguments{ 10 | \item{lines}{A vector representing a vector of lines in American odds format.} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A vector representing the zero-vig implied probabilities. 17 | } 18 | \description{ 19 | Returns a vector of the zero-vig implied probabilities 20 | } 21 | \examples{ 22 | calculateZeroVigProb(c(200, -180, -450, 800)) 23 | 24 | calculateZeroVigProb(-237) 25 | 26 | calculateZeroVigProb(-237, precision = 7) 27 | } 28 | -------------------------------------------------------------------------------- /man/calculateTheoreticalHold.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/betting_tools.R 3 | \name{calculateTheoreticalHold} 4 | \alias{calculateTheoreticalHold} 5 | \title{Calculates the theoretical hold} 6 | \usage{ 7 | calculateTheoreticalHold(pair, precision = 4) 8 | } 9 | \arguments{ 10 | \item{pair}{A vector representing the pair of outcomes in American odds format} 11 | 12 | \item{precision}{A numerical value representing the precision. The default precision 13 | is set to 4 digits.} 14 | } 15 | \value{ 16 | A numerical value representing the theoretical hold. 17 | } 18 | \description{ 19 | Returns the theoretical hold for a two-outcome line set. This corresponds 20 | to the profit a sportsbook would expect to make were a player to bet on either side 21 | of an event with all else being equal. 22 | } 23 | \examples{ 24 | calculateTheoreticalHold(c(-110, -110)) 25 | 26 | calculateTheoreticalHold(c(-1500, 875)) 27 | 28 | calculateTheoreticalHold(c(-1500, 875), precision = 7) 29 | } 30 | -------------------------------------------------------------------------------- /man/nba_odds_2020.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/nba_data.R 3 | \docType{data} 4 | \name{nba_odds_2020} 5 | \alias{nba_odds_2020} 6 | \title{2019-2020 NBA Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of NBA game} 9 | \item{game}{Game number} 10 | \item{away_name}{Away team full name} 11 | \item{home_name}{Home team full name} 12 | \item{away_score}{Away team score} 13 | \item{home_score}{Home team score} 14 | \item{away_open_ml}{Away team opening moneyline in American odds format} 15 | \item{home_open_ml}{Home team opening moneyline in American odds format} 16 | \item{away_close_ml}{Away team closing moneyline in American odds format} 17 | \item{home_close_ml}{Home team closing moneyline in American odds format} 18 | }} 19 | \source{ 20 | \url{https://www.sportsbookreviewsonline.com/scoresoddsarchives/nba/nbaoddsarchives.htm} 21 | } 22 | \usage{ 23 | nba_odds_2020 24 | } 25 | \description{ 26 | Dataset containing historical 2019-2020 NBA Vegas lines on moneylines, point totals, 27 | and point spreads. 28 | } 29 | \keyword{datasets} 30 | -------------------------------------------------------------------------------- /man/calculateKellyStake.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/betting_tools.R 3 | \name{calculateKellyStake} 4 | \alias{calculateKellyStake} 5 | \title{Calculates a single Kelly stake} 6 | \usage{ 7 | calculateKellyStake( 8 | expected, 9 | payout, 10 | kelly_multiplier = 1, 11 | expected_odds = "prob", 12 | payout_odds = "dec" 13 | ) 14 | } 15 | \arguments{ 16 | \item{expected}{A numerical value representing the odds of the expected payout.} 17 | 18 | \item{payout}{A numerical value representing the odds of the actual payout.} 19 | 20 | \item{kelly_multiplier}{A numerical value representing the kelly multiplier of the bet. 21 | The default value of the Kelly multiplier is 1.} 22 | 23 | \item{expected_odds}{A string representing the odds format of the expected payout. 24 | It is either "prob", "dec", or "us". The default odds format is in implied probability.} 25 | 26 | \item{payout_odds}{A string representing the odds format of the actual payout. 27 | It is either "prob", "dec", or "us". The default odds format is in decimal odds.} 28 | } 29 | \value{ 30 | The single Kelly stake. 31 | } 32 | \description{ 33 | Returns the single Kelly stake. This is the percentage of one's bankroll 34 | one should bet to maximize the expected growth of one's bankroll on a single bet. 35 | This is known as the single Kelly stake. 36 | } 37 | \examples{ 38 | calculateKellyStake(0.41, 2.56) 39 | 40 | calculateKellyStake(-120, 150, expected_odds = "us", payout_odds = "us") 41 | 42 | calculateKellyStake(0.70, -150, kelly_multiplier = 0.1, payout_odds = "us") 43 | 44 | calculateKellyStake(0.26, -110, payout_odds = "us") 45 | } 46 | -------------------------------------------------------------------------------- /man/mlb_odds_2014.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2014} 5 | \alias{mlb_odds_2014} 6 | \title{2014 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2014 34 | } 35 | \description{ 36 | Dataset containing historical 2014 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /man/mlb_odds_2015.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2015} 5 | \alias{mlb_odds_2015} 6 | \title{2015 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2015 34 | } 35 | \description{ 36 | Dataset containing historical 2015 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /man/mlb_odds_2016.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2016} 5 | \alias{mlb_odds_2016} 6 | \title{2016 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2016 34 | } 35 | \description{ 36 | Dataset containing historical 2016 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /man/mlb_odds_2017.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2017} 5 | \alias{mlb_odds_2017} 6 | \title{2017 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2017 34 | } 35 | \description{ 36 | Dataset containing historical 2017 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /man/mlb_odds_2018.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2018} 5 | \alias{mlb_odds_2018} 6 | \title{2018 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2018 34 | } 35 | \description{ 36 | Dataset containing historical 2018 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /man/mlb_odds_2019.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/mlb_data.R 3 | \docType{data} 4 | \name{mlb_odds_2019} 5 | \alias{mlb_odds_2019} 6 | \title{2019 MLB Historical Vegas Lines} 7 | \format{\describe{ 8 | \item{date}{Date of MLB game} 9 | \item{game}{Game number} 10 | \item{away_abbrev}{Abbreviation of away team} 11 | \item{home_abbrev}{Abbreviation of home team} 12 | \item{away_name}{Away team full name} 13 | \item{home_name}{Home team full name} 14 | \item{away_score}{Away team score} 15 | \item{home_score}{Home team score} 16 | \item{away_open_ml}{Away team opening moneyline in American odds format} 17 | \item{home_open_ml}{Home team opening moneyline in American odds format} 18 | \item{away_close_ml}{Away team closing moneyline in American odds format} 19 | \item{home_close_ml}{Home team closing moneyline in American odds format} 20 | \item{away_run_line}{Away team run line} 21 | \item{home_run_line}{Home team run line} 22 | \item{away_run_line_odds}{Away team run line odds in American odds format} 23 | \item{home_run_line_odds}{Home team run line odds in American odds format} 24 | \item{open_ou_line}{Opening over/under line} 25 | \item{open_ou_odds}{Opening over/under odds in American odds format} 26 | \item{close_ou_line}{Closing over/under line} 27 | \item{close_ou_odds}{Closing over/under odds in American odds format} 28 | }} 29 | \source{ 30 | \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 31 | } 32 | \usage{ 33 | mlb_odds_2019 34 | } 35 | \description{ 36 | Dataset containing historical 2019 MLB Vegas lines on moneylines, point totals, 37 | and point spreads. 38 | } 39 | \keyword{datasets} 40 | -------------------------------------------------------------------------------- /R/nba_data.R: -------------------------------------------------------------------------------- 1 | #' 2019-2020 NBA Historical Vegas Lines 2 | #' 3 | #' Dataset containing historical 2019-2020 NBA Vegas lines on moneylines. 4 | #' 5 | #' @docType data 6 | #' @format 7 | #' \describe{ 8 | #' \item{date}{Date of NBA game} 9 | #' \item{game}{Game number} 10 | #' \item{away_name}{Away team full name} 11 | #' \item{home_name}{Home team full name} 12 | #' \item{away_score}{Away team score} 13 | #' \item{home_score}{Home team score} 14 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 15 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 16 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 17 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 18 | #' } 19 | #' 20 | #' @source \url{https://www.sportsbookreviewsonline.com/scoresoddsarchives/nba/nbaoddsarchives.htm} 21 | "nba_odds_2020" 22 | 23 | #' 2018-2019 NBA Historical Vegas Lines 24 | #' 25 | #' Dataset containing historical 2018-2019 NBA Vegas lines on moneylines. 26 | #' 27 | #' @docType data 28 | #' @format 29 | #' \describe{ 30 | #' \item{date}{Date of NBA game} 31 | #' \item{game}{Game number} 32 | #' \item{away_name}{Away team full name} 33 | #' \item{home_name}{Home team full name} 34 | #' \item{away_score}{Away team score} 35 | #' \item{home_score}{Home team score} 36 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 37 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 38 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 39 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 40 | #' } 41 | #' 42 | #' @source \url{https://www.sportsbookreviewsonline.com/scoresoddsarchives/nba/nbaoddsarchives.htm} 43 | "nba_odds_2019" 44 | 45 | #' 2017-2018 NBA Historical Vegas Lines 46 | #' 47 | #' Dataset containing historical 2017-2018 NBA Vegas lines on moneylines. 48 | #' 49 | #' @docType data 50 | #' @format 51 | #' \describe{ 52 | #' \item{date}{Date of NBA game} 53 | #' \item{game}{Game number} 54 | #' \item{away_name}{Away team full name} 55 | #' \item{home_name}{Home team full name} 56 | #' \item{away_score}{Away team score} 57 | #' \item{home_score}{Home team score} 58 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 59 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 60 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 61 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 62 | #' } 63 | #' 64 | #' @source \url{https://www.sportsbookreviewsonline.com/scoresoddsarchives/nba/nbaoddsarchives.htm} 65 | "nba_odds_2018" 66 | 67 | -------------------------------------------------------------------------------- /R/betting_tools.R: -------------------------------------------------------------------------------- 1 | library(tibble) 2 | library(dplyr) 3 | 4 | #' @title Calculate probabilities of all win possibilities 5 | #' 6 | #' @description Returns a tibble of all possible win-loss combinations. 7 | #' 8 | #' @param probabilites A vector representing a vector of win probabilities. 9 | #' 10 | #' @return A tibble where each row represents one possible win-loss combination 11 | #' along with the probability of that combination occurring. 12 | #' 13 | #' @examples 14 | #' calculateWinRanges(c(.1, .4, .88, .47)) 15 | #' 16 | #' calculateWinRanges(c(.12, .462, .29)) 17 | #' 18 | #' calculateWinRanges(c(.6, .6, .6, .6)) 19 | calculateWinRanges <- function(probabilities) { 20 | win_probs <- rep(NA, length(probabilities) + 1) 21 | 22 | # W = 0 23 | win_probs[1] <- prod(1 - probabilities) 24 | # W = 1 to length(probabilities) 25 | for (i in 1:length(probabilities)) { 26 | mat <- combn(probabilities, i) 27 | cur_prob <- 0 28 | for (j in 1:ncol(mat)) { 29 | win_vectors <- mat[, j] 30 | loss_vectors <- win_probs[1]/prod(1 - win_vectors) 31 | cur_prob <- cur_prob + prod(win_vectors) * prod(loss_vectors) 32 | } 33 | win_probs[i+1] <- cur_prob 34 | } 35 | 36 | # Create return tibble 37 | return(tibble("W" = c(0:length(probabilities)), 38 | "L" = rev(c(0:length(probabilities))), 39 | "Probability" = win_probs)) 40 | } 41 | 42 | #' @title Calculates single Kelly stakes 43 | #' 44 | #' @description Returns a vector of single Kelly stake. This is the percentage of one's 45 | #' bankroll one should bet to maximize the expected growth of one's bankroll on a single bet. 46 | #' This is known as the single Kelly stake. 47 | #' 48 | #' @param expected A numerical value representing the odds of the expected payout. 49 | #' @param payout A numerical value representing the odds of the actual payout. 50 | #' @param kelly_multiplier A numerical value representing the kelly multiplier of the bet. 51 | #' The default value of the Kelly multiplier is 1. 52 | #' @param expected_odds A string representing the odds format of the expected payout. 53 | #' It is either "prob", "dec", or "us". The default odds format is in implied probability. 54 | #' @param payout_odds A string representing the odds format of the actual payout. 55 | #' It is either "prob", "dec", or "us". The default odds format is in decimal odds. 56 | #' 57 | #' @return The vector of single Kelly stakes. 58 | #' 59 | #' @examples 60 | #' calculateKellyStake(0.41, 2.56) 61 | #' 62 | #' calculateKellyStake(-120, 150, expected_odds = "us", payout_odds = "us") 63 | #' 64 | #' calculateKellyStake(0.70, -150, kelly_multiplier = 0.1, payout_odds = "us") 65 | #' 66 | #' calculateKellyStake(0.26, -110, payout_odds = "us") 67 | #' 68 | #' calculateKellyStake(c(0.41, 0.89, 0.24), c(-103, 780, 400), payout_odds = "us") 69 | calculateKellyStake <- function(expected, payout, kelly_multiplier = 1, 70 | expected_odds = "prob", 71 | payout_odds = "dec", 72 | precision = 4) { 73 | # Calculate a single Kelly stake 74 | calculateKellyStakeHelper <- function(expected_helper, payout_helper) { 75 | odds <- rep(NA, 2) 76 | if (expected_odds == "prob") { 77 | odds[1] <- expected_helper 78 | } else if (expected_odds == "dec") { 79 | odds[1] <- Dec2Implied(expected_helper) 80 | } else if (expected_odds == "us") { 81 | odds[1] <- US2Implied(expected_helper) 82 | } 83 | 84 | if (payout_odds == "dec") { 85 | odds[2] <- payout_helper 86 | } else if (payout_odds == "prob") { 87 | odds[2] <- Implied2Dec(payout_helper) 88 | } else if (payout_odds == "us") { 89 | odds[2] <- US2Dec(payout_helper) 90 | } 91 | 92 | return(round(max(kelly_multiplier * 93 | ((odds[1] * odds[2] - 1)/(odds[2] - 1)), 0), precision)) 94 | } 95 | 96 | # Prepare the vector to return 97 | result <- c() 98 | for (i in 1:length(expected)) { 99 | result <- c(result, calculateKellyStakeHelper(expected[i], payout[i])) 100 | } 101 | return(result) 102 | } 103 | 104 | #' @title Calculates the theoretical hold 105 | #' 106 | #' @description Returns the theoretical hold for a two-outcome line set. This corresponds 107 | #' to the profit a sportsbook would expect to make were a player to bet on either side 108 | #' of an event with all else being equal. 109 | #' 110 | #' @param pair A vector representing the pair of outcomes in American odds format 111 | #' @param precision A numerical value representing the precision. The default precision 112 | #' is set to 4 digits. 113 | #' 114 | #' @return A numerical value representing the theoretical hold. 115 | #' 116 | #' @examples 117 | #' calculateTheoreticalHold(c(-110, -110)) 118 | #' 119 | #' calculateTheoreticalHold(c(-1500, 875)) 120 | #' 121 | #' calculateTheoreticalHold(c(-1500, 875), precision = 7) 122 | calculateTheoreticalHold <- function(pair, precision = 4) { 123 | prob1 <- calculateZeroVigProb(pair[1]) 124 | prob2 <- calculateZeroVigProb(pair[2]) 125 | return(round(1 - 1/(prob1 + prob2), precision)) 126 | } 127 | 128 | #' @title Calculate zero-vig implied probabilities 129 | #' 130 | #' @description Returns a vector of the zero-vig implied probabilities 131 | #' 132 | #' @param lines A vector representing a vector of lines in American odds format. 133 | #' @param precision A numerical value representing the precision. The default precision 134 | #' is set to 4 digits. 135 | #' 136 | #' @return A vector representing the zero-vig implied probabilities. 137 | #' 138 | #' @examples 139 | #' calculateZeroVigProb(c(200, -180, -450, 800)) 140 | #' 141 | #' calculateZeroVigProb(-237) 142 | #' 143 | #' calculateZeroVigProb(-237, precision = 7) 144 | calculateZeroVigProb <- function(lines, precision = 4) { 145 | calculateZeroVigProbHelper <- function(line, precisionHelper = precision) { 146 | if (line >= 100) { 147 | return(round(100/(100 + line), precisionHelper)) 148 | } else if (line <= -100) { 149 | return(round((-1.0 * line)/(100 - line), precisionHelper)) 150 | } else { 151 | return(NA) 152 | } 153 | } 154 | 155 | return(sapply(lines, calculateZeroVigProbHelper)) 156 | } 157 | 158 | #' @title Calculate implied probabilities 159 | #' 160 | #' @description Returns a vector of implied probabilities 161 | #' 162 | #' @param pair A vector representing the several (n >= 2) outcomes in American odds format. 163 | #' @param precision A numerical value representing the precision. The default precision 164 | #' is set to 4 digits. 165 | #' 166 | #' @return A vector representing the implied probabilites 167 | #' 168 | #' @examples 169 | #' calculateNormalizedImplied(c(1000, -800), precision = 7) 170 | #' 171 | #' calculateNormalizedImplied(c(427, -213, 336)) 172 | calculateNormalizedImplied <- function(input, precision = 4) { 173 | probs <- rep(NA, length(input)) 174 | for (i in 1:length(input)) { 175 | probs[i] <- calculateZeroVigProb(input[i], precision = precision) 176 | } 177 | for (i in 1:length(input)) { 178 | probs[i] <- round(probs[i]/sum(probs), precision) 179 | } 180 | 181 | return(probs) 182 | } 183 | 184 | -------------------------------------------------------------------------------- /data-raw/nba_datasets.R: -------------------------------------------------------------------------------- 1 | #################################################################################### 2 | # Load packages 3 | library(dplyr) 4 | library(readr) 5 | library(stringr) 6 | library(teamcolors) 7 | library(magrittr) 8 | library(janitor) 9 | 10 | #################################################################################### 11 | # NBA Team Names Data Frame 12 | nba_teams <- teamcolors::teamcolors %>% 13 | filter(league == "nba") %>% 14 | select(location, sportslogos_name) %>% 15 | mutate(location = str_replace(location, " ", "")) %>% 16 | rename(Team = location) 17 | 18 | #################################################################################### 19 | # Convert date functions 20 | convert_date_to_full_2020 <- function(date) { 21 | month <- date %/% 100 22 | day <- date - 100 * (date %/% 100) 23 | 24 | parse_date(paste("2020", month, day), "%Y %m %d") 25 | } 26 | 27 | convert_date_to_full_2019 <- function(date) { 28 | month <- date %/% 100 29 | day <- date - 100 * (date %/% 100) 30 | 31 | parse_date(paste("2019", month, day), "%Y %m %d") 32 | } 33 | 34 | convert_date_to_full_2018 <- function(date) { 35 | month <- date %/% 100 36 | day <- date - 100 * (date %/% 100) 37 | 38 | parse_date(paste("2018", month, day), "%Y %m %d") 39 | } 40 | 41 | convert_date_to_full_2017 <- function(date) { 42 | month <- date %/% 100 43 | day <- date - 100 * (date %/% 100) 44 | 45 | parse_date(paste("2017", month, day), "%Y %m %d") 46 | } 47 | 48 | convert_date_to_full_2016 <- function(date) { 49 | month <- date %/% 100 50 | day <- date - 100 * (date %/% 100) 51 | 52 | parse_date(paste("2016", month, day), "%Y %m %d") 53 | } 54 | 55 | #################################################################################### 56 | # Wrangle 2019-2020 NBA Data 57 | nba_2019_2020_data_2019 <- read_csv("~/Downloads/nba_odds_2020.csv") %>% 58 | left_join(nba_teams, by = "Team") %>% 59 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 60 | "Los Angeles Lakers", sportslogos_name), 61 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 62 | "Los Angeles Clippers", sportslogos_name), 63 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 64 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 65 | filter(Date > 1000) %>% 66 | mutate(Date = convert_date_to_full_2019(Date)) 67 | 68 | nba_2019_2020_data_2020 <- read_csv("~/Downloads/nba_odds_2020.csv") %>% 69 | left_join(nba_teams, by = "Team") %>% 70 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 71 | "Los Angeles Lakers", sportslogos_name), 72 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 73 | "Los Angeles Clippers", sportslogos_name), 74 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 75 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 76 | filter(Date < 1000) %>% 77 | mutate(Date = convert_date_to_full_2020(Date)) 78 | 79 | nba_odds_2019 <- rbind(nba_2019_2020_data_2019, nba_2019_2020_data_2020) %>% 80 | clean_names() %>% 81 | group_by(date, game) %>% 82 | mutate(home_name = rev(sportslogos_name), 83 | home_score = rev(final), 84 | home_ml = rev(ml), 85 | open = ifelse(open[1] > open[2], open, rev(open))) %>% 86 | ungroup() %>% 87 | filter(row_number() %% 2 == 1) %>% 88 | select(date, game, away_name = sportslogos_name, home_name, 89 | away_score = final, home_score, away_ml = ml, 90 | home_ml) 91 | 92 | #################################################################################### 93 | # Wrangle 2018-2019 NBA Data 94 | nba_2018_2019_data_2018 <- read_csv("~/Downloads/nba_odds_2019.csv") %>% 95 | left_join(nba_teams, by = "Team") %>% 96 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 97 | "Los Angeles Lakers", sportslogos_name), 98 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 99 | "Los Angeles Clippers", sportslogos_name), 100 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 101 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 102 | filter(Date > 1000) %>% 103 | mutate(Date = convert_date_to_full_2018(Date)) 104 | 105 | nba_2018_2019_data_2019 <- read_csv("~/Downloads/nba_odds_2019.csv") %>% 106 | left_join(nba_teams, by = "Team") %>% 107 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 108 | "Los Angeles Lakers", sportslogos_name), 109 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 110 | "Los Angeles Clippers", sportslogos_name), 111 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 112 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 113 | filter(Date < 1000) %>% 114 | mutate(Date = convert_date_to_full_2019(Date)) 115 | 116 | nba_odds_2019 <- rbind(nba_2018_2019_data_2018, nba_2018_2019_data_2019) %>% 117 | clean_names() %>% 118 | group_by(date, game) %>% 119 | mutate(home_name = rev(sportslogos_name), 120 | home_score = rev(final), 121 | home_ml = rev(ml)) %>% 122 | ungroup() %>% 123 | filter(row_number() %% 2 == 1) %>% 124 | select(date, game, away_name = sportslogos_name, home_name, 125 | away_score = final, home_score, away_ml = ml, 126 | home_ml) 127 | 128 | #################################################################################### 129 | # Wrangle 2017-2018 NBA Data 130 | nba_2017_2018_data_2017 <- read_csv("~/Downloads/nba_odds_2018.csv") %>% 131 | left_join(nba_teams, by = "Team") %>% 132 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 133 | "Los Angeles Lakers", sportslogos_name), 134 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 135 | "Los Angeles Clippers", sportslogos_name), 136 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 137 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 138 | filter(Date > 1000) %>% 139 | mutate(Date = convert_date_to_full_2017(Date)) 140 | 141 | nba_2017_2018_data_2018 <- read_csv("~/Downloads/nba_odds_2018.csv") %>% 142 | left_join(nba_teams, by = "Team") %>% 143 | mutate(sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LALakers"), 144 | "Los Angeles Lakers", sportslogos_name), 145 | sportslogos_name = ifelse((is.na(sportslogos_name) & Team == "LAClippers"), 146 | "Los Angeles Clippers", sportslogos_name), 147 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 148 | select(Date, Game, sportslogos_name, Final, Open, Close, ML) %>% 149 | filter(Date < 1000) %>% 150 | mutate(Date = convert_date_to_full_2018(Date)) 151 | 152 | nba_odds_2018 <- rbind(nba_2017_2018_data_2017, nba_2017_2018_data_2018) %>% 153 | clean_names() %>% 154 | group_by(date, game) %>% 155 | mutate(home_name = rev(sportslogos_name), 156 | home_score = rev(final), 157 | home_ml = rev(ml)) %>% 158 | ungroup() %>% 159 | filter(row_number() %% 2 == 1) %>% 160 | select(date, game, away_name = sportslogos_name, home_name, 161 | away_score = final, home_score, away_ml = ml, 162 | home_ml) 163 | 164 | #################################################################################### 165 | # Save NBA Odds Datasets 166 | usethis::use_data(nba_odds_2020, overwrite = TRUE) 167 | usethis::use_data(nba_odds_2019, overwrite = TRUE) 168 | usethis::use_data(nba_odds_2018, overwrite = TRUE) 169 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```{r, include = FALSE} 4 | knitr::opts_chunk$set( 5 | collapse = TRUE, 6 | comment = "#>", 7 | fig.path = "man/figures/README-", 8 | out.width = "100%" 9 | ) 10 | ``` 11 | 12 | # bettingtools 13 | 14 | 15 | 16 | 17 | The bettingtools package has functions related to working with sports betting lines. First, we provide functions to work with American, Decimal, and Implied odds in the tidy format. Next, we provide functions to work with sports betting lines such as calculating zero-vig probabilities, theoretical hold, and the optimal single Kelly stake bet. Furthermore, the package comes with tidy datasets for historical NBA (work in progress), MLB, NFL (work in progress), and NHL (work in progress) Vegas lines for moneylines, point totals, and point spreads. 18 | 19 | These functions take inspiration from posts by user "Ganchrow" on the Sportsbook Review forum. 20 | 21 | ## Installation 22 | 23 | ``` r 24 | remotes::install_github("pwu97/bettingtools") 25 | ``` 26 | 27 | ## Analyze 2019 MLB Historical Odds 28 | 29 | Inside the `bettingtools` package is the 2019 MLB historical odds dataset, `mlb_odds_2019`, where we can utilize functions in this package to answer interesting questions such as what was the average moneyline for games in the 2019 MLB season using a bar chart. 30 | 31 | ```{r} 32 | library(bettingtools) 33 | library(teamcolors) 34 | library(tidyverse) 35 | library(forcats) 36 | theme_set(theme_light()) 37 | 38 | mlb_colors <- teamcolors::teamcolors %>% 39 | filter(league == "mlb") %>% 40 | rename(team = name) %>% 41 | select(team, primary) 42 | 43 | away_team_lines <- mlb_odds_2019 %>% 44 | select(away_name, away_close_ml) %>% 45 | mutate(implied_ml = US2Implied(away_close_ml)) %>% 46 | rename(team = away_name) %>% 47 | select(team, implied_ml) 48 | 49 | home_team_lines <- mlb_odds_2019 %>% 50 | select(home_name, home_close_ml) %>% 51 | mutate(implied_ml = US2Implied(home_close_ml)) %>% 52 | rename(team = home_name) %>% 53 | select(team, implied_ml) 54 | 55 | avg_2019_mlb_ml <- rbind(away_team_lines, home_team_lines) %>% 56 | group_by(team) %>% 57 | summarize(avg_implied_ml = mean(implied_ml), 58 | avg_ml_line = Implied2US(mean(implied_ml))) %>% 59 | left_join(mlb_colors, by = "team") %>% 60 | arrange(desc(avg_ml_line)) %>% 61 | slice(-n()) %>% 62 | mutate(team = fct_reorder(team, avg_ml_line, .desc = TRUE), 63 | primary = fct_reorder(primary, avg_ml_line, .desc = TRUE)) %>% 64 | ungroup() 65 | 66 | # Generate moneyline bar chart 67 | mlb_2019_ml_chart <- avg_2019_mlb_ml %>% 68 | ggplot(aes(x = team, y = avg_ml_line, label = avg_ml_line)) + 69 | geom_col(fill = avg_2019_mlb_ml$primary) + 70 | coord_flip() + 71 | labs(y = "Average Moneyline", 72 | x = "Team Name", 73 | title = "What was the average moneyline for each team during\nthe 2019 MLB Season?") 74 | 75 | ``` 76 | 77 | ![2019 Average MLB Moneylines](mlb_2019_ml_chart.jpg) 78 | 79 | We can also, for example, calculate the average over/under movement, the average away/home closing moneyline, and whether the closing moneylines are sharper than the opening moneylines given by Vegas, on average. We can see that home teams tended to be favored more often, on average, than away teams. Furthermore, there is also evidence that closing moneylines were indeed sharper than opening moneylines by about 2%. 80 | 81 | ```{r} 82 | # What is the average O/U movement for games in which there was line movement? 83 | mlb_odds_2019 %>% 84 | mutate(ou_movement = close_ou_line - open_ou_line) %>% 85 | filter(ou_movement != 0) %>% 86 | summarize(mean_ou_movement = mean(ou_movement)) %>% 87 | pull(mean_ou_movement) 88 | 89 | # What is the average away closing moneyline? 90 | mlb_odds_2019 %>% 91 | mutate(away_implied = US2Implied(away_close_ml)) %>% 92 | summarize(avg_away_closing_ml = Implied2US(mean(away_implied))) %>% 93 | pull(avg_away_closing_ml) 94 | 95 | # What is the average home closing moneyline? 96 | mlb_odds_2019 %>% 97 | mutate(home_implied = US2Implied(home_close_ml)) %>% 98 | summarize(avg_home_closing_ml = Implied2US(mean(home_implied))) %>% 99 | pull(avg_home_closing_ml) 100 | 101 | # Are closing moneylines sharper than opening moneylines? 102 | mlb_odds_2019 %>% 103 | mutate(fav_team_won_open = ifelse((((away_score > home_score) & 104 | (away_open_ml < home_open_ml)) | 105 | ((away_score < home_score) & 106 | (away_open_ml > home_open_ml))), 1, 0), 107 | fav_team_won_close = ifelse((((away_score > home_score) & 108 | (away_close_ml < home_close_ml)) | 109 | ((away_score < home_score) & 110 | (away_close_ml > home_close_ml))), 1, 0)) %>% 111 | summarize(pct_fav_won_open = mean(fav_team_won_open, na.rm = TRUE), 112 | pct_fav_won_close = mean(fav_team_won_close, na.rm = TRUE), 113 | pct_diff = pct_fav_won_close - pct_fav_won_open) 114 | ``` 115 | 116 | ## Calculate single Kelly stake 117 | 118 | We can calculate the percentage of one's bankroll one should bet to maximize the expected growth of one's bankroll on a single bet. Given an expected win probability, payout odds, and an optional Kelly multiplier factor, we can calculate one's optimal single Kelly stake. Note that default odds for the expected win probability is implied probability ("prob") and the default odds for the payout is in decimal ("dec"). We can change them accordingly to how we see fit by specifying additional parameters in our function ("prob", "dec", "us"). 119 | 120 | ```{r example} 121 | library(bettingtools) 122 | 123 | calculateKellyStake(0.53, 1.92) 124 | 125 | calculateKellyStake(0.41, 2.56) 126 | 127 | # Getting a bet at +150 when it is expected to hit at -120 128 | calculateKellyStake(-120, 150, expected_odds = "us", payout_odds = "us") 129 | 130 | # Getting a bet at -150 when expected probability is 70% at a 10% kelly multiplier. 131 | calculateKellyStake(0.70, -150, kelly_multiplier = 0.1, payout_odds = "us") 132 | 133 | # Optimal move is to not place a bet 134 | calculateKellyStake(0.26, -110, payout_odds = "us") 135 | ``` 136 | 137 | ## Calculate all possible win-loss outcomes given probabilities 138 | 139 | We return a tibble where each row is one possible outcome we can observe from a list of given probabilities. 140 | 141 | ```{r} 142 | calculateWinRanges(c(.1, .4, .88, .47)) 143 | 144 | calculateWinRanges(c(.12, .462, .29)) 145 | 146 | calculateWinRanges(c(.6, .6, .6, .6)) 147 | ``` 148 | 149 | 150 | ## Calculate zero-vig implied probabilities 151 | 152 | We can calculate the zero-vig implied probabilities of a vector of lines. The default precision is set to 4 digits. Note that we can set the precision. 153 | 154 | ```{r} 155 | calculateZeroVigProb(c(200, -180, -450, 800)) 156 | 157 | calculateZeroVigProb(-237) 158 | 159 | calculateZeroVigProb(-237, precision = 7) 160 | ``` 161 | 162 | ## Calculate implied probabilities for two-outcome line set 163 | 164 | We can calculate the implied probabilities for two or more lines by first calculating the zero-vig implied probabilities for both of them, and then normalizing them. Again, we can set the precision. 165 | 166 | ```{r} 167 | calculateNormalizedImplied(c(1000, -800), precision = 7) 168 | 169 | calculateNormalizedImplied(c(200, -220)) 170 | 171 | calculateNormalizedImplied(c(427, -213, 336)) 172 | ``` 173 | 174 | ## Calculate theoretical hold 175 | 176 | We can calculate the theoretical hold for a two-outcome line set. This corresponds to the profit a sportsbook would expect to make were a player to bet on either side of an event with all else being equal. Contrary to popular belief, larger nominal spreads doesn't necessarily mean more profit for bookies. 177 | 178 | ```{r} 179 | calculateTheoreticalHold(c(-110, -110)) 180 | 181 | calculateTheoreticalHold(c(-1500, 875)) 182 | 183 | calculateTheoreticalHold(c(-1500, 875), precision = 7) 184 | ``` 185 | 186 | ## Convert American or Decimal Odds to Zero-Vig Fair Odds 187 | 188 | We can convert a pair of American or Decimal odds to the fair zero-vig counterpart by calculating the individual zero-vig probabilities and then normalizing them. 189 | 190 | ```{r} 191 | US2Fair(c(265, -375)) 192 | 193 | US2Fair(c(150, -200)) 194 | 195 | Dec2Fair(c(2.14, 1.86)) 196 | 197 | Dec2Fair(c(4.00, 1.51)) 198 | ``` 199 | 200 | ## Converting between American, Decimal, and Implied Odds 201 | 202 | We can convert between American, Decimal, and Implied Odds. 203 | 204 | ```{r} 205 | US2Implied(c(-250, 600, 137, -110)) 206 | 207 | US2Dec(c(-250, 600, 137, -110)) 208 | 209 | US2All(c(-250, 600, 137, -110)) 210 | 211 | Dec2Implied(c(3.17, 2.14, 2.01, 1.67)) 212 | 213 | Dec2US(c(3.17, 2.14, 2.01, 1.67)) 214 | 215 | Dec2All(c(3.17, 2.14, 2.01, 1.67)) 216 | 217 | Implied2Dec(c(.34, .54, .88, .12)) 218 | 219 | Implied2US(c(.34, .54, .88, .12)) 220 | 221 | Implied2All(c(.34, .54, .88, .12)) 222 | ``` 223 | 224 | -------------------------------------------------------------------------------- /R/odds_converter.R: -------------------------------------------------------------------------------- 1 | library(tibble) 2 | library(dplyr) 3 | 4 | #' @title Convert American to Decimal odds 5 | #' 6 | #' @description Returns a vector of Decimal odds 7 | #' 8 | #' @param american A vector of American odds. 9 | #' @param precision A numerical value representing the precision. The default precision 10 | #' is set to 4 digits. 11 | #' 12 | #' @return A vector of Decimal odds. 13 | #' 14 | #' @examples 15 | #' US2Dec(c(-250, 600, 137, -110)) 16 | US2Dec <- function(american, precision = 2) { 17 | US2DecHelper <- function(one_american, precisionHelper = precision) { 18 | if (one_american >= 100) { 19 | return(round(one_american/100 + 1, precisionHelper)) 20 | } else if (one_american <= -100) { 21 | return(round(100/(-1.0 * one_american) + 1, precisionHelper)) 22 | } else { 23 | return(NA) 24 | } 25 | } 26 | 27 | return(sapply(american, US2DecHelper)) 28 | } 29 | 30 | #' @title Convert American to Implied odds 31 | #' 32 | #' @description Returns a vector of Implied odds 33 | #' 34 | #' @param american A vector of American odds. 35 | #' @param precision A numerical value representing the precision. The default precision 36 | #' is set to 4 digits. 37 | #' 38 | #' @return A vector of Implied odds. 39 | #' 40 | #' @examples 41 | #' US2Implied(c(-250, 600, 137, -110)) 42 | US2Implied <- function(american, precision = 4) { 43 | US2ImpliedHelper <- function(one_american, precisionHelper = precision) { 44 | if (one_american >= 100) { 45 | return(round(100/(one_american + 100), precisionHelper)) 46 | } else if (one_american <= -100) { 47 | return(round((-1.0 * one_american)/(-1.0 * one_american + 100), precisionHelper)) 48 | } else { 49 | return(NA) 50 | } 51 | } 52 | 53 | return(sapply(american, US2ImpliedHelper)) 54 | } 55 | 56 | #' @title Calculate fair lines from a pair of American odds 57 | #' 58 | #' @description Returns a vector of fair lines 59 | #' 60 | #' @param pair A vector representing the two outcomes in American odds format. 61 | #' @param precision A numerical value representing the precision. The default precision 62 | #' is set to 4 digits. 63 | #' 64 | #' @return A vector representing the fair line 65 | #' 66 | #' @examples 67 | #' US2Fair(c(265, -375)) 68 | #' 69 | #' US2Fair(c(150, -200)) 70 | US2Fair <- function(pair, precision = 4) { 71 | prob1 <- calculateImpliedProbPair(pair, precision)[1] 72 | prob2 <- calculateImpliedProbPair(pair, precision)[2] 73 | 74 | return(c(Implied2US(prob1, precision), Implied2US(prob2, precision))) 75 | } 76 | 77 | #' @title Convert Decimal to American odds 78 | #' 79 | #' @description Returns a vector of American odds 80 | #' 81 | #' @param decimal A vector of Decimal odds. 82 | #' @param precision A numerical value representing the precision. The default precision 83 | #' is set to 4 digits. 84 | #' 85 | #' @return A vector of American odds. 86 | #' 87 | #' @examples 88 | #' Dec2US(c(3.17, 2.14, 2.01, 1.67)) 89 | Dec2US <- function(decimal, precision = 4) { 90 | Dec2USHelper <- function(one_decimal, precisionHelper = precision) { 91 | if (one_decimal >= 2) { 92 | return(round((one_decimal - 1) * 100, precisionHelper)) 93 | } else if ((one_decimal < 2) & (one_decimal >= 1)) { 94 | return(round(-100/(one_decimal - 1), precisionHelper)) 95 | } else { 96 | return(NA) 97 | } 98 | } 99 | 100 | return(sapply(decimal, Dec2USHelper)) 101 | } 102 | 103 | #' @title Convert Decimal to Implied odds 104 | #' 105 | #' @description Returns a vector of Implied odds 106 | #' 107 | #' @param decimal A vector of Decimal odds. 108 | #' @param precision A numerical value representing the precision. The default precision 109 | #' is set to 4 digits. 110 | #' 111 | #' @return A vector of Implied odds. 112 | #' 113 | #' @examples 114 | #' Dec2Implied(c(3.17, 2.14, 2.01, 1.67)) 115 | Dec2Implied <- function(decimal, precision = 4) { 116 | Dec2ImpliedHelper <- function(one_decimal, precisionHelper = precision) { 117 | if ((one_decimal >= 2) | (one_decimal < 2) & (one_decimal > 1)) { 118 | american <- Dec2US(one_decimal) 119 | return(round(US2Implied(american), precisionHelper)) 120 | } else { 121 | return(NA) 122 | } 123 | } 124 | 125 | return(sapply(decimal, Dec2ImpliedHelper)) 126 | } 127 | 128 | #' @title Calculate fair lines from a pair of Decimal odds 129 | #' 130 | #' @description Returns a vector of fair lines 131 | #' 132 | #' @param pair A vector representing the two outcomes in Decimal odds format. 133 | #' @param precision A numerical value representing the precision. The default precision 134 | #' is set to 4 digits. 135 | #' 136 | #' @return A vector representing the fair line 137 | #' 138 | #' @examples 139 | #' Dec2Fair(c(2.14, 1.86)) 140 | #' 141 | #' Dec2Fair(c(4.00, 1.51)) 142 | Dec2Fair <- function(pair, precision = 4) { 143 | pair_in_us <- Dec2US(pair) 144 | prob1 <- calculateImpliedProbPair(pair_in_us, precision)[1] 145 | prob2 <- calculateImpliedProbPair(pair_in_us, precision)[2] 146 | 147 | return(c(Implied2US(prob1, precision), Implied2US(prob2, precision))) 148 | } 149 | 150 | #' @title Convert Implied to American odds 151 | #' 152 | #' @description Returns a vector of American odds 153 | #' 154 | #' @param decimal A vector of Implied odds. 155 | #' @param precision A numerical value representing the precision. The default precision 156 | #' is set to 4 digits. 157 | #' 158 | #' @return A vector of American odds. 159 | #' 160 | #' @examples 161 | #' Implied2US(c(.34, .54, .88, .12)) 162 | Implied2US <- function(implied, precision = 4) { 163 | Implied2USHelper <- function(one_implied, precisionHelper = precision) { 164 | if ((one_implied >= 0.5) & (one_implied <= 1)) { 165 | return(round((100 * one_implied)/(-1 + one_implied), precisionHelper)) 166 | } else if ((one_implied >= 0) & (one_implied < 0.5)) { 167 | return(round((100 - 100 * one_implied)/one_implied, precisionHelper)) 168 | } else { 169 | return(NA) 170 | } 171 | } 172 | 173 | return(sapply(implied, Implied2USHelper)) 174 | } 175 | 176 | #' @title Convert Implied to Decimal odds 177 | #' 178 | #' @description Returns a vector of Decimal odds 179 | #' 180 | #' @param decimal A vector of Implied odds. 181 | #' @param precision A numerical value representing the precision. The default precision 182 | #' is set to 4 digits. 183 | #' 184 | #' @return A vector of Decimal odds. 185 | #' 186 | #' @examples 187 | #' Implied2Dec(c(.34, .54, .88, .12)) 188 | Implied2Dec <- function(implied, precision = 2) { 189 | Implied2DecHelper <- function(one_implied, precisionHelper = precision) { 190 | if ((one_implied >= 0) & (one_implied <= 1)) { 191 | american <- Implied2US(one_implied) 192 | return(round(US2Dec(american), precisionHelper)) 193 | } else { 194 | return(NA) 195 | } 196 | } 197 | 198 | return(sapply(implied, Implied2DecHelper)) 199 | } 200 | 201 | #' @title Convert American to Decimal and Implied odds 202 | #' 203 | #' @description Returns a tibble where each row has American, Decimal, and Implied 204 | #' odds 205 | #' 206 | #' @param american A vector of American odds. 207 | #' @param precision A numerical value representing the precision. The default precision 208 | #' is set to 4 digits. 209 | #' 210 | #' @return A tibble of American, Decimal, and Implied odds 211 | #' 212 | #' @examples 213 | #' US2All(c(-250, 600, 137, -110)) 214 | US2All <- function(american, precision = 4) { 215 | decimal <- US2Dec(american, precision) 216 | implied <- US2Implied(american, precision) 217 | return(tibble("American" = american, 218 | "Decimal" = decimal, 219 | "Implied" = implied)) 220 | } 221 | 222 | #' @title Convert Decimal to American and Implied odds 223 | #' 224 | #' @description Returns a tibble where each row has American, Decimal, and Implied 225 | #' odds 226 | #' 227 | #' @param american A vector of Decimal odds. 228 | #' @param precision A numerical value representing the precision. The default precision 229 | #' is set to 4 digits. 230 | #' 231 | #' @return A tibble of American, Decimal, and Implied odds 232 | #' 233 | #' @examples 234 | #' Dec2All(c(3.17, 2.14, 2.01, 1.67)) 235 | Dec2All <- function(decimal, precision = 4) { 236 | american <- Dec2US(decimal, precision) 237 | implied <- Dec2Implied(decimal, precision) 238 | return(tibble("American" = american, 239 | "Decimal" = decimal, 240 | "Implied" = implied)) 241 | } 242 | 243 | #' @title Convert Implied to Decimal and Implied odds 244 | #' 245 | #' @description Returns a tibble where each row has American, Decimal, and Implied 246 | #' odds 247 | #' 248 | #' @param implied A vector of Implied odds. 249 | #' @param precision A numerical value representing the precision. The default precision 250 | #' is set to 4 digits. 251 | #' 252 | #' @return A tibble of American, Decimal, and Implied odds 253 | #' 254 | #' @examples 255 | #' Implied2All(c(.34, .54, .88, .12)) 256 | Implied2All <- function(implied, precision = 4) { 257 | american <- Implied2US(implied, precision) 258 | decimal <- Implied2Dec(implied, precision) 259 | return(tibble("American" = american, 260 | "Decimal" = decimal, 261 | "Implied" = implied)) 262 | } 263 | 264 | -------------------------------------------------------------------------------- /R/mlb_data.R: -------------------------------------------------------------------------------- 1 | #' 2019 MLB Historical Vegas Lines 2 | #' 3 | #' Dataset containing historical 2019 MLB Vegas lines on moneylines, point totals, 4 | #' and point spreads. 5 | #' 6 | #' @docType data 7 | #' @format 8 | #' \describe{ 9 | #' \item{date}{Date of MLB game} 10 | #' \item{game}{Game number} 11 | #' \item{away_abbrev}{Abbreviation of away team} 12 | #' \item{home_abbrev}{Abbreviation of home team} 13 | #' \item{away_name}{Away team full name} 14 | #' \item{home_name}{Home team full name} 15 | #' \item{away_score}{Away team score} 16 | #' \item{home_score}{Home team score} 17 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 18 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 19 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 20 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 21 | #' \item{away_run_line}{Away team run line} 22 | #' \item{home_run_line}{Home team run line} 23 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 24 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 25 | #' \item{open_ou_line}{Opening over/under line} 26 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 27 | #' \item{close_ou_line}{Closing over/under line} 28 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 29 | #' } 30 | #' 31 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 32 | "mlb_odds_2019" 33 | 34 | #' 2018 MLB Historical Vegas Lines 35 | #' 36 | #' Dataset containing historical 2018 MLB Vegas lines on moneylines, point totals, 37 | #' and point spreads. 38 | #' 39 | #' @docType data 40 | #' @format 41 | #' \describe{ 42 | #' \item{date}{Date of MLB game} 43 | #' \item{game}{Game number} 44 | #' \item{away_abbrev}{Abbreviation of away team} 45 | #' \item{home_abbrev}{Abbreviation of home team} 46 | #' \item{away_name}{Away team full name} 47 | #' \item{home_name}{Home team full name} 48 | #' \item{away_score}{Away team score} 49 | #' \item{home_score}{Home team score} 50 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 51 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 52 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 53 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 54 | #' \item{away_run_line}{Away team run line} 55 | #' \item{home_run_line}{Home team run line} 56 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 57 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 58 | #' \item{open_ou_line}{Opening over/under line} 59 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 60 | #' \item{close_ou_line}{Closing over/under line} 61 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 62 | #' } 63 | #' 64 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 65 | "mlb_odds_2018" 66 | 67 | #' 2017 MLB Historical Vegas Lines 68 | #' 69 | #' Dataset containing historical 2017 MLB Vegas lines on moneylines, point totals, 70 | #' and point spreads. 71 | #' 72 | #' @docType data 73 | #' @format 74 | #' \describe{ 75 | #' \item{date}{Date of MLB game} 76 | #' \item{game}{Game number} 77 | #' \item{away_abbrev}{Abbreviation of away team} 78 | #' \item{home_abbrev}{Abbreviation of home team} 79 | #' \item{away_name}{Away team full name} 80 | #' \item{home_name}{Home team full name} 81 | #' \item{away_score}{Away team score} 82 | #' \item{home_score}{Home team score} 83 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 84 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 85 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 86 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 87 | #' \item{away_run_line}{Away team run line} 88 | #' \item{home_run_line}{Home team run line} 89 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 90 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 91 | #' \item{open_ou_line}{Opening over/under line} 92 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 93 | #' \item{close_ou_line}{Closing over/under line} 94 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 95 | #' } 96 | #' 97 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 98 | "mlb_odds_2017" 99 | 100 | #' 2016 MLB Historical Vegas Lines 101 | #' 102 | #' Dataset containing historical 2016 MLB Vegas lines on moneylines, point totals, 103 | #' and point spreads. 104 | #' 105 | #' @docType data 106 | #' @format 107 | #' \describe{ 108 | #' \item{date}{Date of MLB game} 109 | #' \item{game}{Game number} 110 | #' \item{away_abbrev}{Abbreviation of away team} 111 | #' \item{home_abbrev}{Abbreviation of home team} 112 | #' \item{away_name}{Away team full name} 113 | #' \item{home_name}{Home team full name} 114 | #' \item{away_score}{Away team score} 115 | #' \item{home_score}{Home team score} 116 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 117 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 118 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 119 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 120 | #' \item{away_run_line}{Away team run line} 121 | #' \item{home_run_line}{Home team run line} 122 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 123 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 124 | #' \item{open_ou_line}{Opening over/under line} 125 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 126 | #' \item{close_ou_line}{Closing over/under line} 127 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 128 | #' } 129 | #' 130 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 131 | "mlb_odds_2016" 132 | 133 | #' 2015 MLB Historical Vegas Lines 134 | #' 135 | #' Dataset containing historical 2015 MLB Vegas lines on moneylines, point totals, 136 | #' and point spreads. 137 | #' 138 | #' @docType data 139 | #' @format 140 | #' \describe{ 141 | #' \item{date}{Date of MLB game} 142 | #' \item{game}{Game number} 143 | #' \item{away_abbrev}{Abbreviation of away team} 144 | #' \item{home_abbrev}{Abbreviation of home team} 145 | #' \item{away_name}{Away team full name} 146 | #' \item{home_name}{Home team full name} 147 | #' \item{away_score}{Away team score} 148 | #' \item{home_score}{Home team score} 149 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 150 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 151 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 152 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 153 | #' \item{away_run_line}{Away team run line} 154 | #' \item{home_run_line}{Home team run line} 155 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 156 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 157 | #' \item{open_ou_line}{Opening over/under line} 158 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 159 | #' \item{close_ou_line}{Closing over/under line} 160 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 161 | #' } 162 | #' 163 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 164 | "mlb_odds_2015" 165 | 166 | 167 | #' 2014 MLB Historical Vegas Lines 168 | #' 169 | #' Dataset containing historical 2014 MLB Vegas lines on moneylines, point totals, 170 | #' and point spreads. 171 | #' 172 | #' @docType data 173 | #' @format 174 | #' \describe{ 175 | #' \item{date}{Date of MLB game} 176 | #' \item{game}{Game number} 177 | #' \item{away_abbrev}{Abbreviation of away team} 178 | #' \item{home_abbrev}{Abbreviation of home team} 179 | #' \item{away_name}{Away team full name} 180 | #' \item{home_name}{Home team full name} 181 | #' \item{away_score}{Away team score} 182 | #' \item{home_score}{Home team score} 183 | #' \item{away_open_ml}{Away team opening moneyline in American odds format} 184 | #' \item{home_open_ml}{Home team opening moneyline in American odds format} 185 | #' \item{away_close_ml}{Away team closing moneyline in American odds format} 186 | #' \item{home_close_ml}{Home team closing moneyline in American odds format} 187 | #' \item{away_run_line}{Away team run line} 188 | #' \item{home_run_line}{Home team run line} 189 | #' \item{away_run_line_odds}{Away team run line odds in American odds format} 190 | #' \item{home_run_line_odds}{Home team run line odds in American odds format} 191 | #' \item{open_ou_line}{Opening over/under line} 192 | #' \item{open_ou_odds}{Opening over/under odds in American odds format} 193 | #' \item{close_ou_line}{Closing over/under line} 194 | #' \item{close_ou_odds}{Closing over/under odds in American odds format} 195 | #' } 196 | #' 197 | #' @source \url{https://sportsbookreviewsonline.com/scoresoddsarchives/mlb/} 198 | "mlb_odds_2014" 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # bettingtools 6 | 7 | 8 | 9 | 10 | The bettingtools package has functions related to working with sports betting lines. First, we provide functions to work with American, Decimal, and Implied odds in the tidy format. Next, we provide functions to work with sports betting lines such as calculating zero-vig probabilities, theoretical hold, and the optimal single Kelly stake bet. Furthermore, the package comes with tidy datasets for historical NBA (work in progress), MLB, NFL (work in progress), and NHL (work in progress) Vegas lines for moneylines, point totals, and point spreads. 11 | 12 | These functions take inspiration from posts by user "Ganchrow" on the Sportsbook Review forum. 13 | 14 | ## Installation 15 | 16 | ``` r 17 | remotes::install_github("pwu97/bettingtools") 18 | ``` 19 | 20 | ## Analyze 2019 MLB Historical Odds 21 | 22 | Inside the `bettingtools` package is the 2019 MLB historical odds dataset, `mlb_odds_2019`, where we can utilize functions in this package to answer interesting questions such as what was the average moneyline for games in the 2019 MLB season using a bar chart. 23 | 24 | 25 | ```r 26 | library(bettingtools) 27 | library(teamcolors) 28 | library(tidyverse) 29 | library(forcats) 30 | theme_set(theme_light()) 31 | 32 | mlb_colors <- teamcolors::teamcolors %>% 33 | filter(league == "mlb") %>% 34 | rename(team = name) %>% 35 | select(team, primary) 36 | 37 | away_team_lines <- mlb_odds_2019 %>% 38 | select(away_name, away_close_ml) %>% 39 | mutate(implied_ml = US2Implied(away_close_ml)) %>% 40 | rename(team = away_name) %>% 41 | select(team, implied_ml) 42 | 43 | home_team_lines <- mlb_odds_2019 %>% 44 | select(home_name, home_close_ml) %>% 45 | mutate(implied_ml = US2Implied(home_close_ml)) %>% 46 | rename(team = home_name) %>% 47 | select(team, implied_ml) 48 | 49 | avg_2019_mlb_ml <- rbind(away_team_lines, home_team_lines) %>% 50 | group_by(team) %>% 51 | summarize(avg_implied_ml = mean(implied_ml), 52 | avg_ml_line = Implied2US(mean(implied_ml))) %>% 53 | left_join(mlb_colors, by = "team") %>% 54 | arrange(desc(avg_ml_line)) %>% 55 | slice(-n()) %>% 56 | mutate(team = fct_reorder(team, avg_ml_line, .desc = TRUE), 57 | primary = fct_reorder(primary, avg_ml_line, .desc = TRUE)) %>% 58 | ungroup() 59 | 60 | # Generate moneyline bar chart 61 | mlb_2019_ml_chart <- avg_2019_mlb_ml %>% 62 | ggplot(aes(x = team, y = avg_ml_line, label = avg_ml_line)) + 63 | geom_col(fill = avg_2019_mlb_ml$primary) + 64 | coord_flip() + 65 | labs(y = "Average Moneyline", 66 | x = "Team Name", 67 | title = "What was the average moneyline for each team during\nthe 2019 MLB Season?") 68 | ``` 69 | 70 | ![2019 Average MLB Moneylines](mlb_2019_ml_chart.jpg) 71 | 72 | We can also, for example, calculate the average over/under movement, the average away/home closing moneyline, and whether the closing moneylines are sharper than the opening moneylines given by Vegas, on average. We can see that home teams tended to be favored more often, on average, than away teams. Furthermore, there is also evidence that closing moneylines were indeed sharper than opening moneylines by about 2%. 73 | 74 | 75 | ```r 76 | # What is the average O/U movement for games in which there was line movement? 77 | mlb_odds_2019 %>% 78 | mutate(ou_movement = close_ou_line - open_ou_line) %>% 79 | filter(ou_movement != 0) %>% 80 | summarize(mean_ou_movement = mean(ou_movement)) %>% 81 | pull(mean_ou_movement) 82 | #> [1] -0.01659751 83 | 84 | # What is the average away closing moneyline? 85 | mlb_odds_2019 %>% 86 | mutate(away_implied = US2Implied(away_close_ml)) %>% 87 | summarize(avg_away_closing_ml = Implied2US(mean(away_implied))) %>% 88 | pull(avg_away_closing_ml) 89 | #> [1] 109.7098 90 | 91 | # What is the average home closing moneyline? 92 | mlb_odds_2019 %>% 93 | mutate(home_implied = US2Implied(home_close_ml)) %>% 94 | summarize(avg_home_closing_ml = Implied2US(mean(home_implied))) %>% 95 | pull(avg_home_closing_ml) 96 | #> [1] -119.2058 97 | 98 | # Are closing moneylines sharper than opening moneylines? 99 | mlb_odds_2019 %>% 100 | mutate(fav_team_won_open = ifelse((((away_score > home_score) & 101 | (away_open_ml < home_open_ml)) | 102 | ((away_score < home_score) & 103 | (away_open_ml > home_open_ml))), 1, 0), 104 | fav_team_won_close = ifelse((((away_score > home_score) & 105 | (away_close_ml < home_close_ml)) | 106 | ((away_score < home_score) & 107 | (away_close_ml > home_close_ml))), 1, 0)) %>% 108 | summarize(pct_fav_won_open = mean(fav_team_won_open, na.rm = TRUE), 109 | pct_fav_won_close = mean(fav_team_won_close, na.rm = TRUE), 110 | pct_diff = pct_fav_won_close - pct_fav_won_open) 111 | #> # A tibble: 1 x 3 112 | #> pct_fav_won_open pct_fav_won_close pct_diff 113 | #> 114 | #> 1 0.573 0.592 0.0191 115 | ``` 116 | 117 | ## Calculate single Kelly stake 118 | 119 | We can calculate the percentage of one's bankroll one should bet to maximize the expected growth of one's bankroll on a single bet. Given an expected win probability, payout odds, and an optional Kelly multiplier factor, we can calculate one's optimal single Kelly stake. Note that default odds for the expected win probability is implied probability ("prob") and the default odds for the payout is in decimal ("dec"). We can change them accordingly to how we see fit by specifying additional parameters in our function ("prob", "dec", "us"). 120 | 121 | 122 | ```r 123 | library(bettingtools) 124 | 125 | calculateKellyStake(0.53, 1.92) 126 | #> [1] 0.0191 127 | 128 | calculateKellyStake(0.41, 2.56) 129 | #> [1] 0.0318 130 | 131 | # Getting a bet at +150 when it is expected to hit at -120 132 | calculateKellyStake(-120, 150, expected_odds = "us", payout_odds = "us") 133 | #> [1] 0.2425 134 | 135 | # Getting a bet at -150 when expected probability is 70% at a 10% kelly multiplier. 136 | calculateKellyStake(0.70, -150, kelly_multiplier = 0.1, payout_odds = "us") 137 | #> [1] 0.0252 138 | 139 | # Optimal move is to not place a bet 140 | calculateKellyStake(0.26, -110, payout_odds = "us") 141 | #> [1] 0 142 | ``` 143 | 144 | ## Calculate all possible win-loss outcomes given probabilities 145 | 146 | We return a tibble where each row is one possible outcome we can observe from a list of given probabilities. 147 | 148 | 149 | ```r 150 | calculateWinRanges(c(.1, .4, .88, .47)) 151 | #> # A tibble: 5 x 3 152 | #> W L Probability 153 | #> 154 | #> 1 0 4 0.0343 155 | #> 2 1 3 0.309 156 | #> 3 2 2 0.445 157 | #> 4 3 1 0.195 158 | #> 5 4 0 0.0165 159 | 160 | calculateWinRanges(c(.12, .462, .29)) 161 | #> # A tibble: 4 x 3 162 | #> W L Probability 163 | #> 164 | #> 1 0 3 0.336 165 | #> 2 1 2 0.472 166 | #> 3 2 1 0.176 167 | #> 4 3 0 0.0161 168 | 169 | calculateWinRanges(c(.6, .6, .6, .6)) 170 | #> # A tibble: 5 x 3 171 | #> W L Probability 172 | #> 173 | #> 1 0 4 0.0256 174 | #> 2 1 3 0.154 175 | #> 3 2 2 0.346 176 | #> 4 3 1 0.346 177 | #> 5 4 0 0.130 178 | ``` 179 | 180 | 181 | ## Calculate zero-vig implied probabilities 182 | 183 | We can calculate the zero-vig implied probabilities of a vector of lines. The default precision is set to 4 digits. Note that we can set the precision. 184 | 185 | 186 | ```r 187 | calculateZeroVigProb(c(200, -180, -450, 800)) 188 | #> [1] 0.3333 0.6429 0.8182 0.1111 189 | 190 | calculateZeroVigProb(-237) 191 | #> [1] 0.7033 192 | 193 | calculateZeroVigProb(-237, precision = 7) 194 | #> [1] 0.7032641 195 | ``` 196 | 197 | ## Calculate implied probabilities for two-outcome line set 198 | 199 | We can calculate the implied probabilities for two or more lines by first calculating the zero-vig implied probabilities for both of them, and then normalizing them. Again, we can set the precision. 200 | 201 | 202 | ```r 203 | calculateNormalizedImplied(c(1000, -800), precision = 7) 204 | #> [1] 0.0927835 0.9054843 205 | 206 | calculateNormalizedImplied(c(200, -220)) 207 | #> [1] 0.3265 0.6780 208 | 209 | calculateNormalizedImplied(c(427, -213, 336)) 210 | #> [1] 0.1726 0.6286 0.2226 211 | ``` 212 | 213 | ## Calculate theoretical hold 214 | 215 | We can calculate the theoretical hold for a two-outcome line set. This corresponds to the profit a sportsbook would expect to make were a player to bet on either side of an event with all else being equal. Contrary to popular belief, larger nominal spreads doesn't necessarily mean more profit for bookies. 216 | 217 | 218 | ```r 219 | calculateTheoreticalHold(c(-110, -110)) 220 | #> [1] 0.0454 221 | 222 | calculateTheoreticalHold(c(-1500, 875)) 223 | #> [1] 0.0386 224 | 225 | calculateTheoreticalHold(c(-1500, 875), precision = 7) 226 | #> [1] 0.038554 227 | ``` 228 | 229 | ## Convert American or Decimal Odds to Zero-Vig Fair Odds 230 | 231 | We can convert a pair of American or Decimal odds to the fair zero-vig counterpart by calculating the individual zero-vig probabilities and then normalizing them. 232 | 233 | 234 | ```r 235 | US2Fair(c(265, -375)) 236 | #> [1] 288.1988 -288.1988 237 | 238 | US2Fair(c(150, -200)) 239 | #> [1] 166.6667 -166.6667 240 | 241 | Dec2Fair(c(2.14, 1.86)) 242 | #> [1] 115.0538 -115.0538 243 | 244 | Dec2Fair(c(4.00, 1.51)) 245 | #> [1] 264.9635 -264.9635 246 | ``` 247 | 248 | ## Converting between American, Decimal, and Implied Odds 249 | 250 | We can convert between American, Decimal, and Implied Odds. 251 | 252 | 253 | ```r 254 | US2Implied(c(-250, 600, 137, -110)) 255 | #> [1] 0.7143 0.1429 0.4219 0.5238 256 | 257 | US2Dec(c(-250, 600, 137, -110)) 258 | #> [1] 1.40 7.00 2.37 1.91 259 | 260 | US2All(c(-250, 600, 137, -110)) 261 | #> # A tibble: 4 x 3 262 | #> American Decimal Implied 263 | #> 264 | #> 1 -250 1.4 0.714 265 | #> 2 600 7 0.143 266 | #> 3 137 2.37 0.422 267 | #> 4 -110 1.91 0.524 268 | 269 | Dec2Implied(c(3.17, 2.14, 2.01, 1.67)) 270 | #> [1] 0.3155 0.4673 0.4975 0.5988 271 | 272 | Dec2US(c(3.17, 2.14, 2.01, 1.67)) 273 | #> [1] 217.0000 114.0000 101.0000 -149.2537 274 | 275 | Dec2All(c(3.17, 2.14, 2.01, 1.67)) 276 | #> # A tibble: 4 x 3 277 | #> American Decimal Implied 278 | #> 279 | #> 1 217 3.17 0.316 280 | #> 2 114 2.14 0.467 281 | #> 3 101 2.01 0.498 282 | #> 4 -149. 1.67 0.599 283 | 284 | Implied2Dec(c(.34, .54, .88, .12)) 285 | #> [1] 2.94 1.85 1.14 8.33 286 | 287 | Implied2US(c(.34, .54, .88, .12)) 288 | #> [1] 194.1176 -117.3913 -733.3333 733.3333 289 | 290 | Implied2All(c(.34, .54, .88, .12)) 291 | #> # A tibble: 4 x 3 292 | #> American Decimal Implied 293 | #> 294 | #> 1 194. 2.94 0.34 295 | #> 2 -117. 1.85 0.54 296 | #> 3 -733. 1.14 0.88 297 | #> 4 733. 8.33 0.12 298 | ``` 299 | 300 | -------------------------------------------------------------------------------- /data-raw/MLB_Datasets.R: -------------------------------------------------------------------------------- 1 | #################################################################################### 2 | # Load packages 3 | library(Lahman) 4 | library(janitor) 5 | library(dplyr) 6 | library(readr) 7 | 8 | #################################################################################### 9 | # Create team abbreviation and full name data frame 10 | team_abb <- Teams %>% 11 | data.frame() %>% 12 | filter(yearID == 2018) %>% 13 | select(teamID, name) %>% 14 | rename("Team" = "teamID", 15 | "Name" = "name") 16 | team_abb$Team <- as.character(team_abb$Team) 17 | team_abb$Team[5] <- "CWS" 18 | team_abb$Team[6] <- "CUB" 19 | team_abb$Team[12] <- "KAN" 20 | team_abb$Team[14] <- "LAD" 21 | team_abb$Team[18] <- "NYY" 22 | team_abb$Team[19] <- "NYM" 23 | team_abb$Team[23] <- "SDG" 24 | team_abb$Team[25] <- "SFO" 25 | team_abb$Team[26] <- "STL" 26 | team_abb$Team[27] <- "TAM" 27 | team_abb$Name[13] <- "Los Angeles Angels" 28 | 29 | #################################################################################### 30 | # Functions to convert dates in historical MLB lines data frame 31 | convert_date_to_full_2019 <- function(date) { 32 | month <- date %/% 100 33 | day <- date - 100 * (date %/% 100) 34 | 35 | parse_date(paste("2019", month, day), "%Y %m %d") 36 | } 37 | 38 | convert_date_to_full_2018 <- function(date) { 39 | month <- date %/% 100 40 | day <- date - 100 * (date %/% 100) 41 | 42 | parse_date(paste("2018", month, day), "%Y %m %d") 43 | } 44 | 45 | convert_date_to_full_2017 <- function(date) { 46 | month <- date %/% 100 47 | day <- date - 100 * (date %/% 100) 48 | 49 | parse_date(paste("2017", month, day), "%Y %m %d") 50 | } 51 | 52 | convert_date_to_full_2016 <- function(date) { 53 | month <- date %/% 100 54 | day <- date - 100 * (date %/% 100) 55 | 56 | parse_date(paste("2016", month, day), "%Y %m %d") 57 | } 58 | 59 | convert_date_to_full_2015 <- function(date) { 60 | month <- date %/% 100 61 | day <- date - 100 * (date %/% 100) 62 | 63 | parse_date(paste("2015", month, day), "%Y %m %d") 64 | } 65 | 66 | convert_date_to_full_2014 <- function(date) { 67 | month <- date %/% 100 68 | day <- date - 100 * (date %/% 100) 69 | 70 | parse_date(paste("2014", month, day), "%Y %m %d") 71 | } 72 | 73 | convert_date_to_full_2013 <- function(date) { 74 | month <- date %/% 100 75 | day <- date - 100 * (date %/% 100) 76 | 77 | parse_date(paste("2013", month, day), "%Y %m %d") 78 | } 79 | 80 | #################################################################################### 81 | # Create MLB 2019 odds data frame 82 | mlb_odds_2019 <- read_csv("~/Downloads/mlb_odds_2019.csv") %>% 83 | left_join(team_abb, by = "Team") %>% 84 | mutate(Date = convert_date_to_full_2019(Date), 85 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 86 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 87 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 88 | rename(Open_ML = Open, 89 | Close_ML = Close, 90 | `Open OU Line` = `Open OU`, 91 | `Close OU Line` = `Close OU`, 92 | run_line_odds = `X19`, 93 | open_ou_odds = `X21`, 94 | close_ou_odds = `X23`) %>% 95 | clean_names() %>% 96 | mutate(open_ml = as.numeric(open_ml)) %>% 97 | group_by(date, game) %>% 98 | mutate(home_team = rev(team), 99 | home_name = rev(name), 100 | home_final = rev(final), 101 | home_open_ml = rev(open_ml), 102 | home_close_ml = rev(close_ml), 103 | home_run_line = rev(run_line), 104 | home_run_line_odds = rev(run_line_odds), 105 | open_ou_line = rev(open_ou_line), 106 | open_ou_odds = rev(open_ou_odds), 107 | close_ou_line = rev(close_ou_line), 108 | close_ou_odds = rev(close_ou_odds)) %>% 109 | ungroup() %>% 110 | select(date, game, away_abbrev = team, home_abbrev = home_team, 111 | away_name = name, home_name, away_score = final, 112 | home_score = home_final, 113 | away_open_ml = open_ml, home_open_ml, 114 | away_close_ml = close_ml, home_close_ml, 115 | away_run_line = run_line, home_run_line, 116 | away_run_line_odds = run_line_odds, home_run_line_odds, 117 | open_ou_line, open_ou_line, 118 | open_ou_odds, open_ou_odds, 119 | close_ou_line, close_ou_line, 120 | close_ou_odds, 121 | close_ou_odds) %>% 122 | filter(row_number() %% 2 == 1) %>% 123 | tibble() 124 | 125 | #################################################################################### 126 | # Create MLB 2018 odds data frame 127 | mlb_odds_2018 <- read_csv("~/Downloads/mlb_odds_2018.csv") %>% 128 | left_join(team_abb, by = "Team") %>% 129 | mutate(Date = convert_date_to_full_2018(Date), 130 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 131 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 132 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 133 | rename(Open_ML = Open, 134 | Close_ML = Close, 135 | `Open OU Line` = `Open OU`, 136 | `Close OU Line` = `Close OU`, 137 | run_line_odds = `X19`, 138 | open_ou_odds = `X21`, 139 | close_ou_odds = `X23`) %>% 140 | clean_names() %>% 141 | mutate(open_ml = as.numeric(open_ml)) %>% 142 | group_by(date, game) %>% 143 | mutate(home_team = rev(team), 144 | home_name = rev(name), 145 | home_final = rev(final), 146 | home_open_ml = rev(open_ml), 147 | home_close_ml = rev(close_ml), 148 | home_run_line = rev(run_line), 149 | home_run_line_odds = rev(run_line_odds), 150 | open_ou_line = rev(open_ou_line), 151 | open_ou_odds = rev(open_ou_odds), 152 | close_ou_line = rev(close_ou_line), 153 | close_ou_odds = rev(close_ou_odds)) %>% 154 | ungroup() %>% 155 | select(date, game, away_abbrev = team, home_abbrev = home_team, 156 | away_name = name, home_name, away_score = final, 157 | home_score = home_final, 158 | away_open_ml = open_ml, home_open_ml, 159 | away_close_ml = close_ml, home_close_ml, 160 | away_run_line = run_line, home_run_line, 161 | away_run_line_odds = run_line_odds, home_run_line_odds, 162 | open_ou_line, open_ou_line, 163 | open_ou_odds, open_ou_odds, 164 | close_ou_line, close_ou_line, 165 | close_ou_odds, 166 | close_ou_odds) %>% 167 | filter(row_number() %% 2 == 1) 168 | 169 | #################################################################################### 170 | # Create MLB 2017 odds data frame 171 | mlb_odds_2017 <- read_csv("~/Downloads/mlb_odds_2017.csv") %>% 172 | left_join(team_abb, by = "Team") %>% 173 | mutate(Date = convert_date_to_full_2017(Date), 174 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 175 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 176 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 177 | rename(Open_ML = Open, 178 | Close_ML = Close, 179 | `Open OU Line` = `Open OU`, 180 | `Close OU Line` = `Close OU`, 181 | run_line_odds = `X19`, 182 | open_ou_odds = `X21`, 183 | close_ou_odds = `X23`) %>% 184 | clean_names() %>% 185 | mutate(open_ml = as.numeric(open_ml)) %>% 186 | group_by(date, game) %>% 187 | mutate(home_team = rev(team), 188 | home_name = rev(name), 189 | home_final = rev(final), 190 | home_open_ml = rev(open_ml), 191 | home_close_ml = rev(close_ml), 192 | home_run_line = rev(run_line), 193 | home_run_line_odds = rev(run_line_odds), 194 | open_ou_line = rev(open_ou_line), 195 | open_ou_odds = rev(open_ou_odds), 196 | close_ou_line = rev(close_ou_line), 197 | close_ou_odds = rev(close_ou_odds)) %>% 198 | ungroup() %>% 199 | select(date, game, away_abbrev = team, home_abbrev = home_team, 200 | away_name = name, home_name, away_score = final, 201 | home_score = home_final, 202 | away_open_ml = open_ml, home_open_ml, 203 | away_close_ml = close_ml, home_close_ml, 204 | away_run_line = run_line, home_run_line, 205 | away_run_line_odds = run_line_odds, home_run_line_odds, 206 | open_ou_line, open_ou_line, 207 | open_ou_odds, open_ou_odds, 208 | close_ou_line, close_ou_line, 209 | close_ou_odds, 210 | close_ou_odds) %>% 211 | filter(row_number() %% 2 == 1) 212 | # Edge case 213 | mlb_odds_2017$away_name[is.na(mlb_odds_2017$away_name)] <- "Los Angeles Dodgers" 214 | mlb_odds_2017$home_name[is.na(mlb_odds_2017$home_name)] <- "Los Angeles Dodgers" 215 | 216 | #################################################################################### 217 | # Create MLB 2016 odds data frame 218 | mlb_odds_2016 <- read_csv("~/Downloads/mlb_odds_2016.csv") %>% 219 | left_join(team_abb, by = "Team") %>% 220 | mutate(Date = convert_date_to_full_2016(Date), 221 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 222 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 223 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 224 | rename(Open_ML = Open, 225 | Close_ML = Close, 226 | `Open OU Line` = `Open OU`, 227 | `Close OU Line` = `Close OU`, 228 | run_line_odds = `X19`, 229 | open_ou_odds = `X21`, 230 | close_ou_odds = `X23`) %>% 231 | clean_names() %>% 232 | mutate(open_ml = as.numeric(open_ml)) %>% 233 | group_by(date, game) %>% 234 | mutate(home_team = rev(team), 235 | home_name = rev(name), 236 | home_final = rev(final), 237 | home_open_ml = rev(open_ml), 238 | home_close_ml = rev(close_ml), 239 | home_run_line = rev(run_line), 240 | home_run_line_odds = rev(run_line_odds), 241 | open_ou_line = rev(open_ou_line), 242 | open_ou_odds = rev(open_ou_odds), 243 | close_ou_line = rev(close_ou_line), 244 | close_ou_odds = rev(close_ou_odds)) %>% 245 | ungroup() %>% 246 | select(date, game, away_abbrev = team, home_abbrev = home_team, 247 | away_name = name, home_name, away_score = final, 248 | home_score = home_final, 249 | away_open_ml = open_ml, home_open_ml, 250 | away_close_ml = close_ml, home_close_ml, 251 | away_run_line = run_line, home_run_line, 252 | away_run_line_odds = run_line_odds, home_run_line_odds, 253 | open_ou_line, open_ou_line, 254 | open_ou_odds, open_ou_odds, 255 | close_ou_line, close_ou_line, 256 | close_ou_odds, 257 | close_ou_odds) %>% 258 | filter(row_number() %% 2 == 1) 259 | # Edge case 260 | mlb_odds_2016$away_name[is.na(mlb_odds_2016$away_name)] <- "Los Angeles Dodgers" 261 | mlb_odds_2016$home_name[is.na(mlb_odds_2016$home_name)] <- "Los Angeles Dodgers" 262 | 263 | #################################################################################### 264 | # Create MLB 2015 odds data frame 265 | mlb_odds_2015 <- read_csv("~/Downloads/mlb_odds_2015.csv") %>% 266 | left_join(team_abb, by = "Team") %>% 267 | mutate(Date = convert_date_to_full_2015(Date), 268 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 269 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 270 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 271 | rename(Open_ML = Open, 272 | Close_ML = Close, 273 | `Open OU Line` = `Open OU`, 274 | `Close OU Line` = `Close OU`, 275 | run_line_odds = `X19`, 276 | open_ou_odds = `X21`, 277 | close_ou_odds = `X23`) %>% 278 | clean_names() %>% 279 | mutate(open_ml = as.numeric(open_ml)) %>% 280 | group_by(date, game) %>% 281 | mutate(home_team = rev(team), 282 | home_name = rev(name), 283 | home_final = rev(final), 284 | home_open_ml = rev(open_ml), 285 | home_close_ml = rev(close_ml), 286 | home_run_line = rev(run_line), 287 | home_run_line_odds = rev(run_line_odds), 288 | open_ou_line = rev(open_ou_line), 289 | open_ou_odds = rev(open_ou_odds), 290 | close_ou_line = rev(close_ou_line), 291 | close_ou_odds = rev(close_ou_odds)) %>% 292 | ungroup() %>% 293 | select(date, game, away_abbrev = team, home_abbrev = home_team, 294 | away_name = name, home_name, away_score = final, 295 | home_score = home_final, 296 | away_open_ml = open_ml, home_open_ml, 297 | away_close_ml = close_ml, home_close_ml, 298 | away_run_line = run_line, home_run_line, 299 | away_run_line_odds = run_line_odds, home_run_line_odds, 300 | open_ou_line, open_ou_line, 301 | open_ou_odds, open_ou_odds, 302 | close_ou_line, close_ou_line, 303 | close_ou_odds, 304 | close_ou_odds) %>% 305 | filter(row_number() %% 2 == 1) 306 | # Edge case 307 | mlb_odds_2015$away_name[is.na(mlb_odds_2015$away_name)] <- "Los Angeles Dodgers" 308 | mlb_odds_2015$home_name[is.na(mlb_odds_2015$home_name)] <- "Los Angeles Dodgers" 309 | 310 | #################################################################################### 311 | # Create MLB 2014 odds data frame 312 | mlb_odds_2014 <- read_csv("~/Downloads/mlb_odds_2014.csv") %>% 313 | left_join(team_abb, by = "Team") %>% 314 | mutate(Date = convert_date_to_full_2014(Date), 315 | Game = rep(c(1:(n()/2)), 1, each = 2)) %>% 316 | select(Date, Game, Team, Name, Final, Open, Close, `Run Line`, `X19`, 317 | `Open OU`, `X21`, `Close OU`, `X23`) %>% 318 | rename(Open_ML = Open, 319 | Close_ML = Close, 320 | `Open OU Line` = `Open OU`, 321 | `Close OU Line` = `Close OU`, 322 | run_line_odds = `X19`, 323 | open_ou_odds = `X21`, 324 | close_ou_odds = `X23`) %>% 325 | clean_names() %>% 326 | mutate(open_ml = as.numeric(open_ml)) %>% 327 | group_by(date, game) %>% 328 | mutate(home_team = rev(team), 329 | home_name = rev(name), 330 | home_final = rev(final), 331 | home_open_ml = rev(open_ml), 332 | home_close_ml = rev(close_ml), 333 | home_run_line = rev(run_line), 334 | home_run_line_odds = rev(run_line_odds), 335 | open_ou_line = rev(open_ou_line), 336 | open_ou_odds = rev(open_ou_odds), 337 | close_ou_line = rev(close_ou_line), 338 | close_ou_odds = rev(close_ou_odds)) %>% 339 | ungroup() %>% 340 | select(date, game, away_abbrev = team, home_abbrev = home_team, 341 | away_name = name, home_name, away_score = final, 342 | home_score = home_final, 343 | away_open_ml = open_ml, home_open_ml, 344 | away_close_ml = close_ml, home_close_ml, 345 | away_run_line = run_line, home_run_line, 346 | away_run_line_odds = run_line_odds, home_run_line_odds, 347 | open_ou_line, open_ou_line, 348 | open_ou_odds, open_ou_odds, 349 | close_ou_line, close_ou_line, 350 | close_ou_odds, 351 | close_ou_odds) %>% 352 | filter(row_number() %% 2 == 1) 353 | # Edge case 354 | mlb_odds_2014$away_name[is.na(mlb_odds_2014$away_name)] <- "Los Angeles Dodgers" 355 | mlb_odds_2014$home_name[is.na(mlb_odds_2014$home_name)] <- "Los Angeles Dodgers" 356 | 357 | #################################################################################### 358 | # MLB Odds Datasets 359 | usethis::use_data(mlb_odds_2019, overwrite = TRUE) 360 | usethis::use_data(mlb_odds_2018, overwrite = TRUE) 361 | usethis::use_data(mlb_odds_2017, overwrite = TRUE) 362 | usethis::use_data(mlb_odds_2016, overwrite = TRUE) 363 | usethis::use_data(mlb_odds_2015, overwrite = TRUE) 364 | usethis::use_data(mlb_odds_2014, overwrite = TRUE) 365 | --------------------------------------------------------------------------------