EMD经验模态分解实战教学
用MQL5将非平稳行情拆成多周期分量
为什么交易者需要信号分解
我们在实盘中面对的每一根K线,本质都是大量宏观与微观过程叠加的结果:央行预期、流动性脉冲、算法单流、噪声扰动。直接分析原始价格序列,就像看一张被多重透明图层遮住的图,规律互相遮挡、扭曲。把复合过程分解为单个分量再逐一研究,是提升认知与预测可靠性的根本思路。市场分析里很多方法,无论明说与否,都属于从过程里挑出某些分量的分解法。
传统做法中,即便最基础的技术分析也暗含分解:把市场变动归因于若干事件。但若除了一条价格序列外没有任何额外信息,就只能依靠数学或经验分解。最简单的例子是在图表上画一条移动平均线(MA),用原价减MA得到残差;再对残差用更长周期MA,就拆出两层MA加残差。这类做法随意,但要点在于结果属性是否满足分析需要。
傅里叶与小波变换的局限
最著名的分解法是傅里叶变换,它属于使用固定谐波基的正交变换,把过程表示为恒定频率与幅度的谐函数。其有两个关键约束:第一,变换基固定且预先设定,不依赖序列本身;第二,分量频率幅度全局不变,因此若序列在区间内性质变化(非平稳),结果只反映平均状态,无法体现时变特征。
小波变换将傅里叶的固定基改为预设小波,并为每个分量引入随时间的尺度与水平参数,从而能处理非平稳性。两者数学完备、算法高效、通用性强,但基函数仍由人预先选定,不是从数据自适应产生。实践上,我们更希望一种基函数能依数据自适应、且天然处理非平稳与非线性的变换,经验模态分解(EMD)正是此类方法的代表。
- 傅里叶:固定谐波基,全局恒定频率/幅度,不适非平稳
- 小波:预设小波基,带时间参数,仍非数据自适应
- EMD:基由序列本身产生,自适应、可处理非线性非平稳
经验模态分解(EMD)核心概念
EMD是希尔伯特-黄变换(HHT)的第一阶段,由黄锷提出。它将数据拆为本质模态函数(IMF),这些IMF在分析上未被设定,完全由所分析序列单独确定。IMF需满足两条:极值数(最大+最小)与零穿越数相等或差1;任意点由局部极大包络均值与局部极小包络均值之和为零。分解产出一列频率分阶的IMF,后者频率低于前者,但幅度和频率可沿时间轴变化,故用“频率”形容只是近似。
与傅里叶、小波不同,EMD不预设基,而是让数据自己“长”出分量。它特别适合行情这类非线性和非平稳序列。但EMD本身不计算瞬时频率谱(那是HHT第二阶段),本文聚焦EMD算法与MQL5实现。
EMD算法步骤详解
黄锷算法以局部极值包络为基础:先识别所有局部极大与极小,用三次样条连接生成上下包络线;计算两包络均值,从原序列减去该均值,得到首轮IMF逼近。此过程称为筛选(sifting),反复重识别极值并重复,直到满足停止标准,即得一个IMF。然后从原信号减掉该IMF,对剩余重复,直到残数仅含不超过两个极值。
筛选停止标准直接影响分解结果。常用做法是计算相邻筛选间差异比值,若几乎无变化则停止;默认容差可能引致200~300次迭代,文献多警示过大迭代。也可强制固定迭代次数(如10次)换取速度但略失真。整体算法基于经验而非严格数学,简单却有几个弱点:末端效应、极值平坦区处理、隐藏分量提取不足。
- 识别局部极值(改进算法把等值区段中央视为极值)
- 三次样条绘上下包络
- 包络均值从原序列扣除
- 循环筛选至停止标准
- 原序列减IMF后递归直至残数极简
MQL5中CEMDecomp类接口
本文附带的CEMDecomp类实现了上述EMD。公共变量:N为序列长,调用Decomp后等于输入长;Mean为输入均值;nIMF为分量计数,等于提取IMF数加2(索引0为去均值原序列,索引nIMF为残数);MaxIMF默认16,到数即停;MaxIter默认2000;FixedIter默认0表示精度停止,设1则每个IMF固定10次迭代。方法Decomp(double &y[])执行分解;GetIMF(double &x[],int nn)读取第nn个分量至数组。
class CEMDecomp:public CObject
{
public:
int N;
double Mean;
int nIMF;
int MaxIMF;
int MaxIter;
int FixedIter;
double IMFResult[];
private:
double X[];
double Imf[];
double XMax[]; double YMax[];
double XMin[]; double YMin[];
double EnvUpp[]; double EnvLow[];
double Eps;
double Tol;
public:
void CEMDecomp(void);
int Decomp(double &y[]);
void GetIMF(double &x[], int nn);
private:
int arrayprepare(void);
void extrema(double &y[],int &nmax,double &xmax[],double &ymax[], int &nmin,double &xmin[],double &ymin[]);
int SplineInterp(double &x[],double &y[],int n,double &x2[], double &y2[],int btype=0);
};
使用时要先构造实例,传引用数组给Decomp,再用GetIMF取分量。注意若目标数组短于分量长,仅填充可用部分。完整例子见文末EMDecomposition.zip,解压至Indicators或Scripts目录,编译运行EMDecomp_Test.mq5,并允许外部库。
调用示例与实操步骤
下面脚本取当前品种400根开盘价,分解后若无误且分量大于3,则读取2号IMF。实际使用中可改CopyClose、调节周期,或循环GetIMF画出各层。
#include "CEMDecomp.mqh"
void OnStart()
{
int n,ret;
double yy[],imf2[];
n=400;
ArrayResize(yy,n);
ArrayResize(imf2,n);
CopyOpen(_Symbol,PERIOD_CURRENT,0,n,yy);
CEMDecomp *emd=new CEMDecomp();
ret=emd.Decomp(yy);
if((ret==0)&&(emd.nIMF>3))
emd.GetIMF(imf2,2);
delete(emd);
}
- 准备数组并CopyOpen/CopyClose填充行情
- new CEMDecomp并设MaxIMF/MaxIter/FixedIter
- 调用Decomp分解
- 用GetIMF按索引取IMF或残数
- 绘图或进一步外推处理
极值识别的两个算法与坑
类里用了两套极值识别。第一套改进版将等值区间视为极值并置于中央,用于决定分解停止(如无极值则弃IMF)。但若用它绘包络,矩形序列会生成平行直线使筛选失效。第二套把既是最大又是最小的点标出,绘包络后可提取隐藏分量,测试案例可见效。然而三角形序列仍难提取,这是本实现的弱点,或可换CEEMD解决。
EMD在交易中的典型应用
除了HHT谱,EMD可直接平滑:丢弃最高频IMF(如首层),余下加残数即得平滑曲线;丢两层更平滑。预测上,对每个IMF与残数分别外推(如10步线性),再加总得序列预测,且可弃高频抑噪。趋势剔除:把除最后几个分量外的IMF相加,残数或低频IMF当趋势,灵活可控。图例用USDCHF、EURUSD、XAUUSD展示了分解与去趋势效果。
但必须强调,社区实测指出EMD非因果:每增加一个新数据点,历史IMF可能全变,使其在实时交易用作价格过滤器并不比LWMA好。若结合SVM等机器学习做样本外预测,必须每步重算EMD,否则泄露未来信息使回测无效。因此它更适合离线结构分析、周期分量研究,而非直接作为信号线。
- 平滑:舍高频IMF,余加残数
- 预测:分量分别外推再求和,抑高频噪
- 去趋势:舍首尾分量,余相加
- 周期研究:观察IMF振荡结构
与其他分解方案对比及总结
相较傅里叶(静态基)、小波(预设基),EMD基自适应、非平稳友好;但计算重、非因果、末端敏感。若需严格因果滤波,HP滤波器或样条可能更稳。本文目的在熟悉EMD及实现特点,所附CEMDecomp.mqh与CEMD_2.mqh(附录另一实现)可供试验,但均非完美,应进一步测试改进。