概率论与统计学
预计学习时间:3-4 小时
难度:⭐⭐⭐
核心问题:如何在不确定的市场中用数学语言量化风险、评估策略的统计显著性?
概述
概率论与统计学是量化机器学习的基石。本节涵盖量化 ML 必备的概率统计工具:
- 概率分布族:正态、t、对数正态、泊松
- 贝叶斯定理:先验/后验/似然
- 大数定律与中心极限定理
- 假设检验:t 检验、p 值、IC 显著性
- 多重检验校正:Bonferroni、BH-FDR、Holm
- Bootstrap:IC 置信区间
- 蒙特卡洛:MC 模拟 VaR
- 极值理论 (EVT):POT 方法、GPD
- MLE 极大似然估计
- 偏度/峰度
- 稳健统计:MAD、Winsorize
一、概率分布族
1.1 正态分布
参数:均值 (位置)、标准差 (尺度)。金融含义:短期收益率常假设正态,但实际有厚尾。
1.2 t 分布
参数:自由度 。金融含义: 越小尾部越厚,更适合描述极端收益。
1.3 对数正态分布
若 ,则 。金融含义:股票价格(不能为负)天然服从对数正态。
1.4 泊松分布
参数:(均值=方差)。金融含义:建模交易笔数、违约事件数等离散计数。
1.5 四种分布的 Python 生成与可视化
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
np.random.seed(42)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. 正态分布:模拟日收益率
mu, sigma = 0.0005, 0.02
normal_data = np.random.normal(mu, sigma, 5000)
x_n = np.linspace(mu - 4*sigma, mu + 4*sigma, 200)
axes[0, 0].hist(normal_data, bins=60, density=True, alpha=0.6, color='steelblue')
axes[0, 0].plot(x_n, stats.norm.pdf(x_n, mu, sigma), 'r-', lw=2)
axes[0, 0].set_title(f'正态分布 N({mu}, {sigma}²) — 日收益率')
# 2. t 分布:厚尾收益
nu = 5 # 自由度(金融数据通常 3-7)
t_data = stats.t.rvs(df=nu, scale=sigma, size=5000)
x_t = np.linspace(-0.1, 0.1, 200)
axes[0, 1].hist(t_data, bins=60, density=True, alpha=0.6, color='darkorange')
axes[0, 1].plot(x_t, stats.t.pdf(x_t, df=nu, scale=sigma), 'r-', lw=2)
axes[0, 1].plot(x_n, stats.norm.pdf(x_n, 0, sigma), 'b--', lw=1.5, label='正态对比')
axes[0, 1].set_title(f't 分布 (ν={nu}) — 厚尾收益')
axes[0, 1].legend()
# 3. 对数正态分布:股票价格
logn_data = np.random.lognormal(mean=0.05, sigma=0.3, size=5000)
x_ln = np.linspace(0.5, 3.5, 200)
axes[1, 0].hist(logn_data, bins=60, density=True, alpha=0.6, color='green')
axes[1, 0].plot(x_ln, stats.lognorm.pdf(x_ln, s=0.3, scale=np.exp(0.05)), 'r-', lw=2)
axes[1, 0].set_title('对数正态分布 — 股票价格')
# 4. 泊松分布:交易笔数
lam = 15 # 每分钟平均 15 笔
poisson_data = np.random.poisson(lam, 5000)
x_p = np.arange(0, 35)
axes[1, 1].hist(poisson_data, bins=range(0, 36), density=True, alpha=0.6, color='purple')
axes[1, 1].plot(x_p, stats.poisson.pmf(x_p, lam), 'ro-', lw=2, ms=4)
axes[1, 1].set_title(f'泊松分布 (λ={lam}) — 每分钟交易笔数')
plt.tight_layout()
plt.show()
# 比较尾部厚度
print("尾部概率 P(|X| > 2σ):")
print(f" 正态分布: {2 * (1 - stats.norm.cdf(2)):.4f}")
print(f" t(5)分布: {2 * (1 - stats.t.cdf(2, df=5)):.4f}")
print(f" t(3)分布: {2 * (1 - stats.t.cdf(2, df=3)):.4f}")二、贝叶斯定理
2.1 公式
| 术语 | 符号 | 金融含义 |
|---|---|---|
| 先验概率 | 因子有效性的初始信念 | |
| 似然 | $P(B | A)$ |
| 后验概率 | $P(A | B)$ |
| 证据 | 归一化常数 |
2.2 贝叶斯更新公式
收到新数据后,后验成为下一次的先验:
import numpy as np
from scipy import stats
# ============================================================
# 贝叶斯更新:评估因子是否有效
# ============================================================
np.random.seed(42)
# 场景:检验一个因子的 IC 是否显著不为零
# 先验:因子 IC ~ N(0, 0.02)(先验认为因子大概率无效)
prior_mu = 0.0
prior_sigma = 0.02
# 观察到 12 个月的月度 IC 值
observed_ics = np.array([0.035, 0.028, -0.005, 0.042, 0.031,
0.018, -0.002, 0.039, 0.025, 0.033,
0.015, 0.027])
# 假设观测噪声 sigma_obs = 0.01
sigma_obs = 0.01
# 贝叶斯更新(共轭先验:正态-正态模型)
# 后验参数: sigma_post^2 = 1/(1/sigma_prior^2 + n/sigma_obs^2)
# mu_post = sigma_post^2 * (mu_prior/sigma_prior^2 + sum(x)/sigma_obs^2)
n = len(observed_ics)
sigma_post = 1.0 / np.sqrt(1.0 / prior_sigma**2 + n / sigma_obs**2)
mu_post = sigma_post**2 * (prior_mu / prior_sigma**2 + observed_ics.sum() / sigma_obs**2)
print("贝叶斯更新结果:")
print(f" 先验: IC ~ N({prior_mu}, {prior_sigma}²)")
print(f" 样本均值 IC: {observed_ics.mean():.4f}")
print(f" 后验: IC ~ N({mu_post:.4f}, {sigma_post:.4f}²)")
print(f" 后验 P(IC > 0): {1 - stats.norm.cdf(0, mu_post, sigma_post):.4f}")
print(f" 后验 P(IC > 0.02): {1 - stats.norm.cdf(0.02, mu_post, sigma_post):.4f}")三、大数定律与中心极限定理
3.1 大数定律 (LLN)
弱大数定律:当 时,样本均值依概率收敛到总体均值:
金融含义:回测时间越长,收益率的样本均值越接近真实期望值。
3.2 中心极限定理 (CLT)
无论 服从什么分布,当 足够大时,样本均值近似正态:
金融含义:IC 的样本均值在大样本下近似正态,这是IC t 检验的理论基础。
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
# CLT 演示:用泊松分布(非正态)的样本均值
n_simulations = 10000
sample_sizes = [5, 30, 100, 500]
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()
for idx, n in enumerate(sample_sizes):
# 从泊松分布(λ=2)中抽取 n 个样本,重复 10000 次
sample_means = [np.random.poisson(2, n).mean() for _ in range(n_simulations)]
axes[idx].hist(sample_means, bins=50, density=True, alpha=0.7)
axes[idx].set_title(f'n = {n}, 均值={np.mean(sample_means):.3f}, '
f'标准差={np.std(sample_means):.3f}')
axes[idx].axvline(x=2, color='red', linestyle='--', label='μ=2')
axes[idx].legend()
plt.suptitle('中心极限定理:泊松分布样本均值的分布', fontsize=14)
plt.tight_layout()
plt.show()四、假设检验
4.1 t 检验与 p 值
IC 的 t 检验:
- : 真实 IC = 0(因子无效)
- : 真实 IC ≠ 0(因子有效)
p 值:在 为真时,观察到当前或更极端结果的概率。 时拒绝 。
import numpy as np
from scipy import stats
np.random.seed(42)
# ============================================================
# IC 显著性检验
# ============================================================
# 模拟 24 个月的月度 IC
true_ic = 0.03 # 真实 IC
ic_noise = 0.02 # IC 的月度波动
n_months = 24
observed_ics = np.random.normal(true_ic, ic_noise, n_months)
# t 检验
t_stat, p_value = stats.ttest_1samp(observed_ics, 0)
# 手动计算
ic_mean = observed_ics.mean()
ic_std = observed_ics.std(ddof=1)
ic_se = ic_std / np.sqrt(n_months)
t_manual = ic_mean / ic_se
print("IC 显著性检验结果:")
print(f" 样本月数: {n_months}")
print(f" IC 均值: {ic_mean:.4f}")
print(f" IC 标准差: {ic_std:.4f}")
print(f" IC 标准误: {ic_se:.4f}")
print(f" t 统计量: {t_stat:.4f} (手动: {t_manual:.4f})")
print(f" p 值: {p_value:.6f}")
if p_value < 0.05:
print(f" 结论: p < 0.05,因子【显著有效】")
else:
print(f" 结论: p >= 0.05,因子【不显著】")
# IC 的 IR (Information Ratio) = IC_mean / IC_std
ir = ic_mean / ic_std
print(f" IR (IC均值/IC标准差): {ir:.2f}")
print(f" IC > 0 的月份占比: {np.mean(observed_ics > 0):.2%}")五、多重检验校正
5.1 为什么必须校正?
当你同时检验 100 个因子时,即使全部无效,期望也有约 5 个因子 (假阳性)。这就是多重比较问题。
5.2 三种校正方法
| 方法 | 公式 | 特点 |
|---|---|---|
| Bonferroni | 最保守,控制 Family-wise Error Rate | |
| Holm | 逐步 Bonferroni,比 Bonferroni 更有功效 | 控制 FWER,比 Bonferroni 检验力更高 |
| BH-FDR | Benjamini-Hochberg,控制错误发现率 | 控制期望假阳性比例,适合因子挖掘 |
import numpy as np
from scipy import stats
np.random.seed(42)
# ============================================================
# 多重检验校正:100 个因子中筛选有效因子
# ============================================================
n_factors = 100
n_months = 60 # 5 年月度数据
# 模拟:10 个真实有效因子 + 90 个无效因子
true_ics = np.zeros(n_factors)
true_ics[:10] = np.random.uniform(0.02, 0.06, 10)
# 生成 IC 数据
ics_data = np.random.normal(true_ics, 0.03, (n_months, n_factors))
# 计算每个因子的 p 值
p_values = np.array([
stats.ttest_1samp(ics_data[:, i], 0)[1]
for i in range(n_factors)
])
# 方法 1: 未校正
sig_uncorrected = np.sum(p_values < 0.05)
print(f"未校正 (α=0.05): {sig_uncorrected} 个因子显著")
print(f" 其中真实有效: {np.sum(p_values[:10] < 0.05)} / 10")
print(f" 其中假阳性: {np.sum(p_values[10:] < 0.05)} / 90")
# 方法 2: Bonferroni 校正
alpha_bonf = 0.05 / n_factors
sig_bonf = np.sum(p_values < alpha_bonf)
print(f"\nBonferroni (α={alpha_bonf:.6f}): {sig_bonf} 个因子显著")
# 方法 3: Holm 校正
sorted_idx = np.argsort(p_values)
holm_p = np.zeros(n_factors)
for rank, idx in enumerate(sorted_idx):
holm_p[idx] = min(p_values[idx] * (n_factors - rank), 1.0)
if rank > 0:
holm_p[idx] = min(holm_p[idx], holm_p[sorted_idx[rank - 1]])
sig_holm = np.sum(holm_p < 0.05)
print(f"Holm 校正: {sig_holm} 个因子显著")
# 方法 4: BH-FDR 校正
sorted_p = np.sort(p_values)
bh_threshold = 0.05 * np.arange(1, n_factors + 1) / n_factors
max_idx = np.max(np.where(sorted_p <= bh_threshold))
bh_crit = sorted_p[max_idx] if max_idx > 0 else 0
sig_bh = np.sum(p_values <= bh_crit)
print(f"BH-FDR (q=0.05): {sig_bh} 个因子显著")
print(f"\n{'方法':<20} {'显著数':<8} {'假阳性估计'}")
print("-" * 45)
print(f"{'未校正':<20} {sig_uncorrected:<8} {sig_uncorrected - 10}")
print(f"{'Bonferroni':<20} {sig_bonf:<8} {max(0, sig_bonf - 10)}")
print(f"{'Holm':<20} {sig_holm:<8} {max(0, sig_holm - 10)}")
print(f"{'BH-FDR':<20} {sig_bh:<8} {max(0, sig_bh - 10)}")六、Bootstrap 置信区间
Bootstrap 通过重采样估计统计量的分布,无需分布假设。
import numpy as np
np.random.seed(42)
# ============================================================
# Bootstrap: IC 均值的 95% 置信区间
# ============================================================
n_months = 60
true_ic = 0.03
ic_series = np.random.normal(true_ic, 0.025, n_months)
n_bootstrap = 10000
bootstrap_means = np.zeros(n_bootstrap)
for i in range(n_bootstrap):
# 有放回抽样
sample = np.random.choice(ic_series, size=n_months, replace=True)
bootstrap_means[i] = sample.mean()
# 95% 置信区间(百分位法)
ci_lower = np.percentile(bootstrap_means, 2.5)
ci_upper = np.percentile(bootstrap_means, 97.5)
ci_mean = bootstrap_means.mean()
print("Bootstrap IC 置信区间:")
print(f" 样本 IC 均值: {ic_series.mean():.4f}")
print(f" Bootstrap 均值: {ci_mean:.4f}")
print(f" 95% CI: [{ci_lower:.4f}, {ci_upper:.4f}]")
print(f" CI 不包含 0: {'是' if ci_lower > 0 else '否'} → "
f"因子{'显著' if ci_lower > 0 else '不显著'}")
# 对比正态近似 CI
se = ic_series.std(ddof=1) / np.sqrt(n_months)
norm_ci_lower = ic_series.mean() - 1.96 * se
norm_ci_upper = ic_series.mean() + 1.96 * se
print(f"\n正态近似 CI: [{norm_ci_lower:.4f}, {norm_ci_upper:.4f}]")七、蒙特卡洛模拟 VaR
import numpy as np
np.random.seed(42)
# ============================================================
# 蒙特卡洛模拟 VaR 和 ES (Expected Shortfall)
# ============================================================
# 组合参数
n_assets = 5
weights = np.array([0.3, 0.25, 0.2, 0.15, 0.1])
mu = np.array([0.0005, 0.0003, 0.0006, 0.0002, 0.0004])
sigma_matrix = np.array([
[0.0400, 0.0060, 0.0080, 0.0030, 0.0040],
[0.0060, 0.0900, 0.0050, 0.0020, 0.0030],
[0.0080, 0.0050, 0.0600, 0.0040, 0.0060],
[0.0030, 0.0020, 0.0040, 0.0300, 0.0020],
[0.0040, 0.0030, 0.0060, 0.0020, 0.0500]
])
# 蒙特卡洛模拟
n_simulations = 100000
portfolio_value = 1_000_000 # 100 万组合
# 使用 Cholesky 分解生成相关正态随机数
L = np.linalg.cholesky(sigma_matrix)
z = np.random.normal(0, 1, (n_simulations, n_assets))
correlated_returns = z @ L.T + mu # (n_sim, n_assets)
# 组合收益
portfolio_returns = correlated_returns @ weights
# VaR 和 ES 计算
confidence_levels = [0.95, 0.99]
for cl in confidence_levels:
var = np.percentile(portfolio_returns, (1 - cl) * 100)
es = portfolio_returns[portfolio_returns <= var].mean()
var_amount = var * portfolio_value
es_amount = es * portfolio_value
print(f"\n{'='*45}")
print(f"置信水平: {cl:.0%}")
print(f" VaR: {var:.6f} → 亏损 {var_amount:,.0f} 元")
print(f" ES: {es:.6f} → 亏损 {es_amount:,.0f} 元")
print(f" VaR/ES 比: {abs(var/es):.2%}")
# 与参数法 VaR 对比
port_mean = weights @ mu
port_var = weights @ sigma_matrix @ weights
port_std = np.sqrt(port_var)
param_var_95 = port_mean - 1.645 * port_std
print(f"\n参数法 VaR(95%): {param_var_95:.6f}")
print(f"蒙特卡洛 VaR(95%): {np.percentile(portfolio_returns, 5):.6f}")八、极值理论 (EVT)
8.1 POT 方法
Peak-Over-Threshold:只对超过阈值的极端损失建模。
8.2 广义 Pareto 分布 (GPD)
超过阈值 的损失 服从 GPD:
参数:形状参数 ( 为厚尾)、尺度参数 。
import numpy as np
from scipy import stats, optimize
np.random.seed(42)
# ============================================================
# EVT POT 方法:估计尾部风险
# ============================================================
# 模拟厚尾收益(t 分布)
n = 2000
returns = stats.t.rvs(df=5, scale=0.02, size=n)
# 选择阈值:取 90 分位数
threshold = np.percentile(returns, 10) # 左尾(损失端)
exceedances = -returns[returns < threshold] - (-threshold) # 超额损失(正值)
print(f"阈值: {threshold:.4f}")
print(f"超额次数: {len(exceedances)} ({len(exceedances)/n:.1%})")
# MLE 估计 GPD 参数
def gpd_nll(params, data):
"""GPD 负对数似然"""
xi, sigma = params
if sigma <= 0:
return 1e10
n = len(data)
if abs(xi) < 1e-8: # 指数分布情形
return n * np.log(sigma) + data.sum() / sigma
else:
z = 1 + xi * data / sigma
if np.any(z <= 0):
return 1e10
return n * np.log(sigma) + (1/xi + 1) * np.sum(np.log(z))
# 优化
result = optimize.minimize(
gpd_nll, x0=[0.3, 0.01], args=(exceedances,),
method='Nelder-Mead'
)
xi_hat, sigma_hat = result.x
print(f"\nGPD 参数估计:")
print(f" 形状参数 ξ = {xi_hat:.4f} (ξ>0 → 厚尾)")
print(f" 尺度参数 σ = {sigma_hat:.4f}")
# 使用 EVT 估计极端分位数
def evt_var(xi, sigma, threshold, n, n_exceed, cl=0.99):
"""EVT VaR"""
p_tail = n_exceed / n # 超过阈值的比例
q = 1 - cl
var = threshold - sigma / xi * ((p_tail / q)**(-xi) - 1)
return var
def evt_es(xi, sigma, threshold, n, n_exceed, cl=0.99):
"""EVT ES (Expected Shortfall)"""
var = evt_var(xi, sigma, threshold, n, n_exceed, cl)
es = var / (1 - xi) + (sigma - xi * threshold) / (1 - xi)
return es
n_exceed = len(exceedances)
evt_var_99 = evt_var(xi_hat, sigma_hat, threshold, n, n_exceed, 0.99)
evt_es_99 = evt_es(xi_hat, sigma_hat, threshold, n, n_exceed, 0.99)
print(f"\nEVT 尾部风险估计:")
print(f" VaR(99%): {evt_var_99:.4f}")
print(f" ES(99%): {evt_es_99:.4f}")
print(f" 历史模拟 VaR(99%): {np.percentile(returns, 1):.4f}")
print(f" 正态假设 VaR(99%): {np.mean(returns) - 2.326 * np.std(returns):.4f}")
print(f"\nEVT 考虑了更厚的尾部,VaR 绝对值通常大于正态假设")九、MLE 极大似然估计
原理:找到使观察数据出现概率最大的参数值。
import numpy as np
from scipy import stats, optimize
np.random.seed(42)
# ============================================================
# MLE 示例:估计收益率分布参数
# ============================================================
# 生成服从 N(0.001, 0.02²) 的数据
true_mu, true_sigma = 0.001, 0.02
data = np.random.normal(true_mu, true_sigma, 500)
# 对数似然函数
def normal_log_likelihood(params, data):
mu, sigma = params
if sigma <= 0:
return -np.inf
return np.sum(stats.norm.logpdf(data, mu, sigma))
# MLE 优化
result = optimize.minimize(
lambda p, d: -normal_log_likelihood(p, d),
x0=[0, 0.01],
args=(data,),
method='Nelder-Mead'
)
mle_mu, mle_sigma = result.x
print("正态分布 MLE 估计:")
print(f" 真实参数: μ={true_mu}, σ={true_sigma}")
print(f" MLE 估计: μ={mle_mu:.4f}, σ={mle_sigma:.4f}")
print(f" 样本统计: μ={data.mean():.4f}, σ={data.std():.4f}")
print(f" (对正态分布,MLE 等价于样本均值和样本标准差)")十、偏度与峰度
import numpy as np
from scipy import stats
np.random.seed(42)
n = 5000
# 生成不同分布的收益率
normal = np.random.normal(0, 0.02, n)
t_dist = stats.t.rvs(df=5, scale=0.02, size=n)
skewed = np.random.lognormal(0, 0.5, n) - np.exp(0.125) # 中心化
print(f"{'分布':<15} {'偏度':<10} {'超额峰度':<12} {'JB统计量':<12} {'p值'}")
print("-" * 65)
for name, data in [("正态", normal), ("t(5)", t_dist), ("对数正态", skewed)]:
skew = stats.skew(data)
kurt = stats.kurtosis(data) # 超额峰度(已减3)
jb_stat, jb_pval = stats.jarque_bera(data)
print(f"{name:<15} {skew:<10.4f} {kurt:<12.4f} {jb_stat:<12.2f} {jb_pval:.6f}")
# 正态分布: 偏度≈0, 超额峰度≈0
# t 分布: 偏度≈0, 超额峰度>0 (厚尾)
# 对数正态: 偏度>0 (右偏), 超额峰度>0十一、稳健统计
11.1 MAD (Median Absolute Deviation)
当 时,。
11.2 Winsorize (缩尾处理)
将超出百分位范围的极端值替换为边界值,而非直接删除。
import numpy as np
from scipy import stats
np.random.seed(42)
# ============================================================
# 稳健统计 vs 传统统计
# ============================================================
n = 200
clean_data = np.random.normal(0, 1, n)
# 加入极端值(模拟异常数据)
outlier_indices = np.random.choice(n, 5, replace=False)
contaminated = clean_data.copy()
contaminated[outlier_indices] = np.array([10, -12, 8, -15, 20])
# 对比统计量
print(f"{'统计量':<20} {'干净数据':<12} {'污染数据':<12} {'稳健估计'}")
print("-" * 60)
# 均值 vs 中位数
print(f"{'均值/中位数':<20} {clean_data.mean():<12.4f} "
f"{contaminated.mean():<12.4f} {np.median(contaminated):.4f}")
# 标准差 vs MAD
clean_std = clean_data.std()
contaminated_std = contaminated.std()
mad = np.median(np.abs(contaminated - np.median(contaminated)))
robust_std = 1.4826 * mad
print(f"{'标准差/稳健标准差':<20} {clean_std:<12.4f} "
f"{contaminated_std:<12.4f} {robust_std:.4f}")
# Winsorize
winsorized = stats.mstats.winsorize(contaminated, limits=[0.01, 0.01])
print(f"{'缩尾后均值':<20} {'—':<12} {winsorized.mean():.4f}")
print(f"{'缩尾后标准差':<20} {'—':<12} {winsorized.std():.4f}")
print(f"\n稳健统计对异常值不敏感,更适合因子分析和风险建模")总结
| 工具 | 核心功能 | 量化应用 |
|---|---|---|
| 概率分布族 | 描述随机变量 | 收益率建模、VaR |
| 贝叶斯定理 | 更新信念 | 因子有效性评估 |
| CLT | 样本均值正态性 | 统计推断基础 |
| t 检验 | 假设检验 | IC 显著性 |
| 多重校正 | 控制假阳性 | 因子筛选 |
| Bootstrap | 重采样推断 | IC 置信区间 |
| 蒙特卡洛 | 随机模拟 | VaR/ES |
| EVT | 尾部分布建模 | 极端风险管理 |
| MLE | 参数估计 | 分布拟合 |
| 偏度/峰度 | 分布形态 | 收益率特征 |
| 稳健统计 | 抗异常值 | 因子清洗、风险估计 |