预测时间序列(第 1 部分):经验分解模式(EMD)方法(基础篇)
用 EMD 把行情拆成几条本征模态
经验模态分解(EMD)的核心思路,是把一段非平稳的价格序列逐层剥离出若干本征模态函数(IMF)和一个残余趋势项。每一层 IMF 对应不同时间尺度的波动,高频层贴近噪声明细,低频层接近趋势骨架。对 MT5 用户来说,这意味着可以把一根乱糟糟的 H1 收盘价曲线,拆成 5~8 个分量再分别判断结构。 原文作者在 2020 年 6 月发布的这套方法,实测在 EURUSD H1 上能把原序列分解为 7 个 IMF 加 1 个残差,最高频 IMF 周期约 4~6 根 K 线。这种拆解不预设基函数,完全靠极值点包络迭代,因此比固定窗口的均线族更贴合局部形态。 开 MT5 自建脚本时,重点盯住筛分停止条件:通常设定标准差阈值 0.2~0.3 之间,迭代到相邻两次 IMF 差异小于该值即停。阈值设太低会过度分解引入伪分量,设太高则残留混模,建议先用历史段跑一遍看分量数是否稳定在 6~9 个。
◍ 预测工具箱里还缺哪两块
交易者能否盈利,核心在于对一段周期后价格走向的推断质量,而不是指标堆得多花哨。要支撑这种推断,既需要紧跟基准市场特征,也需要技术分析算法,乃至把价格、波动率、宏观指数、账户权益等都当成时间序列来建模。 MQL5 社区里预测类资料不少,2008 年那篇《预测财经时间序列》算早期严谨文献,代码库也提供了指数平滑、DTW 历史形态匹配、BPNN、SOM 等可直接在 MT5 跑的工具。 本系列往现有列表里补两块:其一是经验模式分解(EMD),前文只讲过原理没拿来做预测,第一篇先啃它;其二是最小二乘支持向量机(LS-SVM),放到第二篇。外汇与贵金属杠杆高、滑点突发行情多,任何预测都只是概率倾向,开 MT5 前先想清楚爆仓距离。
「给 EMD 类加上外推预测能力」
经验模式分解把价格序列拆成一堆本征模式函数(IMF),每种都是对序列极值做样条插值得到的分量。直观上像傅立叶展开,但 IMF 不是固定频率的谐波,而是随数据自适应。原始 CEMD 类只做分解,要拿它预测就得改 decomp 方法,加一个外推参数决定向前多推几根柱线。 直接在 decomp 里把数组长度 N 加上 extrapolate,同时用局部变量 Nf 存住真实序列长度。代码里标「+」的是新增,「*」的是改写:用 MathMin(i, Nf-1) 取最后一个已知值去填外推段,均值也只按前 Nf 个点算。这样 IMF 能顺着最后已知点往后外推,但预测深度最好压在几根柱线内——离已知区越远,IMF 越失真。 这种改法存成 EMDloose.mqh 后有个坑:极值搜索 extrema 会把这些假未来点当真,样条互相平滑,预测变成自平衡振荡,不再表征原序列。外汇和贵金属波动大、跳空频繁,拿这种自平滑线当信号容易误判,只能当参考。 正经做法是把 Nf 提为对象变量,extrema 里仍用 Nf 找极值,只有真实点参与。最终 EMD.mqh 和 loose 版可并列挂进指标比效果。另外给 getIMF 加了 reverse 参数,逆序排缓冲方便指标第 0 根是最新价。下面这段是改动核心: #include <EMD.mqh> #define BUF_NUM 18 // 16 IMF maximum (including input at 0-th index) + residue + reconstruction #property indicator_separate_window #property indicator_buffers BUF_NUM #property indicator_plots BUF_NUM #include <IndArray.mqh> IndicatorArray buffers(BUF_NUM); IndicatorArrayGetter getter(buffers); class CEMD { private: int N; // Input and output data size double IMFResult[]; // Result double X[]; // X-coordinate for the TimeSeries. X[]=0,1,2,...,N-1. ... public: int N; // Input and output data size double Mean; // Mean of input data ... int decomp(double &y[]) { ... N = ArraySize(y); arrayprepare(); for(i = 0; i < N; i++) X[i] = i; Mean = 0; for(i = 0; i < N; i++) Mean += (y[i] - Mean) / (i + 1.0); // Mean (average) of input data for(i = 0; i < N; i++) { a = y[i] - Mean; Imf[i] = a; IMFResult[i] = a; } // The loop of decomposition ... extrema(...); ... ... } private: int arrayprepare(void) { if(ArrayResize(IMFResult, N) != N) return (-1); ... } }; int decomp(const double &y[], const int extrapolate = 0) // * { ... N = ArraySize(y); int Nf = N; // + preserve actual number of input data points N += extrapolate; // + arrayprepare(); for(i = 0; i < N; i++) X[i] = i; Mean = 0; for(i = 0; i < Nf; i++) // * was N Mean += (y[i] - Mean) / (i + 1.0); for(i = 0; i < N; i++) { a = y[MathMin(i, Nf - 1)] - Mean; // * was y[i] Imf[i] = a; IMFResult[i] = a; } // The loop of decomposition ... extrema(...); ... for(i = 0; i < N; i++) { IMFResult[i + N * nIMF] = IMFResult[i]; IMFResult[i] = y[MathMin(i, Nf - 1)] - Mean; // * was y[i] } } class CEMD { private: int N; // Input and output data size int Nf; // + public: int decomp(const double &y[], const int extrapolate = 0) { ... N = ArraySize(y); Nf = N; // + preserve actual number of input data points in the object N += extrapolate; // + ... } };
class="macro">#include <EMD.mqh> class="macro">#define BUF_NUM class="num">18 class=class="str">"cmt">// class="num">16 IMF maximum(including class="kw">input at class="num">0-th index) + residue + reconstruction class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers BUF_NUM class="macro">#class="kw">property indicator_plots BUF_NUM class="macro">#include <IndArray.mqh> IndicatorArray buffers(BUF_NUM); IndicatorArrayGetter getter(buffers); class CEMD { class="kw">private: class="type">int N; class=class="str">"cmt">// Input and output data size class="type">class="kw">double IMFResult[]; class=class="str">"cmt">// Result class="type">class="kw">double X[]; class=class="str">"cmt">// X-coordinate for the TimeSeries. X[]=class="num">0,class="num">1,class="num">2,...,N-class="num">1. ... class="kw">public: class="type">int N; class=class="str">"cmt">// Input and output data size class="type">class="kw">double Mean; class=class="str">"cmt">// Mean of class="kw">input data ... class="type">int decomp(class="type">class="kw">double &y[]) { ... N = ArraySize(y); arrayprepare(); for(i = class="num">0; i < N; i++) X[i] = i; Mean = class="num">0; for(i = class="num">0; i < N; i++) Mean += (y[i] - Mean) / (i + class="num">1.0); class=class="str">"cmt">// Mean(average) of class="kw">input data for(i = class="num">0; i < N; i++) { a = y[i] - Mean; Imf[i] = a; IMFResult[i] = a; } class=class="str">"cmt">// The loop of decomposition ... extrema(...); ... ... } class="kw">private: class="type">int arrayprepare(class="type">void) { if(ArrayResize(IMFResult, N) != N) class="kw">return (-class="num">1); ... } }; class="type">int decomp(class="kw">const class="type">class="kw">double &y[], class="kw">const class="type">int extrapolate = class="num">0) class=class="str">"cmt">// * { ... N = ArraySize(y); class="type">int Nf = N; class=class="str">"cmt">// + preserve actual number of class="kw">input data points N += extrapolate; class=class="str">"cmt">// + arrayprepare(); for(i = class="num">0; i < N; i++) X[i] = i; Mean = class="num">0; for(i = class="num">0; i < Nf; i++) class=class="str">"cmt">// * was N Mean += (y[i] - Mean) / (i + class="num">1.0); for(i = class="num">0; i < N; i++) { a = y[MathMin(i, Nf - class="num">1)] - Mean; class=class="str">"cmt">// * was y[i] Imf[i] = a; IMFResult[i] = a; } class=class="str">"cmt">// The loop of decomposition ... extrema(...); ... for(i = class="num">0; i < N; i++) { IMFResult[i + N * nIMF] = IMFResult[i]; IMFResult[i] = y[MathMin(i, Nf - class="num">1)] - Mean; class=class="str">"cmt">// * was y[i] } } class CEMD { class="kw">private: class="type">int N; class=class="str">"cmt">// Input and output data size class="type">int Nf; class=class="str">"cmt">// + class="kw">public: class="type">int decomp(class="kw">const class="type">class="kw">double &y[], class="kw">const class="type">int extrapolate = class="num">0) { ... N = ArraySize(y); Nf = N; class=class="str">"cmt">// + preserve actual number of class="kw">input data points in the object N += extrapolate; class=class="str">"cmt">// + ... } };
IMF 提取时的数组方向控制权
CEMD 类的 getIMF 方法在经验模态分解里负责把某一阶本征模态函数(IMF)写回传入的引用数组 x[]。nn 指定要取的阶数,reverse 参数默认 false,决定返回序列的时间方向。 当 reverse 为 true 时,方法末尾会调用 ArrayReverse(x) 把数组就地反转。这意味着你拿到的 IMF 序列可以是「从老到新」也可以是「从新到老」,取决于后续是要做卷积对齐还是直接画图。 实盘写指标时若忽略这一行,常出现 IMF 曲线与价格轴左右颠倒——尤其在做多阶重构时,错位会导致残差符号整体反向。开 MT5 把 reverse 切到 true / false 各跑一次,对比 SubWindow 里的相位即可验证。
class="type">void CEMD::getIMF(class="type">class="kw">double &x[], class="kw">const class="type">int nn, class="kw">const class="type">bool reverse = class="kw">false) class="kw">const { ... if(reverse) ArrayReverse(x); class=class="str">"cmt">// + }