Files
strategy-lab/to_explore/pyquantnews/2_RealizedVolatilityModels.ipynb
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

16 KiB

No description has been provided for this image

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.

In [ ]:
import math
In [ ]:
import numpy as np
import yfinance as yf

Download historical stock data for Apple Inc. (AAPL) from Yahoo Finance

In [ ]:
data = yf.download("AAPL", start="2017-01-01", end="2022-06-30")

Display the downloaded stock data

In [ ]:
data

Calculate daily returns from adjusted closing prices

In [ ]:
returns = (data["Adj Close"] / data["Adj Close"].shift(1)) - 1

Plot the adjusted closing prices over time

In [ ]:
data["Adj Close"].plot()
In [ ]:
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

In [ ]:
parkinson(data).plot()
In [ ]:
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

In [ ]:
garman_klass(data).plot()
In [ ]:
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

In [ ]:
hodges_tompkins(data).plot()
In [ ]:
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

In [ ]:
rogers_satchell(data).plot()
In [ ]:
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

In [ ]:
yang_zhang(data).plot()
In [ ]:
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

In [ ]:
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.