Creating a VIX Futures Term Structure In R From Official CBOE Settlement Data

This post will be detailing a process to create a VIX term structure from freely available CBOE VIX settlement data and a calendar of freely obtainable VIX expiry dates. This has applications for volatility trading strategies.

So this post, as has been the usual for quite some time, will not be about a strategy, but rather, a tool that can be used for exploring future strategies. Particularly, volatility strategies–which seems to have been a hot topic on this blog some time ago (and might very well be still, especially since the Volatility Made Simple blog has just stopped tracking open-sourced strategies for the past year).

This post’s topic is the VIX term structure–that is, creating a set of continuous contracts–properly rolled according to VIX contract specifications, rather than a hodgepodge of generic algorithms as found on some other websites. The idea is, as of the settlement of a previous day (or whenever the CBOE actually releases their data), you can construct a curve of contracts, and see if it’s in contango (front month cheaper than next month and so on) or backwardation (front month more expensive than next month, etc.).

The first (and most code-intensive) part of the procedure is fairly simple–map the contracts to an expiration date, then put their settlement dates and times to expiry into two separate xts objects, with one column for each contract.

The expiries text file is simply a collection of copied and pasted expiry dates from this site. It includes the January 2018 expiration date. Here is what it looks like:

> head(expiries)
  V1       V2   V3
1 18  January 2006
2 15 February 2006
3 22    March 2006
4 19    April 2006
5 17      May 2006
6 21     June 2006
require(xts)
require(data.table)

# 06 through 17
years <- c(paste0("0", c(6:9)), as.character(c(10:17)))

# futures months
futMonths <- c("F", "G", "H", "J", "K", "M",
            "N", "Q", "U", "V", "X", "Z")

# expiries come from http://www.macroption.com/vix-expiration-calendar/
expiries <- read.table("expiries.txt", header = FALSE, sep = " ")

# convert expiries into dates in R
dateString <- paste(expiries$V3, expiries$V2, expiries$V1, sep = "-")
dates <- as.Date(dateString, format = "%Y-%B-%d")

# map futures months to numbers for dates
monthMaps <- cbind(futMonths, c("01", "02", "03", "04", "05", "06",
                                   "07", "08", "09", "10", "11", "12"))
monthMaps <- data.frame(monthMaps)
colnames(monthMaps) <- c("futureStem", "monthNum")

dates <- data.frame(dates)
dates$dateMon <- substr(dates$dates, 1, 7)

contracts <- expand.grid(futMonths, years)
contracts <- paste0(contracts[,1], contracts[,2])
contracts <- c(contracts, "F18")
stem <- "https://cfe.cboe.com/Publish/ScheduledTask/MktData/datahouse/CFE_"
#contracts <- paste0(stem, contracts, "_VX.csv")

masterlist <- list()
timesToExpiry <- list()
for(i in 1:length(contracts)) {
  
  # obtain data
  contract <- contracts[i]
  dataFile <- paste0(stem, contract, "_VX.csv")
  expiryYear <- paste0("20",substr(contract, 2, 3))
  expiryMonth <- monthMaps$monthNum[monthMaps$futureStem == substr(contract,1,1)]
  expiryDate <- dates$dates[dates$dateMon == paste(expiryYear, expiryMonth, sep="-")]
  data <- suppressWarnings(fread(dataFile))
  
  # create dates
  dataDates <- as.Date(data$`Trade Date`, format = '%m/%d/%Y')
  
  # create time to expiration xts
  toExpiry <- xts(expiryDate - dataDates, order.by=dataDates)
  colnames(toExpiry) <- contract
  timesToExpiry[[i]] <- toExpiry
  
  # get settlements
  settlement <- xts(data$Settle, order.by=dataDates)
  colnames(settlement) <- contract
  masterlist[[i]] <- settlement
}

# cbind outputs
masterlist <- do.call(cbind, masterlist)
timesToExpiry <- do.call(cbind, timesToExpiry)

# NA out zeroes in settlements
masterlist[masterlist==0] <- NA

From there, we need to visualize how many contracts are being traded at once on any given day (I.E. what’s a good steady state number for the term structure)?

sumNonNA <- function(row) {
  return(sum(!is.na(row)))
}

simultaneousContracts <- xts(apply(masterlist, 1, sumNonNA), order.by=index(masterlist))
chart.TimeSeries(simultaneousContracts)

The result looks like this:

So, 8 contracts (give or take) at any given point in time. This is confirmed by the end of the master list of settlements.

dim(masterlist)
tail(masterlist[,135:145])
> dim(masterlist)
[1] 3002  145
> tail(masterlist[,135:145])
           H17    J17    K17    M17    N17    Q17    U17    V17    X17    Z17   F18
