Lesson 3.6.3 · 70 min

VIX and VSTOXX strategies: volatility futures and ETPs

The VIX is a calculation, not an asset: what trades are futures that converge to it and exchange-traded products that roll those futures every day. This lesson rebuilds with the course data the curve, the short-term futures index and two ETPs, a long one that loses 99.4% in twelve years and an inverse one that disappears in a single session, and measures what is left of volatility-selling strategies when you look at their worst day.

Fig. — Volatility ETPs on VORX futures, base 100 in January 2014, log scale: the long one (mimics VXX) loses 99.4% in twelve years; the daily inverse one (mimics XIV) reaches 221 and is liquidated on 2018-02-05 (synthetic data)
Level 3 lessons
  1. 3.1 · Futures in depth

    1. 3.1.1 Main futures contracts
    2. 3.1.2 Futures curve: basis, cost of carry, contango and roll yield
    3. 3.1.3 Roll management, margins, SPAN and implied leverage
    4. 3.1.4 Diversified futures systems: Carver's approach
  2. 3.2 · Futures spreads

    1. 3.2.1 Calendar spreads, butterflies and condors
    2. 3.2.2 Intercommodity spreads: crack, spark and crush
    3. 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
    4. 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
    5. 3.2.5 Spread data and backtesting: listed, synthetic and legged
    6. 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
  3. 3.3 · Advanced forex

    1. 3.3.1 FX microstructure: OTC, ECN, last look and sessions
    2. 3.3.2 Carry and crash risk, momentum and value in currencies; baskets
    3. 3.3.3 Currency risk in multi-currency portfolios; execution via OANDA, IBKR or MT5
  4. 3.4 · Option theory and pricing

    1. 3.4.1 Payoffs, put-call parity and arbitrage bounds
    2. 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
    3. 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
    4. 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
    5. 3.4.5 Smile, skew and term structure; volatility surface with SVI
    6. 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
    7. 3.4.7 Advanced models: Heston, SABR and local volatility
  5. 3.5 · Option structures in Python

    1. 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
    2. 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
    3. 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
    4. 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
    5. 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
  6. 3.6 · Systematic volatility and options strategies

    1. 3.6.1 Systematic premium selling: put-write, strangles and their tail risk
    2. 3.6.2 Delta hedging, gamma scalping and dispersion
    3. 3.6.3 VIX and VSTOXX strategies: volatility futures and ETPs
    4. 3.6.4 Options backtesting: data, fills and event adjustments
  7. 3.7 · Crypto derivatives and specific strategies

    1. 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
    2. 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
    3. 3.7.3 Crypto options, on-chain data and DeFi
    4. 3.7.4 ccxt for trading on multiple exchanges
    5. 3.7.5 MiCA: the EU framework for crypto-assets and their service providers (CASPs)

An index that cannot be bought

The Cboe VIX measures the 30-day expected volatility of the S&P 500 with the prices of a strip of its options, with the variance formula that lesson 3.4.6 applies to the VORX of the course data. Cboe launched it in 1993 on S&P 100 options and changed it in 2003 to the current formula, according to its methodology. The VSTOXX is the European equivalent, on Euro Stoxx 50 options.

Neither of the two can be bought. To hold “the VIX” you would need a basket of 30-day options that changes strikes and expirations every day; it is not a portfolio you buy and keep. What trades are futures on the index and, on top of them, exchange-traded products (ETPs: funds and notes) that hold a position in those futures and roll it. Each layer adds a difference from the index the investor believes they are buying.

settles30 d optionsSpot VIXVX futuresFutures indexLong ETPInverse ETP
Mermaid source
flowchart TB
  O[30 d options] --> V[Spot VIX]
  V -. settles .-> F[VX futures]
  F --> I[Futures index]
  I --> L[Long ETP]
  I --> N[Inverse ETP]

The actual rules of the futures, from their primary sources:

VIX futures (Cboe, VX)VSTOXX futures (Eurex, FVS)
Multiplier1,000 USD per point100 EUR per point
Tick0.05 points (50 USD)0.05 points (5 EUR)
Final settlementThe Wednesday 30 days before the third Friday of the following month30 calendar days before the expiration of the underlying options
Settlement priceSOQ: the VIX calculated with that day's opening option pricesAverage of the index between 11:00 and 12:00 CET on the last day
SettlementCashCash

