市场最危险的时刻,往往不是所有人都恐慌的时候——而是市场刚刚平静下来,所有人都以为最坏的情况已经过去的时候。

2020 年 3 月,新冠疫情冲击全球市场。标普 500 在 16 个交易日内下跌 34%,随后进入剧烈震荡期。2022 年美联储激进加息,科技股纳斯达克 100 在一年间跌幅超过 33%,期间多次出现"单日暴涨 5%,次日再跌 4%"的反复拉扯。这种模式——大波动后跟着大波动,平静期后也倾向于平静——在金融市场上反复出现,从比特币到黄金,从外汇到期权,无一例外。

这不是巧合,而是金融时间序列最顽固的统计规律之一:波动率聚集(Volatility Clustering)。

理解波动率聚集,不仅能解释"为什么市场总是一段时间很安静,一段时间又突然发疯",更重要的是——它为量化交易提供了一个可预测的结构。从 GARCH 模型的条件方差,到风险管理的 VaR 计算,再到期权定价中的波动率曲面构建,波动率聚集是现代量化金融的核心假设之一。

本文从统计学原理出发,系统拆解波动率聚集的本质、GARCH 模型的工程实现,以及如何在实际交易中应用这一规律。


一、波动率聚集:统计学视角下的市场"性格"

1.1 一个直观的现象

想象一张股票价格走势图。如果你只看价格本身,很难发现明显的规律——涨涨跌跌,看似随机。但如果把注意力从"价格"转向"波动的强度"(通常用收益率的绝对值或平方来近似),一个清晰的模式浮现出来:

某些时段,收益率的绝对值持续偏高——连续几天都在剧烈震荡。某些时段,收益率的绝对值持续偏低——连续几周小幅波动,几乎没有存在感。

这便是波动率聚集:$|\epsilon_t|$ 与 $|\epsilon_{t-1}|$、$|\epsilon_{t-2}|$ 之间存在正相关。用通俗的话说:大波动后面倾向于跟着大波动,小波动后面倾向于跟着小波动。

1.2 为什么传统的"随机游走"假设不够用

大多数金融学入门教材会介绍"有效市场假说"(EMH),其核心假设之一是:价格变动是随机游走,即今天的收益率与昨天的收益率无关(白噪声)。在这个框架下,市场的"性格"是恒定的——无论市场是平静还是疯狂,收益率的方差(波动率)都是常数。

但真实市场完全不是这样。市场有"情绪周期":

  • 平静期:波动率低,信息缓慢释放,投资者观望情绪浓厚
  • 冲击期:突发消息(财报、央行决策、地缘政治),波动率骤升
  • 消化期:波动率不会瞬间回落,而是缓慢衰减,可能持续数周
  • 回归平静:波动率逐渐收敛,回到低水平,直到下一次冲击

传统的时间序列模型(AR、MA、ARMA)假设方差是常数(同方差假设),无法捕捉这种"方差不恒定"的现象。统计学家将这种违反同方差假设的现象称为异方差(Heteroscedasticity),而专门用于建模异方差的方法论,始于 Robert Engle 在 1982 年的开创性工作。

1.3 从直观到统计检验:如何"看到"波动率聚集

观察图表是第一步,但量化分析需要统计检验。以下方法可以用来验证波动率聚集的存在:

方法一:收益率平方的自相关检验

如果存在波动率聚集,$\epsilon_t^2$(收益率平方)应该在多个滞后阶数上表现出显著的自相关。这是因为:高波动日的 $|\epsilon_t|$ 大,其平方也大;低波动日的 $|\epsilon_t|$ 小,其平方也小。因此平方序列本身会携带波动状态的信息。

import numpy as np
import pandas as pd
from statsmodels.stats.diagnostic import acorr_ljungbox
import os
import requests

# 从 TickDB 获取历史 K 线数据(以 BTC/USDT 为例)
# 实际使用时请设置 TICKDB_API_KEY 环境变量
symbol = "BTC.USDT"
interval = "1h"
limit = 2000

headers = {"X-API-Key": os.environ.get("TICKDB_API_KEY")}
response = requests.get(
    "https://api.tickdb.ai/v1/market/kline",
    headers=headers,
    params={"symbol": symbol, "interval": interval, "limit": limit},
    timeout=(3.05, 10)
)

