Inovance Machine Learning Histogram Background Algorithmic Automated Trading Home
Backtesting in R

How to Backtest a Strategy in R

By: | 10/20/2014

Comments

We're going to explore the backtesting capabilities of R.

In a previous post we developed some simple entry opportunities for the USD/CAD using a machine-learning algorithm and techniques from a subset of data mining called association rule learning. In this post, we are going to explore how to do a full backtest in R; using our rules from the previous post and implementing take profits and stop losses.

Let's dive right in:

						library(RCurl)
library(quantmod)
#Install the libraries we need
						sit = getURLContent('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', binary=TRUE, followlocation = TRUE, ssl.verifypeer = FALSE)
con = gzcon(rawConnection(sit, 'rb'))
source(con)
close(con)
#Download Michael Kapler's “Systematic Investor Toolbox”, a powerful set of tools used to backtest and evaluate quantitative trading strategies
						data <- new.env()
#Create a new environment
						tickers<-spl('USDCAD')
file.path<- ‘my.file.path’
#Specify the name of the asset and where the csv file is located on your computer. (You can find more ways to load data here.)
						for(n in tickers) { data[[n]] = read.xts(paste(file.path, n, '.csv', sep=''), format='%m / %d / %y %H:%M') }
bt.prep(data, align='remove.na')
#Load and clean the data
						prices = data$prices
models = list()
#Specify the prices and store our models
						data$weight[] = NA
data$weight[] = 1
models$buy.hold = bt.run.share(data, clean.signal=T)
#Create our baseline “Buy and Hold” strategy
						CCI20<-CCI(prices,20)
RSI3<-RSI(prices,3)
DEMA10<-DEMA(prices,n = 10, v = 1, wilder = FALSE)
DEMA10c<-prices - DEMA10
DEMA10c<-DEMA10c/.0001
#Calculate the indicators we need for our strategy
						buy.signal<-ifelse(RSI3 < 30 & CCI20 > -290 & CCI20 < -100 & DEMA10c > -40 & DEMA10c < -20,1,NA)
#Set our long entry conditions found by our algorithms and optimized by us in the last post
						data$weight[] = NA
data$weight[] = buy.signal
models$long = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Create our long model
						sell.signal<-ifelse(DEMA10c > 10 & DEMA10c < 40 & CCI20 > 185 & CCI20 < 325 & RSI3 > 50, -1 ,NA)
#Set our short conditions
						data$weight[] = NA
data$weight[] = sell.signal
models$short = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Create our short model
						long.short.strategy<-iif(RSI3 < 30 & CCI20 > -290 & CCI20 < -100 & DEMA10c > -40 & DEMA10c < -20,1,iif(DEMA10c > 10 & DEMA10c < 40 & CCI20 > 185 & CCI20 < 325 & RSI3 > 50, -1 ,NA))
#Set the long and short conditions for our strategy
						data$weight[] = NA
data$weight[] = long.short.strategy
models$longshort = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Create our long short strategy
						dates = '2014-02-26::2014-09-22'
#Isolate the dates from our validation set (The data not used to train the model or create the rules, our out-of-sample test)
						bt.stop.strategy.plot(data, models$longshort, dates = dates, layout=T, main = 'Long Short Strategy', plotX = F)
#View a plot of our trades
Note: the backtest is built off the 4-hour bars in our data set and doesn’t have a more granular view. Long/Short Strategy

						strategy.performance.snapshoot(models, T)
#View the equity curve and performance statistics.
Long/Short Strategy

The CAGR (compounded annual growth rate) is the percentage gain/loss annualized, meaning it smooths out the growth into equal instalments each year. Since our test was over Let’s see if we can improve the performance by adding a stop loss and take profit.

						stop.loss <- function(weight, price, tstart, tend, pstop) {
index = tstart : tend
if(weight > 0)
price[ index ] < (1 - pstop) * price[ tstart ]
else
price[ index ] > (1 + pstop) * price[ tstart ]
}
#The stop loss function

						Stoploss = .25/100
#Set our maximum loss at a .25% move in price against our trade

						data$weight[] = NA
