当前位置:首页>python>无未来函数的多尺度择时:用 Python 玩转因果经验模态分解(CEMD)

无未来函数的多尺度择时:用 Python 玩转因果经验模态分解(CEMD)

  • 2026-09-09 08:49:24
无未来函数的多尺度择时:用 Python 玩转因果经验模态分解(CEMD)

用 Python 揭秘均值回归策略:你的收益从何而来?

量化交易的生死线:用 Python 算出你的最优下注比例

2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!

引言

在量化投资里,有一个「甜蜜的陷阱」几乎让每个人都栽过跟头——回测时业绩亮眼,一到实盘就原形毕露。罪魁祸首往往是「未来函数」(Lookahead Bias),也就是策略在计算某一天的信号时,偷偷用到了那一天之后才会出现的数据。

经验模态分解(Empirical Mode Decomposition,简称 EMD)是分析金融时间序列的一把利器,它能把杂乱无章的价格曲线拆解成不同时间尺度的「波」,让我们看清楚市场里同时叠加着的噪声、短线波动、中期周期和长期趋势。但传统 EMD 有个致命软肋:它在分解某个时点时,会用到整段历史(包括未来)的极值点,天然带着未来函数。

于是就有了本文的主角——因果经验模态分解(Causal EMD,简称 CEMD)。它是 EMD 的「在线版本」,在每个时点只用当下及之前的历史数据做分解,从根子上杜绝未来函数。本文将带你从原理到代码,一步步搞懂 CEMD,并在合成数据和六个真实资产(SPY、QQQ、TLT、GLD、BTC、IWM)上构建一套「CEMD–EMA」择时策略,最后用样本外测试检验它到底靠不靠谱。

一句话剧透:CEMD 不是能预测涨跌的水晶球,但它是一个非常好用的风控与市场结构分析工具。

一、EMD 到底在干什么

EMD 是希尔伯特—黄变换(HHT)的核心部件。它把一段时间序列 (比如价格或收益率序列)分解为若干个自适应的振荡分量,这些分量叫做本征模态函数(Intrinsic Mode Function,IMF),每个 IMF 对应一个不同的特征时间尺度:

其中  是第  个本征模态函数, 是残差(通常代表长期趋势)。

一个合格的 IMF 要满足两个条件:其一,极值点的个数与过零点的个数最多相差 1;其二,由局部极大值和极小值分别构成的上下包络线,其均值在任何点都约等于 0。这两个条件保证了 IMF 表现得像一个局部对称的振荡,具有明确的瞬时频率。

把股价想象成几种以不同速度运行的隐藏力量叠加的结果,就很好理解了:

  • • IMF_1(高频波动):捕捉最快的抖动,通常对应市场噪声、流动性扰动和突发的波动率尖峰,可用作波动率过滤器。
  • • IMF_2(短期动态):比 IMF_1 更有结构的快速波动,可反映短线动量的转变,用作短线择时信号。
  • • IMF_3(中期周期):对应市场的摆动周期,可用于动量/周期确认、过滤假信号、识别潜在拐点。
  • • IMF_4(长期振荡):反映更宏观的市场状态,如扩张与收缩、风险偏好切换,可用于市场状态(Regime)判别。
  • • 残差(Residual):剩下的最慢分量,代表市场的「脊梁骨」——长期趋势,可用于战略性趋势跟随。

需要提醒的是:每个 IMF 的具体金融含义,会因资产、采样频率和分解参数的不同而变化,上面的对应关系是一种典型解读,不是铁律。

EMD 的优点在于:自适应分解(不需要预设滤波器)、能处理非平稳信号(这对波动率不断切换的金融市场很关键)、天然做多尺度分析、每个分量都可独立解读。

EMD 的致命缺点就是前面提到的未来函数:在完整历史数据上做分解时, 时刻的结果会被未来价格「污染」,导致回测虚高、信号不可实盘复现、实盘表现与历史模拟严重背离。

二、上手 PyEMD

实际操作 EMD 需要安装 EMD-signal 这个包:

!pip install EMD-signal

EMD 内部靠一个叫「筛分」(sifting)的迭代过程工作:找出局部极大值和极小值 → 构造上下包络线 → 计算平均包络 → 从信号中减去平均包络 → 重复,直到满足 IMF 条件。

下面是一个最小示例:

