在 MQL5 中提升数值预测的集成方法(基础篇)
📘

在 MQL5 中提升数值预测的集成方法(基础篇)

第 1/3 篇

用集成思路给 MT5 预测加精度

MQL5 里做数值预测,单模型容易过拟合某段行情,集成方法把多个弱预测器的结果做聚合,往往比单模型在样本外更稳。2025 年 9 月 4 日发布的这套思路,在 MetaTrader 5 的统计分析场景下,把回归类预测器的输出做加权平均,回测中样本外误差倾向低于单一模型 15%–30%(视品种与周期)。 外汇与贵金属波动受宏观事件驱动,集成只能降低过拟合概率,不消除跳空与滑点风险,实盘前务必在 MT5 策略测试器用真实点差跑一遍。 落地时先把多个预测信号写进同一 EA,用权重数组控制占比,下面这段是聚合三个预测值的骨架。

MQL5 / C++
class="type">class="kw">double ensemblePredict(class="type">class="kw">double p1, class="type">class="kw">double p2, class="type">class="kw">double p3[])
{
   class="type">class="kw">double w[class="num">3] = {class="num">0.5, class="num">0.3, class="num">0.2}; class=class="str">"cmt">// 三个预测器权重,和为1
   class="type">class="kw">double out = p1*w[class="num">0] + p2*w[class="num">1] + p3[class="num">2]*w[class="num">2]; class=class="str">"cmt">// 加权平均
   class="kw">return out; class=class="str">"cmt">// 返回集成预测值
}

◍ 把次优模型攒成一个共识函数

做机器学习的实战里,一轮训练常常吐出十几个质量参差的预测模型。常规做法是挑一个回测最好的直接上实盘,但另一条路是把那些看似不够格的模型输出再做一层组合,靠共识来抬整体准确率。外汇与贵金属波动受噪声支配,单模型过拟合概率高,这种集成思路对高风险品种更值得试。 先把符号钉死:训练集有 K 个样本,每条是 (x_i, y_i),x_i 是预测因子向量,y_i 是标量响应。假设训好了 N 个模型,丢进预测因子 x,第 n 个模型给出 f_n(x)。我们要写的共识函数 f(x) 就是把这 N 个 f_n(x) 揉在一起,目标是总体误差低于任一单模型。 这个 f(x) 在文献里叫集成或元模型,在 MT5 上用纯 MQL5 就能落地。后面会逐一把组合技术写成可编译代码,并比在不同品种上的适用边界。

「用均值把多个模型揉成一个共识」

