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.
- Module
- 3.6 · Systematic volatility and options strategies
- Duration
- 70 min
- Prerequisites
- 3.1.2 · Futures curve: basis, cost of carry, contango and roll yield, 3.4.6 · Realized vs implied volatility; volatility risk premium; VIX and VSTOXX, 3.6.1 · Systematic premium selling: put-write, strangles and their tail risk
- Author
- Jesús Cuesta
- Updated
Level 3 lessons
3.1 · Futures in depth
3.2 · Futures spreads
- 3.2.1 Calendar spreads, butterflies and condors
- 3.2.2 Intercommodity spreads: crack, spark and crush
- 3.2.3 Rate and index spreads: 2s10s, NOB, NQ/ES and Europe versus the U.S.
- 3.2.4 Hedge ratios: by notional, by DV01 and by volatility
- 3.2.5 Spread data and backtesting: listed, synthetic and legged
- 3.2.6 Seasonality of spreads: multi-year analysis, patterns and traps
3.3 · Advanced forex
3.4 · Option theory and pricing
- 3.4.1 Payoffs, put-call parity and arbitrage bounds
- 3.4.2 Binomial trees, Black-Scholes-Merton, Black-76 and Monte Carlo
- 3.4.3 Greeks: delta, gamma, vega, theta, rho, vanna, volga and charm
- 3.4.4 Implied volatility: Newton-Raphson, Brent, py_vollib and QuantLib
- 3.4.5 Smile, skew and term structure; volatility surface with SVI
- 3.4.6 Realized vs implied volatility; volatility risk premium; VIX and VSTOXX
- 3.4.7 Advanced models: Heston, SABR and local volatility
3.5 · Option structures in Python
- 3.5.1 Directional and vertical structures: call, put, covered call, collar and spreads
- 3.5.2 Volatility and range structures: straddles, strangles, condors and butterflies
- 3.5.3 Calendars, diagonals, ratios, backspreads, risk reversals and jade lizard
- 3.5.4 A custom tool: payoff, aggregate Greeks and scenarios
- 3.5.5 Management: adjustments, rolls, early closes, early assignment and pin risk
3.6 · Systematic volatility and options strategies
3.7 · Crypto derivatives and specific strategies
- 3.7.1 Perpetuals: funding mechanics, mark price and liquidations
- 3.7.2 Funding arbitrage, basis with futures and cross-exchange arbitrage
- 3.7.3 Crypto options, on-chain data and DeFi
- 3.7.4 ccxt for trading on multiple exchanges
- 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.
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) | |
|---|---|---|
| Multiplier | 1,000 USD per point | 100 EUR per point |
| Tick | 0.05 points (50 USD) | 0.05 points (5 EUR) |
| Final settlement | The Wednesday 30 days before the third Friday of the following month | 30 calendar days before the expiration of the underlying options |
| Settlement price | SOQ: the VIX calculated with that day's opening option prices | Average of the index between 11:00 and 12:00 CET on the last day |
| Settlement | Cash | Cash |
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.
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()){'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}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:
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}")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.000The 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: . 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):
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}")d_futuro : coefficient -1.56, t -4.3, R² 0.024
d_vorx : coefficient 0.42, t 1.0, R² 0.001The 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:
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()}")maximum difference with the file: 0.00005
index: 100 on 2014-01-02, 0.49 on 2025-12-31The 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:
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).Tmaximum 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 NaNThe 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.
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)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.015On 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 and value has an exposure . If its index rises by , its value becomes and its exposure is worth ; to return to times its new value it has to trade
For it does not trade; for or , : both buy after a rally, and both do it at the close, when the price of their index is set.
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) 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.8Mermaid 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:
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)
tabentries 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-24Always 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.
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:
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) 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.249With 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.
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)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.48The 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
- 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
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()}")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-22The 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 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.
- Repeat the roll yield regression with the second future (
orden == 1). Is the coefficient larger or smaller than for the first?
Solution
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}")coefficient -1.48, t -4.3The 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.
- 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
-
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
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.
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.
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.
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.
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.csvhasvvgx, 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
Documentation
- Cboe: methodology of the Cboe Volatility Index (VIX), including derivatives settlement (SOQ)
- Cboe Futures Exchange: rule book (chapter 12, VX futures specifications)
- Eurex: VSTOXX futures specifications (FVS)
- Credit Suisse AG (SEC, Form 6-K, 6 February 2018): acceleration of the XIV ETNs
- ProShares: SVXY, −0.5× target of the S&P 500 VIX Short-Term Futures Index since 27 February 2018