Featured image of post Investigating A VIX Trading Signal, Part 2: Finding A Signal

Investigating A VIX Trading Signal, Part 2: Finding A Signal

A brief look at finding a trading signal based on moving averages of the VIX.

Investigating A Signal

Continuing from where we left off in part 1, we will now consider the idea of a spike level in the VIX and how we might use a spike level to generate a signal. These elevated levels usually occur during market sell-off events or longer term drawdowns in the S&P 500. Sometimes the VIX reverts to recent levels after a spike, but other times levels remain elevated for weeks or even months.

Determining A Spike Level

We will start the 10 day simple moving average (SMA) of the daily high level to get an idea of what is happening recently with the VIX. We’ll then pick an arbitrary spike level (25% above the 10 day SMA), and our signal is generated if the VIX hits a level that is above the spike threshold.

The idea is that the 10 day SMA will smooth out the recent short term volatility in the VIX, and therefore any gradual increases in the VIX are not interpreted as spike events.

We also will generate the 20 and 50 day SMAs for reference, and again to see what is happening with the level of the VIX over slightly longer timeframes.

Here’s the code for the above:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Define the spike multiplier for detecting significant spikes
spike_level = 1.25

# =========================
# Simple Moving Averages (SMA)
# =========================

# Calculate 10-period SMA of 'High'
vix['High_SMA_10'] = vix['High'].rolling(window=10).mean()

# Shift the 10-period SMA by 1 to compare with current 'High'
vix['High_SMA_10_Shift'] = vix['High_SMA_10'].shift(1)

# Calculate the spike level based on shifted SMA and spike multiplier
vix['Spike_Level_SMA'] = vix['High_SMA_10_Shift'] * spike_level

# Calculate 20-period SMA of 'High'
vix['High_SMA_20'] = vix['High'].rolling(window=20).mean()

# Determine if 'High' exceeds the spike level (indicates a spike)
vix['Spike_SMA'] = vix['High'] >= vix['Spike_Level_SMA']

# Calculate 50-period SMA of 'High' for trend analysis
vix['High_SMA_50'] = vix['High'].rolling(window=50).mean()

# =========================
# Exponential Moving Averages (EMA)
# =========================

# Calculate 10-period EMA of 'High'
vix['High_EMA_10'] = vix['High'].ewm(span=10, adjust=False).mean()

# Shift the 10-period EMA by 1 to compare with current 'High'
vix['High_EMA_10_Shift'] = vix['High_EMA_10'].shift(1)

# Calculate the spike level based on shifted EMA and spike multiplier
vix['Spike_Level_EMA'] = vix['High_EMA_10_Shift'] * spike_level

# Calculate 20-period EMA of 'High'
vix['High_EMA_20'] = vix['High'].ewm(span=20, adjust=False).mean()

# Determine if 'High' exceeds the spike level (indicates a spike)
vix['Spike_EMA'] = vix['High'] >= vix['Spike_Level_EMA']

# Calculate 50-period EMA of 'High' for trend analysis
vix['High_EMA_50'] = vix['High'].ewm(span=50, adjust=False).mean()

For this exercise, we will use simple moving averages.

Spike Counts (Signals) By Year

To investigate the number of spike events (or signals) that we receive on a yearly basis, we can run the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Ensure the index is a DatetimeIndex
vix.index = pd.to_datetime(vix.index)

# Create a new column for the year extracted from the date index
vix['Year'] = vix.index.year

# Group by year and the "Spike_SMA" and "Spike_EMA" columns, then count occurrences
spike_count_SMA = vix.groupby(['Year', 'Spike_SMA']).size().unstack(fill_value=0)

display(spike_count_SMA)

Which gives us the following:

YearFalseTrue
19902485
19912494
19922504
19932512
19942439
19952520
19962486
19972476
19982439
19992502
20002484
20012408
20022484
20032511
20042502
20052502
20062429
200723912
200823815
20092493
201023913
201124012
20122482
20132493
201423517
201524012
201623418
20172447
201822823
201924111
202022429
202123517
202223912
20232464
202423715
20259612

And the plot to aid with visualization. Based on the plot, it seems as though volatility has increased since the early 2000’s:

Spike Counts

Spike Counts (Signals) Plots By Year

The most recent yearly plots are shown below for when signals are generated. The images for the previous years are linked below.

Spike/Signals, 1990
Spike/Signals, 1991
Spike/Signals, 1992
Spike/Signals, 1993
Spike/Signals, 1994
Spike/Signals, 1995
Spike/Signals, 1996
Spike/Signals, 1997
Spike/Signals, 1998
Spike/Signals, 1999
Spike/Signals, 2000
Spike/Signals, 2001
Spike/Signals, 2002
Spike/Signals, 2003
Spike/Signals, 2004
Spike/Signals, 2005
Spike/Signals, 2006
Spike/Signals, 2007
Spike/Signals, 2008
Spike/Signals, 2009
Spike/Signals, 2010
Spike/Signals, 2011
Spike/Signals, 2012
Spike/Signals, 2013
Spike/Signals, 2014
Spike/Signals, 2015
Spike/Signals, 2016
Spike/Signals, 2017
Spike/Signals, 2018
Spike/Signals, 2019

2020

Spike/Signals, 2020

2021

Spike/Signals, 2021

2022

Spike/Signals, 2022

2023

Spike/Signals, 2023

2024

Spike/Signals, 2024

2025

Spike/Signals, 2025

For comparison with the VVIX plot for 2025:

VVIX, 2025

Spike Counts (Signals) Plots By Decade

And here are the plots for the signals generated over the past 3 decades:

1990 - 1994

Spike/Signals, 1990 - 1994

1995 - 1999

Spike/Signals, 1995 - 1999

2000 - 2004

Spike/Signals, 2000 - 2004

2005 - 2009

Spike/Signals, 2005 - 2009

2010 - 2014

Spike/Signals, 2010 - 2014

2015 - 2019

Spike/Signals, 2015 - 2019

2020 - 2024

Spike/Signals, 2020 - 2024

2025 - Present

Spike/Signals, 2025 - Present

For comparison with the VVIX plot for 2025:

VVIX, 2025

References

  1. https://www.cboe.com/tradable_products/vix/
  2. https://github.com/ranaroussi/yfinance

Code

Note: The files below are identical to those linked in part 1.

The jupyter notebook with the functions and all other code is available here.
The html export of the jupyter notebook is available here.
The pdf export of the jupyter notebook is available here.

Built with Hugo
Theme Stack designed by Jimmy