别再只用收盘价了!用Python实战对比Parkinson、Garman-Klass等三种高阶波动率算法(附完整代码)
高阶波动率算法实战:Parkinson、Garman-Klass与Rogers-Satchell的Python实现与对比
在量化交易和金融风险管理中,波动率是最核心的指标之一。传统的收盘价波动率(Close-to-Close)虽然计算简单,但它忽略了日内价格变动信息,可能导致对市场真实波动性的低估。本文将带你深入探索三种利用日内价格数据的高阶波动率算法——Parkinson、Garman-Klass和Rogers-Satchell,通过Python实战对比它们的表现差异。
1. 为什么需要高阶波动率算法?
金融市场的价格变动并非只在收盘时发生。日内价格的高点和低点往往蕴含着重要的市场情绪信息。以2020年3月美股熔断期间为例,标普500指数单日振幅经常超过5%,但收盘价变动可能只有2-3%。如果仅依赖收盘价计算波动率,会严重低估市场实际风险。
三种高阶算法的核心思想都是利用OHLC(开盘-最高-最低-收盘)数据中的更多信息:
- Parkinson波动率:专注于日内价格区间(最高价与最低价之比)
- Garman-Klass波动率:在Parkinson基础上加入开盘价与收盘价信息
- Rogers-Satchell波动率:进一步考虑价格路径不对称性的影响
# 示例:传统收盘价波动率 vs 高阶波动率 import numpy as np def close_to_close_volatility(returns, window=20, trading_days=252): return returns.rolling(window).std() * np.sqrt(trading_days)2. 算法原理与Python实现
2.1 Parkinson波动率:捕捉日内价格区间
Parkinson(1980)提出的波动率估计方法基于一个简单而深刻的观察:日内价格区间(最高价-最低价)包含了比单一收盘价更多的波动信息。其公式核心是对数价格区间的平方和:
$$ \hat{\sigma}{parkinson} = \sqrt{\frac{1}{4N\ln2}\sum{i=1}^{N}\left(\ln\frac{H_i}{L_i}\right)^2} $$
实现要点:
- 对最高价/最低价比值取自然对数
- 乘以1/(4ln2)的系数进行标准化
- 年化处理时乘以√252(假设252个交易日)
import numpy as np def parkinson_volatility(data, high_col, low_col, window=20, trading_days=252): """ 计算Parkinson波动率 参数: data: 包含OHLC数据的DataFrame high_col: 最高价列名 low_col: 最低价列名 window: 滚动窗口大小 trading_days: 年化交易天数(默认252) 返回: 波动率序列 """ log_hl = np.log(data[high_col] / data[low_col]) rs = (1.0 / (4.0 * np.log(2))) * log_hl**2 volatility = rs.rolling(window).mean().apply(lambda x: np.sqrt(x * trading_days)) return volatility提示:Parkinson估计量对价格极值特别敏感,在流动性较差的市场中可能高估实际波动率。
2.2 Garman-Klass波动率:整合更多价格信息
Garman和Klass(1980)扩展了Parkinson的方法,引入开盘价和收盘价信息,旨在提供更精确的波动率估计。其公式包含两个部分:
$$ \hat{\sigma}_{gk} = \sqrt{\frac{1}{2N}\left[\sum\left(\ln\frac{H_i}{L_i}\right)^2 - 2(2\ln2-1)\sum\left(\ln\frac{C_i}{O_i}\right)^2\right]} $$
算法优势:
- 同时利用价格区间和收盘变动信息
- 理论效率是收盘价波动率的7倍左右
- 对跳跃性波动有更好的捕捉能力
def garman_klass_volatility(data, high_col, low_col, open_col, close_col, window=20, trading_days=252): """ 计算Garman-Klass波动率 参数: data: 包含OHLC数据的DataFrame high_col: 最高价列名 low_col: 最低价列名 open_col: 开盘价列名 close_col: 收盘价列名 window: 滚动窗口大小 trading_days: 年化交易天数 返回: 波动率序列 """ log_hl = np.log(data[high_col] / data[low_col]) log_co = np.log(data[close_col] / data[open_col]) rs = 0.5 * log_hl**2 - (2*np.log(2)-1) * log_co**2 volatility = rs.rolling(window).mean().apply(lambda x: np.sqrt(x * trading_days)) return volatility2.3 Rogers-Satchell波动率:处理非对称价格路径
Rogers和Satchell(1991)进一步改进了波动率估计,特别考虑了价格路径可能存在的非对称性。这在趋势性市场中尤为重要:
$$ \hat{\sigma}_{rs} = \sqrt{\frac{1}{N}\sum\left[\ln\left(\frac{H_i}{C_i}\right)\ln\left(\frac{H_i}{O_i}\right) + \ln\left(\frac{L_i}{C_i}\right)\ln\left(\frac{L_i}{O_i}\right)\right]} $$
适用场景:
- 市场存在明显趋势时
- 开盘跳空频繁的情况
- 对涨跌不对称性敏感的策略
def rogers_satchell_volatility(data, high_col, low_col, open_col, close_col, window=20, trading_days=252): """ 计算Rogers-Satchell波动率 参数: data: 包含OHLC数据的DataFrame high_col: 最高价列名 low_col: 最低价列名 open_col: 开盘价列名 close_col: 收盘价列名 window: 滚动窗口大小 trading_days: 年化交易天数 返回: 波动率序列 """ log_hc = np.log(data[high_col] / data[close_col]) log_ho = np.log(data[high_col] / data[open_col]) log_lc = np.log(data[low_col] / data[close_col]) log_lo = np.log(data[low_col] / data[open_col]) rs = log_hc * log_ho + log_lc * log_lo volatility = rs.rolling(window).mean().apply(lambda x: np.sqrt(x * trading_days)) return volatility3. 实战对比:以沪深300指数为例
让我们用实际数据对比这三种算法的表现。我们使用tushare获取沪深300指数的日线数据:
import tushare as ts import matplotlib.pyplot as plt # 获取沪深300指数数据 df = ts.get_k_data('hs300', start='2018-01-01', end='2023-12-31') df = df.set_index('date') # 计算各波动率 df['parkinson'] = parkinson_volatility(df, 'high', 'low', window=20) df['garman_klass'] = garman_klass_volatility(df, 'high', 'low', 'open', 'close', window=20) df['rogers_satchell'] = rogers_satchell_volatility(df, 'high', 'low', 'open', 'close', window=20) df['close_to_close'] = close_to_close_volatility(df['close'].pct_change(), window=20) # 绘制对比图 plt.figure(figsize=(12, 6)) plt.plot(df.index, df['parkinson'], label='Parkinson') plt.plot(df.index, df['garman_klass'], label='Garman-Klass') plt.plot(df.index, df['rogers_satchell'], label='Rogers-Satchell') plt.plot(df.index, df['close_to_close'], label='Close-to-Close') plt.title('HS300 Volatility Comparison (20-day window)') plt.legend() plt.grid() plt.show()从实证结果可以看到几个关键现象:
- 高阶算法普遍高于传统波动率:三种方法估计的波动率通常比收盘价波动率高20-30%,说明传统方法确实存在低估
- 市场极端时期的差异:在2020年3月市场剧烈波动期间,Parkinson估计值最高,反映出它对价格区间的敏感性
- 平稳期的收敛:在市场平稳时期,三种估计值差异缩小
4. 算法选择与策略应用指南
不同的波动率算法适用于不同的交易场景,下面通过对比表格总结关键特性:
| 特性 | Parkinson | Garman-Klass | Rogers-Satchell | Close-to-Close |
|---|---|---|---|---|
| 使用数据 | H-L | O-H-L-C | O-H-L-C | C |
| 计算复杂度 | 低 | 中 | 中 | 低 |
| 跳跃敏感性 | 高 | 中 | 中 | 低 |
| 趋势市场适应性 | 一般 | 较好 | 优秀 | 差 |
| 效率(相对传统方法) | 5.2倍 | 7.4倍 | 6.5倍 | 1倍 |
策略应用建议:
- 高频与日内策略:优先考虑Parkinson或Garman-Klass,因其对日内波动敏感
- 趋势跟踪系统:Rogers-Satchell能更好捕捉趋势中的波动不对称性
- 期权定价:Garman-Klass通常能提供更稳定的波动率估计
- 风险控制:在极端市场条件下,Parkinson可能提供更及时的风险信号
# 波动率策略信号生成示例 def volatility_breakout_signal(data, volatility_type='garman_klass', window=20, multiplier=1.5): """ 波动率突破策略信号生成 参数: data: 包含价格和波动率的数据 volatility_type: 使用的波动率类型 window: 波动率计算窗口 multiplier: 波动率乘数 返回: 交易信号(1:做多, -1:做空, 0:保持) """ volatility = data[volatility_type] atr = multiplier * volatility signals = pd.Series(0, index=data.index) signals[data['close'] > data['close'].shift(1) + atr] = 1 signals[data['close'] < data['close'].shift(1) - atr] = -1 return signals注意:实际应用中应考虑交易成本、滑点等因素,简单的波动率突破策略可能需要进一步过滤才能获得稳定收益。
5. 高级话题与优化方向
对于希望进一步优化波动率模型的开发者,可以考虑以下方向:
混合波动率模型:
def hybrid_volatility(data, weights=[0.3, 0.4, 0.3]): """ 混合波动率估计 参数: weights: 对[Parkinson, Garman-Klass, Rogers-Satchell]的权重 """ parkinson = parkinson_volatility(data, 'high', 'low') gk = garman_klass_volatility(data, 'high', 'low', 'open', 'close') rs = rogers_satchell_volatility(data, 'high', 'low', 'open', 'close') return weights[0]*parkinson + weights[1]*gk + weights[2]*rs波动率曲面建模:对不同时间窗口的波动率进行三维可视化,观察波动率的期限结构
机器学习增强:使用LSTM等模型学习波动率的非线性特征,结合传统算法
市场状态识别:根据波动率特征自动识别市场状态(平静、波动、极端)
# 市场状态识别示例 def market_state(volatility, thresholds=[0.15, 0.25]): """ 基于波动率划分市场状态 参数: thresholds: [平静,波动]的阈值 返回: 状态标签(0:平静, 1:波动, 2:极端) """ states = pd.Series(1, index=volatility.index) states[volatility < thresholds[0]] = 0 states[volatility > thresholds[1]] = 2 return states在实际项目中,我发现Garman-Klass通常在大多数市场环境下提供最平衡的估计,而Parkinson在需要快速响应市场波动变化时表现更好。Rogers-Satchell则在趋势明显的市场中展现出独特价值,特别是在识别波动率聚集现象时。
