halo 的技术博客

返回

你拿到一段价格序列,想抽出「长期趋势」做择时、做均值回归的中轴、或者单纯把噪音滤掉看结构。最朴素的工具是移动平均——但它滞后(均线永远慢半拍)、端点缺数据(头尾几根线直接断掉)。再进一步是 HP 滤波,可它端点会漂(序列末尾被狠狠拽向最后一点)。

有没有一种方法,不预设任何基函数(不像小波要挑母小波、傅里叶要整段周期),直接从数据自身的波动里把「趋势 / 周期 / 噪声」一层层剥出来?

结论先放这:经验模态分解 EMD(Huang et al. 1998)做得到。 它不要求你先验地知道有几个周期、周期多长,而是靠「局部极大/极小值 → 上下包络线 → 取均值 → 反复筛(sift)」把序列拆成一组本征模态函数(IMF):高频 IMF 在前、低频 IMF 在后,最后剩下的一块残差就是趋势。把最低的若干块加起来,就是自适应提取的趋势。在我们合成的价格(慢饱和趋势 + 双周期 + 噪声)上,EMD 趋势相对真实趋势的 RMSE≈4.0,与 HP 滤波的 2.3 同量级、远优于 MA(80) 的 13.0;且它不被端点拖拽、不依赖预设基。 诚实地说:EMD 不是银弹——模态混叠、端点漂移、停止准则主观仍是真问题,后文五类陷阱要一一拆穿,但它确实是趋势提取工具箱里「自适应、不预设结构」的那把利器。

EMD 把价格拆成趋势+周期+噪声,再拼回趋势


1. 为什么 MA / HP / 小波都不够「自适应」#

  • 移动平均:固定窗口,等于假设「趋势是局部常数」。窗口越长越平滑也越滞后,端点直接缺数据。
  • HP 滤波:解 minτ(ytτt)2+λ(Δ2τt)2\min_\tau \sum (y_t-\tau_t)^2 + \lambda\sum(\Delta^2\tau_t)^2,靠惩罚项平滑。λ\lambda 要拍、且端点严重漂移(末点被拉向最后观测)。
  • 小波 / 傅里叶:要预设基——小波母函数、傅里叶整段周期。非平稳、突变多的金融序列里,基选错就全错。

EMD 的反叛在于:基由数据自己长出来。它只对 IMF 提两个「物理上合理」的要求:

  1. 极值点数与过零点数相等(或最多差 1);
  2. 上下包络线的均值处处为 0(局部对称)。

满足这两点的分量,就是「一个固有振荡模态」——IMF。


2. Sifting:EMD 的核心循环#

对信号 x(t)x(t),重复以下步骤得到第一个 IMF:

  1. 找所有局部极大 / 极小值;
  2. 用插值(本文用端点钳制的线性插值,避免样条外推炸端点)画出上、下包络线;
  3. 取包络均值 m(t)=12(上包络+下包络)m(t)=\frac12(\text{上包络}+\text{下包络})
  4. 剥离:h(t)=x(t)m(t)h(t)=x(t)-m(t)
  5. hh 还不符合 IMF 两条(有残余的局部不对称 / 极值数不匹配),把 hh 当新 xx 回到第 1 步;否则 hh 就是第 1 个 IMF。

把这第 1 个 IMF 从原信号减去,对残差重复整套流程,得到 IMF₂、IMF₃……直到残差变成单调或常数——那块残差就是趋势

实务提醒:_env_meanfill_value 把端点钳到最近的极值,这是 EMD 实战的关键一招——原始 Huang 用样条端点外推,金融序列头尾常因外推产生巨大假象,钳制能消掉绝大部分端点漂移。


3. 同一价格的 IMF 分层#

我们造一条合成价格:慢饱和趋势(logistic 型爬升)+ 两个周期(120 步的「商业周期」+ 37 步的短振荡)+ 噪声:

def make_signal(n=600, seed=1, noise_sd=0.05):
    rng = np.random.default_rng(seed); t = np.arange(n)
    trend = 100 + 40 / (1 + np.exp(-(t - 300) / 70))      # 慢饱和趋势
    cycle = 6 * np.sin(2 * np.pi * t / 120) + 3 * np.sin(2 * np.pi * t / 37)
    noise = noise_sd * rng.standard_normal(n) * np.std(trend)
    return t.astype(float), trend + cycle + noise, trend, cycle
python

把 EMD 拆出的所有 IMF 画出来——注意能量(std)从高频 IMF₁ 到低频 IMF 的变化:

同一价格的 IMF 分层:高频在前、低频在后

在我们这条 600 点序列上,EMD 拆出约 9 块:前面几块 std 在 0.2–0.5(高频噪声与短周期),中间有几块 std≈1(对应 37 步短周期与 120 步周期),最后一块 std≈14、就是纯趋势残差。IMF 按「局部振荡频率」自然排序——这是 EMD 最迷人的特性:你不用告诉它周期在哪,它自己按频率分层


4. EMD 趋势 vs MA / HP:不被端点拖拽#

把 EMD 趋势(取最低频 2 块 + 残差)和三类基准放一起比:

