交易中的神经网络:搭配预测编码的混合交易框架(终篇)(基础篇)
📘

交易中的神经网络:搭配预测编码的混合交易框架(终篇)(基础篇)

第 1/3 篇

「用预测编码给神经网络交易系统打底」

很多交易者以为把神经网络塞进 EA 就能自动赚钱,但 MT5 实盘里裸跑 NN 多数会过拟合到崩。Dmitriy Gizlyk 在 2025-10-20 发布的框架里,先把预测编码(Predictive Coding)用作特征前置层,再接轻量网络做方向判定,思路是把价格序列的‘预测残差’当信号源而不是原始收盘价。 预测编码的核心是用上一帧的内部模型去猜当前帧,猜错的部分(残差)才往后送。这样喂给 NN 的输入维度被压低,噪声被前置过滤,回测里训练集外的泛化失败率比直接喂 OHLC 低了一截。外汇与贵金属杠杆高,残差信号也仅代表概率倾向,不是进场保证。 想验证这套,开 MT5 新建 EA,把下面这段残差计算先跑通,再看后续网络怎么接。

StockFormer 的三分支注意力骨架

StockFormer 不是一个单一预测器,而是把三条改过的变换器分支塞进同一套注意力机制里协同工作。第一条分支专门挖不同资产之间隐藏的联动关系,第二、第三条分别盯短期与长期走势,让模型同时审计当下和未来的市场倾向。 这种集成靠一系列注意力机制完成,强化了从多头模块里学形态的能力,对噪声大、跳变快的金融序列尤其关键。在上一篇文章的实践章节,我们已经落地了多样化多头注意力(DMH-Attn)模块,它替代标准注意力后,检测时间序列中异质形态和交叉依赖的效率有明显提升。 本文接着拆模型各部件的架构,以及它们如何在统一状态空间里相互作用,并走到决策制定智能体交易策略的训练环节。外汇与贵金属市场高杠杆、高波动,这类框架只提供概率倾向,实盘前务必在 MT5 用历史数据复算。

◍ StockFormer 的预测编码三模型怎么搭

StockFormer 框架用三种基于编码器-解码器变换器的模型做预测编码:一个专门搜多模态时间序列里的依赖关系,另外两个分别预测不同横向范围的走势。三者都跑修改版 DMH-Attn 模块,但在 MT5 实现里编码器和解码器是分开建模的。 依赖项搜索模型的骨架写在 CreateRelationDescriptions 里。编码器第一层全连接直接吃下 HistoryBars*BarDescr 大小的原始张量,因为要接收整段历史深度;多模态数据分布不一,紧接着用批量归一化预处理,训练时随机掩掉最多 50% 输入,由 Dropout 层承担,再叠可学习位置编码,末端是三层多样化多头注意力。解码器架构大半雷同,只是把多头注意力换成交叉注意力,输出接逆向归一化层好和原始输入比对。 两个预测模型共用 CreatePredictionDescriptions 里的同一套结构,编码器复用依赖项搜索版(去掉 Dropout,因为训练不掩码)。解码器只收最后一根柱线的特征向量,过全连接和批归一化;单品种分析时位置编码意义不大直接省掉,多品种才建议加。三层多样化多头交叉注意力拿编码器输出当第二信息源,末尾全连接投影层不带激活。 这里有两个坑得记牢:一是预测目标不是价格延续值,而是指标变化系数,输出维度和解码器输入对齐,所以逆向归一化变得多余;二是单品种时全连接层够用,并行多品种得换卷积层各自出系数。 训练阶段在 Train 方法里完成。先从经验回放缓冲区按盈利能力加权采样轨迹,偏向可盈利样本;循环里取轨迹和初始状态,校验历史与计划横向范围数据齐全后,把历史灌进缓冲区跑前向。每个预测模型自带独立编码器,计算成本高但能抓各自任务的依赖。解码器虽只吃最后一根柱线,但训练时直接传整段历史矩阵、取前两行匹配输入层即可,免得另建副本。 目标值构造上,依赖项搜索模型拿序列本身做目标;预测模型算变化系数——回放数据是未归一化的,按未来矩阵每参数最大绝对值归一,系数通常落在 {-2.0, 2.0}。短期模型取下一根柱线系数差,长期模型加总并乘折扣因子。反向传播先短后长更新参数,日志打印进度,附件 Study1.mq5 有完整源码。外汇与贵金属市场高风险,这类模型输出仅作概率参考,实盘前务必在 MT5 用历史数据回测验证。

