Investigating Vix Levels And Returns¶
Python Imports¶
In [1]:
# Standard Library
import datetime
import io
import os
import random
import sys
import warnings
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¶
Typical Functions¶
In [4]:
# Import functions from source directories
from export_track_md_deps import export_track_md_deps
from df_info import df_info
from df_info_markdown import df_info_markdown
from pandas_set_decimal_places import pandas_set_decimal_places
from load_data import load_data
Project Specific Functions¶
In [5]:
# Import functions from source directories
from yf_pull_data import yf_pull_data
Data Overview¶
Acquire CBOE Volatility Index (VIX) Data¶
In [6]:
yf_pull_data(
base_directory=DATA_DIR,
ticker="^VIX",
source="Yahoo_Finance",
asset_class="Indices",
excel_export=True,
pickle_export=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-04-25 | 24.84 | 27.200001 | 24.84 | 26.219999 | 0 |
Yahoo Finance data complete for ^VIX --------------------
Out[6]:
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-04-21 | 33.820000 | 35.750000 | 31.790001 | 32.750000 | 0 |
2025-04-22 | 30.570000 | 32.680000 | 30.080000 | 32.610001 | 0 |
2025-04-23 | 28.450001 | 30.290001 | 27.110001 | 28.750000 | 0 |
2025-04-24 | 26.469999 | 29.660000 | 26.360001 | 28.690001 | 0 |
2025-04-25 | 24.840000 | 27.200001 | 24.840000 | 26.219999 | 0 |
8895 rows × 5 columns
Set Decimal Places¶
In [7]:
pandas_set_decimal_places(2)
Load Data¶
In [8]:
# 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 For Missing Values & Forward Fill Any Missing Values¶
In [9]:
# Check to see if there are any NaN values
vix[vix['High'].isna()]
Out[9]:
Close | High | Low | Open | |
---|---|---|---|---|
Date |
In [10]:
# Forward fill to clean up missing data
vix['High'] = vix['High'].ffill()
VIX DataFrame Info¶
In [11]:
df_info(vix)
The columns, shape, and data types are: <class 'pandas.core.frame.DataFrame'> DatetimeIndex: 8895 entries, 1990-01-02 to 2025-04-25 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Close 8895 non-null float64 1 High 8895 non-null float64 2 Low 8895 non-null float64 3 Open 8895 non-null float64 dtypes: float64(4) memory usage: 347.5 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-04-21 | 33.82 | 35.75 | 31.79 | 32.75 |
2025-04-22 | 30.57 | 32.68 | 30.08 | 32.61 |
2025-04-23 | 28.45 | 30.29 | 27.11 | 28.75 |
2025-04-24 | 26.47 | 29.66 | 26.36 | 28.69 |
2025-04-25 | 24.84 | 27.20 | 24.84 | 26.22 |
In [12]:
# Copy this <!-- INSERT_01_DF_Info_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="01_DF_Info.md", content=df_info_markdown(vix))
✅ Exported and tracked: 01_DF_Info.md
Statistics¶
In [13]:
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'],
}
In [14]:
vix_stats
Out[14]:
Close | High | Low | Open | |
---|---|---|---|---|
count | 8895.00 | 8895.00 | 8895.00 | 8895.00 |
mean | 19.49 | 20.40 | 18.82 | 19.58 |
std | 7.85 | 8.41 | 7.40 | 7.92 |
min | 9.14 | 9.31 | 8.56 | 9.01 |
25% | 13.86 | 14.53 | 13.40 | 13.93 |
50% | 17.64 | 18.35 | 17.06 | 17.68 |
75% | 22.84 | 23.83 | 22.14 | 22.97 |
max | 82.69 | 89.53 | 72.76 | 82.69 |
mean + -1 std | 11.65 | 11.99 | 11.42 | 11.66 |
mean + 0 std | 19.49 | 20.40 | 18.82 | 19.58 |
mean + 1 std | 27.34 | 28.81 | 26.22 | 27.51 |
mean + 2 std | 35.19 | 37.21 | 33.62 | 35.43 |
mean + 3 std | 43.03 | 45.62 | 41.02 | 43.35 |
mean + 4 std | 50.88 | 54.03 | 48.42 | 51.27 |
mean + 5 std | 58.73 | 62.43 | 55.82 | 59.20 |
In [15]:
# Copy this <!-- INSERT_02_VIX_Stats_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="02_VIX_Stats.md", content=vix_stats.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 02_VIX_Stats.md
Deciles¶
In [16]:
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.62 | 11.72 | 12.13 |
0.20 | 13.24 | 13.87 | 12.85 | 13.30 |
0.30 | 14.59 | 15.28 | 14.07 | 14.67 |
0.40 | 16.09 | 16.75 | 15.55 | 16.12 |
0.50 | 17.64 | 18.35 | 17.06 | 17.68 |
0.60 | 19.55 | 20.38 | 19.00 | 19.68 |
0.70 | 21.64 | 22.64 | 20.99 | 21.79 |
0.80 | 24.32 | 25.36 | 23.51 | 24.39 |
0.90 | 28.73 | 30.02 | 27.80 | 28.88 |
1.00 | 82.69 | 89.53 | 72.76 | 82.69 |
In [17]:
# Copy this <!-- INSERT_03_VIX_Deciles_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="03_VIX_Deciles.md", content=vix_deciles.to_markdown(floatfmt=".2f"))
✅ Exported and tracked: 03_VIX_Deciles.md
Histogram Distribution¶
In [18]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")
# Histogram
plt.hist([vix['High']], label=['High'], bins=200, edgecolor='black', color='steelblue')
# 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("04_Histogram.png", dpi=300, bbox_inches="tight")
plt.show()
In [19]:
# 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'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'Mean: {vix_stats.loc['mean']['High']:.2f}')
plt.axvline(vix_stats.loc['mean + 1 std']['High'], color='green', linestyle='dashed', linewidth=1, label=f'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'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'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'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("05_Histogram+Mean.png", dpi=300, bbox_inches="tight")
plt.show()
Plots¶
Historical VIX Data¶
1990 - 2009¶
In [20]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")
# Plot data
plt.plot(vix[vix.index <= '2009-12-31'].index, vix[vix.index <= '2009-12-31']['High'], label='High', linestyle='-', color='steelblue', linewidth=1.5)
# plt.plot(vix.index, vix['SMA_10'], label='10 Day SMA', linestyle='-', color='r', linewidth=1)
# plt.plot(vix.index, vix['SMA_20'], label='20 Day SMA', linestyle='-', color='orange', linewidth=1)
# plt.plot(vix.index, vix['SMA_50'], label='50 Day SMA', linestyle='-', color='g', linewidth=1)
# Set X axis
plt.gca().xaxis.set_major_locator(mdates.YearLocator())
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%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("CBOE Volatility Index (VIX), 1990 - 2009", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)
# Save figure and display plot
plt.savefig("06_Plot_1990-2009.png", dpi=300, bbox_inches="tight")
plt.show()
2010 - Present¶
In [21]:
# Plotting
plt.figure(figsize=(12, 6), facecolor="#F5F5F5")
# Plot data
plt.plot(vix[vix.index > '2009-12-31'].index, vix[vix.index > '2009-12-31']['High'], label='High', linestyle='-', color='steelblue', linewidth=1.5)
# plt.plot(vix.index, vix['SMA_10'], label='10 Day SMA', linestyle='-', color='r', linewidth=1)
# plt.plot(vix.index, vix['SMA_20'], label='20 Day SMA', linestyle='-', color='orange', linewidth=1)
# plt.plot(vix.index, vix['SMA_50'], label='50 Day SMA', linestyle='-', color='g', linewidth=1)
# Set X axis
plt.gca().xaxis.set_major_locator(mdates.YearLocator())
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%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("CBOE Volatility Index (VIX), 2010 - Present", fontsize=12)
plt.tight_layout()
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(fontsize=9)
# Save figure and display plot
plt.savefig("07_Plot_2010-Present.png", dpi=300, bbox_inches="tight")
plt.show()
Investigating A Signal¶
Determining A Spike Level¶
In [22]:
# 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 [23]:
vix.head()
Out[23]:
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 |
In [24]:
vix.tail()
Out[24]:
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 | ||||||||||||||||
2025-04-21 | 33.82 | 35.75 | 31.79 | 32.75 | 44.65 | 45.63 | 57.04 | 34.58 | False | 26.76 | 37.87 | 38.34 | 47.93 | 35.70 | False | 29.40 |
2025-04-22 | 30.57 | 32.68 | 30.08 | 32.61 | 41.90 | 44.65 | 55.81 | 35.25 | False | 27.08 | 36.93 | 37.87 | 47.34 | 35.41 | False | 29.53 |
2025-04-23 | 28.45 | 30.29 | 27.11 | 28.75 | 39.18 | 41.90 | 52.38 | 35.88 | False | 27.35 | 35.72 | 36.93 | 46.16 | 34.92 | False | 29.56 |
2025-04-24 | 26.47 | 29.66 | 26.36 | 28.69 | 36.35 | 39.18 | 48.97 | 36.41 | False | 27.62 | 34.62 | 35.72 | 44.65 | 34.42 | False | 29.56 |
2025-04-25 | 24.84 | 27.20 | 24.84 | 26.22 | 33.58 | 36.35 | 45.44 | 36.80 | False | 27.82 | 33.27 | 34.62 | 43.27 | 33.73 | False | 29.47 |
In [25]:
vix[vix['High'] >= 50]
Out[25]:
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 [26]:
# 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)
spike_count_SMA
Out[26]:
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 | 67 | 11 |
In [27]:
# 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 [28]:
# 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)
spike_count_EMA
Out[28]:
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 | 69 | 9 |
In [29]:
# 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 [30]:
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 [31]:
for year in range(1990, 2026):
vix_plot(str(year), str(year))
Spike Counts (Signals) Plots By Decade¶
1990 - 1994¶
In [32]:
vix_plot('1990', '1994')
1995 - 1999¶
In [33]:
vix_plot('1995', '1999')
2000 - 2004¶
In [34]:
vix_plot('2000', '2004')
2005 - 2009¶
In [35]:
vix_plot('2005', '2009')
2010 - 2014¶
In [36]:
vix_plot('2010', '2014')
2015 - 2019¶
In [37]:
vix_plot('2015', '2019')
2020 - 2024¶
In [38]:
vix_plot('2020', '2024')
2025 - Present¶
In [39]:
vix_plot('2025', '2029')
Trading History¶
Trades Executed¶
In [40]:
# Import CSV file of VIX transactions from IRA and Brokerage accounts
vix_transactions_IRA = load_data(
base_directory=DATA_MANUAL_DIR,
ticker="VIX_Transactions_IRA",
source=None,
asset_class=None,
timeframe=None,
)
vix_transactions_Brokerage = load_data(
base_directory=DATA_MANUAL_DIR,
ticker="VIX_Transactions_Brokerage",
source=None,
asset_class=None,
timeframe=None,
)
# 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})')
# 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
vix_transactions.sort_values(by=['Exp_Date', 'Trade_Date'], ascending=[True, True], inplace=True)
# Reset index
vix_transactions.reset_index(drop=True, inplace=True)
vix_transactions
Out[40]:
Trade_Date | Action | Symbol | Quantity | Price | Fees & Comm | Amount | Exp_Date | |
---|---|---|---|---|---|---|---|---|
0 | 2024-08-05 | Buy to Open | VIX 09/18/2024 34.00 P | 1 | 10.95 | 1.08 | 1096.08 | 2024-09-18 |
1 | 2024-08-21 | Sell to Close | VIX 09/18/2024 34.00 P | 1 | 17.95 | 1.08 | 1793.92 | 2024-09-18 |
2 | 2024-08-05 | Buy to Open | VIX 10/16/2024 40.00 P | 1 | 16.35 | 1.08 | 1636.08 | 2024-10-16 |
3 | 2024-09-18 | Sell to Close | VIX 10/16/2024 40.00 P | 1 | 21.54 | 1.08 | 2152.92 | 2024-10-16 |
4 | 2024-08-07 | Buy to Open | VIX 11/20/2024 25.00 P | 2 | 5.90 | 2.16 | 1182.16 | 2024-11-20 |
5 | 2024-11-04 | Sell to Close | VIX 11/20/2024 25.00 P | 2 | 6.10 | 2.16 | 1217.84 | 2024-11-20 |
6 | 2024-08-06 | Buy to Open | VIX 12/18/2024 30.00 P | 1 | 10.25 | 1.08 | 1026.08 | 2024-12-18 |
7 | 2024-11-27 | Sell to Close | VIX 12/18/2024 30.00 P | 1 | 14.95 | 1.08 | 1493.92 | 2024-12-18 |
8 | 2025-03-04 | Buy to Open | VIX 04/16/2025 25.00 P | 5 | 5.65 | 5.40 | 2830.40 | 2025-04-16 |
9 | 2025-03-24 | Sell to Close | VIX 04/16/2025 25.00 P | 5 | 7.00 | 5.40 | 3494.60 | 2025-04-16 |
10 | 2025-03-10 | Buy to Open | VIX 05/21/2025 26.00 P | 5 | 7.10 | 5.40 | 3555.40 | 2025-05-21 |
11 | 2025-04-04 | Buy to Open | VIX 05/21/2025 26.00 P | 10 | 4.10 | 10.81 | 4110.81 | 2025-05-21 |
12 | 2025-04-04 | Buy to Open | VIX 05/21/2025 37.00 P | 3 | 13.20 | 3.24 | 3963.24 | 2025-05-21 |
13 | 2025-04-08 | Buy to Open | VIX 05/21/2025 50.00 P | 2 | 21.15 | 2.16 | 4232.16 | 2025-05-21 |
14 | 2025-04-24 | Sell to Close | VIX 05/21/2025 50.00 P | 1 | 25.30 | 1.08 | 2528.92 | 2025-05-21 |
15 | 2025-04-24 | Sell to Close | VIX 05/21/2025 26.00 P | 7 | 3.50 | 7.57 | 2442.43 | 2025-05-21 |
16 | 2025-04-25 | Sell to Close | VIX 05/21/2025 50.00 P | 1 | 25.65 | 1.08 | 2563.92 | 2025-05-21 |
17 | 2025-04-03 | Buy to Open | VIX 06/18/2025 27.00 P | 8 | 7.05 | 8.65 | 5648.65 | 2025-06-18 |
18 | 2025-04-04 | Buy to Open | VIX 06/18/2025 36.00 P | 3 | 13.40 | 3.24 | 4023.24 | 2025-06-18 |
19 | 2025-04-07 | Buy to Open | VIX 06/18/2025 45.00 P | 2 | 18.85 | 2.16 | 3772.16 | 2025-06-18 |
20 | 2025-04-08 | Buy to Open | VIX 06/18/2025 27.00 P | 4 | 4.55 | 4.32 | 1824.32 | 2025-06-18 |
21 | 2025-04-03 | Buy to Open | VIX 07/16/2025 29.00 P | 5 | 8.55 | 5.40 | 4280.40 | 2025-07-16 |
22 | 2025-04-04 | Buy to Open | VIX 07/16/2025 36.00 P | 3 | 13.80 | 3.24 | 4143.24 | 2025-07-16 |
23 | 2025-04-07 | Buy to Open | VIX 07/16/2025 45.00 P | 2 | 21.55 | 2.16 | 4312.16 | 2025-07-16 |
24 | 2025-04-07 | Buy to Open | VIX 08/20/2025 45.00 P | 2 | 21.75 | 2.16 | 4352.16 | 2025-08-20 |
In [41]:
vix_transactions_no_exp = vix_transactions.drop(columns=['Exp_Date'])
In [42]:
# 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
In [43]:
# Split buys and sells and sum the amounts
vix_transactions_sells = vix_transactions[vix_transactions['Action'] == 'Sell to Close']
vix_transactions_sells = vix_transactions_sells.groupby(['Symbol', 'Exp_Date'], as_index=False)[['Amount', 'Quantity']].sum()
vix_transactions_sells
Out[43]:
Symbol | Exp_Date | Amount | Quantity | |
---|---|---|---|---|
0 | VIX 04/16/2025 25.00 P | 2025-04-16 | 3494.60 | 5 |
1 | VIX 05/21/2025 26.00 P | 2025-05-21 | 2442.43 | 7 |
2 | VIX 05/21/2025 50.00 P | 2025-05-21 | 5092.84 | 2 |
3 | VIX 09/18/2024 34.00 P | 2024-09-18 | 1793.92 | 1 |
4 | VIX 10/16/2024 40.00 P | 2024-10-16 | 2152.92 | 1 |
5 | VIX 11/20/2024 25.00 P | 2024-11-20 | 1217.84 | 2 |
6 | VIX 12/18/2024 30.00 P | 2024-12-18 | 1493.92 | 1 |
In [44]:
vix_transactions_sells.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 7 entries, 0 to 6 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Symbol 7 non-null object 1 Exp_Date 7 non-null datetime64[ns] 2 Amount 7 non-null float64 3 Quantity 7 non-null int64 dtypes: datetime64[ns](1), float64(1), int64(1), object(1) memory usage: 356.0+ bytes
In [45]:
vix_transactions_buys = vix_transactions[vix_transactions['Action'] == 'Buy to Open']
vix_transactions_buys = vix_transactions_buys.groupby(['Symbol', 'Exp_Date'], as_index=False)[['Amount', 'Quantity']].sum()
vix_transactions_buys
Out[45]:
Symbol | Exp_Date | Amount | Quantity | |
---|---|---|---|---|
0 | VIX 04/16/2025 25.00 P | 2025-04-16 | 2830.40 | 5 |
1 | VIX 05/21/2025 26.00 P | 2025-05-21 | 7666.21 | 15 |
2 | VIX 05/21/2025 37.00 P | 2025-05-21 | 3963.24 | 3 |
3 | VIX 05/21/2025 50.00 P | 2025-05-21 | 4232.16 | 2 |
4 | VIX 06/18/2025 27.00 P | 2025-06-18 | 7472.97 | 12 |
5 | VIX 06/18/2025 36.00 P | 2025-06-18 | 4023.24 | 3 |
6 | VIX 06/18/2025 45.00 P | 2025-06-18 | 3772.16 | 2 |
7 | VIX 07/16/2025 29.00 P | 2025-07-16 | 4280.40 | 5 |
8 | VIX 07/16/2025 36.00 P | 2025-07-16 | 4143.24 | 3 |
9 | VIX 07/16/2025 45.00 P | 2025-07-16 | 4312.16 | 2 |
10 | VIX 08/20/2025 45.00 P | 2025-08-20 | 4352.16 | 2 |
11 | VIX 09/18/2024 34.00 P | 2024-09-18 | 1096.08 | 1 |
12 | VIX 10/16/2024 40.00 P | 2024-10-16 | 1636.08 | 1 |
13 | VIX 11/20/2024 25.00 P | 2024-11-20 | 1182.16 | 2 |
14 | VIX 12/18/2024 30.00 P | 2024-12-18 | 1026.08 | 1 |
In [46]:
vix_transactions_buys.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 15 entries, 0 to 14 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Symbol 15 non-null object 1 Exp_Date 15 non-null datetime64[ns] 2 Amount 15 non-null float64 3 Quantity 15 non-null int64 dtypes: datetime64[ns](1), float64(1), int64(1), object(1) memory usage: 612.0+ bytes
In [47]:
# Merge buys and sells dataframes back together
vix_transactions = pd.merge(vix_transactions_buys, vix_transactions_sells, on=['Symbol', 'Exp_Date'], how='outer', suffixes=('_Buy', '_Sell'))
vix_transactions = vix_transactions.sort_values(by=['Exp_Date'], ascending=[True])
vix_transactions = vix_transactions.reset_index(drop=True)
vix_transactions
Out[47]:
Symbol | Exp_Date | Amount_Buy | Quantity_Buy | Amount_Sell | Quantity_Sell | |
---|---|---|---|---|---|---|
0 | VIX 09/18/2024 34.00 P | 2024-09-18 | 1096.08 | 1 | 1793.92 | 1.00 |
1 | VIX 10/16/2024 40.00 P | 2024-10-16 | 1636.08 | 1 | 2152.92 | 1.00 |
2 | VIX 11/20/2024 25.00 P | 2024-11-20 | 1182.16 | 2 | 1217.84 | 2.00 |
3 | VIX 12/18/2024 30.00 P | 2024-12-18 | 1026.08 | 1 | 1493.92 | 1.00 |
4 | VIX 04/16/2025 25.00 P | 2025-04-16 | 2830.40 | 5 | 3494.60 | 5.00 |
5 | VIX 05/21/2025 26.00 P | 2025-05-21 | 7666.21 | 15 | 2442.43 | 7.00 |
6 | VIX 05/21/2025 37.00 P | 2025-05-21 | 3963.24 | 3 | NaN | NaN |
7 | VIX 05/21/2025 50.00 P | 2025-05-21 | 4232.16 | 2 | 5092.84 | 2.00 |
8 | VIX 06/18/2025 27.00 P | 2025-06-18 | 7472.97 | 12 | NaN | NaN |
9 | VIX 06/18/2025 36.00 P | 2025-06-18 | 4023.24 | 3 | NaN | NaN |
10 | VIX 06/18/2025 45.00 P | 2025-06-18 | 3772.16 | 2 | NaN | NaN |
11 | VIX 07/16/2025 29.00 P | 2025-07-16 | 4280.40 | 5 | NaN | NaN |
12 | VIX 07/16/2025 36.00 P | 2025-07-16 | 4143.24 | 3 | NaN | NaN |
13 | VIX 07/16/2025 45.00 P | 2025-07-16 | 4312.16 | 2 | NaN | NaN |
14 | VIX 08/20/2025 45.00 P | 2025-08-20 | 4352.16 | 2 | NaN | NaN |
In [48]:
vix_transactions.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 15 entries, 0 to 14 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Symbol 15 non-null object 1 Exp_Date 15 non-null datetime64[ns] 2 Amount_Buy 15 non-null float64 3 Quantity_Buy 15 non-null int64 4 Amount_Sell 7 non-null float64 5 Quantity_Sell 7 non-null float64 dtypes: datetime64[ns](1), float64(3), int64(1), object(1) memory usage: 852.0+ bytes
In [49]:
vix_transactions['Closed'] = (~vix_transactions['Amount_Sell'].isna()) & (~vix_transactions['Amount_Buy'].isna()) & (vix_transactions['Quantity_Buy'] == vix_transactions['Quantity_Sell'])
vix_transactions
Out[49]:
Symbol | Exp_Date | Amount_Buy | Quantity_Buy | Amount_Sell | Quantity_Sell | Closed | |
---|---|---|---|---|---|---|---|
0 | VIX 09/18/2024 34.00 P | 2024-09-18 | 1096.08 | 1 | 1793.92 | 1.00 | True |
1 | VIX 10/16/2024 40.00 P | 2024-10-16 | 1636.08 | 1 | 2152.92 | 1.00 | True |
2 | VIX 11/20/2024 25.00 P | 2024-11-20 | 1182.16 | 2 | 1217.84 | 2.00 | True |
3 | VIX 12/18/2024 30.00 P | 2024-12-18 | 1026.08 | 1 | 1493.92 | 1.00 | True |
4 | VIX 04/16/2025 25.00 P | 2025-04-16 | 2830.40 | 5 | 3494.60 | 5.00 | True |
5 | VIX 05/21/2025 26.00 P | 2025-05-21 | 7666.21 | 15 | 2442.43 | 7.00 | False |
6 | VIX 05/21/2025 37.00 P | 2025-05-21 | 3963.24 | 3 | NaN | NaN | False |
7 | VIX 05/21/2025 50.00 P | 2025-05-21 | 4232.16 | 2 | 5092.84 | 2.00 | True |
8 | VIX 06/18/2025 27.00 P | 2025-06-18 | 7472.97 | 12 | NaN | NaN | False |
9 | VIX 06/18/2025 36.00 P | 2025-06-18 | 4023.24 | 3 | NaN | NaN | False |
10 | VIX 06/18/2025 45.00 P | 2025-06-18 | 3772.16 | 2 | NaN | NaN | False |
11 | VIX 07/16/2025 29.00 P | 2025-07-16 | 4280.40 | 5 | NaN | NaN | False |
12 | VIX 07/16/2025 36.00 P | 2025-07-16 | 4143.24 | 3 | NaN | NaN | False |
13 | VIX 07/16/2025 45.00 P | 2025-07-16 | 4312.16 | 2 | NaN | NaN | False |
14 | VIX 08/20/2025 45.00 P | 2025-08-20 | 4352.16 | 2 | NaN | NaN | False |
Closed Positions¶
In [50]:
closed_trades = vix_transactions[vix_transactions['Closed']]
closed_trades = closed_trades.reset_index(drop=True)
closed_trades['Realized_PnL'] = closed_trades['Amount_Sell'] - closed_trades['Amount_Buy']
closed_trades['Profit_Percent'] = closed_trades['Realized_PnL'] / closed_trades['Amount_Buy']
closed_trades.drop(columns={'Closed', 'Exp_Date'}, inplace=True)
closed_trades['Quantity_Sell'] = closed_trades['Quantity_Sell'].astype(int)
closed_trades
Out[50]:
Symbol | Amount_Buy | Quantity_Buy | Amount_Sell | Quantity_Sell | Realized_PnL | Profit_Percent | |
---|---|---|---|---|---|---|---|
0 | VIX 09/18/2024 34.00 P | 1096.08 | 1 | 1793.92 | 1 | 697.84 | 0.64 |
1 | VIX 10/16/2024 40.00 P | 1636.08 | 1 | 2152.92 | 1 | 516.84 | 0.32 |
2 | VIX 11/20/2024 25.00 P | 1182.16 | 2 | 1217.84 | 2 | 35.68 | 0.03 |
3 | VIX 12/18/2024 30.00 P | 1026.08 | 1 | 1493.92 | 1 | 467.84 | 0.46 |
4 | VIX 04/16/2025 25.00 P | 2830.40 | 5 | 3494.60 | 5 | 664.20 | 0.23 |
5 | VIX 05/21/2025 50.00 P | 4232.16 | 2 | 5092.84 | 2 | 860.68 | 0.20 |
In [51]:
# Copy this <!-- INSERT_11_Closed_Positions_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="11_Closed_Positions.md", content=closed_trades.to_markdown(index=False, floatfmt=".2f"))
✅ Exported and tracked: 11_Closed_Positions.md
In [52]:
net_profit_percent = closed_trades['Realized_PnL'].sum() / closed_trades['Amount_Buy'].sum()
net_profit_percent_str = f"{round(net_profit_percent * 100, 2)}%"
net_profit_percent_str
Out[52]:
'27.02%'
In [53]:
# Copy this <!-- INSERT_11_Net_Profit_Percent_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="11_Net_Profit_Percent.md", content=net_profit_percent_str)
✅ Exported and tracked: 11_Net_Profit_Percent.md
In [54]:
net_PnL = closed_trades['Realized_PnL'].sum()
net_PnL_str = f"${net_PnL:,.2f}"
net_PnL_str
Out[54]:
'$3,243.08'
In [55]:
# Copy this <!-- INSERT_11_Net_PnL_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="11_Net_PnL.md", content=net_PnL_str)
✅ Exported and tracked: 11_Net_PnL.md
Open Positions¶
In [56]:
open_trades = vix_transactions[~vix_transactions['Closed']]
open_trades = open_trades.reset_index(drop=True)
open_trades.drop(columns={'Closed', 'Amount_Sell', 'Quantity_Sell', 'Exp_Date'}, inplace=True)
open_trades
Out[56]:
Symbol | Amount_Buy | Quantity_Buy | |
---|---|---|---|
0 | VIX 05/21/2025 26.00 P | 7666.21 | 15 |
1 | VIX 05/21/2025 37.00 P | 3963.24 | 3 |
2 | VIX 06/18/2025 27.00 P | 7472.97 | 12 |
3 | VIX 06/18/2025 36.00 P | 4023.24 | 3 |
4 | VIX 06/18/2025 45.00 P | 3772.16 | 2 |
5 | VIX 07/16/2025 29.00 P | 4280.40 | 5 |
6 | VIX 07/16/2025 36.00 P | 4143.24 | 3 |
7 | VIX 07/16/2025 45.00 P | 4312.16 | 2 |
8 | VIX 08/20/2025 45.00 P | 4352.16 | 2 |
In [57]:
# Copy this <!-- INSERT_12_Open_Positions_HERE --> to index_temp.md
export_track_md_deps(dep_file=dep_file, md_filename="12_Open_Positions.md", content=open_trades.to_markdown(index=False, floatfmt=".2f"))
✅ Exported and tracked: 12_Open_Positions.md
In [ ]:
In [ ]: