Kelly Criterion

kelly.png

Introduction

The Kelly criterion is a mathematical formula for bet sizing, which is frequently used by investors to decide how much money they should allocate to each investment or bet through a predetermined fraction of assets. This approach is valid when the expected returns are known. The Kelly bet size is found by maximizing the expected value of the logarithm of wealth, which is equivalent to maximizing the expected geometric growth rate. It was described by J. L. Kelly Jr, a researcher at Bell Labs, in 1956.

A lot of variations of the classical Kelly Criterion have been proposed. We will specifically take a look at four of these proposed methods:

  • Continuous-Time Kelly, which is derived from the classical case by taking a continuous probability distribution of outcomes into consideration, rather than a discrete one.

  • Optimal Allocation Kelly, which builds on top of the continuous time case by taking \(n\) assets into account, and derives its formulas from linear algebra.

  • Risk Constrained Kelly (RCK), which imposes an additional constraint by limiting the probability of a drawdown above a certain threshold. There are two variations of RCK:

    • The Finite Risk Constraint Kelly, where the are a finite amount of outcomes.

    • The General Risk Constraint Kelly,where there are an infinite amount of outcomes.

Discrete-Time Kelly

An overview of the theory behind the Kelly Criterion is given here. For a more detailed explanation, see Thorp, E. O. (2011).

Say we want wager even money bets made on i.i.d bets of a biased coin . Let \(p>1 / 2\) be the probability of winning and \(q=1-p\), the probability of losing. Let \(X_{0}\) be the initial capital. Suppose we choose the goal of maximizing the expected value \(E\left(X_{n}\right)\) after \(n\) trials. How much should we bet, \(B_{k}\), on the \(k\) th trial? Denote \(T_{k}=1,\) if the \(k\) th trial is a win and if \(T_{k}=-1,\) if it is a loss. Therefore \(X_{k}=X_{k-1}+T_{k} B_{k}\) for \(k=1,2,3, \ldots\), and \(X_{n}=X_{0}+\sum_{k=1}^{n} T_{k} B_{k}\). And we obtain

\[E\left(X_{n}\right)=X_{0}+\sum_{k=1}^{n} E\left(B_{k} T_{k}\right)=X_{0}+\sum_{k=1}^{n}(p-q) E\left(B_{k}\right)\]

Let us bet according to \(B_i = f X_{i−1}\), where \(0 \le f \le 1\) is the fixed fraction of wealth we are betting. Let \(S\) and \(F\) denote the number of successes and failures respectively. Then we have \(X_n = X_0(1+f)^{S}(1-f)^{F}\). We can write \(e^{n log [\frac{X_n}{X_0}]^{\frac{1}{n}}}\), where \(G(f) = log[\frac{X_n}{X_0}]^{\frac{1}{n}}\) is the exponential rate of increase. Kelly chose to maximize the expected value of the growth rate coefficient, \(g(f)\), where

