预测时间序列(第 1 部分):经验分解模式(EMD)方法·进阶篇
📘

预测时间序列(第 1 部分):经验分解模式(EMD)方法·进阶篇

第 2/2 篇

「EMD 指标的参数与逐根计算骨架」

EMD 指标直接吃报价跑演示,但这种玩法不适合绝对实盘。做价格序列外推,至少得挂个新闻过滤器,把强力外因对基准线的扰动剥掉;短周期里夜盘横盘反而更干净,或者干脆上大周期、跨品种篮子来压噪声。 输入参数里,Offset 和 Length 决定分析序列的起点偏移与柱数,图表上用一条可拖动的垂直虚线示意 Offset,拖完会重算预测——但耗时不固定,跟序列长度、形态和 CPU 都有关。Forecast 是预测柱数:严格算法 EMD.mqh 别超过 5–10,简化版 EMDloose.mqh 能堆更大值。Reconstruction 控制重构时丢弃几个 IMF,建议填 2;填 0 等于只留最后价常数没意义,填 1 忽略最小振荡更平滑,填到等于 IMF 总数就只剩趋势线。 Reconstruction 越小,重构越贴初始序列、像短线 MA,但预测抖得厉害;越大越像长线 MA,平滑也稳。OnInit 里按预测深度给缓冲区设偏移,OnCalculate 用逐根柱线、开盘价计算,这是骨架关键。 EURUSD D1 上跑 Length=250、Offset=0、Forecast=10、Reconstruction=2,严格模式某些谐波反向乱跳导致刻度收缩,是预测深度不足的直观警告;简化模式分量在零附近晃。把 Forecast 拉到 100 能看长线,但严格模式末端多项式发散到无穷、信息区崩在零附近,实战里只适合估未来波动包络。建议 Forecast 取 2–4 根柱线。 拖垂直虚线改 Offset 时,指标调 ChartSetSymbolPeriod 刷图表,MT5 会清掉当前品种所有周期缓存重建——M1 若堆百万根柱可能卡几十秒。应急就走指标属性框改 Offset,或减图表柱数重启终端。

