Leveraged Exchange-Traded Funds (LETFs) seek to replicate the performance of an underlying index with a specified leverage factor.
\[r_{LETF} = r_{index} * l\]
\(r\) being the return and \(l\) a leverage factor which is normally 2 (double), 3 (tripple), or for short LETFs also -1 (simple short), -2 (double short), or -3 (tripple short).
Take for example the S&P 500 Index that can be traded by the SPY (likely the most popular ETF in the world?).
There’s a LETF that is to replicate twice the performance of the S&P 500, it goes under the symbol SSO (ProShares Ultra S&P500). The UPRO (“UltraPro”) replicates 3 times the index. On the short side there’s SH (ProShares Short S&P500), the -2x SDS (“UltraShort”), and the SPXU (“UltraPro Short”).
etfdb.com currently lists around 200 LETFs and 100 inverse ETFs. So there’s many products and indices to choose from.
So one will expect that a 2x LETF delivers twice the performance of the index. But does it?
In 2019 the SPX index returned 31.3% (total return). How much would you have made with the SPY? 31.1%, so there is some lag. The reason for this is most likely the cost of the ETF, aka the expense ratio.
Now, how has the 2x leveraged SSO made? Somewhere above 63% one should assume. Indeed, it we are looking at 63.3%.
What about the 3x leveraged UPRO? 102%, which is yet more than one should have expected. Not bad.
The problem is that what we had in 2019 is not the norm. Look at these graphs:
pdata <- prices_d %>%
left_join(etfs, by = c("symbol" = "Ticker")) %>%
filter(Ind == "SPXT", Leverage >= 1,
year(date) %in% 2010:2019) %>%
group_by(symbol, year = year(date)) %>%
mutate(performance = adjusted/first(adjusted)-1)
pdata %>%
ggplot(aes(x = date, y = performance, color = symbol)) +
geom_line() +
geom_label(data = filter(pdata, date == max(date)), aes(label = percent(performance)), size = 3, alpha = 0.5) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_x_date(date_labels = "%b") +
facet_wrap(~year, scale = "free") +
labs(x = "", y = "") +
theme(legend.position = c(0.75,0.15))
In 2018 the SPX dropped by 5%. You’d expect the SSO to lose 10%. Actually it lost 16%. And the UPRO? It lose a 27%, while one might have expected just a 15% drop.
It’s similar in 1016 where the SPX went up by 14%. The SSO? 25% instead of the expected 28%. And just 37% for the UPRO (and not 42%).
The main reason for this gap is due to the compounding effect.
Assume the following price of an index over 5 days: 100, 105, 110, 106, 102. The performance over these 5 days is 2%. Here’s the table:
index <- tibble(price = c(100, 105, 110, 106, 102)) %>%
mutate(return = price/lag(price)-1,
return_percent = percent(return))
index %>%
select(price, return_percent) %>%
knitr::kable()
price | return_percent |
---|---|
100 | NA% |
105 | 5.00% |
110 | 4.76% |
106 | -3.64% |
102 | -3.77% |
A simple ETF would return just the underlying index if we disregard any fee or frictions (which would not matter over 5 days anyway).
A 2x leveraged ETF doubles the daily return, hence would give you the following:
index <- index %>%
mutate(return_2x = 2*(price/lag(price)-1),
return_percent_2x = percent(return_2x))
index %>%
select(price, return_percent, return_percent_2x) %>%
knitr::kable()
price | return_percent | return_percent_2x |
---|---|---|
100 | NA% | NA% |
105 | 5.00% | 10.0% |
110 | 4.76% | 9.5% |
106 | -3.64% | -7.3% |
102 | -3.77% | -7.5% |
The problem araises when you compound the returns. Say the LETF also has a starting price of 100.
index <- index %>%
mutate(helper = (1+return_2x),
helper = if_else(is.na(helper), 1, helper)) %>%
mutate(performance_2x = cumprod(helper)-1) %>%
mutate(price_2x = 100*(performance_2x+1))
index %>%
select(price, return_percent, return_percent_2x, price_2x) %>%
knitr::kable()
price | return_percent | return_percent_2x | price_2x |
---|---|---|---|
100 | NA% | NA% | 100.0000 |
105 | 5.00% | 10.0% | 110.0000 |
110 | 4.76% | 9.5% | 120.4762 |
106 | -3.64% | -7.3% | 111.7143 |
102 | -3.77% | -7.5% | 103.2830 |
So the performance over the 5 days is \(\dfrac{103.283}{100}-1\) or 3.283%. Not the 4% what one could have expected.
That’s the compounding effect.
The compounding effect is mostly negative but it can be positive when a strong the price follows a strong trend. Say the index goes up 10% in each of 3 consecutive days. That’s a 33.1% index return \((1+10\%)^3-1\). The 2x leveraged goes up 20% each day or \((1+20\%)^3\) in over the 3 days. That’s a whopping 72.8%, 39.7 percentage points more than twice the index!
The compounding effect is also present with short-ETFs (leverage -1, -2, -3).
Without going for a mathematical proof if can be said that the compounding effect affects the performance to the worse when:
The compounding effect can be to the benefit of the investor when:
The fees are higher for LETFs. Here’s the published expense ratios for the S&P 500 LETFs from ProShare and the ETF from State Street SPDR:
etfs %>%
filter(Ind == "SPXT") %>%
mutate(ER = percent(ER_2018/100)) %>%
select(Ticker, Issuer, Leverage, ER) %>%
knitr::kable()
Ticker | Issuer | Leverage | ER |
---|---|---|---|
SPXU | ProShares | -3 | 0.900% |
SDS | ProShares | -2 | 0.890% |
SH | ProShares | -1 | 0.890% |
SPY | State Street SPDR | 1 | 0.090% |
SSO | ProShares | 2 | 0.900% |
UPRO | ProShares | 3 | 0.970% |
There is some performance lag that we cannot explain with the compounding effect nor with the elevated expense ratios. Inefficiencies such as imperfect replication, market abnormalities, product-specific risks etc. that shall not be further investigated here may lead to a negative alpha. This may be amplified in LETFs.
We find that LETFs have lag behind in terms of performance, i.e. have negative alpha. The question is if one can take advantage of this observation with a specific trading strategies.
Consider the following stratetegy:
The portfolio should be market neutral if at any moment the long position balances out the short position. That would be weight them 100% and -50% respectively.
Let’s say we started doing so in 2019:
strategy <- prices_d %>%
filter(year(date) == 2019,
symbol %in% c("SPY", "SSO")) %>%
select(date, symbol, adjusted) %>%
pivot_wider(names_from = symbol, values_from = adjusted) %>%
mutate(RaSPY = SPY/lag(SPY)-1,
RaSSO = SSO/lag(SSO)-1) %>%
mutate(RaPF = RaSPY-0.5*RaSSO)
strategy %>%
mutate(performance = cumprod(if_else(is.na(RaPF), 0, RaPF)+1)-1) %>%
ggplot(aes(x = date, y = performance)) +
geom_line() +
scale_y_continuous(labels = scales::percent_format(accuracy = 0.001)) +
scale_x_date(date_labels = "%b %y") +
labs(x = "", y = "")
Neat upward line, looks great until you see the scale. Also one shall not forget, that for this computation to hold true, it required daily rebalancing. The broker fee plus the cost-to-short might eat all the profit.
By the way, other years were similar:
strategy <- prices_d %>%
filter(year(date) %in% 2010:2019,
symbol %in% c("SPY", "SSO")) %>%
select(date, symbol, adjusted) %>%
pivot_wider(names_from = symbol, values_from = adjusted) %>%
group_by(year = paste0("P", year(date))) %>%
mutate(RaSPY = SPY/lag(SPY)-1,
RaSSO = SSO/lag(SSO)-1) %>%
mutate(RaPF = RaSPY-0.5*RaSSO)
strategy %>%
mutate(performance = cumprod(if_else(is.na(RaPF), 0, RaPF)+1)-1) %>%
ungroup() %>%
select(date, performance, year) %>%
mutate(date = dmy(paste(day(date), month(date), "2000")),
year = factor(str_sub(year, 2))) %>%
ggplot(aes(x = date, y = performance, color = year)) +
geom_line() +
scale_y_continuous(labels = scales::percent_format(accuracy = 0.001)) +
scale_x_date(date_labels = "%b") +
labs(x = "", y = "", title = "performance of long SPY hedged with 1/2 SSO short")
Let’s be more aggressive by going short on both the UPRO and the SPXU at the same time.
strategy <- prices_d %>%
filter(year(date) %in% 2010:2019,
symbol %in% c("UPRO", "SPXU")) %>%
select(date, symbol, adjusted) %>%
pivot_wider(names_from = symbol, values_from = adjusted) %>%
group_by(year = paste0("P", year(date))) %>%
mutate(RaUPRO = UPRO/lag(UPRO)-1,
RaSPXU = SPXU/lag(SPXU)-1) %>%
mutate(RaPF = -RaUPRO-RaSPXU)
strategy %>%
mutate(performance = cumprod(if_else(is.na(RaPF), 0, RaPF)+1)-1) %>%
ungroup() %>%
select(date, performance, year) %>%
mutate(date = dmy(paste(day(date), month(date), "2000")),
year = factor(str_sub(year, 2))) %>%
ggplot(aes(x = date, y = performance, color = year)) +
geom_line() +
scale_y_continuous(labels = scales::percent_format(accuracy = 0.001)) +
scale_x_date(date_labels = "%b") +
labs(x = "", y = "", title = "performance of long SPY hedged with 1/2 SSO short")
This seems to have worked out before but since 2017 things turned. One would actually have made a loss on that. The reason is most probaly the compounding effect that played in favor of the LETF investors, as pointed out before due to the positive autocorrelation.
So the above performance are kind of theoretical. One is assumed to rebalance the holdings on a daily basis to stay market neutral. That comes at a cost, mainly in broker fees (and your time or cost for a robot).
Secondly, short-selling a security requires one to first borrow it. This is not free. Also some securities might not even be avilable for shorting.
Here’s the list for shortable securities on Interactive Brokers: https://ibkr.info/article/2024 (follow the FTP link).
shortable <- read_delim("usa.txt", delim = "|", skip = 1) %>%
rename(SYM = 1)
## Warning: Missing column names filled in: 'X9' [9]
## Parsed with column specification:
## cols(
## `#SYM` = col_character(),
## CUR = col_character(),
## NAME = col_character(),
## CON = col_double(),
## ISIN = col_character(),
## REBATERATE = col_double(),
## FEERATE = col_double(),
## AVAILABLE = col_character(),
## X9 = col_logical()
## )
## Warning: 1 parsing failure.
## row col expected actual file
## 11361 -- 9 columns 2 columns 'usa.txt'
shortable %>%
filter(SYM %in% c("SPY", "SSO", "UPRO", "SPXU"))
## # A tibble: 4 x 9
## SYM CUR NAME CON ISIN REBATERATE FEERATE AVAILABLE X9
## <chr> <chr> <chr> <dbl> <chr> <dbl> <dbl> <chr> <lgl>
## 1 SPXU USD PROSH ULTR… 3.17e8 XXXXX… 0.469 1.07 1200000 NA
## 2 SPY USD SPDR S&… 7.57e5 XXXXX… 1.29 0.25 >10000000 NA
## 3 SSO USD PROSHARES … 3.96e7 XXXXX… 0.373 1.17 80000 NA
## 4 UPRO USD PROSHARES … 6.12e7 XXXXX… 0.272 1.27 450000 NA
Contrary the the SPY, the LETFs are considerably more costly to short. The fee would just inter the little profit one would have made from following a market neutral LETF exploit strategy.