The date of both comes from the same idea: 30 days before the options used to calculate the index expire, so that on settlement day the index depends on a single 30-day expiration. The SOQ (Special Opening Quotation) is not that morning's VIX: it is calculated with the opening prices of a special auction of S&P 500 options, with its own rules (for example, it admits strikes with a zero bid), according to section 3.4 of the Cboe methodology. In the course data the VORX futures expire on the Wednesday 30 days before the third Friday of the following month, like the VX, and settle at that day's VORX close (a simplification).

The VORX futures curve

The data are synthetic (public/curso/datos/nivel-3/README.md): vol3_indices.csv with VORX, which mimics the VIX and is recalculated exactly with the Cboe formula from the ORX chain (lesson 3.4.6), and VVGX, which mimics the VSTOXX; vol3_futuros.parquet with eight monthly futures on VORX, and vol3_etp.csv with the short-term futures index and two ETPs. The futures model is the expectation of VORX at its expiration under the pricing measure, plus today's difference between VORX and its model, which fades with the future's time to expiration (a 10-day time constant). And it has a planted episode: on 2018-02-05 the first two futures settle 22% above their model value, like the inverse ETPs' buying in the settlement window, and it unwinds the next day.

IN [01]python
import numpy as np
import pandas as pd

vi = pd.read_csv("vol3_indices.csv", parse_dates=["fecha"])
vi = vi.set_index("fecha")
fu = pd.read_parquet("vol3_futuros.parquet")
fu = fu.sort_values(["fecha", "vencimiento"])
fu["orden"] = fu.groupby("fecha").cumcount()   # 0 = the first
vx = vi["vorx"]
print(vx.describe().round(2).to_dict())
curvas = {d: fu[fu["fecha"] == d].set_index("dias")["precio"]
          for d in ("2017-06-14", "2020-03-16")}
for d, c in curvas.items():
    print(d, "VORX", vx[d], "|", c.to_dict())
OUT [01]VORX and two futures curves
{'count': 3018.0, 'mean': 19.8, 'std': 8.51, 'min': 9.55, '25%': 14.23, '50%': 17.66, '75%': 22.83, 'max': 73.98}
2017-06-14 VORX 11.05 | {7: 12.3, 35: 15.05, 63: 16.4, 98: 17.3, 126: 17.8, 154: 18.3, 189: 18.6, 217: 19.0}
2020-03-16 VORX 73.09 | {2: 71.45, 30: 51.35, 65: 37.9, 93: 31.35, 128: 26.8, 156: 25.15, 184: 24.2, 219: 23.7}
Fig. — VORX futures curve in calm (2017-06-14) and in crisis (2020-03-16); the point at day 0 is spot VORX (synthetic data)

The two snapshots are the two shapes of the curve. On 14 June 2017, with VORX at 11.05, the 7-day future trades at 12.30 and the 217-day one at 19.00: contango, each expiration more expensive than the previous one, because low volatility is expected to return to its higher mean. On 16 March 2020, with VORX at 73.09, the curve falls from 71.45 to 23.70: backwardation, because the crisis is expected to pass. Measured over the whole sample:

IN [02]python
f1 = fu[fu["orden"] == 0].set_index("fecha")
f2 = fu[fu["orden"] == 1].set_index("fecha")
print(f"first future above VORX: "
      f"{(f1['precio'] > vx).mean():.1%} of sessions")
print(f"second above the first: "
      f"{(f2['precio'] > f1['precio']).mean():.1%}; mean "
      f"difference {(f2['precio'] / f1['precio'] - 1).mean():.1%}")
venc = fu[fu["fecha"] == fu["vencimiento"]].set_index("fecha")
dif = (venc["precio"] - vx.reindex(venc.index)).abs()
print(f"{len(venc)} expirations; |future − VORX| at expiry: "
      f"max. {dif.max():.3f}")
OUT [02]Contango and convergence at expiration
first future above VORX: 62.8% of sessions
second above the first: 71.1%; mean difference 4.4%
144 expirations; |future − VORX| at expiry: max. 0.000

The three figures of the README's known answer (63%, 71% and 4.4%) come out exactly, and the 144 expirations settle at that day's VORX. Convergence is the key to everything that follows: a future in contango has to fall to the index or the index has to rise to it before expiration. Which of the two moves?

Roll yield: does the future converge or the index?

The daily roll of a future is its basis spread over the days it has left: (F−VORX)/days(F - \text{VORX}) / \text{days}. If the basis were an unbiased prediction of the index, the index would rise on average by what the roll says and the future would not move. If it is a premium, the future would fall toward the index and the index would not move predictably. The regression of the next day's changes on the roll, with the first future at 10 days or more and autocorrelation-robust errors (Newey-West with 5 lags):

IN [03]python
import statsmodels.api as sm

fu["siguiente"] = fu.groupby("contrato")["precio"].shift(-1)
g = fu[(fu["orden"] == 0) & (fu["dias"] >= 10)].set_index("fecha")
g = g.assign(roll=(g["precio"] - vx.reindex(g.index)) / g["dias"],
             d_futuro=g["siguiente"] - g["precio"],
             d_vorx=(vx.shift(-1) - vx).reindex(g.index)).dropna()
for y in ("d_futuro", "d_vorx"):
    m = sm.OLS(g[y], sm.add_constant(g["roll"])).fit(
        cov_type="HAC", cov_kwds={"maxlags": 5})
    print(f"{y:9}: coefficient {m.params['roll']:6.2f}, "
          f"t {m.tvalues['roll']:5.1f}, R² {m.rsquared:.3f}")
OUT [03]The basis predicts the future, not the index
d_futuro : coefficient  -1.56, t  -4.3, R² 0.024
d_vorx   : coefficient   0.42, t   1.0, R² 0.001

The change in the future has a coefficient of −1.56 on the roll (t = −4.3): in contango the future falls toward the index and in backwardation it rises. The change in VORX has no significant relationship with the roll (t = 1.0). It is what Simon and Campasano (2014) find in the real VIX from 2006 to 2011: the basis does not predict the spot VIX but it does predict the change in the futures. For whoever holds long futures, contango is a cost paid every day; for whoever sells them, a premium. The R² are small (2.4% for the future) because the daily volatility of the futures is much larger than the roll: the premium exists, but it is collected amid a lot of noise and the occasional jump.

The short-term futures index and the long ETP

A product that wants “30-day volatility” holds a mix of the first and second futures that shifts a little weight from the first to the second every day, so that the average maturity stays around one month. It is the construction of the S&P 500 VIX Short-Term Futures Index, which the prospectus of the first ETN on it describes as a long position that rolls daily between the first and second monthly futures1Note 1 · Barclays Bank PLC, free writing prospectus of the iPath S&P 500 VIX Short-Term Futures ETN (VXX), 2 December 2009, at the SEC: https://www.sec.gov/Archives/edgar/data/312070/000119312509245713/dfwp.htm. It describes the index as a long position that rolls daily from the first to the second VIX monthly future, with a constant one-month maturity, and an annual fee of 0.89%.. In the data, indice_futuros_cp follows the same rule: the weight of the first future is the fraction of sessions of the roll period (from one expiration to the next) left until its expiration. Rebuilding it from the futures is the proof that it is understood:

IN [04]python
etp = pd.read_csv("vol3_etp.csv", parse_dates=["fecha"])
etp = etp.set_index("fecha")
tabla = fu.pivot(index="fecha", columns="vencimiento",
                 values="precio")
vencs, ses = sorted(fu["vencimiento"].unique()), tabla.index
rend = [0.0]
for t in range(len(ses) - 1):
    hoy, man = ses[t], ses[t + 1]
    e1 = next(v for v in vencs if v > hoy)      # first future
    e2 = next(v for v in vencs if v > e1)       # second
    previos = [v for v in vencs if v <= hoy]
    e0 = max(previos) if previos else e1 - pd.Timedelta(days=35)
    w1 = (((ses > hoy) & (ses <= e1)).sum()
          / ((ses > e0) & (ses <= e1)).sum())  # weight of the first
    a0 = w1 * tabla.at[hoy, e1] + (1 - w1) * tabla.at[hoy, e2]
    a1 = w1 * tabla.at[man, e1] + (1 - w1) * tabla.at[man, e2]
    rend.append(a1 / a0 - 1)
rend = pd.Series(rend, ses)
indice = 100 * (1 + rend).cumprod()
print(f"maximum difference with the file: "
      f"{(indice - etp['indice_futuros_cp']).abs().max():.5f}")
print(f"index: 100 on {ses[0].date()}, "
      f"{indice.iloc[-1]:.2f} on {ses[-1].date()}")
OUT [04]Rebuilding the short-term futures index
maximum difference with the file: 0.00005
index: 100 on 2014-01-02, 0.49 on 2025-12-31

The maximum difference is five hundred-thousandths: the file's rounding to four decimals. The futures index falls from 100 to 0.49 in twelve years. It is the roll in contango accumulated session after session: every day a little of the first future, which has fallen toward the index, is sold and the second, more expensive one, is bought.

The long ETP (lvol, which mimics the VXX) adds to the index the 3-month rate on its collateral and subtracts an annual fee of 0.89%, the one set by the VXX prospectus1. With that rule it can also be rebuilt, and the result by year is this:

IN [05]python
ind = pd.read_csv("opciones3_indice.csv", parse_dates=["fecha"])
tb = ind.set_index("fecha")["tipo_3m"] / 100
largo = [100.0]
for t in range(len(ses) - 1):
    largo.append(largo[-1] * (1 + rend.iloc[t + 1]
                              + tb.iloc[t] / 252 - 0.0089 / 252))
largo = pd.Series(largo, ses)
print(f"maximum difference with lvol: "
      f"{(largo - etp['lvol']).abs().max():.5f}")
print(f"total 2014-2025: {largo.iloc[-1] / 100 - 1:.1%}")
anual = etp[["lvol", "ivol"]].resample("YE").last()
anual.loc[pd.Timestamp("2013-12-31")] = 100.0
anual = anual.sort_index().pct_change().iloc[1:]
anual.index = anual.index.year
(100 * anual).round(0).T
OUT [05]The long ETP, year by year
maximum difference with lvol: 0.00005
total 2014-2025: -99.4%
fecha  2014  2015  2016  2017  2018  2019   2020  2021  2022  2023  2024  2025
lvol  -48.0  11.0 -34.0 -57.0  44.0 -85.0  114.0 -75.0  61.0 -61.0 -54.0  -4.0
ivol   45.0 -44.0  10.0  96.0 -91.0   NaN    NaN   NaN   NaN   NaN   NaN   NaN

The header figure is this table over time, on a logarithmic scale. The long ETP loses 99.4% in total, with years of −85% (2019) and −75% (2021), those following a crisis, when VORX falls from high levels and the curve returns to contango, and strong gains in crisis years (+114% in 2020). Holding it is paying for expensive insurance; whoever buys it as a hedge has to get the timing right.

5 February 2018 and the inverse ETP

The daily inverse ETP (ivol, which mimics the XIV) delivers each day −1 times the return of the futures index, plus the rate and minus a 1.35% fee. While the curve is in contango, it earns what the long one loses: it goes from 100 to 221. And it has an acceleration clause: if it loses more than 80% in one day, it is liquidated. The one in the data mimics that of the real XIV: according to Credit Suisse's notice to the SEC of 6 February 2018, the intraday indicative value of the XIV on 5 February fell to 20% or less of the previous close, which constituted an acceleration event; the last trading day would be 20 February and holders would receive the closing indicative value of the valuation date.

IN [06]python
dias = pd.concat([vx.rename("vorx"),
                  f1["precio"].rename("f1"), f2["precio"].rename("f2"),
                  etp], axis=1).loc["2018-01-31":"2018-02-08"]
dias["rend_indice"] = dias["indice_futuros_cp"].pct_change()
print(f"VORX at the end of the sample: {vx.iloc[-1]}")
dias.round(3)
OUT [06]From 31 January to 8 February 2018
VORX at the end of the sample: 13.74
             vorx     f1     f2  indice_futuros_cp    lvol     ivol  rend_indice
fecha
2018-01-31  15.96  16.00  16.65             14.401  14.121  201.189          NaN
2018-02-01  17.44  17.15  16.95             15.041  14.748  192.256        0.044
2018-02-02  20.37  19.45  18.05             16.488  16.168  173.754        0.096
2018-02-05  36.78  41.65  31.45             31.479  30.868   15.780        0.909
2018-02-06  37.46  35.10  26.40             26.468  25.955      NaN       -0.159
2018-02-07  36.74  34.70  26.45             26.390  25.880      NaN       -0.003
2018-02-08  37.04  35.25  26.85             26.795  26.278      NaN        0.015

On 2 February the futures index already rises 9.6%. On Monday the 5th VORX goes from 20.37 to 36.78 and the first future from 19.45 to 41.65, above the index itself: it is the planted 22% rise over the model. The futures index rises 90.9% in one day, the inverse ETP falls from 173.75 to 15.78 and is liquidated; from the 6th its series is empty. The long one, which almost doubles its value that day, gives back part of it the next day, when the planted rise unwinds.

Why would the futures rise more than the index precisely that day? Because of the rebalancing of the products themselves. An ETP with daily leverage LL and value AA has an exposure L⋅AL \cdot A. If its index rises by xx, its value becomes A(1+Lx)A(1 + Lx) and its exposure is worth LA(1+x)LA(1 + x); to return to LL times its new value it has to trade

LA(1+Lx)−LA(1+x)=L(L−1) x A.L A (1 + L x) - L A (1 + x) = L (L - 1)\, x\, A .

For L=1L = 1 it does not trade; for L=2L = 2 or L=−1L = -1, L(L−1)=2>0L(L-1) = 2 > 0: both buy after a rally, and both do it at the close, when the price of their index is set.

IN [07]python
x = dias.loc["2018-02-05", "rend_indice"]
filas = {L: {"valor_tras_subida": 100 * (1 + L * x),
             "compra_de_futuros": 100 * L * (L - 1) * x}
         for L in (-1.0, -0.5, 1.0, 1.5, 2.0)}
pd.DataFrame(filas).T.round(1)
OUT [07]Forced rebalancing after a 90.9% rise
      valor_tras_subida  compra_de_futuros
-1.0                9.1              181.8
-0.5               54.5               68.2
 1.0              190.9                0.0
 1.5              236.4               68.2
 2.0              281.8              181.8
yesVol risesFutures riseInverse ETPs loseThey buy backLoses 80%Liquidated
Mermaid source
flowchart TB
  S[Vol rises] --> F[Futures rise]
  F --> P[Inverse ETPs lose]
  P --> R[They buy back]
  R --> F
  P --> A{Loses 80%}
  A -- yes --> Q[Liquidated]

For every 100 of value before the rally, a −1× inverse has to buy back 181.8 of futures, 20 times what it has left in value (9.1). A −0.5× product has to buy back 68.2 and survives with 54.5. That is why ProShares changed its SVXY from −1× to −0.5× at the close of 27 February 2018, according to the fund's own fact page. When the size of those products is large relative to futures volume in the closing window, the buying pushes the price, the rise increases the losses and forces more buying back. In the data that pressure is planted as the 22% over the model; in the real market its size is a figure to be estimated, not assumed.

Strategies: selling, buying and filtering volatility

With the daily returns of the futures index the basic strategies can be compared, all on notional (selling 1 of futures for each 1 of capital, which stays as collateral; the return in excess of the collateral is measured). Always selling is the inverse ETP without fees and without the acceleration clause; at −0.5×, the post-2018 SVXY; and the contango filter only sells when, at the previous close, the first future is above VORX:

IN [08]python
def medidas(x):
    x = x.iloc[1:]
    eq = (1 + x).cumprod()
    mes = (1 + x).resample("ME").prod() - 1
    return {"anual": eq.iloc[-1] ** (252 / len(x)) - 1,
            "vol": x.std() * np.sqrt(252),
            "sharpe": x.mean() / x.std() * np.sqrt(252),
            "caida_max": (eq / eq.cummax() - 1).min(),
            "peor_dia": x.min(), "peor_mes": mes.min(),
            "fecha_peor_dia": str(x.idxmin().date())}

corto = -rend
contango = (f1["precio"] > vx).reindex(ses).astype(float)
estr = {"always sell": corto, "sell at -0.5x": 0.5 * corto}
for lag in (1, 2, 3):
    estr[f"contango, signal t-{lag}"] = corto * contango.shift(lag)
cambios = contango.shift(1).diff().abs()
estr["contango t-1, cost 0.5%"] = (corto * contango.shift(1)
                                   - 0.005 * cambios)
print(f"entries and exits per year: {cambios.sum() / 12:.0f}")
tab = pd.DataFrame({k: medidas(v.fillna(0)) for k, v in estr.items()}).T
num = tab.columns[:-1]
tab[num] = tab[num].astype(float).round(3)
tab
OUT [08]Selling volatility: mean, tail and signal delay
entries and exits per year: 33
                         anual    vol  sharpe  caida_max  peor_dia  peor_mes fecha_peor_dia
always sell             -0.071  0.592   0.502     -0.989    -0.921    -0.964     2020-03-09
sell at -0.5x            0.101  0.296   0.502     -0.715    -0.460    -0.628     2020-03-09
contango, signal t-1     0.813  0.314   2.058     -0.433    -0.373    -0.332     2016-06-24
contango, signal t-2     0.784  0.313   2.016     -0.456    -0.373    -0.368     2016-06-24
contango, signal t-3     0.281  0.419   1.022     -0.937    -0.909    -0.923     2018-02-05
contango t-1, cost 0.5%  0.522  0.315   1.499     -0.506    -0.373    -0.342     2016-06-24

Always selling gives a Sharpe of 0.50 and a maximum drawdown of 98.9%: the earlier inverse ETP, without its liquidation. The worst day is 2020-03-09 (−92.1%) and the worst month, March 2020 (−96.4%): whoever sold at 1× would have lost practically everything twice in the sample. At −0.5× the maximum drawdown is still 71.5%. Selling volatility futures is the premium selling of lesson 3.6.1 with an even fatter tail: what is collected is the roll, and what is risked is a 90% jump in one session.

Fig. — Short VORX futures index, monthly wealth on a log scale: always, with the contango filter from the previous close and with the same filter three sessions later (synthetic data, no costs)

The contango filter with the previous close's signal gives a Sharpe of 2.06 and a maximum drawdown of 43.3%, with the worst day on 2016-06-24. It is too good, and the table says why: with the signal from two sessions earlier it barely changes, but with the one from three sessions earlier it sells on 2018-02-05, loses 90.9% that day and its maximum drawdown goes to 93.7%. The filter works in these data because, in the two big VORX jumps in the sample, the spot index rises one or two days before the futures and the curve inverts just in time to exit. It is a property of how the data are generated (VORX departs from its model and the difference fades with a 10-day time constant), not a law of the market. With a cost of 0.5% of notional each time the position is opened or closed (about 33 times a year), the Sharpe falls to 1.50 and the maximum drawdown rises to 50.6%.

The other side is buying volatility as insurance. A portfolio that holds a part in the long ETP and the rest in the ORX index with dividends, rebalanced every month:

IN [09]python
rt = ind.set_index("fecha")["cierre_rt"]
mens = (pd.concat([rt, etp["lvol"]], axis=1).resample("ME").last()
        .pct_change().dropna())
seguro = {}
for w in (0.0, 0.05, 0.10):
    p = (1 - w) * mens["cierre_rt"] + w * mens["lvol"]
    eq = (1 + p).cumprod()
    seguro[f"{w:.0%} in LVOL"] = {
        "anual": eq.iloc[-1] ** (12 / len(p)) - 1,
        "vol": p.std() * np.sqrt(12),
        "caida_max": (eq / eq.cummax() - 1).min(),
        "peor_mes": p.min()}
pd.DataFrame(seguro).T.round(3)
OUT [09]The long ETP as portfolio insurance
             anual    vol  caida_max  peor_mes
0% in LVOL   0.122  0.187     -0.366    -0.288
5% in LVOL   0.116  0.157     -0.269    -0.269
10% in LVOL  0.106  0.146     -0.249    -0.249

With 10% in the long ETP, the portfolio's maximum drawdown falls from 36.6% to 24.9% and the worst month from −28.8% to −24.9%. In exchange, the annual return falls from 12.2% to 10.6%: 1.6 points a year for twelve years to trim the worst drawdown. The insurance works because it is rebalanced every month (what rises in crises is sold) and because the crises in the sample were short; with a slow crisis, without volatility jumps, the cost is paid all the same and the hedge barely shows up.

VSTOXX and VIX: what can be compared

The VVGX in the data mimics the VSTOXX: the 30-day volatility of a fictitious European index with a variance 1.2 times that of ORX, returns correlated 0.8 with it and its own noise. There is only the index: no option chain and no futures.

IN [10]python
dif = vi["vvgx"] - vi["vorx"]
print(f"correlation of levels: {vi['vorx'].corr(vi['vvgx']):.3f}; "
      f"of daily changes: "
      f"{vi['vorx'].diff().corr(vi['vvgx'].diff()):.3f}")
print(f"VVGX − VORX: mean {dif.mean():.2f}, std. {dif.std():.2f},"
      f" from {dif.min():.2f} to {dif.max():.2f} points")
vi[["vorx", "vvgx"]].describe().loc[["mean", "50%", "max"]].round(2)
OUT [10]VVGX versus VORX
correlation of levels: 0.992; of daily changes: 0.964
VVGX − VORX: mean 1.50, std. 1.22, from -3.89 to 6.97 points
       vorx   vvgx
mean  19.80  21.31
50%   17.66  19.05
max   73.98  72.48

The European index trades on average 1.5 points above the US one, with a correlation of levels of 0.99. With a differential like that, a volatility spread is often proposed: buying VSTOXX futures and selling VIX futures when the differential is low, or the other way around. With these data it cannot be tested, for three reasons: there are no VVGX futures; the futures of the two exchanges expire on different dates (each 30 days before the expiration of its options) and settle with different methods (the Cboe SOQ versus the Eurex 11:00 to 12:00 CET average); and the multiplier and the currency change (1,000 USD versus 100 EUR per point), so the hedge needs a ratio and leaves currency risk (lesson 3.3.3).

Exercises

  1. Compute a daily 2× long ETP on the futures index (without fee or rate) and compare it with twice the return of the index over the whole period. Why do they not match?
Solution
IN [11]python
doble = (1 + 2 * rend).cumprod()
print(f"index over the period: {indice.iloc[-1] / 100 - 1:.2%}")
print(f"daily 2x: {doble.iloc[-1] - 1:.2%}")
print(f"2x of the period (impossible below -100%): "
      f"{2 * (indice.iloc[-1] / 100 - 1):.2%}")
print(f"maximum of the daily 2x: {doble.max():.2f} on "
      f"{doble.idxmax().date()}")
OUT [11]Daily leverage and period return
index over the period: -99.51%
daily 2x: -100.00%
2x of the period (impossible below -100%): -199.02%
maximum of the daily 2x: 1.08 on 2014-04-22

The daily 2× ends up practically at zero (its maximum, 1.08, was in April 2014). It multiplies each daily return by 2, not that of the period. With a very volatile index trending down, the product of the (1+2rt)(1 + 2r_t) falls much faster than the index: the volatility drag grows with the square of the leverage. That is why the prospectuses of these products say their objective is for a single day.

  1. Repeat the roll yield regression with the second future (orden == 1). Is the coefficient larger or smaller than for the first?
Solution
IN [12]python
g2 = fu[fu["orden"] == 1].set_index("fecha")
g2 = g2.assign(roll=(g2["precio"] - vx.reindex(g2.index))
               / g2["dias"],
               d_futuro=g2["siguiente"] - g2["precio"]).dropna()
m2 = sm.OLS(g2["d_futuro"], sm.add_constant(g2["roll"])).fit(
    cov_type="HAC", cov_kwds={"maxlags": 5})
print(f"coefficient {m2.params['roll']:.2f}, "
      f"t {m2.tvalues['roll']:.1f}")
OUT [12]Roll of the second future
coefficient -1.48, t -4.3

The coefficient is similar (−1.48 versus −1.56, both with t = −4.3): the second future also falls with its roll. Its basis against spot VORX is, even so, a more indirect measure, because it has more than 30 days left to converge and first takes the place of the first future; with so little difference between the two coefficients, the data do not allow saying that one is larger than the other.

  1. An investor wants to “buy the VIX” because they believe there will be a crisis at some point in the next three years and proposes holding a long volatility ETP. Explain what will happen to them if they are right about the crisis but not about the timing.
Solution

The long ETP does not track the VIX: it tracks a futures index that rolls in contango. While the crisis does not arrive, it loses a large fraction of its value every year (in the course data, between 4% and 85% in the years it falls). If the crisis arrives in the third year, the ETP rises a lot in percentage terms, but on a value that has already lost most of itself: in the data, after losing 85% in 2019, the +114% of 2020 leaves the ETP far below where it was at the start of 2019. The alternative consistent with their idea is to buy long-dated options (calls on volatility futures or index puts), whose cost is known when they are bought, or to hold a small rebalanced part as in this lesson's portfolio.

Footnotes

  1. Barclays Bank PLC, free writing prospectus of the iPath S&P 500 VIX Short-Term Futures ETN (VXX), 2 December 2009, at the SEC: https://www.sec.gov/Archives/edgar/data/312070/000119312509245713/dfwp.htm. It describes the index as a long position that rolls daily from the first to the second VIX monthly future, with a constant one-month maturity, and an annual fee of 0.89%. ↩ ↩2

Each answer is folded under its question

Self-assessment

  1. Why is there no ETP that replicates the spot VIX?

    • ABecause the SEC prohibits it
    • BBecause the VIX is only calculated once a day
    • CBecause the VIX is a calculation on 30-day S&P 500 option prices that cannot be held as a static portfolio: what can be replicated are the futures, which converge to it only at expiration
    • DBecause S&P 500 options are not liquid enough
    Show answer

    Answer C. Because the VIX is a calculation on 30-day S&P 500 option prices that cannot be held as a static portfolio: what can be replicated are the futures, which converge to it only at expiration

    The VIX measures 30-day expected volatility with a basket of options that changes every day (strikes and expirations). Exchange-traded products hold futures, and a VIX future is worth the expectation of the VIX at its expiration, not today's VIX.

  2. In the data, the first future is above VORX in 63% of sessions. What happens on average to whoever holds a long position in the first future?

    • AThey earn the roll yield, because the future rises toward the index
    • BNothing: future and index move the same
    • CThey win only if the index rises
    • DThey lose: in contango the future tends to fall toward the index as expiration nears, and the lesson's regression gives a coefficient of −1.56 on the daily roll, while the index does not move predictably
    Show answer

    Answer D. They lose: in contango the future tends to fall toward the index as expiration nears, and the lesson's regression gives a coefficient of −1.56 on the daily roll, while the index does not move predictably

    The basis predicts the change in the future (t = −4.3) and not that of the index (t = 1.0). It is the same conclusion Simon and Campasano (2014) draw from the real VIX from 2006 to 2011: the basis is above all a premium collected by selling futures in contango.

  3. A daily inverse ETP (−1×) has a value of 100 and its index rises 90.9% in one day. How much of the index does it have to buy back at the close to return to −1×?

    • ANothing: inverse products do not rebalance
    • B181.8: the short position is now worth 190.9 and the target is 9.1; buying back means buying futures right after the rally
    • C9.1, what it has left in value
    • D90.9
    Show answer

    Answer B. 181.8: the short position is now worth 190.9 and the target is 9.1; buying back means buying futures right after the rally

    With leverage L, the rebalancing purchase is L·(L − 1)·x per unit of value. With L = −1 and x = 0.909, 2·0.909 = 1.818 times the previous value. Inverse and leveraged products buy after rallies, which pushes the price in the same direction.

  4. Selling the futures index only when the first future is in contango gives a Sharpe of 2.06 in the data. What invalidates it as evidence?

    • AThat with the signal from three sessions earlier instead of yesterday's it drops 90.9% on 2018-02-05 and its maximum drawdown goes from 43% to 94%: the result depends on dodging by one or two days a jump planted in synthetic data
    • BNothing: the contango filter works
    • CThat the Sharpe is computed with daily returns
    • DThat it does not include the index dividend
    Show answer

    Answer A. That with the signal from three sessions earlier instead of yesterday's it drops 90.9% on 2018-02-05 and its maximum drawdown goes from 43% to 94%: the result depends on dodging by one or two days a jump planted in synthetic data

    A result that changes radically with one or two days of delay is fitted to a few episodes. In the synthetic data, VORX rises before the jumps and the curve inverts one day earlier; in a real market nothing guarantees that warning, and selling volatility is still short the tail.

  5. What do the course data allow you to do with the VSTOXX?

    • ACompare its level with that of VORX (the VVGX index), but not backtest its futures: the file only has the index
    • BBacktest its futures, like those of VORX
    • CNothing, there are no European data
    • DRebuild the VSTOXX from Euro Stoxx 50 options
    Show answer

    Answer A. Compare its level with that of VORX (the VVGX index), but not backtest its futures: the file only has the index

    vol3_indices.csv has vvgx, the 30-day volatility of a fictitious European index, with no chain or futures. The rules of VSTOXX futures (Eurex, FVS) can be explained, but not tested with these data.

Key points

  • The VIX and the VSTOXX are calculated with 30-day options and cannot be held in a portfolio; what trades are futures that settle to the index at expiration (Cboe: Special Opening Quotation; Eurex: average from 11:00 to 12:00 CET).
  • The futures curve is in contango almost always (63% of sessions with the first future above VORX, 71% with the second above the first) and inverts in crises; the basis predicts the change in the future, not that of the index.
  • An index that rolls daily from the first to the second future loses in contango: the long ETP falls 99.4% from 2014 to 2025 and wins only in crises.
  • The daily inverse ETP wins while it lasts and loses 90.9% on 2018-02-05: inverse and leveraged products have to buy after rallies, and their acceleration clause liquidates them.
  • Selling volatility has a Sharpe of 0.50 with a maximum drawdown of 98.9%; contango filters greatly improve the result in these data, but they depend on dodging the jumps by one or two days.

Resources