Created
July 15, 2012 22:46
-
-
Save geoquant/3118985 to your computer and use it in GitHub Desktop.
backtesting in R
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
how to put a stop loss of lets say 5% when ever we buy as 10 day moving average crossover 100 day moving average.