if response.status_code == 200:
    data = response.json()
    df = pd.DataFrame(data["data"])
    df["returns"] = df["close"].pct_change()
    df["squared_returns"] = df["returns"] ** 2

    # Ljung-Box 检验:收益率平方是否存在自相关
    lb_result = acorr_ljungbox(
        df["squared_returns"].dropna(),
        lags=[10, 20, 30],
        return_df=True
    )
    print("Ljung-Box 检验结果(收益率平方):")
    print(lb_result)
    print("\n如果 p-value < 0.05,说明存在显著的波动率聚集。")

方法二:ARCH-LM 检验

ARCH-LM 检验专门用于检测残差序列中是否存在 ARCH 效应(即条件异方差)。原假设是"不存在 ARCH 效应",如果拒绝原假设,则说明序列存在波动率聚集。

from statsmodels.stats.diagnostic import het_arch

# ARCH-LM 检验
returns_clean = df["returns"].dropna()
arch_test = het_arch(returns_clean, nlags=12)

print(f"ARCH-LM 检验统计量: {arch_test[0]:.4f}")
print(f"p-value: {arch_test[1]:.4f}")
print(f"F-statistic: {arch_test[2]:.4f}")
print(f"p-value (F): {arch_test[3]:.4f}")

if arch_test[1] < 0.05:
    print("\nARCH-LM 检验显著:序列存在 ARCH 效应,建议使用 GARCH 类模型。")

二、GARCH 模型:波动率聚集的数学语言

2.1 从 ARCH 到 GARCH:模型演进

波动率聚集的数学建模经历了从 ARCHGARCH 的演进。

ARCH(自回归条件异方差)模型由 Robert Engle 在 1982 年提出。其核心思想是:今天的条件方差取决于历史冲击的平方和。

$$\epsilon_t = \sigma_t \cdot z_t, \quad z_t \sim i.i.d.(0,1)$$

$$\sigma_t^2 = \omega + \alpha_1 \epsilon_{t-1}^2 + \alpha_2 \epsilon_{t-2}^2 + \cdots + \alpha_q \epsilon_{t-q}^2$$

其中 $\sigma_t^2$ 是 $t$ 时刻的条件方差,$\epsilon_t$ 是残差项(收益率),$z_t$ 是标准化的随机扰动(通常假设为标准正态分布或学生 t 分布)。

ARCH 模型的问题是:当 $q$ 较大时,需要估计很多参数,模型变得不稳定。Bollerslev 在 1986 年提出的 GARCH 模型引入了一个"条件方差的自身滞后项",大幅减少了参数数量:

$$\sigma_t^2 = \omega + \alpha_1 \epsilon_{t-1}^2 + \beta_1 \sigma_{t-1}^2$$

这就是最常用的 GARCH(1,1) 模型。它只有三个参数($\omega$, $\alpha_1$, $\beta_1$),却能捕捉波动率的持续性。

2.2 理解 GARCH(1,1):参数的经济含义

参数 名称 含义
$\omega$ 常数项 波动率的基准水平(长期平均方差)
$\alpha_1$(简称 $\alpha$) ARCH 项系数 上一期冲击对当前波动率的影响
$\beta_1$(简称 $\beta$) GARCH 项系数 上一期波动率对当前波动率的持续性影响
$\alpha + \beta$ 波动率持续性 衡量冲击衰减的速度,接近 1 意味着冲击持续很久

关键洞察:$\alpha + \beta$ 是最重要的指标:

  • $\alpha + \beta < 1$:冲击会逐渐衰减,波动率最终回归到长期均值 $\omega / (1 - \alpha - \beta)$
  • $\alpha + \beta \approx 1$:冲击衰减极慢,波动率具有长记忆特性(Long Memory)
  • $\alpha + \beta = 1$:单位根情形,波动率呈随机游走,冲击永久存在(IGARCH)

对于大多数金融资产,$\alpha + \beta$ 在 0.90 到 0.99 之间,这意味着波动率的"半衰期"可能长达数周甚至数月——这也是为什么市场一旦进入高波动状态,往往会持续相当长时间。

