Vix single day spikes & their historical returns

Taking a look at Vix single day spikes, since there have been two rather significant and rare single day spikes of 39 and 49% in 2016. Note im not talking about Vix daily swings, but single day spikes from close to close. The intention here is to gauge how Vix has historically behaved after significant single day spikes

First, importing modules, vix data etc. Im running this on Quantopian so importing data is straightforward


from quantopian.interactive.data.quandl import yahoo_index_vix
from odo import odo
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
import scipy as sp

Setting up and cleaning the vix dataframe that came from quandl and adding the single day % change values

data = odo(yahoo_index_vix, pd.DataFrame)
data = data.drop(["open_", "high", "low", "adjusted_close", "volume", "timestamp"], axis = 1)
data["close"].loc[18] = 14.04 #Fixing error data
data = data.set_index(["asof_date"])

data["pct_change"] = data["close"].pct_change()
data = data.dropna()

This years second biggest spike was 39%. There have been only 12 single day spikes of 39% or greater since 1990 (again, from close to close)

len(data[data["pct_change"] > 0.39])

12

When they occurred and what their actual spike percentages were

data[data["pct_change"] > 0.39].sort()

screen-shot-2016-12-09-at-18-26-13

First, looking at all Vix daily percent changes as an empirical cumulative distribution, so one gets better idea how the daily percent changes are distributed. In this post, we are interested in the positive daily percent changes. As one can observe, spikes above 20% are rather rare

ecd = np.arange(1, len(data)+1, dtype=float) / len(data)
ticks = np.arange(-0.3, 1.1, 0.1)

plt.plot(np.sort(data["pct_change"]), ecd)
plt.xlabel("Vix daily percent changes (from close to close)")
plt.ylabel("Fraction of daily pct changes that are smaller than corresponding x")
plt.axvline(linestyle="--", color="#333333", linewidth=1)
plt.xticks(ticks)
plt.yticks(ticks)
plt.grid(alpha=0.21)
plt.margins(0.05)

unknown-1

We can now plot the returns N days later from a spike, iterations are for 10% and 20% spike returns 5 days later in order to have a large enough sample set. I wanted to see weither or not the spike % was correlated to the forward returns, it seems to be the case. The larger the single day spike, the more likely a negative Vix return down the road. Though it must be noted that the sample set for spikes larger than 20% is low

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

ret10 = pct_ret(data["close"], 5).where(data["pct_change"] > 0.1).dropna()
pct10 = data["pct_change"][data["pct_change"] > 0.1]

ret20 = pct_ret(data["close"], 5).where(data["pct_change"] > 0.2).dropna()
pct20 = data["pct_change"][data["pct_change"] > 0.2]

slope, intercept, r_val, p_val, std_err = sp.stats.linregress(pct10, ret10)
ret10_predict = intercept + slope * pct10

plt.plot(pct10, ret10_predict, "-", label="Linreg")
plt.scatter(data["pct_change"][data["pct_change"]>0.1], ret_10,
color="#333333", alpha=0.55, label="Vix spike >= 10%, return 5 days later")
plt.scatter(data["pct_change"][data["pct_change"]>0.2], ret_20,
color="crimson", s=34, label="Vix spike >= 20%, return 5 days later")

plt.ylabel("Vix % return 5 days later")
plt.xlabel("Vix single day spike % (from close to close)")
plt.axhline(linestyle="--", linewidth=1, color="#333333")
plt.legend(loc="upper right")
plt.ylim(-0.5, 1)
plt.grid(alpha=0.21)
plt.title("R2={}".format(r_val**2))

 

unknown

For example, mean Vix return after a 20% single day spike or greater, 5 days later is about -16%

np.mean(ret_20[ret_20 < 0])

-0.16629674607687678

For a clearer picture on how Vix actually looks like after significant spikes, we can also plot the N day returns of all instances where a significant spike occurred (in the chart below, its 64 trading days). There are notable rebound tendencies at the 10th, 20-23rd and 40th trading days after a spike. The higher spike means are more pronounced since the sample size is rather smaller on those instances

data2 = data.copy().reset_index()

