1. 这节究竟解决什么实际问题?¶
1.8 肥尾分布与金融市场的极端风险 的具体任务是:理解肥尾让极端亏损比正态模型预期得更常见。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
会使用百分比和基础代数;建议先完成环境配置。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
示例优先使用仓库固定的日频复权收盘价;它不是实时行情,也不代表完整市场。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 1.8 肥尾分布与金融市场的极端风险 的输入、计算步骤、输出和局限;尤其能说明:理解肥尾让极端亏损比正态模型预期得更常见。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
价格表示资产在特定时点的水平,收益率表示两个时点之间的相对变化。多数跨资产比较使用收益率。
所有公式都先说明分子、分母、单位与时间尺度;年化前必须确认数据频率。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
NumPy/Pandas 帮你算数和对齐索引;统计库帮你估计参数,但不替你解释经济含义。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“理解肥尾让极端亏损比正态模型预期得更常见。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
把百分比和小数混用、忘记 dropna、把日频波动率直接叫作年化波动率、把相关性当因果。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
概念演示也要避免事后选择样本或只展示支持结论的时间段。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/01_financial_concepts/08_heavy_tails_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/01_financial_concepts/08_heavy_tails_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Investments(Bodie, Kane, Marcus)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 09_lln_clt。本节的概念会成为理解它的输入,而不是孤立的名词。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 正态分布假设的危险:尾部概率对比¶
**峰度(Kurtosis)**是衡量尾部厚度的指标:
- 正态分布峰度 = 3(超额峰度 = 0)
- 实际金融数据超额峰度通常 > 3(肥尾)
超额峰度越大 → 极端事件(±3σ 以外)发生频率越高。
n = 10_000
# 正态分布(细尾)
normal_returns = np.random.normal(0, 0.01, n)
# 学生 t 分布(肥尾,自由度=3)
t_returns = np.random.standard_t(df=3, size=n) * 0.006
# 混合分布(模拟市场崩溃):95% 正常 + 5% 危机
mixed = np.concatenate([
np.random.normal(0, 0.008, int(n * 0.95)),
np.random.normal(-0.04, 0.025, int(n * 0.05))
])
np.random.shuffle(mixed)
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
for ax, data, title, color in zip(axes,
[normal_returns, t_returns, mixed],
['正态分布', '学生 t 分布 (df=3)', '混合分布(模拟危机)'],
['steelblue', 'darkorange', 'red']):
ax.hist(data, bins=100, density=True, alpha=0.7, color=color, edgecolor='none')
x = np.linspace(data.min(), data.max(), 200)
ax.plot(x, stats.norm.pdf(x, data.mean(), data.std()), 'k--', lw=2, label='正态拟合')
ax.set_title(f'{title}\n峰度={stats.kurtosis(data):.2f}')
ax.legend(fontsize=8); ax.set_xlim(-0.1, 0.1)
plt.suptitle('正态分布 vs 肥尾分布:峰度差异', fontsize=13)
plt.tight_layout(); plt.show()
2. QQ 图:直观诊断肥尾¶
如果数据服从正态分布,QQ 图应该是一条直线。肥尾数据在两端会向上/向下弯曲。
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for ax, data, title in zip(axes,
[normal_returns, t_returns, mixed],
['正态分布', 't分布(肥尾)', '混合分布']):
stats.probplot(data, dist='norm', plot=ax)
ax.set_title(f'QQ图:{title}')
ax.get_lines()[1].set_color('red')
plt.tight_layout(); plt.show()
print('QQ 图判读:两端偏离红线越多,肥尾越严重')
QQ 图判读:两端偏离红线越多,肥尾越严重
3. 极端事件频率:正态 vs 现实¶
以日收益率标准差 σ=1% 为例,计算不同极端事件在各分布下的预测频率:
sigma = 0.01
print(f'日收益率标准差 σ = {sigma:.1%}\n')
print(f'{'事件':25s} {'正态分布理论频率':20s} {'t分布(df=5)频率':20s}')
print('-' * 65)
events = [(-0.03, '单日跌 3% (3σ)'), (-0.05, '单日跌 5% (5σ)'),
(-0.10, '单日跌 10% (10σ)'), (-0.20, '单日跌 20% (20σ)')]
for threshold, label in events:
z = threshold / sigma
p_normal = stats.norm.cdf(z)
p_t = stats.t.cdf(z, df=5) # 自由度5的 t 分布
freq_normal = f'1/{1/p_normal:.0e}'.replace('/', ' 年一次' if 1/p_normal > 250 else ' 天')
years_normal = 1 / (p_normal * 252)
years_t = 1 / (p_t * 252)
print(f'{label:25s} 每 {years_normal:.1f} 年 每 {years_t:.1f} 年')
日收益率标准差 σ = 1.0% 事件 正态分布理论频率 t分布(df=5)频率 ----------------------------------------------------------------- 单日跌 3% (3σ) 每 2.9 年 每 0.3 年 单日跌 5% (5σ) 每 13843.5 年 每 1.9 年 单日跌 10% (10σ) 每 520778282162624462848.0 年 每 46.4 年 单日跌 20% (20σ) 每 144110227007404368736004373587456829506024729208898486292676143949773087913817533317120.0 年 每 1374.2 年
4. 用学生 t 分布建模肥尾收益率¶
# 用 MLE 拟合真实肥尾收益率
np.random.seed(42)
real_returns = np.random.standard_t(df=4, size=2000) * 0.008 # 模拟「真实」数据
# 拟合正态分布
norm_params = stats.norm.fit(real_returns)
# 拟合 t 分布
t_params = stats.t.fit(real_returns) # (df, loc, scale)
x = np.linspace(real_returns.min(), real_returns.max(), 300)
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(real_returns, bins=60, density=True, alpha=0.5, color='steelblue', label='模拟数据')
ax.plot(x, stats.norm.pdf(x, *norm_params), 'r-', lw=2, label=f'正态拟合')
ax.plot(x, stats.t.pdf(x, *t_params), 'g-', lw=2, label=f't 分布拟合 (df={t_params[0]:.2f})')
ax.set_xlim(-0.08, 0.08)
ax.legend(); ax.set_title('正态 vs 学生 t 分布拟合肥尾收益率')
ax.set_xlabel('日收益率'); plt.show()
# 比较尾部 VaR
alpha = 0.01 # 1% VaR
var_normal = stats.norm.ppf(alpha, *norm_params)
var_t = stats.t.ppf(alpha, *t_params)
print(f'1% VaR (正态分布): {var_normal:.4f} ({var_normal:.2%})')
print(f'1% VaR (t 分布): {var_t:.4f} ({var_t:.2%})')
print(f't 分布的 VaR 更大(更保守),更接近极端市场的真实情况')
1% VaR (正态分布): -0.0256 (-2.56%) 1% VaR (t 分布): -0.0291 (-2.91%) t 分布的 VaR 更大(更保守),更接近极端市场的真实情况
练习¶
- 对 SPY/AAPL 真实历史日收益率进行 Jarque-Bera 正态性检验,输出峰度和 p 值。
- 分别用 df=3、5、10、∞(正态)的 t 分布模拟 Monte Carlo,对比 1% CVaR 的差异。
- 研究「极值理论 GEV」分布(scipy.stats.genextreme),对收益率最大回撤序列进行拟合。
下一节 → 09_lln_clt.ipynb