Introduction
Some readers may want to try using the Kelly criterion, so I have included the source code in VBA and Python. Don’t care about the code being crap. For a detailed explanation of the logic, see the article below.
This is probably the last of “The Kelly Criterion in Gambling” series.
VBA
Code implementing the original Kelly criterion:
Public Function kelly(ByVal n As Long, ByRef p() As Double, ByRef alpha() As Double) As Double()
Dim i As Long
Dim sumP As Double, sumQ As Double, b As Double, minB As Double
ReDim r(n) As Double
ReDim idx(n) As Double
ReDim f(n) As Double
For i = 1 To n
r(i) = p(i) * alpha(i)
idx(i) = i
Next i
Call qsort(r, idx, 1, n)
sumP = 0
sumQ = 0
minB = 1
For i = 1 To n
sumP = sumP + p(idx(i))
sumQ = sumQ + 1# / alpha(idx(i))
If sumQ < 1# Then
b = (1# - sumP) / (1# - sumQ)
Else
b = 1
End If
If b < minB Then
minB = b
End If
Next i
For i = 1 To n
f(i) = p(i) - minB / alpha(i)
If f(i) < 0 Then f(i) = 0
Next i
kelly = f
End Function
For quicksort, we used a modified code that returns the index of the original sorted array.
Sub qsort(ByRef x() As Double, ByRef idx() As Double, ByVal l As Long, ByVal u As Long)
Dim i As Long
Dim j As Long
Dim m As Variant
Dim tmp As Variant
m = x(Int((l + u) / 2))
i = l
j = u
Do
Do While x(i) > m
i = i + 1
Loop
Do While x(j) < m
j = j - 1
Loop
If i >= j Then Exit Do
tmp = x(i)
x(i) = x(j)
x(j) = tmp
tmp = idx(i)
idx(i) = idx(j)
idx(j) = tmp
i = i + 1
j = j - 1
Loop
If (l < i - 1) Then
Call qsort(x, idx, l, i - 1)
End If
If (u > j + 1) Then
Call qsort(x, idx, j + 1, u)
End If
End Sub
If you want to call it from an Excel cell formula, you can add a facade like the one below.
Public Function kellyFacade(ByRef rngP As Range, ByRef rngAlpha As Range) As Variant
Dim i As Long, j As Long, n As Long
Dim f() As Double
n = rngP.Rows.Count
ReDim p(n) As Double
ReDim alpha(n) As Double
For i = 1 To n
p(i) = rngP.Cells(i, 1)
alpha(i) = rngAlpha.Cells(i, 1)
Next i
f = kelly(n, p, alpha)
ReDim ret(0 To UBound(f) - 1, 0 To 0) As Variant
For i = 0 To UBound(f) - 1
ret(i, 0) = f(i + 1)
Next i
kellyFacade = ret
End Function
The last is the Kelly criterion extended to a CRRA-type utility function. \(\small \gamma\) is the relative risk aversion. Simply enter the fractional Kelly ratio value (2 for half Kelly, 3 for 1/3 Kelly, etc.). We have made some efforts to come up with creative calculations, but the results will diverge when they are going to diverge.
Public Function kellyCRRA(ByVal n As Long, ByRef p() As Double, ByRef alpha() As Double, _
ByVal gamma As Double) As Double()
Dim i As Long
Dim sumP As Double, sumQ As Double, sumPQ As Double
Dim b As Double, minB As Double, minK As Double, kgamma As Double
ReDim r(n) As Double
ReDim idx(n) As Double
ReDim f(n) As Double
For i = 1 To n
r(i) = p(i) * alpha(i)
idx(i) = i
Next i
Call qsort(r, idx, 1, n)
If gamma <= 0.0 Then
For i = 1 To n
f(i) = 0
Next i
If r(1) > 1 Then f(idx(1)) = 1
Else
sumP = 0
sumQ = 0
sumPQ = 0
minB = 1
minK = 1
For i = 1 To n
sumP = sumP + p(idx(i))
sumQ = sumQ + 1# / alpha(idx(i))
If p(idx(i)) > 0 Then
sumPQ = sumPQ + Exp(Log(p(idx(i))) / gamma - Log(alpha(idx(i))) * (gamma - 1#) / gamma)
End If
If sumQ < 1# Then
kgamma = (sumPQ + ((1# - sumP) / (1# - sumQ)) ^ (1# / gamma) * (1# - sumQ))
b = ((1# - sumP) / (1# - sumQ)) ^ (1# / gamma) / kgamma
Else
b = 1
End If
If b < minB Then
minB = b
minK = kgamma
End If
Next i
For i = 1 To n
f(i) = Exp(Log(p(i)) / gamma - Log(alpha(i)) * (gamma - 1#) / gamma) / minK - minB / alpha(i)
If f(i) < 0 Then f(i) = 0
Next i
End If
kellyCRRA = f
End Function
Public Function kellyCRRAFacade(ByRef rngP As Range, ByRef rngAlpha As Range, _
ByVal gamma As Double) As Variant
Dim i As Long, j As Long, n As Long
Dim f() As Double
n = rngP.Rows.Count
ReDim p(n) As Double
ReDim alpha(n) As Double
For i = 1 To n
p(i) = rngP.Cells(i, 1)
alpha(i) = rngAlpha.Cells(i, 1)
Next i
f = kellyCRRA(n, p, alpha, gamma)
ReDim ret(0 To UBound(f) - 1, 0 To 0) As Variant
For i = 0 To UBound(f) - 1
ret(i, 0) = f(i + 1)
Next i
kellyCRRAFacade= ret
End Function
Python
Here we present a version that supports CRRA-type utility functions from the start. To use the original Kelly criterion, set gamma=1 or omit the argument. See the main function for usage.
import numpy as np
import math
def kelly(p, alpha, gamma = 1):
n = len(p)
r = [0] * n
f = [0] * n
for i in range(0, n):
r[i] = p[i] * alpha[i]
idx = np.argsort(r).tolist()
idx.reverse()
if gamma <= 0.0:
for i in range(0, n):
f[i] = 0
if r[idx[0]] > 1.0:
f[idx[0]] = 1.0
return f
sumP = 0
sumQ = 0
sumPQ = 0
minB = 1
minK = 1
for i in range(0, n):
sumP += p[idx[i]]
sumQ += 1.0 / alpha[idx[i]]
if p[idx[i]] > 0:
sumPQ += math.exp(math.log(p[idx[i]]) / gamma - math.log(alpha[idx[i]]) * (gamma - 1.0) / gamma)
if sumQ < 1.0:
kgamma = sumPQ + (((1.0 - sumP) / (1.0 - sumQ)) ** (1.0 / gamma)) * (1.0 - sumQ)
b = (((1.0 - sumP) / (1.0 - sumQ)) ** (1.0 / gamma)) / kgamma
else:
b = 1
kgamma = 1
if b < minB:
minB = b
minK = kgamma
for i in range(0, n):
f[i] = math.exp(math.log(p[i]) / gamma - math.log(alpha[i]) * (gamma - 1.0) / gamma) \
/ minK - minB / alpha[i]
if f[i] < 0 or minB == 1:
f[i] = 0
return f
if __name__ == '__main__':
p = [0.5, 0.3, 0.15, 0.05]
alpha = [1.325, 4, 5, 22]
f = kelly(p, alpha)
print(f)
Comments