Skip to content

Instantly share code, notes, and snippets.

@ethankruse
Created August 3, 2023 20:35
Show Gist options
  • Save ethankruse/e242cab8b235faf4018f6e79fe21b597 to your computer and use it in GitHub Desktop.
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
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