I imagine that, if there is anybody actually reading my blog, they are probably ready for a break from my time-series analysis ramblings. Well, such people – if they exist – are in luck.

Recently I’ve been putting the R machine through it’s paces doing some pretty ad-hoc investment strategy evaluation. Nothing fancy yet, just basically asking myself, “what are conventional nuggets of wisdom most often doled out to retail investors and what would have happened if one had implemented those strategies?”

The Yahoo Finance platform makes this type of thing really easy to do because you can just pop in a ticker symbol and get the high, low, close, and adjusted closing price of the stock for every day going back decades….and the really cool thing is that you can just download all this data immediately to a .csv file.

Some really sharp dudes & cats actually made it even easier for R users by writing a package called Quantmod that pulls data directly from Yahoo Finance. I don’t want to get too far ahead of myself here but here’s a quick example of how I imported daily stock prices for Nordic American Tanker (NAT), The T. Rowe Price New Horizons Aggressive Growth Fund (PRNHX), Fidelity’s Contra Fund (FCNTX), Umpqua Bank (UMPQ), and the S&P index from January of 1995 to present:

require(Quantmod)
stockData <- new.env() #Make a new environment for quantmod to store data in
startDate = as.Date("1995-01-01") #Specify period of time we are interested in
endDate = as.Date("2016-01-12")

tickers <- c("FCNTX","PRNHX","NAT","UMPQ","^GSPC") #Define the tickers we are interested in

#Download the stock history (for all tickers)
getSymbols(tickers, env = stockData, src = "yahoo", from = startDate, to = endDate)

Ok, so here’s my first exercise: a lot of traders, both technical and fundamental types, consider an asset’s moving average to be an informative signal. Like a lot of people I know, I don’t get too wrapped up in micro-managing my retirement assets. I basically have the maximum I can deducted from my paycheck so I don’t have to think about it and I weight my contributions pretty heavily towards small and mid-cap growth funds. However, I do have some accounts that I can actively manage – I have a Roth IRA that I started a long time ago, which can basically be invested in anything.

The first thing I started thinking about was, “what if I treated the moving average as a binary signal…basically something to tell me to either i) be invested in the market or ii) sit in a cash position and wait for the market to heat up again.

So if we consider a particular asset (let’s start with an S&P index fund), the question is, “is there any value to using the moving average signal to tell you whether to be in or out…or is it better to just be in the market for the long-haul and not overthink things?”

Here are the parameter of my exercise:

  • I start with $10,000 invested in the S&P index
  • If the S&P index drops below its 3 month, 6 month, or 9 month moving average and stays below it for 3 consecutive months, I cash out and wait until the index crosses back over the moving average and stays above it for 3 consecutive months.

Critical assumptions are:

  • I don’t get any return on money when I’m not in the market.
  • My decision is lagged…meaning that if I am in the 3rd month of a down-cycle – meaning that the S&P index was below it’s moving average the last two months and it is again below the moving average this month – I am still invested in the current month but my position switches to cash at the start of the next month. Likewise, if I am in cash, I have to observe 3 full months of the S&P index closing above its moving average, then I can be invested in the 4th month.

We start first with the 9 month moving average. What this comparison is going to do is say:

“I invest $10,000 in the S&P index and I get out of that investment anytime the S&P index closes below its 9 month moving average for more than 3 consecutive months. I also get back into that investment anytime I am in a cash position but observe that the S&P index closes above its 9 month moving average for more than 3 consecutive months.”

I’m going to compare this strategy with a static strategy that says, “put $10,000 in an S&P index fund and sit on it until retirement.”

The time horizon for both strategies is going to be Jan. 1, 1995 to Jan. 1, 2016.

First the result, then the code. From the plot below, it looks like I’d be up about $10K if I actively managed the portfolio based on the 9 month moving average versus just putting the money in the S&P and sitting on it.

s&amp;p_porteval

 

 

