数据处理的分组方法:在MQL5中实现组合算法·综合运用
📘

数据处理的分组方法:在MQL5中实现组合算法·综合运用

第 3/3 篇

◍ MULTI组合选择类的超参陷阱

multi.mqh里给出了组合选择算法的具体实现,核心是一个继承自LinearModel的MULTI类。它的对外接口和老牌的COMBI类很像,都靠fit()把模型塞进数据集,但fit()里挂的超参数明显更多,调起来更费手。 实操里最该盯住的是kBest和pAverage这两个量。kBest控制保留多少候选组合,pAverage决定用多少历史窗口做平均,乱填可能让拟合结果偏得离谱。 MULTI_test.mq5和MULTI_Multivariable_test.mq5两个脚本演示了完整拟合流程。跑完能发现:在简单数据集上,MULTI吐出的多项式和应用COMBI算法得到的那根曲线完全一致——说明底层数学没变,只是组合筛选路径不同。 下面这段是MULTI类的头文件代码,重点看它对bestCombinations的裁剪逻辑: //+------------------------------------------------------------------+

//multi.mqh
//Copyright 2024, MetaQuotes Ltd.
//[MQL5官方文档]

//+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "[MQL5官方文档] #include "linearmodel.mqh" //+------------------------------------------------------------------+

//Class implementing combinatorial selection MULTI algorithm

//+------------------------------------------------------------------+ class MULTI : public LinearModel { protected: virtual void removeExtraCombinations(void) override { CVector2d realBestCombinations; CVector n; n.push_back(bestCombinations[0][0]); realBestCombinations.push_back(n); bestCombinations = realBestCombinations; } virtual bool preparations(SplittedData &data, CVector &_bestCombinations) override { return (bestCombinations.setAt(0,_bestCombinations) && ulong(level+1) < data.xTrain.Cols()); } void generateCombinations(int n_cols,vector &out[]) override { if(level == 1) { nChooseK(n_cols,level,out); return; } for(int i = 0; i<bestCombinations[0].size(); i++) { for(int z = 0; z<n_cols; z++) { vector comb = bestCombinations[0][i].combination(); double array[]; vecToArray(comb,array); int found = ArrayBsearch(array,double(z)); if(int(array[found])!=z) { array.Push(double(z)); ArraySort(array); comb.Assign(array); 逐行拆解:

  • #include "linearmodel.mqh":把线性模型基类拉进来,MULTI复用它的训练框架。
  • removeExtraCombinations():只留bestCombinations[0][0]这一组,等于强制剪枝到单一组合。
  • preparations():把传入的_bestCombinations放到第0位,并校验level+1小于训练集列数,否则不跑。
  • generateCombinations():level为1时直接nChooseK生成;否则在已有组合上逐个追加z列,用ArrayBsearch查重,无重复才Push并排序后写回comb。

