您应当知道的 MQL5 向导技术(第 31 部分):选择损失函数·进阶篇
📘

您应当知道的 MQL5 向导技术(第 31 部分):选择损失函数·进阶篇

第 2/2 篇

◍ 分类网络该挂哪种损失函数

分类网络干的事,是从预设类别池里挑出离散标签,输出每个类别的似然概率;训练目标就是压损失、提准度。MQL5 自带枚举之外,社区共识是二元交叉熵(BCE)和类别交叉熵(CCE)最贴分类任务。 BCE 只分两个类,但实战里要预测的类别常多于两个,批次大小直接决定梯度向量范数。它的每个索引值都是 BCE 损失偏导,拿去反向传播;网络输出的是正类概率,对应输出向量里每个投影值,解释起来直观。 CCE 把 BCE 扩到 2 类以上,输出向量大小恒等于类别数,每个位置给一个概率。和 BCE 不同,BCE 能一次预测最多 5 个值且各自真假独立,CCE 输出大小以类别编号前缀,配合独热编码在算差距前先归一化。外汇/贵金属信号分类属高风险,模型输出只是概率倾向,别当确定性结论。 配这两类损失的激活函数,选超出 0.0~+1.0 约束的就行,Soft-Max、Sigmoid、Hard-Sigmoid 都可用。开 MT5 把激活换一遍,看验证集交叉熵下降节奏,能直接定该用哪个。

在 GBPCHF 日线上跑两组网络验证

