4、波动率计算:使用Python计算历史波动率、滚动窗口波动率、GARCH模型简介

波动率,说白了就是市场波动的剧烈程度。在碳市场里,这东西比股票市场更刺激——政策一出,价格能瞬间跳几个百分点。我做了这么多年量化,见过太多人只盯着价格涨跌,却忽略了波动率这个核心变量。今天咱们就把它彻底讲透。

4.1 历史波动率:最朴素的度量

历史波动率是最基础的方法。它的逻辑很简单:用过去一段时间的收益率标准差,来估计未来的波动程度。嗯,这里要注意——它假设历史会重演,但碳市场往往不按套路出牌。

计算步骤其实就三步:

  1. 取对数收益率ln(P_t / P_{t-1})
  2. 计算标准差:对收益率序列求样本标准差
  3. 年化处理:乘以 sqrt(252)(交易日数量)

我在项目中遇到过一个问题:直接用收盘价算收益率,结果发现隔夜跳空特别大。后来我改用开盘价和收盘价的中间价,效果好了不少。你想想看,碳配额交易有时候一天都没几笔成交,收盘价可能失真。

核心公式

历史波动率 = σ × √252

其中 σ 是日收益率的标准差,252 是年化交易日数。

import numpy as np
import pandas as pd

def historical_volatility(prices, window=None):
    """
    计算历史波动率
    prices: 价格序列
    window: 如果为None,使用全部数据
    """
    log_returns = np.log(prices / prices.shift(1)).dropna()
    
    if window is None:
        sigma = log_returns.std()
    else:
        sigma = log_returns.tail(window).std()
    
    # 年化
    hv = sigma * np.sqrt(252)
    return hv

# 示例:计算碳配额EUA的历史波动率
eua_prices = pd.Series([...])  # 你的数据
hv = historical_volatility(eua_prices)
print(f"历史波动率: {hv:.2%}")

我的经验:碳市场的数据频率很关键。日度数据够用,但如果你想做高频策略,得用分钟级数据。不过要注意,碳市场的流动性不如股票,分钟级数据里全是噪音。

4.2 滚动窗口波动率:动态捕捉变化

历史波动率有个硬伤:它把所有历史数据一视同仁。但碳市场的波动率是时变的——政策密集期波动大,平淡期波动小。滚动窗口波动率就是为了解决这个问题。

说白了,就是用一个固定大小的窗口,在时间轴上滑动,每滑动一次就计算一次波动率。窗口大小怎么选?我个人习惯用20个交易日(约一个月),既能捕捉短期变化,又不会太敏感。

def rolling_volatility(prices, window=20):
    """
    计算滚动窗口波动率
    window: 滚动窗口大小(交易日数)
    """
    log_returns = np.log(prices / prices.shift(1)).dropna()
    rolling_std = log_returns.rolling(window=window).std()
    rolling_vol = rolling_std * np.sqrt(252)
    return rolling_vol

# 计算并可视化
rolling_vol = rolling_volatility(eua_prices, window=20)
print(f"最新滚动波动率: {rolling_vol.iloc[-1]:.2%}")

避坑指南:我曾经用30天窗口做回测,结果发现波动率曲线太平滑,错过了几次大的波动事件。后来改成20天窗口,效果好了很多。窗口太小也不行——10天窗口噪音太大,容易误判。建议多试几个窗口,找到最适合你策略的那个。

4.3 GARCH模型简介:让波动率自己说话

滚动窗口波动率虽然动态了,但它还是基于历史数据。GARCH模型更聪明——它认为波动率本身也有规律可循,比如大波动之后往往跟着大波动(波动率聚集效应)。

GARCH(1,1)是最常用的形式,公式长这样:

σ²_t = ω + α × ε²_{t-1} + β × σ²_{t-1}

其中:

  • ω:长期平均波动率
  • α:新信息的影响权重(市场冲击)
  • β:旧波动率的衰减速度
  • α + β:波动率持续性(越接近1,波动越持久)