data$weight[] = custom.stop.fn(coredata(long.short.strategy), coredata(prices), stop.loss,pstop = Stoploss)
models$stoploss = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Our long short model with a .25% stop loss

						bt.stop.strategy.plot(data, models$stoploss, dates = dates, layout=T, main = 'Stop Loss', plotX = F)
						#The plot of our trades
					
Long/Short Strategy with Stoploss

						strategy.performance.snapshoot(models[c(1,4:5)], T)
						#And how it compares to the original model
					
Long/Short Strategy Performance with Stoploss

With just a stop loss, performance went down. It looks like we are getting taken out of our trades before they are able to recover. In order to lock in our profits, let’s go ahead and implement a take profit.

						take.profit<- function(weight, price, tstart, tend, pprofit) {
index = tstart : tend
if(weight > 0)
price[ index ] > (1 + pprofit) * price[ tstart ]
else
price[ index ] < (1 - pprofit) * price[ tstart ]
}
#The take profit function

						Takeprofit = .25/100
#Maintain at 1:1 risk/reward ratio and set our take profit at a .25% change in price

						data$weight[] = NA
data$weight[] = custom.stop.fn(coredata(long.short.strategy), coredata(prices), take.profit, pprofit = Takeprofit)
models$takeprofit = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Our long short model with a .25% take profit

						bt.stop.strategy.plot(data, models$takeprofit, dates = dates, layout=T, main = 'Take Profit', plotX = F)
#The plot of our trades
Long/Short Strategy Performance with Take Profit

						strategy.performance.snapshoot(models[c(1,4:6)], T)
						#Compare it to our other models
					
Long/Short Strategy Performance with Take Profit

Locking in our gains with a take profit slightly improved the performance, but not drastically. Let’s incorporate both a stop loss and a take profit.

						stop.loss.take.profit<-function(weight, price, tstart, tend, pstop, pprofit) {
index = tstart : tend
if(weight > 0) {
temp = price[ index ] < (1 - pstop) * price[ tstart ]

# profit target
temp = temp | price[ index ] > (1 + pprofit) * price[ tstart ]
} else {
temp = price[ index ] > (1 + pstop) * price[ tstart ]

# profit target
temp = temp | price[ index ] < (1 - pprofit) * price[ tstart ]
}
return( temp )
}
#The stop loss and take profit function

						data$weight[] = NA
data$weight[] = custom.stop.fn(coredata(long.short.strategy), coredata(prices), stop.loss.take.profit,pstop = Stoploss, pprofit = Takeprofit)
models$stop.loss.take.profit = bt.run.share(data, clean.signal=T, trade.summary = TRUE)
#Our long short model with a .25% stop loss and .25% take profit

Now let’s compare the baseline Long Short strategy, with just a stop loss, just a take profit, and both a take stop loss and take profit.

						layout(1:4)
bt.stop.strategy.plot(data, models$longshort, dates = dates, layout=T, main = 'Long Short', plotX = F)
bt.stop.strategy.plot(data, models$stoploss, dates = dates, layout=T, main = 'Long Short .25% SL', plotX = F)
bt.stop.strategy.plot(data, models$takeprofit, dates = dates, layout=T, main = 'Long Short .25% TP', plotX = F)
bt.stop.strategy.plot(data, models$stop.loss.take.profit, dates = dates, layout=T, main = 'Long Short .25% SL, .25% TP', plotX = F)
#The plot of our trades
Long/Short Strategy Performance with Take Profit

						strategy.performance.snapshoot(models[c(1,4:7)], T)
#Finally comparing all the models we created
Long/Short Strategy Performance with Take Profit

Now you know how to add a take profit and stop loss, I recommend you play around with the data and test different values based on your own personal risk parameters and using your own rules.

Even with powerful algorithms and sophisticated tools, it is difficult to build a successful strategy. For every good idea, we tend to have many more bad ones. Armed with the right tools and knowledge, you can efficiently test your ideas until you get to the good ones. We have streamlined this process in TRAIDE. We’ve developed a testing infrastructure that allows you to see where the patterns are in your data are and in real-time see how they would have performed over your historical data.

We’ll be releasing TRAIDE for 7 major pairs in the FX market with technical indicators in two weeks. If you are interested in testing the software and providing feedback, please send an email to info@inovancetech.com. We have 50 spots available.