开MT5把kBest从默认改到3、pAverage降到0.5,跑一遍MULTI_test能直观看到候选集厚度变化。外汇与贵金属市场波动剧烈,此类模型仅作概率参考,实盘高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                multi.mqh |
class=class="str">"cmt">//|                     Copyright class="num">2024, MetaQuotes Ltd. |
class=class="str">"cmt">//|                                 [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#include "linearmodel.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|   Class implementing combinatorial selection MULTI algorithm          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class MULTI : class="kw">public LinearModel
  {
class="kw">protected:
   class="kw">virtual class="type">void        removeExtraCombinations(class="type">void) class="kw">override
     {
      CVector2d realBestCombinations;
      CVector n;
      n.push_back(bestCombinations[class="num">0][class="num">0]);
      realBestCombinations.push_back(n);
      bestCombinations = realBestCombinations;
     }
   class="kw">virtual class="type">bool        preparations(SplittedData &data, CVector &_bestCombinations) class="kw">override
     {
      class="kw">return (bestCombinations.setAt(class="num">0,_bestCombinations) && class="type">ulong(level+class="num">1) < data.xTrain.Cols());
     }
   class="type">void                generateCombinations(class="type">int n_cols,vector &out[])  class="kw">override
     {
      if(level == class="num">1)
        {
         nChooseK(n_cols,level,out);
         class="kw">return;
        }
      for(class="type">int i = class="num">0; i<bestCombinations[class="num">0].size(); i++)
        {
         for(class="type">int z = class="num">0; z<n_cols; z++)
           {
            vector comb = bestCombinations[class="num">0][i].combination();
            class="type">class="kw">double array[];
            vecToArray(comb,array);
            class="type">int found = ArrayBsearch(array,class="type">class="kw">double(z));
            if(class="type">int(array[found])!=z)
              {
               array.Push(class="type">class="kw">double(z));
               ArraySort(array);
               comb.Assign(array);

「组合去重与多变量拟合入口」

这段逻辑先解决 GMDH 多层迭代里的组合复用问题:用 ulong dif=1 做标记,逐行拿 comb.Compare 跟已存结果比对,阈值取 1e0。只要 dif 为 0 就 break,说明该组合已存在,不再重复入栈。 若循环跑完 dif 仍非零,才用 ArrayResize 把 out 扩容 1 个、保留 100 的储备空间,把新 comb 塞进末尾。这一步直接决定候选模型规模,外汇与贵金属序列里滞后阶一多,组合爆炸会拖垮 MT5 回测速度,属于高风险调参区。 MULTI 类的 fit 有两套重载:单序列版本要求 lags>=1,否则 Print 报错并返回 false;多变量版本额外校验 vars.Cols()>=1 且行数必须等于 targets.Size(),维度不对直接退出。默认 testsize=0.5、kBest=3、pAverage=1,意味着训练测试五五开、每层只留 3 个最优。 下面给出原文里的核心片段,可在 MT5 里直接对照跑通:

MQL5 / C++
class="type">ulong dif = class="num">1;
for(class="type">uint row = class="num">0; row<out.Size(); row++)
  {
   dif = comb.Compare(out[row],class="num">1e0);
   if(!dif)
     class="kw">break;
  }
if(dif)
  {
   ArrayResize(out,out.Size()+class="num">1,class="num">100);
   out[out.Size()-class="num">1] = comb;
  }
}
}
}
}
class="kw">public:
 MULTI(class="type">void):LinearModel()
 {
  CVector members;
  bestCombinations.push_back(members);
  modelName = "MULTI";
 }
 class="type">bool fit(vector &time_series,class="type">int lags,class="type">class="kw">double testsize=class="num">0.5,CriterionType criterion=stab,class="type">int kBest = class="num">3,class="type">int pAverage = class="num">1,class="type">class="kw">double limit = class="num">0.0)
 {
  if(lags < class="num">1)
   {
    Print(__FUNCTION__," lags must be >= class="num">1");
    class="kw">return false;
   }
  PairMVXd transformed = timeSeriesTransformation(time_series,lags);
  SplittedData splited = splitData(transformed.first,transformed.second,testsize);
  Criterion criter(criterion);
  if(validateInputData(testsize, pAverage, limit, kBest))
    class="kw">return false;
  class="kw">return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
 }
 class="type">bool fit(matrix &vars,vector &targets,class="type">class="kw">double testsize=class="num">0.5,CriterionType criterion=stab,class="type">int kBest = class="num">3,class="type">int pAverage = class="num">1,class="type">class="kw">double limit = class="num">0.0)
 {
  if(vars.Cols() < class="num">1)
   {
    Print(__FUNCTION__," columns in vars must be >= class="num">1");
    class="kw">return false;
   }
  if(vars.Rows() != targets.Size())
   {
    Print(__FUNCTION__, " vars dimensions donot correspond with targets");
    class="kw">return false;
   }
  SplittedData splited = splitData(vars,targets,testsize);

把 GMDH 多变量拟合跑成一条脚本

上面这段脚本把多变量 GMDH 封装类 MULTI 直接挂到 OnStart 里,用 12 个递增整数当样本,验证拟合与预测链路是否通。外汇与贵金属市场波动剧烈、杠杆高风险极大,这类合成序列仅用于跑通接口,实盘替换成真实报价前务必自测。 输入参数里 NumLags=2 表示用过去 2 期作滞后特征,NumPredictions=6 要求向外推 6 步,DataSplitSize=0.33 代表 33% 数据留作测试集。critType 选了 stab 准则,NumBest=3 保留每层筛选出的 3 个最优模型,Average=1 不做多次平均。 核心调用是先 multi.fit 用尾段 2 维切片作输入向量,再 multi.predict 生成 6 步外推,最后 Print 出预测值与最优多项式。若 NumPredictions 小于 1 会弹 Alert 并退出,这是脚本里唯一的硬性守卫。 开 MT5 新建脚本,把 #include <GMDH\multi.mqh> 和这段逻辑贴进去,把 tms 换成你自己的收盘价向量,就能在日志里看到预测序列与多项式表达式,据此判断该算法对当前品种的记忆长度是否够用。

MQL5 / C++
Criterion criter(criterion);
if(validateInputData(testsize, pAverage, limit, kBest))
   class="kw">return false;
class="kw">return GmdhModel::gmdhFit(splited.xTrain, splited.yTrain, criter, kBest, testsize, pAverage, limit);
}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+----------------------------------------------------------------------+
class=class="str">"cmt">//|                        MULTI_test.mq5                                |
class=class="str">"cmt">//|           Copyright class="num">2024, MetaQuotes Ltd.                            |
class=class="str">"cmt">//|                 [MQL5官方文档]                               |
class=class="str">"cmt">//+----------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class="macro">#include <GMDH\multi.mqh>
class="kw">input class="type">int NumLags = class="num">2;
class="kw">input class="type">int NumPredictions = class="num">6;
class="kw">input CriterionType critType = stab;
class="kw">input class="type">int Average  = class="num">1;
class="kw">input class="type">int NumBest  = class="num">3;
class="kw">input class="type">class="kw">double DataSplitSize = class="num">0.33;
class="kw">input class="type">class="kw">double critLimit = class="num">0;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   vector tms = {class="num">1,class="num">2,class="num">3,class="num">4,class="num">5,class="num">6,class="num">7,class="num">8,class="num">9,class="num">10,class="num">11,class="num">12};
   if(NumPredictions<class="num">1)
     {
      Alert("Invalid setting for NumPredictions, has to be larger than class="num">0");
      class="kw">return;
     }
   MULTI multi;
   if(!multi.fit(tms,NumLags,DataSplitSize,critType,NumBest,Average,critLimit))
      class="kw">return;
   vector in(class="type">ulong(NumLags),slice,tms,tms.Size()-class="type">ulong(NumLags));
   vector out = multi.predict(in,NumPredictions);
   Print(" predictions ", out);
   Print(multi.getBestPolynomial());
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|          MULTI_Mulitivariable_test.mq5                             |
class=class="str">"cmt">//|           Copyright class="num">2024, MetaQuotes Ltd.                          |

◍ 用 GMDH 多变量脚本跑通一次预测

MT5 里做多变量非线性拟合,可以借 GMDH 库的 multi.mqh 直接写脚本,不必自己造轮。下面这段脚本把 6 行已知样本丢进 MULTI 类做训练,再用 3 行未见数据验证外推效果。 参数上 DataSplitSize=0.33 表示随机留 33% 样本做测试集,NumBest=3 保留每层筛选出的前 3 个最优模型,critType=stab 选用稳定性准则。Average=1 与 critLimit=0 则保持默认不平滑、不卡阈值。 实跑日志里,输入 [1,2,4] 给出预测 7.000000000000002,[1,5,3] 给 9,[9,1,3] 给 13.00000000000001,与训练集输出量级一致,说明该组多项式在外推上未明显失真。外汇与贵金属品种上套用同类脚本,因跳空与杠杆效应,样本外预测可能偏离较大,属高风险验证行为。 [CODE] 标记内的代码逐行拆解: #property script_show_inputs 让输入参数在运行前弹窗可改; #include <GMDH\multi.mqh> 引入多变量 GMDH 实现; input CriterionType critType = stab 选定模型筛选准则为稳定性; input double DataSplitSize = 0.33 设定测试集占比; input int Average = 1 指定对后代模型不做平均; input int NumBest = 3 每层保留最优 3 个; input double critLimit = 0 不限制准则下限; matrix independent 是 6×3 的自变量样本; vector dependent 是 6 个因变量观测; multi.fit(...) 执行训练,失败直接 return; matrix unseen 是 3×3 的待预测样本; 循环里 unseen.Row(row) 取单行,multi.predict(in,1) 输出单步预测; Print(multi.getBestPolynomial()) 打印最优多项式结构。

MQL5 / C++
class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property script_show_inputs
class="macro">#include <GMDH\multi.mqh>
class="kw">input CriterionType critType = stab;
class="kw">input class="type">class="kw">double DataSplitSize = class="num">0.33;
class="kw">input class="type">int Average  = class="num">1;
class="kw">input class="type">int NumBest  = class="num">3;
class="kw">input class="type">class="kw">double critLimit = class="num">0;
class="type">void OnStart()
  {
class=class="str">"cmt">//---
   matrix independent = {{class="num">1,class="num">2,class="num">3},{class="num">3,class="num">2,class="num">1},{class="num">1,class="num">4,class="num">2},{class="num">1,class="num">1,class="num">3},{class="num">5,class="num">3,class="num">1},{class="num">3,class="num">1,class="num">9}};
   vector dependent = {class="num">6,class="num">6,class="num">7,class="num">5,class="num">9,class="num">13};
   MULTI multi;
   if(!multi.fit(independent,dependent,DataSplitSize,critType,NumBest,Average,critLimit))
      class="kw">return;
   matrix unseen = {{class="num">1,class="num">2,class="num">4},{class="num">1,class="num">5,class="num">3},{class="num">9,class="num">1,class="num">3}};
   for(class="type">ulong row = class="num">0; row<unseen.Rows(); row++)
     {
       vector in = unseen.Row(row);
       Print("inputs ", in, " prediction ", multi.predict(in,class="num">1));
     }
   Print(multi.getBestPolynomial());
  }

「多元回归在 BTCUSD 日线上的实测输出」

把三个自变量丢进日线 BTCUSD 做多元拟合,系数直接被优化器压成 1.000000e+00 的整型权重,截距项 -7.330836e-15 基本等于 0,说明模型在这组样本里倾向于把 x1、x2、x3 等权相加。 单变量对照测试 MULTI_test 吐出一串预测值 [13,14,15,16,17,18.00000000000001],最后一位浮点误差来自双精度累加,实战里可忽略。 外汇与贵金属以外的加密品种波动更极端,这类线性外推在高波动区容易失效,上 MT5 跑同款 EA 前建议先缩样本周期验证过拟合。

MQL5 / C++
PP       class="num">0    class="num">18:class="num">24:class="num">28.812   MULTI_Mulitivariable_test(BTCUSD,D1)  y= class="num">1.000000e+00*x1 + class="num">1.000000e+00*x2 + class="num">1.000000e+00*x3 - class="num">7.330836e-15
DP       class="num">0    class="num">18:class="num">25:class="num">04.454   MULTI_test(BTCUSD,D1)  predictions [class="num">13,class="num">14,class="num">15,class="num">16,class="num">17,class="num">18.00000000000001]
MH       class="num">0    class="num">18:class="num">25:class="num">04.454   MULTI_test(BTCUSD,D1)  y= class="num">1.000000e+00*x1 + class="num">2.000000e+00

拿比特币日线跑一遍GMDH预测脚本

把 GMDH 用于比特币每日收盘价的预测,核心不是算法本身,而是那支 GMDH_Price_Model 脚本怎么喂数据。脚本不挑品种,BTC 只是演示,换个图表品种把 SetSymbol 留空也能跑,时间周期由 tf 控制,默认就是日线 PERIOD_D1。 脚本开头用 iBarShift 把训练起止和样本外起止换算成 bar 偏移,任何一次返回负值就直接 Print 错误并 return,避免后面数组越界。训练集长度由 (trainstart - trainstop) + 1 算出,样本外同理,两块任一小于等于 0 就报 Invalid inputs 退出。 参数里几个值直接影响模型形态:Numlags=3 表示用前 3 根收盘做滞后特征;modelType 可在 Combi 与 MIA 间切,MIA 下 polyType 决定新变量怎么构造;critLimit=0 代表外部标准只要不退化就继续叠层。DataSplitSize=0.2 意味着 20% 输入数据拿去评估候选模型。 下载训练数据用 prices.CopyRates 带重试,最多 try=10 次,还失败就放弃。跑完用 NumTestSamplesPlot=20 把最近 20 个样本外点画成预测 vs 实际的对照,肉眼看 Combi 和 MIA 两张图的偏离程度,判断哪类外部标准更贴合 BTC 的波动结构。加密货币高波动,样本外吻合不代表后续概率不变,开 MT5 加载脚本改改日期就能验证。

MQL5 / C++
class=class="str">"cmt">//--- class="kw">input parameters
class="kw">input class="type">class="kw">string  SetSymbol="";
class="kw">input ENUM_GMDH_MODEL modelType = Combi;
class="kw">input class="type">class="kw">datetime TrainingSampleStartDate=D&class="macro">#x27;class="num">2019.12.class="num">31&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime TrainingSampleStopDate=D&class="macro">#x27;class="num">2022.12.class="num">31&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime TestSampleStartDate = D&class="macro">#x27;class="num">2023.01.class="num">01&class="macro">#x27;;
class="kw">input class="type">class="kw">datetime TestSampleStopDate = D&class="macro">#x27;class="num">2023.12.class="num">31&class="macro">#x27;;
class="kw">input ENUM_TIMEFRAMES tf=PERIOD_D1;    class=class="str">"cmt">//time frame
class="kw">input class="type">int Numlags = class="num">3;
class="kw">input CriterionType critType = stab;
class="kw">input PolynomialType polyType = linear_cov;
class="kw">input class="type">int Average  = class="num">10;
class="kw">input class="type">int NumBest  = class="num">10;
class="kw">input class="type">class="kw">double DataSplitSize = class="num">0.2;
class="kw">input class="type">class="kw">double critLimit = class="num">0;
class="kw">input class="type">ulong NumTestSamplesPlot = class="num">20;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//get relative shift of IS and OOS sets
   class="type">int trainstart,trainstop, teststart, teststop;
   trainstart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStartDate);
   trainstop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TrainingSampleStopDate);
   teststart=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TestSampleStartDate);
   teststop=iBarShift(SetSymbol!=""?SetSymbol:NULL,tf,TestSampleStopDate);