Since I wrote this little experiment into an R function, I can repeat the trial with different assets and indicators. Here is what would happen if one would have put $10k into the Fidelity Contra Fund (FCNTX) in 1995 and used the rule that you sell your position anytime the S&P falls below the 6 month moving average and stays there for 3 consecutive months…then you reinvest as soon as the S&P rises above its 6 month moving average and stays there for 3 consecutive months.

fcntx_sp_eval

Note that, in this case, there isn’t much value to our active management strategy. This is mostly because the FCNTX fund actually does pretty well during a few periods of sustained downtrending in the S&P.

Here’s something really wild: Repeat this experiment using the T. Rowe Price New Horizon Fund (PRNHX, a small-cap aggressive growth fund) and the Russell 2000 as the indicator. That is, consider a $10k investment in PRNHX in 1995 and impose the rule that anytime the Russell 2000 Small Cap Index falls below its 9 month moving average for 3 months or more, sell the position…then get back in when the Russell 2000 rises above its 9 month moving average and stays there for at least 3 months.

In this case, we actually leave a lot of money on the table when compared against a strategy of just putting $10k in PRNHX in 1995 and leaving it there.

prnhx_rut_eval

This is because the small cap funds and the small cap index are a bit more volatile than the S&P. Because our rule says that, when we are in cash, we have to stay there until we observe at least 3 consecutive months when the Russell 2000 closes above its 9 month moving average, we miss out on a lot of appreciation that occurs in those months when our money was on the sidelines.

Here is a quick walk-through the code if you want to try some of these strategies out:

First, we pull the data from Yahoo Finance for the ticker symbols we want and go through kind of a tedious exercise to get them all into a single dplyr data frame.

require(ggplot2)
require(dplyr)
require(lubridate)
require(zoo)
require(quantmod)

############################################################################
############################################################################
############################################################################
#Use QuantMod package to pull quotes from Yahoo Finance

stockData <- new.env() #Make a new environment for quantmod to store data in
startDate = as.Date("1995-01-01") #Specify period of time we are interested in
endDate = as.Date("2016-01-12")

#create a character vector of ticker symbols to pull data for
tickers <- c("FCNTX","PRNHX","FSENX","VGPMX","BAC","SPY","^GSPC", "^DJI","^RUT") #Define the tickers we are interested in

#Download the stock history (for all tickers)
getSymbols(tickers, env = stockData, src = "yahoo", from = startDate, to = endDate)

#this is a bit tedious but I want to resulting data in a dplyr object because it
# will be much easier to work with later
x1 <- data.frame(stockData$FCNTX)
dates <- index(stockData$FCNTX)
names(x1) <- c('Open','High','Low','Close','Volume','Adjusted')
x1$date <- dates
x1$asset <- 'FCNTX'

x2 <- data.frame(stockData$PRNHX)
dates <- index(stockData$PRNHX)
names(x2) <- c('Open','High','Low','Close','Volume','Adjusted')
x2$date <- dates
x2$asset<-'PRNHX'

x3 <- data.frame(stockData$FSENX)
dates <- index(stockData$FSENX)
names(x3) <- c('Open','High','Low','Close','Volume','Adjusted')
x3$date <- dates
x3$asset <- 'FSENX'

x4 <- data.frame(stockData$VGPMX)
dates <- index(stockData$VGPMX)
names(x4) <- c('Open','High','Low','Close','Volume','Adjusted')
x4$date <- dates
x4$asset<-'VGPMX'

x5 <- data.frame(stockData$BAC)
dates <- index(stockData$BAC)
names(x5) <- c('Open','High','Low','Close','Volume','Adjusted')
x5$date <- dates
x5$asset<-'BAC'

x6 <- data.frame(stockData$SPY)
dates <- index(stockData$SPY)
names(x6) <- c('Open','High','Low','Close','Volume','Adjusted')
x6$date <- dates
x6$asset <- 'SPY'

x7 <- data.frame(stockData$GSPC)
dates <- index(stockData$GSPC)
names(x7) <- c('Open','High','Low','Close','Volume','Adjusted')
x7$date <- dates
x7$asset<-'GSPC'

