Site icon R-bloggers

Backtesting a Trading Strategy

[This article was first published on Adventures in Statistical Computing, 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.
I’ve ordered Time Series Analysis and Its Applications: With R Examples (Springer Texts in Statistics) to help me up the time series in R learning curve. So far what I have seen it looks good. The author has a good page with the issues in R and time series.  The book should arrive by the end of the week.

In the meantime, I came across a trading strategy while reading an article provide on John Mauldin’s “Over My Shoulder” service (which I highly recommend).  The crux of it was that in the bear market that started with the tech bubble crash, a strategy of betting on mean reversion of the S&P500 generated significant returns.  Naturally I wanted to test.

Please note, I am not recommending anything that follows.  Do your homework and speak with an investment professional if you have questions.

The strategy is to go long the S&P500 when the market closes at a maximum over the previous 3 days.  Reverse the trade and go long when the market closes at the minimum over the previous 3 days.  ETFs make this strategy relatively easy to trade.  SPY will be our vehicle for being long the S&P500 and SH will be our vehicle for going short.

The SH began trading on 06/21/2006.  We focus our backtesting from that point until now.

Using the importSeries() function we previously created, get all the values for SPY and SH.


to   = “2012-01-14”< o:p>
from = “2006-06-21”< o:p>

spy = importSeries(“spy”,to=to,from=from)< o:p>
sh  = importSeries(“sh”,to=to, from=from)< o:p>

series = merge(spy,sh)[,c(“spy.Open”,“spy.Close”,“spy.Return”,< o:p>
                                     “sh.Open”,“sh.Close”,“sh.Return”)]< o:p>


We need to create some additional timeSeries to hold

After we calculate the strategy we will also create a gross return series from the Dollar Amount series.

f = function(x) 0*x< o:p>
ls = fapply(series[,1],FUN=f)< o:p>
colnames(ls) = “long_short”< o:p>

rets = fapply(series[,1],FUN=f)< o:p>
colnames(rets) = “Strat.Return”< o:p>

trades = rets;< o:p>
colnames(trades) = “trade”< o:p>

amt = rets< o:p>
colnames(amt) = “DollarAmount”< o:p>
amt[seq(1,3)] = 10000< o:p>

We will loop from the 3rd day of the series until the end and calculate the values.

n = nrow(series)< o:p>

for (i in seq(3,n)){< o:p>
       maxSpy = max(series[seq(i,i2),“spy.Close”])< o:p>
       minSpy = min(series[seq(i,i2),“spy.Close”])< o:p>
       < o:p>
       #get the appropriate return< o:p>
       if (ls[i1] == 1){< o:p>
              rets[i] = series[i,“spy.Return”]< o:p>
       } else if (ls[i1] == 1){< o:p>
              rets[i] = series[i,“sh.Return”]< o:p>
       }< o:p>
       < o:p>
       < o:p>
       #change long/short as appropriate< o:p>
       if (maxSpy == series[i,“spy.Close”]){< o:p>
              ls[i] = 1< o:p>
       } else if (minSpy == series[i,“spy.Close”]){< o:p>
              ls[i] = 1< o:p>
       } else {< o:p>
              ls[i] = ls[i1]< o:p>
       }< o:p>
       < o:p>
       #mark a trade if we did one< o:p>
       if (ls[i] != ls[i1]) trades[i] = 1< o:p>
       < o:p>
       #Calculate the dollar amount< o:p>
       amt[i] = amt[i1]*exp(rets[i])< o:p>
       if (trades[i]) amt[i] = amt[i] 2< o:p>
}< o:p>

#Calculate gross returns< o:p>
amt2 = returns(amt)< o:p>
colnames(amt2) = “Strat.DollarReturns”< o:p>

Next let’s output the annualized returns and CAPM statistics.  We will do this for the entire period as well as for each year.

#Merge all the series< o:p>
series=merge(series,ls)< o:p>
series = merge(series,rets)< o:p>
series = merge(series,trades)< o:p>
series = merge(series,amt)< o:p>
series = merge(series,amt2)< o:p>

vars = c(“spy.Return”,“sh.Return”,“Strat.DollarReturns”)< o:p>

series[1,vars] = 0< o:p>

#Calculate the Annualized Statistics and the CAPM statistics< o:p>
print(“Total”)< o:p>
table.AnnualizedReturns(series[,vars])< o:p>
table.CAPM(as.xts(series[,“Strat.Return”,drop=FALSE]),as.xts(series[,“spy.Return”,drop=FALSE]))< o:p>

#Overall cumulative returns< o:p>
png(“c:\\temp\\overall.png”)< o:p>
chart.CumReturns(series[,vars],main=“Total Return”,legend.loc=“topleft”)< o:p>
dev.off()< o:p>


#Create the outputs for each year.< o:p>
for (year in seq(2006,2011)){< o:p>
       start = paste(year,“-01-01”,sep=“”)< o:p>
       end = paste(year,“-12-31”,sep=“”)< o:p>
       < o:p>
       title = paste(“Total Return “,year,sep=“”)< o:p>
       file = paste(“c:\\temp\\”,year,“.png”,sep=“”)< o:p>
       < o:p>
       s = window(series,start=start, end=end)< o:p>
       png(file)< o:p>
       chart.CumReturns(s[,vars],main=title,legend.loc=“topleft”)< o:p>
       dev.off()< o:p>
       < o:p>
       print(paste(year,“Returns”,sep=” “))< o:p>
       print(table.AnnualizedReturns(s[,vars]))< o:p>
       print(table.CAPM(as.xts(s[,“Strat.Return”,drop=FALSE]),as.xts(s[,“spy.Return”,drop=FALSE])))< o:p>
}< o:p>

< o:p> Total
spy.Return< o:p>
sh.Return< o:p>
Strat.DollarReturns< o:p>
Annualized Return< o:p>
-0.0067< o:p>
-0.0903< o:p>
0.3535< o:p>
Annualized Std Dev< o:p>
0.2529< o:p>
0.2512< o:p>
0.2508< o:p>
Annualized Sharpe (Rf=0%)< o:p>
-0.0263< o:p>
-0.3593< o:p>
1.4092< o:p>

< o:p> Total
Strat.Return to spy.Return< o:p>
Alpha< o:p>
0.0013< o:p>
Beta< o:p>
0.1921< o:p>
Beta+< o:p>
0.6830< o:p>
Beta-< o:p>
-0.0803< o:p>
R-squared< o:p>
0.0374< o:p>
Annualized Alpha< o:p>
0.3990< o:p>
Correlation< o:p>
0.1934< o:p>
Correlation p-value< o:p>
0.0000< o:p>
Tracking Error< o:p>
0.7447< o:p>
Active Premium< o:p>
0.3694< o:p>
Information Ratio< o:p>
0.4960< o:p>
Treynor Ratio< o:p>
1.8885< o:p>




So there seems to be something to this strategy.  The yearly return and CAPM tables are close to the total.  Some years are better than others.  I will leave it to you to create and study them (mostly to save space on here).  

There are things to think of:
  • It should be noted that this strategy is NOT tax efficient — any gains will be taxed at the short term capital gains rate.  
  • There were 411 trades.  A trade involves buying and selling, so 822 times would you be charged a brokerage fee.  I assumed 1 dollar per buy/sell — what is charged by Interactive Brokers.  Using someone like TD Ameritrade would cost FAR more.  
  • This also assumes that you can buy and sell at the market closing price.  Something that is possible, but slippage will occur.

To leave a comment for the author, please follow the link and comment on their blog: Adventures in Statistical Computing.

R-bloggers.com 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.