Structural “Arbitrage”: Trading the Equity Curve

[This article was first published on QuantStrat TradeR » R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

The last post demonstrated that far from being a world-beating, absolutely amazing strategy, that Harry Long’s Structural “Arbitrage”, was in fact a very risky strategy whose drawdowns were comparable to that of the S&P 500 itself during the financial crisis. Although the annualized returns were fairly solid, the drawdowns themselves were in the realm of unacceptable. One low-hanging fruit that came to mind to try and improve the performance of the strategy is to trade the equity curve of the strategy with an SMA. Some call the 200-day SMA (aka 10 month) strategy the “Ivy” strategy, after Mebane Faber’s book, that I recommend anyone give a read-through.

In any case, picking up where the last post left off, I decided to use the returns of the strategy using the 60/40 non-adjusted TLT (that is, the simple returns on the close of TLT)-XIV configuration.

Here’s the continuation of the script:

applyWeeklySMA <- function(rets, n=200) {
  cumRets <- cumprod(1+rets)
  sma <- SMA(cumRets, n=n)
  smaCrosses <- xts(rep(NA, length(sma)),
  smaCrosses[cumRets > sma & lag(cumRets) < sma] <- 1
  smaCrosses[cumRets < sma & lag(cumRets) > sma] <- 0
  smaCrosses <- na.locf(smaCrosses)
  weights <- xts(rep(NA, length(sma)),
  weights[endpoints(sma, "weeks")] <- smaCrosses[endpoints(sma, "weeks")]
  weights <- lag(weights)
  weights <- na.locf(weights)
  weights[] <- 1
  weightedRets <- rets*weights

tmp <- list()
for(i in seq(from=100, to=200, by=20)) {
  tmp[[i]] <- applyWeeklySMA(stratTest, n=i)
tmp <-, tmp)
colnames(tmp) <- paste0("SMA_", seq(from=100, to=200, by=20))
origStratAsBM <- merge(tmp, stratTest)
colnames(origStratAsBM)[7] <- "No_SMA"
charts.PerformanceSummary(origStratAsBM, colorset=c("black", "blue", "red", "orange", "green", "purple", "darkgray"), 
                          main="SMAs and original strategy")


returnRisk <- data.frame(t(rbind(Return.annualized(origStratAsBM), maxDrawdown(origStratAsBM))))
chart.RiskReturnScatter(R=returnRisk, method="nocalc", add.sharpe=NA, main=NA)

The first function simply applies an n-day SMA (default 200), and stays in the strategy for a week if the Friday’s close is above the SMA, and starts off in the strategy on day 1 (by contrast, an MA crossover strategy in quantstrat would need to actually wait for the first positive crossover). The rest of it is just getting the returns. Essentially, it’s a very simplified example of what quantstrat does. Of course, none of the trading analytics are available through this function, though since it’s in returns space, all return analytics can be done quite simply.

In any case, here is the performance chart corresponding to testing six different MA settings (100, 120, 140, 160, 180, 200) and the benchmark (no filter)

The gray (original strategy) is basically indistinguishable from the MA filters from a return perspective. In fact, applying the MA filter in many cases results in lower returns.

What do the annualized metrics tell us?

> Return.annualized(origStratAsBM)
                    SMA_100   SMA_120   SMA_140   SMA_160   SMA_180   SMA_200    No_SMA
Annualized Return 0.1757805 0.1923969 0.1926832 0.2069332 0.1850422 0.2291408 0.2328424
> SharpeRatio.annualized(origStratAsBM)
                                  SMA_100   SMA_120   SMA_140   SMA_160   SMA_180  SMA_200    No_SMA
Annualized Sharpe Ratio (Rf=0%) 0.8433103 0.9143868 0.9169305 0.9769476 0.8839841 1.058095 0.8780168
> maxDrawdown(origStratAsBM)
                 SMA_100   SMA_120   SMA_140   SMA_160   SMA_180   SMA_200    No_SMA
Worst Drawdown 0.5044589 0.4358926 0.4059265 0.3943257 0.4106122 0.3886326 0.5040189

Overall, the original strategy has the highest overall returns, but pays for the marginally higher returns with even higher marginal drawdowns. So, the basic momentum filter marginally improved the strategy. Here’s another way to look at that sentiment using a modified risk-return chart (by default, it takes in returns and charts annualized return vs. annualized standard deviations, for the portfolio management world out there).

In short, none of the configurations really turned the lemons that was the massive drawdown into lemonade. At best, you had around 40% drawdown, which is still very much in the realm of unacceptable. While the drawdowns are certainly very high, overall, the reward to risk in terms of maximum drawdown is still pedestrian, especially when considering that the worst drawdowns can last for years. After all, given a system with small returns but smaller drawdowns still, such a system can simply be leveraged to obtain the proper reward to risk profile as per the risk appetite of a given investor.

Overall, I’ll wrap up this investigation here. What initially appeared to be a very interesting strategy from Seeking Alpha instead simply showed results for a particularly short period of time. While this longer period of time may not be long enough for some people’s tests, it covers both up markets and down markets. Overall, while the initial replication looked promising, looking over a longer time horizon painted a much different picture. Now it’s time to move on to replicating other ideas.

Thanks for reading.

To leave a comment for the author, please follow the link and comment on their blog: QuantStrat TradeR » R. offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)