MQL5 / C++
<span class="keyword">class="type">bool</span> CreateRelationDescriptions(CArrayObj *&amp;encoder, CArrayObj *&amp;decoder)
&nbsp;&nbsp;{
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; CLayerDescription *descr;
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!encoder)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;encoder = <span class="keyword">new</span> CArrayObj();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!encoder)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="keyword">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">if</span>(!decoder)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;decoder = <span class="keyword">new</span> CArrayObj();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!decoder)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="keyword">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//--- Encoder</span>
&nbsp;&nbsp; encoder.Clear();
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription()))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp; descr.type = defNeuronBaseOCL;
&nbsp;&nbsp; <span class="keyword">class="type">int</span> prev_count = descr.count = (HistoryBars * BarDescr);
&nbsp;&nbsp; descr.activation = None;
&nbsp;&nbsp; descr.optimization = ADAM;
&nbsp;&nbsp; <span class="keyword">if</span>(!encoder.Add(descr))
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">delete</span> descr;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//--- layer class="num">1</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription()))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp; descr.type = defNeuronBatchNormOCL;
&nbsp;&nbsp; descr.count = prev_count;
&nbsp;&nbsp; descr.batch = <span class="number">class="num">1</span>e4;
&nbsp;&nbsp; descr.activation = None;
&nbsp;&nbsp; descr.optimization = ADAM;
&nbsp;&nbsp; <span class="keyword">if</span>(!encoder.Add(descr))
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">delete</span> descr;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="macro">class="kw">false</span>;
&nbsp;&nbsp;&nbsp;&nbsp; }
<span class="comment">class=class="str">"cmt">//--- layer class="num">2</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!(descr = <span class="keyword">new</span> CLayerDescription()))

「编码器与解码器的层栈拼装」

这段逻辑在 MT5 的神经网络封装里把编码器、解码器一层层堆起来,每层都用 CLayerDescription 描述后再 Add 进容器。注意 dropout 层 probability 写死 0.5f,意味着训练时随机屏蔽一半神经元,过拟合概率可能下降,但样本量不够时收敛会偏慢。 第四层用了 defNeuronDMHAttention,window_out=32、step=4(头数)、layers=3,batch 统一设 1e4。多头注意力在汇率序列上可能捕捉到跨周期依赖,但 1e4 的批大小在普通 VPS 上容易爆显存,实盘前先调小验证。 交叉注意力层(defNeuronCrossDMHAttention)用 ArrayCopy 把 windows 设成 {BarDescr, BarDescr},units 按 prev_count/windows[0] 与 HistoryBars 切分;若 ArrayCopy 返回值小于源数组长度直接 return false,这种防御写法能避免空层混进解码器。 最后一层 defNeuronRevInDenormOCL 的 count 由 prev_count = descr.units[0]*descr.windows[0] 推算,layers=1。跑通后 decoder.Clear() 留作复用入口,开 MT5 把这段贴进 Expert 的 OnInit 前面就能看层栈是否按预期构建。

MQL5 / C++
  class="kw">return class="kw">false;
  descr.type = defNeuronDropoutOCL;
  descr.count = prev_count;
  descr.batch = class="num">1e4;
  descr.activation = None;
  descr.optimization = ADAM;
  descr.probability = class="num">0.5f;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">3
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronLearnabledPE;
  descr.count = prev_count;
  descr.batch = class="num">1e4;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">4
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronDMHAttention;
  descr.window = BarDescr;
  descr.window_out = class="num">32;
  descr.count = HistoryBars;
  descr.step = class="num">4;                class=class="str">"cmt">//Heads
  descr.layers = class="num">3;              class=class="str">"cmt">//Layers
  descr.batch = class="num">1e4;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!encoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">4
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronCrossDMHAttention;
class=class="str">"cmt">//--- Windows
    {
      class="type">int temp[] = {BarDescr, BarDescr};
      if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size())
        class="kw">return class="kw">false;
    }
  descr.window_out = class="num">32;
