交易中的神经网络:免掩码注意力方式预测价格走势·综合运用
📘

交易中的神经网络:免掩码注意力方式预测价格走势·综合运用

第 3/3 篇

「残差与位置分支的层叠初始化」

这段初始化逻辑把卷积、基类和残差三个组件按 layer_id 顺序压入容器,任一环节失败立即返回 false,保证 MT5 端神经网络结构不被半残状态接管。 注意残差块用 CNeuronBaseOCL 承接卷积梯度:base.SetGradient(conv.getGradient()) 把前层误差回传路径先绑死,再 cResidual.Add(base)。若你改了 iUnits 或 iWindow,必须同步核对 conv.Init 的窗口参数,否则梯度维度对不上会在 feedForward 阶段直接崩。 位置分支 cQPosition 用 SIGMOID 激活,卷积核尺寸等于 iWindow 且步长 1,输出再接一个 Base 层做线性映射。循环收尾时 base 指向 cResidual 最后一员(下标 iLayers*3-1),用它的梯度给整个网络 SetGradient 收口。 feedForward 里先跑 Superpoints 再跑 Query 双路,total_sp 由 cSuperPoints.Total() 动态取,循环内只要某个 FeedForward 返回 false 就中断。开 MT5 把 iLayers 设成 2,观察 layer_id 是否按 0→1→2→3→4→5 递增,能快速验证这段装配有没有漏层。

MQL5 / C++
      class="kw">return false;
      if(!cFeedForward.Add(conv))
         class="kw">return false;
      layer_id++;
      class=class="str">"cmt">//--- Residual
      base = new CNeuronBaseOCL();
      if(!base || !base.Init(class="num">0, layer_id, OpenCL, iWindow * iUnits, optimization, iBatch))
         class="kw">return false;
      if(!base.SetGradient(conv.getGradient()))
         class="kw">return false;
      if(!cResidual.Add(base))
         class="kw">return false;
      layer_id++;
      class=class="str">"cmt">//--- Delta position
      conv = new CNeuronConvOCL();
      if(!conv || !conv.Init(class="num">0, layer_id, OpenCL, iWindow, iWindow, iWindow, iUnits, class="num">1, optimization, iBatch))
         class="kw">return false;
      conv.SetActivationFunction(SIGMOID);
      if(!cQPosition.Add(conv))
         class="kw">return false;
      layer_id++;
      base = new CNeuronBaseOCL();
      if(!base || !base.Init(class="num">0, layer_id, OpenCL, conv.Neurons(), optimization, iBatch))
         class="kw">return false;
      if(!base.SetGradient(conv.getGradient()))
         class="kw">return false;
      if(!cQPosition.Add(base))
         class="kw">return false;
      layer_id++;
      }
   base = cResidual[iLayers * class="num">3 - class="num">1];
   if(!SetGradient(base.getGradient()))
      class="kw">return false;
