In [1]:
Copied!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats, optimize
np.random.seed(42)
print('Libraries loaded')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats, optimize
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
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. MLE 基本原理¶
目标:找到参数 $\theta$ 使得观测数据 $\{x_1, ..., x_n\}$ 的联合概率最大。
对数似然函数(取 log 便于计算,不影响极值点位置): $$\log L(\theta) = \sum_{i=1}^n \log f(x_i; \theta)$$
MLE 估计量 $\hat{\theta}$ 是使对数似然最大化的参数值: $$\hat{\theta} = \arg\max_\theta \log L(\theta)$$
In [3]:
Copied!
# 演示:用 MLE 拟合正态分布
np.random.seed(42)
true_mu = 0.0005
true_sigma = 0.012
data = np.random.normal(true_mu, true_sigma, 252)
# 分析解(正态 MLE 就是样本均值和标准差)
mu_hat = data.mean()
sigma_hat = data.std(ddof=0) # MLE 用 n 而非 n-1 做分母
print(f'正态分布 MLE 估计:')
print(f' 真实 μ={true_mu:.5f} 估计 μ={mu_hat:.5f}')
print(f' 真实 σ={true_sigma:.5f} 估计 σ={sigma_hat:.5f}')
# 手动计算对数似然
def normal_log_likelihood(params, data):
mu, sigma = params
if sigma <= 0:
return np.inf
return -np.sum(stats.norm.logpdf(data, mu, sigma))
# 数值最值化验证
result = optimize.minimize(normal_log_likelihood, [0, 0.01],
args=(data,), method='Nelder-Mead')
print(f'\n数值 MLE 验证: μ={result.x[0]:.5f}, σ={result.x[1]:.5f}')
# 演示:用 MLE 拟合正态分布
np.random.seed(42)
true_mu = 0.0005
true_sigma = 0.012
data = np.random.normal(true_mu, true_sigma, 252)
# 分析解(正态 MLE 就是样本均值和标准差)
mu_hat = data.mean()
sigma_hat = data.std(ddof=0) # MLE 用 n 而非 n-1 做分母
print(f'正态分布 MLE 估计:')
print(f' 真实 μ={true_mu:.5f} 估计 μ={mu_hat:.5f}')
print(f' 真实 σ={true_sigma:.5f} 估计 σ={sigma_hat:.5f}')
# 手动计算对数似然
def normal_log_likelihood(params, data):
mu, sigma = params
if sigma <= 0:
return np.inf
return -np.sum(stats.norm.logpdf(data, mu, sigma))
# 数值最值化验证
result = optimize.minimize(normal_log_likelihood, [0, 0.01],
args=(data,), method='Nelder-Mead')
print(f'\n数值 MLE 验证: μ={result.x[0]:.5f}, σ={result.x[1]:.5f}')
正态分布 MLE 估计: 真实 μ=0.00050 估计 μ=0.00045 真实 σ=0.01200 估计 σ=0.01158 数值 MLE 验证: μ=0.00046, σ=0.01158
2. 学生 t 分布的 MLE:捕捉肥尾¶
In [4]:
Copied!
# 模拟真实的肥尾收益率(df=4 的 t 分布)
np.random.seed(42)
true_df = 4.5
fat_returns = np.random.standard_t(true_df, 500) * 0.010
# 用 scipy.stats 进行 MLE 拟合
# 正态分布拟合
norm_params = stats.norm.fit(fat_returns)
# t 分布拟合
t_params = stats.t.fit(fat_returns) # (df, loc, scale)
x = np.linspace(fat_returns.min(), fat_returns.max(), 300)
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(fat_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'正态 MLE')
ax.plot(x, stats.t.pdf(x, *t_params), 'g-', lw=2, label=f't MLE (df={t_params[0]:.2f})')
ax.legend(); ax.set_xlim(-0.06, 0.06)
ax.set_title('正态 vs 学生 t 分布:MLE 拟合肥尾收益率')
ax.set_xlabel('日收益率'); plt.show()
# 计算对数似然值进行模型比较
ll_norm = stats.norm.logpdf(fat_returns, *norm_params).sum()
ll_t = stats.t.logpdf(fat_returns, *t_params).sum()
print(f'正态分布对数似然: {ll_norm:.2f} (参数数量 k=2)')
print(f't 分布对数似然: {ll_t:.2f} (参数数量 k=3)')
# AIC = 2k - 2 log L (越小越好)
aic_norm = 2*2 - 2*ll_norm
aic_t = 2*3 - 2*ll_t
print(f'正态 AIC: {aic_norm:.2f} t AIC: {aic_t:.2f}')
print(f'最优模型: {"正态" if aic_norm < aic_t else "t 分布"}')
# 模拟真实的肥尾收益率(df=4 的 t 分布)
np.random.seed(42)
true_df = 4.5
fat_returns = np.random.standard_t(true_df, 500) * 0.010
# 用 scipy.stats 进行 MLE 拟合
# 正态分布拟合
norm_params = stats.norm.fit(fat_returns)
# t 分布拟合
t_params = stats.t.fit(fat_returns) # (df, loc, scale)
x = np.linspace(fat_returns.min(), fat_returns.max(), 300)
fig, ax = plt.subplots(figsize=(10, 5))
ax.hist(fat_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'正态 MLE')
ax.plot(x, stats.t.pdf(x, *t_params), 'g-', lw=2, label=f't MLE (df={t_params[0]:.2f})')
ax.legend(); ax.set_xlim(-0.06, 0.06)
ax.set_title('正态 vs 学生 t 分布:MLE 拟合肥尾收益率')
ax.set_xlabel('日收益率'); plt.show()
# 计算对数似然值进行模型比较
ll_norm = stats.norm.logpdf(fat_returns, *norm_params).sum()
ll_t = stats.t.logpdf(fat_returns, *t_params).sum()
print(f'正态分布对数似然: {ll_norm:.2f} (参数数量 k=2)')
print(f't 分布对数似然: {ll_t:.2f} (参数数量 k=3)')
# AIC = 2k - 2 log L (越小越好)
aic_norm = 2*2 - 2*ll_norm
aic_t = 2*3 - 2*ll_t
print(f'正态 AIC: {aic_norm:.2f} t AIC: {aic_t:.2f}')
print(f'最优模型: {"正态" if aic_norm < aic_t else "t 分布"}')
正态分布对数似然: 1378.41 (参数数量 k=2) t 分布对数似然: 1480.01 (参数数量 k=3) 正态 AIC: -2752.83 t AIC: -2954.02 最优模型: t 分布
3. GARCH 参数的 MLE 估计¶
GARCH(1,1) 的条件似然函数:
$$\log L = -\frac{1}{2}\sum_{t=1}^T \left[\log(2\pi) + \log\sigma_t^2 + \frac{\epsilon_t^2}{\sigma_t^2}\right]$$
其中 $\sigma_t^2 = \omega + \alpha \epsilon_{t-1}^2 + \beta \sigma_{t-1}^2$
In [5]:
Copied!
# 模拟 GARCH(1,1) 数据并用 MLE 估计参数
omega_true, alpha_true, beta_true = 5e-6, 0.10, 0.85
n = 1000
sigma2 = np.zeros(n)
eps = np.zeros(n)
sigma2[0] = omega_true / (1 - alpha_true - beta_true)
for t in range(1, n):
eps[t] = np.random.normal(0, np.sqrt(sigma2[t-1]))
sigma2[t] = omega_true + alpha_true*eps[t-1]**2 + beta_true*sigma2[t-1]
def garch_neg_loglik(params, returns):
omega, alpha, beta = params
if omega <= 0 or alpha <= 0 or beta <= 0 or alpha+beta >= 1:
return 1e10
n = len(returns)
sigma2 = np.zeros(n)
sigma2[0] = omega / (1 - alpha - beta)
for t in range(1, n):
sigma2[t] = omega + alpha*returns[t-1]**2 + beta*sigma2[t-1]
if np.any(sigma2 <= 0):
return 1e10
ll = -0.5 * (np.log(2*np.pi) + np.log(sigma2[1:]) + returns[1:]**2/sigma2[1:])
return -ll.sum()
result = optimize.minimize(
garch_neg_loglik, [1e-5, 0.08, 0.88], args=(eps,),
method='Nelder-Mead', options={'maxiter': 5000, 'xatol': 1e-7, 'fatol': 1e-7}
)
omega_est, alpha_est, beta_est = result.x
print('GARCH(1,1) MLE 估计结果:')
print(f' omega: 真实={omega_true:.2e} 估计={omega_est:.2e}')
print(f' alpha: 真实={alpha_true:.3f} 估计={alpha_est:.4f}')
print(f' beta: 真实={beta_true:.3f} 估计={beta_est:.4f}')
print(f' alpha+beta: 真实={alpha_true+beta_true:.3f} 估计={alpha_est+beta_est:.4f}')
# 模拟 GARCH(1,1) 数据并用 MLE 估计参数
omega_true, alpha_true, beta_true = 5e-6, 0.10, 0.85
n = 1000
sigma2 = np.zeros(n)
eps = np.zeros(n)
sigma2[0] = omega_true / (1 - alpha_true - beta_true)
for t in range(1, n):
eps[t] = np.random.normal(0, np.sqrt(sigma2[t-1]))
sigma2[t] = omega_true + alpha_true*eps[t-1]**2 + beta_true*sigma2[t-1]
def garch_neg_loglik(params, returns):
omega, alpha, beta = params
if omega <= 0 or alpha <= 0 or beta <= 0 or alpha+beta >= 1:
return 1e10
n = len(returns)
sigma2 = np.zeros(n)
sigma2[0] = omega / (1 - alpha - beta)
for t in range(1, n):
sigma2[t] = omega + alpha*returns[t-1]**2 + beta*sigma2[t-1]
if np.any(sigma2 <= 0):
return 1e10
ll = -0.5 * (np.log(2*np.pi) + np.log(sigma2[1:]) + returns[1:]**2/sigma2[1:])
return -ll.sum()
result = optimize.minimize(
garch_neg_loglik, [1e-5, 0.08, 0.88], args=(eps,),
method='Nelder-Mead', options={'maxiter': 5000, 'xatol': 1e-7, 'fatol': 1e-7}
)
omega_est, alpha_est, beta_est = result.x
print('GARCH(1,1) MLE 估计结果:')
print(f' omega: 真实={omega_true:.2e} 估计={omega_est:.2e}')
print(f' alpha: 真实={alpha_true:.3f} 估计={alpha_est:.4f}')
print(f' beta: 真实={beta_true:.3f} 估计={beta_est:.4f}')
print(f' alpha+beta: 真实={alpha_true+beta_true:.3f} 估计={alpha_est+beta_est:.4f}')
GARCH(1,1) MLE 估计结果: omega: 真实=5.00e-06 估计=2.27e-06 alpha: 真实=0.100 估计=0.0677 beta: 真实=0.850 估计=0.9086 alpha+beta: 真实=0.950 估计=0.9763
🎯 练习¶
- 对比 arch 库(专业 GARCH 估计)与我们手动 MLE 的参数估计结果,哪个更准确?
- 在 t 分布的 MLE 中,打印不同 df(1到30)下的对数似然值,绘制「对数似然 vs df」曲线找最优自由度。
- 实现信息矩阵方法:计算 MLE 估计量的 Hessian 矩阵,从而得到参数的标准误和置信区间。
下一节 → ../01_financial_concepts/08_heavy_tails.ipynb
In [ ]:
Copied!