16 KiB
This code calculates various methods of historical volatility from stock price data. It downloads historical prices for Apple Inc. (AAPL) and computes returns. The code then defines functions to compute volatility using different models: Parkinson, Garman-Klass, Hodges-Tompkins, Rogers-Satchell, Yang-Zhang, and standard deviation. Each model is applied to the data, and the results are plotted for visualization. This is useful for risk management, option pricing, and financial analysis.
import math
import numpy as np import yfinance as yf
Download historical stock data for Apple Inc. (AAPL) from Yahoo Finance
data = yf.download("AAPL", start="2017-01-01", end="2022-06-30")
Display the downloaded stock data
data
Calculate daily returns from adjusted closing prices
returns = (data["Adj Close"] / data["Adj Close"].shift(1)) - 1
Plot the adjusted closing prices over time
data["Adj Close"].plot()
def parkinson(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using the Parkinson model Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Parkinson volatility """ # Calculate the Parkinson volatility estimate using high and low prices rs = (1.0 / (4.0 * math.log(2.0))) * ( (price_data["High"] / price_data["Low"]).apply(np.log) ) ** 2.0 # Define a function to apply the rolling mean and scale by trading periods def f(v): return (trading_periods * v.mean()) ** 0.5 # Apply the rolling window calculation to the Parkinson estimate result = rs.rolling(window=window, center=False).apply(func=f) # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot Parkinson volatility over time
parkinson(data).plot()
def garman_klass(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using the Garman-Klass model Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Garman-Klass volatility """ # Calculate log returns of high/low and close/open prices log_hl = (price_data["High"] / price_data["Low"]).apply(np.log) log_co = (price_data["Close"] / price_data["Open"]).apply(np.log) # Compute the Garman-Klass volatility estimate rs = 0.5 * log_hl**2 - (2 * math.log(2) - 1) * log_co**2 # Define a function to apply the rolling mean and scale by trading periods def f(v): return (trading_periods * v.mean()) ** 0.5 # Apply the rolling window calculation to the Garman-Klass estimate result = rs.rolling(window=window, center=False).apply(func=f) # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot Garman-Klass volatility over time
garman_klass(data).plot()
def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using the Hodges-Tompkins model Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Hodges-Tompkins volatility """ # Calculate log returns of closing prices log_return = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log) # Compute the rolling standard deviation and scale by trading periods vol = log_return.rolling(window=window, center=False).std() * math.sqrt( trading_periods ) # Calculate adjustment factors based on window size and sample size h = window n = (log_return.count() - h) + 1 adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2))) # Apply the adjustment factor to the volatility estimate result = vol * adj_factor # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot Hodges-Tompkins volatility over time
hodges_tompkins(data).plot()
def rogers_satchell(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using the Rogers-Satchell model Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Rogers-Satchell volatility """ # Calculate log returns of high/open, low/open, and close/open prices log_ho = (price_data["High"] / price_data["Open"]).apply(np.log) log_lo = (price_data["Low"] / price_data["Open"]).apply(np.log) log_co = (price_data["Close"] / price_data["Open"]).apply(np.log) # Compute the Rogers-Satchell volatility estimate rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co) # Define a function to apply the rolling mean and scale by trading periods def f(v): return (trading_periods * v.mean()) ** 0.5 # Apply the rolling window calculation to the Rogers-Satchell estimate result = rs.rolling(window=window, center=False).apply(func=f) # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot Rogers-Satchell volatility over time
rogers_satchell(data).plot()
def yang_zhang(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using the Yang-Zhang model Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Yang-Zhang volatility """ # Calculate log returns of high/open, low/open, close/open, open/close, and close/close prices log_ho = (price_data["High"] / price_data["Open"]).apply(np.log) log_lo = (price_data["Low"] / price_data["Open"]).apply(np.log) log_co = (price_data["Close"] / price_data["Open"]).apply(np.log) log_oc = (price_data["Open"] / price_data["Close"].shift(1)).apply(np.log) log_oc_sq = log_oc**2 log_cc = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log) log_cc_sq = log_cc**2 # Compute the Rogers-Satchell volatility estimate rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co) # Compute close-to-close and open-to-close volatilities close_vol = log_cc_sq.rolling(window=window, center=False).sum() * ( 1.0 / (window - 1.0) ) open_vol = log_oc_sq.rolling(window=window, center=False).sum() * ( 1.0 / (window - 1.0) ) window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0)) # Calculate the weighting factor 'k' k = 0.34 / (1.34 + (window + 1) / (window - 1)) # Compute the final Yang-Zhang volatility estimate result = (open_vol + k * close_vol + (1 - k) * window_rs).apply( np.sqrt ) * math.sqrt(trading_periods) # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot Yang-Zhang volatility over time
yang_zhang(data).plot()
def standard_deviation(price_data, window=30, trading_periods=252, clean=True): """Calculate volatility using standard deviation of log returns Parameters ---------- price_data : DataFrame Historical stock price data window : int, optional Rolling window size in days (default is 30) trading_periods : int, optional Number of trading periods per year (default is 252) clean : bool, optional Whether to drop NaN values in the result (default is True) Returns ------- Series Standard deviation volatility """ # Calculate log returns of closing prices log_return = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log) # Compute the rolling standard deviation and scale by trading periods result = log_return.rolling(window=window, center=False).std() * math.sqrt( trading_periods ) # Return the cleaned result or the full result based on the 'clean' parameter if clean: return result.dropna() else: return result
Plot standard deviation volatility over time
standard_deviation(data).plot()
PyQuant News is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to get started with Python for quant finance. For educational purposes. Not investment advise. Use at your own risk.