class=class="str">"cmt">//check for errors from ibarshift calls
   if(trainstart<class="num">0 || trainstop<class="num">0 || teststart<class="num">0 || teststop<class="num">0)
     {
       Print(ErrorDescription(GetLastError()));
       class="kw">return;
     }
class=class="str">"cmt">//---set the size of the sample sets
   size_observations=(trainstart - trainstop) + class="num">1 ;
   size_outsample = (teststart - teststop) + class="num">1;
class=class="str">"cmt">//---check for class="kw">input errors
   if(size_observations <= class="num">0  || size_outsample<=class="num">0)
     {
       Print("Invalid inputs ");
       class="kw">return;
     }
class=class="str">"cmt">//---download insample prices for training
   class="type">int try
        = class="num">10;
   while(!prices.CopyRates(SetSymbol,tf,COPY_RATES_CLOSE,TrainingSampleStartDate,TrainingSampleStopDate) && try)
     {
       try
        --;
       if(!try)

◍ 样本外回测与三类 GMDH 模型切换

这段逻辑紧接在样本内价格拉取之后,先以 try=10 为上限循环拷贝样本外行情:若 CopyRates 对 testprices 失败则 Sleep(5000) 重试,耗尽 10 次仍取不到就 Print 错误并 return,避免脏数据进模型。 模型分支用 switch(modelType) 处理三种 GMDH 变体——Combi、Mia、Multi,各自先 fit 训练,失败即退出,成功则 Print 最优多项式并调用 MakePredictions 生成 predictions。默认分支直接报「Invalid GMDH model type」后返回。 绘图样本量由 NumTestSamplesPlot 控制,若未设或越界则回退到 20 或 testprices.Rows()-Numlags;随后用 vector slice 从 testprices 与 predictions 切出 testsample、testpredictions、dates 三段,长度上限 100。外汇与贵金属行情跳空频繁,样本外复制失败率可能偏高,开 MT5 把 try 上限调到 20 观察重试日志更稳。

MQL5 / C++
   {
     Print("error copying to prices  ",GetLastError());
     class="kw">return;
    }
    Sleep(class="num">5000);
class=class="str">"cmt">//---download out of sample prices testing
   try
     = class="num">10;
   while(!testprices.CopyRates(SetSymbol,tf,COPY_RATES_CLOSE|COPY_RATES_TIME|COPY_RATES_VERTICAL,TestSampleStartDate,TestSampleStopDate) && try)
    {
    try
      --;
     if(!try)
      {
       Print("error copying to testprices  ",GetLastError());
       class="kw">return;
      }
     Sleep(class="num">5000);
    }
class=class="str">"cmt">//--- train and make predictions
   class="kw">switch(modelType)
    {
     case Combi:
      {
       COMBI combi;
       if(!combi.fit(prices,Numlags,DataSplitSize,critType))
        class="kw">return;
       Print("Model ", combi.getBestPolynomial());
       MakePredictions(combi,testprices.Col(class="num">0),predictions);
      }
     class="kw">break;
     case Mia:
      {
       MIA mia;
       if(!mia.fit(prices,Numlags,DataSplitSize,polyType,critType,NumBest,Average,critLimit))
        class="kw">return;
       Print("Model ", mia.getBestPolynomial());
       MakePredictions(mia,testprices.Col(class="num">0),predictions);
      }
     class="kw">break;
     case Multi:
      {
       MULTI multi;
       if(!multi.fit(prices,Numlags,DataSplitSize,critType,NumBest,Average,critLimit))
        class="kw">return;
       Print("Model ", multi.getBestPolynomial());
       MakePredictions(multi,testprices.Col(class="num">0),predictions);
      }
     class="kw">break;
     class="kw">default:
       Print("Invalid GMDH model type ");
       class="kw">return;
    }
class=class="str">"cmt">//---
class=class="str">"cmt">//---
   class="type">ulong TestSamplesPlot = (NumTestSamplesPlot>class="num">0)?NumTestSamplesPlot:class="num">20;
class=class="str">"cmt">//---
   if(NumTestSamplesPlot>=testprices.Rows())
     TestSamplesPlot = testprices.Rows()-Numlags;
class=class="str">"cmt">//---
   vector testsample(class="num">100,slice,testprices.Col(class="num">0),Numlags,Numlags+TestSamplesPlot-class="num">1);
   vector testpredictions(class="num">100,slice,predictions,class="num">0,TestSamplesPlot-class="num">1);
   vector dates(class="num">100,slice,testprices.Col(class="num">1),Numlags,Numlags+TestSamplesPlot-class="num">1);
class=class="str">"cmt">//---
class=class="str">"cmt">//Print(testpredictions.Size(), ":", testsample.Size());
class=class="str">"cmt">//---
   class="type">class="kw">double y[], y_hat[];
class=class="str">"cmt">//---

「把预测结果画上图才算跑通」

模型算完不是终点,得把测试集预测值和真实值同时落到图表上,肉眼看偏离才有意义。下面这段就是收尾时把三个向量转成数组并触发绘制的逻辑。

MQL5 / C++
 if(vecToArray(testpredictions,y_hat) && vecToArray(testsample,y) && vecToArray(dates,xaxis))
   {
     PlotPrices(y_hat,y);
   }
class=class="str">"cmt">//---
   ChartRedraw();
}
逐行看:先尝试把预测向量 testpredictions 写入 y_hat 数组,同时把测试样本 testsample 写入 y、日期序列 dates 写入 xaxis;三个转换都成功才进括号。PlotPrices(y_hat,y) 用预测和真实两条序列画图,缺失任一数组都会跳过绘制。 末尾 ChartRedraw() 强制重绘当前图表,否则 MT5 可能懒到不刷新。外汇与贵金属价格序列非线性强,预测偏离常见,开 MT5 接这段后重点看 y_hat 与 y 在跳空段的喇叭口,那一段往往暴露过拟合。

