Structuring Options Data by Contract for Time Series Analysis in R
Summary
The document discusses how to organize option quotes in R when many contracts share each date. One suggested approach creates a separate time series for every contract, identified by underlying, option type, strike, and expiry. Another keeps observations in data frames, joins calls and puts on date, expiry, and strike, then shifts prices and underlying values within expiry-and-strike groups to attach future observations for return calculations.
The examples show how these structures can support matched call-put portfolios and forward price comparisons. The author reports that the grouped data-frame workflow works for the task, while per-contract time series become error prone and longer tests across expiries remain difficult. A functional, mapped data structure is offered as an alternative that can simplify processing and parallelization, though with slower calculations than array-based methods. The discussion is practical rather than a benchmark: it provides no general performance comparison, and the preferred structure depends on the analysis and data scale.
Key ideas
- Option observations can be stored as separate time series for each underlying, type, strike, and expiry.
- A data-frame workflow can join calls and puts on date, expiry, and strike before calculating later prices.
- Future observations should be shifted within contract groups so prices remain aligned with the correct option.
- Mapped functional processing can simplify longer analyses and parallel work, with a potential speed tradeoff.
Tags
Full text
# Is there a technique for using xts or zoo objects with options data (i.e., many entries per date) in R?
# Is there a technique for using xts or zoo objects with options data (i.e., many entries per date) in R?
I am starting to work with options data from optionmetrics. I use data frames, but it seems like xts or zoo objects are the way to go for features and speed. I can't figure out the best work-around to get 1 row per date. Should I be doing a list of xts objects with one object per optionid?
Here's my current approach: I am trying to price higher moments by looking at the returns to hedged portfolios of S&P 500 index options, so I really need to match next period's option price with this period's date. I use a data frame with the dates as a `Date` class and peel off the date, optionid, and option price (boa) columns. I reshape to wide with the optionid as the column names and shift the date back (here I do daily, but it can be any holding period). Then I melt back to long dataframe and merge back on the original data frame. I will give code below.
If I do this for both calls and puts, then I can merge on date, strike, and expiry and form hedged portfolios. This approach works well enough, but it doesn't seem very extensible and probably ignoring a lot of good tools in R. Right now I am just looking at index options, but maybe later I'll be looking at the cross-section, so speed will come in handy.
How do you go about working with options data in R? Thanks!
Here's the code:
```
> head(call.l)
date exdate dte optionid strike close Xms boa delta
1 1996-01-04 1996-03-16 72 10003226 600 617.7 -17.7 25.250 0.768930
2 1996-01-04 1996-02-17 44 10016457 570 617.7 -47.7 48.750 0.000000
3 1996-01-04 1996-06-22 170 10019107 595 617.7 -22.7 39.000 0.732626
4 1996-01-04 1997-06-21 534 10050656 700 617.7 82.3 15.750 0.294790
5 1996-01-04 1996-02-17 44 10060564 615 617.7 -2.7 11.000 0.599153
6 1996-01-04 1996-02-17 44 10091463 655 617.7 37.3 0.375 0.046054
temp.l <- call.l[, c("date", "optionid", "boa")]
temp.w <- dcast(temp.l, formula = "date ~ optionid", value_var = "boa")
temp.w <- temp.w[ order(temp.w[, "date"]), ]
temp.w.shift <- tail(temp.w, -1)
temp.w.shift[, "date"] <- head(temp.w[, "date"], -1)
temp.l.shift <- melt(temp.w.shift, id.vars = "date", variable.name = "optionid", value.name = "boa.fut", na.rm = T)
call.l.new <- merge(call.l, temp.l.shift)
> head(subset(call.l.new, strike == 615))
date optionid exdate dte strike close Xms boa delta boa.fut
5 1996-01-04 10060564 1996-02-17 44 615 617.70 -2.70 11.000 0.599153 10.6250
43 1996-01-04 10372012 1996-03-16 72 615 617.70 -2.70 15.000 0.593709 14.6250
80 1996-01-04 10823593 1996-01-20 16 615 617.70 -2.70 7.750 0.597414 6.0000
144 1996-01-05 10060564 1996-02-17 43 615 616.71 -1.71 10.625 0.577407 12.2500
182 1996-01-05 10372012 1996-03-16 71 615 616.71 -1.71 14.625 0.578089 16.2500
219 1996-01-05 10823593 1996-01-20 15 615 616.71 -1.71 6.000 0.584558 7.0625
```
## Answer by glyphard (score 4, accepted)
https://quant.stackexchange.com/a/453
I haven't seen a framework for options specifically, however... The way I have done this in the past is to essentially setup a timeseries(xts or zoo) for each option(underlying,type,strike,expiry). Obviously doing this via code is important because it is intensely error prone.
We use a build function to put those into the workspace. It is still difficult and brittel to do longer tests that span multiple expiries.
We eventually gave up on R and matlab in favor of a functional programming approach. This way as we evaluate the code scans a mapped structure, instead of an array.
Clearly this is slower, but really simplifies the programming, and is easily parallized. It performs reasonably well on a live data feed. Probably not tractable for HFT ( calcs are in ms, not microseconds).
## Answer by Richard Herron (score 5)
https://quant.stackexchange.com/a/564
FWIW, here's the approach I used. I keep the dates as an integer in YYYYMMDD form and merge the calls and puts in to a data frame `both`. Then I use `ddply` to operate on each matched call and put to find the future SPX close and call/put bid-offer average `boa`.
```
library(plyr)
both <- merge(calls, puts[, c("date", "exdate", "strike", "boa", "delta", "vega")], by = c("date", "exdate", "strike"), suffixes = c(".calls", ".puts"), sort = T)
```
Which gives:
```
> head(both)
date exdate strike close Xms boa.calls delta.calls vega.calls
1 19960104 19960120 490 588.97 -98.97 100.375 0.965229 8.954686
2 19960104 19960120 495 588.97 -93.97 95.375 0.964108 9.199321
3 19960104 19960120 510 588.97 -78.97 80.375 0.960100 10.064710
4 19960104 19960120 515 588.97 -73.97 75.375 0.958478 10.407010
5 19960104 19960120 525 588.97 -63.97 65.500 0.949837 12.130740
6 19960104 19960120 530 588.97 -58.97 60.625 0.942449 13.559780
boa.puts delta.puts vega.puts
1 0.03125 -0.002597 0.960498
2 0.03125 -0.002725 1.003923
3 0.09375 -0.007769 2.548534
4 0.12500 -0.010310 3.279840
5 0.28125 -0.021612 6.162532
6 0.28125 -0.023069 6.512128
both <- transform(both, date.ym = date %/% 100, exdate.ym = exdate %/% 100, exdate.d = exdate %% 100)
both <- ddply(both, .variables = c("exdate", "strike"), .fun = my.shift, direc = "fut", value.col = c("close", "boa.calls", "boa.puts"), .parallel = in.parallel)
```
Which gives:
```
> head(both)
date exdate strike close Xms boa.calls delta.calls vega.calls
1 19960109 19960120 565 582.7998 -17.7998 18.50000 0.953215 9.323605
2 19960109 19960120 575 582.7998 -7.7998 9.06250 0.834628 23.915020
3 19960109 19960120 580 582.7998 -2.7998 6.06250 0.626327 36.502140
4 19960109 19960120 585 582.7998 2.2002 3.81250 0.445721 38.105320
5 19960109 19960120 590 582.7998 7.2002 2.06250 0.287108 32.854870
6 19960109 19960120 595 582.7998 12.2002 1.03125 0.166120 24.049020
boa.puts delta.puts vega.puts date.ym exdate.ym exdate.d close.fut
1 3.1875 -0.220331 28.53901 199601 199601 20 581.99
2 5.1875 -0.345956 35.53591 199601 199601 20 581.99
3 7.0000 -0.432961 37.89640 199601 199601 20 581.99
4 9.6250 -0.525538 38.34777 199601 199601 20 581.99
5 12.8750 -0.610547 36.90346 199601 199601 20 581.99
6 17.1250 -0.671490 34.82305 199601 199601 20 581.99
boa.calls.fut boa.puts.fut
1 17.62500 0.34375
2 8.06250 1.12500
3 4.43750 2.40625
4 1.84375 4.93750
5 0.71875 8.93750
6 0.28125 13.37500
```
Where the `my.shift` function is:
```
my.shift <- function(x, date.col = "date", value.col = NULL, steps = 1, direc = NULL) {
if (direc == "fut") {
x.shift <- tail(x[, c(date.col, value.col)], -1 * steps)
x.shift[, date.col] <- head(x[, date.col], -1 * steps)
} else {
x.shift <- head(x[, c(date.col, value.col)], -1 * steps)
x.shift[, date.col] <- tail(x[, date.col], -1 * steps)
}
x <- merge(x, x.shift, by = date.col, suffixes = c("", paste("", direc, sep = ".", collapse = "")))
return(x)
}
```
HTH (I use the `.ym` and `.d` columns later for picking the right time to expiry).Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.