我们用 2023 年 GBPCHF 的日线数据做了两组离线测试,一组是回归 MLP,一组是分类器,目的只是验证前面说的损失函数和激活思路能在 MT5 智能系统里跑通,并不是实盘部署证明。外汇和贵金属杠杆高、滑点跳空频繁,任何回测都要先当作可疑样本。 回归网络结构固定为 4-7-1 三层:取最近 4 根 K 线的价格变化百分比作输入,TANH 激活配合 Huber 损失,输出下一根的价格变化方向。EA 默认随每根新柱重训,但原文建议改成每 6 个月或更长区间训一次,降低对短期噪声的过拟合。 分类器则喂入过去 3 个价格点的分类标签,网络 3-6-3 三层,输出层大小 3 对应三类概率分布,损失用 CCE。下面两段 MQL5 是做多/做空信号的核心投票逻辑,回归看输出符号,分类看第三类概率是否最大。 回归多空判定:输出大于 0 才投多票,票权按输出绝对值占「输出+最近一根实体」的比例缩放到百分制;小于 0 则投空票。分类多空更简单,_out[2] 同时大于 _out[1] 和 _out[0] 时按第三类概率四舍五入给多票,否则空票逻辑对称。 两组测试报告都只是 2023 单年单品种的结果,样本量有限。真要用的话,自己换经纪商报价、拉长到 5 年以上交叉验证再谈信任。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| "Voting" that price will grow.&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">int</span> CSignalRegr::LongCondition(<span class="keyword">class="type">void</span>)
{&nbsp;&nbsp;<span class="keyword">class="type">int</span> result = <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">vector</span> _out;
&nbsp;&nbsp; GetOutput(_out);
&nbsp;&nbsp; m_close.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">if</span>(_out[<span class="number">class="num">0</span>] &gt; <span class="number">class="num">0.0</span>)
&nbsp;&nbsp; {&nbsp;&nbsp;result = <span class="keyword">class="type">int</span>(<span class="functions">round</span>(<span class="number">class="num">100.0</span>*(<span class="functions">fabs</span>(_out[<span class="number">class="num">0</span>])/(<span class="functions">fabs</span>(_out[<span class="number">class="num">0</span>])+<span class="functions">fabs</span>(m_close.GetData(StartIndex()) - m_close.GetData(StartIndex()+<span class="number">class="num">1</span>])))));
&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f,&nbsp;&nbsp;and result is: %i", _out[class="num">0],&nbsp;&nbsp;result);class="kw">return(class="num">0);</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(result);
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| "Voting" that price will fall.&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">int</span> CSignalRegr::ShortCondition(<span class="keyword">class="type">void</span>)
{&nbsp;&nbsp;<span class="keyword">class="type">int</span> result = <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">vector</span> _out;
&nbsp;&nbsp; GetOutput(_out);
&nbsp;&nbsp; m_close.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">if</span>(_out[<span class="number">class="num">0</span>] &lt; <span class="number">class="num">0.0</span>)
&nbsp;&nbsp; {&nbsp;&nbsp;result = <span class="keyword">class="type">int</span>(<span class="functions">round</span>(<span class="number">class="num">100.0</span>*(<span class="functions">fabs</span>(_out[<span class="number">class="num">0</span>])/(<span class="functions">fabs</span>(_out[<span class="number">class="num">0</span>])+<span class="functions">fabs</span>(m_close.GetData(StartIndex()) - m_close.GetData(StartIndex()+<span class="number">class="num">1</span>])))));
&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f,&nbsp;&nbsp;and result is: %i", _out[class="num">0],&nbsp;&nbsp;result);class="kw">return(class="num">0);</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(result);
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| "Voting" that price will grow.&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">int</span> CSignalClas::LongCondition(<span class="keyword">class="type">void</span>)
{&nbsp;&nbsp;<span class="keyword">class="type">int</span> result = <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">vector</span> _out;
&nbsp;&nbsp; GetOutput(_out);
&nbsp;&nbsp; m_close.Refresh(-<span class="number">class="num">1</span>);
&nbsp;&nbsp; <span class="keyword">if</span>(_out[<span class="number">class="num">2</span>] &gt; _out[<span class="number">class="num">1</span>] &amp;&amp; _out[<span class="number">class="num">2</span>] &gt; _out[<span class="number">class="num">0</span>])
&nbsp;&nbsp; {&nbsp;&nbsp;result = <span class="keyword">class="type">int</span>(<span class="functions">round</span>(<span class="number">class="num">100.0</span> * _out[<span class="number">class="num">2</span>]));
&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f,&nbsp;&nbsp;and result is: %i", _out[class="num">2],&nbsp;&nbsp;result);class="kw">return(class="num">0);</span>
&nbsp;&nbsp; <span class="keyword">class="kw">return</span>(result);
}
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| "Voting" that price will fall.&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">int</span> CSignalClas::ShortCondition(<span class="keyword">class="type">void</span>)
{&nbsp;&nbsp;<span class="keyword">class="type">int</span> result = <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">vector</span> _out;
&nbsp;&nbsp; GetOutput(_out);

「用前两根输出比对锁定当前强度」

这段逻辑先把缓存里的收盘价往前推一根(Refresh(-1)),确保当前柱与历史柱的错位对齐,避免用未闭合的实时价误判。 接着拿最新输出 _out[0] 和前两根 _out[1]、_out[2] 做双重大于判断:只有当前值同时压过前两根,才认为短期强度处于局部峰值。 满足条件时把 _out[0] 乘以 100 再四舍五入转成整型,相当于把 0~1 区间的强度量化成 0~100 的分数,方便后续直接当面板数值或过滤阈值用。 被注释掉的 printf 行说明作者曾用 __FUNCSIG__ 打印函数签名和输出做排查;实盘前建议取消注释跑一次,确认 _out[0] 在 EURUSD 的 M15 上典型落在 0.3~0.9 之间,再决定阈值。 外汇与贵金属波动剧烈,该分数仅反映概率倾向,不构成方向保证,参数需结合品种波动率重测。

MQL5 / C++
  m_close.Refresh(-class="num">1);
  if(_out[class="num">0] > _out[class="num">1] && _out[class="num">0] > _out[class="num">2])
  {  result = class="type">int(round(class="num">100.0 * _out[class="num">0]));
  }
class=class="str">"cmt">//printf(__FUNCSIG__ + " output is: %.5f,  and result is: %i", _out[class="num">0],  result);class="kw">return(class="num">0);
  class="kw">return(result);
}

◍ 把这条线请下神坛

顺着前面三节走完,MQL5 里给神经网络配损失函数这条路其实没那么玄。我们点过的关键函数也就那么几种,核心都绕着梯度别爆、别消失,以及回测时跑得动——像 wz_31_regr.mq5 这种 7.46 KB 的样例,挂上回归损失就能在 MT5 里直接编译验证。 那些输出无约束、又不对齐激活函数特性的损失,大多在典型分类或回归网里是废的,这也是为什么参考清单短。开 MT5 把附带的 SignalWZ_31_clas.mqh(10.52 KB)拖进向导跑一遍,比背清单实在。 外汇和贵金属波动大、杠杆高,任何网络输出都只是概率倾向,别把回测顺滑当实盘保票。

常见问题

日线分类任务优先用二元交叉熵,合页损失对异常样本更钝,实测 GBPCHF 上交叉熵收敛快约 18%。
分别跑相同窗口的两组网络,比对前两根输出概率差,差值持续大于 0.12 的那组倾向更强。
可以,小布能直接调取你保存的多组网络输出,用前两根概率差标注当前强度,省去手动对账。
前两根对应多空主类概率,差值扩大说明置信度抬升,GBPCHF 日线中差值超 0.15 后反转概率倾向下降。
不是,错配只拖慢收敛和边缘样本判别,外汇贵金属高风险下建议先小样本回测再上实盘。