Investigating A VIX Trading Signal¶

Python Imports¶

In [1]:
# Standard Library
import datetime
import io
import os
import random
import sys
import warnings

from datetime import datetime, timedelta
from pathlib import Path

# Data Handling
import numpy as np
import pandas as pd

# Data Visualization
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import seaborn as sns
from matplotlib.ticker import FormatStrFormatter, FuncFormatter, MultipleLocator

# Data Sources
import yfinance as yf

# Statistical Analysis
import statsmodels.api as sm

# Machine Learning
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Suppress warnings
warnings.filterwarnings("ignore")

Add Directories To Path¶

In [2]:
# Add the source subdirectory to the system path to allow import config from settings.py
current_directory = Path(os.getcwd())
website_base_directory = current_directory.parent.parent.parent
src_directory = website_base_directory / "src"
sys.path.append(str(src_directory)) if str(src_directory) not in sys.path else None

# Import settings.py
from settings import config

# Add configured directories from config to path
SOURCE_DIR = config("SOURCE_DIR")
sys.path.append(str(Path(SOURCE_DIR))) if str(Path(SOURCE_DIR)) not in sys.path else None

QUANT_FINANCE_RESEARCH_BASE_DIR = config("QUANT_FINANCE_RESEARCH_BASE_DIR")
sys.path.append(str(Path(QUANT_FINANCE_RESEARCH_BASE_DIR))) if str(Path(QUANT_FINANCE_RESEARCH_BASE_DIR)) not in sys.path else None

QUANT_FINANCE_RESEARCH_SOURCE_DIR = config("QUANT_FINANCE_RESEARCH_SOURCE_DIR")
sys.path.append(str(Path(QUANT_FINANCE_RESEARCH_SOURCE_DIR))) if str(Path(QUANT_FINANCE_RESEARCH_SOURCE_DIR)) not in sys.path else None

# Add other configured directories
BASE_DIR = config("BASE_DIR")
CONTENT_DIR = config("CONTENT_DIR")
POSTS_DIR = config("POSTS_DIR")
PAGES_DIR = config("PAGES_DIR")
PUBLIC_DIR = config("PUBLIC_DIR")
SOURCE_DIR = config("SOURCE_DIR")
DATA_DIR = config("DATA_DIR")
DATA_MANUAL_DIR = config("DATA_MANUAL_DIR")

# Print system path
for i, path in enumerate(sys.path):
    print(f"{i}: {path}")
0: /usr/lib/python313.zip
1: /usr/lib/python3.13
2: /usr/lib/python3.13/lib-dynload
3: 
4: /home/jared/python-virtual-envs/general_313/lib/python3.13/site-packages
5: /home/jared/Cloud_Storage/Dropbox/Websites/jaredszajkowski.github.io/src
6: /home/jared/Cloud_Storage/Dropbox/Quant_Finance_Research
7: /home/jared/Cloud_Storage/Dropbox/Quant_Finance_Research/src

Track Index Dependencies¶

In [3]:
# Create file to track markdown dependencies
dep_file = Path("index_dep.txt")
dep_file.write_text("")
Out[3]:
0

Python Functions¶

In [4]:
from calc_vix_trade_pnl import calc_vix_trade_pnl
from df_info import df_info
from df_info_markdown import df_info_markdown
from export_track_md_deps import export_track_md_deps
from load_data import load_data
from pandas_set_decimal_places import pandas_set_decimal_places
from plot_price import plot_price
from plot_stats import plot_stats
from plot_vix_with_trades import plot_vix_with_trades
from yf_pull_data import yf_pull_data

Data Overview (VIX)¶

Acquire CBOE Volatility Index (VIX) Data¶

In [5]:
yf_pull_data(
    base_directory=DATA_DIR,
    ticker="^VIX",
    source="Yahoo_Finance", 
    asset_class="Indices", 
    excel_export=True,
    pickle_export=True,
    output_confirmation=True,
)
YF.download() has changed argument auto_adjust default to True
[*********************100%***********************]  1 of 1 completed

The first and last date of data for ^VIX is: 
Close High Low Open Volume
Date
1990-01-02 17.24 17.24 17.24 17.24 0
Close High Low Open Volume
Date
2025-06-09 17.16 17.719999 16.82 17.690001 0
Yahoo Finance data complete for ^VIX
--------------------
Out[5]:
Close High Low Open Volume
Date
1990-01-02 17.240000 17.240000 17.240000 17.240000 0
1990-01-03 18.190001 18.190001 18.190001 18.190001 0
1990-01-04 19.219999 19.219999 19.219999 19.219999 0
1990-01-05 20.110001 20.110001 20.110001 20.110001 0
1990-01-08 20.260000 20.260000 20.260000 20.260000 0
... ... ... ... ... ...
2025-06-03 17.690001 19.209999 17.639999 18.830000 0
2025-06-04 17.610001 18.070000 17.410000 17.680000 0
2025-06-05 18.480000 18.799999 17.080000 17.680000 0
2025-06-06 16.770000 18.350000 16.650000 18.160000 0
2025-06-09 17.160000 17.719999 16.820000 17.690001 0

8925 rows × 5 columns

Load Data - VIX¶

In [6]:
# Set decimal places
pandas_set_decimal_places(2)

# VIX
vix = load_data(
    base_directory=DATA_DIR,
    ticker="^VIX",
    source="Yahoo_Finance", 
    asset_class="Indices",
    timeframe="Daily",
)

# Set 'Date' column as datetime
vix['Date'] = pd.to_datetime(vix['Date'])

# Drop 'Volume'
vix.drop(columns = {'Volume'}, inplace = True)

# Set Date as index
vix.set_index('Date', inplace = True)

# Check to see if there are any NaN values
vix[vix['High'].isna()]

# Forward fill to clean up missing data
vix['High'] = vix['High'].ffill()

DataFrame Info - VIX¶

In [7]:
df_info(vix)
The columns, shape, and data types are:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 8925 entries, 1990-01-02 to 2025-06-09
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Close   8925 non-null   float64
 1   High    8925 non-null   float64
 2   Low     8925 non-null   float64
 3   Open    8925 non-null   float64
dtypes: float64(4)
memory usage: 348.6 KB
None
The first 5 rows are:
Close High Low Open
Date
1990-01-02 17.24 17.24 17.24 17.24
1990-01-03 18.19 18.19 18.19 18.19
1990-01-04 19.22 19.22 19.22 19.22
1990-01-05 20.11 20.11 20.11 20.11
1990-01-08 20.26 20.26 20.26 20.26
The last 5 rows are:
Close High Low Open
Date
2025-06-03 17.69 19.21 17.64 18.83
2025-06-04 17.61 18.07 17.41 17.68
2025-06-05 18.48 18.80 17.08 17.68
2025-06-06 16.77 18.35 16.65 18.16
2025-06-09 17.16 17.72 16.82 17.69
In [8]:
# Copy this <!-- INSERT_01_VIX_DF_Info_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_VIX_DF_Info.md", content=df_info_markdown(vix))
✅ Exported and tracked: 01_VIX_DF_Info.md

Statistics - VIX¶

In [9]:
vix_stats = vix.describe()
num_std = [-1, 0, 1, 2, 3, 4, 5]
for num in num_std:
    vix_stats.loc[f"mean + {num} std"] = {
        'Open': vix_stats.loc['mean']['Open'] + num * vix_stats.loc['std']['Open'],
        'High': vix_stats.loc['mean']['High'] + num * vix_stats.loc['std']['High'],
        'Low': vix_stats.loc['mean']['Low'] + num * vix_stats.loc['std']['Low'],
        'Close': vix_stats.loc['mean']['Close'] + num * vix_stats.loc['std']['Close'],
    }
display(vix_stats)
Close High Low Open
count 8925.00 8925.00 8925.00 8925.00
mean 19.49 20.41 18.82 19.59
std 7.84 8.39 7.39 7.91
min 9.14 9.31 8.56 9.01
25% 13.88 14.53 13.41 13.93
50% 17.66 18.39 17.08 17.70
75% 22.84 23.84 22.15 22.98
max 82.69 89.53 72.76 82.69
mean + -1 std 11.66 12.01 11.43 11.68
mean + 0 std 19.49 20.41 18.82 19.59
mean + 1 std 27.33 28.80 26.21 27.50
mean + 2 std 35.16 37.19 33.60 35.41
mean + 3 std 43.00 45.59 40.99 43.32
mean + 4 std 50.84 53.98 48.38 51.23
mean + 5 std 58.67 62.38 55.77 59.14
In [10]:
# Copy this <!-- INSERT_01_VIX_Stats_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_VIX_Stats.md", content=vix_stats.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 01_VIX_Stats.md
In [11]:
# Group by year and calculate mean and std for OHLC
vix_stats_by_year = vix.groupby(vix.index.year)[["Open", "High", "Low", "Close"]].agg(["mean", "std" ,"min", "max"])

# Flatten the column MultiIndex
vix_stats_by_year.columns = ['_'.join(col).strip() for col in vix_stats_by_year.columns.values]
vix_stats_by_year.index.name = "Year"