x8 <- data.frame(stockData$DJI)
dates <- index(stockData$DJI)
names(x8) <- c('Open','High','Low','Close','Volume','Adjusted')
x8$date <- dates
x8$asset<-'DJI'

x9 <- data.frame(stockData$RUT)
dates <- index(stockData$RUT)
names(x9) <- c('Open','High','Low','Close','Volume','Adjusted')
x9$date <- dates
x9$asset<-'RUT'

stocks <- tbl_df(rbind(x1,x2,x3,x4,x5,x6,x7,x8,x9))
##############################################################################
##############################################################################
##############################################################################
##############################################################################

Next we roll the daily prices up to monthly data:

stocks.m <- stocks %>% mutate(year=year(date),month=month(date)) %>%
            group_by(asset,month,year) %>% 
              summarise(Low=min(Low),High=max(High),Close=last(Adjusted)) %>%
              group_by(asset) %>% arrange(asset,year,month) %>%
                mutate(MA3=rollmean(x=Close,3,align='right',fill=NA),
                       MA6=rollmean(x=Close,6,align='right',fill=NA),
                       MA9=rollmean(x=Close,9,align='right',fill=NA),
                       date=as.Date(paste(year,"-",month,"-",'01',sep=""),format="%Y-%m-%d"))

Next, we write a function to carry out the strategy of investing in a single asset and converting to cash anytime the indicator falls below its moving average for 3 consecutive periods….then reinvesting when the indicator moves above its moving average and stays there for 3 consecutive months.

#Scenario analysis:
#this section evaluate one particular investment strategy:

# we start with $10,000 and anytime a market indicator drops belows 
# its 3 month, 6 month, or 9 month moving average and stays there for
# 3 months, we sell our position and remain in cash until the indicator
# crosses over its 3 month, 6 month, or 9 month moving average and remains 
# there for 3 months.

# the function allows 5 inputs
# 1. asset    this is the ticker symbol for the asset that the principal will
#             be invested in
#
# 2. index    this is the market indicator we will use to determine whether to
#              hold our long position or sell and wait for a market rally.  Note that,
#              the index may be the same as the asset, or it may be different.  For
#              most application that I have run, I use the S&P ("GSPC") as the indicator.
#
# 3.  window  this is the moving average term to use for deciding whether to be in cash
#             or be long the asset.
#
# 4. start date   this is the starting date for the evaluation.  It should be intered in the following
#                 format: for Jan, 1, 2000 enter '2000-01-01'
#
# 5.  end date    this is the ending date for the evaluation.  It should be entered in the same format
#                 as the starting date

#NOTE: I don't have any idot proofing built into this yet so you need to make sure that
# the start date and end date are consistent with the span of data for the asset you choose.  

