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
# 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
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-08-04 | 17.52 | 19.58 | 17.48 | 19.559999 | 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-07-29 | 15.980000 | 16.120001 | 14.700000 | 14.950000 | 0 |
2025-07-30 | 15.480000 | 17.270000 | 15.440000 | 15.870000 | 0 |
2025-07-31 | 16.719999 | 17.170000 | 14.740000 | 14.990000 | 0 |
2025-08-01 | 20.379999 | 21.900000 | 17.389999 | 17.400000 | 0 |
2025-08-04 | 17.520000 | 19.580000 | 17.480000 | 19.559999 | 0 |
8963 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",
file_format="excel",
)
# 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: 8963 entries, 1990-01-02 to 2025-08-04 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Close 8963 non-null float64 1 High 8963 non-null float64 2 Low 8963 non-null float64 3 Open 8963 non-null float64 dtypes: float64(4) memory usage: 350.1 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-07-29 | 15.98 | 16.12 | 14.70 | 14.95 |
2025-07-30 | 15.48 | 17.27 | 15.44 | 15.87 |
2025-07-31 | 16.72 | 17.17 | 14.74 | 14.99 |
2025-08-01 | 20.38 | 21.90 | 17.39 | 17.40 |
2025-08-04 | 17.52 | 19.58 | 17.48 | 19.56 |
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 | 8963.00 | 8963.00 | 8963.00 | 8963.00 |
mean | 19.48 | 20.40 | 18.81 | 19.58 |
std | 7.82 | 8.38 | 7.38 | 7.90 |
min | 9.14 | 9.31 | 8.56 | 9.01 |
25% | 13.89 | 14.56 | 13.42 | 13.94 |
50% | 17.64 | 18.37 | 17.07 | 17.69 |
75% | 22.81 | 23.81 | 22.13 | 22.96 |
max | 82.69 | 89.53 | 72.76 | 82.69 |
mean + -1 std | 11.66 | 12.02 | 11.44 | 11.68 |
mean + 0 std | 19.48 | 20.40 | 18.81 | 19.58 |
mean + 1 std | 27.31 | 28.77 | 26.19 | 27.47 |
mean + 2 std | 35.13 | 37.15 | 33.56 | 35.37 |
mean + 3 std | 42.95 | 45.53 | 40.94 | 43.26 |
mean + 4 std | 50.77 | 53.91 | 48.31 | 51.16 |
mean + 5 std | 58.59 | 62.29 | 55.69 | 59.06 |
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.52 | 12.45 | 12.20 | 82.69 | 31.46 | 13.89 | 12.42 | 85.47 | 27.50 | 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 | 20.69 | 6.58 | 14.89 | 60.13 | 22.12 | 8.03 | 15.16 | 60.13 | 19.37 | 4.90 | 14.58 | 38.58 | 20.41 | 6.25 | 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.46 | 5.75 | 9.79 | 44.09 | 19.15 | 6.02 | 10.28 | 44.44 | 17.73 | 5.40 | 9.37 | 34.97 | 18.34 | 5.68 | 9.75 | 40.79 |
7 | 17.83 | 5.67 | 9.18 | 48.17 | 18.53 | 5.90 | 9.52 | 48.46 | 17.21 | 5.41 | 8.84 | 42.05 | 17.76 | 5.60 | 9.36 | 44.92 |
8 | 19.17 | 6.73 | 10.04 | 45.34 | 20.12 | 7.45 | 10.32 | 65.73 | 18.44 | 6.37 | 9.52 | 41.77 | 19.18 | 6.86 | 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.13 | 12.64 | 11.72 | 12.14 |
0.20 | 13.27 | 13.88 | 12.86 | 13.33 |
0.30 | 14.63 | 15.31 | 14.12 | 14.71 |
0.40 | 16.11 | 16.77 | 15.58 | 16.15 |
0.50 | 17.64 | 18.37 | 17.07 | 17.69 |
0.60 | 19.54 | 20.38 | 18.98 | 19.66 |
0.70 | 21.61 | 22.60 | 20.96 | 21.75 |
0.80 | 24.29 | 25.30 | 23.45 | 24.37 |
0.90 | 28.67 | 29.97 | 27.76 | 28.83 |
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()
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",
)
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",
)
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"
)
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"
)
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-08-01 | 109.79 | 112.96 | 104.33 | 106.19 | 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-07-28 | 88.59 | 91.37 | 88.43 | 89.87 | 0 |
2025-07-29 | 95.14 | 96.06 | 87.76 | 87.76 | 0 |
2025-07-30 | 94.33 | 100.55 | 94.10 | 95.63 | 0 |
2025-07-31 | 98.37 | 98.65 | 92.28 | 93.78 | 0 |
2025-08-01 | 109.79 | 112.96 | 104.33 | 106.19 | 0 |
4666 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",
file_format="excel",
)
# 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: 4666 entries, 2007-01-03 to 2025-08-01 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Close 4666 non-null float64 1 High 4666 non-null float64 2 Low 4666 non-null float64 3 Open 4666 non-null float64 dtypes: float64(4) memory usage: 182.3 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-07-28 | 88.59 | 91.37 | 88.43 | 89.87 |
2025-07-29 | 95.14 | 96.06 | 87.76 | 87.76 |
2025-07-30 | 94.33 | 100.55 | 94.10 | 95.63 |
2025-07-31 | 98.37 | 98.65 | 92.28 | 93.78 |
2025-08-01 | 109.79 | 112.96 | 104.33 | 106.19 |
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 | 4666.00 | 4666.00 | 4666.00 | 4666.00 |
mean | 93.52 | 95.57 | 91.95 | 93.77 |
std | 16.39 | 17.99 | 15.04 | 16.44 |
min | 59.74 | 59.74 | 59.31 | 59.31 |
25% | 82.40 | 83.56 | 81.51 | 82.60 |
50% | 90.60 | 92.32 | 89.41 | 90.88 |
75% | 102.18 | 104.99 | 99.88 | 102.51 |
max | 207.59 | 212.22 | 187.27 | 212.22 |
mean + -1 std | 77.13 | 77.58 | 76.91 | 77.33 |
mean + 0 std | 93.52 | 95.57 | 91.95 | 93.77 |
mean + 1 std | 109.91 | 113.56 | 107.00 | 110.21 |
mean + 2 std | 126.30 | 131.55 | 122.04 | 126.65 |
mean + 3 std | 142.68 | 149.54 | 137.09 | 143.09 |
mean + 4 std | 159.07 | 167.53 | 152.13 | 159.53 |
mean + 5 std | 175.46 | 185.52 | 167.17 | 175.97 |
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 | 103.55 | 14.96 | 83.19 | 186.33 | 108.04 | 17.55 | 85.82 | 189.03 | 99.52 | 11.70 | 81.73 | 146.51 | 102.85 | 14.19 | 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 | 93.16 | 14.86 | 63.52 | 155.48 | 94.76 | 16.11 | 63.52 | 172.21 | 91.49 | 13.79 | 63.52 | 140.15 | 92.98 | 14.83 | 63.52 | 151.60 |
7 | 90.10 | 12.82 | 67.21 | 138.42 | 91.63 | 13.88 | 67.21 | 149.60 | 88.60 | 11.94 | 67.21 | 133.82 | 89.98 | 12.78 | 67.21 | 139.54 |
8 | 96.85 | 16.93 | 68.05 | 212.22 | 98.92 | 18.71 | 68.05 | 212.22 | 94.70 | 14.85 | 68.05 | 148.68 | 96.64 | 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.92 | 76.11 | 75.48 | 75.83 |
0.20 | 80.65 | 81.48 | 79.89 | 80.79 |
0.30 | 83.96 | 85.33 | 83.07 | 84.25 |
0.40 | 87.30 | 88.67 | 86.08 | 87.54 |
0.50 | 90.60 | 92.32 | 89.41 | 90.88 |
0.60 | 94.23 | 96.16 | 93.01 | 94.49 |
0.70 | 99.08 | 101.49 | 97.40 | 99.35 |
0.80 | 106.05 | 109.38 | 103.88 | 106.46 |
0.90 | 115.15 | 118.71 | 112.40 | 115.42 |
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()
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",
)
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",
)
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"
)
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"
)
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()
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()
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-07-29 | 15.98 | 16.12 | 14.70 | 14.95 | 16.70 | 16.83 | 21.04 | 17.01 | False | 18.62 | 16.40 | 16.46 | 20.58 | 17.05 | False | 19.04 |
2025-07-30 | 15.48 | 17.27 | 15.44 | 15.87 | 16.48 | 16.70 | 20.88 | 17.00 | False | 18.61 | 16.56 | 16.40 | 20.50 | 17.07 | False | 18.97 |
2025-07-31 | 16.72 | 17.17 | 14.74 | 14.99 | 16.46 | 16.48 | 20.60 | 16.99 | False | 18.55 | 16.67 | 16.56 | 20.70 | 17.08 | False | 18.90 |
2025-08-01 | 20.38 | 21.90 | 17.39 | 17.40 | 16.97 | 16.46 | 20.58 | 17.24 | True | 18.62 | 17.62 | 16.67 | 20.84 | 17.54 | True | 19.01 |
2025-08-04 | 17.52 | 19.58 | 17.48 | 19.56 | 17.23 | 16.97 | 21.22 | 17.29 | False | 18.59 | 17.98 | 17.62 | 22.03 | 17.74 | False | 19.04 |
8963 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 | 133 | 13 |
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 | 136 | 10 |
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()
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))
Spike Counts (Signals) Plots By Decade¶
1990 - 1994¶
In [50]:
vix_plot('1990', '1994')
1995 - 1999¶
In [51]:
vix_plot('1995', '1999')
2000 - 2004¶
In [52]:
vix_plot('2000', '2004')
2005 - 2009¶
In [53]:
vix_plot('2005', '2009')
2010 - 2014¶
In [54]:
vix_plot('2010', '2014')
2015 - 2019¶
In [55]:
vix_plot('2015', '2019')
2020 - 2024¶
In [56]:
vix_plot('2020', '2024')
2025 - Present¶
In [57]:
vix_plot('2025', '2029')
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 |
38 | 2025-06-26 | Buy to Open | VIX 09/17/2025 20.00 C | 10 | 3.00 | 10.81 | 3010.81 | 16.37 | Opened long dated call position; VIX level at ... | 2025-09-17 | 20.00 |
39 | 2025-08-01 | Sell to Close | VIX 09/17/2025 20.00 C | 5 | 3.05 | 5.40 | 1519.60 | 20.48 | Sold half of position due to theta drag, held ... | 2025-09-17 | 20.00 |
40 | 2025-07-23 | Buy to Open | VIX 10/22/2025 21.00 C | 10 | 2.92 | 10.81 | 2930.81 | 15.40 | Continued low volatility, opened long dated ca... | 2025-10-22 | 21.00 |
41 | 2025-06-26 | Buy to Open | VIX 10/22/2025 22.00 C | 10 | 2.94 | 10.81 | 2950.81 | 16.43 | Opened long dated call position; VIX level at ... | 2025-10-22 | 22.00 |
42 | 2025-07-17 | Buy to Open | VIX 10/22/2025 23.00 C | 10 | 2.75 | 10.81 | 2760.81 | 16.86 | Continued low volatility, opened long dated ca... | 2025-10-22 | 23.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 |
38 | 2025-06-26 | Buy to Open | VIX 09/17/2025 20.00 C | 10 | 3.00 | 10.81 | 3010.81 | 16.37 | Opened long dated call position; VIX level at ... |
39 | 2025-08-01 | Sell to Close | VIX 09/17/2025 20.00 C | 5 | 3.05 | 5.40 | 1519.60 | 20.48 | Sold half of position due to theta drag, held ... |
40 | 2025-07-23 | Buy to Open | VIX 10/22/2025 21.00 C | 10 | 2.92 | 10.81 | 2930.81 | 15.40 | Continued low volatility, opened long dated ca... |
41 | 2025-06-26 | Buy to Open | VIX 10/22/2025 22.00 C | 10 | 2.94 | 10.81 | 2950.81 | 16.43 | Opened long dated call position; VIX level at ... |
42 | 2025-07-17 | Buy to Open | VIX 10/22/2025 23.00 C | 10 | 2.75 | 10.81 | 2760.81 | 16.86 | Continued low volatility, opened long dated ca... |
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]:
# Variables to be modifed
esd = "2024-09-18" # Expiration Start Date
eed = "2024-12-18" # Expiration End Date
tsd = "2024-08-05" # Trade Start Date
ted = "2024-11-27" # Trade End Date
index_number = "11"
x_tick_spacing = 10
y_tick_spacing = 5
############################################
## Do not modify the code below this line ##
############################################
trades, closed_pos, open_pos, per_pnl, pnl, tot_opened_pos_mkt_val, tot_closed_pos_mkt_val = 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 the plot start and end 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=x_tick_spacing,
y_tick_spacing=y_tick_spacing,
index_number=index_number,
export_plot=True,
)
print(f"<!-- INSERT_{index_number}_Closed_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Open_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Opened_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Closed_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_PnL_HERE -->")
print(f"<!-- INSERT_{index_number}_Percent_PnL_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Closed_Positions.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Open_Positions.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Opened_Position_Market_Value.md", content=tot_opened_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Closed_Position_Market_Value.md", content=tot_closed_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_PnL.md", content=pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Percent_PnL.md", content=per_pnl)
<!-- INSERT_11_Closed_Positions_HERE --> <!-- INSERT_11_Open_Positions_HERE --> <!-- INSERT_11_Total_Opened_Position_Market_Value_HERE --> <!-- INSERT_11_Total_Closed_Position_Market_Value_HERE --> <!-- INSERT_11_PnL_HERE --> <!-- INSERT_11_Percent_PnL_HERE --> ✅ Exported and tracked: 11_Closed_Positions.md ✅ Exported and tracked: 11_Open_Positions.md ✅ Exported and tracked: 11_Total_Opened_Position_Market_Value.md ✅ Exported and tracked: 11_Total_Closed_Position_Market_Value.md ✅ Exported and tracked: 11_PnL.md ✅ Exported and tracked: 11_Percent_PnL.md
Volatility In March 2025¶
In [68]:
# Variables to be modifed
esd = "2025-04-16" # Expiration Start Date
eed = "2025-04-16" # Expiration End Date
tsd = "2025-03-04" # Trade Start Date
ted = "2025-03-24" # Trade End Date
index_number = "12"
x_tick_spacing = 2
y_tick_spacing = 2
############################################
## Do not modify the code below this line ##
############################################
trades, closed_pos, open_pos, per_pnl, pnl, tot_opened_pos_mkt_val, tot_closed_pos_mkt_val = 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 the plot start and end 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=x_tick_spacing,
y_tick_spacing=y_tick_spacing,
index_number=index_number,
export_plot=True,
)
print(f"<!-- INSERT_{index_number}_Closed_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Open_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Opened_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Closed_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_PnL_HERE -->")
print(f"<!-- INSERT_{index_number}_Percent_PnL_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Closed_Positions.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Open_Positions.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Opened_Position_Market_Value.md", content=tot_opened_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Closed_Position_Market_Value.md", content=tot_closed_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_PnL.md", content=pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Percent_PnL.md", content=per_pnl)
<!-- INSERT_12_Closed_Positions_HERE --> <!-- INSERT_12_Open_Positions_HERE --> <!-- INSERT_12_Total_Opened_Position_Market_Value_HERE --> <!-- INSERT_12_Total_Closed_Position_Market_Value_HERE --> <!-- INSERT_12_PnL_HERE --> <!-- INSERT_12_Percent_PnL_HERE --> ✅ Exported and tracked: 12_Closed_Positions.md ✅ Exported and tracked: 12_Open_Positions.md ✅ Exported and tracked: 12_Total_Opened_Position_Market_Value.md ✅ Exported and tracked: 12_Total_Closed_Position_Market_Value.md ✅ Exported and tracked: 12_PnL.md ✅ Exported and tracked: 12_Percent_PnL.md
Volatility In April 2025¶
In [69]:
# Variables to be modifed
esd = "2025-05-21" # Expiration Start Date
eed = "2025-08-20" # Expiration End Date
tsd = "2025-03-10" # Trade Start Date
ted = "2025-05-13" # Trade End Date
index_number = "13"
x_tick_spacing = 5
y_tick_spacing = 5
############################################
## Do not modify the code below this line ##
############################################
trades, closed_pos, open_pos, per_pnl, pnl, tot_opened_pos_mkt_val, tot_closed_pos_mkt_val = 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 the plot start and end 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=x_tick_spacing,
y_tick_spacing=y_tick_spacing,
index_number=index_number,
export_plot=True,
)
print(f"<!-- INSERT_{index_number}_Closed_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Open_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Opened_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Closed_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_PnL_HERE -->")
print(f"<!-- INSERT_{index_number}_Percent_PnL_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Closed_Positions.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Open_Positions.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Opened_Position_Market_Value.md", content=tot_opened_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Closed_Position_Market_Value.md", content=tot_closed_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_PnL.md", content=pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Percent_PnL.md", content=per_pnl)
<!-- INSERT_13_Closed_Positions_HERE --> <!-- INSERT_13_Open_Positions_HERE --> <!-- INSERT_13_Total_Opened_Position_Market_Value_HERE --> <!-- INSERT_13_Total_Closed_Position_Market_Value_HERE --> <!-- INSERT_13_PnL_HERE --> <!-- INSERT_13_Percent_PnL_HERE --> ✅ Exported and tracked: 13_Closed_Positions.md ✅ Exported and tracked: 13_Open_Positions.md ✅ Exported and tracked: 13_Total_Opened_Position_Market_Value.md ✅ Exported and tracked: 13_Total_Closed_Position_Market_Value.md ✅ Exported and tracked: 13_PnL.md ✅ Exported and tracked: 13_Percent_PnL.md
Low Volatility In June 2025¶
In [70]:
# Variables to be modifed
esd = "2025-09-17" # Expiration Start Date
eed = "2025-12-31" # Expiration End Date
tsd = "2025-06-26" # Trade Start Date
ted = "2025-12-31" # Trade End Date
index_number = "14"
x_tick_spacing = 5
y_tick_spacing = 1
############################################
## Do not modify the code below this line ##
############################################
trades, closed_pos, open_pos, per_pnl, pnl, tot_opened_pos_mkt_val, tot_closed_pos_mkt_val = 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 the plot start and end 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=x_tick_spacing,
y_tick_spacing=y_tick_spacing,
index_number=index_number,
export_plot=True,
)
print(f"<!-- INSERT_{index_number}_Closed_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Open_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Opened_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Closed_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_PnL_HERE -->")
print(f"<!-- INSERT_{index_number}_Percent_PnL_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Closed_Positions.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Open_Positions.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Opened_Position_Market_Value.md", content=tot_opened_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Closed_Position_Market_Value.md", content=tot_closed_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_PnL.md", content=pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Percent_PnL.md", content=per_pnl)
<!-- INSERT_14_Closed_Positions_HERE --> <!-- INSERT_14_Open_Positions_HERE --> <!-- INSERT_14_Total_Opened_Position_Market_Value_HERE --> <!-- INSERT_14_Total_Closed_Position_Market_Value_HERE --> <!-- INSERT_14_PnL_HERE --> <!-- INSERT_14_Percent_PnL_HERE --> ✅ Exported and tracked: 14_Closed_Positions.md ✅ Exported and tracked: 14_Open_Positions.md ✅ Exported and tracked: 14_Total_Opened_Position_Market_Value.md ✅ Exported and tracked: 14_Total_Closed_Position_Market_Value.md ✅ Exported and tracked: 14_PnL.md ✅ Exported and tracked: 14_Percent_PnL.md
Complete Trade History¶
In [71]:
# Variables to be modifed
esd = None
eed = None
tsd = None
ted = None
index_number = "99"
############################################
## Do not modify the code below this line ##
############################################
trades, closed_pos, open_pos, per_pnl, pnl, tot_opened_pos_mkt_val, tot_closed_pos_mkt_val = 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_{index_number}_Closed_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Open_Positions_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Opened_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_Total_Closed_Position_Market_Value_HERE -->")
print(f"<!-- INSERT_{index_number}_PnL_HERE -->")
print(f"<!-- INSERT_{index_number}_Percent_PnL_HERE -->")
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Closed_Positions.md", content=closed_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Open_Positions.md", content=open_pos.to_markdown(index=False, floatfmt=".2f"))
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Opened_Position_Market_Value.md", content=tot_opened_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Total_Closed_Position_Market_Value.md", content=tot_closed_pos_mkt_val)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_PnL.md", content=pnl)
export_track_md_deps(dep_file=dep_file, md_filename=f"{index_number}_Percent_PnL.md", content=per_pnl)
<!-- INSERT_99_Closed_Positions_HERE --> <!-- INSERT_99_Open_Positions_HERE --> <!-- INSERT_99_Total_Opened_Position_Market_Value_HERE --> <!-- INSERT_99_Total_Closed_Position_Market_Value_HERE --> <!-- INSERT_99_PnL_HERE --> <!-- INSERT_99_Percent_PnL_HERE --> ✅ Exported and tracked: 99_Closed_Positions.md ✅ Exported and tracked: 99_Open_Positions.md ✅ Exported and tracked: 99_Total_Opened_Position_Market_Value.md ✅ Exported and tracked: 99_Total_Closed_Position_Market_Value.md ✅ Exported and tracked: 99_PnL.md ✅ Exported and tracked: 99_Percent_PnL.md
In [ ]:
In [ ]:
In [ ]: