Definition of a Leveraged ETF

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.

Why LETFs can’t keep up with the expected performance

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.

The compounding effect in Leveraged-ETFs

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:

  • volatility is higher
  • the higher the leverage factor
  • with negative leverage factors (short LETFs)

The compounding effect can be to the benefit of the investor when:

  • there’s a directional trend (autocorrelation)

Expense ratio

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%

Performance lag

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.

Trading strategies

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.

long SPY, short SSO

Consider the following stratetegy:

  • long SPY (1x ETF)
  • short SSO (2x ETF)

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")

short UPRO, short SPXU

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.

It’s not that easy in reality

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&amp…  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.