display(vix_stats_by_year)
Open_mean Open_std Open_min Open_max High_mean High_std High_min High_max Low_mean Low_std Low_min Low_max Close_mean Close_std Close_min Close_max
Year
1990 23.06 4.74 14.72 36.47 23.06 4.74 14.72 36.47 23.06 4.74 14.72 36.47 23.06 4.74 14.72 36.47
1991 18.38 3.68 13.95 36.20 18.38 3.68 13.95 36.20 18.38 3.68 13.95 36.20 18.38 3.68 13.95 36.20
1992 15.23 2.26 10.29 20.67 16.03 2.19 11.90 25.13 14.85 2.14 10.29 19.67 15.45 2.12 11.51 21.02
1993 12.70 1.37 9.18 16.20 13.34 1.40 9.55 18.31 12.25 1.28 8.89 15.77 12.69 1.33 9.31 17.30
1994 13.79 2.06 9.86 23.61 14.58 2.28 10.31 28.30 13.38 1.99 9.59 23.61 13.93 2.07 9.94 23.87
1995 12.27 1.03 10.29 15.79 12.93 1.07 10.95 16.99 11.96 0.98 10.06 14.97 12.39 0.97 10.36 15.74
1996 16.31 1.92 11.24 23.90 16.99 2.12 12.29 27.05 15.94 1.82 11.11 21.43 16.44 1.94 12.00 21.99
1997 22.43 4.33 16.67 45.69 23.11 4.56 18.02 48.64 21.85 3.98 16.36 36.43 22.38 4.14 17.09 38.20
1998 25.68 6.96 16.42 47.95 26.61 7.36 16.50 49.53 24.89 6.58 16.10 45.58 25.60 6.86 16.23 45.74
1999 24.39 2.90 18.05 32.62 25.20 3.01 18.48 33.66 23.75 2.76 17.07 31.13 24.37 2.88 17.42 32.98
2000 23.41 3.43 16.81 33.70 24.10 3.66 17.06 34.31 22.75 3.19 16.28 30.56 23.32 3.41 16.53 33.49
2001 26.04 4.98 19.21 48.93 26.64 5.19 19.37 49.35 25.22 4.61 18.74 42.66 25.75 4.78 18.76 43.74
2002 27.53 7.03 17.23 48.17 28.28 7.25 17.51 48.46 26.60 6.64 17.02 42.05 27.29 6.91 17.40 45.08
2003 22.21 5.31 15.59 35.21 22.61 5.35 16.19 35.66 21.64 5.18 14.66 33.99 21.98 5.24 15.58 34.69
2004 15.59 1.93 11.41 21.06 16.05 2.02 11.64 22.67 15.05 1.79 11.14 20.61 15.48 1.92 11.23 21.58
2005 12.84 1.44 10.23 18.33 13.28 1.59 10.48 18.59 12.39 1.32 9.88 16.41 12.81 1.47 10.23 17.74
2006 12.90 2.18 9.68 23.45 13.33 2.46 10.06 23.81 12.38 1.96 9.39 21.45 12.81 2.25 9.90 23.81
2007 17.59 5.36 9.99 32.68 18.44 5.76 10.26 37.50 16.75 4.95 9.70 30.44 17.54 5.36 9.89 31.09
2008 32.83 16.41 16.30 80.74 34.57 17.83 17.84 89.53 30.96 14.96 15.82 72.76 32.69 16.38 16.30 80.86
2009 31.75 9.20 19.54 52.65 32.78 9.61 19.67 57.36 30.50 8.63 19.25 49.27 31.48 9.08 19.47 56.65
2010 22.73 5.29 15.44 47.66 23.69 5.82 16.00 48.20 21.69 4.61 15.23 40.30 22.55 5.27 15.45 45.79
2011 24.27 8.17 14.31 46.18 25.40 8.78 14.99 48.00 23.15 7.59 14.27 41.51 24.20 8.14 14.62 48.00
2012 17.93 2.60 13.68 26.35 18.59 2.72 14.08 27.73 17.21 2.37 13.30 25.72 17.80 2.54 13.45 26.66
2013 14.29 1.67 11.52 20.87 14.82 1.88 11.75 21.91 13.80 1.51 11.05 19.04 14.23 1.74 11.30 20.49
2014 14.23 2.65 10.40 29.26 14.95 3.02 10.76 31.06 13.61 2.21 10.28 24.64 14.17 2.62 10.32 25.27
2015 16.71 3.99 11.77 31.91 17.79 5.03 12.22 53.29 15.85 3.65 10.88 29.91 16.67 4.34 11.95 40.74
2016 16.01 4.05 11.32 29.01 16.85 4.40 11.49 32.09 15.16 3.66 10.93 26.67 15.83 3.97 11.27 28.14
2017 11.14 1.34 9.23 16.19 11.72 1.54 9.52 17.28 10.64 1.16 8.56 14.97 11.09 1.36 9.14 16.04
2018 16.63 5.01 9.01 37.32 18.03 6.12 9.31 50.30 15.53 4.25 8.92 29.66 16.64 5.09 9.15 37.32
2019 15.57 2.74 11.55 27.54 16.41 3.06 11.79 28.53 14.76 2.38 11.03 24.05 15.39 2.61 11.54 25.45
2020 29.54 12.45 12.20 82.69 31.46 13.89 12.42 85.47 27.51 10.85 11.75 70.37 29.25 12.34 12.10 82.69
2021 19.83 3.47 15.02 35.16 21.12 4.22 15.54 37.51 18.65 2.93 14.10 29.24 19.66 3.62 15.01 37.21
2022 25.98 4.30 16.57 37.50 27.25 4.59 17.81 38.94 24.69 3.91 16.34 33.11 25.62 4.22 16.60 36.45
2023 17.12 3.17 11.96 27.77 17.83 3.58 12.46 30.81 16.36 2.89 11.81 24.00 16.87 3.14 12.07 26.52
2024 15.69 3.14 11.53 33.71 16.65 4.73 12.23 65.73 14.92 2.58 10.62 24.02 15.61 3.36 11.86 38.57
2025 21.84 7.25 14.89 60.13 23.53 8.85 15.16 60.13 20.30 5.34 14.58 38.58 21.52 6.87 14.77 52.33
In [12]:
# Copy this <!-- INSERT_01_VIX_Stats_By_Year_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_VIX_Stats_By_Year.md", content=vix_stats_by_year.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 01_VIX_Stats_By_Year.md
In [13]:
# Group by month and calculate mean and std for OHLC
vix_stats_by_month = vix.groupby(vix.index.month)[["Open", "High", "Low", "Close"]].agg(["mean", "std", "min", "max"])

# Flatten the column MultiIndex
vix_stats_by_month.columns = ['_'.join(col).strip() for col in vix_stats_by_month.columns.values]
vix_stats_by_month.index.name = "Month"

display(vix_stats_by_month)
Open_mean Open_std Open_min Open_max High_mean High_std High_min High_max Low_mean Low_std Low_min Low_max Close_mean Close_std Close_min Close_max
Month
1 19.34 7.21 9.01 51.52 20.13 7.58 9.31 57.36 18.60 6.87 8.92 49.27 19.22 7.17 9.15 56.65
2 19.67 7.22 10.19 52.50 20.51 7.65 10.26 53.16 18.90 6.81 9.70 48.97 19.58 7.13 10.02 52.62
3 20.47 9.63 10.59 82.69 21.39 10.49 11.24 85.47 19.54 8.65 10.53 70.37 20.35 9.56 10.74 82.69
4 19.43 7.48 10.39 60.13 20.24 7.93 10.89 60.59 18.65 6.88 10.22 52.76 19.29 7.28 10.36 57.06
5 18.60 6.04 9.75 47.66 19.40 6.43 10.14 48.20 17.89 5.63 9.56 40.30 18.51 5.96 9.77 45.79
6 18.45 5.80 9.79 44.09 19.15 6.07 10.28 44.44 17.73 5.44 9.37 34.97 18.34 5.72 9.75 40.79
7 17.87 5.75 9.18 48.17 18.58 5.98 9.52 48.46 17.24 5.48 8.84 42.05 17.80 5.67 9.36 44.92
8 19.17 6.74 10.04 45.34 20.12 7.45 10.32 65.73 18.44 6.38 9.52 41.77 19.18 6.87 9.93 48.00
9 20.51 8.32 9.59 48.93 21.35 8.64 9.83 49.35 19.74 7.90 9.36 43.74 20.43 8.20 9.51 46.72
10 21.83 10.28 9.23 79.13 22.83 11.10 9.62 89.53 20.93 9.51 9.11 67.80 21.75 10.24 9.19 80.06
11 20.34 9.65 9.31 80.74 21.04 10.03 9.74 81.48 19.55 9.02 8.56 72.76 20.16 9.52 9.14 80.86
12 19.34 8.26 9.36 66.68 20.09 8.53 9.55 68.60 18.63 7.88 8.89 62.31 19.29 8.16 9.31 68.51
In [14]:
# Copy this <!-- INSERT_01_VIX_Stats_By_Month_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_VIX_Stats_By_Month.md", content=vix_stats_by_month.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 01_VIX_Stats_By_Month.md

Deciles - VIX¶

In [15]:
vix_deciles = vix.quantile(np.arange(0, 1.1, 0.1))
display(vix_deciles)
Close High Low Open
0.00 9.14 9.31 8.56 9.01
0.10 12.12 12.63 11.72 12.13
0.20 13.26 13.88 12.85 13.31
0.30 14.61 15.29 14.08 14.68
0.40 16.10 16.76 15.56 16.13
0.50 17.66 18.39 17.08 17.70
0.60 19.55 20.39 19.00 19.69
0.70 21.64 22.65 20.99 21.79
0.80 24.31 25.36 23.50 24.38
0.90 28.70 30.00 27.78 28.86
1.00 82.69 89.53 72.76 82.69
In [16]:
# Copy this <!-- INSERT_01_VIX_Deciles_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_VIX_Deciles.md", content=vix_deciles.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 01_VIX_Deciles.md

Plots - VIX¶

Histogram Distribution - VIX¶

In [17]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

# Histogram
plt.hist([vix['High']], label=['High'], bins=200, edgecolor='black', color='steelblue')

