概率论与统计学

预计学习时间: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(BA)$
后验概率$P(AB)$
证据归一化常数

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-FDRBenjamini-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参数估计分布拟合
偏度/峰度分布形态收益率特征
稳健统计抗异常值因子清洗、风险估计