Vix Spx Seasonality By Month, Even Keel

While seeing some “sell in may” headlines a while ago, thought i’d pull up the monthly mean returns for spx and vix. I wanted to see them on an even keel, so that each month starts at 0%, to better gauge their monthly behaviour

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import quandl
import calendar

%matplotlib inline

Since yahoo data went dark, had to pull it in manually. There is a more intelligent solution posted in Trading With Python blog

spx = pd.read_csv("../Data/Spx.csv", index_col="Date")
spx.index = pd.to_datetime(spx.index, format="%Y-%m-%d")
spx = spx.apply(pd.to_numeric, errors="coerce")

vix = pd.read_csv("../Data/Vix.csv", index_col="Date")
vix.index = pd.to_datetime(vix.index, format="%Y-%m-%d")
vix = vix.apply(pd.to_numeric, errors="coerce")

Adding dates and stuff to dataframes

def DatesAndPct(df):
 df["day"] = df.index.dayofyear
 df["month"] = df.index.month
 df["year"] = df.index.year
 df["pct"] = np.log(df["Adj Close"]).diff()
 return df

spx = DatesAndPct(spx)
vix = DatesAndPct(vix)

Making a function for plots and plotting e‘m all at once

plt.figure(figsize=(16, 9))
label_props = dict(boxstyle="square", facecolor="w", edgecolor="none", alpha=0.69)

def plotMonths(df, color, line=1, m_names=True):
 df.index = pd.to_datetime(df.index, format="%Y-%m-%d")
 df.set_index(df["day"], inplace=True, drop=True)

 for i in range(1, 13):
 month_name = calendar.month_abbr[i] # Adding month name

 data = df[df["month"] == i]
 out = data["pct"].groupby(data.index).mean()
 out.iloc[0] = 0 # Setting returns to start from zero

 # Getting coordinates for month name labels
 x = out.index[-1]+2
 y = out.cumsum().iloc[-1]-0.01

 # Plotting
 plt.plot(out.cumsum(), linewidth=line, color=color, label="_nolabel_")
 if m_names == True:
 plt.text(x, y, month_name, size=13, bbox=label_props)

plotMonths(spx, "#555555", 2, m_names=False)
plotMonths(vix, "crimson", m_names=True)

plt.title("Vix and Spx mean returns from month start (log scale)") 
plt.plot([], [], label="Vix (since 1990)", color="crimson") # Adding custom legends
plt.plot([], [], label="Spx (since 1950)", color="#555555") # Adding custom legends
plt.axhline(linestyle="--", color="#555555", alpha=0.55)
plt.legend(loc="upper left")
plt.xlabel("Day of year")
plt.ylabel("Log cumulative monthly return")


While im at it, since Crude and Gold also have volatility indexes available, also pulled summaries for them. Crude vs Ovx and Gold vs Gvz



Thanks for your time


Vix Below Low Redux

Well, Its here, spot Vix close below 10. From what i read from the web, people are piling into short vol strategies on an escalating scale. I suspect unwinding of that trade will be rather brutal. I wish good luck to every short vol trader out there and dont forget to wear a helmet :)

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

%matplotlib inline

vix = quandl.get("YAHOO/INDEX_VIX", authtoken="YOUR_KEY")
vix.index = pd.to_datetime(vix.index)
vix.drop({"Open", "High", "Low", "Close", "Volume"}, inplace=True, axis=1)
vix.columns = ["close"]
vix["pct"] = np.log(vix["close"]).diff()

Lets look at all instances where Vix closed below 10 (if prev close was >= 10)

heat = vix[(vix["close"].shift(1) > 10) & (vix["close"] < 10)].transpose().iloc[:1]
heat.columns = x: x.strftime("%Y-%m-%d"))

cmap = sns.dark_palette("red", as_cmap=True)

fig, ax = plt.subplots(1, figsize=(16, 9))
ax = sns.heatmap(heat, square=True, cmap=cmap, linewidths=1,
 annot=True, cbar=False, annot_kws={"size":42}, fmt="g")
plt.title("All Vix closes < 10 since 1993 (if previous close was >= 10)")


Closes below 10 are rare

ecd = np.arange(1, len(vix)+1) / len(vix)

plt.figure(figsize=(11, 11))
plt.plot(np.sort(vix["close"]), ecd, linestyle="none", marker=".", alpha=0.55, color="#555555")
plt.axvline(10, linestyle="--", color="crimson", label="Vix below 10 threshold")
plt.title("Vix daily close values Ecdf")
plt.xlabel("Vix close")
plt.ylabel("Percentage of closes that are less than corresponding closes on x")
plt.legend(loc="center right")


According to the few past instances, Vix should live’n up in the coming days

def getRets(df, days):
df = df.reset_index()
df_out = pd.DataFrame()
for index, row in df.iterrows():
if df["close"].iloc[index-1] >= 10 and df["close"].iloc[index] < 10:
ret = df["pct"].iloc[index:index+days]
#ret = np.log(ret).diff()
ret.iloc[:1] = 0
ret.reset_index(drop=True, inplace=True)
df_out[index] = ret

return df_out

vix_21rets = getRets(vix, 90+1)

plt.figure(figsize=(16, 9))
plt.plot(vix_21rets.cumsum(), color="#555555", alpha=0.34, label="_nolegend_")
plt.plot(vix_21rets.mean(axis=1).cumsum(), color="crimson", label="Mean rets")
plt.title("Vix returns after closing below 10")
plt.ylabel("Vix % return")
plt.xlabel("Days after closing below 10")
plt.axhline(linestyle="--", linewidth=1, color="#333333")
plt.xticks(np.arange(0, 90+1, 5))
plt.legend(loc="upper left")



Thanks your time and feel free to leave a comment

Vix Below Low

Vix homing in on a close below 10. Has’nt happened during my time as a volatility speculator

All instances of Vix closing below 10 (if previous close was >= 10):