以 MQL5 实现强化分类任务的融汇方法(基础篇)
📘

以 MQL5 实现强化分类任务的融汇方法(基础篇)

第 1/3 篇

◍ 用 MQL5 做强化分类的融汇思路

把强化学习的决策逻辑塞进分类任务,在 MT5 里并不是直接调个库就完事,而是要把行情状态、持仓动作和奖励函数写成可回测的闭环。 原文给出的基准发布于 2025 年 10 月 14 日,在 MetaTrader 5 环境下以 MQL5 实现,发布后统计阅读量为 410,评论数为 0,说明这类融汇方法仍属小众验证阶段。 实操上,建议先开 MT5 新建一个 EA 骨架,把分类标签(如突破 / 震荡 / 反转)和强化奖励(如净值回撤惩罚)分开定义,再观察样本外表现是否稳定。外汇与贵金属杠杆高,任何分类信号都只是概率倾向,需以小仓位做前向验证。

分类融汇的前提与排位输出的价值

把上篇的数值预测模型组合思路搬到分类任务上,不能直接套。数字组合技术依赖模型输出连续数值,但不少分类器只给离散类标签,且基于数字的分类器常有预测不稳定性,所以分类融汇需要专门方法。 分类融汇对组件模型有硬假设:在互斥且穷尽的类目标上训练,每个样本只归一类;若需“以上都不是”,要么单列为类,要么用数字组合配隶属阈值处理。给定输入向量,组件模型应输出 N 个值(N 为类数),可以是概率、置信度、二元独热(1.0/0.0),或 1 到 N 的整数排位。 排位输出在类数多时功用陡增。二元分类排位无额外信息,三类问题排位数值也用处有限;但类数众多且单笔预测带不确定性时,读模型的“次选”就很有价值。例如给 SVM 加上各类决策边界距离,就能从纯二元扩展出置信度洞察。 排位还能抹平不同分类模型的输出量纲差。两个看市场走势的模型,一个盯高流动性短期波动,一个看数周数月长期趋势,后者易在短周期里带噪声。把置信度转排位,短期见解就不会被宽趋势信号盖掉,融汇预测更平衡。外汇与贵金属市场波动剧烈、杠杆风险高,这类模型输出差异在实盘里可能被放大,建议先开 MT5 用历史数据验证排位转换效果。

「分类器组合不必死磕准确率」

做融汇分类器,多数人第一反应是提升分类准确率,但这未必是唯一目标。有些决策场景下,初判就可能错,这时候比准确率更有用的,是另一套成功量值:要么把真实类锁进最小高概率子集,要么把真实类顶到排位最前。 具体有两条互补路线。类集合缩减,只关心留下来的小子集里大概率含真实类,子集内部谁先谁后无所谓,紧凑和覆盖正确才是硬指标。类排序则不设固定阈值,专看真实类离顶部排位的平均距离,距离越短越好。 实际落地时,给某一条路线加权往往更稳。比如先跑向心化类集合缩减,筛出小型高概率类子集;再用次要方法给子集内类排位,取排位最高者为终判。这套两步走,在分类确定性波动大的复杂行情里,比单类预测更扛造。外汇与贵金属市场高风险,模型确定性本就时强时弱,这种双轨框架值得在 MT5 上自己搭一遍验证。

◍ 投票式融合里的平局怎么破