2017-04-18  NA 14.725 14.325 14.525 15.175 15.475 16.225 16.575 16.875 16.925    NA
2017-04-19  NA 14.370 14.575 14.525 15.125 15.425 16.175 16.575 16.875 16.925    NA
2017-04-20  NA     NA 14.325 14.325 14.975 15.375 16.175 16.575 16.875 16.900    NA
2017-04-21  NA     NA 14.325 14.225 14.825 15.175 15.925 16.350 16.725 16.750    NA
2017-04-24  NA     NA 12.675 13.325 14.175 14.725 15.575 16.025 16.375 16.475 17.00
2017-04-25  NA     NA 12.475 13.125 13.975 14.425 15.225 15.675 16.025 16.150 16.75

Using this information, an algorithm can create eight continuous contracts, ranging from front month to eight months out. The algorithm starts at the first day of the master list to the first expiry, then moves between expiry windows, and just appends the front month contract, and the next seven contracts to a list, before rbinding them together, and does the same with the expiry structure.

termStructure <- list()
expiryStructure <- list()
masterDates <- unique(c(first(index(masterlist)), dates$dates[dates$dates %in% index(masterlist)], Sys.Date()-1))
for(i in 1:(length(masterDates)-1)) {
  subsetDates <- masterDates[c(i, i+1)]
  dateRange <- paste(subsetDates[1], subsetDates[2], sep="::")
  subset <- masterlist[dateRange,c(i:(i+7))]
  subset <- subset[-1,]
  expirySubset <- timesToExpiry[index(subset), c(i:(i+7))]
  colnames(subset) <- colnames(expirySubset) <- paste0("C", c(1:8))
  termStructure[[i]] <- subset
  expiryStructure[[i]] <- expirySubset
}

termStructure <- do.call(rbind, termStructure)
expiryStructure <- do.call(rbind, expiryStructure)

Again, one more visualization of when we have a suitable number of contracts:

simultaneousContracts <- xts(apply(termStructure, 1, sumNonNA), order.by=index(termStructure))
chart.TimeSeries(simultaneousContracts)

And in order to preserve the most data, we’ll cut the burn-in period off when we first have 7 contracts trading at once.

first(index(simultaneousContracts)[simultaneousContracts >= 7])
termStructure <- termStructure["2006-10-23::"]
expiryStructure <- expiryStructure[index(termStructure)]

So there you have it–your continuous VIX futures contract term structure, as given by the official CBOE settlements. While some may try and simulate a trading strategy based on these contracts, I myself prefer to use them as indicators or features to a model that would rather buy XIV or VXX.

One last trick, for those that want to visualize things, a way to actually visualize the term structure on any given day, in particular, the most recent one in the term structure.

plot(t(coredata(last(termStructure))), type = 'b')

A clear display of contango.

A post on how to compute synthetic constant-expiry contracts (EG constant 30 day expiry contracts) will be forthcoming in the near future.

Thanks for reading.

NOTE: I am currently interested in networking and full-time positions which may benefit from my skills. I may be contacted at my LinkedIn profile found here.

