Kelly Criterion
- Bet or invest so as to maximize (after each bet) the expected growth rate if capital, which is equivalent to maximizing the expected value of the logarithm of wealth.
\[G_{\infty} = \frac{X_{\infty}}{X_o} = \prod_{t=0}^{\infty}\frac{X_{t+1}}{X_t} = \prod_{t=1}^{\infty} g_t\]
or equivalently
\[\log G_{\infty} = \log (\prod_{t=1}^{\infty} g_t) = \sum_{t=1}^{\infty}\log g_t\]$G_{\infty}$ is the compound gain at the end of the sequence
$X_0$ is the initial capital, or the present capital
$X_t$ is the capital after $t^{th}$ trial, or the future capital
$g_t = X_t/X_{t-1}$ is the gain obtained after the $t^{th}$ trial
- maximizing $g$ is the same as maximizing $E \log X$
binary case
\[E \log g = E[\log\frac{X_n}{X_0}]^{1/n} = p \log(1+cf) + q \log(1-f)\]where $f$ is the portion to invest, $n$ is the number of trials, $c$ is the gain in terms of % on each trial, $-1$ is the loss, $q = 1-p$, and $t$ is removed as it is the same for each repetition in this example.
An optimal fraction is the one that makes the derivative null.
\(g'(f) = \frac{p}{1+f}-\frac{q}{1-f} = \frac{p-q-f}{(1+f)(1-f)}=0\) \(\mathrm{when}\ f=f^*=p-q\) \(f^* =(cp-q)/c\)
from sympy import *
# For a symbolic verification with Python and SymPy one would set the derivative y'(x)
# of the expected value of the logarithmic bankroll y(x) to 0 and solve for x
x, b, p = symbols('x b p')
y = p * log(1 + b * x) + (1 - p) * log(1 - x)
s = solve(diff(y, x), x)
print(str(s))
# [(b*p + p - 1)/b]
example: $p=53, 1=47$, expected win is $2:1$, or $100$ % => $f^{\ast}=(1*0.53 - 0.47)/1 = 0.06$
example: $p=53, 1=47$, expected win is $3:1$, or $200$ % => $f^{\ast}=(2*0.53 - 0.47)/2 = 0.295$
2 independent binary cases
$g(f_1, f_2) = p_1p_2 \log(1+f_1+f_2)$ + $p_1q_2 \log(1+f_1-f_2)$ + $q_1p_2 \log(1-f_1+f_2)$ + $q_1q_2 \log(1-f_1-f_2)$
2 dependent binary cases
more than 2 outcomes
Example from “mathexchange/Kelly criterion with more than two outcomes” $f(x)=0.7log(1−x)$ + $0.2log(1+10x)$ + $0.1log(1+30x)$, where optimum occurs at $x=0.248$, with $f(0.248)=0.263$.
“In other words, if you bet a little under a quarter of your bankroll, you should expect your bankroll to grow on average by $e^{0.263}=1.30$ for every bet.”
from sympy import *
# For a symbolic verification with Python and SymPy one would set the derivative y'(x)
# of the expected value of the logarithmic bankroll y(x) to 0 and solve for x
x, p = symbols('x p')
y = 0.1 * log(1 + 30 * x) + 0.2 * log(1 + 10 * x) + 0.7 * log(1 - x)
s = solve(diff(y, x), x)
print(str(s))
# [-0.0578343329665600, 0.247834332966560]
d = s[1]
fx = 0.1 * log(1 + 30 * d) + 0.2 * log(1 + 10 * d) + 0.7 * log(1 - d)
print(str(fx)) # 0.263191478105847
continuous case
\(E \log g = \int \log(1+fr) P(r)\ \mathrm{d}r\)
where $r$ is the excess return of the asset to invest in (i.e. return minus a risk-free reference).
Again, an optimal fraction is the one that makes the derivative null.
Two options for solving here with Python:
- compute the integral in the 1st equation and maximize the $f^* \operatorname{arg max}_f E \log g$
- compute the derivative of the integral in the 2nd equation and use numeric solver to find zero $\frac{d}{df}E \log g_{f=f^*} = 0$
Example from E. Thorp’s article with Source code from (Leiva, 2018).
Thorp focuses on annual returns and suggests modeling $P(r)$ as a normal distribution truncated at $\mu \pm 3\sigma$. Result is 1.197 and not 1.17 as normalization to account for truncated normal distribution was not performed.
from scipy.optimize import minimize_scalar, newton, minimize
from scipy.integrate import quad
from scipy.stats import norm
def norm_integral(f,m,st):
val,er = quad(lambda l: np.log(1+f*l)*norm.pdf(l,m,st),m-3*st,m+3*st)
return -val
def norm_dev_integral(f,m,st):
val,er = quad(lambda l: (l/(1+f*l))*norm.pdf(l,m,st),m-3*st,m+3*st)
return val
# Reference values from Eduard Thorp's article
m = .058
s = .216
# Option 1: minimize the expectation integral
sol = minimize_scalar(norm_integral,args=(m,s),bounds=[0.,2.],method='bounded')
print('Optimal Kelly fraction: {:.4f}'.format(sol.x))
# Option 2: take the derivative of the expectation and make it null
x0 = newton(norm_dev_integral,.1,args=(m,s))
print('Optimal Kelly fraction: {:.4f}'.format(x0))
Portfolio construction
Example from wduwant, with known expected return at each step.
- Good for educational processes, but miss the point of having distributions of expected return.
- Example is with $\log (\prod g)$ instead of $\sum(\log g)$
We have 2 assets $a$, $b$ and $c$, their weights in the portfolio $w_a + w_b + w_c= 1$ and their expected returns are $r_a$, $r_b$ and $r_c$.
\(\log g = \log(\frac{future\ value}{present\ value}) = \log(\frac{X_t}{X_{t-1}}) =\) \(= \log (\prod_{t=1}^n (1 + w_a r_a + w_b r_b + w_c r_c))\)
assuming asset returns are:
$R_a = [t_1=0.3, t_2=-0.15]$
$R_b = [t_1=-0.05, t_2=0.2]$
$R_c = [t_1=0.05, t_2=0.1]$
\(\log g = log[(1 + 0.3w_a -0.05w_b + 0.05w_c)\) \((1 -0.015w_a 0.2w_b + 0.1w_c)]\)
def objective(x, sign=-1.0):
w1=x[0]
w2=x[1]
w3=x[2]
a=np.array([0.3,-.15])
b=np.array([-0.05,0.2])
c=np.array([0.05,0.1])
fx=np.log(np.prod(1+w1*a+w2*b+w3*c))
return sign*(fx)
def weight_sum(x):
return x[0]+x[1]+x[2]-1.0
b=(-1,1) # bounds for weights
bounds=(b,b,b)
cons={'type': 'eq', 'fun': weight_sum}
x_default=np.zeros(3)
sol = minimize(objective, x_default,method='SLSQP', bounds=bounds, constraints=[cons])
#print(str(sol))
print('Kelly portfolio weights are as follows ' + str(sol.x*100))
Examples
- https://github.com/DinodC/investing-etf-kelly.git
single and multiple securities with ETFs based on “The Kelly Criterion in Blackjack Sports Betting and the Stock Market” using normal distributions. Uses matrix multiplication and python pandas. - https://github.com/jeromeku/Python-Financial-Tools.git
see portfolio.pyoptimize_kelly_criterion()
and the references “Kelly Criterion for Multivariate Portfolios: A Model-Free Approach”. It still uses covariance, but has other useful stuff, calculations and explanations.
References
- Leiva, J. (2018). The Kelly Criterion. https://quantdare.com/kelly-criterion/