MQL5 / C++
class="kw">input class="type">int Length = class="num">300;  class=class="str">"cmt">// Length(bars, > class="num">5)
class="kw">input class="type">int Offset = class="num">0;    class=class="str">"cmt">// Offset(class="num">0..P bars)
class="kw">input class="type">int Forecast = class="num">0;  class=class="str">"cmt">// Forecast(class="num">0..N bars)
class="kw">input class="type">int Reconstruction = class="num">0; class=class="str">"cmt">// Reconstruction(class="num">0..M IMFs)
class="type">int OnInit()
{
   IndicatorSetString(INDICATOR_SHORTNAME, "EMD(" + (class="type">class="kw">string)Length + ")");
   for(class="type">int i = class="num">0; i < BUF_NUM; i++)
   {
      PlotIndexSetInteger(i, PLOT_DRAW_TYPE, DRAW_LINE);
      PlotIndexSetInteger(i, PLOT_SHIFT, Forecast);
   }
   class="kw">return INIT_SUCCEEDED;
}
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
   class="kw">const class="type">int prev_calculated,
   class="kw">const class="type">class="kw">datetime& Time[],
   class="kw">const class="type">class="kw">double& Open[],
   class="kw">const class="type">class="kw">double& High[],
   class="kw">const class="type">class="kw">double& Low[],
   class="kw">const class="type">class="kw">double& Close[],
   class="kw">const class="type">long& Tick_volume[],
   class="kw">const class="type">long& Volume[],
   class="kw">const class="type">int& Spread[])
{
   class="type">int i, ret;
   ArraySetAsSeries(Time, true);
   ArraySetAsSeries(Open, true);
   class="kw">static class="type">class="kw">datetime lastBar = class="num">0;
   class="kw">static class="type">int barCount = class="num">0;
   if(Time[class="num">0] == lastBar && barCount == rates_total && prev_calculated != class="num">0) class="kw">return rates_total;
   lastBar = Time[class="num">0];
   barCount = rates_total;
   if(rates_total < Length || ArraySize(Time) < Length) class="kw">return prev_calculated;
   if(rates_total - class="num">1 < Offset || ArraySize(Time) - class="num">1 < Offset) class="kw">return prev_calculated;

◍ EMD 分解与预测缓冲区的落地写法

这段逻辑把经验模态分解(EMD)直接接进 MT5 自定义指标的缓冲区管理里。先清空 BUF_NUM 个缓冲,再按 Length 长度把 Open 价格倒序塞进 yy 数组——倒序是因为外推预测需要时间轴反向。 CEMD 对象的 decomp 调用一旦返回负值就沿用 prev_calculated 退出,避免重绘崩坏;正常时通过 getN 拿到本征模态函数(IMF)层数 N,getMean 取均值,并把 yy 扩到 n+Forecast 尺寸容纳预测段。 绘图属性按层区分:i<=N+1 的层才显示,第 N+1 层线宽设为 2,其余为 1,全部实线;第 N 层单独改成 STYLE_DOT 点线,方便肉眼区分残差。 重构环节用 sum 数组累加 IMF:仅当 i>Reconstruction 时才把该层并入拟合曲线。若 Forecast>0,预测区前段先填 EMPTY_VALUE 隐藏;当 Reconstruction==0 或大于 N-1 时,完全拟合曲线无法外推,预测点强制置空——这是实测中会出常数的坑。 最后 buffers[0] 存总拟合、buffers[N] 存残差,返回 rates_total。开 MT5 把这段塞进 OnCalculate,调 Length / Forecast / Reconstruction 三个参数即可看分解效果,外汇与贵金属行情波动剧烈,参数过拟合概率偏高,需自行回测验证。

MQL5 / C++
for(class="type">int k = class="num">0; k < BUF_NUM; k++)
{
buffers[k].empty();
}
class="type">class="kw">double yy[];
class="type">int n = Length;
ArrayResize(yy, n, n + Forecast);
for(i = class="num">0; i < n; i++)
{
yy[i] = Open[n - i + Offset - class="num">1]; class=class="str">"cmt">// we need to reverse for extrapolation
}
CEMD emd;
ret = emd.decomp(yy, Forecast);
if(ret < class="num">0) class="kw">return prev_calculated;
class="kw">const class="type">int N = emd.getN();
class="kw">const class="type">class="kw">double mean = emd.getMean();
n += Forecast;
ArrayResize(yy, n);
for(i = class="num">0; i < BUF_NUM; i++)
{
PlotIndexSetInteger(i, PLOT_SHOW_DATA, i <= N + class="num">1);
PlotIndexSetInteger(i, PLOT_LINE_WIDTH, i == N + class="num">1 ? class="num">2 : class="num">1);
PlotIndexSetInteger(i, PLOT_LINE_STYLE, STYLE_SOLID);
}
emd.getIMF(yy, class="num">0, true);
if(Forecast > class="num">0)
{
for(i = class="num">0; i < Forecast; i++) yy[i] = EMPTY_VALUE;
}
buffers[N + class="num">1].set(Offset, yy);
class="type">class="kw">double sum[];
ArrayResize(sum, n);
ArrayInitialize(sum, class="num">0);
for(i = class="num">1; i < N; i++)
{
emd.getIMF(yy, i, true);
buffers[i].set(Offset, yy);
if(i > Reconstruction)
{
for(class="type">int j = class="num">0; j < n; j++)
{
sum[j] += yy[j];
}
}
}
PlotIndexSetInteger(N, PLOT_LINE_STYLE, STYLE_DOT);
emd.getIMF(yy, N, true);
buffers[N].set(Offset, yy);
for(class="type">int j = class="num">0; j < n; j++)
{
sum[j] += yy[j];
if(j < Forecast && (Reconstruction == class="num">0 || Reconstruction > N - class="num">1)) class=class="str">"cmt">// completely fitted curve can not be forecasted(gives a constant)
{
sum[j] = EMPTY_VALUE;
}
}
buffers[class="num">0].set(Offset, sum);
class="kw">return rates_total;
}