strat <- function(investment,index,window,startdate,enddate){

  if(window==3){
    inv <- stocks.m %>% filter(date>=startdate & date<=enddate & asset %in% c(investment,index)) %>% 
    select(date,year,month,Close,MA3,MA6,MA9)%>%
    group_by(asset) %>% arrange(asset,date) %>%
    mutate(ind=ifelse(Close>MA3,1,0),
           rsum=rollapply(ind,width=3,FUN=sum,align="right",fill=NA,na.rm=F),
           growth=(Close-lag(Close))/lag(Close)) %>%
      mutate(rsum=replace(rsum, is.na(rsum), 1))
  }
  
  if(window==6){
    inv <- stocks.m %>% filter(date>=startdate & date<=enddate & asset %in% c(investment,index)) %>% 
      select(date,year,month,Close,MA3,MA6,MA9)%>%
      group_by(asset) %>% arrange(asset,date) %>%
      mutate(ind=ifelse(Close>MA6,1,0),
             rsum=rollapply(ind,width=3,FUN=sum,align="right",fill=NA,na.rm=F),
             growth=(Close-lag(Close))/lag(Close)) %>%
      mutate(rsum=replace(rsum,is.na(rsum),1))
  }
  
  if(window==9){
    inv <- stocks.m %>% filter(date>=startdate & date<=enddate & asset %in% c(investment,index)) %>% 
      select(date,year,month,Close,MA3,MA6,MA9)%>%
      group_by(asset) %>% arrange(asset,date) %>%
      mutate(ind=ifelse(Close>MA9,1,0),
             rsum=rollapply(ind,width=3,FUN=sum,align="right",fill=NA,na.rm=F),
             growth=(Close-lag(Close))/lag(Close)) %>%
      mutate(rsum=replace(rsum,is.na(rsum),1))
  }
  
  
  #there is probably a dplyr way to do this but I'm doing it in a loop because it's
# a bit easier to see what is going on
invested <- 1
Val <- 10000
k<-0
r<-data.frame(date=inv$date[which(inv$asset==investment)],
              value=c(rep(0,length(inv$date[which(inv$asset==investment)]))),
              invested=c(rep(1,length(inv$date[which(inv$asset==investment)]))))
r$value[1] <- Val
r$invested[1] <- invested

for(d in inv$date[which(inv$asset==investment)][2:length(inv$date[which(inv$asset==investment)])]){
  k<-k+1
  
  if(invested==1){
    Val <- Val*(1+ inv$growth[which(inv$asset==investment & inv$date==d)])
  }else{
    Val <- Val
  }

r$value[which(r$date==d)]<- Val
r$invested[which(r$date==d)]<-invested
  
#for the first couple periods we cannot change
if(k>2){

  #if you are invested and the rolling 3 month indicator for the s&p above the 
  # 3month moving average is not equal to 0 then you stay invest
  if(invested==1 & inv$rsum[which(inv$asset==index & inv$date==d)]!=0){
    invested=1
  }
  
  #if you are invested and the s&p has been below the 3month moving average for 3 
  # consecutive months then you sell
  if(invested==1 & inv$rsum[which(inv$asset==index & inv$date==d)]==0){
    invested=0
  }  

  #if you are not invested and the s&p has been above the 3 month for 3 consecutive months
  # then you reinvest
  if(invested==0 & inv$rsum[which(inv$asset==index & inv$date==d)]==3){
    invested=1
  }
  
  #if you are not invested and the s&p has been below the 3 month MA for 3 consecutive months
  # you remain uninvested
}  
  
}
return(r)
}

Finally, we invoke the function with a ticker symbol for the asset we want to invest in, the ticker symbol for what we want to use as the index, a start date, an end date, and a moving average window.

#plot growth of $10,000 using the invest/not invest rule versus the buy and hold
# strategy
startdate <- '1995-01-01'
index <- 'RUT'
investment <- 'PRNHX'
r<-strat(investment=investment,index=index,startdate=startdate,enddate='2016-01-01',window=9)

r.alt.df <- stocks.m %>% filter(asset==investment & date>=startdate & date <= '2016-01-01') %>%
      arrange(date) %>% mutate(growth=(Close-lag(Close))/lag(Close))
k<-0
r.alt <- data.frame(date=as.Date(startdate,format="%Y-%m-%d"),value=10000)
val.last <- 10000
  for(d in r.alt.df$date[2:length(r.alt.df$date)]){
    tmp <- data.frame(date=as.Date(d),value=val.last*(1+r.alt.df$growth[which(r.alt.df$date==d)]))
    r.alt <- rbind(r.alt,tmp)
    val.last <- r.alt$value[nrow(r.alt)]
  }

r.alt$invested <- 1
r.alt$strategy <- 'static'
r$strategy<-'active'
r <- rbind(r,r.alt)
ggplot(r,aes(x=date,y=value,color=strategy)) + geom_line() +theme_bw()

Next up on the SamuelsonCondition Horizon: I have a few other strategies I want to play around with…mostly allocation rules like, “rebalance the portfolio every year and move increasingly larger shares of the principal into those assets that have performed the best over the past year” and stuff like that.

Advertisement