Search code examples
rback

Back testing for Stock Market with R


I am very new user for R and want to use R for back testing my Strategy. I try to combine some scripts found in web. However, it did not work according my idea. My problem is the transaction date cannot be generated according to my strategy design date.

library(quantmod)
library(lubridate)
stock1<-getSymbols("AAPL",src="yahoo",from="2016-01-01",auto.assign=F)

stock1<-na.locf(stock1)
stock1$EMA9<-EMA(Cl(stock1),n=9)
stock1$EMA19<-EMA(Cl(stock1),n=19)
stock1$EMACheck<-ifelse(stock1$EMA9>stock1$EMA19,1,0)
stock1$EMA_CrossOverUp<-ifelse(diff(stock1$EMACheck)==1,1,0)
stock1$EMA_CrossOverDown<-ifelse(diff(stock1$EMACheck)==-1,-1,0)

stock1<-stock1[index(stock1)>="2016-01-01",]

stock1_df<-data.frame(index(stock1),coredata(stock1))

colnames(stock1_df)<-c("Date","Open","High","Low","Close","Volume","Adj","EMA9","EMA19","EMACheck","EMACheck_up","EMACheck_down")

#To calculate the number of crossoverup transactions during the duration from 2016-01-01

sum(stock1_df$EMACheck_up==1 & index(stock1)>="2016-01-01",na.rm=T)

stock1_df$Date[stock1_df$EMACheck_up==1 & index(stock1)>="2016-01-01"]

sum(stock1_df$EMACheck_down==-1 & index(stock1)>="2016-01-01",na.rm=T)

stock1_df$Date[stock1_df$EMACheck_down==-1 & index(stock1)>="2016-01-01"]

#To generate the transcation according to the strategy

transaction_dates<-function(stock2,Buy,Sell)
{
Date_buy<-c()
Date_sell<-c()
hold<-F
stock2[["Hold"]]<-hold
for(i in 1:nrow(stock2)) {
  if(hold == T) {
    stock2[["Hold"]][i]<-T
    if(stock2[[Sell]][i] == -1) {
      #stock2[["Hold"]][i]<-T
      hold<-F
    }
  } else {
    if(stock2[[Buy]][i] == 1) {
      hold<-T
      stock2[["Hold"]][i]<-T
    }
  }
}


stock2[["Enter"]]<-c(0,ifelse(diff(stock2[["Hold"]])==1,1,0))
stock2[["Exit"]]<-c(ifelse(diff(stock2[["Hold"]])==-1,-1,0),0)

Buy_date <- stock2[["Date"]][stock2[["Enter"]] == 1]
Sell_date <- stock2[["Date"]][stock2[["Exit"]] == -1]

if (length(Sell_date)<length(Buy_date)){
  #Sell_date[length(Sell_date)+1]<-tail(stock2[["Date"]],n=2)[1]
  Buy_date<-Buy_date[1:length(Buy_date)-1]

}

return(list(DatesBuy=Buy_date,DatesSell=Sell_date))
}

#transaction dates generate:
stock1_df <- na.locf(stock1_df)

transactionDates<-transaction_dates(stock1_df,"EMACheck_up","EMACheck_down")

transactionDates

num_transaction1<-length(transactionDates[[1]])

Open_price<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Open"]]}
transactions_date<-function(df,x) {df[as.integer(rownames(df[df[["Date"]]==x,]))+1,][["Date"]]}

transactions_generate<-function(df,num_transaction)
{
price_buy<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[1]][x])})
price_sell<-sapply(1:num_transaction,function(x) {Open_price(df,transactionDates[[2]][x])})
Dates_buy<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[1]][x])}))
Dates_sell<-as.Date(sapply(1:num_transaction,function(x) {transactions_date(df,transactionDates[[2]][x])}))


transactions_df<-data.frame(DatesBuy=Dates_buy,DatesSell=Dates_sell,pricesBuy=price_buy,pricesSell=price_sell)
#transactions_df$return<-100*(transactions_df$pricesSell-transactions_df$pricesBuy)/transactions_df$pricesBuy
transactions_df$Stop_loss<-NA
return(transactions_df)
}

transaction_summary<-transactions_generate(stock1_df,num_transaction1)
transaction_summary$Return<-100*(transaction_summary$pricesSell-transaction_summary$pricesBuy)/transaction_summary$pricesBuy
transaction_summary

sum(transaction_summary$Return,na.rm=T)

Hi, I am very new user for R and want to use R for back testing my Strategy. I try to combine some scripts found in web. However, it did not work according my idea. My problem is the transaction date cannot be generated according to my strategy design date.

problem as this image


Solution

  • The code you have is to complicated for it's own good.

    The issue lies in the fact that the functions Open_price and transactions_date look for use rownames to find a record number and then take the next one. But then instead of looking for the rownames again, it is used as an index. There it goes wrong.

    If you look at the following result for the first date, it returns 40.

    as.integer(rownames(stock1_df[stock1_df[["Date"]] == "2016-03-01", ]))
    [1] 40
    

    So the next record it would look for will be 41. But stock_df[41, ] is not the same as rowname 41. An issue with rownames is that if you filter / remove records from the data.frame the rownames don't change. To get the correct index number you should use which. If you look at the stock1_df, you can see that it returns 21 and we need record 22

    which(stock1_df[["Date"]] == "2016-03-01")
    [1] 21
    

    I changed the Open_price and transactions_date functions to use the which function. This will now return the correct results.

    Open_price <- function(df, x) {
      df[which(df[["Date"]] == x) + 1, ][["Open"]]
    }
    
    transactions_date <- function(df, x) {
      df[which(df[["Date"]] == x) + 1, ][["Date"]]
    }
    
    
    head(transaction_summary)
         DatesBuy  DatesSell pricesBuy pricesSell Stop_loss    Return
    1  2016-03-02 2016-04-25    100.51     105.00        NA  4.467215
    2  2016-05-27 2016-06-20     99.44      96.00        NA -3.459374
    3  2016-07-13 2016-09-12     97.41     102.65        NA  5.379322
    4  2016-09-15 2016-11-02    113.86     111.40        NA -2.160547
    5  2016-12-12 2017-06-13    113.29     147.16        NA 29.896728
    6  2017-07-17 2017-09-19    148.82     159.51        NA  7.183166
    

    A bit of advice, try to use spaces in your code. That makes it more readable. Look for example at this style guide. Your whole code be rewritten to only use stock1 without the need to turning it into a data.frame halfway your code. But for now the code does what it needs to do.