Simple Moving Average Strategy with a Volatility Filter
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
I would describe my trading approach as systematic long term trend following. A trend following strategy can be difficult mentally to trade after experiencing multiple consecutive losses when a trade reverses due to a volatility spike or the trend reverses. Volatility tends to increase when prices fall. This is not good for a long only trend following strategy, especially when initially entering trades.
Can adding a volatility filter to a simple system improve performance?
SMA System with Volatility Filter Rules
- Buy Rule: Go long if close is greater than the N period SMA and a volatility measure is less than its median over the last N periods.
- Exit Rule: Exit if long and close is less than the N period SMA
SMA System without Volatility Filter Rules
- Buy Rule: Go long if close is greater than the N period SMA
- Exit Rule: Exit if close is less than the N period SMA
For this test, my volatility measure is the 52 period standard deviation of the 1 period change of close prices and I will use a 52 period SMA.
I will test the strategy on the total return series of the S&P500 using weekly prices from 1/1/1990 to 4/17/2012.
yuck… the equity curves look pretty good up until 1999, then not so good after that.
CAGR | maxDD | MAR | # Trades | Ending Equity | Percent Winning Trades | |
SMA with Volatility Filter | 4.369174 | -22.3993 | 0.195059 | 34 | $239,104.70 | 58.82 |
SMA System | 7.442673 | -22.2756 | 0.334119 | 57 | $464,198.80 | 53.57 |
This test shows that adding a volatility filter to our entries can actually hinder performance. Keep in mind this is ny no means an exhaustive test on a single instrument. I also chose the 52 period SMA and SDEV somewhat arbitrarily because it represents a year.
Reading through trading forums, it is clear to see that people are in search of the “holy grail” trading system. Some people claim to have found the “holy grail” system, but that system is usually combination of 10+ indicators and rules that say “use indicator A, B, and C when the market is doing X or use indicators D, E, and F when the market is doing Y.” Beware of these “filters” and always test yourself.
Stay tuned for future posts that will look at adding a similar filter on a multiple instrument test.
What have you found with adding entry filters to trading systems?
require(PerformanceAnalytics) require(quantstrat) 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') 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 RB <- function(x,n){ x <- x roc <- ROC(x, n=1, type="discrete") sd <- runSD(roc,n, sample= FALSE) sd[is.na(sd)] <- 0 med <- runMedian(sd,n) med[is.na(med)] <- 0 mavg <- SMA(x,n) signal <- ifelse(sd < med & x > mavg,1,0) colnames(signal) <- "RB" #ret <- cbind(x,roc,sd,med,mavg,signal) #colnames(ret) <- c("close","roc","sd","med","mavg","lowvol") 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 = "RB", 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 Strat.Wealth.Index <- exp(cumsum(ec$logret)) #growth of $1 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 #write.zoo(mktdata, file = "E:\\a.csv") charts.PerformanceSummary(ec$logret, wealth.index = TRUE, colorset = "steelblue2", main = "SMA with Volatility Filter System Performance")
Disclaimer: Past results do not guarantee future returns. Information on this website is for informational purposes only and does not offer advice to buy or sell any securities.
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.