2.3 GARCH 模型为何能解释波动率聚集

GARCH 模型捕捉波动率聚集的机制如下:

  1. 初始冲击:一个大的正向或负向消息到达市场(|$\epsilon_{t-1}$| 很大)
  2. 波动率响应:由于 $\alpha \epsilon_{t-1}^2$ 项的存在,$\sigma_t^2$ 变大
  3. 持续效应:由于 $\beta \sigma_{t-1}^2$ 项的存在,即使没有新的大冲击,$\sigma_{t+1}^2$ 仍然较大
  4. 缓慢衰减:冲击的影响以指数速度衰减(衰减速度由 $1 - \alpha - \beta$ 决定)

这形成了一个正反馈回路:高波动状态会自我维持一段时间。这正是"大波动后面总是跟着大波动"的数学机制。

2.4 生产级 GARCH 建模代码

以下代码展示了从数据获取、模型拟合、诊断检验到波动率预测的完整流程:

import numpy as np
import pandas as pd
import os
import requests
import time
import random
from arch import arch_model
from statsmodels.stats.diagnostic import acorr_ljungbox, het_arch
from scipy import stats

# ============================================
# Step 1: 从 TickDB 获取历史 K 线数据
# ============================================
def fetch_kline_data(symbol, interval, limit, max_retries=3):
    """
    从 TickDB API 获取 K 线数据
    包含重试机制和限频处理
    """
    headers = {"X-API-Key": os.environ.get("TICKDB_API_KEY")}
    url = "https://api.tickdb.ai/v1/market/kline"
    params = {"symbol": symbol, "interval": interval, "limit": limit}

    for attempt in range(max_retries):
        try:
            response = requests.get(
                url,
                headers=headers,
                params=params,
                timeout=(3.05, 10)
            )

            # 限频处理:code 3001 表示请求频率超限
            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 5))
                wait_time = retry_after + random.uniform(0, 1)
                print(f"限频触发,等待 {wait_time:.1f} 秒...")
                time.sleep(wait_time)
                continue

            if response.status_code == 200:
                return response.json()

        except requests.exceptions.RequestException as e:
            # 网络异常:指数退避重连
            delay = min(2 ** attempt * 0.5, 30) + random.uniform(0, 0.5)
            print(f"请求异常 ({e}),{delay:.1f} 秒后重试...")
            time.sleep(delay)

    raise RuntimeError("数据获取失败,请检查网络和 API Key 配置")


def fit_garch_model(returns, p=1, q=1, dist='t'):
    """
    拟合 GARCH(p,q) 模型
    默认使用学生 t 分布以捕捉厚尾特征

    参数:
        returns: 收益率序列(建议转换为百分比形式)
        p: GARCH 阶数
        q: ARCH 阶数
        dist: 残差分布 ('normal' | 't' | 'skewt')

    返回:
        fitted_model: 拟合结果对象
    """
    # ⚠️ GARCH 模型对收益率的尺度敏感,建议乘以100转为百分比
    returns_pct = returns.dropna() * 100

    # 构建 GARCH 模型
    model = arch_model(
        returns_pct,
        vol='Garch',
        p=p,
        q=q,
        dist=dist,
        mean='Constant'  # 收益率均值方程设为常数
    )

    # 拟合模型(disp='off' 关闭迭代输出)
    # ⚠️ 生产环境建议增加迭代次数上限和收敛容差检查
    result = model.fit(
        disp='off',
        options={'maxiter': 1000, 'ftol': 1e-7}
    )

    return result


