There are a number of sites that market their signals as great indicators for outperforming the market, let me highlight how careful one needs to be when looking at backtests.
In the strategy I am about to show you we create a ratio between the S&P 500 and the Dow and we trigger a signal to buy the market when the current ratio is above the rolling mean ratio. I will include the code at the bottom for those who want to understand the details, but the table and chart will illustrate my point. A further important point that I must emphasize and will continue to highlight is that I look at risk-adjusted returns as my proxy for out-performance.
So lets begin:
Yes Sir you beauty, we have here a 24yr backtest where our model handily outperforms a buy and hold with a Sharpe Ratio of 0.32 vs 0.22. So should we bet the farm on this baby? Not so fast I say, lets look at this strategy over some more data, in the table below we look at performance from 1950 a lengthy 64yrs.
What we see here, is underperformance; so it is very important when considering a model to ensure that the starting date isn’t cherry picked. In this illustration there are very few parameters, and we only tweaked the date. Many people pushing automated models love to “curve-fit” parameters to satisfy the backtest with no basis of reality.
Here is the R code for those that are interested:
require(quantmod) require(PerformanceAnalytics) getSymbols("^GSPC", from= "1900-01-01") sp500.weekly <- GSPC[endpoints(GSPC, "weeks"),6] sp500rets<- ROC(sp500.weekly, type = "discrete", n = 1) DJ<- read.csv('http://www.quandl.com/api/v1/datasets/BCB/UDJIAD1.csv?&auth_token=kvYEqCqKCTyL4anWz5Zv&trim_start=1896-07-14&trim_end=2015-05-12&sort_order=desc', colClasses=c('Date'='Date')) date<- DJ$Date values<- DJ[,2] DJ_xts<- as.xts(values, order.by = as.Date(date, "%d/%m/%Y")) dj.weekly <- DJ_xts[endpoints(DJ_xts, "weeks"),1] djrets<- ROC(dj.weekly, type = "discrete", n = 1) data<- merge(sp500.weekly,dj.weekly) data.sub = data['1950-02-05::'] ratio<- data.sub[,1]/data.sub[,2] ave.ratio<- rollapply(ratio,20,mean) lead.lag<- ifelse(ratio >= ave.ratio, "Lead", "Lag") # filtered results investing in S&P500 with the signal ma_sig <- Lag(ifelse(lead.lag=="Lead", 1, 0)) ma_ret <- sp500rets * ma_sig dowtimer<- cbind(ma_ret,sp500rets) # or colnames(dowtimer) = c('SPX/DJI-Timer','Buy&Hold') maxDrawdown(dowtimer) table.AnnualizedReturns(dowtimer, Rf= 0.04/52) charts.PerformanceSummary(dowtimer, Rf = 0.04/52, main="SPX/DJI Timer",geometric=FALSE)