An interesting visualization of the kelly criterion

The Kelly criterion says there is an optimal constant betting size given an investment mean return and variance. I added a curve that plots the optimal leverage from the beginning to the end of the backtest. The backtest goes from the beginning of 2003 to the beginning of 2013. It shows that the optimal leverage for a constantly rebalanced portfolio is ~2.0 during this period. You can try changing the leverage to a different number to see that your end return will be sub-optimal. I hoped this was interesting for you as it was for me.

84
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import math
import numpy as np
import pandas

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.0))

context.days = 0
context.bought = False
context.spy = symbol('SPY')
set_benchmark(symbol('SPY'))
schedule_function(daily,date_rule=date_rules.every_day(),time_rule=time_rules.market_close(minutes=1))
# Will be called on every trade event for the securities you specify.
order_target_percent(context.spy,2)

def daily(context, data):
context.days += 1
record(leverage=context.account.leverage)
prices = data.history(context.spy,'price',context.days,'1d')
r = prices.pct_change().dropna()
mean = r.mean()
var = r.var()
k = mean/var
record(kelly = k)
There was a runtime error.
10 responses

With that said, if the long term leverage for SPY is greater than or equal to 2.0, then your best bet is to buy and hold a leveraged 2x ETF like SSO for maximum return!

The volatility and drawdown do not justify the added return. Try equal weight 45/45/10 SPY/TLT/cash, with 2x leverage.

This was more of a theoretical exercise. The kelly criterion only cares about maximizing growth regardless of drawdowns in between. If you knew the optimal leverage, you'll always come out on top at the end.

My comment was regarding this part:

"With that said, if the long term leverage for SPY is greater than or equal to 2.0, then your best bet is to buy and hold a leveraged 2x ETF like SSO for maximum return! "

This is practical trading advice in my opinion, and not a theoretical musing. Anyway, your "best" bet is not to use a leveraged ETF...your "best" bet is to reduce SPY exposure, and add other more stable assets and then rebalance. If you want to use leverage, feel free.

Well I should have added that it depends on your time horizon. If you notice the optimal leverage at the bottom of 2008 from 2003, it is negative. Your best bet would have been to stay out of equities. If your time horizon is 20 plus years, you should basically put all your money on the riskiest and highest return asset then slowly allocate to less risky assets. That's basically how a lot of retirement funds work.

Well here's a backtest with the optimal leverage for a combination of TLT and SPY. It's some ludicrous leverage but I think you'll notice that the leverage is weighed more on the bonds. I guess there is some value to adding bonds!

84
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
 Returns 1 Month 3 Month 6 Month 12 Month
 Alpha 1 Month 3 Month 6 Month 12 Month
 Beta 1 Month 3 Month 6 Month 12 Month
 Sharpe 1 Month 3 Month 6 Month 12 Month
 Sortino 1 Month 3 Month 6 Month 12 Month
 Volatility 1 Month 3 Month 6 Month 12 Month
 Max Drawdown 1 Month 3 Month 6 Month 12 Month
import math
import numpy as np
import pandas

# Put any initialization logic here.  The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
set_slippage(slippage.VolumeShareSlippage(volume_limit=1, price_impact=0.0))

context.days = 0
context.bought = False
context.equity = symbol('SPY')
context.bond = symbol('TLT')
set_benchmark(symbol('SPY'))
schedule_function(daily,date_rule=date_rules.every_day(),time_rule=time_rules.market_close(minutes=1))
# Will be called on every trade event for the securities you specify.
order_target_percent(context.bond,6)
order_target_percent(context.equity,3.8)

def daily(context, data):
context.days += 1
record(leverage=context.account.leverage)
prices = data.history([context.equity,context.bond],'price',context.days,'1d')
r = prices.pct_change().dropna()
mean = r.mean().values
cov = r.cov().values
cov_inv = np.linalg.inv(cov)
k = np.dot(cov_inv,mean)
k = pandas.Series(k,index=r.columns)
record(equity_k=k[context.equity],bond_k=k[context.bond])

There was a runtime error.

Based on prior readings, my assumption was that the Kelly criterion was to adjust leverage in proportion to the expected odds of winning. So in your backtest, you use mean and variance of returns to calculate projected winning odds?

Adjusting leverage in anticipation of low volatility or higher returns using momentum, treasury yield, seasonality curves as factors is legit (IMO). But I think the issue with your implementation is how you calculate the "odds of winning" or "expected value". You use the lookback window as backtest start to current day instead of a moving window of say, 1 year. This leads to some pretty large formation periods of a decade which includes a huge drawdown.

My python reading is pretty bad, but I think you are actually employing constant leverage in your code, as opposed to variable leverage suggested by the Kelly criterion.

Minh, you might find this interesting, reminded me of it: http://ddnum.com/articles/leveragedETFs.php

@Wu

Based on prior readings, my assumption was that the Kelly criterion
was to adjust leverage in proportion to the expected odds of winning.
So in your backtest, you use mean and variance of returns to calculate
projected winning odds?

Most people are familiar with the Kelly formula for binary and fixed returns. There's another formula that incorporates the mean and variance of returns.

My python reading is pretty bad, but I think you are actually
employing constant leverage in your code, as opposed to variable
leverage suggested by the Kelly criterion.

I used a growing window to demonstrate the optimal kelly bet from one point to another. Kelly betting is a constant leverage strategy not a variable leverage strategy. For example, you can see the kelly leverage is around 6.5 from the beginning from 2003 to the beginning of 2008. That means that if you deploy a constant leverage of 6.5 during that period with SPY, you would have the best constantly rebalanced portfolio. If you maintained a leverage of 6.5 to 2013 like the backtest, your total return would be suboptimal compared to the 2.0 at the end.