多数规则把多个分类模型当成评委,每个模型投一票给自己预测概率最高的那个类,得票最多的类就是集成输出。它只吃离散类选择,不碰连续概率,所以在模型能力有限、只能吐出顶层标签的系统里很实用,代码层面由 ensemble.mqh 里的 CMajority 类通过 classify() 管控。 classify() 接收预测因子向量和 IClassify 指针数组,返回 0 到(类总数-1)之间的整数。IClassify 接口规范了 train、classify、getNumInputs、getNumOutputs 几个方法,和前一篇的 IModel 操控方式接近,方便统一调度不同模型。 真正麻烦的是平票:两个或更多类票数一样时,现实里能重投,程序里不行。CMajority 在计票比较时给每个类的票数加了 0.999 * 均匀随机扰动(见代码 temp = m_output[i] + 0.999 * HQRndUniformR),让平局类被选中概率相等,避免确定性偏向。 但多数规则只采每个模型的顶层选择,丢掉了次优类里的信息;类数一多,简单投票也抓不住类间细微关系,而且所有模型被一视同仁,不管谁在历史行情里更靠谱。外汇与贵金属杠杆高、波动剧烈,直接拿这套融合信号下单风险很大,建议先在 MT5 策略测试器里跑分类准确率再谈。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Compute the winner via simple majority                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CMajority
  {
class="kw">private:
   class="type">ulong                m_outputs;
   class="type">ulong                m_inputs;
   vector               m_output;
   matrix               m_out;
class="kw">public:
                     CMajority(class="type">void);
                    ~CMajority(class="type">void);
   class="type">ulong               classify(vector &inputs, IClassify* &models[]);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| IClassify interface defining methods for manipulation of          |
class=class="str">"cmt">//|classification algorithms                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
interface IClassify
  {
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
   vector classify(vector &predictors);
class=class="str">"cmt">//get number of inputs for a model
   class="type">ulong getNumInputs(class="type">void);
class=class="str">"cmt">//get number of class outputs for a model
   class="type">ulong getNumOutputs(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  ensemble classification                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">ulong CMajority::classify(vector &inputs,IClassify *&models[])
  {
   class="type">class="kw">double best, sum, temp;
   class="type">ulong ibest;
   best =class="num">0;
   ibest = class="num">0;

   CHighQualityRandStateShell state;
   CHighQualityRand::HQRndRandomize(state.GetInnerObj());

   m_output = vector::Zeros(models[class="num">0].getNumOutputs());
   for(class="type">uint i  = class="num">0;i<models.Size(); i++)
     {
      vector classification = models[i].classify(inputs);
      m_output[classification.ArgMax()] += class="num">1.0;
     }
   sum = class="num">0.0;
   for(class="type">ulong i=class="num">0 ; i<m_output.Size() ; i++)
     {
      temp = m_output[i] + class="num">0.999 * CAlglib::HQRndUniformR(state);
      if((i == class="num">0)  || (temp > best))
        {
         best = temp ;
         ibest = i ;

用权重归一锁定最优输出索引

这段收尾逻辑把数组 m_output 里逐项累加,得到 sum 后再判断是否大于 0.0。只有和为正时才做 m_output /= sum 的归一化,避免除以零或负权导致的分布畸变。 归一化后直接 return ibest,把当前最优索引交还调用方。实盘里若 m_output 各元素代表不同信号强度,sum>0.0 的判定能保证只在存在正向共识时输出标准化权重。 复制这段代码到 MT5 的自定义指标 OnInit 尾声或 OnCalculate 末尾,把 m_output 换成你自己的信号数组,即可在日志或缓冲区观察 ibest 随 tick 的漂移。外汇与贵金属波动剧烈,归一结果仅反映历史窗口概率,不构成方向保证。

MQL5 / C++
   }
      sum += m_output[i] ;
   }
   if(sum>class="num">0.0)
      m_output/=sum;
   class="kw">return ibest;
   }

「用 Borda 计数把多模型排位揉成一类」

Borda 计数干的事,是跨所有组件模型统计每个类「排位低于多少其他类」,把这个数当得分。假设系统有 m 个模型、k 个类,得分区间被钉死:所有模型里始终垫底的类计 0 分,始终排第一的类拿满分 m(k-1)。它比简单投票细,又没把计算量搞崩,属于效率和信息利用的折中方案。 二元分类时 Borda 和多数规则等价,优势只在三类及以上才显出来。跨模型的最终计数仍可能打平,得单独处理。CBorda 类在 ensemble.mqh 里跑,不需要训练前置,直接吃输入向量算输出。 流程是先建一个累积计数的输出向量,再挨个让组件模型对输入做分类,给类建索引数组,把每个模型的输出升序排好,最后按排位把分累加进 m_output。下一段直接看 MT5 里 CBorda 的骨架,复制进本地能改的参数主要是模型数组和输出维度。 外汇与贵金属模型集成属高风险,回测亮眼不代表实盘概率占优,参数请先在策略测试器跑样本外。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;Compute the winner via Borda count&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</span> CBorda
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="keyword">class="type">ulong</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_outputs;
&nbsp;&nbsp; <span class="keyword">class="type">ulong</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_inputs;
&nbsp;&nbsp; <span class="keyword">vector</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_output;
&nbsp;&nbsp; <span class="keyword">matrix</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_out;
&nbsp;&nbsp; <span class="keyword">class="type">long</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_indices[];
<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; CBorda(<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;~CBorda(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="keyword">class="type">ulong</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; classify(<span class="keyword">vector</span>&amp; inputs, IClassify* &amp;models[]);
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| ensemble classification&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>
<span class="keyword">class="type">ulong</span> CBorda::classify(<span class="keyword">vector</span> &amp;inputs,IClassify *&amp;models[])
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> best=<span class="number">class="num">0</span>, sum, temp;
&nbsp;&nbsp; <span class="keyword">class="type">ulong</span> ibest=<span class="number">class="num">0</span>;
&nbsp;&nbsp;
&nbsp;&nbsp; CHighQualityRandStateShell state;
&nbsp;&nbsp; CHighQualityRand::HQRndRandomize(state.GetInnerObj());
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">if</span>(m_indices.Size())
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">ArrayFree</span>(m_indices);
&nbsp;&nbsp; m_output = <span class="keyword">vector</span>::Zeros(models[<span class="number">class="num">0</span>].getNumOutputs());
&nbsp;&nbsp; <span class="keyword">if</span>(<span class="functions">ArrayResize</span>(m_indices, <span class="keyword">class="type">int</span>(m_output.Size()))&lt;<span class="number">class="num">0</span>)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="keyword">__FUNCTION__</span>, <span class="class="type">class="kw">string">"&nbsp;&nbsp; "</span>, <span class="keyword">__LINE__</span>, <span class="class="type">class="kw">string">" array resize error "</span>, <span class="functions">GetLastError</span>());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">ULONG_MAX</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">uint</span> i = <span class="number">class="num">0</span>; i&lt;models.Size(); i++)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">vector</span> classification&nbsp;&nbsp;= models[i].classify(inputs);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">long</span> j = <span class="number">class="num">0</span>; j&lt;<span class="keyword">class="type">long</span>(classification.Size()); j++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_indices[j] = j;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!classification.Size())
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">Print</span>(<span class="keyword">__FUNCTION__</span>,<span class="class="type">class="kw">string">" "</span>, <span class="keyword">__LINE__</span>,<span class="class="type">class="kw">string">" empty vector "</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="macro">ULONG_MAX</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;qsortdsi(<span class="number">class="num">0</span>,classification.Size()-<span class="number">class="num">1</span>,classification,m_indices);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">ulong</span> k =<span class="number">class="num">0</span>; k&lt;classification.Size(); k++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_output[m_indices[k]] += <span class="keyword">class="type">class="kw">double</span>(k);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; sum = <span class="number">class="num">0.0</span>;

常见问题

不必。融汇思路里分类器组合看的是排位互补,单个不准但倾向不同反而能靠融合提升整体区分度。
平局用权重归一锁定最优输出索引,或改用 Borda 计数把多模型排位揉成一类,避免随机猜。
可以。小布能替你把各模型的排位输出做权重归一与 Borda 计数,直接标出最优分类,省去手算。
排位输出保留类别次序,融合时能揉进更多边际信息,比硬标签更抗噪声,适合高风险的贵金属分化。
把各分类器权重除以和做归一,取累积超 0.5 的最小索引即为最优,代码里加边界判断就不会越界。