Trading Strategy – S&P VWAP Trend Follow (BUGGY)

UPDATE: The exceptional returns seen in this strategy were due to a 2 day look forward bias in the signal (and then subsequent trade direction), ie when returns were calculated for day T the trade signal used was actually from day T+2.

This bias occurred in the lines:

?View Code RSCODE
signal <- na.omit(signal)

Both the signal and trade dataframe had the correct dates for each signal/trades however when indexRet*trade happened then trade was treated as undated vectors (which is 2 elements shorter than index ret) hence the 2 day shift. The moral of this story is to merge dataframes before multiplying!

Thank you for everyone that commented on this, a corrected post is to follow!

Original Post

This strategy is going to use the volume weighted average price (VWAP) as an indicator to determine the direction of the current trend and trade the same direction as the trend. Annualized Sharpe Ratio (Rf=0%) is 8.510472.

Trade logic:

  • All conditions are checked at the close, and the trade held for one day from the close
  • If price/vwap > uLim go long
  • If price/vwap < lLim go short

Initially I thought that the price would be mean reverting to VWAP (this can be see in high freq data) however this didn’t appear to be the case with EOD data. For such a simple strategy I’m amazed that the Sharpe ratio is so high (suspiciously high). The code has been double&tripple checked to see if any forward bias has slipped in, however I haven’t spotted anything.

Onto the code:

?View Code RSPLUS
#Trade logic - Follow the trade demand, ie if price > vwap then go long
#If price/vwap > uLim go LONG
#If price/vwap < lLim go SHORT
#Script parameters
symbol <- "^GSPC"     #Symbol
nlookback <- 3 #Number of days to lookback and calculate vwap
uLim <- 1.001  #If price/vwap > uLim enter a long trade
lLim <- 0.999  #If price/vwap < lLim enter a short trade
#Specify dates for downloading data
startDate = as.Date("2006-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
#Calculate volume weighted average price
vwap <- VWAP(Cl(mktdata), Vo(mktdata), n=nlookback)
#Can calculate vwap like this, but it is slower
#vwap <- runSum(Cl(mktdata)*Vo(mktdata),nlookback)/runSum(Vo(mktdata),nlookback)
#Calulate the daily returns
dailyRet <- Delt(Cl(mktdata),k=1,type="arithmetic") #Daily Returns
#signal = price/vwap
signal <- Cl(mktdata) / vwap
signal <- na.omit(signal)
trade <- apply(signal,1, function(x) {if(x<lLim) { return (-1) } else { if(x>uLim) { return(1) } else { return (0) }}})
#Calculate the P&L
#The daily ret is DailyRet(T)=(Close(T)-Close(T-1))/Close(T-1)
#We enter the trade on day T so need the DailyRet(T+1) as our potential profit
#Hence the lag in the line below
strategyReturns <- trade * lag(dailyRet,-1)
strategyReturns <- na.omit(strategyReturns)
#### Performance Analysis ###
#Calculate returns for the index
indexRet <- dailyRet #Daily returns
colnames(indexRet) <- "IndexRet"
zooTradeVec <- cbind(as.zoo(strategyReturns),as.zoo(indexRet)) #Convert to zoo object
colnames(zooTradeVec) <- c(paste(symbol," VWAP Trade"),symbol)
zooTradeVec <- na.omit(zooTradeVec)
#Lets see how all the strategies faired against the index
charts.PerformanceSummary(zooTradeVec,main=paste("Performance of ", symbol, " VWAP Strategy"),geometric=FALSE)
#Lets calculate a table of montly returns by year and strategy
cat("Calander Returns - Note 13.5 means a return of 13.5%\n")
#Calculate the sharpe ratio
cat("Sharpe Ratio")
Share on FacebookShare on TwitterSubmit to StumbleUpon ThisSubmit to redditShare via email