Trading Strategy – Volatility Carry Trade

This strategy is going to look at a vega neutral volatility carry trading strategy. Two different futures contract will be traded, the VXX and VXZ. These contracts are rolling futures on the S&P 500 Vix index, the VXX is a short term future and the VXZ is a medium term future. Annualized Sharpe Ratio (Rf=0%) is 1.759449.

The strategy is very simple, the rules are:

  • If VXX / VXZ > 1 then in backwardation so do a reverse carry trade (buy VXX, sell VXZ)
  • If VXX / VXZ < 1 then do a carry trade (sell VXX, buy VXZ)

If the volatility spot price doesn’t change, then we’re extracting the cost of carry. Due to buying and selling (or vice versa) the short to mid term futures the vega exposure is hedged.

In the script the above two rules have been slightly changed, a slight offset is added/subtracted from the ratio. Essentially we want to be deep into contango zone or deep into backwardation zone before we trade, if we’re close to the flip point then don’t trade.

Section 1: Downloaded the data, and calculate the Open to Close return. This strategy will look for entry at the open and exit at the close.

Section 2: Regress the daily returns of VXX with VXZ to calculate the hedge ratio

Section 3: Generate the backwardation / contango signal

Section 4: Simulate the trading

Section 5: Analyse the performance

Onto the code:

?View Code RSPLUS
library("quantmod")
library("PerformanceAnalytics")
 
#Control variables for entering a trade
#Used to check for the level of contango / backwardation
#IF signal < signalLowLim then in contango and do a carry trade
#IF signal > signalUpperLim then in backwardation so do a reverse carry
#ELSE do nothing
signalLowLim <- 0.9
signalUpperLim <- 1.1
 
#Use volatility futures, shortdate vs medium dated
#VXX iPath S&P 500 VIX Short-Term Futures ETN (VXX)
#VXZ iPath S&P 500 VIX Mid-Term Futures ETN (VXZ)
symbolLst <- c("VXX","VXZ")
 
#Specify dates for downloading data, training models and running simulation
startDate = as.Date("2009-01-01") #Specify what date to get the prices from
hedgeTrainingStartDate = as.Date("2009-01-01") #Start date for training the hedge ratio
hedgeTrainingEndDate = as.Date("2009-05-01") #End date for training the hedge ratio
tradingStartDate = as.Date("2009-05-02") #Date to run the strategy from
 
### SECTION 1 - Download Data & Calculate Returns ###
#Download the data
symbolData <- new.env() #Make a new environment for quantmod to store data in
getSymbols(symbolLst, env = symbolData, src = "yahoo", from = startDate)
 
#Plan is to check for trade entry at the open, and exit the trade at the close
#So need to calculate the open to close return as they represens our profit or loss
#Calculate returns for VXX and VXZ
vxxRet <- (Cl(symbolData$VXX)/Op(symbolData$VXX))-1
colnames(vxxRet) <- "Ret"
symbolData$VXX <- cbind(symbolData$VXX,vxxRet)
 
vxzRet <- (Cl(symbolData$VXZ)/Op(symbolData$VXZ))-1
colnames(vxzRet) <- "Ret"
symbolData$VXZ <- cbind(symbolData$VXZ,vxzRet)
 
### SECTION 2 - Calculating the hedge ratio ###
#Want to work out a hedge ratio, so that we can remain Vega neutral (the futures contact are trading VEGA)
#Select a small amount of data for training the hedge model on
subVxx <- window(symbolData$VXX$Ret,start=hedgeTrainingStartDate ,end=hedgeTrainingEndDate)
subVxz <- window(symbolData$VXZ$Ret,start=hedgeTrainingStartDate ,end=hedgeTrainingEndDate)
datablock = na.omit(cbind(subVxx,subVxz))
colnames(datablock) <- c("VXX","VXZ")
 
