Case Study: Hedging A Long-Only SPX Portfolio With Costless Collars

Case Study: Hedging A Long-Only SPX Portfolio With Costless Collars#

import warnings
from pathlib import Path

import numpy as np
import pandas as pd
import plotly.io as pio

import pull_options_data
import spx_hedging_functions
from settings import config

pd.set_option("display.max_columns", None)
pio.templates.default = "plotly_white"


warnings.filterwarnings("ignore")
# Load environment variables
DATA_DIR = Path(config("DATA_DIR"))
OUTPUT_DIR = Path(config("OUTPUT_DIR"))
WRDS_USERNAME = config("WRDS_USERNAME")

START_DATE = config("START_DATE")
END_DATE = config("END_DATE")
asof_date = "2023-08-02"
interpolation_args = {"method": "cubic", "order": 3}
# enter the range of costless collar to construct
d = 0.80
contracts_to_display = 15
r = 0.045
N = 1000
days_in_year = 365.0

strike_range = (1000, 6000)
expiry_range = (0.01, 2.0)

dt = 1 / days_in_year

option_chain = pull_options_data.load_spx_option_data(
    asof_date=asof_date, start_date=START_DATE, end_date=END_DATE
)
# do we need to check if forward prices for calls and puts are the same? no, because the sql_query() function is pulling the forward price from a single other dataset and joining tables
# Build market predictions
delta_map, prediction_range = spx_hedging_functions.build_market_predictions(
    option_chain=option_chain,
    asof_date=asof_date,
    contracts_to_display=contracts_to_display,
    interpolation_args=interpolation_args,
)
# Display prediction range table
prediction_range_reformat = prediction_range.copy()
prediction_range_reformat.index = prediction_range_reformat.index.strftime("%Y-%m-%d")
prediction_range_reformat.index.name = "Expiration Date"
prediction_range_reformat = prediction_range_reformat.T
prediction_range_reformat = prediction_range_reformat.iloc[:, :contracts_to_display]
prediction_range_reformat = prediction_range_reformat.style.format("{:.2f}")
prediction_range_reformat
Expiration Date 2023-08-03 2023-08-04 2023-08-07 2023-08-08 2023-08-09 2023-08-10 2023-08-11 2023-08-14 2023-08-15 2023-08-16 2023-08-17 2023-08-18 2023-08-21 2023-08-22 2023-08-23
80% Below 4545.00 4560.00 4560.00 4565.00 4570.00 4580.00 4585.00 4595.00 4600.00 4605.00 4605.00 4610.00 4620.00 4625.00 4625.00
50/50 4515.00 4515.00 4515.00 4520.00 4520.00 4520.00 4520.00 4520.00 4520.00 4525.00 4525.00 4525.00 4525.00 4525.00 4530.00
80% Above 4475.00 4460.00 4455.00 4450.00 4440.00 4430.00 4425.00 4420.00 4415.00 4410.00 4405.00 4400.00 4395.00 4395.00 4390.00
Fwd Prices 2023-08-02 4513.99 4514.59 4516.40 4517.01 4517.61 4518.21 4518.81 4520.64 4521.25 4521.86 4522.47 4523.09 4524.94 4525.56 4526.18
# Create pareto chart visualization
pareto_data = spx_hedging_functions.build_pareto_chart(
    ticker="SPX",
    asof_date=asof_date,
    prediction_range=prediction_range,
    delta_map=delta_map,
    contracts_to_display=contracts_to_display,
)

# Display pareto chart
pareto_data['figure'].show()
# Display prediction range table
pareto_data['prediction_range'].style.format("{:.2f}")
Expiration Date 2023-08-03 2023-08-04 2023-08-07 2023-08-08 2023-08-09 2023-08-10 2023-08-11 2023-08-14 2023-08-15 2023-08-16 2023-08-17 2023-08-18 2023-08-21 2023-08-22 2023-08-23
80% Below 4545.00 4560.00 4560.00 4565.00 4570.00 4580.00 4585.00 4595.00 4600.00 4605.00 4605.00 4610.00 4620.00 4625.00 4625.00
50/50 4515.00 4515.00 4515.00 4520.00 4520.00 4520.00 4520.00 4520.00 4520.00 4525.00 4525.00 4525.00 4525.00 4525.00 4530.00
80% Above 4475.00 4460.00 4455.00 4450.00 4440.00 4430.00 4425.00 4420.00 4415.00 4410.00 4405.00 4400.00 4395.00 4395.00 4390.00
Fwd Prices 2023-08-02 4513.99 4514.59 4516.40 4517.01 4517.61 4518.21 4518.81 4520.64 4521.25 4521.86 4522.47 4523.09 4524.94 4525.56 4526.18
# Create delta heatmap visualization
delta_heatmap = spx_hedging_functions.plot_delta_heatmap(
    ticker="SPX",
    asof_date=asof_date,
    delta_map=delta_map,
    contracts_to_display=contracts_to_display,
    heatmap_step=3,
)
Expiration Date 2023-08-03 2023-08-04 2023-08-07 2023-08-08 2023-08-09 2023-08-10 2023-08-11 2023-08-14 2023-08-15 2023-08-16 2023-08-17 2023-08-18 2023-08-21 2023-08-22 2023-08-23
Price >=                              
$4,615.00 1% 2% 4% 7% 8% 11% 7% 12% 14% 16% 17% 19% 21% 22% 23%
$4,600.00 1% 4% 8% 10% 12% 10% 14% 17% 19% 21% 22% 24% 26% 27% 28%
$4,585.00 2% 8% 12% 9% 13% 17% 20% 23% 25% 26% 28% 29% 30% 31% 32%
$4,570.00 5% 14% 13% 18% 21% 25% 27% 29% 31% 32% 33% 34% 36% 36% 37%
$4,555.00 12% 14% 24% 27% 29% 32% 34% 36% 37% 38% 39% 39% 41% 41% 42%
$4,540.00 24% 29% 34% 36% 38% 40% 41% 42% 43% 44% 44% 45% 46% 46% 46%
$4,525.00 36% 42% 45% 46% 46% 47% 48% 49% 49% 49% 50% 50% 51% 51% 51%
$4,510.00 55% 54% 54% 54% 54% 54% 54% 55% 55% 55% 55% 55% 55% 55% 55%
$4,495.00 69% 64% 63% 62% 62% 61% 60% 60% 60% 60% 59% 59% 59% 59% 59%
$4,480.00 79% 72% 71% 69% 68% 66% 65% 65% 65% 64% 64% 64% 64% 63% 63%
$4,465.00 85% 79% 77% 75% 73% 71% 70% 70% 69% 68% 68% 67% 67% 67% 66%
$4,450.00 88% 83% 81% 79% 78% 76% 74% 74% 73% 72% 71% 71% 71% 70% 70%
$4,435.00 90% 87% 85% 83% 82% 79% 78% 77% 76% 75% 75% 74% 74% 73% 73%
$4,420.00 91% 89% 88% 86% 85% 82% 81% 80% 79% 78% 77% 77% 76% 76% 75%
$4,405.00 92% 91% 90% 88% 87% 85% 83% 83% 82% 81% 80% 79% 79% 78% 78%
$4,390.00 93% 92% 91% 90% 89% 87% 86% 85% 84% 83% 82% 81% 81% 80% 80%
$4,375.00 94% 93% 92% 91% 91% 88% 87% 87% 86% 85% 84% 83% 83% 82% 82%
$4,360.00 94% 93% 93% 92% 92% 90% 89% 88% 87% 87% 86% 85% 85% 84% 83%
$4,345.00 94% 94% 94% 93% 93% 91% 90% 90% 89% 88% 87% 87% 86% 85% 85%
$4,330.00 95% 94% 94% 94% 93% 92% 91% 91% 90% 89% 88% 88% 87% 87% 86%
$4,315.00 95% 95% 95% 94% 94% 93% 92% 92% 91% 90% 90% 89% 89% 88% 88%
$4,300.00 95% 95% 95% 95% 94% 93% 93% 92% 92% 91% 90% 90% 90% 89% 89%
$4,285.00 96% 95% 95% 95% 95% 94% 93% 93% 93% 92% 91% 91% 91% 90% 90%
$4,270.00 96% 96% 96% 95% 95% 94% 94% 94% 93% 93% 92% 92% 91% 91% 90%
$4,255.00 96% 96% 96% 96% 95% 94% 94% 94% 94% 93% 93% 92% 92% 92% 91%
$4,240.00 96% 96% 96% 96% 96% 95% 95% 95% 94% 94% 93% 93% 93% 92% 92%
$4,225.00 96% 96% 96% 96% 96% 95% 95% 95% 95% 94% 94% 93% 93% 93% 93%
$4,210.00 96% 96% 97% 96% 96% 95% 95% 95% 95% 95% 94% 94% 94% 93% 93%
$4,195.00 97% 96% 97% 97% 96% 96% 95% 96% 95% 95% 95% 94% 94% 94% 94%
$4,180.00 97% 97% 97% 97% 96% 96% 96% 96% 96% 95% 95% 95% 95% 94% 94%
$4,165.00 97% 97% 97% 97% 97% 96% 96% 96% 96% 96% 95% 95% 95% 95% 94%
$4,150.00 97% 97% 97% 97% 97% 96% 96% 96% 96% 96% 95% 95% 95% 95% 95%
$4,135.00 97% 97% 97% 97% 97% 96% 96% 96% 96% 96% 96% 96% 96% 95% 95%
$4,120.00 97% 97% 97% 97% 97% 96% 96% 97% 96% 96% 96% 96% 96% 96% 95%
$4,100.00 97% 97% 97% 97% 97% 97% 97% 97% 97% 97% 96% 96% 96% 96% 96%
$4,075.00 97% 97% 98% 97% 97% 97% 97% 97% 97% 97% 96% 96% 96% 96% 96%
$4,050.00 97% 97% 98% 98% 98% 97% 97% 97% 97% 97% 97% 97% 97% 97% 96%
$4,025.00 98% 98% 98% 98% 98% 97% 97% 97% 97% 97% 97% 97% 97% 97% 97%
$4,000.00 98% 98% 98% 98% 98% 97% 97% 98% 97% 97% 97% 97% 97% 97% 97%
$3,975.00 98% 98% 98% 98% 98% 97% 97% 98% 98% 98% 97% 97% 97% 97% 97%
$3,950.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 97% 97% 98% 97% 97%
$3,925.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,900.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,875.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,850.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,825.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,800.00 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,775.00 98% 98% 99% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98% 98%
$3,750.00 98% 98% 99% 99% 99% 98% 98% 98% 99% 99% 98% 98% 99% 98% 98%
$3,725.00 98% 98% 99% 99% 99% 98% 98% 98% 99% 99% 98% 98% 99% 99% 99%
$3,700.00 98% 98% 99% 99% 99% 98% 98% 98% 99% 99% 99% 99% 99% 99% 99%
$3,675.00 98% 99% 99% 99% 99% 98% 98% 99% 99% 99% 99% 98% 99% 99% 99%
$3,650.00 98% 99% 99% 99% 99% 99% 98% 99% 99% 99% 99% 99% 99% 99% 99%
$3,625.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,600.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,575.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,550.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,525.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,500.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,450.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,375.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,300.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
$3,225.00 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99% 99%
# get the forward prices
fwd_prices = spx_hedging_functions.get_fwd_prices(asof_date, option_chain, contracts_to_display)
iv_surface, price_surface = spx_hedging_functions.build_iv_and_price_surface(
    option_chain,
    fwd_prices,
    asof_date,
    strike_range,
    expiry_range,
    smoothing=True,
    smoothing_params={"sigma": 2, "clip_bounds": (0.05, 0.75)},
)
spx_hedging_functions.plot_vol_price_charts(
    iv_surface=iv_surface,
    price_surface=price_surface,
    strike_range=[np.floor(iv_surface.index.min()), np.ceil(iv_surface.index.max())],
    expiry_range=[iv_surface.columns.min(), iv_surface.columns.max()],
    iv_surface_title="SPX Implied Volatility",
    price_surface_title="Price Surface as of " + asof_date,
)