def diagnose_garch_model(result):
    """
    GARCH 模型诊断检验

    检验内容:
    1. 标准化残差是否服从期望分布(均值0,方差1)
    2. 标准化残差是否存在自相关(应该没有)
    3. 标准化残差平方是否存在自相关(应该没有,说明模型已捕捉波动率聚集)
    """
    std_resid = result.resid / result.conditional_volatility
    std_resid_clean = std_resid.dropna()

    diagnostics = {}

    # 1. 基本统计量
    diagnostics['mean'] = std_resid_clean.mean()
    diagnostics['std'] = std_resid_clean.std()
    diagnostics['skewness'] = stats.skew(std_resid_clean)
    diagnostics['kurtosis'] = stats.kurtosis(std_resid_clean)

    # 2. Jarque-Bera 正态性检验
    jb_stat, jb_pvalue = stats.jarque_bera(std_resid_clean)
    diagnostics['jb_stat'] = jb_stat
    diagnostics['jb_pvalue'] = jb_pvalue

    # 3. Ljung-Box 检验:标准化残差平方是否存在自相关
    # 如果存在,说明模型未能完全捕捉波动率动态
    lb_sq = acorr_ljungbox(std_resid_clean ** 2, lags=[10, 20], return_df=True)
    diagnostics['lb_sq_pvalues'] = lb_sq['lb_pvalue'].to_dict()

    return diagnostics


def print_diagnosis_report(diagnostics, alpha_params, beta_params):
    """打印诊断报告"""
    print("\n" + "=" * 50)
    print("模型诊断报告")
    print("=" * 50)

    print(f"\n标准化残差统计量:")
    print(f"  均值: {diagnostics['mean']:.4f} (期望: 0)")
    print(f"  标准差: {diagnostics['std']:.4f} (期望: 1)")
    print(f"  偏度: {diagnostics['skewness']:.4f} (期望: 0)")
    print(f"  峰度: {diagnostics['kurtosis']:.4f} (期望: 0,正态分布)")

    print(f"\nJarque-Bera 正态性检验:")
    print(f"  统计量: {diagnostics['jb_stat']:.4f}, p-value: {diagnostics['jb_pvalue']:.4f}")
    if diagnostics['jb_pvalue'] < 0.05:
        print("  ⚠️ 显著偏离正态分布(正常现象,金融数据通常有厚尾)")

    print(f"\nLjung-Box 检验(标准化残差平方):")
    for lag, pvalue in diagnostics['lb_sq_pvalues'].items():
        status = "✓ 通过" if pvalue > 0.05 else "⚠️ 存在残余自相关"
        print(f"  滞后 {lag}: p-value = {pvalue:.4f} {status}")

    # 波动率持续性
    persistence = sum(alpha_params) + sum(beta_params)
    print(f"\n波动率持续性 (α + β): {persistence:.4f}")
    if persistence > 0.95:
        print("  ⚠️ 高持续性:波动率具有长记忆特征,冲击衰减缓慢")
    elif persistence > 0.85:
        print("  ✓ 中等持续性:波动率冲击会逐渐衰减")


# ============================================
# 主程序:完整 GARCH 建模流程
# ============================================
if __name__ == "__main__":
    # ⚠️ 使用真实 API Key 替换
    api_key = os.environ.get("TICKDB_API_KEY")
    if not api_key:
        raise ValueError("请设置 TICKDB_API_KEY 环境变量")

    # 1. 获取数据
    print("正在从 TickDB 获取 BTC/USDT 历史 K 线...")
    data = fetch_kline_data("BTC.USDT", "1h", limit=2000)

    # 2. 计算收益率
    df = pd.DataFrame(data["data"])
    df["returns"] = df["close"].pct_change()
    returns = df["returns"].dropna()

    print(f"数据样本量: {len(returns)} 条收益率记录")

    # 3. ARCH-LM 预检验
    print("\n正在执行 ARCH-LM 预检验...")
    arch_test = het_arch(returns, nlags=12)
    print(f"ARCH-LM 统计量: {arch_test[0]:.4f}, p-value: {arch_test[1]:.4f}")
    if arch_test[1] < 0.05:
        print("✓ 检测到显著的 ARCH 效应,使用 GARCH 模型建模")
    else:
        print("⚠️ 未检测到显著 ARCH 效应,但 GARCH 模型仍可用于波动率估计")

    # 4. 拟合 GARCH(1,1) 模型
    print("\n正在拟合 GARCH(1,1) 模型...")
    result = fit_garch_model(returns, p=1, q=1, dist='t')
    print(result.summary())

    # 5. 诊断检验
    alpha_params = [result.params[f'alpha[{i}]'] for i in range(1, 2)]
    beta_params = [result.params[f'beta[{i}]'] for i in range(1, 2)]
    diagnostics = diagnose_garch_model(result)
    print_diagnosis_report(diagnostics, alpha_params, beta_params)

    # 6. 波动率预测(未来 10 期)
    print("\n未来 10 期的条件波动率预测:")
    forecast = result.forecast(horizon=10)
    conditional_vol = np.sqrt(forecast.variance.iloc[-1].values)
    for i, vol in enumerate(conditional_vol):
        print(f"  第 {i+1} 期年化波动率: {vol * np.sqrt(252 * 24):.2%}")