#Simply linearly regress the returns of Vxx with Vxz
regression <- lm(datablock[,"VXZ"] ~ datablock[,"VXX"]) #Linear Regression
 
#Plot the regression
plot(x=as.vector(datablock[,"VXX"]), y=as.vector(datablock[,"VXZ"]), main=paste("Hedge Regression: XXZret =",regression$coefficient[2]," * RXXret + intercept"),
  	 xlab="Vxx Ret", ylab="Vxz ", pch=19)
abline(regression, col = 2 )
hedgeratio = regression$coefficient[2]
 
 
### SECTION 3 - Generate trading signals ###
#Generate Trading signal
#Check ratio to see if contango or backwarded volatility future
#If shortTermVega < midTermVega in contango so do carry trade
#If shortTermVega > midTermVega in backwardation so do reverse carry
#If VXX less than VXZ then want to short VXX and long VXZ
 
#Calculate the contango / backwardation signal
tSig <- Op(symbolData$VXX)/Op(symbolData$VXZ)
colnames(tSig) <- "Signal"
 
### SECTION 4 - Do the trading ###
#Generate the individual buy/sell signals for each of the futures contract
vxxSignal <- apply(tSig,1, function(x) {if(x<signalLowLim) { return (-1) } else { if(x>signalUpperLim) { return(1) } else { return (0) }}})
vxzSignal <- -1 * vxxSignal
 
#Strategy returns are simply the direction * the Open-to-Close return for the day
#Include the hedge ratio here so that we remain vega neutral
strategyReturns <- ((vxxSignal * symbolData$VXX$Ret) + hedgeratio * (vxzSignal * symbolData$VXZ$Ret) )
strategyReturns <- window(strategyReturns,start=tradingStartDate,end=Sys.Date(), extend = FALSE)
#Normalise the amount of money being invested on each trade so that we can compare to the S&P index later
strategyReturns <- strategyReturns * 1 / (1+abs(hedgeratio))
colnames(strategyReturns) <- "StrategyReturns"
#plot(cumsum(strategyReturns))
 
#SECTION 5
#### Performance Analysis ###
 
#Get the S&P 500 index data
indexData <- new.env()
startDate = as.Date("2009-01-01") #Specify what date to get the prices from
getSymbols("^GSPC", env = indexData, src = "yahoo", from = startDate)
 
