halo 的技术博客

返回

跨资产联动最阴险的地方,不是「一直同涨同跌」那种一眼看穿的,而是局部的、带特定周期的、只在某段窗口冒出来的那一种。比如两支平时各走各有的资产,到了某段危机里突然开始踩同一个 16 日节奏同涨同跌——你用最朴素的 Pearson 相关系数一算全样本,得到 r≈0.08,心想「这俩独立得很」。可就在那 120 个交易日里,它们正死死锁在同一频率上。

结论先放这:能抓「局部同频联动」的不是时域相关系数,而是小波相干(wavelet coherence)。 它把「相关性」从一个数,展开成一张(周期 × 时间)的二维场,能在某个周期、某段时间里,把藏起来的联动单独拎出来。本文用纯 numpy 从零实现 Morlet 连续小波变换(CWT)+ 平滑相干,合成两支「平时独立、危机窗口共享 16 日周期」的资产:全样本时域相关仅 0.08、危机窗口也只 0.43,但 16 日带的相干在危机段冲到 0.95(其余时段 0.84)——时域指标彻底失明的地带,时频相干一眼锁定。所有图表均由下文 Python 真实计算,非占位图。

跨资产序列:平时各自游走、中段危机窗口注入同频联动,时域几乎看不出结构

一、为什么 Pearson 相关系数在这里失明#

Pearson 相关 r = ρ(x,y) 算的是全样本二阶矩——它把整条序列压成一个数。如果两支资产只在「中段 120 天、16 日周期」上联动,其余 392 天各自乱走,那 120/512 ≈ 23% 的局部信号,会被 77% 的噪声稀释掉,最终 r 贴近 0。

下面这三张图就是证据。资产 A、B 的时域序列看着毫无章法;把它们画成散点,也是一团没有斜率的云;最终全样本 r = 0.081,谁都会判「独立」:

时域相关失明:资产 A/B 全样本 r 仅 0.08、散点无结构,危机里的同频联动被平均掉了

可真相是:危机窗口里它们被钉在同一个 16 日正弦上。时域指标看不见,是因为它不区分周期、不区分时间。这正好是小波相干要解决的。

二、连续小波变换:把序列拆成「周期 × 时间」的谱#

小波变换的核心思想:用一族会伸缩、会平移的母小波 ψ,去「戳」原序列。在尺度 a、位置 b 上的系数

W(a,b)=1ax(t)ψ ⁣(tba)dtW(a,b) = \frac{1}{\sqrt{a}}\int x(t)\,\psi\!\left(\frac{t-b}{a}\right)dt

把序列同时按「周期(≈尺度 a)」和「时间(b)」展开成一张谱。金融上最常用 Morlet 小波(一个被高斯包络调制的复正弦),因为它在时频两域都有不错的局部性,且相位有清晰物理含义(可拿来算相位差/领先滞后)。

下面是从零实现的 Morlet CWT,关键在 Fourier 域做卷积(FFT 提速),并只对正频构造 Morlet 谱(保证小波满足可容许条件):

跑一遍资产 A,得到它的时频能量谱 |W|²。能清楚看到 40 日主周期成片亮起,而中段叠加了一个 16 日的危机同频带:

资产 A 的小波能量谱:40 日主周期成片亮团,中段叠加 16 日危机同频带

三、小波相干:两张谱的「时频相关」#

连续小波只画单条序列的谱。要量「A、B 在某周期、某时间上联动多强」,得把两条谱的交叉谱做平滑再归一。定义小波相干(Torrence & Compo 1998):

R(a,b)=S(W1W2)2S(W12)S(W22)[0,1]R(a,b) = \frac{\left|S(W_1 W_2^*)\right|^2}{S(|W_1|^2)\,S(|W_2|^2)} \in [0,1]

其中 S(·) 是「时间方向 × 尺度方向」的滑动平滑(用 uniform_filter)。直观讲:R≈1 表示在这个(周期,时间)格点上,A 和 B 的振荡同相且强相关;R≈0 表示没联动。

def wavelet_coherence(W1, W2, dt=1.0, time_win=8, scale_win=3):
    cross = W1 * np.conj(W2)
    p1 = np.abs(W1)**2
    p2 = np.abs(W2)**2
    S = lambda M: (uniform_filter(M.real, size=(scale_win, time_win))
                   + 1j*uniform_filter(M.imag, size=(scale_win, time_win)))
    Sc, Sp1, Sp2 = S(cross), uniform_filter(p1, size=(scale_win, time_win)), uniform_filter(p2, size=(scale_win, time_win))
    R = np.abs(Sc)**2 / (Sp1*Sp2 + 1e-12)
    return np.clip(R, 0, 1)
