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”
from = “2006-06-21”

spy = importSeries(“spy”,to=to,from=from)
sh  = importSeries(“sh”,to=to, from=from)

series = merge(spy,sh)[,c(“spy.Open”,“spy.Close”,“spy.Return”,
                                     “sh.Open”,“sh.Close”,“sh.Return”)]


We need to create some additional timeSeries to hold

  • Long/Short Flag — lets us know the current status of our holdings.
  • Trade Flag — signals that we instituted a trade on this date.
  • Strat.Returns — nominal return for the day with the strategy.
  • Dollar Amount — a gross dollar value of the portfolio assuming a $10,000 dollar value on 06/21/2006, and a $2 transaction fee when we trade.
After we calculate the strategy we will also create a gross return series from the Dollar Amount series.

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

rets = fapply(series[,1],FUN=f)
colnames(rets) = “Strat.Return”

trades = rets;
colnames(trades) = “trade”

amt = rets
colnames(amt) = “DollarAmount”
amt[seq(1,3)] = 10000

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

n = nrow(series)

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

#Calculate gross returns
amt2 = returns(amt)
colnames(amt2) = “Strat.DollarReturns”

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
series=merge(series,ls)
series = merge(series,rets)
series = merge(series,trades)
series = merge(series,amt)
series = merge(series,amt2)

vars = c(“spy.Return”,“sh.Return”,“Strat.DollarReturns”)

series[1,vars] = 0

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

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


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

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

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




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.

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)