{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c599b573",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88633890",
   "metadata": {},
   "source": [
    "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."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1d67415",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2a29969",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import yfinance as yf"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8da6c98e",
   "metadata": {},
   "source": [
    "Download historical stock data for Apple Inc. (AAPL) from Yahoo Finance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f2be4ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = yf.download(\"AAPL\", start=\"2017-01-01\", end=\"2022-06-30\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b8097d1",
   "metadata": {},
   "source": [
    "Display the downloaded stock data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c28f735",
   "metadata": {},
   "outputs": [],
   "source": [
    "data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3677bc76",
   "metadata": {},
   "source": [
    "Calculate daily returns from adjusted closing prices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0713eb09",
   "metadata": {},
   "outputs": [],
   "source": [
    "returns = (data[\"Adj Close\"] / data[\"Adj Close\"].shift(1)) - 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03de5a56",
   "metadata": {},
   "source": [
    "Plot the adjusted closing prices over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "615a1ff9",
   "metadata": {},
   "outputs": [],
   "source": [
    "data[\"Adj Close\"].plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "297e9737",
   "metadata": {},
   "outputs": [],
   "source": [
    "def parkinson(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using the Parkinson model\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Parkinson volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate the Parkinson volatility estimate using high and low prices\n",
    "    rs = (1.0 / (4.0 * math.log(2.0))) * (\n",
    "        (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
    "    ) ** 2.0\n",
    "\n",
    "    # Define a function to apply the rolling mean and scale by trading periods\n",
    "    def f(v):\n",
    "        return (trading_periods * v.mean()) ** 0.5\n",
    "\n",
    "    # Apply the rolling window calculation to the Parkinson estimate\n",
    "    result = rs.rolling(window=window, center=False).apply(func=f)\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60f29474",
   "metadata": {},
   "source": [
    "Plot Parkinson volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31a319a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "parkinson(data).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4ac1610e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def garman_klass(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using the Garman-Klass model\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Garman-Klass volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate log returns of high/low and close/open prices\n",
    "    log_hl = (price_data[\"High\"] / price_data[\"Low\"]).apply(np.log)\n",
    "    log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
    "\n",
    "    # Compute the Garman-Klass volatility estimate\n",
    "    rs = 0.5 * log_hl**2 - (2 * math.log(2) - 1) * log_co**2\n",
    "\n",
    "    # Define a function to apply the rolling mean and scale by trading periods\n",
    "    def f(v):\n",
    "        return (trading_periods * v.mean()) ** 0.5\n",
    "\n",
    "    # Apply the rolling window calculation to the Garman-Klass estimate\n",
    "    result = rs.rolling(window=window, center=False).apply(func=f)\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01168fcc",
   "metadata": {},
   "source": [
    "Plot Garman-Klass volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88a8d0d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "garman_klass(data).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2011bdea",
   "metadata": {},
   "outputs": [],
   "source": [
    "def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using the Hodges-Tompkins model\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Hodges-Tompkins volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate log returns of closing prices\n",
    "    log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
    "\n",
    "    # Compute the rolling standard deviation and scale by trading periods\n",
    "    vol = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
    "        trading_periods\n",
    "    )\n",
    "\n",
    "    # Calculate adjustment factors based on window size and sample size\n",
    "    h = window\n",
    "    n = (log_return.count() - h) + 1\n",
    "\n",
    "    adj_factor = 1.0 / (1.0 - (h / n) + ((h**2 - 1) / (3 * n**2)))\n",
    "\n",
    "    # Apply the adjustment factor to the volatility estimate\n",
    "    result = vol * adj_factor\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4349cdc",
   "metadata": {},
   "source": [
    "Plot Hodges-Tompkins volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4824347",
   "metadata": {},
   "outputs": [],
   "source": [
    "hodges_tompkins(data).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c6305c6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def rogers_satchell(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using the Rogers-Satchell model\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Rogers-Satchell volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate log returns of high/open, low/open, and close/open prices\n",
    "    log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
    "    log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
    "    log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
    "\n",
    "    # Compute the Rogers-Satchell volatility estimate\n",
    "    rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
    "\n",
    "    # Define a function to apply the rolling mean and scale by trading periods\n",
    "    def f(v):\n",
    "        return (trading_periods * v.mean()) ** 0.5\n",
    "\n",
    "    # Apply the rolling window calculation to the Rogers-Satchell estimate\n",
    "    result = rs.rolling(window=window, center=False).apply(func=f)\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bea6c84e",
   "metadata": {},
   "source": [
    "Plot Rogers-Satchell volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2887f9a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "rogers_satchell(data).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06167183",
   "metadata": {},
   "outputs": [],
   "source": [
    "def yang_zhang(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using the Yang-Zhang model\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Yang-Zhang volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate log returns of high/open, low/open, close/open, open/close, and close/close prices\n",
    "    log_ho = (price_data[\"High\"] / price_data[\"Open\"]).apply(np.log)\n",
    "    log_lo = (price_data[\"Low\"] / price_data[\"Open\"]).apply(np.log)\n",
    "    log_co = (price_data[\"Close\"] / price_data[\"Open\"]).apply(np.log)\n",
    "\n",
    "    log_oc = (price_data[\"Open\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
    "    log_oc_sq = log_oc**2\n",
    "\n",
    "    log_cc = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
    "    log_cc_sq = log_cc**2\n",
    "\n",
    "    # Compute the Rogers-Satchell volatility estimate\n",
    "    rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)\n",
    "\n",
    "    # Compute close-to-close and open-to-close volatilities\n",
    "    close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (\n",
    "        1.0 / (window - 1.0)\n",
    "    )\n",
    "    open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (\n",
    "        1.0 / (window - 1.0)\n",
    "    )\n",
    "    window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))\n",
    "\n",
    "    # Calculate the weighting factor 'k'\n",
    "    k = 0.34 / (1.34 + (window + 1) / (window - 1))\n",
    "\n",
    "    # Compute the final Yang-Zhang volatility estimate\n",
    "    result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(\n",
    "        np.sqrt\n",
    "    ) * math.sqrt(trading_periods)\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71ed8302",
   "metadata": {},
   "source": [
    "Plot Yang-Zhang volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40717ed4",
   "metadata": {},
   "outputs": [],
   "source": [
    "yang_zhang(data).plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e64bbc96",
   "metadata": {},
   "outputs": [],
   "source": [
    "def standard_deviation(price_data, window=30, trading_periods=252, clean=True):\n",
    "    \"\"\"Calculate volatility using standard deviation of log returns\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    price_data : DataFrame\n",
    "        Historical stock price data\n",
    "    window : int, optional\n",
    "        Rolling window size in days (default is 30)\n",
    "    trading_periods : int, optional\n",
    "        Number of trading periods per year (default is 252)\n",
    "    clean : bool, optional\n",
    "        Whether to drop NaN values in the result (default is True)\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Series\n",
    "        Standard deviation volatility\n",
    "    \"\"\"\n",
    "\n",
    "    # Calculate log returns of closing prices\n",
    "    log_return = (price_data[\"Close\"] / price_data[\"Close\"].shift(1)).apply(np.log)\n",
    "\n",
    "    # Compute the rolling standard deviation and scale by trading periods\n",
    "    result = log_return.rolling(window=window, center=False).std() * math.sqrt(\n",
    "        trading_periods\n",
    "    )\n",
    "\n",
    "    # Return the cleaned result or the full result based on the 'clean' parameter\n",
    "    if clean:\n",
    "        return result.dropna()\n",
    "    else:\n",
    "        return result"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86c10b8e",
   "metadata": {},
   "source": [
    "Plot standard deviation volatility over time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45c9f814",
   "metadata": {},
   "outputs": [],
   "source": [
    "standard_deviation(data).plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "243a9283",
   "metadata": {},
   "source": [
    "<a href=\"https://pyquantnews.com/\">PyQuant News</a> 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 <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
