Skip to content

Instantly share code, notes, and snippets.

@JeffSackmann
Created February 7, 2011 22:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save JeffSackmann/815377 to your computer and use it in GitHub Desktop.
Save JeffSackmann/815377 to your computer and use it in GitHub Desktop.
Find win expectancy and volatility given inning, out, base, run situation.
## Find win expectancy and volatility given inning, out, base, run situation.
## no. of runs that score with HR in diff. base situations
baseHr = {1: 1,
2: 2,
3: 2,
4: 3,
5: 2,
6: 3,
7: 3,
8: 4
}
tangoRunExp = {'60': {1: 0.51400000000000001, 2: 0.19400000000000001, 3: 0.14999999999999999, 4: 0.076999999999999999, 5: 0.036999999999999998, 6: 0.017000000000000001, 7: 0.0060000000000000001, 8: 0.0030000000000000001, 9: 0.001, 10: 0.001, 'm': -0.216, 'b': 0.247}, '61': {1: 0.59599999999999997, 2: 0.17599999999999999, 3: 0.13200000000000001, 4: 0.057000000000000002, 5: 0.024, 6: 0.0089999999999999993, 7: 0.0040000000000000001, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.11600000000000001, 'b': 0.40600000000000003}, '62': {1: 0.55900000000000005, 2: 0.20599999999999999, 3: 0.158, 4: 0.051999999999999998, 5: 0.017000000000000001, 6: 0.0050000000000000001, 7: 0.002, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.19900000000000001, 'b': 0.82799999999999996}, '82': {1: 0.27300000000000002, 2: 0.35499999999999998, 3: 0.17000000000000001, 4: 0.13800000000000001, 5: 0.041000000000000002, 6: 0.014999999999999999, 7: 0.0050000000000000001, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.20899999999999999, 'b': 0.78900000000000003}, '80': {1: 0.311, 2: 0.247, 3: 0.17000000000000001, 4: 0.14399999999999999, 5: 0.070999999999999994, 6: 0.031, 7: 0.012999999999999999, 8: 0.0080000000000000002, 9: 0.0030000000000000001, 10: 0.002, 'm': -0.127, 'b': 0.193}, '81': {1: 0.39700000000000002, 2: 0.24399999999999999, 3: 0.151, 4: 0.123, 5: 0.050999999999999997, 6: 0.021000000000000001, 7: 0.0080000000000000002, 8: 0.0030000000000000001, 9: 0.001, 10: 0.0, 'm': -0.14199999999999999, 'b': 0.40200000000000002}, '20': {1: 0.42399999999999999, 2: 0.29899999999999999, 3: 0.14999999999999999, 4: 0.071999999999999995, 5: 0.032000000000000001, 6: 0.012999999999999999, 7: 0.0050000000000000001, 8: 0.002, 9: 0.001, 10: 0.0, 'm': -0.27800000000000002, 'b': 0.71599999999999997}, '21': {1: 0.44400000000000001, 2: 0.32600000000000001, 3: 0.13700000000000001, 4: 0.056000000000000001, 5: 0.021999999999999999, 6: 0.0089999999999999993, 7: 0.0030000000000000001, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.26800000000000002, 'b': 0.86299999999999999}, '22': {1: 0.45300000000000001, 2: 0.374, 3: 0.11600000000000001, 4: 0.039, 5: 0.012, 6: 0.0050000000000000001, 7: 0.001, 8: 0.0, 9: 0.0, 10: 0.0, 'm': -0.19400000000000001, 'b': 0.97399999999999998}, '42': {1: 0.49399999999999999, 2: 0.23699999999999999, 3: 0.18099999999999999, 4: 0.059999999999999998, 5: 0.017999999999999999, 6: 0.0070000000000000001, 7: 0.002, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.14399999999999999, 'b': 0.84099999999999997}, '40': {1: 0.36199999999999999, 2: 0.25600000000000001, 3: 0.19400000000000001, 4: 0.104, 5: 0.048000000000000001, 6: 0.02, 7: 0.0089999999999999993, 8: 0.0040000000000000001, 9: 0.002, 10: 0.001, 'm': -0.16700000000000001, 'b': 0.45000000000000001}, '41': {1: 0.40100000000000002, 2: 0.25800000000000001, 3: 0.20300000000000001, 4: 0.083000000000000004, 5: 0.034000000000000002, 6: 0.012999999999999999, 7: 0.0050000000000000001, 8: 0.002, 9: 0.001, 10: 0.0, 'm': -0.17399999999999999, 'b': 0.66900000000000004}, '72': {1: 0.185, 2: 0.54800000000000004, 3: 0.16900000000000001, 4: 0.067000000000000004, 5: 0.023, 6: 0.0060000000000000001, 7: 0.002, 8: 0.0, 9: 0.0, 10: 0.0, 'm': -0.095000000000000001, 'b': 0.78500000000000003}, '71': {1: 0.41299999999999998, 2: 0.32800000000000001, 3: 0.13800000000000001, 4: 0.072999999999999995, 5: 0.029000000000000001, 6: 0.010999999999999999, 7: 0.0050000000000000001, 8: 0.002, 9: 0.001, 10: 0.0, 'm': -0.311, 'b': 0.47799999999999998}, '70': {1: 0.315, 2: 0.35599999999999998, 3: 0.16800000000000001, 4: 0.085999999999999993, 5: 0.043999999999999997, 6: 0.017999999999999999, 7: 0.0070000000000000001, 8: 0.0040000000000000001, 9: 0.002, 10: 0.0, 'm': -0.22900000000000001, 'b': 0.26100000000000001}, '11': {1: 0.59999999999999998, 2: 0.24299999999999999, 3: 0.097000000000000003, 4: 0.036999999999999998, 5: 0.014, 6: 0.0060000000000000001, 7: 0.002, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.29599999999999999, 'b': 0.98799999999999999}, '12': {1: 0.67300000000000004, 2: 0.222, 3: 0.070999999999999994, 4: 0.023, 5: 0.0070000000000000001, 6: 0.002, 7: 0.001, 8: 0.0, 9: 0.0, 10: 0.0, 'm': -0.16300000000000001, 'b': 1.014}, '32': {1: 0.68600000000000005, 2: 0.20399999999999999, 3: 0.072999999999999995, 4: 0.025000000000000001, 5: 0.0080000000000000002, 6: 0.002, 7: 0.001, 8: 0.0, 9: 0.0, 10: 0.0, 'm': -0.107, 'b': 0.83199999999999996}, '31': {1: 0.59399999999999997, 2: 0.23400000000000001, 3: 0.104, 4: 0.042000000000000003, 5: 0.017000000000000001, 6: 0.0060000000000000001, 7: 0.0030000000000000001, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.191, 'b': 0.69299999999999995}, '30': {1: 0.56599999999999995, 2: 0.22600000000000001, 3: 0.114, 4: 0.052999999999999999, 5: 0.023, 6: 0.01, 7: 0.0040000000000000001, 8: 0.002, 9: 0.001, 10: 0.0, 'm': -0.35799999999999998, 'b': 0.55900000000000005}, '51': {1: 0.73699999999999999, 2: 0.152, 3: 0.067000000000000004, 4: 0.027, 5: 0.010999999999999999, 6: 0.0040000000000000001, 7: 0.001, 8: 0.001, 9: 0.0, 10: 0.0, 'm': -0.27000000000000002, 'b': 0.47699999999999998}, '50': {1: 0.65400000000000003, 2: 0.185, 3: 0.088999999999999996, 4: 0.041000000000000002, 5: 0.017999999999999999, 6: 0.0080000000000000002, 7: 0.0030000000000000001, 8: 0.001, 9: 0.001, 10: 0.0, 'm': -0.37, 'b': 0.35499999999999998}, '52': {1: 0.73199999999999998, 2: 0.17699999999999999, 3: 0.059999999999999998, 4: 0.021000000000000001, 5: 0.0070000000000000001, 6: 0.002, 7: 0.001, 8: 0.0, 9: 0.0, 10: 0.0, 'm': -0.047, 'b': 0.76300000000000001}}
def getRunsInn(rpinn):
runsinn = {0: 1/((rpinn*.761)+1),
1: (rpinn*(0.761**2))/(((rpinn*.761)+1)**2)
}
for i in range(2, 11):
v = (rpinn*(0.761**2)*(((rpinn*.761) - 0.761 + 1)**(i-1)))/(((rpinn*.761)+1)**(i+1))
runsinn[i] = v
return runsinn
def getRunExp(rpinn, runsinn):
runExp = {'10': runsinn
}
for i in range(0, 3):
for j in range(1, 9):
k = str(j) + str(i)
if k == '10': continue
runExp[k] = {0: ((tangoRunExp[k]['m']*rpinn) + tangoRunExp[k]['b'])
}
for r in range(1, 11):
runExp[k][r] = ((1 - runExp[k][0])*tangoRunExp[k][r])
return runExp
def getInnWinexp(runExp):
## Chance of home team winning with zero
## outs at the beg. of each inning
innWinexp = {'101': {0: 0.5
}
}
for i in range(-25, 0):
innWinexp['101'][i] = 0
for i in range(1, 26):
innWinexp['101'][i] = 1
for i in range(9, 0, -1):
for j in range(2, 0, -1):
if j == 2: next = str(i+1) + '1'
else: next = str(i) + '2'
this = str(i) + str(j)
innWinexp[this] = {}
if j == 2:
for k in range(-25, 26):
p = 0
if i == 9 and k > 0:
innWinexp[this][k] = 1
continue
else: pass
for m in range(0, 11):
if k+m > 25: iw = 1
else: iw = innWinexp[next][k+m]
p += runExp['10'][m]*iw
innWinexp[this][k] = p
else:
for k in range(-25, 26):
p = 0
for m in range(0, 11):
if k-m < -25: iw = 0
else: iw = innWinexp[next][k-m]
p += runExp['10'][m]*iw
innWinexp[this][k] = p
return innWinexp
def getWinexp(innWinexp, runExp, inn, half, base, outs, rdiff):
if inn > 9: inn = 9
innkey = str(inn) + str(half)
if outs > 2: outs = 2
sitkey = str(base) + str(outs)
if half == 2: next = str(inn+1) + '1'
else: next = str(inn) + '2'
if sitkey == '10': ## beginning of half inning
if rdiff > 25: rdiff = 25
elif rdiff < -25: rdiff = -25
else: pass
Winexp = innWinexp[innkey][rdiff]
elif half == 1:
Winexp = 0
for i in range(10, -1, -1):
if rdiff-i < -25: iw = 0
elif rdiff-i > 25: iw = 1
else: iw = innWinexp[next][rdiff-i]
Winexp += runExp[sitkey][i]*iw
else:
Winexp = 0
for i in range(0, 11):
if rdiff-i < -25: iw = 0
elif rdiff+i > 25: iw = 1
else: iw = innWinexp[next][rdiff+i]
Winexp += runExp[sitkey][i]*iw
return Winexp
def getVol(innWinexp, runExp, inn, half, base, outs, rdiff):
## changes if strikeout:
if outs == 2:
outsK = 0
baseK = 1
if half == 1:
halfK = 2
innK = inn
else:
halfK = 1
innK = inn + 1
else:
outsK = outs + 1
baseK, halfK, innK = base, half, inn
WinexpK = getWinexp(innWinexp, runExp, innK, halfK, baseK, outsK, rdiff)
## changes if homerun
if half == 1:
rdiff -= baseHr[base]
else:
rdiff += baseHr[base]
base = 1
WinexpHr = getWinexp(innWinexp, runExp, inn, half, base, outs, rdiff)
return (abs(WinexpHr - WinexpK))/0.133
def rpgToInnWinexp(rpg):
rpinn = float(rpg)/9 ## r/inn
runsinn = getRunsInn(rpinn)
runExp = getRunExp(rpinn, runsinn)
innWinexp = getInnWinexp(runExp)
return innWinexp, runExp
####sample usage
##inn = 2
##half = 1 ## 1 = top, 2 = bottom
##base = 1 ## base situation: 1 through 8
##outs = 2
##rdiff = 2 ## run differential from home team perspective
##
##rpg = 4.5 ## runs per team-game
##innWinexp, runExp = rpgToInnWinexp(rpg)
##
##winexp = getWinexp(innWinexp, runExp, inn, half, base, outs, rdiff)
##
##vol = getVol(innWinexp, runExp, inn, half, base, outs, rdiff)
##
##print winexp, lev
@JeffSackmann
Copy link
Author

In the very unlikely event anything further happens with this, it lives in this repo:
https://github.com/JeffSackmann/baseball_misc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment