1.4 概率论、随机过程与模拟:量化交易的“排雷演习”¶
“如果历史再来一次,我的策略还会赚钱吗?”
量化交易本质上是在不确定性中寻求概率优势。传统的金融学入门往往假设一切都是完美的“正态分布”和简单的“线性相关”。但在真实的极端市场中,这些假设是不堪一击的。
这一节,我们将深入 QuantEcon 的核心思想,揭开随机微积分、相依结构和贝叶斯推断的面纱。别怕,我们会用最直白的语言和代码来可视化这些高阶数学定理。
核心知识点¶
- 拆穿正态分布的假象:从直观直方图到严谨的 Q-Q Plot。
- 微积分的进化 (SDE & Itô's Lemma):普通微积分在金融里为什么行不通?认识 GBM (几何布朗运动) 和 OU (均值回归) 过程。
- 相关系数的陷阱 (Copulas):平时不相关,一跌一起跌?引入 Copula 揭示尾部相关性。
- 终极模拟器:从参数化蒙特卡洛到真实的 Bootstrapping,量化尾部风险 (CVaR)。
- 贝叶斯推断实战:用 Beta 分布动态更新你对“策略胜率”的信仰。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats as stats
import yfinance as yf
import seaborn as sns
plt.style.use('seaborn-v0_8-muted')
plt.rcParams['figure.figsize'] = (12, 6)
print("环境准备就绪 ✅")
环境准备就绪 ✅
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 拆穿正态分布的假象¶
在金融领域,假设收益率是正态分布是最危险的错误之一,这就好比你为了设计桥梁,假设风力永远是平均的微风,而忽略了台风(黑天鹅)。
# 获取一段真实的标普 500 收益率
spy = yf.download('SPY', start='2015-01-01', end='2024-01-01', progress=False)['Close'].squeeze()
returns = spy.pct_change().dropna()
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
# 1. 密度分布图
sns.histplot(returns, bins=100, kde=True, stat="density", ax=ax[0], color="skyblue", label="真实收益率")
# 绘制与之对比的标准正态分布
x = np.linspace(returns.min(), returns.max(), 100)
ax[0].plot(x, stats.norm.pdf(x, returns.mean(), returns.std()), color="red", lw=2, label="拟合的理论正态分布")
ax[0].set_title("直方图对比:中间更尖,两边更长(尖峰厚尾)")
ax[0].legend()
# 2. Q-Q Plot (Quantile-Quantile Plot)
# Q-Q 图是检验分布最严谨的图形工具:如果点全落在红线上,说明是完美正态。
stats.probplot(returns, dist="norm", plot=ax[1])
ax[1].set_title("Q-Q Plot:尾部的散点严重偏离红线(黑天鹅频发)")
plt.tight_layout()
plt.show()
print("数学洞察:真实的金融市场具有『尖峰厚尾』(Leptokurtic)特征,即平时波动极小,但一旦爆发剧烈波动,其幅度远超正态分布预期。")
数学洞察:真实的金融市场具有『尖峰厚尾』(Leptokurtic)特征,即平时波动极小,但一旦爆发剧烈波动,其幅度远超正态分布预期。
2. 随机微积分进阶:SDE 与伊藤引理 (Itô's Lemma)¶
传统的牛顿微积分 $y' = dy/dx$ 是连续平滑的。但金融资产的价格充满无数微小的随机跳动(锯齿状)。为了描述这种“带噪声的微积分”,我们需要随机微分方程 (SDE)。
核心差异(伊藤引理的直观内涵):由于价格的变化中包含随机布朗运动项 $dW$,该噪声的波动幅度是时间间隔的平方根( $\sqrt{dt}$ )。这就使得关于价格变量的二阶导数也会对一阶变化产生影响,传统的泰勒展开必须保留至二阶项!
两个最经典的随机过程:¶
① 几何布朗运动 (GBM) - 用于模拟股票趋势¶
$$dS_t = \mu S_t dt + \sigma S_t dW_t$$
- $\mu S_t dt$: 确定的漂移项(每隔一段时间按无风险利率或趋势上涨)。
- $\sigma S_t dW_t$: 随机扩散项(抛硬币式的股价波动噪声)。
② Ornstein-Uhlenbeck (OU) 过程 - 用于均值回归与配对交易¶
$$dx_t = \theta (\mu - x_t) dt + \sigma dW_t$$
- 当 $x_t$ 大于均值 $\mu$ 时,$(\mu - x_t)$ 是负数,像一根橡皮筋一样把价格往回拉(回归均值)。$\theta$ 决定了橡皮筋有多紧。
def sim_sde_paths(n_steps=1000, dt=0.01):
t = np.linspace(0, n_steps*dt, n_steps)
# 生成标准的布朗运动增量(白噪声乘上时间步长的平方根)
dW = np.random.normal(0, np.sqrt(dt), n_steps)
# 1. GBM 模拟 (股票趋势)
mu_gbm, sigma_gbm = 0.05, 0.2
S = np.zeros(n_steps)
S[0] = 100
for i in range(1, n_steps):
S[i] = S[i-1] + mu_gbm * S[i-1] * dt + sigma_gbm * S[i-1] * dW[i]
# 2. OU 过程模拟 (配对交易价差、VIX 指数)
theta_ou, mu_ou, sigma_ou = 2.0, 0, 0.3
X = np.zeros(n_steps)
X[0] = 2.0 # 初始偏离极高
for i in range(1, n_steps):
X[i] = X[i-1] + theta_ou * (mu_ou - X[i-1]) * dt + sigma_ou * dW[i]
return t, S, X
t, path_gbm, path_ou = sim_sde_paths()
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
ax[0].plot(t, path_gbm, color='darkblue')
ax[0].set_title("几何布朗运动 (GBM):带漂移的随机漫步 (不归路)")
ax[0].grid(alpha=0.3)
ax[1].plot(t, path_ou, color='darkred')
ax[1].axhline(0, color='black', linestyle='--', label="均值回归中心 (\mu=0)")
ax[1].set_title("OU 过程 (均值回归):被橡皮筋拉扯的随机波动")
ax[1].legend()
ax[1].grid(alpha=0.3)
plt.show()
<>:30: SyntaxWarning: invalid escape sequence '\m' <>:30: SyntaxWarning: invalid escape sequence '\m' C:\Users\DELL\AppData\Local\Temp\ipykernel_39516\3667307583.py:30: SyntaxWarning: invalid escape sequence '\m' ax[1].axhline(0, color='black', linestyle='--', label="均值回归中心 (\mu=0)")
3. 分布相依结构:相关系数的谎言与 Copula¶
金融危机时,为什么“原本分散化投资組合”里的所有资产突然一起暴跌? 因为传统的 皮尔逊相关系数 ($Pearson\ \rho$) 只能捕捉整体线性相关性,它无法区分:这些资产是在平静时相关,还是在股灾时相关?
Copula 函数 的出现解决了这个问题。它把多个资产的“边缘分布”和“相依结构”剥离开来。不同品种的 Copula 具有不同的极值特征:
- Gaussian Copula:在两端(大涨/大跌时)相关性会减弱。2008年次贷危机的量化核弹(Li's Copula)过度使用了它。
- Student-t Copula:强关联的厚尾。即使平时相关性不高,一旦危机爆发,极端的左下角(尾部崩溃)呈现强相关。(“一跌一起跌”数学化)。
from scipy.stats import multivariate_normal, multivariate_t
# 假定两者整体相关系数均为 0.5
cov_matrix = [[1.0, 0.5], [0.5, 1.0]]
# 1. 生成二维正态高斯分布样本
samples_gaussian = multivariate_normal.rvs(mean=[0, 0], cov=cov_matrix, size=5000, random_state=42)
# 2. 生成二维学生t分布样本 (自由度低,极端值依赖强)
samples_t = multivariate_t.rvs(loc=[0, 0], shape=cov_matrix, df=3, size=5000, random_state=42)
fig, ax = plt.subplots(1, 2, figsize=(14, 6), sharex=True, sharey=True)
# 绘制散点图
ax[0].scatter(samples_gaussian[:, 0], samples_gaussian[:, 1], alpha=0.1, s=10)
ax[0].set_title("Gaussian Copula 结构: 左下/右上角落较为平滑稀疏")
ax[0].grid(alpha=0.3)
ax[1].scatter(samples_t[:, 0], samples_t[:, 1], alpha=0.1, s=10, color='purple')
ax[1].set_title("Student-t Copula 结构: 左下角极端踩踏事件极其密集 (尾部相依)")
ax[1].grid(alpha=0.3)
# 框出极度恐慌区域(左下角:两大资产同时暴跌超过 3 个标准差)
for axis in ax:
rect = plt.Rectangle((-10, -10), 7, 7, fill=False, edgecolor='red', lw=2, linestyle='--')
axis.add_patch(rect)
plt.xlim(-8, 8)
plt.ylim(-8, 8)
plt.show()
print("结论:评估组合风险时,决不能仅看相关系数矩阵,而忽略了在暴跌时突然飙升的‘尾部相依性’。")
结论:评估组合风险时,决不能仅看相关系数矩阵,而忽略了在暴跌时突然飙升的‘尾部相依性’。
4. 极致回测模拟:Bootstrapping 求 VaR/CVaR¶
知道了理想的数学公式,但在实战中,最好的分布假设就是“不作假设”。 Bootstrapping(自助法采样) 从我们拥有的真实收益率中不断“随机乱切、重拼”,它自动保留了真实市场的偏度、厚尾和部分相依结构。
VaR (Value at Risk): 在 95% 信心水准下,我最多亏多少? CVaR (Conditional VaR, 预期尾部损失): 如果今天太倒霉跌破了 VaR,平均起来我会亏多惨?
# 提取 SPY 单日收益率数据
historical_rets = returns.values
n_days = 252 # 模拟未来 1 年
n_sims = 5000 # 生成 5000 种未来平行宇宙
# Bootstrapping 获取 5000 组 * 252天的 收益率矩阵
simulated_rets_paths = np.random.choice(historical_rets, size=(n_days, n_sims), replace=True)
# 计算期末总收益率
sim_cumulative_rets = (1 + simulated_rets_paths).cumprod(axis=0)[-1, :] - 1
alpha = 0.05 # 95% 置信度
var_95 = np.percentile(sim_cumulative_rets, alpha * 100)
cvar_95 = sim_cumulative_rets[sim_cumulative_rets <= var_95].mean()
plt.figure(figsize=(10, 5))
sns.histplot(sim_cumulative_rets, bins=60, kde=True, color='teal')
plt.axvline(var_95, color='orange', linestyle='--', lw=2, label=f'95% VaR: {var_95:.2%}')
plt.axvline(cvar_95, color='red', lw=2, label=f'95% CVaR: {cvar_95:.2%}')
plt.title("Bootstrapping 模拟持仓1年的收益分布与极端风险")
plt.legend()
plt.show()
print(f"统计推断结论:在未来一年里,我们有 5% 的概率亏损超过 {abs(var_95):.2%};一旦踏入这悲惨的 5% 概率领域,平均亏损将达到 {abs(cvar_95):.2%}。")
统计推断结论:在未来一年里,我们有 5% 的概率亏损超过 17.38%;一旦踏入这悲惨的 5% 概率领域,平均亏损将达到 23.53%。
5. 贝叶斯思维:动态更新你对策略胜率的认知¶
你在历史回测里跑到 60% 的胜率。上线第一天,连亏 3 笔。 你是该相信回测,继续抗单?还是立刻止损?
贝叶斯定理 提供了一套最严谨的“世界观更新指南”: 后验观点 (Posterior) = 旧有成见 (Prior) $\times$ 新数据的说服力 (Likelihood)
对于“胜率”这样在 $0$ 到 $1$ 之间的概率,最常使用的旧有成见分布模型是 Beta 分布 ($Beta(\alpha, \beta)$),因为它是二项分布的共轭先验:
- 假设回测赚了 60 次,亏了 40 次。此时先验信念为 $Beta(60, 40)$。
- 实盘中随后遇到 2 赚 8 亏。你的信念应该无缝更新为 $Beta(60+2, 40+8)$ 即 $Beta(62, 48)$。
from scipy.stats import beta
x = np.linspace(0, 1, 500)
prior_wins, prior_losses = 60, 40
prior_pdf = beta.pdf(x, prior_wins, prior_losses)
# 实盘发生了严酷的打击:最近 20 笔交易,赢 4 输 16
new_wins, new_losses = 4, 16
posterior_pdf = beta.pdf(x, prior_wins + new_wins, prior_losses + new_losses)
plt.figure(figsize=(10, 5))
plt.plot(x, prior_pdf, label=f'先验信念 (回测胜率中心处在 60%)\nBeta({prior_wins}, {prior_losses})', lw=2)
plt.plot(x, posterior_pdf, label=f'后验信念 (实盘遇大减撤,胜率信念下调)\nBeta({prior_wins + new_wins}, {prior_losses + new_losses})', lw=2, color='crimson')
plt.fill_between(x, 0, prior_pdf, alpha=0.1)
plt.fill_between(x, 0, posterior_pdf, color='crimson', alpha=0.2)
plt.title("贝叶斯胜率更新:永远跟随新数据进化")
plt.xlabel("真实策略胜率可能值")
plt.ylabel("概率密度")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
🎯 总结与启示¶
- 绝不轻信均值方差框架:真实金融世界的尾部极厚,且充满尾部相依的 Copula 陷阱。
- 驾驭随机过程:将市场的震荡理解为带有漂移项的布朗运动噪声,而不是确定的线性趋势线。
- 坚守贝叶斯更新:永远不要把回测的胜率当成绝对真理。只要市场给出了相反的新数据,随时动态调整你的信仰和仓位。
本章涵盖了高阶量化交易乃至衍生品定价(Black-Scholes 模型等)的底层概率思想。带着这些对随机性的敬畏之心,我们准备进入下一章:获取市场真实的数据流。 → 02_data/
🎯 练习¶
- 修改蒙特卡洛模拟参数(漂移率、波动率),观察最终净值分布如何变化。
- 实现一个函数,计算给定置信水平(如 95%)下,持有 N 天的最大预期亏损(VaR)。
- 使用 Bootstrap 方法,对 AAPL 真实历史收益率进行 10000 次重抽样,估计年化夏普比率的置信区间。
下一节 → 06_statistics_fundamentals.ipynb