I want to use this post to replicate an article I found on SeekingAlpha, along with demonstrating PerformanceAnalytics’s ability to do on-the-fly portfolio rebalancing, without having to rewrite return-tracking functionality yourself.
Recently, I read an article written by Harry Long about an ETF trading strategy that went long the XIV for 40% of the portfolio and 60% long TMF, rebalancing weekly. While I initially attempted to back-cast this strategy since before the inception of these two fairly recent ETFs, I’ll have to save that for another blog post. In any case, here’s the link to Mr. Long’s article.
While I have no opinion on where this strategy is going, here’s how to get a very close replication.
#long TMF 60% (leveraged 3x t-bond bull), long XIV 40% getSymbols("XIV", from="1990-01-01") getSymbols("TMF", from="1990-01-01") tmfRets <- Return.calculate(Cl(TMF)) xivRets <- Return.calculate(Ad(XIV)) both <- merge(xivRets, tmfRets, join='inner') colnames(both) <- c("xiv", "tmf") portfRets <- Return.rebalancing(both, weights=c(.4, .6), rebalance_on="weeks", geometric=FALSE) colnames(portfRets) <- "XIVTMF" getSymbols("SPY", from="1990-01-01") SPYrets <- diff(log(Cl(SPY))) charts.PerformanceSummary(merge(portfRets, SPYrets, join='inner'))
Although I dislike using adjusted prices, I was forced to in the case of XIV due to its 1:10 split.
The line to pay attention to (that I recently learned about) is the call to Return.rebalancing. The allocate and drift cycle mechanism is perhaps the foundation of quantitative portfolio management. That is, the “here are my weights at the end of March, what are my returns through June? And then after reallocating at the beginning of July, how do I compute returns through September?” and so on. Suffice to say, this one line takes care of all of that. The single line of weights coupled with the rebalance_on argument specifies a consistently rebalanced portfolio at the specified time frequency. If the user has a series of evolving weights (E.G. from portfolio optimization), then that xts should be passed into the weights argument, and the rebalance_on argument left as its default NA, which would then let the function rebalance at the specified time intervals.
So, how does our replication do?
Here’s the output from the performance charts.
The strategy’s equity curve looks highly similar to the second equity curve from the article, essentially showing a very close if not dead on replication.
And here are the portfolio statistics with SPY for comparison over the same period:
> SharpeRatio.annualized(merge(portfRets, SPYrets, join='inner')) portfolio.returns SPY.Close Annualized Sharpe Ratio (Rf=0%) 1.461949 0.8314073 > Return.annualized(merge(portfRets, SPYrets, join='inner')) portfolio.returns SPY.Close Annualized Return 0.3924696 0.1277928 > maxDrawdown(merge(portfRets, SPYrets, join='inner')) portfolio.returns SPY.Close Worst Drawdown 0.2796041 0.2070681
In short, definitely a strong strategy. However, a rise in rates coupled with a spike in volatility (stagflation) would seriously hurt this particular configuration of this strategy, and the author, Harry Long, says as much in his article.
In any case, it isn’t every day that authors actually publish working strategies, especially ones that work as well as this one does. 28% drawdown does seem a bit rough, but on a whole, this is definitely evidence that the author knows what he’s talking about, given when he published the book first outlining this (or a similar, haven’t read it) strategy, as this is effectively all out-of-sample performance, if this is the same strategy suggested in the book. However, what’s even more impressive is that the author’s article was as clear as it was, and was able to be replicated so closely. I strive to provide the same clarity in my trading posts, which is why I include all the code and data I use to obtain my results. Additionally, it helps that the R xts/zoo->PerformanceAnalytics/quantstrat/PortfolioAnalytics libraries contain code that is more easily read.
In the future, I’ll go into some of the pitfalls of trying to back-cast this strategy, and a possible solution I may have found through quandl data.
Thanks for reading.