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.