In the fourth posting in this series, we saw the performance comparison between the ARMA strategy and buy-and-hold over the last approximately 10 years. Over the last few weeks (it does take time, believe me) I back-tested the ARMA strategy over the full 60 years (since 1950) of S&P 500 historic data. Let’s take a look at the full results.
It looks quite good to me. In fact, it looks so impressive that I have been looking for bugs in the code since. Even on a logarithmic chart the performance of this method is stunning. Moreover, the ARMA strategy achieves this performance with a maximum drawdown of only 41.18% vs 56.78% for the S&P 500. Computing the S&P 500 returns and drawdowns is simple:
library(quantmod) library(timeSeries) getSymbols("^GSPC", from="1900-01-01") gspcRets = Ad(GSPC) / lag(Ad(GSPC)) - 1 gspcRets[as.character(head(index(Ad(GSPC)),1))] = 0 gspcBHGrowth = cumprod( 1 + gspcRets ) head(drawdownsStats(as.timeSeries(gspcRets)),10)
The above code will produce the 10 biggest drawdowns in the return series. To compute the ARMA strategy growth, we first need the daily indicator. This indicator is what took so long to compute. It is in Excel format (since WordPress doesn’t allow csv files). To use the file in R, save it as csv, without any quotes, and then import it in R via:
library(quantmod) gspcArmaInd = as.xts( read.zoo(file="gspc.all.csv", format="%Y-%m-%d", header=T, sep=",") )
The first column is the date, the second the position for this day: 1 for long, -1 for short, 0 for none. Note, the position is already aligned with the day of the return (it is computed at the close of the previous day), in other words, no need to shift right via lag. The indicator needs to be multiplied with the S&P 500 daily returns, and then we can follow the above path. The next two columns are the number of auto regressive and the number of moving average coefficients for the model giving the best fit and used for the prediction. The GARCH components are always (1,1).
The only thing I didn’t like was the number of trades, a bit high for my taste – 6,346, or a trade every 2.35 days on average. This has the potential to eat most of the profits, more so in the past than today, however (lower transaction costs, higher liquidity). Still, taking into account the gains from this strategy together with the exceptional liquidity of S&P 500 instruments (SPY for instance trades about 167.7 million shares lately), should suffice to keep a significant amount of the profits.
Last, the simple R-code that produced this nice chart from the two growth vectors is worth showing:
png(width=480, height=480, filename="~/ttt/growth.png") plot(log(gspcArmaGrowth), col="darkgreen", main="Arma vs Buy-And-Hold") lines(log(gspcBHGrowth), col="darkblue") legend(x="topleft", legend=c("ARMA", "Buy and Hold"), col=c("darkgreen", "darkblue"), lty=c(1,1), bty="n") dev.off()
Pretty neat if you ask me! Code snippets like this one are what makes me believe command line interface is the most powerful interface.