# Plot a vertical line at the mean, mean + 1 std, and mean + 2 std
plt.axvline(vix_stats.loc['mean + -1 std']['High'], color='brown', linestyle='dashed', linewidth=1, label=f'High Mean - 1 std: {vix_stats.loc['mean + -1 std']['High']:.2f}')
plt.axvline(vix_stats.loc['mean']['High'], color='red', linestyle='dashed', linewidth=1, label=f'High Mean: {vix_stats.loc['mean']['High']:.2f}')
plt.axvline(vix_stats.loc['mean + 1 std']['High'], color='green', linestyle='dashed', linewidth=1, label=f'High Mean + 1 std: {vix_stats.loc['mean + 1 std']['High']:.2f}')
plt.axvline(vix_stats.loc['mean + 2 std']['High'], color='orange', linestyle='dashed', linewidth=1, label=f'High Mean + 2 std: {vix_stats.loc['mean + 2 std']['High']:.2f}')
plt.axvline(vix_stats.loc['mean + 3 std']['High'], color='black', linestyle='dashed', linewidth=1, label=f'High Mean + 3 std: {vix_stats.loc['mean + 3 std']['High']:.2f}')
plt.axvline(vix_stats.loc['mean + 4 std']['High'], color='yellow', linestyle='dashed', linewidth=1, label=f'High Mean + 4 std: {vix_stats.loc['mean + 4 std']['High']:.2f}')

# Set X axis
x_tick_spacing = 5  # Specify the interval for y-axis ticks
plt.gca().xaxis.set_major_locator(MultipleLocator(x_tick_spacing))
plt.xlabel("VIX", fontsize=10)
plt.xticks(rotation=0, fontsize=8)

# Set Y axis
y_tick_spacing = 25  # Specify the interval for y-axis ticks
plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
plt.ylabel("# Of Datapoints", fontsize=10)
plt.yticks(fontsize=8)

# Set title, layout, grid, and legend
plt.title("CBOE Volatility Index (VIX) Histogram (200 Bins)", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)

# Save figure and display plot
plt.savefig("01_Histogram+Mean+SD.png", dpi=300, bbox_inches="tight")
plt.show()
No description has been provided for this image

Historical Data - VIX¶

In [18]:
plot_price(
    price_df=vix,
    plot_start_date=None,
    plot_end_date="2009-12-31",
    plot_columns=["High", "Low"],
    title="CBOE Volatility Index (VIX), 1990 - 2009",
    x_label="Date",
    x_format="Year",
    y_label="VIX",
    y_format="Decimal",
    y_tick_spacing=5,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="01_VIX_Plot_1990-2009",
)
No description has been provided for this image
In [19]:
plot_price(
    price_df=vix,
    plot_start_date="2010-01-01",
    plot_end_date=None,
    plot_columns=["High", "Low"],
    title="CBOE Volatility Index (VIX), 2010 - Present",
    x_label="Date",
    x_format="Year",
    y_label="VIX",
    y_format="Decimal",
    y_tick_spacing=5,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="01_VIX_Plot_2010-Present",
)
No description has been provided for this image

Stats By Year - VIX¶

In [20]:
plot_stats(
    stats_df=vix_stats_by_year,
    plot_columns=["Open_mean", "High_mean", "Low_mean", "Close_mean"],
    title="VIX Mean OHLC By Year",
    x_label="Year",
    x_rotation=45,
    x_tick_spacing=1,
    y_label="Price",
    y_tick_spacing=1,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="01_VIX_Stats_By_Year"
)
No description has been provided for this image

Stats By Month - VIX¶

In [21]:
plot_stats(
    stats_df=vix_stats_by_month,
    plot_columns=["Open_mean", "High_mean", "Low_mean", "Close_mean"],
    title="VIX Mean OHLC By Month",
    x_label="Month",
    x_rotation=0,
    x_tick_spacing=1,
    y_label="Price",
    y_tick_spacing=1,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="01_VIX_Stats_By_Month"
)
No description has been provided for this image

Data Overview (VVIX)¶

Acquire CBOE VVIX Data¶

In [22]:
yf_pull_data(
    base_directory=DATA_DIR,
    ticker="^VVIX",
    source="Yahoo_Finance", 
    asset_class="Indices", 
    excel_export=True,
    pickle_export=True,
    output_confirmation=True,
)
[*********************100%***********************]  1 of 1 completed

The first and last date of data for ^VVIX is: 
Close High Low Open Volume
Date
2007-01-03 87.63 87.63 87.63 87.63 0
Close High Low Open Volume
Date
2025-06-09 88.97 91.84 88.59 91.58 0
Yahoo Finance data complete for ^VVIX
--------------------
Out[22]:
Close High Low Open Volume
Date
2007-01-03 87.63 87.63 87.63 87.63 0
2007-01-04 88.19 88.19 88.19 88.19 0
2007-01-05 90.17 90.17 90.17 90.17 0
2007-01-08 92.04 92.04 92.04 92.04 0
2007-01-09 92.76 92.76 92.76 92.76 0
... ... ... ... ... ...
2025-06-03 90.89 94.38 90.06 91.58 0
2025-06-04 90.79 94.56 90.42 91.16 0
2025-06-05 93.20 94.53 88.92 90.26 0
2025-06-06 89.45 91.20 88.32 91.20 0
2025-06-09 88.97 91.84 88.59 91.58 0

4629 rows × 5 columns

Load Data - VVIX¶

In [23]:
# Set decimal places
pandas_set_decimal_places(2)

# VVIX
vvix = load_data(
    base_directory=DATA_DIR,
    ticker="^VVIX",
    source="Yahoo_Finance", 
    asset_class="Indices",
    timeframe="Daily",
)

# Set 'Date' column as datetime
vvix['Date'] = pd.to_datetime(vvix['Date'])

# Drop 'Volume'
vvix.drop(columns = {'Volume'}, inplace = True)

# Set Date as index
vvix.set_index('Date', inplace = True)

# Check to see if there are any NaN values
vvix[vvix['High'].isna()]

# Forward fill to clean up missing data
vvix['High'] = vvix['High'].ffill()

DataFrame Info - VVIX¶

In [24]:
df_info(vvix)
The columns, shape, and data types are:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4629 entries, 2007-01-03 to 2025-06-09
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Close   4629 non-null   float64
 1   High    4629 non-null   float64
 2   Low     4629 non-null   float64
 3   Open    4629 non-null   float64
dtypes: float64(4)
memory usage: 180.8 KB
None
The first 5 rows are:
Close High Low Open
Date
2007-01-03 87.63 87.63 87.63 87.63
2007-01-04 88.19 88.19 88.19 88.19
2007-01-05 90.17 90.17 90.17 90.17
2007-01-08 92.04 92.04 92.04 92.04
2007-01-09 92.76 92.76 92.76 92.76
The last 5 rows are:
Close High Low Open
Date
2025-06-03 90.89 94.38 90.06 91.58
2025-06-04 90.79 94.56 90.42 91.16
2025-06-05 93.20 94.53 88.92 90.26
2025-06-06 89.45 91.20 88.32 91.20
2025-06-09 88.97 91.84 88.59 91.58
In [25]:
# Copy this <!-- INSERT_02_VVIX_DF_Info_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VVIX_DF_Info.md", content=df_info_markdown(vix))
✅ Exported and tracked: 02_VVIX_DF_Info.md

Statistics - VVIX¶

In [26]:
vvix_stats = vvix.describe()
num_std = [-1, 0, 1, 2, 3, 4, 5]
for num in num_std:
    vvix_stats.loc[f"mean + {num} std"] = {
        'Open': vvix_stats.loc['mean']['Open'] + num * vvix_stats.loc['std']['Open'],
        'High': vvix_stats.loc['mean']['High'] + num * vvix_stats.loc['std']['High'],
        'Low': vvix_stats.loc['mean']['Low'] + num * vvix_stats.loc['std']['Low'],
        'Close': vvix_stats.loc['mean']['Close'] + num * vvix_stats.loc['std']['Close'],
    }
display(vvix_stats)
Close High Low Open
count 4629.00 4629.00 4629.00 4629.00
mean 93.50 95.55 91.94 93.75
std 16.44 18.04 15.09 16.49
min 59.74 59.74 59.31 59.31
25% 82.34 83.46 81.48 82.55
50% 90.54 92.26 89.37 90.86
75% 102.20 105.01 99.93 102.55
max 207.59 212.22 187.27 212.22
mean + -1 std 77.06 77.50 76.85 77.26
mean + 0 std 93.50 95.55 91.94 93.75
mean + 1 std 109.94 113.59 107.04 110.25
mean + 2 std 126.38 131.64 122.13 126.74
mean + 3 std 142.82 149.68 137.22 143.23
mean + 4 std 159.26 167.73 152.32 159.72
mean + 5 std 175.69 185.77 167.41 176.21
In [27]:
# Copy this <!-- INSERT_02_VVIX_Stats_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VVIX_Stats.md", content=vvix_stats.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 02_VVIX_Stats.md
In [28]:
# Group by year and calculate mean and std for OHLC
vvix_stats_by_year = vvix.groupby(vvix.index.year)[["Open", "High", "Low", "Close"]].agg(["mean", "std", "min", "max"])

# Flatten the column MultiIndex
vvix_stats_by_year.columns = ['_'.join(col).strip() for col in vvix_stats_by_year.columns.values]
vvix_stats_by_year.index.name = "Year"