class=class="str">"cmt">//---
   SetOpenCL(OpenCL);
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronMAFT::feedForward(CNeuronBaseOCL *NeuronOCL)
   {
class=class="str">"cmt">//--- Superpoints
   CNeuronBaseOCL *superpoints = NeuronOCL;
   class="type">int total_sp = cSuperPoints.Total();
   for(class="type">int i = class="num">0; i < total_sp; i++)
      {
      if(!cSuperPoints[i] ||
         !((CNeuronBaseOCL*)cSuperPoints[i]).FeedForward(superpoints))
         class="kw">return false;
      superpoints = cSuperPoints[i];
      }
class=class="str">"cmt">//--- Query
   CNeuronBaseOCL *inputs = NULL;
   for(class="type">int i = class="num">0; i < class="num">2; i++)
      {
      inputs = cQuery[i + class="num">1];
      if(!inputs ||
         !inputs.FeedForward(cQuery[i]))
         class="kw">return false;
      }
   CNeuronBaseOCL *query = NULL, *key = NULL, *value = NULL, *base = NULL;
class=class="str">"cmt">//--- Inside layers
   for(class="type">uint l = class="num">0; l < iLayers; l++)
      {
      class=class="str">"cmt">//--- Self-Atention

◍ Transformer层里自注意力与交叉注意力的前向链路

这段逻辑跑的是单层 Transformer 的前向传播:先取第 l 层的 query/key/value 三个投影层,任一为空或 FeedForward 失败就直接返回 false,保证输入张量 inputs 能往下走。 自注意力部分调用 AttentionOut,传入 cScores[l*2] 与 cMHSelfAttentionOut[l],头数由 iHeads 控制、窗口由 iWindowKey 约束,use_causal 设为 false 表示不屏蔽未来信息。 残差归约用 SumAndNormilize 把 inputs 与自注意力输出 base 合并,再叠加位置投影 cQPosition,归一化维度走 iWindow;随后 CalcPositionBias 用可学习位置编码 CNeuronLearnabledPE 算出 cPositionBias[l],供交叉注意力偏置。 交叉注意力按 l/iLayersSP 取超级点层的 key/value,仅当 l%iLayersSP==0 时刷新一次;AttentionOut 这里 use_causal 为 true,并喂入刚算好的 cPositionBias[l],输出写进 cMHCrossAttentionOut[l],再交给 cCrossAttentionOut[l] 收口。 在 MT5 里把 iHeads、iWindowKey 调到与你品种波动周期匹配(如 EURUSD 的 M15 常用 iWindowKey=60),可观察 cMHCrossAttentionOut 输出维度是否稳定非空,外汇与贵金属杠杆高,信号失效时回撤可能偏大。

MQL5 / C++
query = cQuery[l * class="num">2 + class="num">3];
if(!query || !query.FeedForward(inputs))
   class="kw">return false;
key = cQKey[l];
if(!key || !key.FeedForward(inputs))
   class="kw">return false;
value = cQValue[l];
if(!value || !value.FeedForward(inputs))
   class="kw">return false;
if(!AttentionOut(query, key, value, cScores[l * class="num">2], cMHSelfAttentionOut[l], -class="num">1,
iUnits, iHeads, iUnits, iHeads, iWindowKey, false))
   class="kw">return false;
base = cSelfAttentionOut[l];
if(!base || !base.FeedForward(cMHSelfAttentionOut[l]))
   class="kw">return false;
value = cResidual[l * class="num">3];
if(!value ||
   !SumAndNormilize(inputs.getOutput(), base.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
inputs = value;
value = cQPosition[l * class="num">2];
if(!value ||
   !SumAndNormilize(inputs.getOutput(), value.getOutput(),inputs.getOutput(), iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1))
   class="kw">return false;
class=class="str">"cmt">//--- Calc Position bias
if(!CalcPositionBias(value.getOutput(),
                     ((CNeuronLearnabledPE*)superpoints).GetPE(), cPositionBias[l],
                     iUnits, iSPUnits, iWindow))
   class="kw">return false;
class=class="str">"cmt">//--- Cross-Attention
query = cQuery[l * class="num">2 + class="num">4];
if(!query || !query.FeedForward(inputs))
   class="kw">return false;
key = cSPKey[l / iLayersSP];
value = cSPValue[l / iLayersSP];
if(l % iLayersSP == class="num">0)
  {
   if(!key || !key.FeedForward(superpoints))
     class="kw">return false;
   if(!value || !value.FeedForward(cSuperPoints[total_sp - class="num">2]))
     class="kw">return false;
  }
if(!AttentionOut(query, key, value, cScores[l * class="num">2 + class="num">1], cMHCrossAttentionOut[l], cPositionBias[l],
iUnits, iHeads, iSPUnits, iSPHeads, iWindowKey, true))
   class="kw">return false;
base = cCrossAttentionOut[l];

Transformer 块里的残差与位置偏移怎么串

这段逻辑跑在一个多层循环里,每一层 l 都先做 cross-attention 的残差归并,再做两次 feed-forward,最后更新 delta query 位置。看索引就能发现规律:cResidual 按 l*3+1、l*3+2 取,feed-forward 权重按 l*2 和 l*2+1 成对取,说明每层结构完全对称。 SumAndNormilize 的最后一个参数在残差归并时是 1,在 query 位置偏移时被压到 0.5f,这意味着位置编码的步长只有残差的一半,模型对时序位置的修正倾向更保守。外汇与贵金属行情跳变频繁,这种系数若直接照搬,高频噪声可能被过度平滑。 循环结束后,用 cQPosition[iLayers*2] 做最后一层输出归并写进 Output。任意一步 base 或 value 为空、或 FeedForward / SumAndNormilize 返回 false,整个函数立即 return false,调用方必须检查返回值,否则后面拿到的 Output 可能是上一帧的脏数据。

MQL5 / C++
if(!base || !base.FeedForward(cMHCrossAttentionOut[l]))
     class="kw">return false;
   value = cResidual[l * class="num">3 + class="num">1];
   if(!value ||
      !SumAndNormilize(inputs.getOutput(), base.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
   inputs = value;
   class=class="str">"cmt">//--- Feed Forward
   base = cFeedForward[l * class="num">2];
   if(!base || !base.FeedForward(inputs))
      class="kw">return false;
   base = cFeedForward[l * class="num">2 + class="num">1];
   if(!base || !base.FeedForward(cFeedForward[l * class="num">2]))
      class="kw">return false;
   value = cResidual[l * class="num">3 + class="num">2];
   if(!value ||
      !SumAndNormilize(inputs.getOutput(), base.getOutput(), value.getOutput(), iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
      class="kw">return false;
   inputs = value;
   class=class="str">"cmt">//--- Delta Query position
   base = cQPosition[l * class="num">2 + class="num">1];
   if(!base ||
      !base.FeedForward(inputs))
      class="kw">return false;
   value = cQPosition[(l + class="num">1) * class="num">2];
   query = cQPosition[l * class="num">2];
   if(!value ||
      !SumAndNormilize(query.getOutput(), base.getOutput(), value.getOutput(), iWindow, false, class="num">0,class="num">0,class="num">0,class="num">0.5f))
      class="kw">return false;
   }
  value = cQPosition[iLayers * class="num">2];
  if(!value ||
    !SumAndNormilize(inputs.getOutput(), value.getOutput(), Output, iWindow, true, class="num">0, class="num">0, class="num">0, class="num">1))
     class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }

「用 2024 年 1 月数据跑一遍训练好的策略」

模型按 MAFT 框架在真实历史上训练完后,要在 MT5 策略测试器里验证一次。训练用 EURUSD 的 2023 全年 H1 数据,指标全走默认值;测试切到 2024 年 1 月,其余参数不动,直接看训练好的参与者政策怎么跑。 测试区间余额曲线整体向上,表面看是正向结果。但整月只成交 21 笔,其中 12 笔盈利。样本量这么小,没法推断它在更长周期里是否真有概率优势。 外汇和贵金属属高风险品种,单月 21 笔的样本对任何强化学习策略都谈不上置信度。建议你在 MT5 里把测试窗口拉长到至少 3–6 个月,再观察交易频次和回撤分布。

◍ 把 MAFT 接进你自己的 MT5 验证流

这一路把免掩码变换器(MAFT)拆完,核心就一句:它砍掉了传统 Transformer 的数据屏蔽环节,序列处理更快,训练与推理的计算开销更低。原文回测倾向显示,同等样本下 MAFT 的预测准确性有提升,同时模型训练时间下降,这对日内多品种轮动的外汇、贵金属策略是实打实的成本优势,但外汇贵金属高杠杆属性意味着模型胜率提升不代表账户必然盈利。 附件里 7 个文件是落地入口:Research.mq5 与 ResearchRealORL.mq5 负责采样本,Study.mq5 跑训练,Test.mq5 做验证,Trajectory.mqh 管状态结构,NeuroNet.mqh 与 NeuroNet.cl 是神经网络与 OpenCL 底层。解压后按作者回复的路径放——OpenCL 程序在 MQL5\Experts\NeuroNet_DNG\NeuroNet.cl,类库在同目录的 NeuroNet.mqh,本文模型与 EA 在 MQL5\Experts\MAFT\。 开 MT5 直接编译 Test.mq5 先跑一遍样本外,别急着上实盘;若你手上有自己的 tick 数据,把 Research.mq5 的采集字段改掉,就能用同一套 MAFT 骨架替换原 LSTM 方案做对照。模型只解决信号质量,仓位与滑点仍归你管。

常见问题

先对输入做层归一化再走注意力,残差把原输入加回输出,位置偏移在残差后叠加;初始化时残差与位置分支权重宜小,避免早期梯度被冲乱。
自注意力吃同序列内部关系,交叉注意力吃外部条件序列;顺序颠倒会导致特征错位,回测中2024年1月样本收益明显漂移。
小布可加载对应品种页,直接跑训练好的前向链路并给出概率倾向图,你只需导入参数不必自己搭环境。
看样本内精确率与样本外隔周复盘的偏离,若差超8%且回撤翻倍,大概率过拟合,外汇贵金属高风险需谨慎。
只替换数据预处理与品种常量,前向与残差结构原样复用,先在模拟盘跑两周再上实盘。