三、波动率聚集的成因:为什么市场"记性"这么好

3.1 信息流的不均匀性

波动率聚集的根本原因是信息到达市场的速度不均匀

在平静期,大部分投资者认为价格已经反映了所有已知信息,新的信息缓慢、均匀地到达市场。在这种情况下,买卖双方的力量相对均衡,价格小幅波动。

当一个重大冲击出现时(央行意外加息、某公司发布灾难级财报),大量新信息在短时间内涌入市场。投资者需要重新评估资产价值,意见分歧急剧扩大,价格开始大幅波动。由于信息处理和头寸调整需要时间,这个过程不会在一瞬间完成——波动率在冲击后的缓慢衰减,本质上是信息逐渐被市场吸收的过程

3.2 投资者行为:羊群效应与风险管理反馈

除了信息流,投资者行为也会强化波动率聚集:

止损链效应:当价格跌破某个阈值时,程序化止损单被触发,强制卖出。这会进一步压低价格,触发更多止损。这种正反馈机制使得下跌趋势自我强化,波动率在短期内维持在高位。

流动性螺旋:高波动期,做市商会扩大买卖价差、减少挂单量以保护自己。这降低了流动性,使得即使是小额订单也能引发较大的价格变动。这种流动性紧缩会持续一段时间,直到市场情绪稳定。

羊群效应与注意力聚焦:在市场剧烈波动期,财经媒体大量报道、社交网络高度活跃,所有投资者的注意力都被市场吸引。这种集体关注本身会放大波动——更多的交易行为、更多的信息解读、更多的情绪反应交织在一起,形成持续的波动状态。

3.3 从微观结构到宏观现象

以上微观机制共同作用,在宏观上表现为:

  1. 波动率具有持续性:今天的波动率与昨天的波动率高度相关
  2. 波动率有均值回归趋势:虽然持续性高,但不会永远维持,最终会回归长期均值
  3. 波动率存在"尖峰厚尾":极端波动出现的频率比正态分布预测的要高
  4. 负收益对波动率的影响更大:利空消息比利好消息更能引发波动(杠杆效应)

这些特征被 GARCH 族模型以不同方式捕捉:

模型 捕捉的特征
GARCH(1,1) 基本的波动率聚集和持续性
GJR-GARCH / TGARCH 波动率的非对称性(杠杆效应)
EGARCH 对数形式的条件方差,保证正值约束
FIGARCH 波动率的分数阶持续性(长记忆)
GARCH-MIDAS 波动率的混合频率建模(日内高频 + 日间低频)

四、从理论到实践:波动率聚集的交易应用

4.1 风险管理的 VaR 计算

波动率聚集对风险管理有直接影响。传统的历史模拟法 VaR 假设所有历史观测同等重要,没有考虑近期波动率的影响。GARCH 模型允许"时变波动率",可以更准确地估计极端风险。

def calculate_garch_var(returns, percentile=0.99, horizon=1):
    """
    基于 GARCH 模型的条件 VaR 计算

    参数:
        returns: 收益率序列
        percentile: 置信水平(默认 99%)
        horizon: 持有期(天数)

    返回:
        var: 在险价值(正值表示损失)
    """
    # 拟合 GARCH 模型
    result = fit_garch_model(returns, p=1, q=1, dist='t')

    # 预测未来波动率
    forecast = result.forecast(horizon=horizon)
    cond_vol = np.sqrt(forecast.variance.iloc[-1].values[0]) / 100  # 还原为小数

    # 获取残差分布的分位数(学生 t 分布)
    from scipy.stats import t as t_dist
    # 获取学生 t 分布的自由度参数
    dof = result.params.get('p[1]', 6)  # 通常在 4-10 之间

    # 计算指定置信水平的分位数
    quantile = t_dist.ppf(1 - percentile, df=dof)

    # VaR = -mean_return + quantile * volatility * sqrt(horizon)
    # 对于短期持有期,均值项通常忽略
    mean_ret = returns.mean()

    var = -(mean_ret + quantile * cond_vol * np.sqrt(horizon))
    return var


