Created
August 3, 2023 20:35
-
-
Save ethankruse/e242cab8b235faf4018f6e79fe21b597 to your computer and use it in GitHub Desktop.
Profit for a sports book free bet as a function of odds and vig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import matplotlib.pyplot as plt | |
def winnings(stake: float, odds): | |
""" | |
Total return (stake + profit) for a bet with a given stake and set of | |
American odds. | |
""" | |
# turn into an array if only given a single odd. | |
single = False | |
try: | |
len(odds) | |
except TypeError: | |
odds = np.array([odds]) | |
single = True | |
# handle the negative and positive odds cases | |
profit = np.zeros_like(odds) * 1. | |
profit[odds > 0] = stake * odds[odds > 0] / 100 | |
profit[odds < 0] = stake * 100 / np.abs(odds[odds < 0]) | |
# don't return an array if input wasn't an array | |
if single: | |
return stake + profit[0] | |
else: | |
return stake + profit | |
# range of negative and positive odds we'll consider for a free bet | |
negodds = np.arange(-1000, -109, 10) | |
negprob = negodds / (negodds - 100) | |
# XXX: once you get to exactly +2000 (implied probability < 4.8%, i.e. the vig), | |
# it's impossible to maintain the same vig, so this breaks down | |
posodds = np.arange(100, 1500, 10) | |
posprob = 100 / (posodds + 100) | |
odds1 = np.concatenate((negodds, posodds)) | |
prob1 = np.concatenate((negprob, posprob)) | |
# standard value for sportsbooks | |
vig = 1/21 | |
prob2 = 1 + vig - prob1 | |
# these odds are for the favorite and will be negative | |
neg = prob2 >= ((1 + vig)/2) | |
odds2 = prob2 * 100 / (prob2-1) | |
odds2[~neg] = -100 * (prob2[~neg] - 1) / prob2[~neg] | |
# assume a $100 bet, what is the total return | |
win1 = winnings(100, odds1) | |
win2 = winnings(100, odds2) | |
# how much to stake on the hedge to match the free bet potential profit | |
hedgestake = 100 * (win1 - 100) / win2 | |
# total profit is the free bet winnings - the free bet stake - the hedge stake | |
freeprofit = win1 - 100 - hedgestake | |
plt.close('all') | |
plt.figure() | |
plt.plot(odds1[odds1 < 0], freeprofit[odds1 < 0], c='k') | |
plt.plot(odds1[odds1 > 0], freeprofit[odds1 > 0], c='k') | |
plt.xlabel('Free Bet Odds (American)') | |
plt.ylabel('Profit per $100') | |
# now try with a range of potential vigs from 0-5% | |
vigs = np.arange(0, 5, 0.1) / 100 | |
# what probability is needed to match each vig for each free bet odds | |
impliedprob2 = 1 + vigs - prob1[:, np.newaxis] | |
neg = impliedprob2 >= ((1 + vigs)/2) | |
# turn probability into odds | |
impliedodds2 = impliedprob2 * 100 / (impliedprob2-1) | |
impliedodds2[~neg] = -100 * (impliedprob2[~neg] - 1) / impliedprob2[~neg] | |
# get total winnings again | |
win1 = winnings(100, odds1) | |
win2 = winnings(100, impliedodds2) | |
# total stake and profit for each vig and free bet odds combination | |
hedgestake = 100 * (win1[:, np.newaxis] - 100) / win2 | |
freeprofit = win1[:, np.newaxis] - 100 - hedgestake | |
# 2D map of profit for the various free bet odds and vig combinations | |
plt.figure() | |
plt.pcolormesh(vigs, odds1[odds1 > 0], freeprofit[odds1 > 0, :], | |
shading='nearest') | |
cbar = plt.colorbar() | |
cbar.ax.set_ylabel('Profit per $100') | |
plt.ylabel('Free Bet Odds (American)') | |
plt.xlabel('Vig') | |
# line showing the odds of maximum profit for each vig | |
plt.plot(vigs, odds1[freeprofit.argmax(axis=0)], c='k') | |
# maximum possible profit for each vig | |
plt.figure() | |
plt.plot(vigs, freeprofit.max(axis=0), c='k') | |
plt.xlabel('Vig') | |
plt.ylabel('Maximum Free Bet Profit per $100') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment