How To Compute Volatility 6 Ways Most People Don’t Know

July 22, 2022
Facebook logo.
Twitter logo.
LinkedIn logo.
Get this code in Google Colab

How To Compute Volatility 6 Ways Most People Don’t Know

How To Compute Volatility 6 Ways Most People Don’t Know

In today’s issue, I’m going to show you 6 ways to compute statistical volatility in Python.

The first way you've probably heard of. The other 5 may be new to you.

Statistical volatility (also called historic or realized volatility) is a measurement of how much the price or returns of stock value. It’s used to optimize portfolios, detect regime changes, and price derivatives. The most common way to measure statistical volatility is the standard deviation.

Unfortunately, there are some downsides to using standard deviation that most people don’t consider.

Before I show you how to compute volatility in 6 different ways, let's get setup.

Jupyter Notebook

I recommend using Jupyter Notebook. Jupyter Notebook is free and can be installed with pip:

1pip install notebook

Stock Price Data

First, we’ll use the excellent yFinance library to grab some stock price data. We’ll also import the math and NumPy libraries.

1import math
2
3import numpy as np
4import yfinance as yf
5
6
7data = yf.download("AAPL", start="2017-01-01", end="2022-06-30")

One line of code and we have four years of AAPL data. What a great time to be alive.

A quick plot of the daily close shows us the price chart of Apple.

1data['Close'].plot()
How To Compute Volatility 5 Ways Most People Don’t Know. Stock Price Data

Standard Deviation

We'll start with the standard deviation. Standard deviation measures how widely returns are dispersed from the average return. It’s the most common (and biased) estimator of volatility.

1def standard_deviation(price_data, window=30, trading_periods=252, clean=True):
2
3    log_return = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log)
4
5    result = log_return.rolling(window=window, center=False).std() * math.sqrt(
6        trading_periods
7    )
8
9    if clean:
10        return result.dropna()
11    else:
12        return result

Let's plot it:

1standard_deviation(data).plot()
How To Compute Volatility 5 Ways Most People Don’t Know. Standard Deviation

Parkinson

Parkinson's volatility uses the stock’s high and low price of the day rather than just close to close prices. It’s useful to capture large price movements during the day.

1def parkinson(price_data, window=30, trading_periods=252, clean=True):
2
3    rs = (1.0 / (4.0 * math.log(2.0))) * (
4        (price_data["High"] / price_data["Low"]).apply(np.log)
5    ) ** 2.0
6
7    def f(v):
8        return (trading_periods * v.mean()) ** 0.5
9
10    result = rs.rolling(window=window, center=False).apply(func=f)
11
12    if clean:
13        return result.dropna()
14    else:
15        return result

We take the price data and compute a 30-day rolling volatility metric that we can plot.

parkinson(data).plot()

How To Compute Volatility 5 Ways Most People Don’t Know. Parkinson

Garman-Klass

Garman-Klass volatility extends Parkinson's volatility by taking into account the opening and closing price. As markets are most active during the opening and closing of a trading session, it makes volatility estimation more accurate.

1def garman_klass(price_data, window=30, trading_periods=252, clean=True):
2
3    log_hl = (price_data["High"] / price_data["Low"]).apply(np.log)
4    log_co = (price_data["Close"] / price_data["Open"]).apply(np.log)
5
6    rs = 0.5 * log_hl ** 2 - (2 * math.log(2) - 1) * log_co ** 2
7
8    def f(v):
9        return (trading_periods * v.mean()) ** 0.5
10
11    result = rs.rolling(window=window, center=False).apply(func=f)
12
13    if clean:
14        return result.dropna()
15    else:
16        return result

Let’s plot it:

1garman_klass(data).plot()
How To Compute Volatility 5 Ways Most People Don’t Know. Garman-Klass

Hodges-Tompkins

Hodges-Tompkins volatility is a bias correction for estimation using an overlapping data sample that produces unbiased estimates and a substantial gain in efficiency.

1def hodges_tompkins(price_data, window=30, trading_periods=252, clean=True):
2
3    log_return = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log)
4
5    vol = log_return.rolling(window=window, center=False).std() * math.sqrt(
6        trading_periods
7    )
8
9    h = window
10    n = (log_return.count() - h) + 1
11
12    adj_factor = 1.0 / (1.0 - (h / n) + ((h ** 2 - 1) / (3 * n ** 2)))
13
14    result = vol * adj_factor
15
16    if clean:
17        return result.dropna()
18    else:
19        return

Let’s plot it:

1hodges_tompkins(data).plot()

Rogers-Satchell

Rogers-Satchell is an estimator for measuring the volatility of securities with an average return not equal to zero. Unlike Parkinson and Garman-Klass estimators, Rogers-Satchell incorporates a drift term (mean return not equal to zero).

1def rogers_satchell(price_data, window=30, trading_periods=252, clean=True):
2
3    log_ho = (price_data["High"] / price_data["Open"]).apply(np.log)
4    log_lo = (price_data["Low"] / price_data["Open"]).apply(np.log)
5    log_co = (price_data["Close"] / price_data["Open"]).apply(np.log)
6
7    rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
8
9    def f(v):
10        return (trading_periods * v.mean()) ** 0.5
11
12    result = rs.rolling(window=window, center=False).apply(func=f)
13
14    if clean:
15        return result.dropna()
16    else:
17        return result

Let's plot it:

1rogers_satchell(data).plot()

Yang-Zhang

Yang-Zhang volatility is the combination of the overnight (close-to-open volatility), a weighted average of the Rogers-Satchell volatility and the day's open-to-close volatility.

1def yang_zhang(price_data, window=30, trading_periods=252, clean=True):
2
3    log_ho = (price_data["High"] / price_data["Open"]).apply(np.log)
4    log_lo = (price_data["Low"] / price_data["Open"]).apply(np.log)
5    log_co = (price_data["Close"] / price_data["Open"]).apply(np.log)
6
7    log_oc = (price_data["Open"] / price_data["Close"].shift(1)).apply(np.log)
8    log_oc_sq = log_oc ** 2
9
10    log_cc = (price_data["Close"] / price_data["Close"].shift(1)).apply(np.log)
11    log_cc_sq = log_cc ** 2
12
13    rs = log_ho * (log_ho - log_co) + log_lo * (log_lo - log_co)
14
15    close_vol = log_cc_sq.rolling(window=window, center=False).sum() * (
16        1.0 / (window - 1.0)
17    )
18    open_vol = log_oc_sq.rolling(window=window, center=False).sum() * (
19        1.0 / (window - 1.0)
20    )
21    window_rs = rs.rolling(window=window, center=False).sum() * (1.0 / (window - 1.0))
22
23    k = 0.34 / (1.34 + (window + 1) / (window - 1))
24    result = (open_vol + k * close_vol + (1 - k) * window_rs).apply(
25        np.sqrt
26    ) * math.sqrt(trading_periods)
27
28    if clean:
29        return result.dropna()
30    else:
31        return result

Let's plot it:

1yang_zhang(data).plot()

Which One Do I Use?

Each measure has tradeoffs you must consider. Most practitioners will choose the one they believe captures the information they think is relevant. In a future issue, I’ll show you how to build a simple trading strategy based on different volatility measures.

Man with glasses and a wristwatch, wearing a white shirt, looking thoughtfully at a laptop with a data screen in the background.