第 08 章 Bootstrap
本章对应 Wasserman 原书第 8 章《The Bootstrap》。Bootstrap 是 Efron (1979) 发明的估计标准误和构造置信区间的方法。它的思想只有一句话——在经验分布 \(\hat F_n\) 上重演一遍抽样——却解决了第 07 章留下的难题:中位数、偏度、相关系数、Sharpe 比率、最大回撤这些统计量,没有现成的标准误公式时怎么办。在量化研究中,bootstrap 可能是使用频率最高的推断工具,也是最容易被误用的工具。本章除原书内容外,补充了金融数据必需的块 bootstrap。
学习目标
- 理解 bootstrap 的两步思想:用 \(\mathbb V_{\hat F_n}(T_n)\) 估计 \(\mathbb V_F(T_n)\),再用模拟近似 \(\mathbb V_{\hat F_n}(T_n)\);能说清两层近似误差各自的来源。
- 会写 bootstrap 方差估计算法,能对任意统计量(中位数、相关系数、比值、Sharpe 比率)算出 bootstrap 标准误。
- 掌握三种 bootstrap 置信区间——正态区间、枢轴区间、百分位区间——的公式和推导,理解枢轴区间中分位数"反转"的原因。
- 了解刀切法及其局限,以及 bootstrap 失效的典型场景(参数空间边界、极值统计量)。
- 能对有自相关的收益序列使用移动块 bootstrap 和平稳 bootstrap,并理解为什么 IID bootstrap 会低估标准误。
读前导读
这一章在解决什么问题。 第 01 章用 Delta 方法推出了 Sharpe 比率的标准误,推导不短,而且依赖 IID 和矩存在。如果你要的是最大回撤、中位数、Calmar 比率、两个策略 Sharpe 之差的标准误,公式要么推不出来,要么推出来也不可信。Bootstrap 的办法是放弃推公式,改用模拟:既然第 07 章说历史分布 \(\hat F_n\) 是真实分布 \(F\) 的好替身,那就从历史数据里有放回地重新抽出很多条"平行历史",在每一条上重算统计量,看它们之间差多少。这个差距就近似了真实的抽样误差。
你在风险管理里用过的蒙特卡洛 VaR 是"假设一个模型、模拟很多情景";bootstrap 是"不假设模型、直接从历史里重抽情景",这和历史模拟法 VaR 是同一种精神。本章还会讲它的三个陷阱:样本里没有的极端事件抽不出来;极值类统计量会失效;收益有自相关时逐日重抽会把相关性打乱,必须按块抽。
需要先想起来的数学。
- 大数定律用于模拟。 抽得越多,模拟平均越接近真实期望。蒙特卡洛期权定价也是靠这一点:模拟 10 万条路径取平均 payoff 再贴现。本章 8.2 节就是这个原理。见第 01 章 1.5.5 节与 第 00 册第 07 章 概率中的分析工具。
- 分位数与"条件在数据上"。 \(\theta^*_\beta\) 是 \(B\) 个 bootstrap 复制值排序后第 \(\beta B\) 个;\(\mathbb E(\cdot\mid X_1,\dots,X_n)\) 的意思是"把原始数据当作固定不变,只对重抽样的随机性求期望"。
- \(e\) 的极限 \((1-1/n)^n\to e^{-1}\)。 与连续复利 \((1+r/n)^n\to e^r\) 同一个公式,取 \(r=-1\)。用来算"某个观测一次都没被抽中"的概率约 36.8%。见 第 00 册第 04 章 级数与收敛。
- 单调变换保持分位数。 若 \(m\) 递增,则 \(m(X)\) 的中位数等于 \(m(X\text{ 的中位数})\)。例:收益率的中位数是 5%,则 \(\log(1+r)\) 的中位数就是 \(\log1.05\)。但均值没有这个性质(Jensen)。百分位区间的论证用到它。
- 自协方差与长期方差。 序列相关时 \(\mathbb V(\bar X)\ne\sigma^2/n\),还要加上各阶自协方差的贡献。这个结论 Newey–West 标准误也用到。见第 01 章 1.3 节的方差公式。
怎么读这一章。 8.1–8.3 是核心,必须读懂"两个世界"的对照和"两层近似"。8.4 的三种区间中,正态区间和百分位区间最常用,枢轴区间的推导值得跟一遍,理解为什么分位数要"反转";8.4.3 的附录论证第一次可以只看结论。例 8.5–8.7 挑一个读即可,例 8.7 的"按受试者整体重抽样"对应量化里"按日期整体重抽样",比较重要。8.5.3 和 8.6.3(块 bootstrap)对金融数据是必读的。
8.1 问题与思想
设 \(T_n=g(X_1,\dots,X_n)\) 是一个统计量(数据的任意函数),我们想知道它的方差 \(\mathbb V_F(T_n)\)。下标 \(F\) 强调:方差依赖于未知的真实分布 \(F\)。
举个简单的例子。\(T_n=\bar X_n\) 时,\(\mathbb V_F(T_n)=\sigma^2/n\),其中 \(\sigma^2=\int(x-\mu)^2\,dF(x)\) 是 \(F\) 的泛函。我们不知道 \(F\),但可以用插入原则:把 \(F\) 换成 \(\hat F_n\),得 \(\hat\sigma^2/n\),\(\hat\sigma^2=\frac1n\sum(X_i-\bar X_n)^2\)。这就是我们一直在用的 \(\widehat{\text{se}}=\hat\sigma/\sqrt n\)。
Bootstrap 把这个想法推广到任意统计量,分两步:
- 第 1 步(插入):用 \(\mathbb V_{\hat F_n}(T_n)\) 估计 \(\mathbb V_F(T_n)\)。\(\mathbb V_{\hat F_n}(T_n)\) 的意思是:"假如数据真是从 \(\hat F_n\) 抽出来的,\(T_n\) 的方差是多少"。
- 第 2 步(模拟):用蒙特卡洛模拟近似计算 \(\mathbb V_{\hat F_n}(T_n)\)。
对样本均值,第 1 步就能得到闭式结果 \(\hat\sigma^2/n\)。对中位数、相关系数等复杂统计量,写不出 \(\mathbb V_{\hat F_n}(T_n)\) 的公式,才需要第 2 步。
8.2 模拟:用大数定律算期望
第 2 步依赖一个简单事实。从分布 \(G\) 抽 IID 样本 \(Y_1,\dots,Y_B\),由大数定律
8.3 Bootstrap 方差估计
8.3.1 两个世界
要模拟 \(T_n\) 在 \(\hat F_n\) 下的分布,就从 \(\hat F_n\) 中抽 \(X_1^*,\dots,X_n^*\),算 \(T_n^*=g(X_1^*,\dots,X_n^*)\)。对照如下:
怎样从 \(\hat F_n\) 抽样?\(\hat F_n\) 在每个数据点上放 \(1/n\) 的质量,所以从 \(\hat F_n\) 抽一个观测,等价于从原始数据中随机挑一个点。抽 \(n\) 个,就是从原数据中有放回地抽 \(n\) 个。有放回是关键:每个 bootstrap 样本里,有的原始观测出现多次,有的一次也没出现(平均约有 \(1-(1-1/n)^n\approx63.2\%\) 的不同观测被抽到)。
白话解释:两个世界的对照是本章的全部要点。真实世界里,我们想知道"如果历史重演,Sharpe 会差多少",但历史只有一条。Bootstrap 世界里,原始样本扮演"总体",原始样本的 Sharpe \(\hat\theta_n\) 扮演"真值",每条重抽的序列扮演"一次可能的历史"。在这个世界里一切都已知,可以无限重演。我们赌的是:bootstrap 世界里"估计值围绕真值的波动"与真实世界里的波动相似。 为什么必须有放回?如果不放回地抽 \(n\) 个,得到的永远是原始样本的一个排列,均值、Sharpe 都不变,毫无波动可言。有放回才能制造出"有的日子出现两次、有的日子没出现"的差异。 63.2% 的来源:某个观测在一次抽取中没被抽到的概率是 \(1-1/n\),\(n\) 次都没被抽到是 \((1-1/n)^n\approx e^{-1}\approx0.368\)。
8.3.2 算法
Bootstrap 方差估计:
- 从原数据有放回地抽 \(n\) 个,得 \(X_1^*,\dots,X_n^*\);
- 计算 \(T_n^*=g(X_1^*,\dots,X_n^*)\);
- 重复第 1–2 步 \(B\) 次,得到 \(T_{n,1}^*,\dots,T_{n,B}^*\);
- 计算
\[v_{boot}=\frac1B\sum_{b=1}^B\Big(T_{n,b}^*-\frac1B\sum_{r=1}^BT_{n,r}^*\Big)^2,\qquad\widehat{\text{se}}_{boot}=\sqrt{v_{boot}}.\]
例 8.1(中位数的标准误)。 原书给出 R 风格的伪代码,写成 Python 是:
import numpy as np
def boot_se_median(x, B=1000, rng=np.random.default_rng(1)):
n = len(x)
tboot = [np.median(rng.choice(x, size=n, replace=True)) for _ in range(B)]
return np.std(tboot, ddof=1)
x = np.random.default_rng(0).standard_normal(400)
print(boot_se_median(x), "理论约", np.sqrt(np.pi / 2 / 400))
输出为 0.0759 理论约 0.0627(已四舍五入)(理论值是正态样本中位数的渐近标准误 \(\sqrt{\pi/(2n)}\);bootstrap 对中位数这种不光滑统计量的标准误估计偏粗糙,但量级正确)。计算量是 \(O(B\times\text{计算一次 }g\text{ 的成本})\)。
8.3.3 两层近似
金融直觉:这和蒙特卡洛定价的两种误差是一回事。用 GBM 模型对期权做蒙特卡洛定价,路径数从 1 万加到 100 万,只能消除"模拟噪声";如果波动率参数本身估错了(模型误差),模拟再多次价格也是错的。bootstrap 中 \(B\) 对应路径数,\(\hat F_n\) 对应你喂给模拟器的"模型",后者的误差由数据量 \(n\) 决定。
第一层误差来自用 \(\hat F_n\) 代替 \(F\),由样本量 \(n\) 决定,我们无法控制;第二层是模拟误差,加大 \(B\) 就能消除。一个常见的误解是"bootstrap 次数越多越准"——\(B\) 再大也只能消除第二层误差,第一层误差是数据本身的限度。实践中,估计标准误 \(B\) 取几百到一千就够了;估计置信区间的端点(尾部分位数)需要更多,通常取几千。
例 8.2. 神经数据(第 07 章例 7.2)的偏度插入估计 \(\hat\theta=\frac1n\sum(X_i-\bar X_n)^3/\hat\sigma^3\)。对每个 bootstrap 样本计算偏度,\(B=1000\),得偏度估计的标准误为 0.16。(正态分布的偏度为 0。)
8.4 Bootstrap 置信区间
原书介绍三种方法。设 \(\theta=T(F)\),\(\hat\theta_n=T(\hat F_n)\),\(\hat\theta^*_{n,1},\dots,\hat\theta^*_{n,B}\) 是 bootstrap 复制值,\(\theta^*_\beta\) 表示它们的 \(\beta\) 样本分位数。
8.4.1 方法一:正态区间
最简单,但只有在 \(T_n\) 的分布接近正态时才准确。它对称,不能反映抽样分布的偏斜,还可能超出参数的取值范围(如相关系数大于 1)。
8.4.2 方法二:枢轴区间
思路。 定义枢轴量(pivot)\(R_n=\hat\theta_n-\theta\),即估计误差,其 CDF 为 \(H(r)=\mathbb P_F(R_n\le r)\)。如果知道 \(H\),令
用 bootstrap 估计 \(H\)。 在 bootstrap 世界里,\(\hat\theta_n\) 扮演"真值"的角色,误差是 \(R^*_{n,b}=\hat\theta^*_{n,b}-\hat\theta_n\):
\(1-\alpha\) bootstrap 枢轴置信区间:
定理 8.3. 在 \(T(F)\) 满足较弱条件时,\(\mathbb P_F(T(F)\in C_n)\to1-\alpha\)(\(n\to\infty\))。
推导拆解:第一个等号把"\(a\le\theta\le b\)"改写成误差 \(R_n=\hat\theta_n-\theta\) 的范围:三边同时乘 \(-1\)(不等号反向)再加 \(\hat\theta_n\),得 \(\hat\theta_n-b\le R_n\le\hat\theta_n-a\)。代入 \(a,b\) 的定义,\(\hat\theta_n-b=H^{-1}(\alpha/2)\),\(\hat\theta_n-a=H^{-1}(1-\alpha/2)\)。连续 CDF 下,"落在两个分位数之间"的概率就是 \((1-\alpha/2)-\alpha/2=1-\alpha\)。 数值例:样本 Sharpe \(\hat\theta=0.05\),bootstrap 复制值的 2.5% 与 97.5% 分位数分别是 0.00 与 0.14(右偏:向上 0.09、向下 0.05)。bootstrap 世界告诉我们"估计值容易高估 0.09、低估 0.05"。回到真实世界,真值应该在 \(0.05-0.09=-0.04\) 到 \(0.05+0.05=0.10\) 之间,即 \((2\times0.05-0.14,\ 2\times0.05-0.00)\)。百分位区间则直接给 \((0.00,0.14)\)。抽样分布对称时两者一致,偏斜时两者朝相反方向偏移。
8.4.3 方法三:百分位区间
直接取 bootstrap 复制值的分位数。它最直观,而且自动落在参数的取值范围内。
为什么百分位区间合理(原书附录)。 假设存在一个单调变换 \(U=m(T)\) 使 \(U\sim N(\phi,c^2)\),\(\phi=m(\theta)\)。我们不需要知道 \(m\),只需要它存在。单调变换保持分位数,所以 bootstrap 复制值变换后的分位数 \(u^*_\beta=m(\theta^*_\beta)\)。又因 \(U\) 正态,\(u^*_{\alpha/2}\approx U-z_{\alpha/2}c\),\(u^*_{1-\alpha/2}\approx U+z_{\alpha/2}c\)。于是
白话解释:这段论证的逻辑是"换个刻度看问题"。相关系数在 0.9 附近被上限 1 挤压,抽样分布是歪的;但换成 Fisher \(z=\tfrac12\log\frac{1+\rho}{1-\rho}\) 的刻度后,它近似对称正态。在对称正态的刻度上,"估计值 ± 1.96 个标准差"恰好就是 bootstrap 分布的 2.5% 与 97.5% 分位数;再换回原刻度,单调变换不改变"第几个分位数",所以原刻度上直接取 bootstrap 分位数也对。妙处在于你不需要知道这个变换是什么,只要它存在。 读证明时有一处要留意:\(u^*_\beta\approx U\pm z c\) 这一步假设了 bootstrap 世界中变换后的复制值近似服从 \(N(U,c^2)\),即以观测值 \(U\) 为中心——这是 bootstrap 原理本身的假设,原文略过了。
8.4.4 例子
例 8.4(神经数据偏度的 95% 区间)。 正态 \((1.44,2.09)\);枢轴 \((1.48,2.11)\);百分位 \((1.42,2.03)\)。三者都是近似的,精度相近。还有更精确但更复杂的 bootstrap 区间(如 BCa,bias-corrected and accelerated),本书不讨论,见 Efron & Tibshirani (1993)。
例 8.5(胆固醇数据,中位数之差)。 第 07 章例 7.15 的两组数据,目标是两组胆固醇中位数之差。两组要各自有放回重抽样(保持原样本量 \(n_1,n_2\)),每次计算 median(第二组) − median(第一组),\(B=1000\)。结果:点估计 18.5,bootstrap se 7.42;95% 区间——正态 \((3.7,33.3)\),枢轴 \((5.0,34.0)\),百分位 \((5.0,33.3)\)。都不含 0,第二组胆固醇似乎更高,但区间很宽,"高多少"的不确定性很大。
作者提醒:下面两例的样本量很小,小样本下统计方法可能不可靠,仅供教学,结果应持怀疑态度。
例 8.6(Efron 最早的 bootstrap 示例:法学院数据)。 15 所法学院的入学考试平均分 LSAT 与本科平均绩点 GPA:
| LSAT | 576 | 635 | 558 | 578 | 666 | 580 | 555 | 661 | 651 | 605 | 653 | 575 | 545 | 572 | 594 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| GPA | 3.39 | 3.30 | 2.81 | 3.03 | 3.44 | 3.07 | 3.00 | 3.43 | 3.36 | 3.13 | 3.12 | 2.74 | 2.76 | 2.88 | 2.96 |
关心相关系数 \(\theta\),插入估计即样本相关 \(\hat\theta=0.776\);\(B=1000\) 得 \(\widehat{\text{se}}=0.137\)。bootstrap 复制值的直方图左偏(相关系数上限为 1,向上的空间有限)。正态 95% 区间 \(0.78\pm2\widehat{\text{se}}=(0.51,1.00)\),百分位区间 \((0.46,0.96)\)。大样本下两者会更接近。
例 8.7(生物等效性,取自 Efron & Tibshirani 1993)。 药企推出新药时有时需证明生物等效(bioequivalence):新药与现有疗法没有实质差异。8 名受试者分别使用安慰剂、旧贴片、新贴片,测量血液中的激素水平:
| 受试者 | 安慰剂 | 旧 | 新 | \(Z\)=旧−安慰剂 | \(Y\)=新−旧 |
|---|---|---|---|---|---|
| 1 | 9243 | 17649 | 16449 | 8406 | −1200 |
| 2 | 9671 | 12013 | 14614 | 2342 | 2601 |
| 3 | 11792 | 19979 | 17274 | 8187 | −2705 |
| 4 | 13357 | 21816 | 23798 | 8459 | 1982 |
| 5 | 9055 | 13850 | 12560 | 4795 | −1290 |
| 6 | 6290 | 9806 | 10157 | 3516 | 351 |
| 7 | 12412 | 17208 | 16570 | 4796 | −638 |
| 8 | 18806 | 29044 | 26325 | 10238 | −2719 |
FDA 的标准是 \(|\theta|\le0.20\),\(\theta=\mathbb E_F(Y)/\mathbb E_F(Z)\)。插入估计 \(\hat\theta=\bar Y/\bar Z=-452.3/6342=-0.0713\);bootstrap se 为 0.105;\(B=1000\) 得 95% 区间 \((-0.24,0.15)\)。区间没有完全落在 \((-0.20,0.20)\) 内,因此在 95% 水平上未能证明生物等效。
注意这里重抽样的单位是受试者:每次抽中某个受试者,就把他的 \((Z,Y)\) 一起带走,保留两者之间的相关性。\(\bar Y/\bar Z\) 这种比值结构与 Beta(协方差/方差)、信息比率完全相同,也可以用第 01 章的多元 Delta 方法求标准误,两者可以相互验证。
8.5 刀切法与 bootstrap 的局限
8.5.1 刀切法
刀切法(jackknife)由 Quenouille (1949) 提出,比 bootstrap 早,计算量小,但适用范围窄。记 \(T_{(-i)}\) 为去掉第 \(i\) 个观测后算出的统计量,\(\bar T_n=\frac1n\sum_iT_{(-i)}\),刀切方差估计为
推导拆解:用样本均值验算一遍就清楚了。去掉第 \(i\) 个点后 \(T_{(-i)}=\frac{n\bar X-X_i}{n-1}\),而这些值的平均 \(\bar T_n=\bar X\),所以 \(T_{(-i)}-\bar T_n=-\frac{X_i-\bar X}{n-1}\)——每个偏差只有原数据偏差的 \(1/(n-1)\)。代入:\(v_{jack}=\frac{n-1}n\cdot\frac{1}{(n-1)^2}\sum(X_i-\bar X)^2=\frac{1}{n(n-1)}\sum(X_i-\bar X)^2=\frac{S_n^2}{n}\),正好是均值方差的标准估计。系数 \(\frac{n-1}{n}\) 就是为了让这个特例精确成立而选的。在适当条件下 \(v_{jack}/\mathbb V(T_n)\xrightarrow{as}1\),即相合。但与 bootstrap 不同,刀切法不能相合地估计样本分位数(如中位数)的标准误——中位数对去掉单个点不够"光滑"。刀切法对光滑统计量(均值的光滑函数,如 Sharpe 比率)效果很好,而且结果确定、可复现。
8.5.2 Bootstrap 何时失效
Bootstrap 的有效性依赖于 \(T\) 对 \(F\) 的"光滑性"。原书习题 7 给出一个典型的失效例子:\(X_1,\dots,X_n\sim\text{Uniform}(0,\theta)\),\(\hat\theta=X_{\max}\)。真实世界中 \(\hat\theta\) 是连续变量,\(\mathbb P(\hat\theta=\theta)=0\);但 bootstrap 世界里,只要原样本的最大值被抽中,\(\hat\theta^*\) 就等于 \(\hat\theta\):
对量化的启示:最大回撤、最差单日亏损、"一组策略中最大的 Sharpe"等极值型统计量,直接 bootstrap 可能严重失真。可以考虑子抽样(subsampling,抽 \(m\ll n\) 个不放回)、极值理论,或者至少用模拟检验 bootstrap 在该问题上的覆盖率。
8.5.3 IID 假设:金融数据的块 bootstrap
本章到此为止的 bootstrap 都假设数据 IID。有放回逐点重抽样会彻底打乱时间顺序,从而抹掉所有序列相关。如果收益有正自相关(趋势策略、流动性差的资产、平滑估值的私募基金),真实的均值标准误比 IID 公式大;IID bootstrap 会和 IID 公式一样低估它。
推导拆解:为什么正自相关会放大均值的标准误?由第 01 章的方差公式, \(\mathbb V(\bar X_n)=\frac1{n^2}\Big[\sum_i\mathbb V(X_i)+2\sum_{i<j}\operatorname{Cov}(X_i,X_j)\Big]\)。 IID 时协方差项为 0,得 \(\sigma^2/n\)。AR(1) 时 \(\operatorname{Cov}(X_i,X_{i+k})=\sigma^2\phi^k\),\(n\) 很大时方括号约为 \(n\sigma^2(1+2\phi+2\phi^2+\dots)=n\sigma^2\frac{1+\phi}{1-\phi}\)(用了几何级数 \(\sum_{k\ge1}\phi^k=\frac{\phi}{1-\phi}\))。所以 \(\mathbb V(\bar X_n)\approx\frac{\sigma^2}{n}\cdot\frac{1+\phi}{1-\phi}\),\(\phi=0.3\) 时放大 1.86 倍,标准误放大 \(\sqrt{1.86}\approx1.36\) 倍。IID bootstrap 把日子打乱,相当于强行令所有协方差为 0,所以只能得到 \(\sigma^2/n\)。 金融直觉:这就是私募股权、房地产基金"估值平滑"导致波动率和 Sharpe 失真的同一机制。块 bootstrap 的做法相当于"按连续的月份或季度整段抽取",段内的相关性被保留下来。
补救办法是按块重抽样,保留块内的依赖结构(以下方法不在原书第 8 章范围内,是金融应用的标准补充):
- 移动块 bootstrap(moving block bootstrap):把序列切成所有长度为 \(L\) 的重叠块,有放回地抽 \(\lceil n/L\rceil\) 个块首尾相接,截取前 \(n\) 个。
- 平稳 bootstrap(stationary bootstrap,Politis & Romano 1994):块长随机,服从均值为 \(L\) 的几何分布;序列首尾循环相接。这样生成的重抽样序列仍是平稳的,对块长的选择也没那么敏感。
块长 \(L\) 要大到能覆盖主要的相关结构,又要小到有足够多的块可供重组。常见的经验取法是 \(L\propto n^{1/3}\),也有基于数据的自动选择方法(Politis & White 2004)。对于没有自相关但有波动聚集的收益(典型的日收益),IID bootstrap 对均值的标准误影响不大,但对方差、Sharpe 比率等二阶量的标准误仍有偏差,块 bootstrap 更稳妥。
8.6 量化实战
8.6.1 Sharpe 比率:bootstrap、刀切与 Delta 方法
用两年(504 天)\(t(5)\) 厚尾日收益,真实日 Sharpe 0.08(年化约 1.27),计算样本 Sharpe 的三种标准误和三种 bootstrap 区间。
import numpy as np
from scipy import stats
rng = np.random.default_rng(8)
def sharpe(x, axis=-1):
return x.mean(axis) / x.std(axis, ddof=1)
# 一个策略的 504 天日收益:t(5) 厚尾,日 Sharpe 约 0.08(年化约 1.27)
n, nu = 504, 5
x = 0.0008 + 0.01 * rng.standard_t(nu, n) / np.sqrt(nu / (nu - 2))
th = sharpe(x)
# Bootstrap:B 次有放回重抽样
B = 5000
idx = rng.integers(0, n, size=(B, n))
tb = sharpe(x[idx])
se_boot = tb.std(ddof=1)
# 刀切法
jk = np.array([sharpe(np.delete(x, i)) for i in range(n)])
se_jack = np.sqrt((n - 1) / n * np.sum((jk - jk.mean()) ** 2))
# Delta 方法(含样本偏度、峰度)
sk, ku = stats.skew(x), stats.kurtosis(x, fisher=False)
se_delta = np.sqrt((1 - sk * th + (ku - 1) / 4 * th**2) / n)
print(f"样本日 Sharpe={th:.4f}(年化 {th*np.sqrt(252):.2f})")
print(f"se: bootstrap={se_boot:.4f} jackknife={se_jack:.4f} Delta={se_delta:.4f}")
z = stats.norm.ppf(0.975)
lo, hi = np.quantile(tb, [0.025, 0.975])
print(f"95% 正态区间 ({th - z*se_boot:.4f}, {th + z*se_boot:.4f})")
print(f"95% 枢轴区间 ({2*th - hi:.4f}, {2*th - lo:.4f})")
print(f"95% 百分位区间 ({lo:.4f}, {hi:.4f})")
print(f"bootstrap 下 Sharpe<=0 的比例: {np.mean(tb <= 0):.4f}")
输出:
样本日 Sharpe=0.0515(年化 0.82)
se: bootstrap=0.0445 jackknife=0.0453 Delta=0.0448
95% 正态区间 (-0.0357, 0.1387)
95% 枢轴区间 (-0.0367, 0.1383)
95% 百分位区间 (-0.0353, 0.1398)
bootstrap 下 Sharpe<=0 的比例: 0.1198
三种标准误高度一致(0.045 左右),说明对这种"均值的光滑函数",bootstrap、刀切法和 Delta 方法可以互相验证。三种区间也几乎相同,因为抽样分布接近正态。真实年化 Sharpe 1.27 的策略,这次两年样本只算出 0.82,而 95% 区间从负值一直延伸到年化 2.2 左右——两年日数据对 Sharpe 的信息量非常有限。
最后一行的"bootstrap 复制值中 Sharpe ≤ 0 的比例"常被当作"策略无效的概率",这是不对的:它是 bootstrap 分布的一个尾部面积,大致对应一个单侧检验的 p 值,不是"\(\theta\le0\) 的概率"(第 06 章 6.4.2 节,置信不是关于参数的概率;用 bootstrap 标准误做 Wald 检验见本册第 10a 章)。
8.6.2 三种区间的覆盖率:何时出问题
上面只是一个样本。区间方法好不好,要看它在重复抽样下的覆盖率(原书习题 2、3 的思路)。下面对两类策略各做 1000 次"回测",每次 \(n=252\)、\(B=1000\),统计三种 95% 区间套住真实 Sharpe 的比例。
import numpy as np
rng = np.random.default_rng(88)
def sharpe(x, axis=-1):
return x.mean(axis) / x.std(axis, ddof=1)
def short_vol(size): # 第 01 章的负偏收益:1% 概率暴跌
return np.where(rng.random(size) < 0.01, -0.0984, 0.0016) + rng.normal(0, 0.002, size)
def t5(size):
return 0.0006 + 0.01 * rng.standard_t(5, size) / np.sqrt(5 / 3)
true_sr = {"t(5)": 0.06, "负偏": 0.0006 / np.sqrt(0.01 * 0.99 * 0.1**2 + 0.002**2)}
n, R, B, z = 252, 1000, 1000, 1.959964
for name, gen in [("t(5)", t5), ("负偏", short_vol)]:
hits = np.zeros(3)
for _ in range(R):
x = gen(n)
th = sharpe(x)
tb = sharpe(x[rng.integers(0, n, size=(B, n))])
se = tb.std(ddof=1)
lo, hi = np.quantile(tb, [0.025, 0.975])
s = true_sr[name]
hits += [abs(th - s) <= z * se, 2*th - hi <= s <= 2*th - lo, lo <= s <= hi]
print(f"{name}: 名义 95%,实际覆盖率 正态={hits[0]/R:.3f} 枢轴={hits[1]/R:.3f} 百分位={hits[2]/R:.3f}")
输出:
t(5): 名义 95%,实际覆盖率 正态=0.956 枢轴=0.946 百分位=0.957
负偏: 名义 95%,实际覆盖率 正态=0.898 枢轴=0.849 百分位=0.908
对称厚尾收益下,三种区间都接近 95%。对"1% 概率暴跌"的负偏策略,一年数据下三种区间的覆盖率都不足,枢轴区间只有 85%。原因和第 01 章 1.6.2 节一样:约 8% 的样本里一次暴跌都没出现,bootstrap 只能从样本里重抽,样本里没有的暴跌,bootstrap 永远抽不出来。这是 bootstrap 最根本的局限:它只能重现数据里已有的信息。对尾部风险主导的策略,必须用更长的历史、压力情景或参数化的尾部模型来补充。
8.6.3 自相关收益:IID bootstrap 与块 bootstrap
模拟 1000 天 AR(1) 收益(\(\phi=0.3\),正自相关),比较均值标准误的几种估计。理论上,AR(1) 的长期方差使均值标准误放大 \(\sqrt{(1+\phi)/(1-\phi)}\approx1.36\) 倍。
import numpy as np
rng = np.random.default_rng(808)
def ar1(n, phi=0.3, sd=0.01, mu=0.0005):
e = rng.normal(0, sd * np.sqrt(1 - phi**2), n) # 平稳方差为 sd^2
x = np.empty(n); x[0] = rng.normal(0, sd)
for t in range(1, n):
x[t] = phi * x[t - 1] + e[t]
return mu + x
def iid_boot(x, B):
n = len(x)
return x[rng.integers(0, n, (B, n))].mean(1)
def moving_block_boot(x, B, L):
n = len(x); k = int(np.ceil(n / L))
starts = rng.integers(0, n - L + 1, (B, k))
idx = (starts[:, :, None] + np.arange(L)).reshape(B, -1)[:, :n]
return x[idx].mean(1)
def stationary_boot(x, B, L):
# Politis-Romano:块长服从均值为 L 的几何分布,首尾循环相接
n = len(x)
idx = np.empty((B, n), dtype=int)
idx[:, 0] = rng.integers(0, n, B)
new_block = rng.random((B, n)) < 1 / L
jump = rng.integers(0, n, (B, n))
for t in range(1, n):
idx[:, t] = np.where(new_block[:, t], jump[:, t], (idx[:, t - 1] + 1) % n)
return x[idx].mean(1)
n, phi, B, L = 1000, 0.3, 2000, 20
true_se = np.std([ar1(n, phi).mean() for _ in range(4000)])
theory = 0.01 / np.sqrt(n) * np.sqrt((1 + phi) / (1 - phi))
x = ar1(n, phi)
print(f"均值的真实 se: 模拟={true_se*1e4:.2f}bp 理论≈{theory*1e4:.2f}bp")
print(f"IID bootstrap se = {iid_boot(x, B).std()*1e4:.2f}bp")
print(f"移动块 bootstrap se(L={L}) = {moving_block_boot(x, B, L).std()*1e4:.2f}bp")
print(f"平稳 bootstrap se(L={L}) = {stationary_boot(x, B, L).std()*1e4:.2f}bp")
# bootstrap 失效:Uniform(0,1) 样本的最大值
m = 50
u = rng.uniform(0, 1, m)
mb = u[rng.integers(0, m, (10000, m))].max(1)
print(f"P(θ*=θ̂) 模拟={np.mean(mb == u.max()):.3f} 理论 1-(1-1/n)^n={1-(1-1/m)**m:.3f}")
输出:
均值的真实 se: 模拟=4.27bp 理论≈4.31bp
IID bootstrap se = 3.16bp
移动块 bootstrap se(L=20) = 4.59bp
平稳 bootstrap se(L=20) = 4.35bp
P(θ*=θ̂) 模拟=0.631 理论 1-(1-1/n)^n=0.636
IID bootstrap 把标准误低估了约 26%(3.16 vs 4.27),对应的 t 值会被高估约 1.35 倍——一个真实 t 值 1.5 的策略会被报告成 2.0,"显著"了。两种块 bootstrap 都恢复到真实水平附近。最后两行验证了 8.5.2 节的失效例子:\(n=50\) 时 bootstrap 最大值恰好等于样本最大值的概率是 0.636(\(n\to\infty\) 时趋于 0.632)。
8.6.4 Bootstrap 在量化中的其他用途
- 两策略比较:把例 8.5 的两组重抽样改为同步重抽样(同一天的两个策略收益一起抽),就得到两策略 Sharpe 之差的 bootstrap 区间。同期策略的收益相关,不能像例 8.5 那样各自独立重抽样。
- 数据窥探检验:同时测试几百个策略后挑出最好的一个,其 Sharpe 必然被高估。White (2000) 的 Reality Check 与 Hansen (2005) 的 SPA 检验用(平稳)bootstrap 构造"最好策略的 Sharpe"在原假设下的分布,是本章方法在策略筛选中的直接延伸(详见本册第 10b 章多重检验与第 11 册)。
- 组合与模型稳定性:对收益样本 bootstrap 后反复做均值-方差优化,观察权重的波动(Michaud 的再抽样有效前沿 resampled efficiency);或者反复估计因子载荷,看哪些暴露是稳定的。
- Bootstrap 不是万能的:它不能创造样本里没有的信息(8.6.2 节),不能修复数据窥探带来的选择偏差(除非像 Reality Check 那样把选择过程一起放进重抽样),也不能修复非平稳性(结构突变前后的数据混在一起重抽样,只会得到一个"平均"的、哪个时期都不像的分布)。
本章小结
Bootstrap 是插入原则加蒙特卡洛模拟:用经验分布 \(\hat F_n\) 代替未知的 \(F\),从原数据有放回地重抽样 \(B\) 次,用复制值的波动近似统计量的抽样波动。误差有两层,模拟误差随 \(B\) 增大而消失,用 \(\hat F_n\) 代替 \(F\) 的误差则受样本量限制。三种置信区间各有特点:正态区间最简单但要求近似正态;枢轴区间基于误差分布,要"反转"分位数;百分位区间直接取复制值分位数,在存在近似正态化变换时合理。刀切法计算便宜,但对分位数失效;bootstrap 在参数边界和极值统计量上也会失效。金融收益有序列相关时,必须用块 bootstrap,否则标准误被低估、显著性被高估。
| 概念 | 公式 / 要点 |
|---|---|
| Bootstrap 思想 | \(\mathbb V_F(T_n)\approx\mathbb V_{\hat F_n}(T_n)\approx v_{boot}\) |
| 重抽样 | 从原数据有放回抽 \(n\) 个 |
| 方差估计 | \(v_{boot}=\frac1B\sum_b(T^*_{n,b}-\bar T^*)^2\) |
| 正态区间 | \(T_n\pm z_{\alpha/2}\widehat{\text{se}}_{boot}\) |
| 枢轴区间 | \((2\hat\theta_n-\theta^*_{1-\alpha/2},\ 2\hat\theta_n-\theta^*_{\alpha/2})\) |
| 百分位区间 | \((\theta^*_{\alpha/2},\ \theta^*_{1-\alpha/2})\) |
| 刀切法 | \(v_{jack}=\frac{n-1}n\sum_i(T_{(-i)}-\bar T_n)^2\),对分位数不相合 |
| 失效例子 | Uniform 最大值:\(\mathbb P(\hat\theta^*=\hat\theta)\to1-e^{-1}\approx0.632\) |
| 两组独立样本 | 各组分别重抽样,保持各自样本量 |
| 时间序列 | 移动块 / 平稳 bootstrap,块长 \(L\propto n^{1/3}\) 量级 |
练习
基础
- 用例 8.6 的法学院数据,计算相关系数的插入估计、bootstrap 标准误和三种 95% 区间(原书习题 1)。哪种区间超出了 \([-1,1]\)?为什么百分位区间不会?
- 证明:若原样本没有重复值,不同的 bootstrap 样本(作为多重集合)共有 \(\binom{2n-1}{n}\) 个。(提示:把 \(n\) 个球放进 \(n\) 个桶。)
- 设 \(X_1^*,\dots,X_n^*\) 是 bootstrap 样本。求 \(\mathbb E(\bar X_n^*\mid X_1,\dots,X_n)\) 与 \(\mathbb V(\bar X_n^*\mid X_1,\dots,X_n)\)。(答案:\(\bar X_n\) 与 \(\hat\sigma^2/n\),\(\hat\sigma^2=\frac1n\sum(X_i-\bar X_n)^2\)。这说明对样本均值,bootstrap 精确地复现了插入公式。)
- 解释为什么刀切法方差公式中有因子 \((n-1)/n\) 乘以平方和,而不是 \(1/(n-1)\)。(提示:对 \(T=\bar X\) 直接计算,\(T_{(-i)}-\bar T_n=-(X_i-\bar X)/(n-1)\)。)
- 两个策略在同一时期交易。写出它们 Sharpe 之差的 bootstrap 区间算法,说明为什么不能对两个策略各自独立重抽样。
进阶
- 计算机实验(原书习题 2):\(n=50\),\(Y_i\sim N(0,1)\),\(X_i=e^{Y_i}\),目标是 \(X\) 的偏度(真值 \((e+2)\sqrt{e-1}\approx6.18\))。用模拟比较三种 bootstrap 区间的真实覆盖率。预期结果是什么?为什么?(提示:覆盖率会远低于 95%;对数正态的偏度估计受少数极端值支配,\(n=50\) 时插入估计严重偏低。)
- 计算机实验(原书习题 3):\(n=25\) 的 \(t_3\) 样本,\(\theta=(q_{0.75}-q_{0.25})/1.34\)(基于四分位距的稳健尺度)。比较三种区间的覆盖率与平均长度,并与刀切法比较。
- 原书习题 6:\(X_i\sim N(\mu,1)\),\(\mu=5\),\(n=100\),\(\theta=e^\mu\),\(\hat\theta=e^{\bar X}\)。求 bootstrap se 与 95% 区间,并与第 01 章 Delta 方法的结果 \(\text{se}\approx e^\mu/\sqrt n\) 以及真实抽样分布对比。
- 原书习题 7:完整证明 \(\mathbb P(\hat\theta^*=\hat\theta)=1-(1-1/n)^n\),并用模拟画出 \(\hat\theta^*\) 的直方图与真实抽样分布 \(n(\theta-\hat\theta)/\theta\rightsquigarrow\text{Exp}(1)\) 的对比。
- 在 8.6.3 节代码中把块长 \(L\) 依次设为 1、5、20、50、200,记录平稳 bootstrap 的标准误。解释 \(L=1\) 与 \(L\) 很大时分别发生了什么。
原书推荐习题:第 8 章习题 2、3(三种区间覆盖率模拟,必做)、7(bootstrap 失效)、1、4、5、6。
原书对照
| 本章内容 | 原书章节 | PDF 页码 |
|---|---|---|
| Bootstrap 思想、两步法 | 第 8 章引言 | p.119–120 |
| 模拟 | 8.1 | p.120 |
| Bootstrap 方差估计、例 8.1–8.2 | 8.2 | p.120–122 |
| 正态、枢轴、百分位区间,例 8.4–8.7 | 8.3 | p.122–127 |
| 文献注 | 8.4 | p.127 |
| 刀切法、百分位区间的理由 | 8.5 附录 | p.127–128 |
| 习题 | 8.6 | p.128–130 |
| 块 bootstrap(本教材补充) | — | — |
勘误说明:精读笔记中例 8.6 法学院数据最后一所学校的 GPA 记为 3.96,与原书给出的样本相关系数 0.776 不符;采用 Efron 原始数据的 2.96 时,相关系数恰为 0.776,本章据此更正。