def rets(df, days, pct, pct_to):
ret_df = pd.DataFrame()
for index, row in df.iterrows():
if row["pct_change"] > pct and row["pct_change"] < pct_to and df["pct_change"].iloc[index-1] < pct:
ret = df["close"].iloc[index:index+days]
ret = np.log(ret).diff().fillna(0)
ret = pd.Series(ret).reset_index(drop=True)
ret_df[index] = ret
return ret_df

twenty = rets(data2, 65, 0.2, 0.3).mean(axis=1).cumsum()
thirty = rets(data2, 65, 0.3, 0.4).mean(axis=1).cumsum()
forty = rets(data2, 65, 0.4, 1).mean(axis=1).cumsum()

plt.plot(twenty, color="royalblue", label="Vix mean return after a spike > 20% and < 30%")
plt.plot(thirty, color="crimson", label="Vix mean return after a spike > 30% and < 40%")
plt.plot(forty, color="cadetblue", label="Vix mean return after a spike of > 40%")

plt.xlabel("# Of days from spike")
plt.ylabel("Vix % return")
plt.grid(alpha=0.21)
plt.ylim(-0.3, 0.1)
plt.xlim(0, 64)
plt.legend(loc="upper right")

unknown-1

The mean returns chart is desceptive, since there are of course plenty of instances where Vix just keeps going up, so one can get a better picture by looking at all the instances. In the case below, i plotted all spike instances of 20% or greater, 64 days forward

plt.plot(rets(data2, 65, 0.2, 1).cumsum(axis=0), linewidth=1, alpha=0.21, color="#333333")
plt.plot(forty, color="crimson", label="Mean")

plt.title("All instances of Vix singe day spikes > 20%, returns 64 days forward")
plt.xlabel("# Of days from spike")
plt.ylabel("Vix % return")
plt.grid(alpha=0.21)
plt.axhline(0, linewidth=1, linestyle="--", color="#333333")
plt.xlim(0, 64)
plt.legend(loc="upper right")

unknown-2

One additional way of looking at the dataset is to make a heatmap of all single day spike instances, meaning we plot out all Vix single day spikes and their mean returns, regardless of the size of the spike or the direction of the spike. First converted the pct changes to integers and then grouped all the data by those. From that a heatmap of mean returns for all Vix spike % instances can be summoned

Nothing meaningful happens in the middle of the % change range, but the edges are more pronounced, however again its worth noting that the sample size on the edges is also smaller

df3 = data.copy()

for i in range(1, 35):
df3[str(i)] = pct_ret(data3["close"], i)

df3["pct_change"] = df3["pct_change"].apply(lambda x: int(round(x*100)))
df3.reset_index(inplace=True)
df3.drop(["asof_date","close"], axis=1, inplace=True)

grouped = df3.loc["1":].groupby(df3["pct_change"], as_index=True, squeeze=True).mean()
grouped.drop("pct_change", axis=1, inplace=True)

plt.figure(figsize=(16, 13))
sns.heatmap(grouped, annot=False, cmap="RdBu")
plt.ylabel("Vix single day spike %")
plt.xlabel("Number of days after spike")
plt.title("Vix single day % spikes vs. mean returns N days later")

unknown-3

5 thoughts on “Vix single day spikes & their historical returns

  1. Quantocracy's Daily Wrap for 12/19/2016 | Quantocracy

  2. Good read and great look! I am still interested in examination of 1-day spike distribution and fitting it with GEV models as one can do for black swans. You made me thinking again on that! Cheers.

  3. Research Links on VIX and related ETFs – Fang's Page

  4. Good stuff. I was thinking along the same line: what is the speed of mean reversion of VIX after a spike ? My initial thinking was: if the spike was due to a binary event (natural disaster, political events …) then VIX will revert more quickly to the mean. If the spike is due to macro, lingering concerns (e.g. global growth, Chinese currency devaluation etc.) then VIX will revert to the mean at a slower pace. Haven’t had time to formalize this idea. Any thoughts ?

  5. Sure. It’s easy. I do agree with you on your thoughts. It’s obvious to split one-off events with a possible pattern “to be fitted” somehow. You’re correct. A slower pace would involve a direct correlation with some local/global trend or collateral thinking. If so, if you could spot the repetitive (e.g., a decay) characteristics every single time the spike occurs, that would be a step forward in this research! Does it make a sense? Remember – the best remains still ahead of us. Dare to explore! :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s