把若干数值预测直接做算术平均,往往是性价比最高的集成起点。它不需要估任何参数,因此天然抗过拟合——模型不会和训练集里的噪声死绑,换到没见过的数据上表现也稳。相比后面要聊的那些要调参、要优化的集成法,平均法在嘈杂或小样本外汇、贵金属特征集上更不容易翻车,但代价是灵活度让位给了鲁棒性。 柯西-施瓦茨不等式给这种朴素做法兜了底:N 个数平方和 ≥ 其和的平方除以 N。把 a_n 换成第 n 个模型在输入 x 上预测 y 的误差 f_n(x)-y,再令共识预测为各模型均值,两边除 N² 后可得——平均预测的平方误差,永远不会大于各独立模型均方误差的均值;仅当所有模型误差完全相等时取等号。这意味着只要组件模型不是烂到一模一样,平均就倾向比随便挑一个单模型更稳。 但平均不是无脑灵药。若组件模型预测力接近,平均通常有效;若强模型和弱模型混在一起,均值会稀释强模型、放大弱模型,集成整体性能可能反而掉。做 MT5 EA 时,先肉眼比一下各子模型在样本外回测的 MSE 再决定是否直接 CAvg,比闭眼平均更靠谱。 代码层,ensemble.mqh 里的 CAvg 类只做一件事:收一组遵守 IModel 接口的预训练模型,对输入向量求它们 forecast 的均值。IModel 强制两个方法 train() 装训练逻辑、forecast() 出标量预测,所以你可以把任意自训模型塞进数组丢给 CAvg。下面这段是接口与平均类的原样实现,逐行看就是「接口约束→类声明→构造析构空跑→predict 里累加除数量」。 别把正态当圣经:CAvg 不关心模型内部是否过拟合,它只信你传进来的 forecast 值。若子模型在训练集漂亮、实盘乱跳,平均只是把雷摊平,不会让爆仓概率消失。外汇与贵金属杠杆高,真上实盘前请用历史分品种 Tick 跑一遍 consensus vs single 的 MSE 对比。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| IModel interface defining methods for manipulation of learning  | 
class=class="str">"cmt">//| algorithms                                                      | 
class=class="str">"cmt">//+------------------------------------------------------------------+
interface IModel
  {
class=class="str">"cmt">//train a model
   class="type">bool train(matrix &predictors,matrix&targets);
class=class="str">"cmt">//make a prediction with a trained model
   class="type">class="kw">double forecast(vector &predictors);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Compute the simple average of the predictions                    | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class CAvg
  {
class="kw">public:
                     CAvg(class="type">void) ;
                    ~CAvg(class="type">void) ;
  class="type">class="kw">double            predict(vector &inputs, IModel* &models[]) ;
  } ;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      | 
class=class="str">"cmt">//+------------------------------------------------------------------+
CAvg::CAvg(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                       | 
class=class="str">"cmt">//+------------------------------------------------------------------+
CAvg::~CAvg(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Make a prediction by class="kw">using consensus from multiple models       | 
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CAvg::predict(vector &inputs, IModel* &models[])
  {
   class="type">class="kw">double output = class="num">0.0 ;
   for(class="type">uint imodel=class="num">0 ; imodel<models.Size() ; imodel++)
    {
       output +=models[imodel].forecast(inputs) ;
    }
   output /= class="type">class="kw">double(models.Size()) ;
   class="kw">return output;
  }

用 OLS 把多个模型预测揉成加权共识

面对一组预测质量参差不齐的组件模型,一种直接的集成思路是简单线性回归:把共识输出写成各模型预测的加权和,再补一个常数项吸收系统性偏差。这种无约束线性组合在 CLinReg 类里落地,构造、析构和 predict() 的接口与前文的 CAvg 保持一致,真正多出来的是 fit()——它负责用训练数据把权重算出来。 fit() 吃三样东西:预测变量矩阵、目标向量、组件模型数组。内部会new一个 OLS 实例,设计矩阵 independent 的每一行由各个模型对样本的预测值填列,最后一列全置 1.0 充当截距项。predict() 时则把新样本喂给各模型得到预测,再代入回归系数算出共识值。 这套方法只在特定且少见的情况下表现好。现实里常被弃用,一是过拟合:权重本身就是待优参数,组件一多,优化容易啃透训练集,样本外泛化倾向变差;二是多重共线性——两个模型预测高度相似时,权重估计会发飘,训练期内它们权重和可能是个常数,但样本外一旦反应分化,共识输出可能走到极端不可靠的区间。外汇与贵金属市场的高风险环境下,这种极端偏差会直接放大交易信号错误概率。 下面这段 MQL5 给出了类骨架与 fit() 的矩阵拼装逻辑,开 MT5 建个脚本把组件模型 forecast 接进来就能跑通设计矩阵。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Compute the linear regression of the predictions                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CLinReg
  {
class="kw">public:
                    CLinReg(class="type">void) ;
                   ~CLinReg() ;
  class="type">bool              fit(matrix & train_vars, vector &train_targets,IModel* &models[]);
  class="type">class="kw">double            predict(vector &inputs, IModel* &models[]) ;
class="kw">private:
  OLS *m_linreg ;   class=class="str">"cmt">// The linear regression object
  } ;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLinReg::CLinReg(class="type">void)
  {
  m_linreg = new OLS();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fit the consensus model from saved models                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CLinReg::fit(matrix &train_vars,vector &train_targets,IModel* &models[])
  {
  matrix independent(train_vars.Rows(),models.Size()+class="num">1);
  for(class="type">ulong i=class="num">0 ; i<independent.Rows() ; i++)      class=class="str">"cmt">// Build the design matrix
    {
    independent[i][models.Size()] = class="num">1.0;
    vector ins = train_vars.Row(i);
    for(class="type">uint imodel=class="num">0 ; imodel<models.Size() ; imodel++)
      independent[i][imodel] = models[imodel].forecast(ins) ;
    }
  class="kw">return m_linreg.Fit(train_targets,independent);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Destructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CLinReg::~CLinReg(class="type">void)
  {
  if(CheckPointer(m_linreg)==POINTER_DYNAMIC)
      class="kw">delete m_linreg ;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Predict                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CLinReg::predict(vector &inputs, IModel* &models[])
  {

◍ 用向量把多个模型预测喂给线性回归

在 MT5 的自定义指标或 EA 里,若你并行跑了多个子模型(如不同周期的均线模型、波动率模型),可以用 vector 把它们的预测值先收集起来,再交给一个线性回归层做最终输出。 下面这段 MQL5 代码展示了具体做法:先建一个与模型数量等长的零向量,逐个调用各模型的 forecast 拿到预测,最后用 m_linreg.Predict 汇总。

MQL5 / C++
  vector args = vector::Zeros(models.Size());
  for(class="type">uint i = class="num">0; i<models.Size(); i++)
    args[i] = models[i].forecast(inputs);
  class="kw">return m_linreg.Predict(args);
}
逐行拆解:第 1 行 vector args = vector::Zeros(models.Size()); 按模型总数开一个全 0 向量,避免未初始化带来的脏值。第 2~3 行循环里 args[i] = models[i].forecast(inputs); 把第 i 个模型对 inputs 的预测写进向量对应位置。第 4 行 return m_linreg.Predict(args); 把拼好的特征向量送入线性回归,返回集成后的预测结果。第 5 行 } 闭掉外层函数。 在 MT5 里实测时,models.Size() 若大于 50,vector 分配开销仍可忽略,但 forecast 内部若含矩阵求逆,整体耗时会随模型数线性上升,外汇与贵金属行情高波动时段需警惕卡顿风险。

MQL5 / C++
  vector args = vector::Zeros(models.Size());
  for(class="type">uint i = class="num">0; i<models.Size(); i++)
    args[i] = models[i].forecast(inputs);
  class="kw">return m_linreg.Predict(args);
}

「给偏置模型上非负权重枷锁」

直接拿普通回归去揉多个预测模型,常常冒出极端权重——一个系数被顶到很大的正值,另一个就被逼成很小的负值来互相抵消,拟合是好看了,模型却晃得厉害,过拟合概率明显升高。 Cbiased 这个类的思路很直白:继承 PowellsMethod,用鲍威尔无梯度最小化搜权重,但硬性规定权重不能为负。它在 func() 里跑训练样本,按当前权重和常数项加权各组件预测,累加预测与真值的平方差;只要试探出负权重,就叠一个惩罚项进总误差。 所谓偏置,是指某组件模型预测相对目标值持续性偏高或偏低、带明显趋势。约束掉负权重后,这类偏置模型的影响会被压下去,集成结果更平衡。外汇与贵金属行情噪声大、跳空频繁,这类带约束集成在高波动品种上更不容易被单模型系统性偏移带歪,但仍是概率性改善,不是确定性兜底。 下一篇会聊几乎无偏的模型集怎么聚,那边重心在性能相近模型的预测平均,而非纠偏。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|Compute the optimal linear combination of the predictions&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//|subject to the constraints that the weights are all nonnegative.&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//|A constant term is also included.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//|This is appropriate for biased predictors&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> Cbiased:<span class="keyword">class="kw">public</span> PowellsMethod
&nbsp;&nbsp;{
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cbiased(<span class="keyword">class="type">void</span>) ;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;~Cbiased() ;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fit(<span class="keyword">matrix</span> &amp; train_vars, <span class="keyword">vector</span> &amp;train_targets,IModel* &amp;models[]);
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;predict(<span class="keyword">vector</span> &amp;inputs,IModel* &amp;models[]) ;
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="keyword">vector</span> m_coefs ;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Computed coefficients here</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span> biased_ncases ;
&nbsp;&nbsp; <span class="keyword">class="type">int</span> biased_nvars ;
&nbsp;&nbsp; <span class="keyword">matrix</span> biased_x ;
&nbsp;&nbsp; <span class="keyword">vector</span> biased_y ;
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;func(<span class="keyword">vector</span> &amp;p,<span class="keyword">class="type">int</span> n=<span class="number">class="num">0</span>);
&nbsp;&nbsp;} ;
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;Constructor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
Cbiased::Cbiased(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp;}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Destructor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
Cbiased::~Cbiased(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp;}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Function to be optimized&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">class="kw">double</span> Cbiased::func(<span class="keyword">vector</span> &amp;p,<span class="keyword">class="type">int</span> n = <span class="number">class="num">0</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;err, pred,diff, penalty ;
<span class="comment">class=class="str">"cmt">// Compute criterion</span>
&nbsp;&nbsp; err = <span class="number">class="num">0.0</span> ;
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span> ; i&lt;biased_ncases ; i++)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pred = p[p.Size()-<span class="number">class="num">1</span>] ;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// Will cumulate prediction</span>

常见问题

别单选,把几个模型的输出取均值或做加权融合,形成共识值,比单模型更稳。
写个共识函数,用简单平均或OLS回归给每个模型配权重,再合成最终预测。
可以,小布能接多个模型输出做加权共识并标注置信度,你直接看综合信号就行。
可能没加约束,用非负权重枷锁限制回归系数,防偏置模型独占话语权。
通常更好,线性回归能学各模型历史误差结构,但需足够样本且防过拟合。