Recently, Harry Long posted not one but four new articles on Seeking Alpha called It’s Amazing How Well Dumb Things Work II. The last time I replicated a strategy by Mr. Long, it came up short on the expectations it initially built. For the record, here are the links to the four part series.
While the strategy itself wasn’t the holy grail (nothing is, really, and it did outperform the S&P), it did pay off with some long-history ETF data for which I am extremely grateful (a project I’m currently working on involves those exact indices, I’ll see if I can blog about it), and it did give me the chance to show some R functionality that I hadn’t shown before that point, which in my opinion, made the endeavor more than worth it.
For this particular strategy, I’m not so sure that’s the case.
In short, the S&P 500, gold, and long-term bonds, rebalance to an equal-weight portfolio annually. Dirt simple. So simple, in fact, that I can backtest this strategy back to 1978!
The data I used was the actual GSPC index from Yahoo Finance, and some quandl data that I cleaned up with the quandClean function in my IKTrading package. While some of the futures I originally worked with have huge chunks of missing data, the long term bonds and gold futures are relatively intact. Gold had about 44 missing days in the 21st century when GLD had returns, so there may be some data integrity issues there, though the average return for GLD on those days is about -3 bps, so the general concept demonstration is intact. With the bonds, there were 36 missing days that TLT had returns for, but the average returns for those days were a -18 bps, so I instead imputed those missing days to zero.
In any case, here’s the script to set up the data:
require(IKTrading) require(PerformanceAnalytics) require(quantmod) getSymbols("^GSPC", from="1800-01-01") SPrets <- Return.calculate(Cl(GSPC)) goldFutures <- quandClean(stemCode = "CHRIS/CME_GC", verbose = TRUE) getSymbols("GLD", from="1990-01-01") #quandl's data had a few gaps--let's use GLD to fill them in. goldGLD <- merge(Cl(goldFutures), Cl(GLD), join='outer') goldRets <- Return.calculate(goldGLD) sum(is.na(goldRets[,1])) mean(goldRets[is.na(goldRets[,1]),2], na.rm=TRUE) goldRets[is.na(goldRets[,1]),1] <- goldRets[is.na(goldRets[,1]),2] #impute missing returns data with GLD returns data for that day goldRets <- goldRets[,1] thirtyBond <- quandClean(stemCode="CHRIS/CME_US", verbose = TRUE) getSymbols("TLT", from="1990-01-01") bondTLT <- merge(Cl(thirtyBond), Cl(TLT), join='outer') bondRets <- Return.calculate(bondTLT) sum(is.na(bondRets[,1])) mean(bondRets[is.na(bondRets[,1]),2], na.rm=TRUE) #18 basis points? Just going to impute as zero. bondRets[is.na(bondRets[,1]),1] <- 0 #there are 259 other such instances prior to this imputing bondRets <- bondRets[,1] SPbondGold <- cbind(SPrets, goldRets, bondRets) SPbondGold <- SPbondGold["1977-12-30::"] #start off at beginning of 1978, since that's when gold futures were first in inception colnames(SPbondGold) <- c("SandP", "Bonds", "Gold") DTW_II_returns <- Return.rebalancing(R = SPbondGold, weights = c(1/3, 1/3, 1/3), geometric = FALSE, rebalance_on = "years") stratSP <- merge(DTW_II_returns, SPrets, join='inner') colnames(stratSP) <- c("Harry Long Strategy", "S&P 500")
First, let’s recreate the original equity curve, to prove that I’m comparing apples to apples.#Recreate the original equity curve charts.PerformanceSummary(stratSP["2004-12-01::"])
So, as you can see, everything seems to match, article confirmed, and that’s good.
But I wrote the backtest to go back to 1978, just so we can see as much of the performance as we possibly can. So, let’s take a look. Now, keep in mind that the article stated the following two assertions:
“As we previously observed, the approach has higher returns and lower drawdowns across an entire bull and bear market cycle than the S&P 500…” and that “…a portfolio manager who employed this decidedly humble, simple approach, would actually have been doing well for clients as a fiduciary.”
Let’s see if the full-period equity curve supports these assertions.charts.PerformanceSummary(stratSP)
Here are some numerical values to put this into perspective:> Return.cumulative(stratSP) Harry Long Article S&P 500 Cumulative Return 6.762379 20.25606 > Return.annualized(stratSP) Harry Long Article S&P 500 Annualized Return 0.05713769 0.08640992 > SharpeRatio.annualized(stratSP) Harry Long Article S&P 500 Annualized Sharpe Ratio (Rf=0%) 0.583325 0.4913877 > maxDrawdown(stratSP) Harry Long Article S&P 500 Worst Drawdown 0.334808 0.5677539
In short, a 5.7% to an 8.6% annualized return in favor of the S&P 500 since 1978, with both sets of returns sporting substantial drawdowns compared to their annualized rates of return. So while this strategy gives a better risk-adjusted return, you get the return you pay for with the risks taken–smaller max drawdown for a similar Sharpe? Smaller return. Furthermore, the early 80s seem to have hurt this “all-weather” strategy more than the S&P 500.
For fun, let’s leverage the strategy 2x over and see what happens.stratSP$leveragedStrat <- stratSP[,1]*2 charts.PerformanceSummary(stratSP)
And this is the resulting equity curve:
Hey, now we’re talking, right?! Well, the late 70s and early 80s say not exactly. But what if I wanted to make myself look better than the strategy justifies? Well, I can simply truncate those drawdowns, by just leaving off everything before 1985! (And put some marketing spin on it to justify it.)charts.PerformanceSummary(stratSP["1985::"])
Here are some statistics:> maxDrawdown(stratSP["1985::"]) Harry.Long.Article S.P.500 leveragedStrat Worst Drawdown 0.2228608 0.5677539 0.4065492 > Return.annualized(stratSP["1985::"]) Harry.Long.Article S.P.500 leveragedStrat Annualized Return 0.05782724 0.08700208 0.1106892 > SharpeRatio.annualized(stratSP["1985::"]) Harry.Long.Article S.P.500 leveragedStrat Annualized Sharpe Ratio (Rf=0%) 0.6719875 0.4753468 0.6431376
Now look, higher returns, lower max drawdown, higher Sharpe, we can all go home happy?
Of course not. So what’s the point of this post?
Simply, to not believe everything you see at first glance. There’s often a phrase that’s thrown around that states “you’ll never see a bad backtest”–simply implying that if the backtest is bad, it’s thrown out, and all that’s left are the results with cherry-picked time frames, brute-force curve-fit parameter sets, and who knows what other sort of methodologies baked in to make a strategy look as good as possible.
On my blog, I try my best to be on the opposite side of the spectrum. In instances in which ideas were published years ago, some low-hanging fruit is an out-of sample test. On the opposite side of the spectrum, such as with Mr. Harry Long of SeekingAlpha here, when there are backtests with a very small time frame owing to newly released instruments, I’ll try and provide a much larger context. I provide the code, the data, and explanations in plain English, all to the best of my ability here on this blog. If you don’t wish to take my word for it, you can always run the scripts yourself. Worst to worst, leave a comment, and I’ll answer to the best of my ability.
I’ll have another Harry Long backtest replication up soon.
Thanks for reading.