This post is about my replication attempt of Logical Invest’s “Hell On Fire” strategy — which is its Universal Investment Strategy using SPXL and TMF (aka the 3x leveraged ETFs). I don’t match their results, but I do come close.
It seems that some people at Logical Invest have caught whiff of some of the work I did in replicating Harry Long’s ideas. First off, for the record, I’ve actually done some work with Harry Long in private, and the strategies we’ve worked on together are definitely better than the strategies he has shared for free, so if you are an institution hoping to vet his track record, I wouldn’t judge it by the very much incomplete frameworks he posts for free.
This post’s strategy is the Logical Invest Universal Investment Strategy leveraged up three times over. Here’s the link to their newest post. Also, I’m happy to see that they think positively of my work.
In any case, my results are worse than those on Logical Invest’s, so if anyone sees a reason for the discrepancy, please let me know.
Here’s the code for the backtest–most of it is old, from my first time analyzing Logical Invest’s strategy.
LogicalInvestUIS <- function(returns, period = 63, modSharpeF = 2.8) { returns[is.na(returns)] <- 0 #impute any NAs to zero configs <- list() for(i in 1:11) { weightFirst <- (i-1)*.1 weightSecond <- 1-weightFirst config <- Return.portfolio(R = returns, weights=c(weightFirst, weightSecond), rebalance_on = "months") configs[[i]] <- config } configs <- do.call(cbind, configs) cumRets <- cumprod(1+configs) #rolling cumulative rollAnnRets <- (cumRets/lag(cumRets, period))^(252/period) - 1 rollingSD <- sapply(X = configs, runSD, n=period)*sqrt(252) modSharpe <- rollAnnRets/(rollingSD ^ modSharpeF) monthlyModSharpe <- modSharpe[endpoints(modSharpe, on="months"),] findMax <- function(data) { return(data==max(data)) } #configs$zeroes <- 0 #zeroes for initial periods during calibration weights <- t(apply(monthlyModSharpe, 1, findMax)) weights <- weights*1 weights <- xts(weights, order.by=as.Date(rownames(weights))) weights[is.na(weights)] <- 0 weights$zeroes <- 1-rowSums(weights) configCopy <- configs configCopy$zeroes <- 0 stratRets <- Return.portfolio(R = configCopy, weights = weights) weightFirst <- apply(monthlyModSharpe, 1, which.max) weightFirst <- do.call(rbind, weightFirst) weightFirst <- (weightFirst-1)*.1 align <- cbind(weightFirst, stratRets) align <- na.locf(align) chart.TimeSeries(align[,1], date.format="%Y", ylab=paste("Weight", colnames(returns)[1]), main=paste("Weight", colnames(returns)[1])) return(stratRets) }
In this case, rather than steps of 5% weights, I used 10% weights after looking at the Logical Invest charts more closely.
Now, let’s look at the instruments.
getSymbols("SPY", from="1990-01-01") getSymbols("TMF", from="1990-01-01") TMFrets <- Return.calculate(Ad(TMF)) getSymbols("TLT", from="1990-01-01") TLTrets <- Return.calculate(Ad(TLT)) tmf3TLT <- merge(TMFrets, 3*TLTrets, join='inner') charts.PerformanceSummary(tmf3TLT) Return.annualized(tmf3TLT[,2]-tmf3TLT[,1]) discrepancy <- as.numeric(Return.annualized(tmf3TLT[,2]-tmf3TLT[,1])) tmf3TLT[,2] <- tmf3TLT[,2] - ((1+discrepancy)^(1/252)-1) modifiedTLT <- 3*TLTrets - ((1+discrepancy)^(1/252)-1) rets <- merge(3*Return.calculate(Ad(SPY)), modifiedTLT, join='inner') colnames(rets) <- gsub("\\.[A-z]*", "", colnames(rets)) leveragedReturns <- rets colnames(leveragedReturns) <- paste("Leveraged", colnames(leveragedReturns), sep="_") leveragedReturns <- leveragedReturns[-1,]
Again, more of the same that I did from my work analyzing Harry Long’s strategies to get a longer backtest of SPXL and TMF (aka leveraged SPY and TLT).
Now, let’s look at some configurations.
hof <- LogicalInvestUIS(returns = leveragedReturns, period = 63, modSharpeF = 2.8) hof2 <- LogicalInvestUIS(returns = leveragedReturns, period = 73, modSharpeF = 3) hof3 <- LogicalInvestUIS(returns = leveragedReturns, period = 84, modSharpeF = 4) hof4 <- LogicalInvestUIS(returns = leveragedReturns, period = 42, modSharpeF = 1.5) hof5 <- LogicalInvestUIS(returns = leveragedReturns, period = 63, modSharpeF = 6) hof6 <- LogicalInvestUIS(returns = leveragedReturns, period = 73, modSharpeF = 2) hofComparisons <- cbind(hof, hof2, hof3, hof4, hof5, hof6) colnames(hofComparisons) <- c("d63_F2.8", "d73_F3", "d84_F4", "d42_F1.5", "d63_F6", "d73_F2") rbind(table.AnnualizedReturns(hofComparisons), maxDrawdown(hofComparisons), CalmarRatio(hofComparisons))
With the following statistics:
> rbind(table.AnnualizedReturns(hofComparisons), maxDrawdown(hofComparisons), CalmarRatio(hofComparisons)) d63_F2.8 d73_F3 d84_F4 d42_F1.5 d63_F6 d73_F2 Annualized Return 0.3777000 0.3684000 0.2854000 0.1849000 0.3718000 0.3830000 Annualized Std Dev 0.3406000 0.3103000 0.3010000 0.4032000 0.3155000 0.3383000 Annualized Sharpe (Rf=0%) 1.1091000 1.1872000 0.9483000 0.4585000 1.1785000 1.1323000 Worst Drawdown 0.5619769 0.4675397 0.4882101 0.7274609 0.5757738 0.4529908 Calmar Ratio 0.6721751 0.7879956 0.5845827 0.2541127 0.6457823 0.8455274
It seems that the original 73 day lookback, sharpe F of 2 had the best performance.
Here are the equity curves (log scale because leveraged or volatility strategies look silly at regular scale):
chart.TimeSeries(log(cumprod(1+hofComparisons)), legend.loc="topleft", date.format="%Y", main="Hell On Fire Comparisons", ylab="Value of $1", yaxis = FALSE) axis(side=2, at=c(0, 1, 2, 3, 4), label=paste0("$", round(exp(c(0, 1, 2, 3, 4)))), las = 1)
In short, sort of upwards from 2002 to the crisis, where all the strategies take a dip, and then continue steadily upwards.
Here are the drawdowns:
dds <- PerformanceAnalytics:::Drawdowns(hofComparisons) chart.TimeSeries(dds, legend.loc="bottomright", date.format="%Y", main="Drawdowns Hell On Fire Variants", yaxis=FALSE, ylab="Drawdown", auto.grid=FALSE) axis(side=2, at=seq(from=0, to=-.7, by = -.1), label=paste0(seq(from=0, to=-.7, by = -.1)*100, "%"), las = 1)
Basically, some regular bumps along the road given the CAGRs (that is, if you’re going to leverage something that has an 8% drawdown on the occasion three times over, it’s going to have a 24% drawdown on those same occasions, if not more), and the massive hit in the crisis when bonds take a hit, and on we go.
In short, this strategy is basically the same as the original strategy, just leveraged up, so for those with the stomach for it, there you go. Of course, Logical Invest is leaving off some details, since I’m not getting a perfect replica. Namely, their returns seem slightly higher, and their drawdowns slightly lower. I suppose that’s par for the course when selling subscriptions and newsletters.
One last thing, which I think people should be aware of–when people report statistics on their strategies, make sure to ask the question as to which frequency. Because here’s a quick little modification, going from daily returns to monthly returns:
> betterStatistics <- apply.monthly(hofComparisons, Return.cumulative) > rbind(table.AnnualizedReturns(betterStatistics), maxDrawdown(betterStatistics), CalmarRatio(betterStatistics)) d63_F2.8 d73_F3 d84_F4 d42_F1.5 d63_F6 d73_F2 Annualized Return 0.3719000 0.3627000 0.2811000 0.1822000 0.3661000 0.377100 Annualized Std Dev 0.3461000 0.3014000 0.2914000 0.3566000 0.3159000 0.336700 Annualized Sharpe (Rf=0%) 1.0746000 1.2036000 0.9646000 0.5109000 1.1589000 1.119900 Worst Drawdown 0.4323102 0.3297927 0.4100792 0.6377512 0.4636949 0.311480 Calmar Ratio 0.8602366 1.0998551 0.6855148 0.2856723 0.7894636 1.210563
While the Sharpe ratios don’t improve too much, the Calmars (aka the return to drawdown) statistics increase dramatically. EG, imagine a month in which there’s a 40% drawdown, but it ends at a new equity high. A monthly return series will sweep that under the rug, or, for my fellow Jewish readers, pass over it. So, be wary.
Thanks for reading.
NOTE: I am a freelance consultant in quantitative analysis on topics related to this blog. If you have contract or full time roles available for proprietary research that could benefit from my skills, please contact me through my LinkedIn here.
Pingback: The Whole Street’s Daily Wrap for 4/6/2015 | The Whole Street
Hi Ilya,
This strategy is very interesting! I replicated it on Quantopian with a small tweak here if you are interested: https://www.quantopian.com/posts/the-logical-invest-hell-on-fire
I capped the weights at 20% – 80% rather than 0% – 100%, and I used increments of 1% rather than 10%. I also rebalanced weekly.
Ilya, you make the point that leverage a CAGR which entails an 8% drawdown three times will cause a drawdown of 24% or more at some stage (if history repeats). Consider then the case of stock indices. The 2008/9 collapse in the S&P 500 was 55%. Leveraging this three times causes a drawdown of 100% (since the ETF or program goes to zero rather than minus 165%).
So I will take a look at your other post to see how you back filled the SPY.
I am puzzled!
Except it does not quite work like that perhaps. Leveraging up the S&P 500 TR index seems to produce a max drawdown of just under 100%. So the ETF survives. Just.
Because of course you can’t go below zero with a geared ETF. Nonetheless you can effectively lose 100%. Doing the calculations on the DJIA since 1900 produces wipe out after 1929.
And in practice because of margin calls the ETF would be shut down long before that. So for all intents and purposes an ETF geared 3 times is NOT a suitable long term investment.
Pingback: Backtest: Sistema de trading con ETFs apalancados