In [1]:
Copied!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)
print("依赖库加载完毕 ✅")
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
np.random.seed(42)
print("依赖库加载完毕 ✅")
依赖库加载完毕 ✅
In [2]:
Copied!
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 波动率聚集(Volatility Clustering)的直观展示¶
Random Walk(随机游走)假设收益率是 i.i.d. 正态分布——每天的波动率都是一样的常数。 但真实金融数据并非如此。
In [3]:
Copied!
# 模拟两种收益率序列:iid 正态 vs GARCH(1,1)
n = 1000
# A. 简单 iid 正态(不现实)
iid_returns = np.random.normal(0, 0.01, n)
# B. 手动模拟 GARCH(1,1) 的波动率聚集
# sigma_t^2 = omega + alpha * epsilon_{t-1}^2 + beta * sigma_{t-1}^2
omega = 0.00001
alpha = 0.15
beta = 0.80
sigma2 = np.zeros(n)
garch_returns = np.zeros(n)
sigma2[0] = omega / (1 - alpha - beta) # 无条件方差
for t in range(1, n):
sigma2[t] = omega + alpha * garch_returns[t-1]**2 + beta * sigma2[t-1]
garch_returns[t] = np.random.normal(0, np.sqrt(sigma2[t]))
# 可视化对比
fig, axes = plt.subplots(2, 2, figsize=(14, 8))
axes[0,0].plot(iid_returns, color='steelblue', alpha=0.7, linewidth=0.8)
axes[0,0].set_title('IID 正态收益率(无波动率聚集)')
axes[0,0].set_ylabel('每日收益率')
axes[0,1].plot(garch_returns, color='darkorange', alpha=0.7, linewidth=0.8)
axes[0,1].set_title('GARCH(1,1) 模拟收益率(有波动率聚集)')
axes[0,1].set_ylabel('每日收益率')
axes[1,0].plot(iid_returns**2, color='steelblue', alpha=0.7, linewidth=0.8)
axes[1,0].set_title('IID 平方收益率(代理波动率)- 无规律随机')
axes[1,0].set_ylabel('收益率²')
axes[1,1].plot(sigma2, color='red', alpha=0.8, linewidth=1.0)
axes[1,1].set_title('GARCH(1,1) 条件方差 σ²_t(明显聚集)')
axes[1,1].set_ylabel('条件方差 σ²')
plt.suptitle('IID vs GARCH(1,1):波动率聚集的证明', fontsize=13)
plt.tight_layout()
plt.show()
print("注意 GARCH 右上图:有些时期波动率持续升高(如金融危机),有些时期持续平静")
# 模拟两种收益率序列:iid 正态 vs GARCH(1,1)
n = 1000
# A. 简单 iid 正态(不现实)
iid_returns = np.random.normal(0, 0.01, n)
# B. 手动模拟 GARCH(1,1) 的波动率聚集
# sigma_t^2 = omega + alpha * epsilon_{t-1}^2 + beta * sigma_{t-1}^2
omega = 0.00001
alpha = 0.15
beta = 0.80
sigma2 = np.zeros(n)
garch_returns = np.zeros(n)
sigma2[0] = omega / (1 - alpha - beta) # 无条件方差
for t in range(1, n):
sigma2[t] = omega + alpha * garch_returns[t-1]**2 + beta * sigma2[t-1]
garch_returns[t] = np.random.normal(0, np.sqrt(sigma2[t]))
# 可视化对比
fig, axes = plt.subplots(2, 2, figsize=(14, 8))
axes[0,0].plot(iid_returns, color='steelblue', alpha=0.7, linewidth=0.8)
axes[0,0].set_title('IID 正态收益率(无波动率聚集)')
axes[0,0].set_ylabel('每日收益率')
axes[0,1].plot(garch_returns, color='darkorange', alpha=0.7, linewidth=0.8)
axes[0,1].set_title('GARCH(1,1) 模拟收益率(有波动率聚集)')
axes[0,1].set_ylabel('每日收益率')
axes[1,0].plot(iid_returns**2, color='steelblue', alpha=0.7, linewidth=0.8)
axes[1,0].set_title('IID 平方收益率(代理波动率)- 无规律随机')
axes[1,0].set_ylabel('收益率²')
axes[1,1].plot(sigma2, color='red', alpha=0.8, linewidth=1.0)
axes[1,1].set_title('GARCH(1,1) 条件方差 σ²_t(明显聚集)')
axes[1,1].set_ylabel('条件方差 σ²')
plt.suptitle('IID vs GARCH(1,1):波动率聚集的证明', fontsize=13)
plt.tight_layout()
plt.show()
print("注意 GARCH 右上图:有些时期波动率持续升高(如金融危机),有些时期持续平静")
注意 GARCH 右上图:有些时期波动率持续升高(如金融危机),有些时期持续平静
2. ARCH 模型原理¶
ARCH(p) - Autoregressive Conditional Heteroskedasticity(自回归条件异方差)
Engle(1982,获得诺贝尔经济学奖)提出:条件方差依赖于过去 p 个残差的平方:
$$\sigma_t^2 = \omega + \sum_{i=1}^{p} \alpha_i \epsilon_{t-i}^2$$
GARCH(p,q) - Generalized ARCH
Bollerslev(1986)在 ARCH 基础上加入了波动率的自回归项:
$$\sigma_t^2 = \omega + \sum_{i=1}^{p} \alpha_i \epsilon_{t-i}^2 + \sum_{j=1}^{q} \beta_j \sigma_{t-j}^2$$
最常用的 GARCH(1,1) 公式: $$\sigma_t^2 = \omega + \alpha \epsilon_{t-1}^2 + \beta \sigma_{t-1}^2$$
其中:
- $\omega$ (omega):长期背景方差
- $\alpha$:ARCH 效应(短期冲击持久性)
- $\beta$:GARCH 效应(长期波动率记忆性)
- 平稳条件:$\alpha + \beta < 1$(否则条件方差会爆炸)
In [4]:
Copied!
# 检验 ARCH 效应:Ljung-Box 对平方残差的自相关性检验
from statsmodels.stats.diagnostic import acorr_ljungbox
# 对模拟 GARCH 数据计算平方收益率的自相关
lb_garch = acorr_ljungbox(garch_returns**2, lags=10, return_df=True)
lb_iid = acorr_ljungbox(iid_returns**2, lags=10, return_df=True)
print("GARCH 模拟数据 - 平方收益率的 Ljung-Box 检验(p值越小越有 ARCH 效应):")
print(lb_garch[['lb_stat', 'lb_pvalue']].round(4))
print("\nIID 数据 - 平方收益率的 Ljung-Box 检验:")
print(lb_iid[['lb_stat', 'lb_pvalue']].round(4))
# 检验 ARCH 效应:Ljung-Box 对平方残差的自相关性检验
from statsmodels.stats.diagnostic import acorr_ljungbox
# 对模拟 GARCH 数据计算平方收益率的自相关
lb_garch = acorr_ljungbox(garch_returns**2, lags=10, return_df=True)
lb_iid = acorr_ljungbox(iid_returns**2, lags=10, return_df=True)
print("GARCH 模拟数据 - 平方收益率的 Ljung-Box 检验(p值越小越有 ARCH 效应):")
print(lb_garch[['lb_stat', 'lb_pvalue']].round(4))
print("\nIID 数据 - 平方收益率的 Ljung-Box 检验:")
print(lb_iid[['lb_stat', 'lb_pvalue']].round(4))
GARCH 模拟数据 - 平方收益率的 Ljung-Box 检验(p值越小越有 ARCH 效应):
lb_stat lb_pvalue
1 181.6585 0.0
2 282.2214 0.0
3 385.7022 0.0
4 490.9699 0.0
5 565.0586 0.0
6 581.4905 0.0
7 586.8447 0.0
8 601.2193 0.0
9 607.1268 0.0
10 612.1537 0.0
IID 数据 - 平方收益率的 Ljung-Box 检验:
lb_stat lb_pvalue
1 0.4824 0.4873
2 2.3138 0.3145
3 2.6666 0.4459
4 2.8504 0.5832
5 3.1593 0.6754
6 4.2655 0.6408
7 4.4086 0.7317
8 4.7392 0.7851
9 6.1032 0.7296
10 6.2194 0.7965
3. 使用 arch 库拟合 GARCH 模型¶
arch 库是 Python 中最成熟的 GARCH 建模工具,由 Kevin Sheppard 维护。
In [5]:
Copied!
try:
from arch import arch_model
# 将收益率转换为百分比(arch 库习惯)
returns_pct = garch_returns * 100
# 拟合 GARCH(1,1) 模型,使用 t 分布(更适合金融数据肥尾)
garch_model = arch_model(returns_pct, vol='Garch', p=1, q=1, dist='t')
result = garch_model.fit(disp='off')
print(result.summary())
# 提取拟合的条件波动率(年化)
fitted_vol = result.conditional_volatility * np.sqrt(252) / 100
plt.figure(figsize=(12, 4))
plt.plot(fitted_vol, color='red', label='GARCH(1,1) 条件波动率(年化)', linewidth=1.2)
plt.axhline(fitted_vol.mean(), color='gray', linestyle='--', alpha=0.7, label=f'均值={fitted_vol.mean():.3f}')
plt.title('GARCH(1,1) 估计的条件年化波动率')
plt.ylabel('年化波动率')
plt.legend()
plt.show()
# 预测未来 10 日的波动率
forecasts = result.forecast(horizon=10)
forecast_vol = np.sqrt(forecasts.variance.iloc[-1]) * np.sqrt(252) / 100
print("\n未来 10 日预测波动率(年化):")
for i, v in enumerate(forecast_vol, 1):
print(f" Day +{i}: {v:.4f}")
except ImportError:
print("请先安装 arch 库:!pip install arch")
print("这里展示拟合结果的期望输出格式:")
print(" omega = 4.32e-06 (长期方差)")
print(" alpha = 0.1489 (ARCH 效应)")
print(" beta = 0.8011 (GARCH 持久性)")
print(" alpha + beta = 0.9500 < 1.0(模型平稳)")
try:
from arch import arch_model
# 将收益率转换为百分比(arch 库习惯)
returns_pct = garch_returns * 100
# 拟合 GARCH(1,1) 模型,使用 t 分布(更适合金融数据肥尾)
garch_model = arch_model(returns_pct, vol='Garch', p=1, q=1, dist='t')
result = garch_model.fit(disp='off')
print(result.summary())
# 提取拟合的条件波动率(年化)
fitted_vol = result.conditional_volatility * np.sqrt(252) / 100
plt.figure(figsize=(12, 4))
plt.plot(fitted_vol, color='red', label='GARCH(1,1) 条件波动率(年化)', linewidth=1.2)
plt.axhline(fitted_vol.mean(), color='gray', linestyle='--', alpha=0.7, label=f'均值={fitted_vol.mean():.3f}')
plt.title('GARCH(1,1) 估计的条件年化波动率')
plt.ylabel('年化波动率')
plt.legend()
plt.show()
# 预测未来 10 日的波动率
forecasts = result.forecast(horizon=10)
forecast_vol = np.sqrt(forecasts.variance.iloc[-1]) * np.sqrt(252) / 100
print("\n未来 10 日预测波动率(年化):")
for i, v in enumerate(forecast_vol, 1):
print(f" Day +{i}: {v:.4f}")
except ImportError:
print("请先安装 arch 库:!pip install arch")
print("这里展示拟合结果的期望输出格式:")
print(" omega = 4.32e-06 (长期方差)")
print(" alpha = 0.1489 (ARCH 效应)")
print(" beta = 0.8011 (GARCH 持久性)")
print(" alpha + beta = 0.9500 < 1.0(模型平稳)")
Constant Mean - GARCH Model Results
====================================================================================
Dep. Variable: y R-squared: 0.000
Mean Model: Constant Mean Adj. R-squared: 0.000
Vol Model: GARCH Log-Likelihood: -1662.25
Distribution: Standardized Student's t AIC: 3334.51
Method: Maximum Likelihood BIC: 3359.05
No. Observations: 1000
Date: Sat, Mar 07 2026 Df Residuals: 999
Time: 01:27:48 Df Model: 1
Mean Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
mu 0.1007 3.659e-02 2.752 5.916e-03 [2.900e-02, 0.172]
Volatility Model
==========================================================================
coef std err t P>|t| 95.0% Conf. Int.
--------------------------------------------------------------------------
omega 0.2014 5.963e-02 3.377 7.317e-04 [8.453e-02, 0.318]
alpha[1] 0.1835 3.571e-02 5.138 2.782e-07 [ 0.113, 0.253]
beta[1] 0.7070 5.489e-02 12.881 5.747e-38 [ 0.599, 0.815]
Distribution
=============================================================================
coef std err t P>|t| 95.0% Conf. Int.
-----------------------------------------------------------------------------
nu 100.1259 236.286 0.424 0.672 [-3.630e+02,5.632e+02]
=============================================================================
Covariance estimator: robust
未来 10 日预测波动率(年化): Day +1: 0.1546 Day +2: 0.1624 Day +3: 0.1690 Day +4: 0.1746 Day +5: 0.1795 Day +6: 0.1838 Day +7: 0.1875 Day +8: 0.1907 Day +9: 0.1936 Day +10: 0.1961
4. GARCH 在风险管理中的应用:动态 VaR¶
传统的 VaR 使用固定的历史波动率。GARCH-VaR 使用预测的当日条件波动率,更加精准。
In [6]:
Copied!
# 用模拟数据演示动态 GARCH-VaR
confidence = 0.95
z_score = stats.norm.ppf(1 - confidence) # ≈ -1.645
# 静态 VaR(使用总体平均波动率)
static_vol = np.std(garch_returns)
static_var = z_score * static_vol
# 动态 GARCH-VaR(使用当日条件波动率)
dynamic_vol = np.sqrt(sigma2)
dynamic_var = z_score * dynamic_vol
plt.figure(figsize=(13, 5))
plt.plot(garch_returns, color='gray', alpha=0.5, linewidth=0.8, label='每日收益率')
plt.plot(dynamic_var, color='red', linewidth=1.5, label=f'动态 GARCH-VaR (95%)')
plt.axhline(static_var, color='blue', linestyle='--', linewidth=1.5, label=f'静态 VaR (95%): {static_var:.4f}')
plt.title('动态 GARCH-VaR vs 静态 VaR(95% 置信水平)')
plt.ylabel('收益率 / VaR')
plt.legend()
plt.show()
static_violations = (garch_returns < static_var).mean()
dynamic_violations = (garch_returns < dynamic_var).mean()
print(f"静态 VaR 实际违约率(应约为 5%): {static_violations:.2%}")
print(f"动态 GARCH-VaR 实际违约率(应约为 5%): {dynamic_violations:.2%}")
print("\n结论:动态 GARCH-VaR 更贴近 5% 目标,因为它在高波动期扩大了 VaR 区间")
# 用模拟数据演示动态 GARCH-VaR
confidence = 0.95
z_score = stats.norm.ppf(1 - confidence) # ≈ -1.645
# 静态 VaR(使用总体平均波动率)
static_vol = np.std(garch_returns)
static_var = z_score * static_vol
# 动态 GARCH-VaR(使用当日条件波动率)
dynamic_vol = np.sqrt(sigma2)
dynamic_var = z_score * dynamic_vol
plt.figure(figsize=(13, 5))
plt.plot(garch_returns, color='gray', alpha=0.5, linewidth=0.8, label='每日收益率')
plt.plot(dynamic_var, color='red', linewidth=1.5, label=f'动态 GARCH-VaR (95%)')
plt.axhline(static_var, color='blue', linestyle='--', linewidth=1.5, label=f'静态 VaR (95%): {static_var:.4f}')
plt.title('动态 GARCH-VaR vs 静态 VaR(95% 置信水平)')
plt.ylabel('收益率 / VaR')
plt.legend()
plt.show()
static_violations = (garch_returns < static_var).mean()
dynamic_violations = (garch_returns < dynamic_var).mean()
print(f"静态 VaR 实际违约率(应约为 5%): {static_violations:.2%}")
print(f"动态 GARCH-VaR 实际违约率(应约为 5%): {dynamic_violations:.2%}")
print("\n结论:动态 GARCH-VaR 更贴近 5% 目标,因为它在高波动期扩大了 VaR 区间")
静态 VaR 实际违约率(应约为 5%): 3.90% 动态 GARCH-VaR 实际违约率(应约为 5%): 4.20% 结论:动态 GARCH-VaR 更贴近 5% 目标,因为它在高波动期扩大了 VaR 区间
🎯 练习¶
- 对真实 AAPL 日收益率数据进行 Ljung-Box 平方收益率检验,是否存在显著的 ARCH 效应?
- 分别拟合 ARCH(1), GARCH(1,1), GJR-GARCH(1,1) 三种模型,用 AIC 和 BIC 判断哪个更优。
- 修改 GARCH(1,1) 中的 alpha 和 beta 参数,观察当 alpha+beta 接近 1.0 时,波动率预测曲线形态如何变化。
下一节 → 06_statistics_fundamentals.ipynb
In [ ]:
Copied!