Simple Moving Average Strategy with a Volatility Filter: Follow-Up Part 2

In the Follow-Up Part 1, I explored some of the functions in the quantstrat package that allowed us to drill down trade by trade to explain the difference in performance of the two strategies. By doing this, I found that my choice of a volatility measure may not have been the best choice. Although the volatility filter kept me out of trades during periods of higher volatility, it also had a negative impact on position sizing and overall return.

The volatility measure presented in the original post was the 52 period standard deviation of the 1 period change of close prices. I made a custom indicator to incorporate the volatility filter into the buy rule. Here is the original RB function:

#Custom indicator function 
RB <- function(x,n){
  x <- x
  roc <- ROC(x, n=1, type="discrete")
  sd <- runSD(roc,n, sample= FALSE)
  med <- runMedian(sd,n)
  mavg <- SMA(x,n)
  signal <- ifelse(sd < med & x > mavg,1,0)
  colnames(signal) <- "RB"
  reclass(signal,x)
  }

Created by Pretty R at inside-R.org

The new volatility filter will be the 52 period standard deviation of close prices. Now, the buy rule can be interpreted as follows:

  • Buy Rule: Go long if close is greater than the 52 period SMA and the 52 period standard deviation of close prices is less than its median over the last N periods.
  • Exit Rule: Exit if long and close is less than the N period SMA

A slight change to the RB function will do the trick, I will call it RBrev1 (that is my creative side coming out ;))

#Custom indicator function 
RBrev1 <- function(x,n){
  x <- x
  sd <- runSD(x, n, sample= FALSE)
  med <- runMedian(sd,n)
  mavg <- SMA(x,n)
  signal <- ifelse(sd < med & x > mavg,1,0)
  colnames(signal) <- "RB"
  #ret <- cbind(x,roc,sd,med,mavg,signal) #Only use for further analysis of indicator
  #colnames(ret) <- c("close","roc","sd","med","mavg","RB") #Only use for further analysis of indicator
  reclass(signal,x)
  }

Created by Pretty R at inside-R.org

I will test the strategy on the adjusted close of the S&P500 using weekly prices from 1/1/1990 to 1/1/2000 just as in the previous post.

And the winner is… both! There is no difference in performance on this single instrument in this specific window of time I used for the test.

rbresearch

Always do your own testing to decide whether or not a filter of any kind will add value to your system. This single instrument test in the series of posts showed that choosing the “wrong” volatility filter can hinder performance and another choice of volatility filter doesn’t have much impact, if any, at all.

How do you think the volatility filter will affect a multiple instrument test?

require(PerformanceAnalytics)
require(quantstrat)

suppressWarnings(rm("order_book.RBtest",pos=.strategy))
suppressWarnings(rm("account.RBtest","portfolio.RBtest",pos=.blotter))
suppressWarnings(rm("account.st","portfolio.st","symbols","stratBBands","initDate","initEq",'start_t','end_t'))

sym.st = "GSPC"
currency("USD")
stock(sym.st, currency="USD",multiplier=1)
getSymbols("^GSPC", src='yahoo', index.class=c("POSIXt","POSIXct"), from='1990-01-01', to='2012-04-17')
GSPC <- to.weekly(GSPC,indexAt='lastof',drop.time=TRUE)

#Custom Order Sizing Function to trade percent of equity based on a stopsize
osPCTEQ <- function(timestamp, orderqty, portfolio, symbol, ruletype, ...){
  tempPortfolio <- getPortfolio(portfolio.st)
  dummy <- updatePortf(Portfolio=portfolio.st, Dates=paste('::',as.Date(timestamp),sep=''))
  trading.pl <- sum(getPortfolio(portfolio.st)$summary$Realized.PL) #change to ..$summary$Net.Trading.PL for Total Equity Position Sizing
  assign(paste("portfolio.",portfolio.st,sep=""),tempPortfolio,pos=.blotter)
  total.equity <- initEq+trading.pl
  DollarRisk <- total.equity * trade.percent
  ClosePrice <- as.numeric(Cl(mktdata[timestamp,]))
  mavg <- as.numeric(mktdata$SMA52[timestamp,])
  sign1 <- ifelse(ClosePrice > mavg, 1, -1)
  sign1[is.na(sign1)] <- 1
  Posn = getPosQty(Portfolio = portfolio.st, Symbol = sym.st, Date = timestamp)
  StopSize <- as.numeric(mktdata$SDEV[timestamp,]*StopMult) #Stop = SDAVG * StopMult !Must have SDAVG or other indictor to determine stop size
  orderqty <- ifelse(Posn == 0, sign1*round(DollarRisk/StopSize), 0) # number contracts traded is equal to DollarRisk/StopSize
  return(orderqty)
}

#Function that calculates the n period standard deviation of close prices.
#This is used in place of ATR so that I can use only close prices.
SDEV <- function(x, n){
  sdev <- runSD(x, n, sample = FALSE)
  colnames(sdev) <- "SDEV"
  reclass(sdev,x)
}

#Custom indicator function 
RBrev1 <- function(x,n){
  x <- x
  sd <- runSD(x, n, sample= FALSE)
  med <- runMedian(sd,n)
  mavg <- SMA(x,n)
  signal <- ifelse(sd < med & x > mavg,1,0)
  colnames(signal) <- "RB"
  #ret <- cbind(x,roc,sd,med,mavg,signal) #Only use for further analysis of indicator
  #colnames(ret) <- c("close","roc","sd","med","mavg","RB") #Only use for further analysis of indicator
  reclass(signal,x)
  }