display(vvix_stats_by_year)
Open_mean Open_std Open_min Open_max High_mean High_std High_min High_max Low_mean Low_std Low_min Low_max Close_mean Close_std Close_min Close_max
Year
2007 87.68 13.31 63.52 142.99 87.68 13.31 63.52 142.99 87.68 13.31 63.52 142.99 87.68 13.31 63.52 142.99
2008 81.85 15.60 59.74 134.87 81.85 15.60 59.74 134.87 81.85 15.60 59.74 134.87 81.85 15.60 59.74 134.87
2009 79.78 8.63 64.95 104.02 79.78 8.63 64.95 104.02 79.78 8.63 64.95 104.02 79.78 8.63 64.95 104.02
2010 88.36 13.07 64.87 145.12 88.36 13.07 64.87 145.12 88.36 13.07 64.87 145.12 88.36 13.07 64.87 145.12
2011 92.94 10.21 75.94 134.63 92.94 10.21 75.94 134.63 92.94 10.21 75.94 134.63 92.94 10.21 75.94 134.63
2012 94.84 8.38 78.42 117.44 94.84 8.38 78.42 117.44 94.84 8.38 78.42 117.44 94.84 8.38 78.42 117.44
2013 80.52 8.97 62.71 111.43 80.52 8.97 62.71 111.43 80.52 8.97 62.71 111.43 80.52 8.97 62.71 111.43
2014 83.01 14.33 61.76 138.60 83.01 14.33 61.76 138.60 83.01 14.33 61.76 138.60 83.01 14.33 61.76 138.60
2015 95.44 15.59 73.07 212.22 98.47 16.39 76.41 212.22 92.15 13.35 72.20 148.68 94.82 14.75 73.18 168.75
2016 93.36 10.02 77.96 131.95 95.82 10.86 78.86 132.42 90.54 8.99 76.17 115.15 92.80 10.07 76.17 125.13
2017 90.50 8.65 75.09 134.98 92.94 9.64 77.34 135.32 87.85 7.78 71.75 117.29 90.01 8.80 75.64 135.32
2018 102.60 13.22 83.70 176.72 106.27 16.26 85.00 203.73 99.17 11.31 82.60 165.35 102.26 14.04 83.21 180.61
2019 91.28 8.43 75.58 112.75 93.61 8.98 75.95 117.63 88.90 7.86 74.36 111.48 91.03 8.36 74.98 114.40
2020 118.64 19.32 88.39 203.03 121.91 20.88 88.54 209.76 115.05 17.37 85.31 187.27 118.36 19.39 86.87 207.59
2021 115.51 9.37 96.09 151.35 119.29 11.70 98.36 168.78 111.99 8.14 95.92 144.19 115.32 10.20 97.09 157.69
2022 102.58 18.01 76.48 161.09 105.32 19.16 77.93 172.82 99.17 16.81 76.13 153.26 101.81 17.81 77.05 154.38
2023 90.95 8.64 74.43 127.73 93.72 9.98 75.31 137.65 88.01 7.37 72.27 119.64 90.34 8.38 73.88 124.75
2024 92.88 15.06 59.31 169.68 97.32 18.33 74.79 192.49 89.51 13.16 59.31 137.05 92.81 15.60 73.26 173.32
2025 106.30 15.89 83.19 186.33 111.30 18.68 85.82 189.03 101.64 12.37 81.73 146.51 105.26 15.14 81.89 170.92
In [29]:
# Copy this <!-- INSERT_02_VVIX_Stats_By_Year_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VVIX_Stats_By_Year.md", content=vvix_stats_by_year.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 02_VVIX_Stats_By_Year.md
In [30]:
# Group by month and calculate mean and std for OHLC
vvix_stats_by_month = vvix.groupby(vvix.index.month)[["Open", "High", "Low", "Close"]].agg(["mean", "std", "min", "max"])

# Flatten the column MultiIndex
vvix_stats_by_month.columns = ['_'.join(col).strip() for col in vvix_stats_by_month.columns.values]
vvix_stats_by_month.index.name = "Year"

display(vvix_stats_by_month)
Open_mean Open_std Open_min Open_max High_mean High_std High_min High_max Low_mean Low_std Low_min Low_max Close_mean Close_std Close_min Close_max
Year
1 92.46 15.63 64.87 161.09 94.37 17.63 64.87 172.82 90.69 14.23 64.87 153.26 92.23 15.78 64.87 157.69
2 93.49 18.24 65.47 176.72 95.39 20.70 65.47 203.73 91.39 16.43 65.47 165.35 93.13 18.58 65.47 180.61
3 95.30 21.66 66.97 203.03 97.38 23.56 66.97 209.76 92.94 19.51 66.97 187.27 94.89 21.59 66.97 207.59
4 92.18 19.03 59.74 186.33 94.01 20.57 59.74 189.03 90.30 17.21 59.74 152.01 91.88 18.60 59.74 170.92
5 92.25 16.93 61.76 145.18 93.95 17.99 61.76 151.50 90.54 16.14 61.76 145.12 91.79 16.79 61.76 146.28
6 92.91 14.96 63.52 155.48 94.44 16.21 63.52 172.21 91.30 13.92 63.52 140.15 92.72 14.93 63.52 151.60
7 89.97 13.16 67.21 138.42 91.46 14.23 67.21 149.60 88.48 12.26 67.21 133.82 89.84 13.12 67.21 139.54
8 96.83 16.94 68.05 212.22 98.89 18.72 68.05 212.22 94.68 14.86 68.05 148.68 96.61 16.63 68.05 173.32
9 94.71 14.03 67.94 135.17 96.50 15.52 67.94 146.31 92.86 12.50 67.94 128.46 94.40 13.78 67.94 138.93
10 97.74 14.01 64.97 149.60 99.43 15.11 64.97 154.99 96.14 13.35 64.97 144.55 97.52 14.15 64.97 152.01
11 93.53 14.17 63.77 142.68 95.07 15.36 63.77 161.76 91.98 13.39 63.77 140.44 93.28 14.24 63.77 149.74
12 93.35 15.03 59.31 151.35 95.33 16.63 62.71 168.37 91.78 13.70 59.31 144.19 93.46 15.07 62.71 156.10
In [31]:
# Copy this <!-- INSERT_02_VVIX_Stats_By_Month_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VVIX_Stats_By_Month.md", content=vvix_stats_by_month.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 02_VVIX_Stats_By_Month.md

Deciles - VVIX¶

In [32]:
vvix_deciles = vvix.quantile(np.arange(0, 1.1, 0.1))
display(vvix_deciles)
Close High Low Open
0.00 59.74 59.74 59.31 59.31
0.10 75.83 76.02 75.44 75.80
0.20 80.58 81.43 79.82 80.75
0.30 83.90 85.22 83.01 84.17
0.40 87.08 88.57 85.98 87.46
0.50 90.54 92.26 89.37 90.86
0.60 94.21 96.14 93.03 94.51
0.70 99.11 101.53 97.43 99.40
0.80 106.06 109.40 103.90 106.48
0.90 115.27 118.80 112.48 115.53
1.00 207.59 212.22 187.27 212.22
In [33]:
# Copy this <!-- INSERT_02_VVIX_Deciles_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VVIX_Deciles.md", content=vvix_deciles.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 02_VVIX_Deciles.md

Plots - VVIX¶

Histogram Distribution - VVIX¶

In [34]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

# Histogram
plt.hist([vvix['High']], label=['High'], bins=200, edgecolor='black', color='steelblue')

# Plot a vertical line at the mean, mean + 1 std, and mean + 2 std
plt.axvline(vvix_stats.loc['mean + -1 std']['High'], color='brown', linestyle='dashed', linewidth=1, label=f'Mean - 1 std: {vvix_stats.loc['mean + -1 std']['High']:.2f}')
plt.axvline(vvix_stats.loc['mean']['High'], color='red', linestyle='dashed', linewidth=1, label=f'Mean: {vvix_stats.loc['mean']['High']:.2f}')
plt.axvline(vvix_stats.loc['mean + 1 std']['High'], color='green', linestyle='dashed', linewidth=1, label=f'Mean + 1 std: {vvix_stats.loc['mean + 1 std']['High']:.2f}')
plt.axvline(vvix_stats.loc['mean + 2 std']['High'], color='orange', linestyle='dashed', linewidth=1, label=f'Mean + 2 std: {vvix_stats.loc['mean + 2 std']['High']:.2f}')
plt.axvline(vvix_stats.loc['mean + 3 std']['High'], color='black', linestyle='dashed', linewidth=1, label=f'Mean + 3 std: {vvix_stats.loc['mean + 3 std']['High']:.2f}')
plt.axvline(vvix_stats.loc['mean + 4 std']['High'], color='yellow', linestyle='dashed', linewidth=1, label=f'Mean + 4 std: {vvix_stats.loc['mean + 4 std']['High']:.2f}')

# Set X axis
x_tick_spacing = 5  # Specify the interval for y-axis ticks
plt.gca().xaxis.set_major_locator(MultipleLocator(x_tick_spacing))
plt.xlabel("VVIX", fontsize=10)
plt.xticks(rotation=0, fontsize=8)

# Set Y axis
y_tick_spacing = 25  # Specify the interval for y-axis ticks
plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
plt.ylabel("# Of Datapoints", fontsize=10)
plt.yticks(fontsize=8)

# Set title, layout, grid, and legend
plt.title("CBOE VVIX Histogram (200 Bins)", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)

# Save figure and display plot
plt.savefig("02_Histogram+Mean+SD.png", dpi=300, bbox_inches="tight")
plt.show()
No description has been provided for this image

Historical Data - VVIX¶