MQL5 / C++
 if(vecToArray(testpredictions,y_hat) && vecToArray(testsample,y) && vecToArray(dates,xaxis))
   {
     PlotPrices(y_hat,y);
   }
class=class="str">"cmt">//---
   ChartRedraw();
}

GMDH 工具箱的落地路径

GMDH 的组合算法给复杂系统建模提供了一套数据驱动的归纳框架,在金融时间序列里确有可挖的潜力。但原生实现碰到大型数据集就掉速,组合选择算法只缓解一部分,加可调参数能提速却把调优负担转嫁回使用者。 附件里 Mql5\include\GMDH\ 下的头文件是可直接挂载的:gmdh.mqh 定义基类,linearmodel.mqh 是 COMBI 与 MULTI 的共同底座,combi.mqh 和 multi.mqh 各自实现两类模型,mia.mqh 跑多层迭代。脚本侧 COMBI_test.mq5 仅 1.59 KB,能最快验证单变量时序建模。 外汇与贵金属行情高波动、高杠杆,用这套做预测仅作概率参考,实盘前务必在 MT5 用 GMDH_Price_Model.mqh(8.41 KB)跑一遍自己的品种,看回测漂移再决定参数粒度。

常见问题

超参过多容易过拟合历史噪声,建议先固定分组数与深度,用样本外数据验证后再微调。
在生成候选组合后立即按变量集合哈希去重,再送入拟合入口,能省掉大量重复计算。
可以,小布能按你给的品种和周期自动调度脚本、去重组合并汇总预测结果,你只看输出。
重点看样本外误差和回归系数稳定性,若系数频繁变号说明拟合脆弱,仅作概率参考。
准备干净日线数据→写单脚本调用 GMDH 拟合→输出预测值与置信区间,先跑通一次再优化。