halo 的技术博客

返回

你拿到一段收益率序列,想确认「它到底是不是独立的」。最自然的做法是跑一个线性自回归、或者看 ACF / Ljung-Box 的 p 值。但如果一段序列**明明不独立、却对所有线性检验都「乖乖不拒绝」**呢?

这类情况比想象中常见:GARCH 的波动聚集(今天波动大、明天往往也大,但波动的符号几乎无线性自相关)、逻辑斯蒂混沌(完全确定、无任何随机,但序列看着像噪声)。线性工具对它们集体失明。

结论先放这:BDS 检验(Brock-Dechert-Scheinkman, 1987/1996)不问「线性相不相关」,而是问「序列是不是 i.i.d.(独立同分布)」。它把独立性翻译成一个可算的几何命题——若独立,则任意两个 m 维延迟向量「足够接近」的概率,必须等于它们「1 维接近」概率的 m 次方。偏离这个恒等式,就是非线性的指纹。 在我们合成的三类序列上:随机游走的 B 统计量贴在 0 附近(bootstrap p≈0.25,不拒绝独立)、GARCH 显著非零(p<0.01,抓到波动聚集的非线性)、逻辑斯蒂混沌极端大(p≈0,确定性混沌无所遁形)。诚实地说:BDS 测的是「非 i.i.d.」,不是「能不能赚钱」;它把非线性依赖显形,但真正据此下注还要结合经济逻辑与样本外验证——这正是后文五类陷阱要拆的。

三条都像随机游走,但只有第一条真的独立


1. 为什么线性检验会漏掉非线性#

线性自相关测的是 Cov(rt,rtk)Cov(r_t, r_{t-k})。它只能识别「昨天涨、今天倾向于涨多少」这种线性记忆。我们造两个反例:

  • GARCH 波动聚集:回报 rt=σtztr_t=\sigma_t z_t,其中 σt2=ω+αrt12+βσt12\sigma_t^2=\omega+\alpha r_{t-1}^2+\beta\sigma_{t-1}^2rtr_t条件方差强烈依赖过去,但 rtr_t 本身的符号几乎无线性自相关(因为 ztz_t 是独立正态)。于是 ACF 看着像白噪声,可序列远非独立。
  • 逻辑斯蒂混沌xt+1=axt(1xt)x_{t+1}=a\,x_t(1-x_t)。完全确定、零随机,但轨迹对初值极端敏感,序列外观高度像噪声,ACF 同样接近 0。

两者都对线性检验「隐身」。要抓它们,需要一种不预设线性关系的独立性检验——这就是 BDS。


2. 核心思想:相关积分与「独立性恒等式」#

把标量序列 {xt}t=1N\{x_t\}_{t=1}^N 嵌入成 m 维延迟向量 Xtm=(xt,xt+1,,xt+m1)X_t^m=(x_t,x_{t+1},\dots,x_{t+m-1})。定义相关积分 C(m)(ε)C^{(m)}(\varepsilon) 为「随机挑两个向量,它们的最大坐标差(切比雪夫距离)小于 ε\varepsilon」的概率:

C(m)(ε)=P(max1imxt+i1xs+i1<ε)C^{(m)}(\varepsilon) = P\big(\max_{1\le i\le m}|x_{t+i-1}-x_{s+i-1}| < \varepsilon\big)

若序列是 i.i.d. 的,那么两个 m 维向量的每个坐标都独立地「接近」,于是联合邻近概率分解为坐标乘积:

C(m)(ε)=[C(1)(ε)]mC^{(m)}(\varepsilon) = \big[C^{(1)}(\varepsilon)\big]^m

这就是独立性的恒等式。BDS 统计量就是它左边减右边:

Wm(ε)=C(m)(ε)[C(1)(ε)]mW_m(\varepsilon) = C^{(m)}(\varepsilon) - \big[C^{(1)}(\varepsilon)\big]^m

  • 独立(i.i.d.)→ Wm0W_m \approx 0
  • 任何依赖(线性或非线性)→ WmW_m 系统性偏离 0。

关键:$\varepsilon$ 通常取成「以序列标准差为单位的固定距离」(本文用 ε=0.7σ\varepsilon=0.7\sigma),这样不同序列可比。下面用 scipy 的 cKDTree精确的切比雪夫盒计数来算 C(m)C^{(m)}——它等价于暴力两两距离比较,但对高维延迟向量快得多。

实务提醒:p=np.infcount_neighbors 数的是「每个坐标差都 ≤ ε」的盒,正是切比雪夫距离定义。这是 O(NlogN)\mathcal O(N\log N) 级别,比 O(N2)\mathcal O(N^2) 的暴力距离矩阵稳。


3. 三类序列:Wₘ 一测便知#

造三条长度 2000 的序列:

  • 随机游走:独立正态收益 → 真 i.i.d.;
  • GARCH(1,1)ω=105,α=0.09,β=0.90\omega=10^{-5},\alpha=0.09,\beta=0.90 → 波动聚集非线性;
  • 逻辑斯蒂混沌a=3.9a=3.9 的确定映射残差 → 纯非线性。

把 Wₘ(用正态近似标准误 σ^=C(1)(1C(1))/N\hat\sigma=\sqrt{C^{(1)}(1-C^{(1)})/N} 标准化)随嵌入维数 m 画出来:

Wₘ 随嵌入维数 m 缩放

数字(m=2..6,已归一化):

序列m=2m=3m=4m=5m=6
随机游走0.010.040.040.030.03
GARCH0.270.310.290.240.18
逻辑斯蒂混沌3.383.062.351.791.27

看得很清楚:随机游走从头到尾贴在 0 附近(归一化后基本落在 ±1.96 绿带内);GARCH 系统性地偏离 0——波动聚集被非线性地抓住了;混沌偏离最狠,确定性结构彻底暴露。线性 ACF 在这三条上可能都「不显著」,BDS 却一眼分辨出谁是真独立、谁是披着噪声皮的非随机。


4. 蒙特卡洛功效:非线性越强越不放过#

检验好不好,要看「该抓的时候抓不抓得住」。我们对逻辑斯蒂映射调参数 aa(越大混沌越强),重复 250 次、每次 bootstrap 求 p 值,统计拒绝率(功效):

def bds_bootstrap(x, m, eps, B=150, rng=None):
    """i.i.d. 零假设下的 bootstrap p 值:重抽样 x 后看 |W*| ≥ |W_obs| 的比例。"""
    if rng is None: rng = np.random.default_rng()
    x = np.asarray(x, float)
    W_obs = Wm(x, m, eps)
    N = len(x); Wstar = np.empty(B)
    for b in range(B):
        idx = rng.integers(0, N, N)
        Wstar[b] = Wm(x[idx], m, eps)
    return W_obs, np.mean(np.abs(Wstar) >= np.abs(W_obs))
python

非线性越强,BDS 功效越高

结果(拒绝率随 aa 从 3.6 升到 4.0):1.00 / 1.00 / 1.00 / 1.00 / 1.00 / 1.00 / 1.00。在 a3.6a\ge 3.6 后功效已经锁死在 100%——只要有一点确定性混沌,BDS 几乎从不漏检。这正是一个好「随机性守门员」该有的样子:宁可多报,不可放过。


5. 检验水平的诚实校准:有效市场不被误杀#

功效高不等于可以乱拒绝。一个检验若把本就独立的序列也频繁拒掉,那它的「拒绝」毫无意义。我们对 400 条真随机游走跑 bootstrap,看零假设下的拒绝率(检验水平 / size)该≈5%:

reps, n, B = 400, 1500, 150
rng = np.random.default_rng(20260723)
rej = 0; pvals = []
for s in range(reps):
    x = rng.standard_normal(n)
    _, p = bds_bootstrap(x, 4, 0.7, B=B, rng=rng)
    pvals.append(p)
    rej += (p < 0.05)
print(f"size = {rej/reps:.3f}")
python

随机游走下 p 值应均匀、拒绝率≈5%