In [35]:
plot_price(
    price_df=vvix,
    plot_start_date=None,
    plot_end_date="2016-12-31",
    plot_columns=["High", "Low"],
    title="CBOE VVIX, 2007 - 2016",
    x_label="Date",
    x_format="Year",
    y_label="VIX",
    y_format="Decimal",
    y_tick_spacing=15,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="02_VVIX_Plot_2007-2016",
)
No description has been provided for this image
In [36]:
plot_price(
    price_df=vvix,
    plot_start_date="2017-01-01",
    plot_end_date=None,
    plot_columns=["High", "Low"],
    title="CBOE VVIX, 2017 - Present",
    x_label="Date",
    x_format="Year",
    y_label="VIX",
    y_format="Decimal",
    y_tick_spacing=15,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="02_VVIX_Plot_2017-Present",
)
No description has been provided for this image

Stats By Year - VVIX¶

In [37]:
plot_stats(
    stats_df=vvix_stats_by_year,
    plot_columns=["Open_mean", "High_mean", "Low_mean", "Close_mean"],
    title="VVIX Mean OHLC By Year",
    x_label="Year",
    x_rotation=45,
    x_tick_spacing=1,
    y_label="Price",
    y_tick_spacing=5,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="02_VVIX_Stats_By_Year"
)
No description has been provided for this image

Stats By Month - VVIX¶

In [38]:
plot_stats(
    stats_df=vvix_stats_by_month,
    plot_columns=["Open_mean", "High_mean", "Low_mean", "Close_mean"],
    title="VVIX Mean OHLC By Month",
    x_label="Month",
    x_rotation=0,
    x_tick_spacing=1,
    y_label="Price",
    y_tick_spacing=1,
    grid=True,
    legend=True,
    export_plot=True,
    plot_file_name="02_VVIX_Stats_By_Month"
)
No description has been provided for this image
In [39]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

# Plot data
plt.plot(vvix[vvix.index > '2023-12-31'].index, vvix[vvix.index > '2023-12-31']['High'], label='High', linestyle='-', color='steelblue', linewidth=1.5)
plt.plot(vvix[vvix.index > '2023-12-31'].index, vvix[vvix.index > '2023-12-31']['Low'], label='Low', linestyle='-', color='brown', linewidth=1.5)

# Set X axis
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
plt.xlabel("Date", fontsize=10)
plt.xticks(rotation=45, fontsize=8)

# Set Y axis
y_tick_spacing = 5  # Specify the interval for y-axis ticks
plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
plt.ylabel("VVIX", fontsize=10)
plt.yticks(fontsize=8)

# Set title, layout, grid, and legend
plt.title("CBOE VVIX, 2024 - Present", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)

# Save figure and display plot
plt.savefig("02_VVIX_Plot_2024-Present.png", dpi=300, bbox_inches="tight")
plt.show()
No description has been provided for this image
In [40]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

# Plot data
plt.plot(vvix[vvix.index > '2024-12-31'].index, vvix[vvix.index > '2024-12-31']['High'], label='High', linestyle='-', color='steelblue', linewidth=1.5)
plt.plot(vvix[vvix.index > '2024-12-31'].index, vvix[vvix.index > '2024-12-31']['Low'], label='Low', linestyle='-', color='brown', linewidth=1.5)

# Set X axis
plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
plt.xlabel("Date", fontsize=10)
plt.xticks(rotation=45, fontsize=8)

# Set Y axis
y_tick_spacing = 5  # Specify the interval for y-axis ticks
plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
plt.ylabel("VVIX", fontsize=10)
plt.yticks(fontsize=8)

# Set title, layout, grid, and legend
plt.title("CBOE VVIX, 2025 - Present", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)

# Save figure and display plot
plt.savefig("02_VVIX_Plot_2025-Present.png", dpi=300, bbox_inches="tight")
plt.show()
No description has been provided for this image

Investigating A Signal¶

Determining A Spike Level¶

In [41]:
# 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()
In [42]:
display(vix)
Close High Low Open High_SMA_10 High_SMA_10_Shift Spike_Level_SMA High_SMA_20 Spike_SMA High_SMA_50 High_EMA_10 High_EMA_10_Shift Spike_Level_EMA High_EMA_20 Spike_EMA High_EMA_50
Date
1990-01-02 17.24 17.24 17.24 17.24 NaN NaN NaN NaN False NaN 17.24 NaN NaN 17.24 False 17.24
1990-01-03 18.19 18.19 18.19 18.19 NaN NaN NaN NaN False NaN 17.41 17.24 21.55 17.33 False 17.28
1990-01-04 19.22 19.22 19.22 19.22 NaN NaN NaN NaN False NaN 17.74 17.41 21.77 17.51 False 17.35
1990-01-05 20.11 20.11 20.11 20.11 NaN NaN NaN NaN False NaN 18.17 17.74 22.18 17.76 False 17.46
1990-01-08 20.26 20.26 20.26 20.26 NaN NaN NaN NaN False NaN 18.55 18.17 22.71 18.00 False 17.57
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2025-06-03 17.69 19.21 17.64 18.83 20.82 20.89 26.11 21.04 False 27.75 20.58 20.88 26.10 21.93 False 24.37
2025-06-04 17.61 18.07 17.41 17.68 20.76 20.82 26.02 20.68 False 27.72 20.12 20.58 25.72 21.57 False 24.13
2025-06-05 18.48 18.80 17.08 17.68 20.53 20.76 25.95 20.34 False 27.74 19.88 20.12 25.15 21.30 False 23.92
2025-06-06 16.77 18.35 16.65 18.16 20.16 20.53 25.66 20.08 False 27.73 19.60 19.88 24.85 21.02 False 23.70
2025-06-09 17.16 17.72 16.82 17.69 19.38 20.16 25.20 19.82 False 27.70 19.26 19.60 24.50 20.71 False 23.46

8925 rows × 16 columns

In [43]:
vix[vix['High'] >= 50]
Out[43]:
Close High Low Open High_SMA_10 High_SMA_10_Shift Spike_Level_SMA High_SMA_20 Spike_SMA High_SMA_50 High_EMA_10 High_EMA_10_Shift Spike_Level_EMA High_EMA_20 Spike_EMA High_EMA_50
Date
2008-10-06 52.05 58.24 45.12 45.12 42.92 40.52 50.65 37.24 True 28.17 44.33 41.24 51.55 38.82 True 31.65
2008-10-07 53.68 54.19 47.03 52.05 44.73 42.92 53.65 38.66 True 28.76 46.12 44.33 55.41 40.29 False 32.53
2008-10-08 57.53 59.06 51.90 53.68 46.97 44.73 55.91 40.34 True 29.46 48.47 46.12 57.65 42.07 True 33.57
2008-10-09 63.92 64.92 52.54 57.57 49.94 46.97 58.71 42.27 True 30.31 51.46 48.47 60.59 44.25 True 34.80
2008-10-10 69.95 76.94 65.63 65.85 53.99 49.94 62.42 44.79 True 31.39 56.10 51.46 64.33 47.36 True 36.46
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2024-08-05 38.57 65.73 23.39 23.39 23.84 18.95 23.69 19.11 True 15.66 28.04 19.66 24.58 22.15 True 17.62
2025-04-07 46.98 60.13 38.58 60.13 28.60 24.51 30.63 26.10 True 22.35 33.61 27.72 34.65 28.48 True 23.95
2025-04-08 52.33 57.52 36.48 44.04 32.58 28.60 35.76 27.50 True 23.05 37.96 33.61 42.01 31.25 True 25.27
2025-04-09 33.62 57.96 31.90 50.98 36.47 32.58 40.72 29.05 True 23.84 41.60 37.96 47.45 33.79 True 26.55
2025-04-10 40.72 54.87 34.44 34.44 40.03 36.47 45.59 30.49 True 24.58 44.01 41.60 51.99 35.80 True 27.66

97 rows × 16 columns

Spike Counts (Signals) By Year¶

In [44]:
# 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)
Spike_SMA False True
Year
1990 248 5
1991 249 4
1992 250 4
1993 251 2
1994 243 9
1995 252 0
1996 248 6
1997 247 6
1998 243 9
1999 250 2
2000 248 4
2001 240 8
2002 248 4
2003 251 1
2004 250 2
2005 250 2
2006 242 9
2007 239 12
2008 238 15
2009 249 3
2010 239 13
2011 240 12
2012 248 2
2013 249 3
2014 235 17
2015 240 12
2016 234 18
2017 244 7
2018 228 23
2019 241 11
2020 224 29
2021 235 17
2022 239 12
2023 246 4
2024 237 15
2025 96 12
In [45]:
# Copy this <!-- INSERT_08_Spike_Counts_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="08_Spike_Counts.md", content=spike_count_SMA.to_markdown())
✅ Exported and tracked: 08_Spike_Counts.md
In [46]:
# 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_EMA = vix.groupby(['Year', 'Spike_EMA']).size().unstack(fill_value=0)

display(spike_count_EMA)
Spike_EMA False True
Year
1990 247 6
1991 251 2
1992 253 1
1993 251 2
1994 247 5
1995 252 0
1996 252 2
1997 250 3
1998 246 6
1999 250 2
2000 250 2
2001 241 7
2002 250 2
2003 251 1
2004 251 1
2005 250 2
2006 248 3
2007 242 9
2008 240 13
2009 251 1
2010 243 9
2011 242 10
2012 250 0
2013 250 2
2014 236 16
2015 243 9
2016 238 14
2017 244 7
2018 230 21
2019 242 10
2020 228 25
2021 239 13
2022 244 7
2023 248 2
2024 244 8
2025 99 9
In [47]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

# Bar positions
x = np.arange(len(spike_count_SMA[True].index))
width = 0.35

# Plot SMA bars
plt.bar(x - width / 2, spike_count_SMA[True].values, width, color="steelblue", label="Spike Counts Using SMA")

