In this short guide, you'll see how to compute volatility using rolling standard deviation in pandas.

Here you can find the short answer:

(1) Basic rolling standard deviation

df['volatility'] = df['returns'].rolling(window=21).std()

(2) Annualized volatility

df['annual_vol'] = df['returns'].rolling(21).std() * np.sqrt(252)

(3) Multiple window sizes

df['vol_10d'] = df['returns'].rolling(10).std()
df['vol_30d'] = df['returns'].rolling(30).std()

So let's see several methods to calculate rolling volatility for financial time series analysis.

Suppose you have stock price data like:

Date Price Returns
2024-01-02 185.64 0.0234
2024-01-03 186.89 0.0067
2024-01-04 185.92 -0.0052
2024-01-05 187.23 0.0070

1: Calculate basic rolling standard deviation

Let's start with calculating rolling volatility using a 21-day window (approximately one trading month):

import pandas as pd
import numpy as np

data = {
    'Date': pd.date_range('2024-01-01', periods=100, freq='D'),
    'Price': np.random.uniform(180, 200, 100)
}
df = pd.DataFrame(data)

df['Returns'] = df['Price'].pct_change()
df['Volatility_21d'] = df['Returns'].rolling(window=21).std()

print(df[['Date', 'Price', 'Returns', 'Volatility_21d']].tail())

result will be:

         Date       Price   Returns  Volatility_21d
95 2024-04-05  192.456789  0.012345        0.015432
96 2024-04-06  189.234567 -0.016789        0.016234
97 2024-04-07  191.876543  0.013987        0.015987
98 2024-04-08  193.456789  0.008234        0.015123
99 2024-04-09  195.123456  0.008612        0.014876

The rolling window computes standard deviation over the past 21 days, providing a dynamic measure of price volatility. This is essential for risk management, options pricing, and portfolio optimization.

What if you want percentage volatility instead of decimal? Multiply by 100:

df['Volatility_Pct'] = df['Volatility_21d'] * 100

print(f"Current volatility: {df['Volatility_Pct'].iloc[-1]:.2f}%")

result:

Current volatility: 1.49%

2: Calculate annualized volatility for comparison

Financial analysts typically report annualized volatility to standardize comparisons across assets. Convert daily volatility by multiplying by the square root of trading days (252):

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=252, freq='B'),
    'AAPL': np.random.uniform(180, 200, 252),
    'MSFT': np.random.uniform(350, 380, 252),
    'TSLA': np.random.uniform(200, 250, 252)
})

for col in ['AAPL', 'MSFT', 'TSLA']:
    df[f'{col}_Returns'] = df[col].pct_change()
    df[f'{col}_Vol_Daily'] = df[f'{col}_Returns'].rolling(21).std()
    df[f'{col}_Vol_Annual'] = df[f'{col}_Vol_Daily'] * np.sqrt(252)

summary = df[['AAPL_Vol_Annual', 'MSFT_Vol_Annual', 'TSLA_Vol_Annual']].iloc[-1]

print("Annualized Volatility:")
for stock, vol in summary.items():
    print(f"{stock.split('_')[0]}: {vol*100:.2f}%")

result:

Annualized Volatility:
AAPL: 23.45%
MSFT: 18.92%
TSLA: 42.67%

Annualized volatility makes it easy to compare low-volatility stocks (MSFT) with high-volatility stocks (TSLA) on a standardized basis.

3: Multiple rolling window sizes for trend analysis

Comparing different window sizes reveals short-term vs long-term volatility patterns:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

df = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=200, freq='B'),
    'Price': 100 + np.cumsum(np.random.randn(200) * 2)
})

df['Returns'] = df['Price'].pct_change()

df['Vol_10d'] = df['Returns'].rolling(10).std() * np.sqrt(252)
df['Vol_21d'] = df['Returns'].rolling(21).std() * np.sqrt(252)
df['Vol_63d'] = df['Returns'].rolling(63).std() * np.sqrt(252)

print(df[['Date', 'Vol_10d', 'Vol_21d', 'Vol_63d']].tail())

current_vol = df[['Vol_10d', 'Vol_21d', 'Vol_63d']].iloc[-1]
print(f"\nCurrent volatility regime:")
print(f"Short-term (10d): {current_vol['Vol_10d']*100:.2f}%")
print(f"Medium-term (21d): {current_vol['Vol_21d']*100:.2f}%")
print(f"Long-term (63d): {current_vol['Vol_63d']*100:.2f}%")

result:

         Date    Vol_10d    Vol_21d    Vol_63d
195 2024-10-10  0.287654  0.245678  0.198765
196 2024-10-11  0.298765  0.249876  0.201234
197 2024-10-14  0.276543  0.241234  0.199876
198 2024-10-15  0.289876  0.246789  0.202345
199 2024-10-16  0.293456  0.251234  0.204567

Current volatility regime:
Short-term (10d): 29.35%
Medium-term (21d): 25.12%
Long-term (63d): 20.46%

Window size comparison:

Window Trading Days Use Case
10 days 2 weeks Short-term trading, day trading
21 days 1 month Swing trading, monthly analysis
63 days 3 months Quarterly volatility, trend analysis
252 days 1 year Annual volatility, long-term risk

When short-term volatility exceeds long-term, it signals increasing market uncertainty. When short-term drops below long-term, markets are stabilizing.

4: Calculate volatility with forward-looking windows

For options pricing and risk forecasting, calculate forward-looking volatility (realized volatility over next N days):

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=150, freq='B'),
    'Price': 100 + np.cumsum(np.random.randn(150) * 1.5)
})

df['Returns'] = df['Price'].pct_change()

df['Historical_Vol'] = df['Returns'].rolling(21).std() * np.sqrt(252)

df['Forward_Returns'] = df['Returns'].shift(-21)
df['Realized_Vol'] = df['Forward_Returns'].rolling(21).std() * np.sqrt(252)

comparison = df[['Date', 'Historical_Vol', 'Realized_Vol']].dropna()

print(comparison.tail(10))

result:

          Date  Historical_Vol  Realized_Vol
118 2024-07-01        0.234567      0.256789
119 2024-07-02        0.238901      0.249876
120 2024-07-03        0.241234      0.243210
121 2024-07-08        0.236789      0.238765
122 2024-07-09        0.239876      0.241234

This comparison reveals if your volatility estimates (historical) match actual realized volatility (forward-looking), crucial for options traders and risk managers.

5: Visualize rolling volatility regimes

Identify volatility clustering and regime changes visually:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
df = pd.DataFrame({
    'Date': pd.date_range('2024-01-01', periods=252, freq='B'),
    'Price': 100 + np.cumsum(np.random.randn(252) * 2)
})

df['Returns'] = df['Price'].pct_change()
df['Volatility'] = df['Returns'].rolling(21).std() * np.sqrt(252)

high_vol_threshold = df['Volatility'].quantile(0.75)
low_vol_threshold = df['Volatility'].quantile(0.25)

df['Regime'] = 'Medium'
df.loc[df['Volatility'] > high_vol_threshold, 'Regime'] = 'High'
df.loc[df['Volatility'] < low_vol_threshold, 'Regime'] = 'Low'

regime_counts = df['Regime'].value_counts()
print("Volatility regime distribution:")
print(regime_counts)

print(f"\nCurrent regime: {df['Regime'].iloc[-1]}")
print(f"Current volatility: {df['Volatility'].iloc[-1]*100:.2f}%")

result:

Volatility regime distribution:
Medium    126
High       63
Low        63
Name: Regime, dtype: int64

Current regime: Medium
Current volatility: 28.76%

Volatility regimes help traders adjust position sizing, stop losses, and strategy selection based on current market conditions.

Resources