import numpy as npimport matplotlib.pyplot as pltfrom PyEMD import EMD, Visualisation# 构造一个示例输入信号t = np.arange(0, 3, 0.01)S = np.sin(13 * t + 0.2 * t**1.4) - np.cos(3 * t)# 执行 EMD 分解emd = EMD()emd.emd(S)imfs, res = emd.get_imfs_and_residue()  # 取出各个 IMF 和残差# 可视化plt.plot(t, S)vis = Visualisation()vis.plot_imfs(imfs=imfs, residue=res, t=t, include_residue=True)  # 画 IMFvis.plot_instant_freq(t, imfs=imfs)  # 画瞬时频率vis.show()

易错点提醒:EMD 本质上是串行算法,速度较慢且很难优化——因为筛分本身是迭代的,而且下一个 IMF 依赖上一个的计算结果,无法简单并行化。数据量大时要有心理准备。

三、因果版 EMD:CEMD 的核心思想

CEMD 的思路其实很朴素:在  时刻,只用  时刻及之前的历史数据  做分解。实践中通常采用一个长度为  的滚动历史窗口:

在每个窗口上做完 CEMD 分解后,只保留最新一个样本点的各 IMF 数值,然后窗口向前滑动一格,重复整个过程。

它的完整工作流是这样的:收集历史数据并构造最近  个观测的滚动窗口 → 在窗口上做 CEMD 分解 → 只提取每个 IMF 和残差的最新一个值 → 根据时间尺度生成多尺度特征 → 组合特征生成交易信号 → 新数据到来时,丢掉最老的一个观测、追加最新的一个,重复上述过程。

由于每一次分解都只基于历史数据,之前生成的交易信号永远不会被「回头修改」——这正是它避免未来函数的关键。

四、CEMD 的核心实现

下面这段代码,就是把标准 EMD 改造成滚动窗口在线分解的核心:

import numpy as npimport pandas as pdfrom PyEMD import EMDdef causal_emd(series, window=600, max_imfs=5):    """    滚动窗口的因果 EMD    在 t 时刻:IMF(t) = EMD(series[t-window:t])    绝不使用任何未来观测值。    """    emd = EMD()    causal_imfs = []    for t in range(len(series)):        # 历史数据不够长,先用 NaN 占位        if t < window:            causal_imfs.append([np.nan] * max_imfs)            continue        # 仅取历史窗口的数据        x = series.iloc[t - window:t].values        decomposition = emd(x)        # 提取当前各 IMF 的最新值        values = []        for i in range(max_imfs):            if i < decomposition.shape[0]:                values.append(decomposition[i, -1])  # 该 IMF 的最后一个值            else:                values.append(np.nan)        causal_imfs.append(values)    return pd.DataFrame(        causal_imfs,        index=series.index,        columns=[f"IMF_{i+1}" for i in range(max_imfs)]    )

CEMD 的优点:无未来函数、自适应分解、多尺度信息、非参数(不需要预设频率或平稳性假设)、能过滤噪声、天然识别市场状态、比黑箱机器学习模型更可解释。

CEMD 的代价(务必牢记):计算成本高,其复杂度约为 ,其中  是序列长度、 是窗口大小、 是 IMF 个数、 是每个 IMF 的平均筛分迭代次数;此外还有窗口长度敏感、边界效应、模态混叠(mode mixing)、IMF 顺序不保证、因果性带来的滞后等问题。最重要的一句话是:CEMD 是信号分解技术,不是预测模型。

五、合成数据实验:先在「已知答案」上验证

为了验证 CEMD 是否真能还原出隐藏成分,我们先构造一个「答案已知」的合成价格序列——它由长期趋势、商业周期、短期周期、噪声四个成分叠加而成:

import numpy as npnp.random.seed(42)N = 2500t = np.arange(N)# IMF4:长期市场趋势trend = 0.0025 * t + 2.5 * np.sin(2 * np.pi * t / 1800)# IMF3:商业周期business_cycle = 1.4 * np.sin(2 * np.pi * t / 420) + 0.6 * np.sin(2 * np.pi * t / 700)# IMF2:短期市场周期short_cycle = 0.7 * np.sin(2 * np.pi * t / 80) + 0.35 * np.sin(2 * np.pi * t / 35)# IMF1:市场噪声 + 动量脉冲(AR(1) 过程模拟持续性动量)white = np.random.normal(scale=0.35, size=N)momentum = np.zeros(N)phi = 0.88eps = np.random.normal(scale=0.18, size=N)for i in range(1, N):    momentum[i] = phi * momentum[i - 1] + eps[i]noise = white + momentum# 最终的合成金融时间序列price = 100 + trend + business_cycle + short_cycle + noise