# Plot EMA bars
plt.bar(x + width / 2, spike_count_EMA[True].values, width, color="forestgreen", label="Spike Counts Using EMA")

# Set X axis
# x_tick_spacing = 5  # Specify the interval for y-axis ticks
# plt.gca().xaxis.set_major_locator(MultipleLocator(x_tick_spacing))
plt.xlabel("Year", fontsize=10)
plt.xticks(x, spike_count_SMA[True].index, rotation=45, fontsize=8)
plt.xlim(x[0] - 2 * width, x[-1] + 2 * width)

# # Set Y axis
y_tick_spacing = 2  # Specify the interval for y-axis ticks
plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
plt.ylabel("Count", fontsize=10)
plt.yticks(fontsize=8)

# Set title, layout, grid, and legend
plt.title("Yearly Totals Of Spike Counts", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)

# Save figure and display plot
plt.savefig("08_Spike_Counts.png", dpi=300, bbox_inches="tight")
plt.show()
No description has been provided for this image

Spike Counts (Signals) Plots By Year¶

In [48]:
def vix_plot(start_year, end_year):
    # Start and end dates
    start_date = start_year + '-01-01'
    end_date = end_year + '-12-31'

    # Create temporary dataframe for the specified date range
    vix_temp = vix[(vix.index >= start_date) & (vix.index <= end_date)]

    # Plotting
    plt.figure(figsize=(12, 6), facecolor="#F5F5F5")

    # Plot data
    plt.plot(vix_temp.index, vix_temp['High'], label='High', linestyle='-', color='steelblue', linewidth=1)
    plt.plot(vix_temp.index, vix_temp['Low'], label='Low', linestyle='-', color='brown', linewidth=1)
    plt.plot(vix_temp.index, vix_temp['High_SMA_10'], label='10 Day High SMA', linestyle='-', color='red', linewidth=1)
    plt.plot(vix_temp.index, vix_temp['High_SMA_20'], label='20 Day High SMA', linestyle='-', color='orange', linewidth=1)
    plt.plot(vix_temp.index, vix_temp['High_SMA_50'], label='50 Day High SMA', linestyle='-', color='green', linewidth=1)
    plt.scatter(vix_temp[vix_temp['Spike_SMA'] == True].index, vix_temp[vix_temp['Spike_SMA'] == True]['High'], label='Spike (High > 1.25 * 10 Day High SMA)', linestyle='-', color='black', s=20)

    # Set X axis
    plt.gca().xaxis.set_major_locator(mdates.MonthLocator())
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
    plt.xlabel("Date", fontsize=10)
    plt.xticks(rotation=45, fontsize=8)

    # Set Y axis
    y_tick_spacing = 5  # Specify the interval for y-axis ticks
    plt.gca().yaxis.set_major_locator(MultipleLocator(y_tick_spacing))
    plt.ylabel("VIX", fontsize=10)
    plt.yticks(fontsize=8)

    # Set title, layout, grid, and legend
    plt.title(f"CBOE Volatility Index (VIX), {start_year} - {end_year}", fontsize=12)
    plt.tight_layout()
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.legend(fontsize=9)

    # Save figure and display plot
    plt.savefig(f"09_VIX_SMA_Spike_{start_year}_{end_year}.png", dpi=300, bbox_inches="tight")
    plt.show()

Yearly Plots¶

In [49]:
for year in range(1990, 2026):
    vix_plot(str(year), str(year))
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Spike Counts (Signals) Plots By Decade¶

1990 - 1994¶

In [50]:
vix_plot('1990', '1994')
No description has been provided for this image

1995 - 1999¶

In [51]:
vix_plot('1995', '1999')
No description has been provided for this image

2000 - 2004¶

In [52]:
vix_plot('2000', '2004')
No description has been provided for this image

2005 - 2009¶

In [53]:
vix_plot('2005', '2009')
No description has been provided for this image

2010 - 2014¶

In [54]:
vix_plot('2010', '2014')
No description has been provided for this image

2015 - 2019¶

In [55]:
vix_plot('2015', '2019')
No description has been provided for this image

2020 - 2024¶

In [56]:
vix_plot('2020', '2024')
No description has been provided for this image

2025 - Present¶

In [57]:
vix_plot('2025', '2029')
No description has been provided for this image

Trading History¶

Trades Executed¶

In [58]:
# from schwab_order_history import schwab_order_history
In [59]:
# from datetime import datetime
# import pandas as pd

# # Define your date ranges
# range_2024 = {
#     "from": "2024-01-01T00:00:00.000Z",
#     "to": "2024-12-31T23:59:59.000Z",
# }

# range_2025 = {
#     "from": "2025-01-01T00:00:00.000Z",
#     "to": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z"),
# }

# # Pull both sets of orders
# df_2024 = schwab_order_history(
#     max_results=1000,  # or whatever large number you want
#     from_entered_time=range_2024["from"],
#     to_entered_time=range_2024["to"],
#     account_id=None,  # or pass your specific encrypted account ID
# )

# df_2025 = schwab_order_history(
#     max_results=1000,
#     from_entered_time=range_2025["from"],
#     to_entered_time=range_2025["to"],
#     account_id=None,
# )

# # Combine the two dataframes
# df_all = pd.concat([df_2024, df_2025], ignore_index=True)
In [60]:
# df_2024
In [61]:
# # Filter for symbols that start with "VIX"
# df_vix = df_all[df_all["symbol"].str.startswith("VIX")].copy()
# df_vix = df_vix.sort_values(by=['symbol', 'execution_time'], ascending=[True, True])
In [62]:
# df_vix

Trades Executed¶

In [63]:
# Import CSV file of VIX transactions from IRA and Brokerage accounts
vix_transactions_IRA = pd.read_csv(DATA_MANUAL_DIR / "VIX_Transactions_IRA.csv")
vix_transactions_Brokerage = pd.read_excel(DATA_MANUAL_DIR / "VIX_Transactions_Brokerage.xlsx", sheet_name="VIX_Transactions_Brokerage")
In [64]:
# Combine the two DataFrames
vix_transactions = pd.concat([vix_transactions_IRA, vix_transactions_Brokerage], ignore_index=True)

# Drop unnecessary columns
vix_transactions.drop(columns = {'Description'}, inplace=True)

# Convert Amount, Price, and Fees & Comm columns to numeric
vix_transactions['Amount'] = vix_transactions['Amount'].replace({'\$': '', ',': ''}, regex=True).astype(float)
vix_transactions['Price'] = vix_transactions['Price'].replace({'\$': '', ',': ''}, regex=True).astype(float)
vix_transactions['Fees & Comm'] = vix_transactions['Fees & Comm'].replace({'\$': '', ',': ''}, regex=True).astype(float)

# Convert Amount column to absolute values
vix_transactions['Amount'] = abs(vix_transactions['Amount'])

# Extract date for option expiration with regex (MM/DD/YYYY)
vix_transactions["Exp_Date"] = vix_transactions["Symbol"].str.extract(r'(\d{2}/\d{2}/\d{4})')

# Extract date for option strike price with regex and convert to float
vix_transactions["Strike_Price"] = vix_transactions["Symbol"].str.extract(r'(\d{2}\.\d{2})').astype(float)

# Convert expiration date and trade date to datetime
vix_transactions["Exp_Date"] = pd.to_datetime(vix_transactions["Exp_Date"], format="%m/%d/%Y")
vix_transactions['Date'] = pd.to_datetime(vix_transactions['Date'])

# Rename date to trade date
vix_transactions.rename(columns={'Date': 'Trade_Date'}, inplace=True)

# Sort by Exp_Date, then Strike_Price, then Trade_Date
vix_transactions.sort_values(by=['Exp_Date', 'Strike_Price', 'Trade_Date'], ascending=[True, True, True], inplace=True)