用 EMD 预测差值驱动 EA 开仓

TestEMD 这个 EA 的逻辑很直接:实例化 EMD 指标,在柱线开盘时用开盘价做预测,取 SignalBar 上的指标预测值与当前 0 号柱的差值当信号。差值为正做多,为负做空;SignalBar 一般设成负的 Forecast 值,也就是去看未来预测柱的位置。 EA 有四个核心输入:Length 是喂给指标的时间序列长度,Forecast 是预测柱数量,Reconstruction 是重构时丢弃的较小谐波数,SignalBar 是从缓冲区取预测的柱编号。正常交易模式下 Offset 固定为 0,不翻历史预测;另有一个非交易虚拟模式,把 SignalBar 设 0,EA 自动把 Offset 设为 Forecast,用预测值对比最后预测柱的实际价格变化算理论盈利,测试器里选自定义优化条件即可快速跑优化。 因为预测可覆盖前几根柱且同周期可同向加仓,逆向持仓能并存。比如 Forecast=3,每笔持仓拿 3 根柱,任一时刻场内有 3 笔不同方向的仓,这种结构需要对冲账户才扛得住。EA 交易部分借了 MT4Orders 库,没用魔幻数字区分订单,也没严格错误处理、滑点、止损止盈,Lot 是固定手数市价单。 文末 TestEMD.set 给了样例,加速模式优化 2018 年 EURUSD D1 得到:Length=110、Forecast=4、Reconstruction=2、SignalBar=-4。用这套在 2018 初至 2020 年 2 月复盘、2019 和 2020 初验证,系统显示有收益但仍有改进空间。外汇与贵金属属高风险品种,这类基于历史预测的结果不预示未来,实盘前务必在 MT5 用策略测试器自行验证。 更频繁的步进重优化、试探不同步长,有可能提升机器人表现。EMD 算法在大周期上识别报价动量波动并据此建模,倾向能支撑可盈利系统的构建。

「把 TSA 类塞进指标缓冲区」

原版的 TSAnalysis 类把分析结果写成外部 HTML,只能在浏览器里看,终端内无法直接读图。改写思路很直接:新建 TSAnalysisMod.mqh,保留 Calc 方法一次性算好全部特征,但去掉无用的虚方法 show,把界面控制权完全交给调用代码,让指标用缓冲区把数组画出来。 标量特征收进 TSStatMeasures 结构,从最小值、中位数到 Jarque-Bera 检验 p 值一共 22 个字段;数组类特征用 TSA_TYPE 枚举区分,例如 tsa_ACF 是自相关函数,tsa_PACF 是偏自相关。读取时可用 getTimeSeriesSorted 这类宏生成方法,或用通用方法 getResult 按枚举取任意数组,两者都返回填充的元素个数。 指标 TSA.mq5 负责可视化,输入含 Type、Length、Offset、Differencing、Smoothing、Method、Price。以 EURUSD D1 取 500 根柱线看自相关函数为例,开启一阶差值能提升序列平稳性,但高阶差值的不利面留到后续讨论。部分自相关函数在此先备好,后面换预测方法会用到。 外汇与贵金属波动受杠杆和消息面驱动,高阶统计仅描述历史分布,对后市方向只具概率意义,实操前请在 MT5 用脚本跑一遍验证。