# 示例:计算 99% VaR(1天持有期)
returns = df["returns"].dropna()
var_99 = calculate_garch_var(returns, percentile=0.99, horizon=1)
print(f"99% VaR (1天): {-var_99:.2%}")
print(f"含义:明天有 99% 的概率,损失不会超过 {-var_99:.2%}")

4.2 波动率择时策略框架

基于 GARCH 模型的波动率预测,可以设计简单的择时逻辑:

def volatility_timing_signal(returns, lookback=252, vol_threshold=0.20):
    """
    波动率择时信号生成

    策略逻辑:
    - 当预测年化波动率 > 阈值时,降低风险敞口
    - 当预测年化波动率 < 阈值时,可以适度增加风险敞口

    参数:
        returns: 收益率序列
        lookback: GARCH 模型估计窗口
        vol_threshold: 年化波动率阈值(默认 20%)

    返回:
        signal: 0 = 低配, 1 = 标配, 2 = 超配
    """
    # 使用滚动窗口估计最新的 GARCH 参数
    recent_returns = returns.iloc[-lookback:]
    result = fit_garch_model(recent_returns, p=1, q=1, dist='t')

    # 预测下一期条件波动率
    forecast = result.forecast(horizon=1)
    cond_vol_daily = np.sqrt(forecast.variance.iloc[-1].values[0]) / 100
    annualized_vol = cond_vol_daily * np.sqrt(252)

    # 生成信号
    if annualized_vol > vol_threshold * 1.5:
        return 0, annualized_vol, "高波动环境,建议低配或离场"
    elif annualized_vol > vol_threshold:
        return 1, annualized_vol, "中等波动环境,标配或略低配"
    else:
        return 2, annualized_vol, "低波动环境,可以适度超配"


# 实际使用时,每天收盘后计算一次
signal, pred_vol, msg = volatility_timing_signal(returns)
print(f"当前预测年化波动率: {pred_vol:.2%}")
print(f"信号: {signal}, 建议: {msg}")

4.3 波动率聚集与策略设计原则

理解波动率聚集后,以下原则对策略设计至关重要:

原则 含义
不要假设波动率恒定 回测时使用固定波动率假设会导致严重高估/低估
关注波动率的变化趋势 上升趋势中做趋势跟踪可能有优势;下降趋势中均值回归更有效
高波动期降低仓位 波动率升高意味着风险暴露增加,应动态调整
使用 GARCH 类模型做风险管理 比简单的历史波动率更准确地估计极端风险
警惕波动率的"记忆" 高波动期过后不要急于"All in",波动率通常缓慢衰减

五、深度:长记忆与分数阶 GARCH

5.1 为什么 $\alpha + \beta \approx 1$ 意味着"长记忆"

在标准 GARCH(1,1) 中,波动率冲击的衰减速度由 $1 - \alpha - \beta$ 决定。如果 $\alpha + \beta = 0.95$,则每增加一期的延迟,冲击的影响乘以 0.95。经过 20 期后,初始冲击仍有 $0.95^{20} \approx 36%$ 的影响;经过 100 期后,仍有约 $0.54%$ 的影响。

这种"衰减缓慢但持续时间长"的特性,被称为长记忆(Long Memory)。它意味着市场"记性很好"——很久之前的信息对当前波动率仍有不可忽视的影响。

5.2 FIGARCH:更精确的长记忆建模

标准 GARCH 模型假设冲击以指数速度衰减。但有些市场的衰减速度更慢,可能服从双曲衰减(hyperbolic decay)。分数阶 GARCH(FIGARCH)模型允许冲击的影响以分数阶方式衰减:

$$\phi(L)(1-L)^d \epsilon_t^2 = c + \nu_t$$

