Simple Moving Average Strategy with a Volatility Filter: Follow-Up Part 2
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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) }
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) }
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.
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")
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.