# Reset index
vix_transactions.reset_index(drop=True, inplace=True)
vix_transactions
Out[64]:
Trade_Date Action Symbol Quantity Price Fees & Comm Amount Approx_VIX_Level Comments Exp_Date Strike_Price
0 2024-08-05 Buy to Open VIX 09/18/2024 34.00 P 1 10.95 1.08 1096.08 34.33 NaN 2024-09-18 34.00
1 2024-08-21 Sell to Close VIX 09/18/2024 34.00 P 1 17.95 1.08 1793.92 16.50 NaN 2024-09-18 34.00
2 2024-08-05 Buy to Open VIX 10/16/2024 40.00 P 1 16.35 1.08 1636.08 42.71 NaN 2024-10-16 40.00
3 2024-09-18 Sell to Close VIX 10/16/2024 40.00 P 1 21.54 1.08 2152.92 18.85 NaN 2024-10-16 40.00
4 2024-08-07 Buy to Open VIX 11/20/2024 25.00 P 2 5.90 2.16 1182.16 27.11 NaN 2024-11-20 25.00
5 2024-11-04 Sell to Close VIX 11/20/2024 25.00 P 2 6.10 2.16 1217.84 22.43 NaN 2024-11-20 25.00
6 2024-08-06 Buy to Open VIX 12/18/2024 30.00 P 1 10.25 1.08 1026.08 32.27 NaN 2024-12-18 30.00
7 2024-11-27 Sell to Close VIX 12/18/2024 30.00 P 1 14.95 1.08 1493.92 14.04 NaN 2024-12-18 30.00
8 2025-03-04 Buy to Open VIX 04/16/2025 25.00 P 5 5.65 5.40 2830.40 25.75 NaN 2025-04-16 25.00
9 2025-03-24 Sell to Close VIX 04/16/2025 25.00 P 5 7.00 5.40 3494.60 18.01 NaN 2025-04-16 25.00
10 2025-03-10 Buy to Open VIX 05/21/2025 26.00 P 5 7.10 5.40 3555.40 27.54 Missed opportunity to close position for 20% p... 2025-05-21 26.00
11 2025-04-04 Buy to Open VIX 05/21/2025 26.00 P 10 4.10 10.81 4110.81 38.88 Averaged down on existing position 2025-05-21 26.00
12 2025-04-24 Sell to Close VIX 05/21/2025 26.00 P 7 3.50 7.57 2442.43 27.37 Sold half of position due to vol spike concern... 2025-05-21 26.00
13 2025-05-02 Sell to Close VIX 05/21/2025 26.00 P 4 4.35 4.32 1735.68 22.73 Sold half of remaining position due to vol spi... 2025-05-21 26.00
14 2025-05-07 Sell to Close VIX 05/21/2025 26.00 P 4 3.55 4.32 1415.68 24.49 Closed position ahead of Fed’s (Powell’s) comm... 2025-05-21 26.00
15 2025-04-04 Buy to Open VIX 05/21/2025 37.00 P 3 13.20 3.24 3963.24 36.46 NaN 2025-05-21 37.00
16 2025-05-07 Sell to Close VIX 05/21/2025 37.00 P 3 13.75 3.24 4121.76 24.51 Closed position ahead of Fed’s (Powell’s) comm... 2025-05-21 37.00
17 2025-04-08 Buy to Open VIX 05/21/2025 50.00 P 2 21.15 2.16 4232.16 NaN NaN 2025-05-21 50.00
18 2025-04-24 Sell to Close VIX 05/21/2025 50.00 P 1 25.30 1.08 2528.92 NaN NaN 2025-05-21 50.00
19 2025-04-25 Sell to Close VIX 05/21/2025 50.00 P 1 25.65 1.08 2563.92 NaN NaN 2025-05-21 50.00
20 2025-04-03 Buy to Open VIX 06/18/2025 27.00 P 8 7.05 8.65 5648.65 27.62 NaN 2025-06-18 27.00
21 2025-04-08 Buy to Open VIX 06/18/2025 27.00 P 4 4.55 4.32 1824.32 55.44 Averaged down on existing position 2025-06-18 27.00
22 2025-05-12 Sell to Close VIX 06/18/2025 27.00 P 6 7.55 6.49 4523.51 19.05 Market up on positive news of lowering tariffs... 2025-06-18 27.00
23 2025-05-12 Sell to Close VIX 06/18/2025 27.00 P 6 7.40 6.49 4433.51 19.47 Market up on positive news of lowering tariffs... 2025-06-18 27.00
24 2025-04-04 Buy to Open VIX 06/18/2025 36.00 P 3 13.40 3.24 4023.24 36.61 NaN 2025-06-18 36.00
25 2025-05-12 Sell to Close VIX 06/18/2025 36.00 P 3 16.00 3.24 4796.76 19.14 Market up on positive news of lowering tariffs... 2025-06-18 36.00
26 2025-04-07 Buy to Open VIX 06/18/2025 45.00 P 2 18.85 2.16 3772.16 53.65 NaN 2025-06-18 45.00
27 2025-05-12 Sell to Close VIX 06/18/2025 45.00 P 2 25.00 2.16 4997.84 19.24 Market up on positive news of lowering tariffs... 2025-06-18 45.00
28 2025-04-03 Buy to Open VIX 07/16/2025 29.00 P 5 8.55 5.40 4280.40 29.03 NaN 2025-07-16 29.00
29 2025-05-13 Sell to Close VIX 07/16/2025 29.00 P 3 10.40 3.24 3116.76 17.72 NaN 2025-07-16 29.00
30 2025-05-13 Sell to Close VIX 07/16/2025 29.00 P 2 10.30 2.16 2057.84 17.68 NaN 2025-07-16 29.00
31 2025-04-04 Buy to Open VIX 07/16/2025 36.00 P 3 13.80 3.24 4143.24 36.95 NaN 2025-07-16 36.00
32 2025-05-13 Sell to Close VIX 07/16/2025 36.00 P 1 17.00 1.08 1698.92 17.79 NaN 2025-07-16 36.00
33 2025-05-13 Sell to Close VIX 07/16/2025 36.00 P 2 16.90 2.16 3377.84 17.72 NaN 2025-07-16 36.00
34 2025-04-07 Buy to Open VIX 07/16/2025 45.00 P 2 21.55 2.16 4312.16 46.17 NaN 2025-07-16 45.00
35 2025-05-13 Sell to Close VIX 07/16/2025 45.00 P 2 25.65 2.16 5127.84 17.96 NaN 2025-07-16 45.00
36 2025-04-07 Buy to Open VIX 08/20/2025 45.00 P 2 21.75 2.16 4352.16 49.07 NaN 2025-08-20 45.00
37 2025-05-13 Sell to Close VIX 08/20/2025 45.00 P 2 25.40 2.16 5077.84 18.06 NaN 2025-08-20 45.00
In [65]:
vix_transactions_no_exp = vix_transactions.drop(columns=['Exp_Date', 'Strike_Price'])
vix_transactions_no_exp
Out[65]:
Trade_Date Action Symbol Quantity Price Fees & Comm Amount Approx_VIX_Level Comments
0 2024-08-05 Buy to Open VIX 09/18/2024 34.00 P 1 10.95 1.08 1096.08 34.33 NaN
1 2024-08-21 Sell to Close VIX 09/18/2024 34.00 P 1 17.95 1.08 1793.92 16.50 NaN
2 2024-08-05 Buy to Open VIX 10/16/2024 40.00 P 1 16.35 1.08 1636.08 42.71 NaN
3 2024-09-18 Sell to Close VIX 10/16/2024 40.00 P 1 21.54 1.08 2152.92 18.85 NaN
4 2024-08-07 Buy to Open VIX 11/20/2024 25.00 P 2 5.90 2.16 1182.16 27.11 NaN
5 2024-11-04 Sell to Close VIX 11/20/2024 25.00 P 2 6.10 2.16 1217.84 22.43 NaN
6 2024-08-06 Buy to Open VIX 12/18/2024 30.00 P 1 10.25 1.08 1026.08 32.27 NaN
7 2024-11-27 Sell to Close VIX 12/18/2024 30.00 P 1 14.95 1.08 1493.92 14.04 NaN
8 2025-03-04 Buy to Open VIX 04/16/2025 25.00 P 5 5.65 5.40 2830.40 25.75 NaN
9 2025-03-24 Sell to Close VIX 04/16/2025 25.00 P 5 7.00 5.40 3494.60 18.01 NaN
10 2025-03-10 Buy to Open VIX 05/21/2025 26.00 P 5 7.10 5.40 3555.40 27.54 Missed opportunity to close position for 20% p...
11 2025-04-04 Buy to Open VIX 05/21/2025 26.00 P 10 4.10 10.81 4110.81 38.88 Averaged down on existing position
12 2025-04-24 Sell to Close VIX 05/21/2025 26.00 P 7 3.50 7.57 2442.43 27.37 Sold half of position due to vol spike concern...
13 2025-05-02 Sell to Close VIX 05/21/2025 26.00 P 4 4.35 4.32 1735.68 22.73 Sold half of remaining position due to vol spi...
14 2025-05-07 Sell to Close VIX 05/21/2025 26.00 P 4 3.55 4.32 1415.68 24.49 Closed position ahead of Fed’s (Powell’s) comm...
15 2025-04-04 Buy to Open VIX 05/21/2025 37.00 P 3 13.20 3.24 3963.24 36.46 NaN
16 2025-05-07 Sell to Close VIX 05/21/2025 37.00 P 3 13.75 3.24 4121.76 24.51 Closed position ahead of Fed’s (Powell’s) comm...
17 2025-04-08 Buy to Open VIX 05/21/2025 50.00 P 2 21.15 2.16 4232.16 NaN NaN
18 2025-04-24 Sell to Close VIX 05/21/2025 50.00 P 1 25.30 1.08 2528.92 NaN NaN
19 2025-04-25 Sell to Close VIX 05/21/2025 50.00 P 1 25.65 1.08 2563.92 NaN NaN
20 2025-04-03 Buy to Open VIX 06/18/2025 27.00 P 8 7.05 8.65 5648.65 27.62 NaN
21 2025-04-08 Buy to Open VIX 06/18/2025 27.00 P 4 4.55 4.32 1824.32 55.44 Averaged down on existing position
22 2025-05-12 Sell to Close VIX 06/18/2025 27.00 P 6 7.55 6.49 4523.51 19.05 Market up on positive news of lowering tariffs...
23 2025-05-12 Sell to Close VIX 06/18/2025 27.00 P 6 7.40 6.49 4433.51 19.47 Market up on positive news of lowering tariffs...
24 2025-04-04 Buy to Open VIX 06/18/2025 36.00 P 3 13.40 3.24 4023.24 36.61 NaN
25 2025-05-12 Sell to Close VIX 06/18/2025 36.00 P 3 16.00 3.24 4796.76 19.14 Market up on positive news of lowering tariffs...
26 2025-04-07 Buy to Open VIX 06/18/2025 45.00 P 2 18.85 2.16 3772.16 53.65 NaN
27 2025-05-12 Sell to Close VIX 06/18/2025 45.00 P 2 25.00 2.16 4997.84 19.24 Market up on positive news of lowering tariffs...
28 2025-04-03 Buy to Open VIX 07/16/2025 29.00 P 5 8.55 5.40 4280.40 29.03 NaN
29 2025-05-13 Sell to Close VIX 07/16/2025 29.00 P 3 10.40 3.24 3116.76 17.72 NaN
30 2025-05-13 Sell to Close VIX 07/16/2025 29.00 P 2 10.30 2.16 2057.84 17.68 NaN
31 2025-04-04 Buy to Open VIX 07/16/2025 36.00 P 3 13.80 3.24 4143.24 36.95 NaN
32 2025-05-13 Sell to Close VIX 07/16/2025 36.00 P 1 17.00 1.08 1698.92 17.79 NaN
33 2025-05-13 Sell to Close VIX 07/16/2025 36.00 P 2 16.90 2.16 3377.84 17.72 NaN
34 2025-04-07 Buy to Open VIX 07/16/2025 45.00 P 2 21.55 2.16 4312.16 46.17 NaN
35 2025-05-13 Sell to Close VIX 07/16/2025 45.00 P 2 25.65 2.16 5127.84 17.96 NaN
36 2025-04-07 Buy to Open VIX 08/20/2025 45.00 P 2 21.75 2.16 4352.16 49.07 NaN
37 2025-05-13 Sell to Close VIX 08/20/2025 45.00 P 2 25.40 2.16 5077.84 18.06 NaN
In [66]:
# Copy this <!-- INSERT_10_Trades_Executed_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="10_Trades_Executed.md", content=vix_transactions_no_exp.to_markdown(index=False, floatfmt=".2f"))
✅ Exported and tracked: 10_Trades_Executed.md