\[\begin{split}\begin{aligned} g(f) &= E\{[log[\frac{X_n}{X_0}]^{\frac{1}{n}}\} = E\{ \frac{S}{n}log(1+f) +\frac{F}{n}log(1-f)\} \\ &= p log(1+f) + qlog(1 − f) \end{aligned}\end{split}\]

Calculating the derivative of \(g(f)\) and setting equal to zero we find the optimal size bet for fastest growth as

\[f^{*} = p - q.\]

The Kelly criterion can easily be extended to uneven payoffs. Suppose we win \(b\) units for every unit wager and lose \(a\) units. Further, suppose that on each trial the win probability is \(p > 0\) and \(m = bp − aq > 0\) so that the game is advantageous. We again maximize \(g(f)\) to obtain

\[f^{*} = \frac{m}{ab}\]

It is well known that Kelly optimal bets can lead to substantial drawdown risk. One ad hoc method for handling this is to compute a Kelly optimal bet \(f^{\star}\), and then use the so-called fractional Kelly bet given by

\[f= cf^{\star}\]

where \(c \in(0,1)\) is the fraction. The fractional Kelly bet scales down the (risky) bets by \(c\). Fractional Kelly bets have smaller drawdowns than Kelly bets, at the cost of reduced growth rate. We will see that trading off growth rate and drawdown risk can be more directly (and better) handled.

Continuous-Time Kelly Bet Sizing

When applying the Kelly criterion to securities markets, a new analytic problems is encountered. A bet on a security typically has many outcomes. This leads to the use of continuous instead of discrete probability distributions. Therefore we need to find, \(f\), as to maximize \(g(f ) = E [ln(1 + f)] = \int (1 + f x) dP (x)\), where \(P (x)\) is a probability measure describing the outcomes. Frequently the problem is to find an optimum portfolio from among \(n\) securities.

Let \(X\) be a random variable with \(P(X=m+s)=P(X=m-s)=0.5\). Then \(E(X)=m, \operatorname{Var}(X)=s^{2}\). With initial capital \(V_{0}\), betting fraction \(f\), and return per unit of \(X\), the result is

\[V(f)=V_{0}(1+(1-f) r+f X)=V_{0}(1+r+f(X-r)),\]

where \(r\) is the rate of return on the remaining capital. Then

\[\begin{split}\begin{aligned} g(f) &=E(G(f))=E\left(\ln \left(V(f) / V_{0}\right)\right)=E \ln (1+r+f(X-r)) \\ &=0.5 \ln (1+r+f(m-r+s))+0.5 \ln (1+r+f(m-r-s)) \end{aligned}\end{split}\]

Now subdivide the time interval into \(n\) equal independent steps, keeping the same drift and the same total variance. Thus \(m, s^{2}\) and \(r\) are replaced by \(m / n, s^{2} / n\) and \(r / n\), respectively. We have \(n\) independent \(X_{i}, i=1, \ldots, n\), with

\[\begin{split}&P\left(X_{i}=m / n+s n^{-1 / 2}\right)=P\left(X_{i}=m / n-s n^{-1 / 2}\right)=0.5 \\ &\implies V_{n}(f) / V_{0}=\prod_{i=1}^{n}\left(1+(1-f) r+f X_{i}\right) .\end{split}\]

Taking \(E(\log (\cdot))\) of both sides gives \(g(f)\). Expanding the result in a power series leads to

\[\begin{split}g(f)&=r+f(m-r)-s^{2} f^{2} / 2+\mathrm{O}\left(n^{-1 / 2}\right) \\ g_{\infty}(f) & \equiv r+f(m-r)-s^{2} f^{2} / 2 .\end{split}\]

The following is a summary of results derived

\[\begin{split}\begin{aligned} & \bullet f^{*}=(m-r) / s^{2} \text{ - Optimal Kelly bet size} \\ & \bullet g_{\infty}(f)=r+f(m-r)-s^{2} f^{2} / 2 \text{ - Expected capital growth rate}\\ & \bullet g_{\infty}\left(f^{*}\right)=(m-r)^{2} / 2 s^{2}+r \text{ - Optimal expected capital growth rate}\\ & \bullet \operatorname{Var}\left(G_{\infty}(f)\right)=s^{2} f^{2}, \quad \operatorname{Sdev}\left(G_{\infty}(f)\right)=s f \\ & \bullet t_{k}=k^{2} s^{2} f^{2} / g_{\infty}^{2} \text{ - Time initial investment is exceeded} \end{aligned}\end{split}\]

Implementation

class ContinuousTimeKelly(returns: array, risk_free_rate: float = 0, trading_days: int = 252)

ContinuousTimeKelly implements the continuous-time approximation to the kelly criterion.

As presented in The Kelly Criterion in Blackjack Sports Betting and The Stock Market by Edward O. Thorp.

__init__(returns: array, risk_free_rate: float = 0, trading_days: int = 252)

Initialize ContinuousTimeKelly class with needed parameters.

Parameters:
  • returns – (np.array) Returns vector for n assets.

  • risk_free_rate – (float) Yearly risk-free rate to use in calculations.

  • trading_days – (int) Number of trading days in a year.

capital_growth_std(bet_size: float) float

Computes the capital growth standard deviation given the bet size.

\[Sdev(G_{\infty}(f)) = sf\]

Where s is the standard deviation of the mean returns and f is the bet size.

Parameters:

bet_size – (float) Kelly bet size (\(f\)).

Returns:

(float) Expected growth rate standard deviation (\(Sdev(G_{\infty}(f))\)).

expected_capital_growth_rate(bet_size: float = 1.0) float

Computes the expected capital growth rate given the bet size.

\[g_{\infty}(f) = r + f(m - r) - s^2f^2/2\]

Where f is the bet size, m is the mean returns, r is the risk-free rate, and s is the standard deviation.

Parameters:

bet_size – (float) Kelly bet size (\(f\)).

Returns:

(float) Expected capital growth rate (\(g_{\infty}\)).

optimal_bet_size(fraction: float = 1.0) float

Computes optimal kelly bet size.

\[cf^* = c(m - r) / s^2\]

Where c is the fractional value, m is the mean returns, r is the risk-free rate, and s is the standard deviation.

Parameters:

fraction – (float) Fractional kelly value (\(c\)).

Returns:

(float) Optimal kelly bet size.

optimal_capital_growth_std(fraction: float = 1.0) float

Computes the optimal capital growth standard deviation.

\[Sdev(G_{\infty}(cf^*)) = c(m - r) / s\]

Where m is the mean returns, r is the risk-free rate, c is the fractional kelly value, \(f^*\) is the optimal kelly bet, and s is the standard deviation.

Parameters:

fraction – (float) Fractional kelly value (\(c\)).

Returns:

(float) Expected capital growth rate standard deviation (\(Sdev(G_{\infty}(cf^*))\)).

optimal_expected_capital_growth_rate(fraction: float = 1.0) float

Computes the optimal expected capital growth rate.

\[g_{\infty}(cf*) = ((m - r)^2(c - c^2/2))/s^2 + r\]

Where c is the fractional value, f* is the optimal kelly bet, m is the mean returns, r is the risk-free rate, and s is the standard deviation.

Parameters:

fraction – (float) Fractional kelly value (\(c\)).

Returns:

(float) Optimal expected capital growth rate (\(g_{\infty}\)).

optimal_time_to_growth(std_k: int, fraction: float) float

Computes the time it will take an investment to grow larger than the initial invested amount with a confidence \(k\) standard deviations for a given bet size.

\[t_k = k^2c^2((m - r)^2/s^2)/((m - r)^2/s^2(c - c^2/2) + r)^2\]
Parameters:
  • std_k – (float) Standard deviations of confidence (\(k\)).

  • fraction – (float) Fractional kelly value (\(c\)).

Returns:

(float) Time \(t_k\) to achieve optimal growth at \(k\) confidence.

time_to_growth(bet_size: float, std_k: int) float

Computes the time it will take an investment to grow larger than the initial invested amount with a confidence \(k\) standard deviations for a given bet size.

\[t_k = k^2s^2f^2/g_{\infty}^2\]

Where k is the standard deviation for the investment to be larger than the invested amount, \(f\) is the kelly bet size, \(g_{\infty}\) is the expected capital growth rate, and s is the standard deviation of the returns.

Parameters:
  • bet_size – (float) Kelly bet size (\(f\)).

  • std_k – (float) Standard deviations of confidence (\(k\)).

Returns:

(float) Time \(t_k\) to achieve growth at \(k\) confidence.

Examples

# Importing tools
import matplotlib.pyplot as plt
from mlfinlab.datasets.load_datasets import load_stock_prices
from mlfinlab.bet_sizing.kelly import ContinuousTimeKelly

# Lets load some securities prices
prices = load_stock_prices().iloc[:, [6,8]]

# Here we are getting the returns for the past 1000 days
returns = prices.iloc[-1000:,:].pct_change().dropna()

# Plot the prices to inspect
plt.plot(prices.iloc[-1000:,])
plt.legend(returns.columns.values)

# Initialise class with risk free rate = 0.03, by default it is 0
ck = ContinuousTimeKelly(returns, risk_free_rate = 0.03)

# Get the optimal bet size
optimal_bet = ck.optimal_bet_size()

# Use fractional kelly betting with c = 0.5
half_kelly_bet = ck.optimal_bet_size(fraction=0.5)

# Now we can get the expected capital gowth rate g(f) and the std of g(f)
expected_growth = ck.optimal_expected_capital_growth_rate()
expected_growth_std = ck.optimal_capital_growth_std()

Optimal Allocation Kelly

Let’s now consider the case of determining the optimal bet sizes given a portfolio of \(n\) securities. Let \(f_{0}\) be a riskless security with return \(r\) and \(f_{1}, \ldots, f_{n}\) the portfolio fractions, where \(f_{0} +\cdots+f_{n}=1\). Let \(C=\left(s_{i j}\right)\) be the matrix such that \(s_{i j}, i, j=1, \ldots, n\), is the covariance of the \(i\) th and \(j\) th securities and \(M=\left(m_{1}, m_{2}, \ldots, m_{n}\right)^{T}\) be the row vector such that \(m_{i}, i=1, \ldots, n\), is the drift rate of the \(i\) th security. Then the portfolio satisfies

\[\begin{split}\begin{aligned} m &=f_{0} r+f_{1} m_{1}+\cdots+f_{n} m_{n}=r+f_{1}\left(m_{1}-r\right)+\cdots+f_{n}\left(m_{n}-r\right) \\ &=r+F^{T}(M-R) \\ s^{2} &=F^{T} C F \end{aligned}\end{split}\]

where \(F^{T}=\left(f_{1}, \ldots, f_{n}\right)\) and \(T\) means “transpose”, and \(R\) is the column vector \((r, r, \ldots, r)^{T}\) of length \(n\).

The previous results for one security plus a riskless security thus apply to \(g_{\infty}\left(f_{1}, \ldots, f_{n}\right)=m-s^{2} / 2\). This is a standard quadratic maximization problem. Using the equation for \(s^2\) and solving the simultaneous equations \(\partial g_{\infty} / \partial f_{i}=0, i=1, \ldots, n\), we get

\[\begin{split}\begin{aligned} &F^{*}=C^{-1}[M-R], \\ &g_{\infty}\left(f_{1}^{*}, \ldots, f_{n}^{*}\right)=r+\left(F^{*}\right)^{T} C F^{*} / 2 \end{aligned}\end{split}\]

where for a unique solution we require \(C^{-1}\) to exist, i.e., \(\operatorname{det} C \neq 0\). When all the securities are uncorrelated, \(C\) is diagonal and we have \(f_{i}^{*}=\left(m_{i}-r\right) / s_{i i}\) or \(f_{i}^{*}= \left(m_{i}-r\right) / s_{i}^{2}\).

Warning

Due to instability when inverting the covariance matrix, the OptimalAllocationKelly class will work best with a small (max. 5 or 6) number of securities or portfolio strategies.

Implementation

class OptimalAllocationKelly(returns: DataFrame, risk_free_rate: float = 0, trading_days: int = 252)

OptimalAllocationKelly implements the placing of many bets across different portfolios of securities of trading strategies in order to achieved maximum capital growth.

__init__(returns: DataFrame, risk_free_rate: float = 0, trading_days: int = 252)

Document parameters, expose trading days option.

capital_growth_rate(f_allocate=None, fraction: float = 1.0) float

Returns capital growth rate for the betting allocation.

\[g(F^*) = r + F^{T}CF/2\]
Parameters:
  • f_allocate – (float) The betting (allocation) vector specified.

  • fraction – (float) Fractional kelly value.

Returns:

(float) Capital growth rate.

optimal_betting_vector(fraction: float = 1.0) array

Computes optimal betting vector across n portfolios of securities or trading strategies.

\[F = C^{-1}[M - R]\]
Parameters:

fraction – (float) Fractional kelly value.

Returns:

(np.array) Optimal betting vector.

sharpe_ratio(f_allocate=None, fraction: float = 1.0) float

Returns sharpe ratio for optimal betting allocation.

\[S = \sqrt{F^{*T}CF^*}\]
Parameters:
  • f_allocate – (float) The betting (allocation) vector specified.

  • fraction – (float) Fractional kelly value.

Returns:

(float) Sharpe ratio.

Examples

# Importing tools
from mlfinlab.bet_sizing.kelly import OptimalAllocationKelly
from mlfinlab.datasets.load_datasets import load_stock_prices

# Get returns for 3 assets / securities
prices = load_stock_prices().iloc[-1000:, [-1,-4,-12]]
returns = prices.pct_change().dropna()

# Get optimal bet allocation
kelly = OptimalAllocationKelly(returns,risk_free_rate = 0.02)
bet_allocation = kelly.optimal_betting_vector()

# Get the growth rate and sharpe ratio for optimal bet.
growth_rate = kelly.capital_growth_rate()
sharpe_ratio = kelly.sharpe_ratio()

Risk-Constrained Kelly Bet Sizing

As in the previous sections, say we want to place a fixed fraction of our total wealth (assumed positive) on \(n\) bets. We denote the fractions as \(f \in \mathbf{R}^{n}\), so \(f \geq 0\) and \(\mathbf{1}^{T} f=1\), where \(\mathbf{1}\) is the vector with all components 1 . Denoted \(r \in \mathbf{R}_{+}^{n}\), so the wealth after the bet changes by the (random) factor \(r^{T} b\). This means that \(f_{0}\) represents the fraction of our wealth that we do not wager, or hold as cash. The bet vector \(f=e_{0}\) corresponds to not betting at all.

As was the case in the previous section, we want \(f\) to maximize \(\mathbf{E} \log \left(r^{T} f\right)\), the growth rate of wealth. We can also state the maximization problem as

\[\begin{split}\begin{array}{ll} \operatorname{maximize} & \mathbf{E} \log \left(r^{T} f\right) \\ \text {subject to} & \mathbf{1}^{T} f=1, \quad f \geq 0 \end{array}\end{split}\]

with variable \(f\). We call a solution \(f{*}\) of this problem a set of Kelly optimal bets. The Kelly gambling problem is always feasible, since we could have \(f=e_{n}\) (which corresponds to not placing at all).

We can look at Risk-Constrained Kelly bet sizing which simply adds additional constraints on the formulation above in order to limit downside risk. A brief overview of the methodology is given here. For a more detailed explanation please see Busseti, E., Ryu, E.K. and Boyd, S., 2016.

Let \(W_{t} = X_t/X_0\), i.e the return on the initial investment at time \(t\). Define the minimum wealth as the infimum of the wealth trajectory over time,

\[W^{\min }=\inf _{t=1,2, \ldots} W_{t}\]

In this case we define the drawdown as \(1-W^{\min }\), while we define drawdown risk as \(\operatorname{Prob}\left(W^{\min }<\alpha\right)\), where \(\alpha \in(0,1)\) is a given target (undesired) minimum wealth. This risk depends on the bet vector \(f\) in a very complicated way. There is in general no formula for the risk in terms of \(f\), but we can always (approximately) compute the drawdown risk for a given \(f\) using Monte Carlo simulation. As an example, a drawdown risk of \(0.1\) for \(\alpha=0.7\) means the probability of experiencing more than \(30 \%\) drawdown is only \(10 \%\). The smaller the drawdown risk (with any target), the better.

We can add a drawdown risk constraint to the Kelly maximization problem formulated earlier, to obtain

\[\begin{split}\begin{array}{ll} \operatorname{maximize} & \mathbf{E} \log \left(r^{T} f\right) \\ \text {subject to} & \mathbf{1}^{T} f=1, \quad f \geq 0 \\ & \operatorname{Prob}\left(W^{\min }<\alpha\right)<\beta \end{array}\end{split}\]

with variable \(f\), where \(\alpha, \beta \in(0,1)\) are given parameters. The last constraint limits the probability of a drop in wealth of value \(\alpha\) to be no more than \(\beta\).

We can now derive a condition that bounds the drawdown risk. Consider any \(\lambda>0\) and bet \(b\). For any \(\alpha \in(0,1)\) and \(\beta \in(0,1)\) that satisfies \(\lambda=\log \beta / \log \alpha\) we have

\[\mathbf{E}\left(r^{T} f\right)^{-\lambda} \leq 1 \Longrightarrow \operatorname{Prob}\left(W^{\min }<\alpha\right)<\beta\]

Replacing the drawdown risk constraint in the problem with \(\lambda=\log \beta / \log \alpha\), yields the risk-constrained Kelly gambling problem (RCK).

\[\begin{split}\begin{array}{ll} \operatorname{maximize} & \mathbf{E} \log \left(r^{T} f\right), \\ \text { subject to } & \mathbf{1}^{T} f=1, \quad f \geq 0, \\ & \mathbf{E}\left(r^{T} f\right)^{-\lambda} \leq 1, \end{array}\end{split}\]

Finite Risk-constrained Kelly Criterion

For the finite outcomes case we can restate the RCK problem in a convenient and tractable form. We first take the \(\log\) of the last constraint and get

\[\log \sum_{i=1}^{K} \pi_{i}\left(r_{i}^{T} f\right)^{-\lambda} \leq 0\]

where \(\pi_{i}\) is the probability of the $i$’th outcome, we then write it as

\[\log \left(\sum_{i=1}^{K} \exp \left(\log \pi_{i}-\lambda \log \left(r_{i}^{T} f\right)\right)\right) \leq 0\]

To see that this constraint is convex we note that the log-sum-exp function is convex and increasing, and its arguments are all convex functions of \(f\) (since \(log(r_t^T f)\) is concave), so the lefthand side function is convex in f. Thus we have

\[\begin{split}\begin{array}{ll} \operatorname{maximize} & \sum_{i=1}^{K} \log \pi_{i}\left(r_{i}^{T} f\right) \ \\ \text { subject to } & \mathbf{1}^{T} f=1, \quad f \geq 0, \\ & \log \left(\sum_{i=1}^{K} \exp \left(\log \pi_{i}-\lambda \log \left(r_{i}^{T} f\right)\right)\right) \leq 0, \end{array}\end{split}\]

The implementation is reformulated as a quadratic approximation of the RCK problem, which the authors call the quadratic RCK problem (QRCK), to which it can be shown that it has a close connection to Markowitz portfolio optimization.

Implementation

class FiniteRiskConstrainedKelly(returns_r: array, probabilities_pi: array, n_outcomes: int, alpha: float = 0, beta: float = 0, lambda_risk: float = 0)

FiniteRiskConstrainedKelly implements risk constrained kelly betting for finite returns.

As presented in Risk-Constrained Kelly Gambling by Busseti, Ryu and Boyd.

__init__(returns_r: array, probabilities_pi: array, n_outcomes: int, alpha: float = 0, beta: float = 0, lambda_risk: float = 0)

Initialize FiniteRiskConstrainedKelly class with needed parameters.

Parameters:
  • returns_r – (np.array) Returns vector for n assets.

  • probabilities_pi – (np.array) Probabilities (\(\pi\)) vector of returns for n assets.

  • n_outcomes – (np.array) Number of outcomes (\(k\)).

  • alpha – (float) Max target drawdown (\(\alpha\)).

  • beta – (float) Probability of drawdown (\(\beta\)).

  • lambda_risk – (float) Risk-aversion parameter (\(\lambda\)).

optimal_bet_size(asset_n: int, fraction: float = 1.0) float

Select optimal bet size for the n-th asset.

Parameters:
  • asset_n – (int) The n-th asset in the betting vector.

  • fraction – (float) Fractional kelly bet value.

Returns:

(float) Bet size.

optimal_betting_vector(fraction: float = 1.0) array

Returns optimal betting vector.

Parameters:

fraction – (float) Fractional kelly value.

Returns:

(np.array) Optimal betting vector.

optimal_quadratic_bet_size(asset_n: int, fraction: float = 1.0) float

Select optimal quadratic bet size for the n-th asset.

Parameters:
  • asset_n – (int) The n-th asset in the betting vector.

  • fraction – (float) Fractional kelly bet value.

Returns:

(float) Bet size.

optimal_quadratic_betting_vector(fraction: float = 1.0) array

Returns optimal quadratic betting vector.

Parameters:

fraction – (float) Fractional kelly value.

Returns:

(np.array) Optimal quadratic betting vector.

Examples

# Importing tools
from mlfinlab.bet_sizing import FiniteRiskConstrainedKelly
from mlfinlab.util.bet_sizing import draw_batch_finite_returns

# Drawing sample returns
returns_r, probabilities_pi = draw_batch_finite_returns(seed=0)
outcomes = 100
alpha = 0.7

# Applying RCK
rck = FiniteRiskConstrainedKelly(returns_r, probabilities_pi, n_outcomes=outcomes,
                                 alpha=alpha, beta=0.1)
mid_rck = FiniteRiskConstrainedKelly(returns_r, probabilities_pi, n_outcomes=outcomes,
                                     lambda_risk=5.5)
no_constraint_rck = FiniteRiskConstrainedKelly(returns_r, probabilities_pi,
                                               n_outcomes=outcomes,
                                               lambda_risk=0)

# Checking results
rck_b = rck.optimal_betting_vector()
mid_rck_b = mid_rck.optimal_betting_vector()
no_rck_b = no_constraint_rck.optimal_betting_vector()

General Risk-constrained Kelly Criterion

For the case of general returns we can solve the RCK problem using a stochastic optimization method. A primal-dual stochastic gradient method applied to the Lagrangian

\[L(f, \kappa)=-\mathbf{E} \log \left(r^{T} f\right)+\kappa\left(\mathbf{E}\left(r^{T} f\right)^{-\lambda}-1\right),\]

with \(f \in \Delta_{\varepsilon}\) and \(\kappa \geq 0\). (As in the unconstrained Kelly optimization case, we make the technical assumption that \(f_{n}^{*}>\varepsilon>0\).) In can be shown that the RCK problem has an optimal dual variable \(\kappa^{*}\) for the constraint \(\mathbf{E}\left(r^{T} f\right)^{-\lambda} \leq 1\), which implies that solving the RCK problem is equivalent to finding a saddle point of \(L(f, \kappa)\).

The implementation is reformulated as a quadratic approximation of the RCK problem, which the authors call the quadratic RCK problem (QRCK), to which it can be shown that it has a close connection to Markowitz portfolio optimization.

Implementation

class GeneralRiskConstrainedKelly(returns: array, alpha: float = 0, beta: float = 0, lambda_risk: float = 0)

GeneralRiskConstrainedKelly implements risk constrained kelly betting for infinite returns.

As presented in Risk-Constrained Kelly Gambling by Busseti, Ryu and Boyd.

__init__(returns: array, alpha: float = 0, beta: float = 0, lambda_risk: float = 0)

Initialize GeneralRiskConstrainedKelly class with needed parameters.

Parameters:
  • returns – (np.array) Returns vector for n assets.

  • alpha – (float) Max target drawdown (\(\alpha\)).

  • beta – (float) Probability of drawdown (\(\beta\)).

  • lambda_risk – (float) Risk-aversion parameter (\(\lambda\)).

optimal_bet_size(asset_n: int, fraction: float = 1.0) float

Select optimal bet size for the n-th asset.

Parameters:
  • asset_n – (int) The n-th asset in the betting vector.

  • fraction – (float) Fractional kelly bet value.

Returns:

(float) Bet size.

optimal_betting_vector(fraction: float = 1.0) array

Returns optimal betting vector.

Parameters:

fraction – (float) Fractional kelly value.

Returns:

(np.array) Optimal betting vector.

optimal_quadratic_bet_size(asset_n: int, fraction: float = 1.0) float

Select optimal quadratic bet size for the n-th asset.

Parameters:
  • asset_n – (int) The n-th asset in the betting vector.

  • fraction – (float) Fractional kelly bet value.

Returns:

(float) Bet size.

optimal_quadratic_betting_vector(fraction: float = 1.0) array

Returns optimal quadratic betting vector.

Parameters:

fraction – (float) Fractional kelly value.

Returns:

(np.array) Optimal quadratic betting vector.

Examples

# Importing tools
from mlfinlab.bet_sizing.kelly import FiniteRiskConstrainedKelly
from mlfinlab.util.bet_sizing import draw_batch_finite_returns

# Drawing sample returns
sample_returns = draw_batch_infinite_returns(seed=10)
alpha = 0.7

# Applying RCK
rck = GeneralRiskConstrainedKelly(sample_returns, alpha=alpha, beta=0.1)
mid_constrained_rck = GeneralRiskConstrainedKelly(sample_returns, lambda_risk=5.7)
no_constraint_rck = GeneralRiskConstrainedKelly(sample_returns, lambda_risk=0)

# Checking results
rck_b = rck.optimal_betting_vector()
mid_rck_b = mid_constrained_rck.optimal_betting_vector()
no_rck_b = no_constraint_rck.optimal_betting_vector()

Monte Carlo Utilities

The following utilities are adapted from Busseti et. al (2016). from their notebooks for Risk-Constrained Kelly Gambling. You might want to use these utilities to validate your risk-constrained kelly betting results via method of simulations.

Helper utilities for Kelly criterion bet sizing research.

compute_drawdown_probability(w_mins: array, alpha: float) float

Computes probability of a drawdown greater than target alpha of minimum wealth. Defined as the drawdown risk.

\[\boldsymbol{Prob}(W^{min} < \alpha)\]
Parameters:
  • w_mins – (np.array) Array with welath trajectories.

  • alpha – (float) Desired target \(\alpha\) for which we want to avoid dropping our minimium wealth.

Returns:

(float) Probability of drawdown for given target.

compute_wealth_miniminums(logw_samples: array) array

Returns the minimum wealth (\(W^{min}\)) trajectories from the log returns.

Parameters:

logw_samples – (np.array) Log of cumulative wealth returns.

Returns:

(np.array) Wealth trajectories.

draw_batch_finite_returns(n_draws: int = 20, outcomes: int = 100, seed: int = 0) Tuple[array, array]

Function performs n_draws for \(K\) possible outcomes.

Probabilities are sampled from a uniform distribution on [0, 1]. The 30 returns are sampled uniformly as well from [0.7, 1.3]. The probability that a return vector \(r\) contains at least one “extreme” return (e.g. 0.2 or 2) is approximately 0.45.

Returns a tuple of returns \(r\) and probabilities \(\pi\).

Parameters:
  • n_draws – (int) Number of draws to perform (e.g. n - 1 assets or securities)

  • outcomes – (int) Number of \(K\) outcomes

  • seed – (int) Seed for random number generator.

Returns:

(Tuple[np.array, np.array]) Tuple of returns \(r\) and probabilities \(\pi\)

draw_batch_infinite_returns(n_draws: int = 20, draws: int = 1000000, seed: int = 0) array

Function performs a batch of n_draws (e.g. each draw is the nth asset) where for each draw a sample of draws number of returns is produced.

For infinite returns we draw from a mixture of lognormals.

Parameters:
  • n_draws – (int) Number of draws to perform (e.g. n assets or securities).

  • draws – (int) Number of observations to sample for each draw.

  • seed – (int) Seed for random number generator.

Returns:

(np.array) Array of n_draws each having draws number of sample returns.

empirical_cdf(samples: array, grid: array) array

Returns the observed cumulative distribution function for the log returns.

Parameters:
  • samples – (np.array) Sample of log returns.

  • grid – (np.array) Series to which accumulate the log returns.

Returns:

(np.array) Cumulative distribution function (CDF) for the log returns.

growth_rate_wealth_mins(logw_samples: array) float

Returns average growth rate of log wealth returns.

Parameters:

logw_samples – (np.array) Log of wealth returns for n assets.

Returns:

(float) Growth rate of wealth.

sample_monte_carlo_finite_returns(returns_r: array, probabilities_pi: array, n_samples: int = 10000, mc_time: int = 100) array

Sample returns for finite distributions. This is function is used to perform monte carlo simulations for the FiniteRiskConstrainedKelly class.

Parameters:
  • returns_r – (np.array) Sample asset returns.

  • probabilities_pi – (np.array) Sample probabilities of returns.

  • n_samples – (int) Number of samples to draw.

  • mc_time – (int) Monte carlo time.

Returns:

(np.array) Simulated returns.

sample_monte_carlo_infinite_returns(n_samples: int = 10000, mc_time: int = 100, n_assets: int = 20) array

Sample returns for infinite distributions. This is function is used to perform monte carlo simulations for the GeneralRiskConstrainedKelly class.

Parameters:
  • n_samples – (int) Number of samples.

  • n_assets – (int) Number of assets.

  • mc_time – (int) Monte carlo time.

Returns:

(np.array) Simulated returns.

simulate_trajectories(sampled_returns: array, bet_vector_b: array) array

Simulate wealth trajectories for Kelly betting monte carlo simulations.

Function will take sampled returns and multiply by the betting vector produced by the given Kelly Criterion implementation.

Parameters:
  • sampled_returns – (np.array) Sample returns for n assets.

  • bet_vector_b – (np.array) Bet size for n assets.

Returns:

(np.array) Cumulative wealth returns for each asset given the bet size.

Research Notebook

The following research notebooks can be used to better understand Kelly Criterion bet sizing.

References