from scipy.linalg import solve_banded
def hp_filter(y, lam=1600.0):
    y = np.asarray(y, float); n = len(y)
    A = np.zeros((n, n))
    for i in range(n - 2):
        A[i+1, i] += lam; A[i+1, i+1] -= 2*lam; A[i+1, i+2] += lam
    A += np.eye(n)
    ab = np.zeros((5, n))
    for j in range(n):
        for off in (-2,-1,0,1,2):
            i = j + off
            if 0 <= i < n: ab[2+off, j] = A[i, j]
    return solve_banded((2, 2), ab, y)
python

EMD 趋势 vs HP / 移动平均

相对真实趋势的 RMSE(越小越好):

方法EMD (keep_low=2)HP 滤波长期 MA(80)短期 MA(20)
RMSE3.602.3412.997.52

读这张表要诚实:

  • HP 略优于 EMD(2.3 vs 3.6)——因为 HP 是「为平滑量身定做」的凸优化,而我们这条趋势正好是它擅长的平滑曲线;
  • EMD 远胜 MA(3.6 vs 13.0 / 7.5)——MA 的滞后与端点缺失让它在这条慢变趋势上误差巨大;
  • EMD 的关键优势不在「最小 RMSE」,而在「自适应 + 不预设基 + 端点稳」:它没被告知周期长度、没被调 λ\lambda,却把趋势抓得和 HP 同量级,且对序列末尾的处理不依赖外推。

5. 对噪声的鲁棒性#

把噪声强度从 0.02 调到 0.20(相对趋势标准差),看 EMD 趋势 RMSE 怎么变:

EMD 趋势对噪声的鲁棒性

RMSE 随噪声:0.02→4.110.05→4.050.10→3.910.20→4.09噪声翻倍,趋势误差几乎不动(稳定在 ~4)——因为 EMD 把高频噪声单独筛进前几块 IMF,提取趋势时只取最低频块,天然对加性噪声有隔离作用。这正是它比简单平滑更适合「脏数据」的原因。


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

  1. 模态混叠(mode mixing):若某个 IMF 里混进了本该属于另一频率的成分(比如一个突变同时污染多个 IMF),趋势提取会失真。标准解法是 EEMD(集合 EMD)——对数据加多次不同白噪声后做集合平均,把混叠「平均掉」。本文基础 EMD 不集合,突变多的真实价格上要警惕。
  2. 端点漂移:哪怕用了钳制,序列头尾几个点仍因极值稀疏而不稳。长周期趋势的端点尤其要复核——可补一段镜像/延拓再分解,或只信任中段趋势。
  3. 停止准则主观:「筛到什么时候算 IMF」靠 tol(本文 1e-4)和最大迭代次数。tol 太松→IMF 没筛干净(残存不对称);太紧→迭代爆炸、把噪声当 IMF。需对 tol 做敏感性检查。
  4. 噪声敏感与 IMF 数量:噪声大时前几块 IMF 几乎全是噪声,取哪几块当「趋势」要按频率(而非固定块数)判断。本文 keep_low=2 是在合成数据上调出的,真实数据上应画 IMF 谱、按拐决定。
  5. 计算成本:每次 sift 都要插值包络,长序列(万点以上日线、或 ticks)比 HP / MA 慢一到两个数量级。实务上先降采样或只对中段做 EMD,再插值回原频。

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

EMD 在量化工作流里最适合三类场景,但边界同样清晰:

  • 自适应趋势中轴:做均值回归 / 通道策略时,EMD 给出的趋势比 MA 滞后小、比 HP 端点稳,可直接当「公允价值中轴」;尤其是非平稳、结构突变多的品种。
  • 周期 / 状态分解:把 IMF 按频率分组(高频=噪声、中频=交易周期、低频=商业周期、残差=趋势),能直观看「当前波动是噪声还是真周期」——比硬套傅里叶基灵活。
  • 去噪预处理的「保真」版:需要滤高频噪声但不想动低频结构时,EMD 只丢前几块 IMF、保留所有低频信息,比一刀切的 MA 平滑更少扭曲趋势形状。

但它不能直接告诉你「现在该买还是该卖」。EMD 只是把信号拆开,趋势块本身不携带方向预测;而且模态混叠、端点不稳会直接污染趋势。把 EMD 当成「把价格拆成可解释分量」的拆解工具,趋势块再喂给你的择时 / 回归逻辑,而不是把它当决策黑箱。

8. 完整 Python 代码#

与本文全部数字、配图一一对应的端到端复现脚本(自洽合成数据,仅演示方法;真实落地请替换为真实价格、按需上 EEMD 与端点延拓):

真实落地提示:突变多的数据上用 EEMD(加噪集合平均)抑制模态混叠;头尾做镜像延拓再分解以稳端点;tol 与 keep_low 做敏感性检查;万点以上先降采样。EMD 是「自适应拆解」工具,趋势块要再喂给你的择时 / 回归逻辑,而非直接当决策黑箱。

经验模态分解 EMD 趋势提取:把价格拆成「趋势+一堆周期+噪声」再拼回你要的趋势
https://blog.halo26812.eu.org/blog/emd-trend-extraction
Author halo
Published at 2026年7月21日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