MQL5 / C++
class="kw">struct TSStatMeasures
  {
    class="type">class="kw">double MinTS;      class=class="str">"cmt">// Minimum time series value
    class="type">class="kw">double MaxTS;      class=class="str">"cmt">// Maximum time series value
    class="type">class="kw">double Median;     class=class="str">"cmt">// Median
    class="type">class="kw">double Mean;       class=class="str">"cmt">// Mean(average)
    class="type">class="kw">double Var;        class=class="str">"cmt">// Variance
    class="type">class="kw">double uVar;       class=class="str">"cmt">// Unbiased variance
    class="type">class="kw">double StDev;      class=class="str">"cmt">// Standard deviation
    class="type">class="kw">double uStDev;     class=class="str">"cmt">// Unbiaced standard deviation
    class="type">class="kw">double Skew;       class=class="str">"cmt">// Skewness
    class="type">class="kw">double Kurt;       class=class="str">"cmt">// Kurtosis
    class="type">class="kw">double ExKurt;     class=class="str">"cmt">// Excess Kurtosis
    class="type">class="kw">double JBTest;     class=class="str">"cmt">// Jarque-Bera test
    class="type">class="kw">double JBpVal;     class=class="str">"cmt">// JB test p-value
    class="type">class="kw">double AJBTest;    class=class="str">"cmt">// Adjusted Jarque-Bera test
    class="type">class="kw">double AJBpVal;    class=class="str">"cmt">// AJB test p-values
    class="type">class="kw">double maxOut;     class=class="str">"cmt">// Sequence Plot. Border of outliers
    class="type">class="kw">double minOut;     class=class="str">"cmt">// Sequence Plot. Border of outliers
    class="type">class="kw">double UPLim;      class=class="str">"cmt">// ACF. Upper limit(class="num">5% significance level)
    class="type">class="kw">double LOLim;      class=class="str">"cmt">// ACF. Lower limit(class="num">5% significance level)
    class="type">int NLags;         class=class="str">"cmt">// Number of lags for ACF and PACF Plot
    class="type">int IP;            class=class="str">"cmt">// Autoregressive model order
  };
  enum TSA_TYPE
  {
    tsa_TimeSeries,
    tsa_TimeSeriesSorted,
    tsa_TimeSeriesCentered,
    tsa_HistogramX,
    tsa_HistogramY,
    tsa_NormalProbabilityX,
    tsa_ACF,
    tsa_ACFConfidenceBandUpper,
    tsa_ACFConfidenceBandLower,
    tsa_ACFSpectrumY,
    tsa_PACF,
    tsa_ARSpectrumY,
    tsa_Size class=class="str">"cmt">//  
  };        class=class="str">"cmt">//  ^ non-breaking space(to hide aux element tsa_Size name)
  class="type">int getARRAYNAME(class="type">class="kw">double &result[]) class="kw">const;
  class="type">int getResult(class="kw">const TSA_TYPE type, class="type">class="kw">double &result[]) class="kw">const

◍ 画得少,看得清

EMD 这套实现把经验模态分解塞进了 MT5 的指标和 EA 里,短线预测能当交易决策的附加因子,而不是单独扛旗的信号源。附件 MQL5EMD.zip 实测 45.93 KB,解压时务必保留原文件夹结构,否则用 MQL4 编辑器打开会报 'Offset' 运算符缺失,换 MQL5 编辑器即可正常编译。 俄文原帖下有人把回测起点从 2020.1.1 挪到当下重跑,曲线形态随样本窗口漂移,说明分解层数敏感,参数别一把梭。外汇和贵金属波动受事件驱动,拿 EMD 残差当滤波层就好,重仓赌延伸段属于高风险动作。 下一篇作者打算接 LS-SVM 做预测,两条路子可以叠着试:EMD 先拆趋势和本征模,再喂给支持向量机,比单模型更抗非平稳。真要验证,先把 zip 里 EA 跑一周模拟盘,别急着上实盘。

常见问题

一般设 5~8 个 IMF 分量足够捕捉主要周期,超过 10 个在分钟周期上可能拖慢刷新,先从 6 试起。
建议用最近 200 根的预测误差标准差的 1.5 倍作阈值,低于此差值视为噪声,高于才触发方向判断。
可以,小布已内置经验模态分解诊断,打开对应品种页就能直接看各 IMF 分量和预测缓冲,省去手搓代码。
只渲染残差预测线能避开 IMF 互相叠加的视觉噪声,重点看预测与现实的偏离,决策更快。
先检查分解窗口是否过旧,换近 300 根重算;若差仍扩大,多为趋势突变,EMD 滞后属正常,应降仓观望。