Volatility In August 2024¶

In [67]:
esd = "2024-09-18"
eed = "2024-12-18"
tsd = "2024-08-05"
ted = "2024-11-27"

trades, closed_pos, open_pos, per_pnl, pnl = calc_vix_trade_pnl(
    transaction_df=vix_transactions,
    exp_start_date=esd,
    exp_end_date=eed,
    trade_start_date=tsd,
    trade_end_date=ted,
)

# Convert to datetime objects
tsd_dt = datetime.strptime(tsd, "%Y-%m-%d")
ted_dt = datetime.strptime(ted, "%Y-%m-%d")

# Adjust dates
plot_start = tsd_dt - timedelta(days=10)
plot_end = ted_dt + timedelta(days=10)

plot_vix_with_trades(
    vix_price_df=vix,
    trades_df=trades,
    plot_start_date=plot_start.strftime("%Y-%m-%d"),
    plot_end_date=plot_end.strftime("%Y-%m-%d"),
    x_tick_spacing=10,
    y_tick_spacing=5,
    index_number="11",
    export_plot=True,
)

print(f"<!-- INSERT_11_Closed_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_11_Open_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_11_Percent_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_11_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"11_Closed_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"11_Open_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"11_Percent_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=per_pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"11_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=pnl)
No description has been provided for this image
<!-- INSERT_11_Closed_Positions_2024-09-18_2024-12-18_2024-08-05_2024-11-27_HERE -->
<!-- INSERT_11_Open_Positions_2024-09-18_2024-12-18_2024-08-05_2024-11-27_HERE -->
<!-- INSERT_11_Percent_PnL_2024-09-18_2024-12-18_2024-08-05_2024-11-27_HERE -->
<!-- INSERT_11_PnL_2024-09-18_2024-12-18_2024-08-05_2024-11-27_HERE -->
✅ Exported and tracked: 11_Closed_Positions_2024-09-18_2024-12-18_2024-08-05_2024-11-27.md
✅ Exported and tracked: 11_Open_Positions_2024-09-18_2024-12-18_2024-08-05_2024-11-27.md
✅ Exported and tracked: 11_Percent_PnL_2024-09-18_2024-12-18_2024-08-05_2024-11-27.md
✅ Exported and tracked: 11_PnL_2024-09-18_2024-12-18_2024-08-05_2024-11-27.md

Volatility In March 2025¶

In [68]:
esd = "2025-04-16"
eed = "2025-04-16"
tsd = "2025-03-04"
ted = "2025-03-24"

trades, closed_pos, open_pos, per_pnl, pnl = calc_vix_trade_pnl(
    transaction_df=vix_transactions,
    exp_start_date=esd,
    exp_end_date=eed,
    trade_start_date=tsd,
    trade_end_date=ted,
)

# Convert to datetime objects
tsd_dt = datetime.strptime(tsd, "%Y-%m-%d")
ted_dt = datetime.strptime(ted, "%Y-%m-%d")

# Adjust dates
plot_start = tsd_dt - timedelta(days=10)
plot_end = ted_dt + timedelta(days=10)

plot_vix_with_trades(
    vix_price_df=vix,
    trades_df=trades,
    plot_start_date=plot_start.strftime("%Y-%m-%d"),
    plot_end_date=plot_end.strftime("%Y-%m-%d"),
    x_tick_spacing=2,
    y_tick_spacing=2,
    index_number="12",
    export_plot=True,
)

print(f"<!-- INSERT_12_Closed_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_12_Open_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_12_Percent_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_12_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"12_Closed_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"12_Open_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"12_Percent_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=per_pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"12_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=pnl)
No description has been provided for this image
<!-- INSERT_12_Closed_Positions_2025-04-16_2025-04-16_2025-03-04_2025-03-24_HERE -->
<!-- INSERT_12_Open_Positions_2025-04-16_2025-04-16_2025-03-04_2025-03-24_HERE -->
<!-- INSERT_12_Percent_PnL_2025-04-16_2025-04-16_2025-03-04_2025-03-24_HERE -->
<!-- INSERT_12_PnL_2025-04-16_2025-04-16_2025-03-04_2025-03-24_HERE -->
✅ Exported and tracked: 12_Closed_Positions_2025-04-16_2025-04-16_2025-03-04_2025-03-24.md
✅ Exported and tracked: 12_Open_Positions_2025-04-16_2025-04-16_2025-03-04_2025-03-24.md
✅ Exported and tracked: 12_Percent_PnL_2025-04-16_2025-04-16_2025-03-04_2025-03-24.md
✅ Exported and tracked: 12_PnL_2025-04-16_2025-04-16_2025-03-04_2025-03-24.md

Volatility In April 2025¶

In [69]:
esd = "2025-05-21"
eed = "2025-08-20"
tsd = "2025-03-10"
ted = "2025-05-13"

trades, closed_pos, open_pos, per_pnl, pnl = calc_vix_trade_pnl(
    transaction_df=vix_transactions,
    exp_start_date=esd,
    exp_end_date=eed,
    trade_start_date=tsd,
    trade_end_date=ted,
)

# Convert to datetime objects
tsd_dt = datetime.strptime(tsd, "%Y-%m-%d")
ted_dt = datetime.strptime(ted, "%Y-%m-%d")

# Adjust dates
plot_start = tsd_dt - timedelta(days=10)
plot_end = ted_dt + timedelta(days=10)

plot_vix_with_trades(
    vix_price_df=vix,
    trades_df=trades,
    plot_start_date=plot_start.strftime("%Y-%m-%d"),
    plot_end_date=plot_end.strftime("%Y-%m-%d"),
    x_tick_spacing=5,
    y_tick_spacing=5,
    index_number="13",
    export_plot=True,
)

print(f"<!-- INSERT_13_Closed_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_13_Open_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_13_Percent_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_13_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"13_Closed_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"13_Open_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"13_Percent_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=per_pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"13_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=pnl)
No description has been provided for this image
<!-- INSERT_13_Closed_Positions_2025-05-21_2025-08-20_2025-03-10_2025-05-13_HERE -->
<!-- INSERT_13_Open_Positions_2025-05-21_2025-08-20_2025-03-10_2025-05-13_HERE -->
<!-- INSERT_13_Percent_PnL_2025-05-21_2025-08-20_2025-03-10_2025-05-13_HERE -->
<!-- INSERT_13_PnL_2025-05-21_2025-08-20_2025-03-10_2025-05-13_HERE -->
✅ Exported and tracked: 13_Closed_Positions_2025-05-21_2025-08-20_2025-03-10_2025-05-13.md
✅ Exported and tracked: 13_Open_Positions_2025-05-21_2025-08-20_2025-03-10_2025-05-13.md
✅ Exported and tracked: 13_Percent_PnL_2025-05-21_2025-08-20_2025-03-10_2025-05-13.md
✅ Exported and tracked: 13_PnL_2025-05-21_2025-08-20_2025-03-10_2025-05-13.md

Complete Trade History¶

In [70]:
esd = None
eed = None
tsd = None
ted = None

trades, closed_pos, open_pos, per_pnl, pnl = calc_vix_trade_pnl(
    transaction_df=vix_transactions,
    exp_start_date=esd,
    exp_end_date=eed,
    trade_start_date=tsd,
    trade_end_date=ted,
)

print(f"<!-- INSERT_14_Closed_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_14_Open_Positions_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_14_Percent_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
print(f"<!-- INSERT_14_PnL_{esd}_{eed}_{tsd}_{ted}_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"14_Closed_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"14_Open_Positions_{esd}_{eed}_{tsd}_{ted}.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"14_Percent_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=per_pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"14_PnL_{esd}_{eed}_{tsd}_{ted}.md", content=pnl)
<!-- INSERT_14_Closed_Positions_None_None_None_None_HERE -->
<!-- INSERT_14_Open_Positions_None_None_None_None_HERE -->
<!-- INSERT_14_Percent_PnL_None_None_None_None_HERE -->
<!-- INSERT_14_PnL_None_None_None_None_HERE -->
✅ Exported and tracked: 14_Closed_Positions_None_None_None_None.md
✅ Exported and tracked: 14_Open_Positions_None_None_None_None.md
✅ Exported and tracked: 14_Percent_PnL_None_None_None_None.md
✅ Exported and tracked: 14_PnL_None_None_None_None.md