library(tidyverse)
library(lubridate)
library(scales)
library(ggrepel)
library(tidyquant)
library(jsonlite)
theme_set(theme_minimal())
invisible(Sys.setlocale("LC_TIME", "en_US.UTF-8"))
library(broom)
library(purrr)
library(DT)
library(familyoffice) # prop package
library(factoextra) # install required this (1st answer) https://stackoverflow.com/questions/59055291/problem-installing-factoextra-package-in-r
library(ggcorrplot)
I believe that managed futures have been around for quite some time. More recently, ETFs promising a managed futures strategy have been launched. I wonder if these ETFs are uncorrelated with the stock market. Let’s find out.
Managed futures basically can go long or short in futures contracts on a variety of asset classes. The idea is that they can profit from both rising and falling markets. Often, the strategy is trend-following, meaning that they buy when the price is rising and sell when the price is falling, may these be stocks, bonds, commodities, or currencies. Especially commodities and currencies are often used in managed futures strategies.
From what I’ve understand, there are several main approaches to packaging managed futures into an ETF:
# source: https://etfdb.com/etfs/asset-class/alternatives/#etfs&sort_name=assets_under_management&sort_order=desc&page=2
etfdb <- "FTLS First Trust Long/Short Equity ETF $1,537.78 14.39% 138,165 $63.64 -0.38%
DBMF iMGP DBi Managed Futures Strategy ETF $976.96 9.13% 365,870 $27.48 -0.29%
QAI NYLI Hedge Multi-Strategy Tracker ETF $629.90 6.54% 149,294 $32.07 0.03%
RLY SPDR SSgA Multi-Asset Real Return ETF $506.88 8.00% 63,355 $28.93 0.52%
BTAL AGF U.S. Market Neutral Anti-Beta Fund $403.25 15.99% 609,150 $19.66 -0.56%
KMLM KFA Mount Lucas Managed Futures Index Strategy ETF $353.64 -3.96% 87,059 $27.68 -0.36%
CTA Simplify Managed Futures Strategy ETF $289.80 14.09% 140,845 $26.31 1.43%
FMF First Trust Managed Futures Strategy Fund $238.65 3.60% 50,676 $46.84 0.17%
MNA NYLI Merger Arbitrage ETF $232.28 4.68% 56,650 $32.88 0.03%
RSST Return Stacked U.S. Stocks & Managed Futures ETF $225.39 15.44% 98,283 $23.33 -0.26%
FLSP Franklin Systematic Style Premia ETF $205.88 10.35% 11,852 $23.89 -0.04%
WTMF WisdomTree Managed Futures Strategy Fund $202.82 0.20% 30,508 $35.14 -0.28%
CLSE Convergence Long/Short Equity ETF $175.88 32.98% 54,780 $22.68 0.00%
EQLS Simplify Market Neutral Equity Long/Short ETF $143.22 -0.80% 7,750 $21.89 0.14%
PFIX Simplify Interest Rate Hedge ETF $125.44 16.58% 59,270 $45.35 2.25%
QIS Simplify Multi-QIS Alternative ETF $107.38 -1.56% 6,476 $24.08 0.38%
PRMN PlanRock Market Neutral Income ETF $104.90 0.44% 1,603 $29.01 0.14%
RSBT Return Stacked Bonds & Managed Futures ETF $96.04 -1.10% 28,600 $17.06 0.00%
UPAR UPAR Ultra Risk Parity ETF $82.61 7.97% 17,865 $14.43 0.00%
HTUS Capitol Series Trust Hull Tactical US ETF $78.67 23.43% 17,885 $44.13 -0.07%
ARB AltShares Merger Arbitrage ETF $72.72 4.00% 9,161 $27.82 0.04%
FIAX Nicholas Fixed Income Alternative ETF $71.03 5.43% 53,667 $19.66 -0.10%
VAMO Cambria Value & Momentum ETF $51.80 4.74% 2,891 $29.44 -0.54%
EHLS Even Herd Long Short ETF $50.73 N/A 3,502 $21.02 -0.57%
LBAY Leatherback Long/Short Alternative Yield ETF $41.95 8.55% 2,355 $27.46 -0.15%
TPMN Timothy Plan Market Neutral ETF $40.05 0.80% 2,591 $23.83 0.38%
CBLS Clough Hedged Equity ETF $35.05 23.11% 3,285 $25.24 0.08%
HFND Unlimited HFND Multi-Strategy Return Tracker ETF $34.83 7.90% 8,120 $22.46 -0.13%
FFLS Future Fund Long/Short ETF $34.45 15.75% 6,979 $23.39 0.52%
BSR Beacon Selective Risk ETF $30.92 13.23% 414 $29.36 -0.37%
MARB First Trust Merger Arbitrage ETF $28.87 -0.07% 6,709 $19.92 0.10%
HDG ProShares Hedge Replication ETF $28.16 4.70% 1,809 $49.69 -0.16%
ARP PMV Adaptive Risk Parity ETF $28.03 13.97% 2,065 $28.66 0.10%
FORH Formidable ETF $23.89 4.14% 2,242 $23.26 -0.21%
LSEQ Harbor Long-Short Equity ETF $21.90 13.29% 1,041 $27.19 -0.77%
RINF ProShares Inflation Expectations ETF $21.67 8.65% 5,206 $33.03 0.36%
HF DGA Core Plus Absolute Return ETF $19.00 12.70% 444 $23.29 -0.04%
ZIVB -1x Short VIX Mid-Term Futures Strategy ETF $14.32 3.88% 32,614 $19.39 0.10%
MSVX LHA Market State Alpha Seeker ETF $11.02 -7.46% 6,953 $22.05 0.00%
TOAK Twin Oak Short Horizon Absolute Return ETF $10.52 N/A N/A $26.99 0.04%
MRGR Proshares Merger ETF $10.50 5.06% 636 $42.05 0.00%
CLIX ProShares Long Online/Short Stores ETF $9.27 25.73% 1,238 $47.66 0.17%
ASMF Virtus Alphasimplex Managed Futures ETF $9.03 N/A 2,611 $24.10 -0.21%
EVNT AltShares Event-Driven ETF $4.88 4.23% 426 $10.61 0.00%
USE USCF Energy Commodity Strategy Absolute Return Fund $3.96 29.08% 192 $40.70 2.62%
RYSE Vest 10 Year Interest Rate Hedge ETF $2.28 3.69% 4,803 $22.80 0.18%
RATE Global X Interest Rate Hedge ETF $1.86 3.05% 992 $18.70 0.75%
HYKE Vest 2 Year Interest Rate Hedge ETF $1.27 N/A 874 $25.35 -0.55%
AQMIX AQR Managed Futures Strategy Fund Class I $1454.00 -0.10% 1,000 $10.00 0.00%"
etfdb <- read_lines(etfdb) |>
as_tibble() |>
separate(value, sep = "\t", into = c("ticker", "name", "aum", "ytd", "volume", "price", "change"))
Besides, managed futures, there are other alternative ETFs around, which might also be able to deliver uncorrelated returns. ETFdb.com lists 49 Alternative ETFs, among which some MF ones that gained a lot of popularity in the past years, e.g. DBMD, CTA, or RSST.
We manually add AQMIX AQR Managed Futures Strategy Fund Class I to the list which is a mutual fund but has in comparison to the ETFs has been around for longer (since 2010).
DT::datatable(etfdb,
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
fixedHeader=TRUE))
We download price data from Yahoo Finance.
etf_prices <- tq_get(etfdb$ticker, from = 0)
etf_prices |> saveRDS("data/uncorrelatedetfs/etf_prices.RDS")
spy <- tq_get("SPY", from = 0)
spy |> saveRDS("data/uncorrelatedetfs/spy.RDS")
agg <- tq_get("AGG", from = 0)
agg |> saveRDS("data/uncorrelatedetfs/agg.RDS")
etf_prices <- readRDS("data/uncorrelatedetfs/etf_prices.RDS")
spy <- readRDS("data/uncorrelatedetfs/spy.RDS")
agg <- readRDS("data/uncorrelatedetfs/agg.RDS")
etf_prices |>
group_by(symbol) |>
summarise(from = min(date),
to = max(date)) |>
ungroup() |>
left_join(etfdb, by = c("symbol" = "ticker")) |>
mutate(aum = parse_number(aum),
MF = ifelse(grepl("Managed", name), "MF", "Other")) |>
ggplot() +
geom_segment(aes(x = from, xend = to, y = reorder(symbol, desc(from)), yend = reorder(symbol, desc(from)),
color = aum),
size = 2) +
# highlight managed futures
geom_segment(data = ~. |> filter(MF == "MF"),
aes(x = from, xend = to, y = reorder(symbol, desc(from)), yend = reorder(symbol, desc(from)),
color = aum),
size = 2) +
geom_text(aes(x = from, y = reorder(symbol, desc(from)), label = symbol),
nudge_x = -1, hjust = 1) +
# red dot if MF
geom_point(data = ~. |> filter(MF == "MF"),
aes(x = from, y = reorder(symbol, desc(from))),
size = 2, color = "red") +
labs(title = "Alt ETFs Inception and AUM (red dot = Managed Futures)",
x = NULL, y = NULL, color = "AUM M$") +
scale_color_viridis_c() +
theme(legend.position = "bottom")
etf_returns <- etf_prices |>
group_by(symbol) |>
transmute(symbol, date, r = adjusted/lag(adjusted)-1) |>
ungroup() |>
drop_na()
annual_rets <- etf_returns |>
group_by(symbol, year = year(date)) |>
filter(n() > 240 | year == 2024) |>
summarise(r = prod(1+r)-1)
annual_rets |>
group_by(symbol) |>
mutate(minyear = min(year)) |>
ggplot(aes(x = year, y = reorder(symbol, -minyear), fill = r>0)) +
geom_tile(color = "white") +
geom_text(aes(label = scales::percent(r, accuracy = 0.1)), size = 3, color = "white") +
scale_fill_manual(values = c("red", "darkgreen")) +
scale_x_continuous(breaks = seq(2000, 2050, 1)) +
labs(title = "Annual Returns",
x = NULL, y = NULL, fill = NULL) +
theme(legend.position = "none")
The main selling point of managed futures is that they deliver positive returns while being uncorrelated with other asset classes. We look at the uncorrelatedness in a moment, but for now in the chart above, we see that the returns of can vary quite a bit from year to year. Interestingly, in 2024 so far, most of the ETFs have delivered positive returns (only 7/48 red).
pos_years <- annual_rets |>
filter(year < 2024) |>
group_by(r = r > 0) |>
count() |>
ungroup() |>
mutate(percent = n/sum(n)) |>
filter(r)
Excluding 2024, 62% of the annual returns were positive.
The big question is: Are these ETFs uncorrelated?
stock_bond_rets <- bind_rows(spy |> transmute(date, symbol, r = adjusted/lag(adjusted)-1),
agg |> transmute(date, symbol, r = adjusted/lag(adjusted)-1)) |>
drop_na() |>
pivot_wider(names_from = symbol, values_from = r) |>
drop_na()
# r ~ SPY + AGG
# grouped by symbol (later also by year)
stock_corr <- etf_returns |>
left_join(stock_bond_rets, by = "date") |>
group_by(symbol) |>
filter(n() > 252) |>
tq_mutate_xy(
x = SPY,
y = r,
mutate_fun = runCor,
# runCor args
n = 252,
use = "pairwise.complete.obs",
# tq_mutate args
col_rename = "stock_corr"
)
stock_corr |>
drop_na() |>
ggplot(aes(x = date, y = stock_corr, color = symbol)) +
geom_line(size = 0.1) +
geom_text_repel(aes(label = ifelse(date == max(date), symbol, NA_character_)),
nudge_x = 1, nudge_y = 0.01, size = 2.3, direction = "y") +
geom_hline(yintercept = 0, linetype = "dashed", color = "red", size = 0.1) +
scale_y_continuous(limits = c(-1, 1)) +
labs(title = "Stock Market Correlation (rolling 252 days)",
x = NULL, y = NULL, color = NULL) +
theme(legend.position = "none")
bond_corr <- etf_returns |>
left_join(stock_bond_rets, by = "date") |>
group_by(symbol) |>
filter(n() > 252) |>
tq_mutate_xy(
x = AGG,
y = r,
mutate_fun = runCor,
# runCor args
n = 252,
use = "pairwise.complete.obs",
# tq_mutate args
col_rename = "bond_corr"
)
bond_corr |>
drop_na() |>
ggplot(aes(x = date, y = bond_corr, color = symbol)) +
geom_line(size = 0.1) +
geom_text_repel(aes(label = ifelse(date == max(date), symbol, NA_character_)),
nudge_x = 1, nudge_y = 0.01, size = 2.3, direction = "y") +
geom_hline(yintercept = 0, linetype = "dashed", color = "blue", size = 0.1) +
scale_y_continuous(limits = c(-1, 1)) +
labs(title = "Bond Market Correlation (rolling 252 days)",
x = NULL, y = NULL, color = NULL) +
theme(legend.position = "none")
While stock correlations seem to tend to be in the positive range (some like BTAL AGF U.S. Market Neutral Anti-Beta Fund were actually negatively correlated to stocks), bond correlations are more around zero.
We would also like to know the stock and bond market betas.
stock_beta <- etf_returns |>
left_join(stock_bond_rets, by = "date") |>
group_by(symbol) |>
nest() |>
mutate(
model = map(data, ~ lm(r ~ SPY + AGG, data = .x)),
tidied = map(model, tidy)
) |>
unnest(tidied) |>
filter(term %in% c("SPY", "AGG")) |>
select(symbol, term, estimate, p.value) |>
pivot_wider(names_from = term, values_from = c(estimate, p.value)) |>
rename(coefSPY = estimate_SPY,
pvalSPY = p.value_SPY,
coefAGG = estimate_AGG,
pvalAGG = p.value_AGG) |>
left_join(
etf_returns |> group_by(symbol) |> summarise(from = min(date)),
by = "symbol"
)
stock_beta |>
ggplot(aes(x = coefSPY, y = coefAGG, color = from)) +
geom_point() +
geom_text_repel(aes(label = symbol), nudge_x = 0.01, nudge_y = 0.01) +
labs(title = "Stock and Bond Market Betas",
x = "Stock Beta", y = "Bond Beta", color = "Inception") +
theme(legend.position = "none")
The stock betas are mostly positive, but some are negative. The bond betas are mostly around zero.
DT::datatable(stock_beta |>
left_join(etfdb, by = c("symbol" = "ticker")) |>
select(symbol, name, coefSPY, pvalSPY, coefAGG, pvalAGG),
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
fixedHeader=TRUE)) |>
# coefSPY, coefAGG, pvalSPY, pvalAGG all show max 2 digits 0.01
formatRound(columns = c("coefSPY", "coefAGG", "pvalSPY", "pvalAGG"),
digits = 2, )
stock_beta |>
left_join(etfdb, by = c("symbol" = "ticker")) |>
mutate(MF = ifelse(grepl("Managed", name), "MF", "Other")) |>
ggplot(aes(x = coefSPY, y = coefAGG, color = MF)) +
geom_point() +
geom_text_repel(aes(label = ifelse(MF == "MF", symbol, NA_character_)),
nudge_x = 0.01, nudge_y = 0.01, size = 3) +
scale_color_manual(values = c("red", "black")) +
labs(title = "Stock and Bond Market Betas",
x = "Stock Beta", y = "Bond Beta", color = "Managed Futures")
We see that the main managed futures ETFs have a stock beta around 0 indicating that they are indeed uncorrelated with the stock market. The bond beta is around zero or in the negative range. The latter would mean that as bond prices rise, the managed futures ETFs tend to fall in price, or some positive dependency on yields.
Some exceptions are RSST Return Stacked U.S. Stocks & Managed Futures ETF with a stock beta of 1.5, but the fund name suggests that it is not a pure managed futures ETF. Also, ASMF Virtus Alphasimplex Managed Futures ETF had a positive stock beta of 0.6, however, it only having launched in May 2024, this stat not be very reliable.
The following table shows the managed futures ETFs sorted by the percentage of months with positive returns.
mf_returns_m <- etf_returns |>
left_join(etfdb, by = c("symbol" = "ticker")) |>
# only MF
filter(grepl("Managed", name)) |>
group_by(symbol, name, date = floor_date(date, "month")) |>
filter(n() > 18) |>
summarise(r = prod(1+r)-1)
stock_bond_rets_m <- stock_bond_rets |>
pivot_longer(cols = c(SPY, AGG), names_to = "symbol", values_to = "r") |>
group_by(symbol, name = "", date = floor_date(date, "month")) |>
filter(n() > 18) |>
summarise(r = prod(1+r)-1)
mf_returns_m |>
bind_rows(stock_bond_rets_m) |>
group_by(symbol, name, return = ifelse(r > 0, "up", "down")) |>
count() |>
ungroup() |>
pivot_wider(names_from = return, values_from = n) |>
mutate(percentUP = up/(up+down)) |>
arrange(percentUP) |>
DT::datatable(~.,
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
pageLength = 12,
fixedHeader=TRUE)) |>
formatPercentage(columns = c("percentUP"), digits = 1)
The following charts show the cumulative returns.
mf_returns_m |>
bind_rows(stock_bond_rets_m) |>
# histogram for each (facet wrap)
ggplot(aes(x = r)) +
geom_histogram(bins = 30) +
geom_vline(xintercept = 0, linetype = "dashed", color = "red") +
facet_wrap(~symbol, scales = "free") +
labs(title = "Monthly Returns",
x = NULL, y = NULL, fill = NULL) +
scale_x_continuous(labels = scales::percent_format(accuracy = 0.1),
limits = c(-0.15, 0.15)) +
theme(legend.position = "none")
The returns are somewhat symmetrically distributed around zero, especially compared to the SPY and AGG returns. Stock returns (SPY) are more skewed to the left meaning that there are some months with stronger negative returns.
# scatterplot SPY vs individual ETFs
mf_returns_m |>
left_join(stock_bond_rets_m |>
pivot_wider(names_from = symbol, values_from = r),
by = "date") |>
ggplot(aes(x = SPY, y = r)) +
geom_point(size = 0.3) +
geom_smooth(method = "lm") +
facet_wrap(~symbol, scales = "free") +
scale_x_continuous(labels = scales::percent_format(accuracy = 0.1), limits = c(-0.1, 0.1)) +
scale_y_continuous(labels = scales::percent_format(accuracy = 0.1), limits = c(-0.1, 0.1)) +
labs(title = "Monthly Returns vs SPY",
x = "SPY", y = NULL) +
theme(legend.position = "none")
etf_returns |>
bind_rows(stock_bond_rets |> pivot_longer(cols = c(SPY, AGG), names_to = "symbol", values_to = "r")) |>
group_by(symbol, date = floor_date(date, "month")) |>
filter(n() > 18) |>
summarise(r = prod(1+r)-1) |>
summarise(annualizedret = prod(1+r)^(12/n())-1,
annualizedvola = sd(r)*sqrt(12),
SR = annualizedret/annualizedvola,
since = min(date)) |>
DT::datatable(~.,
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
fixedHeader=TRUE)) |>
formatPercentage(columns = c("annualizedret"), digits = 1) |>
formatPercentage(columns = c("annualizedvola"), digits = 1)
ret_mat <- mf_returns_m |>
bind_rows(stock_bond_rets_m) |>
group_by(symbol) |>
filter(min(date) < as.Date("2022-01-01")) |>
select(symbol, date, r) |>
pivot_wider(names_from = symbol, values_from = r) |>
drop_na() |>
arrange(date) |>
as.data.frame() |>
column_to_rownames(var = "date") |>
as.matrix()
pca_results <- ret_mat |>
prcomp(center = TRUE, scale. = TRUE)
pca_results$rotation |>
as.data.frame() |>
rownames_to_column(var = "symbol") |>
arrange(desc(PC1)) |>
select(symbol, PC1, PC2, PC3, PC4) |>
DT::datatable(~.,
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
fixedHeader=TRUE)) |>
formatPercentage(columns = c("PC1", "PC2", "PC3", "PC4"), digits = 1)
pca_results |> fviz_pca_biplot()
ret_mat <- mf_returns_m |>
bind_rows(stock_bond_rets_m) |>
group_by(symbol) |>
filter(min(date) < as.Date("2023-01-01")) |>
select(symbol, date, r) |>
pivot_wider(names_from = symbol, values_from = r) |>
drop_na() |>
arrange(date) |>
as.data.frame() |>
column_to_rownames(var = "date") |>
as.matrix()
pca_results <- ret_mat |>
prcomp(center = TRUE, scale. = TRUE)
pca_results$rotation |>
as.data.frame() |>
rownames_to_column(var = "symbol") |>
arrange(desc(PC1)) |>
select(symbol, PC1, PC2, PC3, PC4) |>
DT::datatable(~.,
extensions = c('FixedColumns',"FixedHeader"),
options = list(scrollX = F,
paging=T,
fixedHeader=TRUE)) |>
formatPercentage(columns = c("PC1", "PC2", "PC3", "PC4"), digits = 1)
pca_results |> fviz_pca_biplot()
cor_mat <- ret_mat |> cor()
ggcorrplot(cor_mat,
hc.order = TRUE,
type = "lower",
lab = TRUE,
colors = c("#E46726", "white", "#6D9EC1"))
There is clear correlation between the ETFs, especially the managed futures ones. The stock and bond market ETFs are negatively correlated with the managed futures ETFs. One exception is the WTMF WisdomTree Managed Futures Strategy Fund which is somwewhat positively correlated with the stock market (0.46).