initDate='1900-01-01'
initEq <- 100000

trade.percent <- .05 #percent risk used in sizing function
StopMult = 1 #stop size used in sizing function

#Name the portfolio and account
portfolio.st='RBtest'
account.st='RBtest'

#Initialization
initPortf(portfolio.st, symbols=sym.st, initPosQty=0, initDate=initDate, currency="USD")
initAcct(account.st,portfolios=portfolio.st, initDate=initDate, initEq=initEq)
initOrders(portfolio=portfolio.st,initDate=initDate)

#Name the strategy
stratRB <- strategy('RBtest')

#Add indicators
#The first indicator is the 52 period SMA
#The second indicator is the RB indicator. The RB indicator returns a value of 1 when close > SMA & volatility < runMedian(volatility, n = 52)
stratRB <- add.indicator(strategy = stratRB, name = "SMA", arguments = list(x = quote(Cl(mktdata)), n=52), label="SMA52")
stratRB <- add.indicator(strategy = stratRB, name = "RBrev1", arguments = list(x = quote(Cl(mktdata)), n=52), label="RB")
stratRB <- add.indicator(strategy = stratRB, name = "SDEV", arguments = list(x = quote(Cl(mktdata)), n=52), label="SDEV")

#Add signals
#The buy signal is when the RB indicator crosses from 0 to 1
#The exit signal is when the close crosses below the SMA
stratRB <- add.signal(strategy = stratRB, name="sigThreshold", arguments = list(threshold=1, column="RB",relationship="gte", cross=TRUE),label="RB.gte.1")
stratRB <- add.signal(strategy = stratRB, name="sigCrossover", arguments = list(columns=c("Close","SMA52"),relationship="lt"),label="Cl.lt.SMA")

#Add rules
stratRB <- add.rule(strategy = stratRB, name='ruleSignal', arguments = list(sigcol="RB.gte.1", sigval=TRUE, orderqty=1000, ordertype='market', orderside='long', osFUN = 'osPCTEQ', pricemethod='market', replace=FALSE), type='enter', path.dep=TRUE)
stratRB <- add.rule(strategy = stratRB, name='ruleSignal', arguments = list(sigcol="Cl.lt.SMA", sigval=TRUE, orderqty='all', ordertype='market', orderside='long', pricemethod='market',TxnFees=0), type='exit', path.dep=TRUE)

# Process the indicators and generate trades
start_t<-Sys.time()
out<-try(applyStrategy(strategy=stratRB , portfolios=portfolio.st))
end_t<-Sys.time()
print("Strategy Loop:")
print(end_t-start_t)

start_t<-Sys.time()
updatePortf(Portfolio=portfolio.st,Dates=paste('::',as.Date(Sys.time()),sep=''))
end_t<-Sys.time()
print("updatePortf execution time:")
print(end_t-start_t)

chart.Posn(Portfolio=portfolio.st,Symbol=sym.st)

#Update Account
updateAcct(account.st)

#Update Ending Equity
updateEndEq(account.st)

#ending equity
getEndEq(account.st, Sys.Date()) + initEq

tstats <- tradeStats(Portfolio=portfolio.st, Symbol=sym.st)

#View order book to confirm trades
getOrderBook(portfolio.st)

#Trade Statistics for CAGR, Max DD, and MAR
#calculate total equity curve performance Statistics
ec <- tail(cumsum(getPortfolio(portfolio.st)$summary$Net.Trading.PL),-1)
ec$initEq <- initEq
ec$totalEq <- ec$Net.Trading.PL + ec$initEq
ec$maxDD <- ec$totalEq/cummax(ec$totalEq)-1
ec$logret <- ROC(ec$totalEq, n=1, type="continuous")
ec$logret[is.na(ec$logret)] <- 0

RBrev1WI <- exp(cumsum(ec$logret)) #growth of $1
#write.zoo(RBrev1WI, file = "E:\\volfiltertest.csv", sep=",")

period.count <- NROW(ec)-104 #Use 104 because there is a 104 week lag for the 52 week SD and 52 week median of SD
year.count <- period.count/52
maxDD <- min(ec$maxDD)*100
totret <- as.numeric(last(ec$totalEq))/as.numeric(first(ec$totalEq))
CAGR <- (totret^(1/year.count)-1)*100
MAR <- CAGR/abs(maxDD)

Perf.Stats <- c(CAGR, maxDD, MAR)
names(Perf.Stats) <- c("CAGR", "maxDD", "MAR")
Perf.Stats

transactions <- getTxns(Portfolio = portfolio.st, Symbol = sym.st)
#write.zoo(transactions, file = "E:\\filtertxn.csv")

charts.PerformanceSummary(ec$logret, wealth.index = TRUE, ylog = TRUE, colorset = "steelblue2", main = "SMA with Volatility Filter System Performance")

Created by Pretty R at inside-R.org

3 thoughts on “Simple Moving Average Strategy with a Volatility Filter: Follow-Up Part 2

  1. Interesting post, appreciate your work and openness with it. Can you do monte carlo simulations with quantstrat as standard historical backtesting?

    • jnoble,
      Monte carlo simulations and trading system simulations are two different things. Quantstrat is for transaction oriented backtesting of trading systems so you can use the outputs from quantstrat to plug into a monte carlo simulator.I’m not familiar with any monte carlo packages in R, but I’ll take a look at the boot package for bootstrap functions. You can find the boot package by going to the CRAN Task View and searching in the Optimization packages.

      Ross

  2. Pingback: Simple Moving Average Strategy with a Volatility Filter: Follow-Up Part 3 | rbresearch

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s