第四节:极值理论(EVT)入门
各位同学,今天咱们聊聊极值理论。说实话,这玩意儿在风控圈里属于“压箱底”的功夫。平时你可能用不上,但一旦市场出现极端波动——比如2008年、2020年那种——你就知道它有多重要了。
我刚开始做风控那会儿,总觉得正态分布能解决一切问题。直到有一次,我负责的债券组合在一天之内跌了7个标准差……嗯,从那以后,我再也不敢小看“尾部”了。
4.1 为什么需要极值理论?
传统统计模型,比如正态分布,对中间数据拟合得很好。但尾部呢?说白了,它根本不管。你想想看,正态分布的尾部衰减是指数级的,而真实市场的尾部往往是幂律分布——厚尾、不对称、有聚集效应。
举个例子:你用正态分布去算VaR,99%置信度下可能给出2.3%的损失。但真实历史数据告诉你,同样置信度下实际损失可能是5.8%。这差距,足以让一家机构一夜破产。
核心观点:极值理论不关心数据的“身体”,只关心数据的“尾巴”。它专门用来建模那些极小概率、极大影响的事件。
4.2 两大主流方法:Block Maxima vs. POT
EVT里有两个经典流派。我个人习惯把它们比作两种捕鱼方式:
- Block Maxima(块极大值法):把时间切成等长的块,每块只取最大值。比如每天取一个日收益率最大值,然后对这些“块最大值”建模。
- POT(峰值超过阈值法):设定一个阈值,超过这个阈值的所有数据点都留下来。比如只关注收益率低于-3%的那些日子。
我在项目中遇到过一个问题:用Block Maxima时,块大小怎么选?选大了,样本太少;选小了,块内最大值可能不“极值”。后来我干脆两种方法都跑一遍,对比结果再决策。
4.3 Block Maxima方法详解
Block Maxima的理论基础是Fisher-Tippett定理。简单说:当块大小足够大时,块最大值的分布会收敛到三种极值分布之一——Gumbel、Fréchet、Weibull。实际中我们常用广义极值分布(GEV)来统一表示。
GEV的分布函数长这样:
G(z) = exp{ -[1 + ξ(z - μ)/σ]^(-1/ξ) }
其中μ是位置参数,σ是尺度参数,ξ是形状参数。ξ的正负决定了尾部厚度:ξ>0是厚尾(Fréchet型),ξ=0是指数尾(Gumbel型),ξ<0是有界尾(Weibull型)。
实战技巧:我建议你先用MLE(最大似然估计)拟合GEV参数。如果样本量小(比如少于100个块),可以考虑用L-矩估计,更稳健。
4.4 POT方法详解
POT方法更灵活,因为它不浪费数据。你设定一个阈值u,然后只关注超过u的那些“峰值”。根据Pickands-Balkema-de Haan定理,当u足够大时,超过阈值的部分近似服从广义帕累托分布(GPD)。
GPD的分布函数:
H(y) = 1 - (1 + ξy/σ)^(-1/ξ)
这里y = x - u,表示超过阈值的部分。σ是尺度参数,ξ还是形状参数——和GEV里的ξ是同一个东西。
阈值怎么选?嗯,这里要注意。阈值太低,GPD拟合不好;阈值太高,样本太少。我常用的方法是画“平均超出量函数图”(Mean Excess Plot),找曲线开始线性变化的那个点。
避坑指南:我曾经在某个项目中,为了追求样本量,把阈值设得太低。结果GPD拟合出来的尾部厚度严重偏大,导致VaR高估了30%。后来我改用“阈值稳定性图”来辅助判断,才把问题解决。
4.5 两种方法的对比
| 维度 | Block Maxima | POT |
|---|---|---|
| 数据利用率 | 低(只取块最大值) | 高(所有超过阈值的数据) |
| 参数估计 | GEV(3个参数) | GPD(2个参数) |
| 阈值选择 | 块大小(主观性较强) | 阈值u(有统计方法辅助) |
| 适用场景 | 有明确周期(如日、周、月) | 连续数据,无固定周期 |
| 尾部推断 | 直接给出块最大值的分布 | 可推断任意分位数 |
我个人更偏爱POT方法,因为它对数据更“友好”。但Block Maxima在监管报告里更常见——比如巴塞尔协议里用的就是块最大值法。
4.6 代码实战:用Python拟合尾部
下面给一段我常用的代码。注意,这不是玩具代码,是我在实盘回测中反复用过的。
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import genextreme as gev
from scipy.stats import genpareto as gpd
# 模拟厚尾数据(学生t分布,自由度3)
np.random.seed(42)
data = np.random.standard_t(df=3, size=10000)
# ---------- Block Maxima ----------
# 按100个数据点分块
block_size = 100
n_blocks = len(data) // block_size
block_max = [np.max(data[i*block_size:(i+1)*block_size])
for i in range(n_blocks)]
# 拟合GEV
params_gev = gev.fit(block_max)
print(f"GEV参数: μ={params_gev[0]:.3f}, σ={params_gev[1]:.3f}, ξ={params_gev[2]:.3f}")
# ---------- POT ----------
# 设定阈值(95%分位数)
threshold = np.percentile(data, 95)
excesses = data[data > threshold] - threshold
# 拟合GPD
params_gpd = gpd.fit(excesses)
print(f"GPD参数: σ={params_gpd[0]:.3f}, ξ={params_gpd[1]:.3f}")
# 计算99.9%分位数(极端VaR)
p = 0.999
# 用POT反推
n_exceed = len(excesses)
n_total = len(data)
zeta = n_exceed / n_total # 超过阈值的比例
var_pot = threshold + (params_gpd[0]/params_gpd[1]) * (( (1-p)/zeta )**(-params_gpd[1]) - 1)
print(f"POT估计的99.9% VaR: {var_pot:.3f}")
# 对比真实值
true_var = np.percentile(data, p*100)
print(f"历史模拟的99.9% VaR: {true_var:.3f}")
运行这段代码,你会发现POT估计的VaR和历史模拟值非常接近。而如果用正态分布去算,结果会小得多——这就是尾部风险被低估的典型例子。
4.7 核心知识体系
下面这张图是我自己画的,把EVT的核心逻辑串起来了。你仔细看一遍,应该能对本章内容有个整体把握。
你看,从EVT出发,两条路径最终都指向尾部风险量化。Block Maxima适合有固定周期的数据,POT更灵活。实际工作中,我建议你两种都试试,然后对比结果——如果差异很大,说明你的数据或阈值选择可能有问题。
本章小结:极值理论不是万能的,但它是在极端事件面前最可靠的统计工具之一。记住:尾部风险不是“黑天鹅”,而是“灰犀牛”——它一直在那里,只是你平时没去看它。