Skip to content

Instantly share code, notes, and snippets.

@geoquant
Created July 15, 2012 22:46
Show Gist options
  • Select an option

  • Save geoquant/3118985 to your computer and use it in GitHub Desktop.

Select an option

Save geoquant/3118985 to your computer and use it in GitHub Desktop.
backtesting in R
library(quantmod)
library(PerformanceAnalytics)
library(TTR)
###############################################################
### User Input ################################################
###############################################################
getSymbols("SPY", from = "1900-01-01", to="2012-06-01")
close <- SPY$SPY.Close
equity <-10000
riskFreeRate<-.04/252
# Indicator Rules: 200-Day Moving Average
# - buy when daily price is > 200-day moving average
# - sell and move to cash when daily price < 200-day moving average
ma200<-SMA(close,n=200)
range <- na.omit(ma200)
position200 <- ifelse(range >= close[200:length(close)],0,1) # 0 sell, 1 buy or hold
# Indicator Rules: 10,100 Crossover
# - buy when 10-day moving average > 100-day moving average
# - sell and move to cash when 10-day moving average < 100-day moving average
ma10<-SMA(close,n=10)
ma100<-SMA(close,n=100)
merge10_100 <-na.omit(merge(ma10[100:length(ma10)],ma100))
position10_100 <- ifelse(merge10_100$ma100 >= merge10_100[,1],0,1) # 0 sell, 1 buy or hold
###############################################################
### Control: Buy and Hold ####################################
###############################################################
# Returns
dailyBH<-dailyReturn(close)
monthlyBH<-monthlyReturn(close)
# Return Statistics
annualizedReturn <- Return.annualized(dailyBH,scale=252, geometric=FALSE)
cumulativeReturn <-Return.cumulative(dailyBH)
# Risk Statistics
annualizedSTD <-sd.annualized(dailyBH,scale=252)
maxDrawdown <- maxDrawdown(dailyBH)
duration <- sortDrawdowns(findDrawdowns(dailyBH))
# Sharpe Ratio
excessBH <- dailyBH[2:length(dailyBH)]-riskFreeRate
dailyBHstd <- sd(dailyBH)
avgExcessBH <- mean(excessBH)
sharpeBH <- 10*(avgExcessBH/dailyBHstd*sqrt(252))
###############################################################
### Strategy 1: 200-Day Moving Average ########################
###############################################################
# Blank data frame (Df) to store closing price, position, number of shares, market value
zero = mat.or.vec(length(range),1)
blankShares <- zoo(zero, order.by=index(range))
blankMV<- zoo(zero,order.by=index(range))
# dataframe: Df
# [,1] close
# [,2] position
# [,3] blankShares
# [,4] blankMV
Df <- data.frame(merge(close[200:length(close)], position200, blankShares, blankMV))
names(Df)[names(Df)=="SPY.Close.1"] <- "position"
# Assign first number of shares and market value
Df[1,3]<-floor(equity/Df[1,1])
Df[1,4]<- Df[1,3]*Df[1,1]
# Number of Shares and Market Value:
# a for loop iterates through the vector specified: in this example, we're
# looping sequentially through a vector that starts at 2 and goes to the
# end of the data frame
for(i in 2:(dim(Df)[1])){
# blank Shares: three conditions:
# if the decision is to sell (0), number of shares is 0
if(Df[i,2] == 0)
Df[i,3] = 0
#if decision is to buy (1) and previous was buy (1), number of shares is the same as before
else if(Df[i,2] == 1 & Df[i-1,2] == 1)
Df[i,3] = Df[i-1,3]
# if decisions is to buy (1) and previous was to sell (0), then the number of shares is
# the number that could be bought at todays close with yesterdays marketValue
else if(Df[i,2] == 1 & Df[i-1,3] == 0)
Df[i,3] = floor(Df[i-1,4]/Df[i,1])
# marketValue:
# if the current and previous are to buy (1), market value is
# the product of the current price and the current number of shares
if(Df[i,2] == 1 & Df[i-1,2] == 1)
Df[i,4] = Df[i,1]*Df[i,3]
# if the current and previous are to sell, market values is
# the same as the previous value
else if(Df[i,2] == 0 & Df[i-1,2] == 0)
Df[i,4] = Df[i-1,4]
# if the current is to buy and previous is to sell, market value is
# THE SAME
else if(Df[i,2] == 1 & Df[i-1,2] == 0)
Df[i,4] = Df[i-1,4]
# if the current is to sell and the previous is to buy, market values is
# the current price times the previous number of shares
else if(Df[i,2] == 0 & Df[i-1,2] == 1)
Df[i,4] = Df[i,1]*Df[i-1,3]
}
# index
index200 <- as.numeric(lapply(Df[,4],function(x) x/Df[1,4]))
### Annualized Returns without excluding 0 cash days
### annualizedReturn200 <- Return.annualized(daily200,scale=252, geometric=FALSE)
# returns excluding 0 cash days
indexXTS200<-xts(index200,order.by=index(position200))
daily200 <- dailyReturn(indexXTS200)
na200 <- ifelse(daily200==0,NA,daily200)
nozero200<-na.omit(na200)
annualizedReturn200 <- mean(nozero200)*252
cumulativeReturn200 <- Return.cumulative(daily200)
# Risk Statistics
annualizedStd200 <-sd.annualized(nozero200,scale=252)
maxDrawdown200 <- maxDrawdown(daily200)
duration200 <- sortDrawdowns(findDrawdowns(daily200))
# Sharpe Ratio
excess200 <- nozero200-riskFreeRate
daily200std <- sd(excess200)
avgExcess200 <- mean(excess200)
sharpe200 <- 10*(avgExcess200/daily200std*sqrt(252))
###############################################################
### Strategy 2: 10/100 Day Cross ##############################
###############################################################
# Blank vectors storing share number and market values
zero10 = mat.or.vec(length(position10_100),1)
blankShares10 <- zoo(zero10,order.by=index(position10_100))
blankMV10<- zoo(zero10,order.by=index(position10_100))
# dataframe: Df10
# [,1] close
# [,2] position10
# [,3] blankShares10
# [,4] blankMV10
Df10 <- data.frame(merge(close[100:length(close)], position10_100, blankShares10, blankMV10))
names(Df10)[names(Df10)=="ma100"] <- "position10"
# Assign first number of shares and market values in the data frame Df10
Df10[1,3]<-floor(equity/Df10[1,1])
Df10[1,4]<- Df10[1,3]*Df10[1,1]
# Number of Shares and Market Value:
# a for loop iterates through the vector specified: in this example, we're
# looping sequentially through a vector that starts at 2 and goes to the
# end of the data frame
for(i in 2:(dim(Df10)[1])){
# blank Shares: three conditions:
# if the decision is to sell (0), number of shares is 0
if(Df10[i,2] == 0)
Df10[i,3] = 0
#if decision is to buy (1) and previous was buy (1), number of shares is the same as before
else if(Df10[i,2] == 1 & Df10[i-1,2] == 1)
Df10[i,3] = Df10[i-1,3]
# if decisions is to buy (1) and previous was to sell (0), then the number of shares is
# the number that could be bought at todays close with yesterdays marketValue
else if(Df10[i,2] == 1 & Df10[i-1,3] == 0)
Df10[i,3] = floor(Df10[i-1,4]/Df10[i,1])
# marketValue:
# if the current and previous are to buy (1), market value is
# the product of the current price and the current number of shares
if(Df10[i,2] == 1 & Df10[i-1,2] == 1)
Df10[i,4] = Df10[i,1]*Df10[i,3]
# if the current and previous are to sell, market values is
# the same as the previous value
else if(Df10[i,2] == 0 & Df10[i-1,2] == 0)
Df10[i,4] = Df10[i-1,4]
# if the current is to buy and previous is to sell, market value is
# THE SAME
else if(Df10[i,2] == 1 & Df10[i-1,2] == 0)
Df10[i,4] = Df10[i-1,4]
# if the current is to sell and the previous is to buy, market values is
# the current price times the previous number of shares
else if(Df10[i,2] == 0 & Df10[i-1,2] == 1)
Df10[i,4] = Df10[i,1]*Df10[i-1,3]
}
# index
index10 <- as.numeric(lapply(Df10[,4],function(x) x/Df10[1,4]))
indexzoo10<-zoo(index10,order.by=index(position10_100))
# returns excluding 0 cash days
indexXTS10<-as.xts(indexzoo10)
daily10 <- dailyReturn(indexXTS10)
na10 <- ifelse(daily10==0,NA,daily10)
nozero10<-na.omit(na10)
annualizedReturn10 <- mean(nozero10)*252
cumulativeReturn10 <- Return.cumulative(daily10)
# Risk Statistics
annualizedStd10 <-sd.annualized(nozero10,scale=252)
maxDrawdown10 <- maxDrawdown(daily10)
duration10 <- sortDrawdowns(findDrawdowns(daily10))
# Sharpe Ratio
excess10 <- nozero10-riskFreeRate
daily10std <- sd(excess10)
avgExcess10 <- mean(excess10)
sharpe10 <- 10*(avgExcess10/daily10std*sqrt(252))
####################################################################
### Output: equity curves, statistics table, monthly returns table #
####################################################################
### (1) Plot "Growth of $1"
indexClose <- apply(close, 1, function (x) x/close[1])
zooindex1 <- zoo(indexClose,order.by=index(close))
zooindex2 <- zoo(index200,order.by=index(range))
zooindex3 <- zoo(index10,order.by=index(position10_100))
plot <- merge(zooindex1,zooindex2,zooindex3)
plot.zoo(plot,
plot.type="single",
col=c("#8DA0CB","#66C2A5","#FC8D62"),
xlab="Year",
ylab="Equity",
main="Growth of $1",
xaxt="n")
ticks <- as.Date(unique(cut(time(close),"years")))
axis.Date(1, at=ticks, format="%Y")
##Plot closing price and 200 Day MA
##plot<-cbind(close,ma200)
##plot.zoo(plot,
## plot.type="single",
## col=c("black","red"),
## xlab="Date",
## ylab="Price",
## main="SPY: Closing Price & 200 Day Moving Average")
##
##Plot 10,100-day crossover
##
##plot(close, col="black", lwd=2, xlab="Date", ylab="Price", main="SPY: 10,100 Day Crossover")
##lines
##lines(ma10, col="red", lwd=1)
##lines(ma100, col="blue", lwd=1)
### (2) Monlthy Return Tables
# monthly returns: buy and hold
table.CalendarReturns(monthlyBH, digits=2, as.perc=TRUE)
# monthly returns: 200 day MA
monthly200<-monthlyReturn(indexXTS200)
table.CalendarReturns(monthly200, digits=2, as.perc=TRUE)
# monthly returns: 10-100 crossover
monthly10<-monthlyReturn(indexXTS10)
table.CalendarReturns(monthly10, digits=2, as.perc=TRUE)
### (3) Statistics table
a <- c(annualizedReturn, annualizedReturn200, annualizedReturn10)
b <- c(cumulativeReturn, cumulativeReturn200, cumulativeReturn10)
c <- c(annualizedSTD, annualizedStd200, annualizedStd10)
d <- c(maxDrawdown, maxDrawdown200, maxDrawdown10)
table <- as.data.frame(rbind(a,b,c,d))
table <- format(table[1:4,1:3]*100, digits=2)
names(table)[names(table)=="V1"] <- "Buy & Hold"
names(table)[names(table)=="V2"] <- "200-Day Moving AVG"
names(table)[names(table)=="V3"] <- "10-100 Crossover"
rownames(table)[rownames(table)=="a"] <- "annualized return"
rownames(table)[rownames(table)=="b"] <- "cumulative return"
rownames(table)[rownames(table)=="c"] <- "annualized std.dev"
rownames(table)[rownames(table)=="d"] <- "drawdown maximum"
# append drawdown duration to table
e <- c(max(duration$length), max(duration200$length), max(duration10$length))
table[5,] <-rbind(e)
rownames(table)[rownames(table)=="5"] <- "drawdown duration"
# append sharpe ratio to table
f <- c(sharpeBH,sharpe200,sharpe10)
table[6,] <- rbind(format(f, digits=3))
rownames(table)[rownames(table)=="6"] <- "sharpe ratio"
table
@anup47
Copy link

anup47 commented Sep 1, 2013

how to put a stop loss of lets say 5% when ever we buy as 10 day moving average crossover 100 day moving average.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment