VIX closes on a 1 year low and below 12, what happens next?

This tweet from @aaljechin looked interesting: Let’s look at what happens after a year low in VIX index.

  • VIX index must be below the minimum from the last 255 days (business year).
  • No other year low in the past month
  • VIX below 12

First get some data:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy as sp
import scipy.stats
import quandl

vix_index = quandl.get("YAHOO/INDEX_VIX")
vix_fut = quandl.get("CHRIS/CBOE_VX1")

Create signals for both index and futures. There where no futures traded before june 2005.

# vix closes on a 1 year low and below 12
signal1 = ( (pd.rolling_min(vix_index.shift(1).Close,255) > vix_index.Close)  &
          (vix_index.Close < 12) )

# and no match in the last month
signal2 = signal1 & (pd.rolling_sum(signal1.shift(1),20) == 0)

signal = signal2[signal2==True]

# no VIX futures before this date...
signal_fut = signal['2005-6-20':]

There are 16 events found with these conditions:

def pct_ret(close, amount):
    rets = (close.shift(-amount) / close) - 1
    return rets

# 15 occasions
ret5days_index = pct_ret(vix_index.Close, 5)[signal.index].dropna()
ret10days_index = pct_ret(vix_index.Close, 10)[signal.index].dropna()
ret15days_index = pct_ret(vix_index.Close, 15)[signal.index].dropna()
ret30days_index = pct_ret(vix_index.Close, 30)[signal.index].dropna()
ret60days_index = pct_ret(vix_index.Close, 60)[signal.index].dropna()

# 20% avg 
ret60days_index.mean()

VIX is 20% above the year min after 60 days. This heatmap shows each instance:

vix_index_heatmap=vix_index.copy()

for i in range(1, 60):
    vix_index_heatmap[str(i)] = 100*pct_ret(vix_index["Close"], i)

vix_index_heatmap.drop(["Open","High","Low","Close","Volume","Adjusted Close"], axis=1, inplace=True)
vix_index_heatmap=vix_index_heatmap.loc[signal.index].dropna()

plt.figure(figsize=(16, 13))
sns.heatmap(vix_index_heatmap, yticklabels=False,annot=True, fmt="d")
plt.ylabel("VIX on 1 year low events")
plt.xlabel("Days after low")
plt.title("VIX year low vs. index returns N days later")

Can we trade this with VIX futures? Let’s check:

# 6 occasions
ret5days_fut = pct_ret(vix_fut.Close, 5)[signal_fut.index].dropna()
ret10days_fut = pct_ret(vix_fut.Close, 10)[signal_fut.index].dropna()
ret15days_fut = pct_ret(vix_fut.Close, 15)[signal_fut.index].dropna()
ret30days_fut = pct_ret(vix_fut.Close, 30)[signal_fut.index].dropna()
ret60days_fut = pct_ret(vix_fut.Close, 60)[signal_fut.index].dropna()

# -11% avg
ret60days_fut.mean()

In the six occasions that the signal is activated, we get an average return of -11% after 60 days. Looks like all the profit plus more is eaten by the contango in the VIX futures.

vix_fut_heatmap=vix_fut.copy()

for i in range(1, 60):
    vix_fut_heatmap[str(i)] = 100*pct_ret(vix_fut["Close"], i)

vix_fut_heatmap.drop(["Open","High","Low","Close","Settle", "Change",  "Total Volume",  "EFP","Prev. Day Open Interest"], axis=1, inplace=True)
vix_fut_heatmap=vix_fut_heatmap.loc[signal_fut.index].dropna()

plt.figure(figsize=(16, 13))
sns.heatmap(vix_fut_heatmap, yticklabels=False,annot=True, fmt="d")
plt.ylabel("VIX on 1 year low events")
plt.xlabel("Days after low")
plt.title("VIX year low vs. futures returns N days later")