Trading Strategy – VWAP Mean Reversion

This strategy is going to use the volume weighted average price (VWAP) as an indicator to trade mean version back to VWAP. Annualized Sharpe Ratio (Rf=0%) is 0.9016936.

This post is a response to where there was a bug in the code indicating that VWAP wasn’t reverting (this didn’t sit well with me, or some of the people who commented). As always don’t take my word for anything, backtest the strategy yourself. One of the dangers of using R or Matlab is that it’s easy for forward bias to slip into your code. There are libraries such as Quantstrat for R which protect against this, but I’ve found them terribly slow to run.

Trade logic:

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

Onto the code:

?View Code RSPLUS
#Trade logic - Look for mean reversion
#If price/vwap > uLim go SHORT
#If price/vwap < lLim go LONG
#Script parameters
symbol <- "^GSPC"     #Symbol
nlookback <- 3 #Number of days to lookback and calculate vwap
uLim <- 1.001  #If price/vwap > uLim enter a short trade
lLim <- 0.999  #If price/vwap < lLim enter a long 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[] <- 1 #Setting to one means that no trade will occur for NA's
#Stripping NA's caused all manner of problems in a previous post
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")

8 thoughts on “Trading Strategy – VWAP Mean Reversion

    • It’s just a threshold used to determine when to enter the trade

      So if price/vwap is greater than the upper limit (uLim) do a trade

      So if uLim was 1.02 a trade would only occur when the price is greater than 1.02*vwap. The larger uLim (or smaller lower limit lLim) then the strategy waits for a more extreme move away from vwap before trading. This means that there will be less trades per year and will probably reduce the sharpe ratio (although doesn’t mean it is a bad strategy).

      If you want to look for more extreme moves then it might be best to run the strategy on many different index/stocks so that you have a greater chance of being in the market.

  1. Thanks. Interesting idea. From the graph it looks like equity curve became sideways since mid 2009. Any thoughts on what could be the reason? Low ,market volatility?

  2. Normally any trend trading strategies works good if the VIX is at higher levels. However when the VIX dropped then you trading strategy might give you less losses or profits. 2010- mid of 2013 market volatility is very less across the globe. Those are the periods one should stop trading their stratgegy.

  3. Thanks for sharing this code! I reproduced it and it works well. My question is, when you wrote the variable ‘trade’, you only wrote when to long and short nothing about when to close a position. so, you must assume the trader will close the open position at the daily closing price by the end of each trading day, right?

  4. Hello! Thanks for the article!
    Why not to replace this:
    trade <- apply(signal,1, function(x) {if(xuLim) { return(-1) } else { return (0) }}})
    with this:
    trade <- ifelse(signal uLim, -1, 0))

    I did not test it, but it should work. And should work faster.

Leave a Reply

Your email address will not be published. Required fields are marked *