31 thoughts on “Creating a VIX Futures Term Structure In R From Official CBOE Settlement Data

  1. Pingback: Creating a VIX Futures Term Structure In R From Official CBOE Settlement Data – Mubashir Qasim

  2. Ilya, great primer on contract construction.. Why not use Quandl for contract data? Some contracts go all the way back to 1984. Also, do you have any opinions on different continuous construction methods (such as volume switch or Settlement Date etc) in terms what might be better for backtesting and how does the method selection impact results?

    • Because I’ve tried Quandl’s free data in the past and it has…questionable…quality in many cases. Furthermore, the CBOE is the official exchange for the VIX data, so this is straight from the source. Lastly, on opinions on different construction methods: there’s only one official roll method, which is the one specified in the VIX contract specifications. If I’m going to create any sort of trading signal from the term structure of the contracts, I’m going to use the actual, specified roll specifications rather than some shorthand shortcut created by a short-sighted short-list of generic algorithms that in this case, come up…short.

  3. Pingback: Quantocracy's Daily Wrap for 04/27/2017 | Quantocracy

  4. Hello, appreciating your post. Do you have idea about modelling long term contract/hedge using vix….also, could you please tell me some influential/seminal paper on VIX that might help me to get future research direction on vix. I have much interest on this issue. Thanks

  5. ‘qmao’ performs a similar task through getSymbols.cfe (i.e. fetch the vix futures data) although the function requires an update in the code.
    I think it may be beneficial to build a theoretical term structure to generate trading signals, though we agree that futures are the only tredable instrument

  6. A magnificent piece of work that has rescued in timely fashion my current struggle with defining how to specify the requirements to an R coder. Kudos^, Ilya, very generous!

  7. Looking forward for your post:
    “A post on how to compute synthetic constant-expiry contracts (EG constant 30 day expiry contracts) will be forthcoming in the near future.”

  8. Pingback: Constant Expiry VIX Futures (Using Public Data) | QuantStrat TradeR

  9. I get:
    Error in `[.xts`(masterlist, dateRange, c(i:(i + 7))) :
    subscript out of bounds

    I changed:
    subset <- masterlist[dateRange, c((i – 5): (i + 2))]

    expirySubset <- timesToExpiry[index(subset), c((i – 5): (i + 2))]
    and of course i has to start from 6

    • I also encounterd this error, and implemented your changes. Running again, I got:
      Error in `[.xts`(masterlist, dateRange, c((i – 5):(i + 2))) :
      only zeros may be mixed with negative subscripts

      I’m an R neophyte so I guessed that your last line comment implied I had to change
      for(i in 1:(length(masterDates)-1)) { ….to
      for(i in 6:(length(masterDates)-1)) {

      That got rid of the error but the range (amount) of captured contracts doesn’t match Ilya’s chart, and my term structure graph runs from 6 to 8. Any clues what I’m failing to do?

      thank you in advance
      Glenn

  10. Awesome!! This tool is especially helpful. Thanks a lot for sharing, Ilya.

    R coders with a locale other than English might want to add “Sys.setlocale(“LC_TIME”, “C”)” before the expiries-to-date conversion, otherwise the month names in expiries.txt might not be recognized and ‘dates’ will contain NAs.

  11. Pingback: An Out of Sample Update on DDN’s Volatility Momentum Trading Strategy and Beta Convexity | QuantStrat TradeR

  12. Pingback: The birth of a strategy pt. 2 – extending VXX history and other data concerns | I AM QUANT BEAR

  13. subscript out of bounds error because:

    Error in `[.xts`(timesToExpiry, index(subset), c(i:(i + 7))) :
    subscript out of bounds
    > ncol(subset)
    [1] 8
    > ncol(timesToExpiry)
    [1] 6

    There is a bug in the downloading of data and the time to expiration calculation.
    Error in timesToExpiry[[i]] <- toExpiry :
    more elements supplied than there are to replace

    Not sure what the bug is yet but trying to debug the code.

  14. For those of you with Error in `[.xts`(timesToExpiry, index(subset), c(i:(i + 7))) :
    subscript out of bounds message. Ensure you copy the FULL expiration dates from http://www.macroption.com/vix-expiration-calendar/. Copy and paste all years from 2006 to 2018 to the .txt file. Also change the code to include 2018 expiration:
    # 06 through 18
    years <- c(paste0("0", c(6:9)), as.character(c(10:18)))

    The code should work as intended. I believe those with the subscript error are not pasting all expiration's from 2006 to 2018. If only using 2006, as Ilya presented for demonstrations only, then the code will not work!

    • Correct. Make sure that all the dates are copied. The dates I wrote in the post were for illustrative purposes, as the total file would take up too much eyeball space. Thank you.

      • Also to note, when working out the days to expiration:
        # create time to expiration xts
        toExpiry length(toExpiry)
        [1] 40
        > toExpiry[1]
        [,1]
        2005-11-18 61

        This wont matter until work out the constant maturity.

    • Not sure if I followed rightly or wrongly. I extracted the full dates from the website. I got the following error.

      Error in curl::curl_download(input, tt, mode = “wb”, quiet = !showProgress): HTTP error 404.
      Traceback:

      1. suppressWarnings(fread(dataFile))
      2. withCallingHandlers(expr, warning = function(w) invokeRestart(“muffleWarning”))
      3. fread(dataFile)
      4. curl::curl_download(input, tt, mode = “wb”, quiet = !showProgress)

      appears that no data is downloaded. subsequently got this.

      Error in apply(masterlist, 1, sumNonNA): dim(X) must have a positive length
      Traceback:

      1. xts(apply(masterlist, 1, sumNonNA), order.by = index(masterlist))
      2. apply(masterlist, 1, sumNonNA)
      3. stop(“dim(X) must have a positive length”)

  15. Pingback: VIX Term Structure – Quantitative Analysis And Back Testing

  16. Pingback: Creating Constant 30 Day Maturity From VIX1 | VIX2 Futures Contracts – Quantitative Analysis And Back Testing

  17. Pingback: (Don’t Get) Contangled Up In Noise | QuantStrat TradeR

  18. Pingback: (Don’t Get) Contangled Up In Noise – Cloud Data Architect

  19. Pingback: (Don’t Get) Contangled Up In Noise - biva

  20. Pingback: Replicating Volatiltiy ETN Returns From CBOE Futures | QuantStrat TradeR

  21. Pingback: Replicating Volatiltiy ETN Returns From CBOE Futures – Cloud Data Architect

Leave a comment