As readers of my books will know, I don't recommend leveraged ETFs as a way to get leverage. Their ways are very dark and mysterious. But like many dark and mysterious things, they are also kind of funky and cool. In this post I will explore their general funkiness, and I will also show you how you can use them to produce a positive skewed return without the general faff of alternative ways of doing that: building a trend following strategy or trading options.
There is some simple python code in this post, but you don't need to be a pythonista to follow.
A simple model for leveraged ETF payoffs
As I was feeling unusually patriotic when I wrote this post, I decided to use the following FTSE 100 2x leveraged as my real life examples of leveraged ETFs:
Long: https://www.justetf.com/uk/etf-profile.html?isin=IE00B4QNJJ23
Short: https://www.justetf.com/uk/etf-profile.html?isin=IE00B4QNK008
It's very easy to work out how much a 2xleveraged ETF will be worth at some terminal point in the future. Assuming the current value is 1, and given a set of daily percentage returns, and specifying a long or short ETF:
def terminal_value_of_etf(returns: np.array, long: bool = True) -> float:
if long:
leveraged_returns = returns * 2
else:
leveraged_returns = returns * -2
terminal_value = (leveraged_returns + 1).cumprod()[-1]
return terminal_value
Now the best things in life are free, but ETFs aren't. We have to pay trading and management costs. The management costs on my two examples are around 0.55% a year (one is 0.5%, the other 0.6%) and the spread cost come in at 0.05% per trade. If we hold for a year then that will set us back 0.65%; call it 0.75% if we also have to pay commission (that would be the commission on a £5k trade if you're paying £5 a go).
Assuming we hold the ETFs for a year (~256 business days), we can then generate some random returns with some Gaussian noise and some given parameters, and get the terminal value. Finally, it's probably easier to think in terms of percentage gain or loss:
from random import gauss
def random_returns(annual_mean=0, annual_std=0.16, count=256):
return np.array([gauss(annual_mean / 256, annual_std / 16) for _ in range(count)])
def one_year_return_given_random_returns(
long: bool = True, annual_mean=0, annual_std=0.16, cost=0.0075
) -> float:
returns = random_returns(annual_mean=annual_mean, annual_std=annual_std)
value = terminal_value_of_etf(
returns, long=long
)
return (value - cost - 1.0) / 1.0
Let's generate a bunch of these random terminal payoffs and see what they look like.
x = [one_year_return_given_random_returns(long=False) for _ in range(100000)]
import pandas as pd
def plot_distr_x_and_title(x: list):
x_series = pd.Series(x)
x_series.plot.hist(bins=50)
plt.title(
"Mean %.1f%% Median %.1f%% 1%% left tail %.1f%% 1%% right tail %.1f%% skew %.3f"
% (
x_series.mean() * 100,
x_series.median() * 100,
x_series.quantile(0.01) * 100,
x_series.quantile(0.99) * 100,
x_series.skew(),
)
)
plot_distr_x_and_title(x)
Well the mean makes sense - it's equal to our costs (as the mean of the Gaussian noise here is zero), but where is that glorious fat right tail coming from? It's what will happen with compounded gains and losses. Think about it like this; if we get unlucky and lose say 0.1% every day then the cumulative product of 0.999^256 is 0.77; a loss of 23%. But if we make 0.1% a day then 1.001^256 is 1.29; a gain of 29%.
Note that we'd get exactly the same graph with a short leveraged ETF, again with the mean of the noisy returns equal to zero.
What if the standard deviation was higher; say 32% a year?
Interesting.
ETF payoffs versus drift
def one_year_return_and_index_return_given_random_returns(
long: bool = True, annual_mean=0, annual_std=0.16, cost=0.0075
):
returns = random_returns(annual_mean=annual_mean, annual_std=annual_std)
index_return = ((returns + 1).cumprod() - 1)[-1]
value = terminal_value_of_etf(returns, long=long)
etf_return = (value - cost - 1.0) / 1.0
return index_return, etf_return
index_returns = np.arange(start=-0.25, stop=0.25, step=0.0001)
all_index_returns = []
all_etf_returns = []
for mean_drift in index_returns:
for _ in range(100):
results = one_year_return_and_index_return_given_random_returns(
annual_mean=mean_drift
)
all_index_returns.append(results[0])
all_etf_returns.append(results[1])
to_scatter = pd.DataFrame(
dict(index_returns=all_index_returns, etf_returns=all_etf_returns)
)
to_scatter.plot.scatter(x="index_returns", y="etf_returns")
Double the fun
def one_year_return_and_index_return_given_random_returns_for_long_and_short(
annual_mean=0, annual_std=0.16, cost=0.0075
):
returns = random_returns(annual_mean=annual_mean, annual_std=annual_std)
index_return = returns.mean()*len(returns)
long_value = terminal_value_of_etf(returns, long=True)
short_value = terminal_value_of_etf(returns, long=False)
long_etf_return = (long_value - cost - 1.0) / 1.0
short_etf_return = (short_value - cost - 1.0) / 1.0
total_return = (long_etf_return + short_etf_return) / 2.0
return index_return, total_return
index_returns = np.arange(start=-0.25, stop=0.25, step=0.0001)
all_index_returns = []
all_etf_returns = []
for mean_drift in index_returns:
for _ in range(100):
results = (
one_year_return_and_index_return_given_random_returns_for_long_and_short(
annual_mean=mean_drift
)
)
all_index_returns.append(results[0])
all_etf_returns.append(results[1])
to_scatter = pd.DataFrame(
dict(index_returns=all_index_returns, etf_returns=all_etf_returns)
)
to_scatter.plot.scatter(x="index_returns", y="etf_returns")
There's a typo in `one_year_return_and_index_return_given_random_returns_for_long_and_short`. Instead of `total_return = long_etf_return + short_etf_return / 2.0`, it should be `total_return = (long_etf_return + short_etf_return) / 2.0`. That will remove the big skew to the left.
ReplyDeletefixed and updated plots, thanks
DeleteThe function `gauss` is not mentioned or defined. I have replaced it with `return np.random.normal(annual_mean / 256, annual_std / 16, count)`
ReplyDelete"We never lose more than 7% - which is a bit like the premium of the option - but if the index moves a fair bit in eithier direction then we make serious bank."
ReplyDeleteWhy is it so? When you run both a long and short, wouldn't they just erase each others profits? I assume you have equal weight?
They would cancel if they were unleveraged
DeletePlease explain why leverage from futures trading is better than leveraged ETFs for a balanced portfolio that deploys leverage. Is it because of the daily leverage reset in levered ETFs? If so, can you please explain that a bit.
ReplyDeleteJust google 'dangers of leveraged ETFs'; but yes it's the daily reset (also fees)
Delete