The Logical Invest “Hell On Fire” Replication Attempt

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.

6 thoughts on “The Logical Invest “Hell On Fire” Replication Attempt

  1. Pingback: The Whole Street’s Daily Wrap for 4/6/2015 | The Whole Street

  2. 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.

  3. 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.

  4. Pingback: Backtest: Sistema de trading con ETFs apalancados

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s