实测拒绝率 = 0.050(恰好 5%)。p 值直方图也接近均匀分布——零假设下既不轻易拒绝、也不系统性偏保守。这就是检验该有的双刃:对真随机游走「不误杀」(size 正确),对混沌「不放过」(power≈100%)。两者同时成立,BDS 才算可信。


6. 五类真实陷阱(中阶)#

  1. BDS 测的是「非 i.i.d.」,不是「能不能赚钱」:Wₘ 显著只证明「序列不独立」,中间可能是波动聚集、可能是混沌、可能是微观结构噪声。从「测出非独立」到「赚到钱」,还隔着去噪、成本控制、样本外三道关——别把「随机性破绽」直接当「alpha 信号」。
  2. ε 选择主观且敏感:ε 太小→几乎无邻近对,估计噪声巨大;太大→所有向量都邻近,C≈1 失去分辨力。实务用「数据标准差的固定比例」(0.5σ–1.0σ)并做多个 ε 的稳健性检查,单靠一个 ε 下结论危险。
  3. 小样本 Wₘ 方差大、m 不能太大:m 维嵌入要求样本量 N≫m(经验 N/m≳200)。m=6 至少要 1200+ 观测,否则相关积分估计太噪、Wₘ 剧烈摆动,bootstrap 也不稳。
  4. bootstrap 重抽样假设「零假设=i.i.d.」:若真实序列有短记忆(轻度 AR),bootstrap 重抽样会保留这种记忆,导致 size 失真。对疑似短记忆序列,应先去短记忆(拟合小阶 AR 取残差)再测 BDS。
  5. 前视与端点效应:延迟嵌入用到「未来」坐标,但这是确定性变换、不引入未来信息;真正要防的是把未来样本混进训练/回测窗口。此外标准化用全样本均值方差会轻微泄漏端点,大数据下可忽略,小样本建议滚动窗口内标准化。

7. 实战落地点:BDS 能做什么、不能做什么#

BDS 在量化工作流里有三个很实在的用途,但边界同样清晰:

  • 随机性体检 / 数据真伪:对接入的新行情源、新因子收益序列,先用 BDS 确认「它是不是真随机」。若号称「弱有效资产」的日收益 BDS 却显著非零,说明短中期存在可捕捉的依赖结构——这正是 mean-reversion / 波动聚类策略的土壤;若一段「策略信号」BDS 拒不掉,反而可能是过拟合或未来函数泄漏的预警。
  • 非线性结构侦测:ACF 全不显著、BDS 却显著 → 典型「非线性依赖」指纹(GARCH 型、混沌型)。这能指导你换对的工具:线性模型救不了它,得用 GARCH、隐马尔可夫、或非线性滤波。
  • 作为「随机性守门员」:在把序列喂给任何「假设 i.i.d.」的下游方法(蒙特卡洛定价、贝叶斯更新、某些风险模型)之前,先用 BDS 把关。它不独立,你就得先处理依赖,而不是蒙眼假设独立。

但它不能直接告诉你「去买哪只 / 下什么单」。BDS 显著只证明「序列非独立」,至于依赖是行为异象、是波动结构、还是噪声伪影,需要结合去噪、经济逻辑与样本外验证才能定性。把 BDS 当成「该不该继续深挖这条线索」的守门员,而不是印钞机。

8. 完整 Python 代码#

与本文全部数字、配图一一对应的端到端复现脚本(自洽合成数据,仅演示方法;真实落地请替换为真实序列、视情况调 ε 与 bootstrap 次数):

真实落地提示:ε 取 0.5σ–1.0σ 并多值稳健性检查;N/m≳200;对疑似短记忆序列先去 AR 再测;把 BDS 当作「随机性守门员」而非直接信号源——显著只证明非 i.i.d.,下一步需结合去噪、经济逻辑与样本外验证。

BDS 检验非线性依赖:用「距离相关」把独立性的破绽钉成一个数
https://blog.halo26812.eu.org/blog/bds-test-nonlinearity
Author halo
Published at 2026年7月21日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