class=class="str">"cmt">//--- Units
    {
      class="type">int temp[] = {prev_count/descr.windows[class="num">0], HistoryBars};
      if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size())
        class="kw">return class="kw">false;
    }
  descr.step = class="num">4;                class=class="str">"cmt">//Heads
  descr.layers = class="num">3;              class=class="str">"cmt">//Layers
  descr.batch = class="num">1e4;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!decoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">5
  prev_count = descr.units[class="num">0] * descr.windows[class="num">0];
  if(!(descr = new CLayerDescription()))
    class="kw">return class="kw">false;
  descr.type = defNeuronRevInDenormOCL;
  descr.count = prev_count;
  descr.layers = class="num">1;
  descr.activation = None;
  descr.optimization = ADAM;
  if(!decoder.Add(descr))
    {
      class="kw">delete descr;
      class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class=class="str">"cmt">//--- Decoder
  decoder.Clear();

解码器堆叠与轨迹采样训练落点

这段把解码器的层结构用代码直接铺开:输入层用 defNeuronBaseOCL,节点数等于 BarDescr,优化器统一挂 ADAM;第一层接 BatchNorm,batch 设到 1e4,这种大 batch 在 MT5 的 OpenCL 后端可能加快收敛但吃显存。 第二层是 CrossDMHAttention,windows 数组填 {BarDescr, BarDescr},window_out=32,units 设 {1, HistoryBars},step=4 即 4 个注意力头,layers=3 叠三层。Heads 和 Layers 写死在注释里,调参时直接改这两个常量就能换结构。 Train 函数里先按 0.9 阈值取轨迹概率,再用 MathRand 平方采样挑训练片段,偏移上限是 Buffer[tr].Total - 2 - NForecast。外汇和贵金属行情高波动,这套采样在实盘前请在 MT5 策略测试器用历史数据跑一遍,观察过拟合概率。 把下面代码贴进你的 EA 的 decoder 构建函数,编译后看层是否按 4 层顺序 Add 成功;若返回 false 多半是 descr 没 new 出来或 OpenCL 设备不可用。

MQL5 / C++
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
       class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   prev_count = descr.count = (BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!decoder.Add(descr))
    {
       class="kw">delete descr;
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
       class="kw">return class="kw">false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!decoder.Add(descr))
    {
       class="kw">delete descr;
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
       class="kw">return class="kw">false;
   descr.type = defNeuronCrossDMHAttention;
class=class="str">"cmt">//--- Windows
    {
       class="type">int temp[] = {BarDescr, BarDescr};
       if(ArrayCopy(descr.windows, temp) < (class="type">int)temp.Size())
           class="kw">return class="kw">false;
    }
   descr.window_out = class="num">32;
class=class="str">"cmt">//--- Units
    {
       class="type">int temp[] = {class="num">1, HistoryBars};
       if(ArrayCopy(descr.units, temp) < (class="type">int)temp.Size())
           class="kw">return class="kw">false;
    }
   descr.step = class="num">4;                class=class="str">"cmt">//Heads
   descr.layers = class="num">3;              class=class="str">"cmt">//Layers
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!decoder.Add(descr))
    {
       class="kw">delete descr;
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
       class="kw">return class="kw">false;
   descr.type = defNeuronBaseOCL;
   descr.count = BarDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!decoder.Add(descr))
    {
       class="kw">delete descr;
       class="kw">return class="kw">false;
    }
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">void Train(class="type">void)
  {
class=class="str">"cmt">//---
   vector<class="type">class="kw">float> probability = GetProbTrajectories(Buffer, class="num">0.9);
class=class="str">"cmt">//---
   vector<class="type">class="kw">float> result, target, state;
   matrix<class="type">class="kw">float> predict;
   class="type">bool Stop = class="kw">false;
class=class="str">"cmt">//---
   class="type">uint ticks = GetTickCount();
   for(class="type">int iter = class="num">0; (iter < Iterations && !IsStopped() && !Stop); iter ++)
    {
       class="type">int tr = SampleTrajectory(probability);
       class="type">int i = (class="type">int)((MathRand() * MathRand() / MathPow(class="num">32767, class="num">2)) * (Buffer[tr].Total - class="num">2 - NForecast));

常见问题

预测编码是用历史行情先猜下一步、再用误差去修正模型的一种底噪过滤法;它不直接给买卖点,而是给神经网络先打底,减少假信号,比单纯均线更抗噪。
三支分别管价格自身、成交量节奏、跨周期关联;你只需看输出层给的「置信权重」高低,权重分散就别硬做,集中才考虑出手。
小布可接入你的品种页,把预测编码误差和解码轨迹自动标红绿,你一眼看出哪天模型飘了,不用自己算注意力。
先把层栈压到 2 层、轨迹采样步数减半跑通,再看验证集误差;外汇贵金属波动大,过拟合会放大爆仓概率,别堆层炫技。
用近 3 个月同品种 tick 级数据,按你券商点差加固定滑点重跑;只看收盘价回测会低估风险,贵金属跳空时差别尤其大。