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
sns.set(style="whitegrid")

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.grid(alpha=0.21)
plt.legend(loc="upper left")
plt.xlabel("Day of year")
plt.ylabel("Log cumulative monthly return")

Unknown-18

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

Unknown-19

Unknown-20

Thanks for your time

Advertisements

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

sns.set(style="whitegrid")
%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 = heat.columns.map(lambda 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")
ax.axes.get_yaxis().set_visible(False)
plt.title("All Vix closes < 10 since 1993 (if previous close was >= 10)")

Unknown-7

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.grid(alpha=0.21)
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")

Unknown-8

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.grid(alpha=0.21)
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")

Unknown-9

Notebook:
https://github.com/Darellblg/voodoomarkets/blob/master/BlogVixBelowLow.ipynb

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):
Unknown-6

Vix Blues, Large Close to Close Declines in Vix

This monday, we were witnesses to a rather large decline in Vix. Taking a quick look at how often drops like this happen and how has Vix behaved after large single day drops


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

sns.set(style="whitegrid")
%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"] = vix["close"].pct_change()

There havent been that many instances of Vix gapping down more than 20%. Mondays declide has made it into the top three of the hall of shame, only topped by august 2011 and october 2008


heat = vix[vix["pct"] <= -0.2].transpose().iloc[1:]
heat.columns = heat.columns.map(lambda x: str(x)[:10])

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":21})
ax.axes.get_yaxis().set_visible(False)
plt.title("Hall of shame - Top 10 close to close pct declines in Vix")

Unknown

20%+ declines are indeed rare while 20%+ spikes are not (comparatively speaking)


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

plt.figure(figsize=(11, 11))
plt.plot(np.sort(vix["pct"]), ecd, linestyle="none", marker=".", alpha=0.55, color="#555555")
plt.axvline(-0.26, linestyle="--", color="crimson", label="26% Decline on Monday 24’th of April 2017")
plt.grid(alpha=0.21)
plt.xlabel("Vix single day pct change")
plt.legend(loc="center right")

Unknown-1

Not much really to look at on the scatter, since the sample size is very small on the 20%+ declines


def rets(df, shift):
out = (df.shift(-shift) / df) - 1
return out

rets_10 = rets(vix["close"], 21).where(vix["pct"] <= -0.1).dropna()
vix_10 = vix["pct"][vix["pct"] <= -0.1].iloc[:-1]
rets_20 = rets(vix["close"], 21).where(vix["pct"] <= -0.2)

slope, intercept, r_val, p_val, std_err = sp.stats.linregress(vix_10, rets_10)
rets_10_pred = intercept + slope * vix_10

plt.figure(figsize=(16, 9))
plt.plot(vix_10, rets_10_pred, linestyle="-", label="Linreg")
plt.scatter(vix_10, rets_10, color="#333333", alpha=0.55, s=21, label="Vix declines >= 10%")
plt.scatter(vix["pct"], rets_20, color="crimson", alpha=0.89, s=42, label="Vix declines >= 20%")
plt.grid(alpha=0.21)
plt.title("VIx returns 21 days after large single day declines")
plt.ylabel("Vix % return 21 days later")
plt.xlabel("Vix single day decline pct (from close to close)")
plt.axhline(linestyle="--", linewidth=1, color="#333333")
plt.xticks(np.arange(-0.3, -0.09, 0.01))
plt.legend(loc="upper left")

Unknown-3

According to past instances, Vix should head south again after gathering itself


def getRets(df, days, pct, pct_to):
df = df.reset_index()
df_out = pd.DataFrame()
for index, row in df.iterrows():
if row["pct"] <= pct and row["pct"] > pct_to and df2["pct"].iloc[index-1] > pct:
ret = df2["close"].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_10 = getRets(vix, 55, -0.1, -0.2).mean(axis=1).cumsum()
vix_21rets_20 = getRets(vix, 55, -0.2, -1).mean(axis=1).cumsum()

plt.figure(figsize=(16, 9))
plt.plot(vix_21rets_10, color="#555555", label="Vix single day declines <= 10% and > 20%")
plt.plot(vix_21rets_20, color="crimson", label="Vix single day declines > 20%")
plt.grid(alpha=0.21)
plt.title("Vix returns after large single day declines")
plt.ylabel("Vix % return")
plt.xlabel("Days from large single day decline")
plt.axhline(linestyle="--", linewidth=1, color="#333333")
plt.xticks(np.arange(0, 56, 5))
plt.legend(loc="upper right")

Unknown-4

Thanks for your time

Vix Fed Rate Returns, Update

Almost a month has passed from the fed rate hike and just looking back at the post i did before the rate announcement, where i summarised Vix returns when rates were raised, lowered or left unchanged

History suggested Vix would rise from the announcement, and indeed it did. Now lets see if the cooloff suggested by data also holds true. Cooloff should start from day 30 to 40 from the hike
Unknown-1
Thanks your time and feel free to leave a comment

Vix And Fed Rate Decision Announcements

Since today is Fed day, i thought id take a look at how rate decisions have affected Vix. Vix data starts from early 90’s so we’ll have start from there.

import quandl
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime as dt
from pandas.tseries.offsets import *

sns.set(style="whitegrid")
%matplotlib inline

fed = pd.read_csv("fed_dates.csv", index_col="Date")
fed.index = pd.to_datetime(fed.index, format="%m/%d/%Y")
fed["Rate"] = fed["Rate"].apply(lambda x: x[:-1])