python

画成相干图,真相立刻浮现:在 16 日周期那条水平带上、时间轴 t≈180–300 的危机窗口内,整条红得发亮(R≈1)——这正是被时域相关抹掉的联动:

小波相干 |R|:危机窗口中段、16 日周期处相干≈1(红色高亮),其余时段弱

为了证明「这真的是被时域指标漏掉的,而非我编的」,把三个数字摆一起:

  • 时域相关(全样本):r = 0.081
  • 时域相关(危机窗口):r = 0.431
  • 小波相干(16 日带 · 危机窗口):R = 0.947
  • 小波相干(16 日带 · 其余时段):R = 0.838

相干 vs 时域相关:时域局部只到 0.43,时频相干在危机波段冲到 0.95

关键洞察:危机窗口里的「同频联动」是频率特异的——只有 16 日这个周期在联动,别的频率依然各走各的。时域相关把「16 日联动 + 其他频率无关 + 噪声」搅成一锅,得出 0.43;而小波相干在 16 日带上单独剥离,得到 0.95。一个把信号稀释,一个把信号放大。

再补一张「功率增量图」坐实这件事:把资产 A 的局部功率减去该周期的全样本均值,16 日带在危机窗口(t≈220–250)单独红了一块,证明这个频率是「中段才涌现」的,不是一直存在:

局部功率增量(减各周期全样本均值):16 日危机同频带在中段被「点亮」

四、五类真实陷阱(必看)#

1. 时域平均会抹平局部联动,但反过来也成立——别用相干假装「全样本相关」。 相干 R 是时频场的局地量,它高只说明「在那个周期、那段窗口」联动强,不代表两支资产整体同步。本文全样本时域 r 才 0.08,相干高点 0.95 是局部事实。汇报时务必同时给「时域整体 r」和「相干热点(周期+窗口)」,缺一个都是误导。

2. 边缘效应(Cone of Influence, COI)会伪造高相干。 小波在序列两端、以及大尺度(长周期)的边缘,信息是「缺数据补出来」的,相干在此处会被系统性高估。标准做法是画 COI 锥、并把锥外区域在图里挖掉(本文 coi_mask 把每尺度 ±0.5·period 的边缘置 NaN)。凡是不标 COI 的相干图,两端的红区大概率是假的。

3. 平滑窗口是相干的「超参数」,设错结论翻车。 R 里的 uniform_filter 窗口 (scale_win, time_win) 决定了「多宽的范围算一个联动」。窗口太小→噪声剧烈、相干乱跳;太大→把瞬态联动抹成一片、还可能跨尺度混频。实务里建议 time_win ≈ 周期本身量级(几到十几个时间点),并在结论里注明窗口参数,最好做灵敏度检查。

4. 尺度/周期选错了,热点会跑偏。 小波对「周期=尺度×常数」敏感,且不同小波(Morlet / Paul / DOG)的中心频率不同。如果你把 16 日周期设成采样只覆盖到 6–64 日,热点会准;若采样漏掉 16 日这一档,它会被「摊」到邻近尺度,看着像 12 日或 20 日。务必先用单条序列的小波功率图(本文图三)确认热点周期位置,再读相干图。

5. 相干 ≠ 因果,领先滞后要单独算。 R 高只说明「同频同相地一起动」,不说明谁带谁。真要判断 A 领先 B 多少天,得用相位差 ∠(S(W1·W2*)) 换算成时间滞后(Morlet 下 lag = (phase/2π)·period)。本文只证了「联动存在」,没证方向;落到交易信号前,这一步不能省。

结语#

跨资产联动最容易被漏掉的,是那种「带特定周期、只在某段窗口出现」的局部同步——而传统 Pearson 相关恰恰把这种信号平均成噪声。小波相干用一张(周期 × 时间)的时频场,把它单独拎出来:本文合成资产全样本时域相关仅 0.08、危机窗口 0.43,可 16 日带的相干在危机段冲到 0.95。但记住五条边界——时域整体 r 与相干热点要同报、COI 边缘必须挖掉、平滑窗口是超参数、先功率图定周期再读相干、相干不等于因果要补相位滞后。下回有人拿「r 很低所以这俩没关系」当结论,你可以说:那是时域的失明,不是真相的缺席。

小波相干与跨资产联动:用「时频放大镜」抓出藏起来的共同周期
https://blog.halo26812.eu.org/blog/wavelet-coherence
Author halo
Published at 2026年7月23日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