#Calculate returns for the index
indexRet <- (Cl(indexData$GSPC)-lag(Cl(indexData$GSPC),1))/lag(Cl(indexData$GSPC),1)
colnames(indexRet) <- "IndexRet"
zooTradeVec <- cbind(as.zoo(strategyReturns),as.zoo(indexRet)) #Convert to zoo object
colnames(zooTradeVec) <- c("Vol Carry Trade","S&P500")
zooTradeVec <- na.omit(zooTradeVec)
#Lets see how all the strategies faired against the index
dev.new()
charts.PerformanceSummary(zooTradeVec,main="Performance of Volatility Carry Trade",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")
print(table.CalendarReturns(zooTradeVec))
#Calculate the sharpe ratio
cat("Sharpe Ratio")
print(SharpeRatio.annualized(zooTradeVec))
#Calculate other statistics
cat("Other Statistics")
print(table.CAPM(zooTradeVec[,"Vol Carry Trade"],zooTradeVec[,"S&P500"]))
 
dev.new()
#Lets make a boxplot of the returns
chart.Boxplot(zooTradeVec)
 
dev.new()
#Set the plotting area to a 2 by 2 grid
layout(rbind(c(1,2),c(3,4)))
 
#Plot various histograms with different overlays added
chart.Histogram(zooTradeVec, main = "Plain", methods = NULL)
chart.Histogram(zooTradeVec, main = "Density", breaks=40, methods = c("add.density", "add.normal"))
chart.Histogram(zooTradeVec, main = "Skew and Kurt", methods = c("add.centered", "add.rug"))
chart.Histogram(zooTradeVec, main = "Risk Measures", methods = c("add.risk"))
Share on FacebookShare on TwitterSubmit to StumbleUponhttp://gekkoquant.com/wp-content/uploads/2012/06/VolatilityCarryTrade.jpegDigg ThisSubmit to redditShare via email

Trading Strategy – Buy on Gap (EPChan)

This post is going to investigate a strategy called Buy on Gap that was discussed by E.P Chan in his blog post “the life and death of a strategy”. The strategy is a mean reverting strategy that looks to buy the weakest stocks in the S&P 500 at the open and liquidate the positions at the close. The performance of the strategy is seen in the image below, Annualized Sharpe Ratio (Rf=0%) 2.129124.

All numbers in this table are %(ie 12.6 is 12.6%)
      Jan  Feb  Mar  Apr  May  Jun  Jul  Aug  Sep  Oct  Nov  Dec BuyOnGap S&P500
2005  0.0  0.0  0.0  0.0  0.0 -0.4 -0.6  1.1  0.7  1.0 -0.2  0.3      1.8   -0.1
2006  0.2 -0.6 -0.3  0.0  1.1  0.1  0.0  0.4  0.1  0.1  0.2 -0.2      1.1   -2.0
2007  0.8  0.9  0.1 -1.1  0.3 -0.2 -1.5  0.2 -0.2  0.9 -0.4  0.3      0.0    1.0
2008  4.3 -1.9  0.8 -0.5  0.0  0.7  0.2 -0.7  2.0  3.3  2.0  2.0     12.6    6.0
2009 -2.9  0.1  1.4 -1.1  1.3 -0.8  0.4  0.0 -0.3 -1.9  0.8 -1.0     -4.0   -7.3
2010 -1.0 -0.1  0.0 -1.1 -0.7 -0.6  1.1  0.7 -0.5  0.6  0.5  0.1     -1.1   -5.9
2011  0.6  0.3  0.2 -0.1  0.2  0.4  0.7  0.1 -1.4 -1.2  1.4 -0.2      1.0    2.1
2012 -0.3 -0.5 -0.1  0.0 -1.0   NA   NA   NA   NA   NA   NA   NA     -1.8   -0.8

From the post two trading criterion were mentioned:

  1. Buy the 100 stocks out of the S&P 500 constituents that have the lowest previous days lows to the current days opening price
  2. Provided that the above return is less than the 1 times the 90day standard deviation of Close to Close returns
The criterion are fairly specific however it is important to write flexible code where it is easy to change the main model parameters, below is a list of variable names that specify the parameters in the R script:
  • nStocksBuy – How many stocks to buy
  • stdLookback – How many days to look back for the standard deviation calculation
  • stdMultiple – Number to multiply the standard deviation by (was 1 in criterion 2.), the larger this variable the more stocks that will satisfy criterion 2.

The code is split into 5 distinct sections.

Section 1: Loop through all the stocks loaded from the data file, for each stock calculate the previous day close to current days open (lowOpenRet). Calculate the Close Close return and calculate the standard deviation (stdClClRet). Also calculate the Open to Close return for every day (dayClOpRet), if we decide to trade this day this would be the return of the strategy for the day.

Section 2: This section combines columns from each of the individual stock data frames into large matrices that cover all the stocks. retMat contains the lowOpenRet for each stock. stdMat contains the stdClClRet for all stocks, dayretMat contains the dayClOpRet for all stocks.

Essentially instead of having lots of variables, we combine them into a  big matrix.

Section 3: This will check if matrices in section 2 match the trade entry criterion. This section produces two matrices (conditionOne and conditionTwo). The matrices contain a 1 for a passed entry criterion and a 0 for a failed entry criterion.

Section 4: This multiples the conditionOne with conditionTwo to give conditionsMet, since those matricies are binary multiplying them together identifies the regions where both conditions passed (1*1=1 ie a pass). This means enter a trade.

conditionsMet is then used as a mask, it has 1′s when a trade should occur and 0′s when no trade should happen. So multiplying this with dayClOpRet gives us the Open to Close daily returns for all days and stocks that a trade occurred on.

The script assumes capital is split equally between all the stocks that are bought at the open, if less than 100 stocks meet the entry criteria then it is acceptable to buy less.

Section 5: This section does simple performance analytics and plots the equity curve against the S&P 500 index.

Onto the code (note the datafile is generated in Stock Data Download & Saving R):

?View Code RSPLUS
#install.packages("quantmod")
library("quantmod")
#install.packages("caTools") #for rolling standard deviation
library("caTools")
#install.packages("PerformanceAnalytics")
library("PerformanceAnalytics") #Load the PerformanceAnalytics library
 
datafilename = "stockdata.RData"
 
stdLookback <- 90 #How many periods to lookback for the standard deviation calculation
stdMultiple <- 1 #A Number to multiply the standard deviation by
nStocksBuy <- 100 #How many stocks to buy
 
load(datafilename)
 
#CONDITION 1
#Buy 100 stocks with lowest returns from their previous days lows
#To the current days open
 
#CONDITION 2
#Provided returns are lower than one standard deviation of the
#90 day moving standard deviation of close close returns
#Exit long positions at the end of the day
 
 
#SECTION 1
symbolsLst <- ls(stockData)
#Loop through all stocks in stockData and calculate required returns / stdev's
for (i in 1:length(symbolsLst)) {
cat("Calculating the returns and standard deviations for stock: ",symbolsLst[i],"\n")
sData <- eval(parse(text=paste("stockData$",symbolsLst[i],sep="")))
#Rename the colums, there is a bug in quantmod if a stock is called Low then Lo() breaks!
#Ie if a column is LOW.x then Lo() breaks
oldColNames <- names(sData)
colnames(sData) <- c("S.Open","S.High","S.Low","S.Close","S.Volume","S.Adjusted")
 
#Calculate the return from low of yesterday to the open of today
lowOpenRet <- (Op(sData)-lag(Lo(sData),1))/lag(Lo(sData),1)
colnames(lowOpenRet) <- paste(symbolsLst[i],".LowOpenRet",sep="")
 
#Calculate the n day standard deviation from the close of yesterday to close 2 days ago
stdClClRet <- runsd((lag(Cl(sData),1)-lag(Cl(sData),2))/lag(Cl(sData),2),k=stdLookback,endrule="NA",align="right")
stdClClRet <- stdMultiple*stdClClRet + runmean(lag(Cl(sData),1)/lag(Cl(sData),2),k=stdLookback,endrule="NA",align="right")
colnames(stdClClRet) <- paste(symbolsLst[i],".StdClClRet",sep="")
 
#Not part of the strategy but want to calculate the Close/Open ret for current day
#Will use this later to evaluate performance if a trade was taken
dayClOpRet <- (Cl(sData)-Op(sData))/Op(sData)
colnames(dayClOpRet) <- paste(symbolsLst[i],".DayClOpRet",sep="")
 
colnames(sData) <- oldColNames
eval(parse(text=paste("stockData$",symbolsLst[i]," <- cbind(sData,lowOpenRet,stdClClRet,dayClOpRet)",sep="")))
}
 
#SECTION 2
#Have calculated the relevent returns and standard deviations
#Now need to to work out what 100 (nStocksBuy) stocks have the lowest returns
#Make a returns matrix
for (i in 1:length(symbolsLst)) {
  cat("Assing stock: ",symbolsLst[i]," to the returns table\n")
  sDataRET <- eval(parse(text=paste("stockData$",symbolsLst[i],"[,\"",symbolsLst[i],".LowOpenRet\"]",sep="")))
  sDataSTD <- eval(parse(text=paste("stockData$",symbolsLst[i],"[,\"",symbolsLst[i],".StdClClRet\"]",sep="")))
  sDataDAYRET <- eval(parse(text=paste("stockData$",symbolsLst[i],"[,\"",symbolsLst[i],".DayClOpRet\"]",sep="")))
  if(i == 1){
  retMat <- sDataRET
  stdMat <- sDataSTD
  dayretMat <- sDataDAYRET
  } else {
  retMat <- cbind(retMat,sDataRET)
  stdMat <- cbind(stdMat,sDataSTD)
  dayretMat <- cbind(dayretMat,sDataDAYRET)
  }
}
 
#SECTION 3
#CONDITON 1 test output (0 = failed test, 1 = passed test)
#Now will loop over the returns matrix finding the nStocksBuy smallest returns
conditionOne <- retMat #copying the structure and data, only really want the structure
conditionOne[,] <- 0 #set all the values to 0
for (i in 1:length(retMat[,1])){
orderindex <- order((retMat[i,]),decreasing=FALSE)  #order row entries smallest to largest
orderindex <- orderindex[1:nStocksBuy] #want the smallest n (nStocksBuy) stocks
conditionOne[i,orderindex] <- 1 #1 Flag indicates entry is one of the nth smallest
}
 
#CONDITON 2
#Check Close to Open return is less than 90day standard deviation
conditionTwo <- retMat #copying the structure and data, only really want the structure
conditionTwo[,] <- 0 #set all the values to 0
conditionTwo <- retMat/stdMat #If ClOp ret is < StdRet tmp will be < 1
conditionTwo[is.na(conditionTwo)] <- 2 #GIVE IT FAIL CONDITION JUST STRIPPING NAs here
conditionTwo <- apply(conditionTwo,1:2, function(x) {if(x<1) { return (1) } else { return (0) }})
 
#SECTION 4
#CHECK FOR TRADE output (1 = passed conditions for trade, 0 = failed test)
#Can just multiply the two conditions together since they're boolean
conditionsMet <- conditionOne * conditionTwo
colnames(conditionsMet) <- gsub(".LowOpenRet","",names(conditionsMet))
 
#Lets calculate the results
tradeMat <- dayretMat
colnames(tradeMat) <- gsub(".DayClOpRet","",names(tradeMat))
tradeMat <- tradeMat * conditionsMet
tradeMat[is.na(tradeMat)] <- 0
tradeVec <- as.data.frame(apply(tradeMat, 1,sum) / apply(conditionsMet, 1,sum)) #Calculate the mean for each row
colnames(tradeVec) <- "DailyReturns"
tradeVec[is.nan(tradeVec[,1]),1] <- 0 #Didnt make or loose anything on this day
 
plot(cumsum(tradeVec[,1]),xlab="Date", ylab="EPCHAN Buy on Gap",xaxt = "n")
 
#SECTION 5
#### Performance Analysis ###
 
#Get the S&P 500 index data
indexData <- new.env()
startDate = as.Date("2005-01-13") #Specify what date to get the prices from
getSymbols("^GSPC", env = indexData, src = "yahoo", from = startDate)
 
#Calculate returns for the index
indexRet <- (Cl(indexData$GSPC)-lag(Cl(indexData$GSPC),1))/lag(Cl(indexData$GSPC),1)
colnames(indexRet) <- "IndexRet"
zooTradeVec <- cbind(as.zoo(tradeVec),as.zoo(indexRet)) #Convert to zoo object
colnames(zooTradeVec) <- c("BuyOnGap","S&P500")
 
#Lets see how all the strategies faired against the index
dev.new()
charts.PerformanceSummary(zooTradeVec,main="Performance of EPCHAN Buy on Gap",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")
table.CalendarReturns(zooTradeVec)
 
dev.new()
#Lets make a boxplot of the returns
chart.Boxplot(zooTradeVec)
 
dev.new()
#Set the plotting area to a 2 by 2 grid
layout(rbind(c(1,2),c(3,4)))
 
#Plot various histograms with different overlays added
chart.Histogram(zooTradeVec, main = "Plain", methods = NULL)
chart.Histogram(zooTradeVec, main = "Density", breaks=40, methods = c("add.density", "add.normal"))
chart.Histogram(zooTradeVec, main = "Skew and Kurt", methods = c("add.centered", "add.rug"))
chart.Histogram(zooTradeVec, main = "Risk Measures", methods = c("add.risk"))

Possible Future Modifications

  • Add shorting the strongest stocks so that the strategy is market neutral
  • Vary how many stocks to hold
  • Vary the input variables (discussed above)
  • Try a different asset class, does this work for forex?
Share on FacebookShare on TwitterSubmit to StumbleUponhttp://gekkoquant.com/wp-content/uploads/2012/06/Buy-On-Gap-EPCHAN-GekkoQuant-PerformanceSummary.jpegDigg ThisSubmit to redditShare via email

Stock Data Download & Saving R

The quantmod library will be used to download prices from yahoo. This script allows a csv of tickers to be loaded and automatically downloaded. The script also has primitive error handling and is capable of retrying the download of the price history for a stock. Sometimes yahoo randomly returns error 404 for stock downloads, this script will retry a couple of times using getting around this problem.

Any data downloaded will be saved to a RData file, this allows the data to be analysed by another script. Rather than having each trading strategy maintaining its own stock data it is preferential to have one script (this one) to download the data which can then be shared across strategies.

From now on the strategies examined on this site will their price data from the output of this script.

Attached is a list of sp500 tickers (taken from the brilliant blog.quanttrader.org)

Onto the code:

?View Code RSPLUS
#install.packages("quantmod")
library("quantmod")
#Script to download prices from yahoo
#and Save the prices to a RData file
#The tickers will be loaded from a csv file
 
#Script Parameters
tickerlist <- "sp500.csv"  #CSV containing tickers on rows
savefilename <- "stockdata.RData" #The file to save the data in
startDate = as.Date("2005-01-13") #Specify what date to get the prices from
maxretryattempts <- 5 #If there is an error downloading a price how many times to retry
 
#Load the list of ticker symbols from a csv, each row contains a ticker
stocksLst <- read.csv("sp500.csv", header = F, stringsAsFactors = F)
stockData <- new.env() #Make a new environment for quantmod to store data in
nrstocks = length(stocksLst[,1]) #The number of stocks to download
 
#Download all the stock data
for (i in 1:nrstocks){
    for(t in 1:maxretryattempts){
 
       tryCatch(
           {
               #This is the statement to Try
               #Check to see if the variables exists
               #NEAT TRICK ON HOW TO TURN A STRING INTO A VARIABLE
               #SEE  http://www.r-bloggers.com/converting-a-string-to-a-variable-name-on-the-fly-and-vice-versa-in-r/
                if(!is.null(eval(parse(text=paste("stockData$",stocksLst[i,1],sep=""))))){
                    #The variable exists so dont need to download data for this stock
                    #So lets break out of the retry loop and process the next stock
                    #cat("No need to retry")
                    break
                }
 
              #The stock wasnt previously downloaded so lets attempt to download it
              cat("(",i,"/",nrstocks,") ","Downloading ", stocksLst[i,1] , "\t\t Attempt: ", t , "/", maxretryattempts,"\n")
              getSymbols(stocksLst[i,1], env = stockData, src = "yahoo", from = startDate)
           }
        #Specify the catch function, and the finally function
       , error = function(e) print(e))
     }
}
 
#Lets save the stock data to a data file
tryCatch(
    {
    save(stockData, file=savefilename)
    cat("Sucessfully saved the stock data to %s",savefilename)
    }
    , error = function(e) print(e))
Share on FacebookShare on TwitterSubmit to StumbleUponSave on DeliciousDigg ThisSubmit to redditShare via email