Im attempting to plot financial probability cones based off historical price and implied volatility data, but the bounds that are output seem quite a bit wrong. When i plug in the same parameters in a expected move formula, i get far smaller bounds, which seem more reasonable.
Expected Move = Price * IV * sqrt(Time to Expiry/365)
Expected Move = $71,180 * 0.7866 * sqrt(91/365)
Expected Move = $27,956
This gives me an upper bound of $99,136 at expiry.
When I run the probability cone formula (which to my knowledge should net me the same upper bound at expiry?)
Confidence interval is 95%
Bound = Price * e**(z*IV*sqrt(Time to Expiry in Years)
Bound = $71,180 * e**(1.96*0.7866*sqrt(91/365)
Bound = $153,703
Confidence interval is 68%
Bound = Price * e**(z*IV*sqrt(Time to Expiry in Years)
Bound = $71,180 * e**(1*0.7866*sqrt(91/365)
Bound = $105,422
Both of these are different. So either my code is messing up, or im doing something very wrong.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Load the price history and 3-month implied volatility data from the uploaded CSV files
price_history_path = '/Users/admin/Documents/financial_modeling/Standard/pricehistory.csv'
iv_data_path = '/Users/admin/Documents/financial_modeling/Standard/3miv.csv'
# Read the files
price_history_data = pd.read_csv(price_history_path)
iv_data = pd.read_csv(iv_data_path)
# Convert the 'DateTime' columns to datetime type for both datasets
price_history_data['DateTime'] = pd.to_datetime(price_history_data['DateTime'])
iv_data['DateTime'] = pd.to_datetime(iv_data['DateTime'])
# Merge the datasets on 'DateTime'
merged_data = pd.merge_asof(price_history_data.sort_values('DateTime'), iv_data.sort_values('DateTime'), on='DateTime')
# Calculate daily IV from 3-month IV
merged_data['Daily IV'] = merged_data['3M IV'] / 100 / np.sqrt(90) # Dividing by sqrt of quarter days
# Find the start of each quarter
merged_data['Quarter'] = merged_data['DateTime'].dt.to_period('Q')
quarter_starts = merged_data.groupby('Quarter').first().reset_index()
# Plotting
plt.figure(figsize=(14, 8))
plt.plot(merged_data['DateTime'], merged_data['Index Price'], label='Historical Price', color='gray')
# Constants for confidence intervals
ci_levels = [0.67, 1] # Multipliers for 50% CI and 68% CI
ci_labels = ['50% CI', '68% CI']
# Generate and plot probability cones for each quarter and confidence interval
for _, row in quarter_starts.iterrows():
start_date = row['DateTime']
start_price = row['Index Price']
daily_iv = row['Daily IV']
days = 90 # Days in a quarter
time_points_future = np.arange(1, days + 1)
for ci, label in zip(ci_levels, ci_labels):
upper_prices_future = start_price * np.exp(daily_iv * ci * np.sqrt(time_points_future))
lower_prices_future = start_price * np.exp(-daily_iv * ci * np.sqrt(time_points_future))
future_dates = [start_date + pd.Timedelta(days=int(d - 1)) for d in time_points_future]
plt.plot(future_dates, upper_prices_future, label=f'Upper Bound ({label}, Start: {start_date.date()})', linestyle='--')
plt.plot(future_dates, lower_prices_future, label=f'Lower Bound ({label}, Start: {start_date.date()})', linestyle='--')
plt.fill_between(future_dates, lower_prices_future, upper_prices_future, alpha=0.1)
plt.title('Bitcoin Price and Quarterly Probability Cones for Different Confidence Intervals')
plt.xlabel('Date')
plt.ylabel('Price')
plt.grid(True)
plt.show()
I tried adjusting confidence intervals but because the expected move doesnt take that into account i didnt find that to make much sense.
Ludvig Sjøstrøm is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.