对这段合成数据做 EMD 分解并做滤波后,我们发现了一个漂亮的结果:去掉 IMF_1 后剩下被移除的分量,与真实的白噪声成分之间的皮尔逊相关系数高达 0.8153;去掉 IMF_1 + IMF_2 后,被移除分量与完整噪声成分  的相关系数为 0.6327。后者相关性略低是意料之中的——因为  里除了白噪声,还含有一个持续性的动量成分,这部分会被分散到多个 IMF 尺度上去。

方法论上的诚实提醒:皮尔逊相关衡量的是线性相似度,并不等于因果对应或精确还原。若要更严谨,应当补充 RMSE、方差解释率等指标,逐一对照每个已知的真实成分。

更关键的对比是:把标准 EMD 和 CEMD 在相同滤波配置下的输出相减,差异不到 1.5%,可以忽略不计。这说明 CEMD 并没有丢失标准 EMD 能捕捉到的重要信号结构,只是它诚实地不去偷看未来而已。

六、合成数据上的交叉择时策略

有了可靠的分解,我们就能构建第一个策略:把短期分量(IMF_1 + IMF_2)作为「快线」,长期分量(IMF_4 + IMF_5)作为「慢线」,快线上穿慢线做多、下穿则空仓。

import numpy as npfrom PyEMD import EMDdef causal_emd_group_crossover(price, window=500, fast_imfs=[0, 1], slow_imfs=[3, 4]):    """    因果 EMD 的 IMF 分组交叉策略    快线 = IMF1 + IMF2   慢线 = IMF4 + IMF5    """    n = len(price)    fast_series = np.full(n, np.nan)    slow_series = np.full(n, np.nan)    emd = EMD()    for t in range(window, n):        x = price[t - window:t]  # 因果滚动窗口        imfs = emd.emd(x)        if imfs.shape[0] < 5:  # 需要至少 5 个 IMF            continue        fast_component = np.sum(imfs[fast_imfs], axis=0)  # 快线分量重构        slow_component = np.sum(imfs[slow_imfs], axis=0)  # 慢线分量重构        fast_series[t] = fast_component[-1]  # 只取最新值(因果)        slow_series[t] = slow_component[-1]    # 生成持仓信号    position = np.zeros(n)    for t in range(1, n):        if np.isnan(fast_series[t]):            continue        position[t] = 1 if fast_series[t] > slow_series[t] else 0    return fast_series, slow_series, position

在计算收益时,务必把持仓信号向后平移一天(shift(1))再乘以次日收益——这是防止未来函数的关键一步:

import pandas as pdreturns = pd.Series(price).pct_change()live_position = pd.Series(position).shift(1).fillna(0)  # 信号滞后一天执行strategy_returns = returns * live_positionequity = (1 + strategy_returns).cumprod()  # 净值曲线

样本内结果令人振奋:CEMD 策略实现了 19.51% 的总收益(买入持有仅 8.54%),同时波动率更低(4.99% 对 8.06%)、最大回撤更小(−5.92% 对 −7.78%),夏普比率从 0.143 提升到 0.385。妙就妙在,更高的收益并非靠承担更多风险换来的,而是波动率显著更低。

但这还只是样本内结果。真正的考验是样本外(Out-of-Sample,OOS)测试:把数据按 85% / 15% 切分,参数只在训练段确定,然后原封不动拿到从未见过的测试段上跑。样本外结果显示,CEMD 策略仅用 11.2% 的时间暴露在市场中,就取得了正收益,最大回撤仅 −1.78%(买入持有为 −5.39%),夏普比率 0.358 对 0.052。不过要清醒地认识到:这个样本外结论只基于 3 笔交易,属于初步证据,它更多说明的是「风险降低和选择性暴露」,而非确凿的超额收益能力。

七、六大真实资产实战:CEMD–EMA 策略