其中 $d$ 是分数阶参数($0 < d < 1$),$(1-L)^d$ 是分数阶差分算子。当 $d = 1$ 时,退化为 IGARCH(冲击永久存在);当 $d = 0$ 时,退化为 ARCH(冲击快速衰减)。

# FIGARCH 模型(需要 arch 库支持)
def fit_figarch_model(returns, p=1, q=1, dist='t'):
    """
    拟合 FIGARCH(p,d,q) 模型

    参数 d 衡量长记忆程度:
    - d = 0: 短记忆(标准 GARCH)
    - 0 < d < 1: 长记忆
    - d = 1: 永久记忆(IGARCH)
    """
    returns_pct = returns.dropna() * 100
    model = arch_model(
        returns_pct,
        vol='Figarch',
        p=p,
        q=q,
        dist=dist,
        power=1.0  # power=2 时为 FHYGARCH,power=1 时为 FIGARCH
    )
    result = model.fit(disp='off')
    return result


# 注意:FIGARCH 参数估计收敛较慢,在生产环境中需要更多迭代

5.3 长记忆的实际意义

长记忆的存在意味着:

  1. 波动率的预测 horizons 可以更长:如果波动率是短记忆(标准 GARCH),预测多期后趋近无条件方差;如果波动率是长记忆(FIGARCH),则在更长的时间范围内预测值仍然取决于近期条件。

  2. 均值回归速度很慢:这解释了为什么市场在高波动期后会持续"神经紧张"相当长时间。

  3. 历史信息权重衰减更慢:不同于指数加权移动平均(EWMA),长记忆模型赋予历史观测的权重衰减遵循幂律而非指数。


六、总结与思考

6.1 核心结论

波动率聚集是金融市场最稳健的统计规律之一。它的本质是:市场波动不是完全随机的,而是存在统计上的持续性——大波动倾向于聚集,小波动也倾向于聚集。

GARCH 模型为这一现象提供了简洁而有力的数学框架:今天的波动率取决于昨天的冲击和昨天的波动率。关键的参数 $\alpha + \beta$ 衡量了波动率的持续性——对大多数资产来说,这个值在 0.90-0.99 之间,意味着冲击的半衰期可能长达数周。

理解波动率聚集,对量化交易有直接的实践价值:更准确的风险管理(VaR)、更合理的仓位管理(波动率目标)、更有效的择时信号。

6.2 更深层的思考

GARCH 模型虽然强大,但也有其局限:

  • 它本质上是条件均值框架下的副产品,没有将波动率本身建模为一个独立的状态变量。
  • 它假设波动率是确定性函数(给定过去信息,波动率是确定的),而实际上波动率可能存在随机性(Stochastic Volatility)。
  • 面对极端事件(如 2020 年 3 月新冠冲击),GARCH 模型的"缓慢衰减"假设可能低估了波动率的骤升

这也是为什么 GARCH 族模型仍在不断演进——EGARCH、FIGARCH、GARCH-MIDAS、Stochastic Volatility 模型,都是在特定方向上的深化。

理解模型的历史演进和局限,不是为了否定它,而是为了更准确地在合适的场景中使用它。


下一步行动

如果你是金融工程学生或研究者,建议从 GARCH(1,1) 的参数推导开始,尝试在手写推导中理解 $\alpha + \beta$ 的含义,然后用真实市场数据做实证——比较 $\alpha + \beta$ 在不同资产类别(股票、外汇、数字货币)上的差异。

如果你想获取历史数据进行验证,可以在 TickDB 控制台申请 API Key(免费层已包含足够的数据额度),获取过去数年的 K 线数据,计算收益率平方的 Ljung-Box 统计量,亲自验证波动率聚集的存在。

如果你对波动率建模有更深入的需求,可以考虑以下方向:

  • 使用 GJR-GARCH 或 EGARCH 捕捉波动率的杠杆效应
  • 使用 GARCH-MIDAS 结合宏观因子(VIX、经济不确定性指数)做混合频率波动率预测
  • 将 GARCH 条件波动率作为风险预算仓位管理的输入信号

风险提示:本文不构成任何投资建议。波动率聚集是历史统计规律,不代表未来必然重复。模型存在固有局限,实际应用前请充分评估。