你想想看,碳市场的波动率持续性通常很高——因为政策影响是长期的。我见过α+β超过0.98的情况,这意味着一次冲击的影响会持续很久。

from arch import arch_model

def garch_volatility(prices, p=1, q=1):
    """
    使用GARCH模型估计波动率
    p: ARCH项阶数
    q: GARCH项阶数
    """
    log_returns = np.log(prices / prices.shift(1)).dropna() * 100
    
    # 拟合GARCH模型
    model = arch_model(log_returns, vol='Garch', p=p, q=q)
    results = model.fit(disp='off')
    
    # 获取条件波动率
    conditional_vol = results.conditional_volatility / 100  # 还原为小数
    annualized_vol = conditional_vol * np.sqrt(252)
    
    return annualized_vol, results

# 拟合GARCH(1,1)
garch_vol, garch_results = garch_volatility(eua_prices)
print(f"GARCH参数: ω={garch_results.params['omega']:.6f}, "
      f"α={garch_results.params['alpha[1]']:.4f}, "
      f"β={garch_results.params['beta[1]']:.4f}")
print(f"最新GARCH波动率: {garch_vol.iloc[-1]:.2%}")

三种方法对比

方法 优点 缺点 适用场景
历史波动率 简单、直观 假设波动率恒定 长期趋势判断
滚动窗口波动率 动态、易实现 窗口选择主观 中短期交易
GARCH模型 捕捉波动率聚集 参数估计复杂 高频/事件驱动策略

4.4 实战:碳市场波动率计算全流程

光说不练假把式。咱们用真实数据走一遍完整流程。我记得有一次做碳配额套利策略,就是用这套流程找到了波动率异常点,赚了一波。

# 完整流程示例
import yfinance as yf  # 假设数据源
import matplotlib.pyplot as plt

# 1. 获取数据(以EUA期货为例)
# 实际中你需要从ICE或碳交易所获取数据
data = pd.read_csv('eua_futures.csv', index_col=0, parse_dates=True)
prices = data['Close']

# 2. 计算三种波动率
hv = historical_volatility(prices)
rv = rolling_volatility(prices, window=20)
gv, _ = garch_volatility(prices)

# 3. 可视化对比
plt.figure(figsize=(12, 6))
plt.plot(rv.index, rv, label='滚动波动率(20天)', alpha=0.7)
plt.plot(gv.index, gv, label='GARCH波动率', alpha=0.7)
plt.axhline(y=hv, color='r', linestyle='--', label=f'历史波动率({hv:.2%})')
plt.legend()
plt.title('碳市场EUA波动率对比')
plt.show()

# 4. 输出最新值
print(f"历史波动率: {hv:.2%}")
print(f"滚动波动率: {rv.iloc[-1]:.2%}")
print(f"GARCH波动率: {gv.iloc[-1]:.2%}")

我的建议:别只依赖一种方法。我通常把三种波动率都算出来,如果它们方向一致,说明信号可靠;如果分歧很大,就要警惕了——可能是市场结构在变化。

4.5 知识体系总览

下面这张图帮你理清本章的核心逻辑。波动率计算不是终点,而是策略的起点。

碳市场波动率计算知识体系 价格序列输入 历史波动率 全样本标准差 滚动窗口波动率 滑动窗口标准差 GARCH模型 条件异方差 波动率序列输出 策略应用:波动率交易、风险对冲、期权定价

三种方法各有千秋。历史波动率适合做基准,滚动窗口波动率适合做信号,GARCH模型适合做预测。我个人习惯把它们结合起来用——就像用三把尺子量同一块布,量出来的结果越一致,越可信。

最后提醒:碳市场的波动率计算有个特殊之处——非交易日处理。碳市场有拍卖日和非拍卖日,波动率模式完全不同。我曾经因为没处理这个,回测结果漂亮得离谱,实盘直接打脸。记得把拍卖日的波动单独建模。

专注资料整理