vix = quandl.get("YAHOO/INDEX_VIX", authtoken="YOUR_TOKEN_HERE")
vix["pct"] = np.log(vix["Close"]).diff()

Setting up the rate decision dates

fed_raised = fed[fed["Rate"] > fed["Rate"].shift(1)]
fed_lowered = fed[fed["Rate"] < fed["Rate"].shift(1)]
fed_unch = fed[fed["Rate"] == fed["Rate"].shift(1)]

Eyeballing all rate decisions since early 90’s along with Vix.
Clicking on the plot, summons you a bigger version.

sdate = vix.index[1] # When vix data starts

fig = plt.figure(figsize=(21, 11))

plt.plot(vix["Close"], linewidth=1, color="#555555", label="Vix (Log scale)")
plt.vlines(fed_raised.loc[sdate:].index, 8, 89, color="crimson", alpha=0.34, label="Rates raised")
plt.vlines(fed_lowered.loc[sdate:].index, 8, 89, color="forestgreen", alpha=0.34, label="Rates lowered")
plt.vlines(fed_unch.loc[sdate:].index, 8, 89, color="k", alpha=0.11, label="Rates unchanged")

plt.grid(alpha=0.21)
plt.title("Vix vs Fed funds rate decisions")
plt.margins(0.02)
plt.legend(loc="upper left", facecolor="w", framealpha=1, frameon=True)

Unknown-3.png

The sample size is small, nevertheless lets look at how Vix has behaved in rate increases, decreases and when rates have unchanged

def get_rets(dates):
days_before = 10+1
days_after = 64+1
out_instances = pd.DataFrame()

for index, row in dates.iterrows():
start_date = index - BDay(days_before)
end_date = index + BDay(days_after)

out = vix["pct"].loc[start_date: end_date]
out.reset_index(inplace=True, drop=True)
out = out.fillna(0)
out_instances[index] = out

out_instances.ix[-1] = 0 # Starting from 0 pct
out_instances.sort_index(inplace=True)
out_instances.reset_index(inplace=True)
out_instances.drop("index", axis=1, inplace=True)
return out_instances

inst_raised = get_rets(fed_raised.loc[sdate:])
inst_lowered = get_rets(fed_lowered.loc[sdate:])
inst_unch = get_rets(fed_unch.loc[sdate:])

fig = plt.figure(figsize=(21, 8))

plt.plot(inst_raised.mean(axis=1).cumsum(), color="crimson", label="Rates raised")
plt.plot(inst_lowered.mean(axis=1).cumsum(), color="forestgreen", label="Rates lowered")
plt.plot(inst_unch.mean(axis=1).cumsum(), color="#555555", label="Rates unchanged")
plt.axvline(13, color="crimson", linestyle="--", alpha=1, label="Fed rate announcement date")
plt.axhline(0, color="#555555", linestyle="--", alpha=0.34, label="_no_label_")
plt.title("Vix mean returns after Fed rate decisions")
plt.ylabel("Vix cumulative pct change")
plt.xlabel("Days...")
plt.grid(alpha=0.21)
plt.legend(loc="upper left", facecolor="w", framealpha=1, frameon=True)

Unknown-2

Thanks your time and feel free to leave a comment

AAII Sentiment At New Spx 21 Week Highs

Nothing quantitative here, just taking a look at how the AAII setiment has been when Spx is making new 21 week rolling highs. The recent AAII setiment has turned siginificantly negative even as Spx is plowing up and wanted to see when has that happened in the past.

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

%matplotlib inline
sns.set(style="whitegrid")

aaii = quandl.get("AAII/AAII_SENTIMENT", authtoken="YOUR_KEY")
aaii.rename(columns={"S&P 500 Weekly High":"weekly_close"}, inplace=True)
weekly_max = aaii["weekly_close"].rolling(21).max()

bull_mean = (aaii["Bullish"] - aaii["Bullish"].rolling(21).mean()) / aaii["Bullish"].rolling(21).std()
bear_mean = (aaii["Bearish"] - aaii["Bearish"].rolling(21).mean()) / aaii["Bearish"].rolling(21).std()

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(16, 10), sharex=True)

ax1.set_title("AAII sentiment at Spx rolling 21 week highs")
ax1.plot(aaii["weekly_close"], color="#555555", label="_no_label_")

ax1.fill_between(aaii.index, 0, aaii["weekly_close"],
where=((bull_mean>=0) & (weekly_max == aaii["weekly_close"])),
color="forestgreen", alpha=0.34, label="Bullish sentiment dominant")
ax1.fill_between(aaii.index, 0, aaii["weekly_close"],
where=((bear_mean>=0) & (weekly_max == aaii["weekly_close"])),
color="crimson", alpha=0.96, label="Bearish sentiment dominant")
ax1.grid(alpha=0.21)
ax1.set_yscale("log")
ax1.legend(loc="upper left")

ax2.set_title("Standardized AAII setiment")
ax2.plot(bull_mean, linewidth=1, color="darkseagreen", label="Bullish")
ax2.plot(bear_mean, linewidth=1, color="crimson", label="Bearish")
ax2.grid(alpha=0.21)
ax2.legend(loc="upper left")
plt.margins(0.02)
fig.subplots_adjust(hspace=0.089)

Unknown-13

Notebook:
https://github.com/Darellblg/voodoomarkets/blob/master/BlogAaiiSentimentAndSpxHighs.ipynb

Thanks your time and feel free to leave a comment