接下来把方法搬到真实市场。这里的核心工具是「因果重构 + 双均线交叉」:先用滚动 CEMD 重构出一条平滑的价格信号(保留选定的 IMF 加残差),再在这条信号上算快、慢两条指数移动平均线(EMA),快线上穿慢线做多、下穿空仓。

from PyEMD import EMDimport numpy as npimport pandas as pddef causal_emd_reconstruct(series, window=500, keep_imfs=(2, 3, 4, 5)):    """    因果 EMD 价格重构(可自选保留哪些 IMF)    只用历史窗口,绝不使用未来观测。    """    n = len(series)    reconstructed = np.full(n, np.nan)    residual = np.full(n, np.nan)    emd = EMD()    values = series.values    for t in range(window, n):        x = values[t - window:t]  # 因果窗口        imfs = emd.emd(x)        n_imfs = imfs.shape[0]        if n_imfs == 0:            continue        # 重构选定的 IMF        smooth = np.zeros(imfs.shape[1])        for i in keep_imfs:            if i < n_imfs:                smooth += imfs[i]        # 残差(趋势)        reconstructed_imfs = np.sum(imfs, axis=0)        res = x - reconstructed_imfs        reconstructed[t] = smooth[-1] + res[-1]  # 只取最新值        residual[t] = res[-1]    return (pd.Series(reconstructed, index=series.index),            pd.Series(residual, index=series.index))

信号生成时同样要滞后一期执行,杜绝未来函数:

# 在重构信号上计算快、慢 EMAema_fast = reconstructed.ewm(span=fast_span, adjust=False).mean()ema_slow = reconstructed.ewm(span=slow_span, adjust=False).mean()# 快线在慢线上方则做多(1),否则空仓(0)signal = (ema_fast > ema_slow).astype(int)# 关键:信号滞后一期再乘以收益,防止未来函数strategy_returns = (signal.shift(1) * returns).fillna(0)

通过对窗口长度、IMF 组合、快慢 EMA 跨度做网格搜索(以夏普比率排序选优),六个资产的样本内表现呈现出清晰的规律。这里挑几个典型说说:

  • • SPY:策略并没有跑赢买入持有的绝对收益(总收益 192% 对 300%),但它把波动率降低约 40%、最大回撤降低约 44%,仓位暴露仅 47.6%。它是典型的「用收益换风险」。
  • • QQQ:最亮眼的案例——收益和风险两头都赢。年化 23.50% 对 19.75%,波动率 15.97% 对 22.34%,最大回撤 −16.83% 对 −35.12%,夏普 1.40 对 0.92,仓位暴露仅 75.93%。
  • • TLT:在买入持有亏损 5.97% 的区间里,策略反而赚了 53.85%,最大回撤从 −48.35% 收窄到 −16.30%,堪称风控典范。
  • • GLD:收益几乎与买入持有持平(约 283%),但波动率更低、回撤更小(−13.22% 对 −22.00%),卡玛比率 1.09 对 0.66。
  • • BTC:总收益 387.84% 对 203.29%,波动率从 55.77% 降到 45.01%,最大回撤从 −83.40% 收窄到 −64.33%。
  • • IWM:小幅牺牲收益,换来大幅降低的波动(13.44% 对 23.03%)和回撤(−30.02% 对 −41.13%),夏普反而更高(0.72 对 0.52)。

一个反复出现的现象值得注意:这些策略的胜率往往很低(有的甚至不到 20%),单笔期望值为负,但总体表现却很好。这说明它们靠的是抓住少数几波大行情,而不是频繁地赢小钱——典型的趋势跟随特征。

八、样本外验证:最硬核的检验

前面的样本内结果再漂亮,也可能是「过拟合」的幻觉。真正的试金石是:参数只在训练段优化,冻结后原封不动地丢到从未见过的样本外数据上。这里所有资产都遵循同一套严格流程——训练段选参 → 冻结参数 → 用因果 CEMD 在全序列上生成信号 → 只截取样本外区间评估。

样本外的核心发现如下:

  • • QQQ 提供了最令人鼓舞的稳健性证据。冻结参数(窗口 100、IMF 3/4/5、快线 20、慢线 150)后,样本外年化 12.08% 略微超过买入持有的 11.94%,同时波动率从 23.64% 降到 15.02%,最大回撤从 −34.83% 收窄到 −18.23%,夏普从 0.51 升到 0.80。
  • • GLD 样本外表现同样出色,年化 83.10% 对 73.87%,夏普 3.73 对 3.17,波动和回撤双降。
  • • BTC 样本外年化 17.04% 对 11.86%,波动率从 42.79% 降到 29.52%,最大回撤从 −66.89% 大幅收窄到 −36.39%。
  • • SPY 样本外没有跑赢绝对收益,但保住了低波动、小回撤、高夏普的风控优势。
  • • TLT 样本外两者都亏损,但策略亏得更少(年化 −4.74% 对 −8.31%),回撤也更小——风控有效,但样本外并未盈利。
  • • IWM 结果最保守:策略样本外收益和夏普略低于买入持有,虽仍降低了波动和回撤。这恰恰是一个重要的反面教材——样本内的强势并不自动等于样本外的超额收益。

从训练段到样本外,业绩普遍出现回落,这完全正常且符合预期。真正有价值的信息是:在多数资产上,降低风险这个特性成功地延续到了未见过的数据上。

总结

本文系统梳理了从标准 EMD 到因果 CEMD 的完整链条,并在合成数据与六大真实资产上做了从分解、滤波、构建策略到样本外验证的全流程实验。可以提炼出几条核心结论:

第一,CEMD 的价值在于「诚实地分解」,而非「预测未来」。它是一种自适应、可解释的多尺度信号分解框架,能把噪声、短线波动、中期周期和长期趋势清晰地分离开,而全程只使用实盘中真实可得的历史信息,从根本上杜绝了未来函数。

第二,CEMD–EMA 策略最稳定、最可靠的贡献是「风险可控的市场参与」,而不是普适的超额收益。在多个资产上,即便它没有跑赢买入持有的绝对收益,也显著降低了波动率、最大回撤、尾部风险和市场暴露时间。QQQ、GLD、BTC 表现最强,SPY 和 IWM 则更偏向风控。

第三,不同市场对同一分解尺度的反应差异很大,这提醒我们把 CEMD 当作一个需要因地制宜的自适应框架,而非一条放之四海皆准的交易铁律。

最后,务必牢记它的局限:滚动窗口计算开销大、对窗口长度和 IMF 组合敏感、存在边界效应与模态混叠、IMF 顺序不保证,而且——它不是预测工具,不要把它的输出当成方向性的涨跌判断。对于学习 Python 的量化爱好者来说,CEMD 更适合作为你工具箱里的一把「结构分析与风控」的手术刀,而不是点石成金的魔法棒。

参考文章

加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。

财经数据与量化投研知识社区

2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:

  1. 1. 双典系统赋能:获赠《财经数据宝典》与《量化投研宝典》完整文档,凝练多年实战经验,构建系统化知识框架;
  2. 2. 量化因子日更教程(2026重磅新增):每日更新「量化因子专题教程」,配套完整可运行代码与实战案例,深度拆解因子构建、回测与优化全流程;
  3. 3. 量化文章专题教程库:500+篇星球独有高质量教程式文章,系统覆盖策略开发、因子研究、风险管理等核心领域,内容基本每日更新,并配套精选学习资料与实战参考;
  4. 4. 量化投研实战课程:赠送《AKQuant-入门及实战》《PyBroker-入门及实战》视频课程,手把手教学,快速掌握量化策略开发技能;
  5. 5. 财经数据支持:定期更新国内外财经数据,为策略研发提供精准、可靠的数据基础;
  6. 6. 顶尖学者与行业专家分享:年度邀请学术界博士与业界资深专家开展前沿论文精讲与实战案例分享,不少于4场,直击研究前沿与产业实践;专家直连答疑:与核心开发者及领域专家实时互动,高效解决投研实战难题;
  7. 7. 专业社群与专属福利:加入高质量交流社群,获取课程折扣及更多独家资源。

星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!

好文推荐

1. 用 Python 打造股票预测系统:Transformer 模型教程(一)

2. 用 Python 打造股票预测系统:Transformer 模型教程(二)

3. 用 Python 打造股票预测系统:Transformer 模型教程(三)

4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)

5. 揭秘隐马尔可夫模型:因子投资的制胜武器

6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用

7. 金融 AI 助手:FinGPT 让你轻松掌握市场分析

8. 量化交易秘籍:为什么专业交易员都在用对数收益率?

9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解

10. 掌握金融波动率模型:完整 Python 实现指南

好书推荐

最新文章

随机文章