Oxford Dictionary Definition of a trend
- noun: a general direction in which something is developing or changing
Often people try to capture a trend using technical indicators such as moving average cross overs. If there is a trend this technique is generally good at capturing it. However if there isn’t a trend it becomes difficult to identify side ways markets with moving averages (see http://gekkoquant.com/2012/08/29/parameter-optimisation-backtesting-part-2/ moving averages performing excellently well during 2009 when there was a real trend).
Is there a better way to identify a trend? Potentially. A common modelling assumption in finance is to say that stock returns follow Brownian motion and that over short periods the mean return is 0, or in other words daily returns are Gaussian distributed with 0 mean.
This post will use the same assumption, asset returns are Guassian and hence returns are symmetrically distributed around their mean (assume 0 mean). Symmetric distributions have a skewness of 0, any deviation from 0 indicates that one of the tails is beginning to get bigger and possibly that the stock is trending.
Skewness is a measure of assymetery for a probability distribution, the skewness tells us the amount and direction of the skew. This strategy will take a rolling distribution of returns and calculate the skew for those returns. The skew will then be used to take trades.
- If skewness > UptrendSkewLimit then go Long
- If skewness < DowntrendSkewLimit then go Short
- If skewness between UptrendSkewLimit and DowntrendSkewLimit then returns are symmetrical, it’s a sideways market do nothing
Trend Following – Annualized Sharpe Ratio (Rf=0%) 0.7162438
S&P500 Long Open Close Returns – Annualized Sharpe Ratio (Rf=0%) 0.2129997
library("quantmod") library("PerformanceAnalytics") library(e1071) #For the skewness command #Model paramters nLookback <- 30 #When calculating the rolling skew use the nLookback number of days UptrendSkewLimit <- 0.3 #If the rolling skew is greater than this value go long DowntrendSkewLimit <- -0.5 #If the rolling skew is lower than this value go short #If the rolling skew is between the two limits do nothing, the skew is too weak to indicate a trend #Script parameters symbol <- "^GSPC" #Symbol #Specify dates for downloading data startDate = as.Date("2005-01-01") #Specify what date to get the prices from symbolData <- new.env() #Make a new environment for quantmod to store data in getSymbols(symbol, env = symbolData, src = "yahoo", from = startDate) mktdata <- eval(parse(text=paste("symbolData$",sub("^","",symbol,fixed=TRUE)))) mktdata <- head(mktdata,-1) #Hack to fix some stupid duplicate date problem with yahoo dayOpClRet <- Cl(mktdata)/Op(mktdata) - 1 cat("About to calculate the rolling skew") #Lets calculate the rolling skew #Lag the rolling skew by one day so the skew measured at the close of day T is sifted to day T+1 #The skew will be used to determine the trade at the open rollingSkew <- Lag(rollapply(dayOpClRet,FUN="skewness",width=nLookback, align="right"),1) #Possible improvement - Do a exponential moving average on the skew signal to smooth it #rollingSkew <- EMA(rollingSkew,n=nLookback) longSignals <- (rollingSkew>UptrendSkewLimit) longReturns <- longSignals*dayOpClRet shortSignals <- (rollingSkew<DowntrendSkewLimit) shortReturns <- -1*shortSignals*dayOpClRet totalReturns <- longReturns + shortReturns #Uncomment the line below to increase the position size for larger skews #totalReturns <- totalReturns * (abs(rollingSkew)+1) totalReturns[is.na(totalReturns)] <- 0 GEKKORed <- rgb(255/255, 0/255, 0/255, 0.1) GEKKOGreen <- rgb(0/255, 255/255, 0/255, 0.1) dev.new() par(mfrow=c(3,1)) plot(Cl(mktdata), main="Close of S&P 500") lines(longSignals*max(Cl(mktdata)), col=(GEKKOGreen),type="h") lines(shortSignals*max(Cl(mktdata)), col=(GEKKORed),type="h") plot(rollingSkew) abline(UptrendSkewLimit,0,col="green") abline(DowntrendSkewLimit,0,col="red") plot(cumsum(totalReturns), main="Cumulative Returns - Trend Following") #### Performance Analysis ### colnames(dayOpClRet) <- "Long IndexOpCloseRet" zooTradeVec <- cbind(as.zoo(totalReturns),as.zoo(dayOpClRet)) #Convert to zoo object colnames(zooTradeVec) <- c("Trend Following","S&P500 Long Open Close Returns") zooTradeVec <- na.omit(zooTradeVec) #Lets see how all the strategies faired against the index dev.new() charts.PerformanceSummary(zooTradeVec,main="Performance of Trend Following",geometric=FALSE) #Calculate the sharpe ratio cat("Sharpe Ratio") print(SharpeRatio.annualized(zooTradeVec))