交易中的神经网络:具有层化记忆的智代(终篇)(基础篇)
📘

交易中的神经网络:具有层化记忆的智代(终篇)(基础篇)

第 1/3 篇

◍ 层化记忆智代在 MT5 上的基本定位

这篇系列把一类带层化记忆结构的神经网络代理(智代)搬进 MetaTrader 5,用来替代传统静态指标做行情状态判别。它核心不在于预测点位,而是让网络按时间层级缓存市场特征,再对当前结构做条件响应。 原文发布于 2025 年 12 月 15 日,在 MQL5 社区显示为 870 次浏览、0 条评论,说明这类偏底层的实现方案仍属小众验证阶段,未进入大众讨论池。 对已有 MT5 开发经验的交易者,最直接可做的动作是:开 MT5 → 用 MetaEditor 新建 EA 骨架 → 把文中网络层缓存逻辑接进 OnTick,先跑 EURUSD 的 M15 看层间激活差异。外汇与贵金属杠杆高,这类实验性系统仅作技术验证,实盘存在显著回撤风险。

「FinMem 框架在 MT5 里的取舍」

FinMem 原版用层化记忆把数据分两路:工作记忆吃每日新闻和行情波动这类短期料,长期记忆存分析报告和研究素材。优先级由结构自动排,短期事件即时算,深层信息留着以后用。 我们之前的 MQL5 移植版有意拿掉了大语言模型——这是原框架的核心部件。去掉之后整条架构都得改,但信息流主干还是复刻了:CNeuronFinMem 对象保留层化处理方式,把短期信息和长期策略方法揉在一起,力求在动态行情里出稳定可预期的表现。 框架作者实验里,FinMem 在有限输入下仍能并发分析数十个市场信号并按重要性结构化,优于其他自主交易模型。外汇和贵金属市场高波动、高杠杆,这类信号并发处理只降低认知负载,不承诺收益。

FinMem 前馈与梯度回传的双流闭环

CNeuronFinMem 的 feed-forward 吃两条数据流:环境状态张量(报价 + 指标数值)和账户状态向量(余额、盈亏、时间戳)。张量先经置换便于投影,再分送两个记忆模块——一个啃柱线时间动态,一个挖多模态单变量间的隐藏相关,最后用交叉注意力把两套表征揉在一起。 账户向量随后介入两次交叉注意力:一次和记忆结果比,看市场变动怎么撬动余额;一次和智代近期动作比,评估策略效率与可接受风险。注意智代动作张量本身是第三条源,但类只接两条输入,于是借对象输出缓冲区存上一帧动作,用指针回调内部交叉注意力,形成重复模块式的闭环。 反向传播 calcInputGradients 必须严格镜像前馈逆序。前馈末尾调了父类生成新动作张量,所以梯度也从父类方法起步,先灌进两条并行路径的内部交叉注意力。难点在于其中一条路径递归用了上一次前馈的结果,回传时会成环——要靠替换缓冲区指针把旧结果无损恢复出来再断环。 账户状态缓冲区被记忆流和动作流共用,两路梯度得分别算完再汇总。环境-状态张量比动作矢量大得多,因此可用其多余缓冲区暂存梯度数据,开销最小。所有指针复位后方法返回逻辑结果,整套非线性链路在 MT5 里跑通时,梯度分发准确性高度依赖这些指针切换顺序。 [CODE] 段里 CNeuronFinMem 继承 CNeuronRelativeCrossAttention,保护成员含 1 个转置对象、2 个记忆单元与 3 个交叉注意力对象;feedForward 和 calcInputGradients 的各双参重载是核心,单参版直接返回 false 被架空。Init 参数暴露了 window / heads / accoiunt_descr / nactions 等维度,调参时 units_count 与 heads 不匹配会直接让 OpenCL 内核编译失败。

MQL5 / C++
<span class="keyword">class</span> CNeuronFinMem&nbsp;&nbsp; :&nbsp;&nbsp;<span class="keyword">class="kw">public</span> CNeuronRelativeCrossAttention
&nbsp;&nbsp;{
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp; CNeuronTransposeOCL&nbsp;&nbsp;cTransposeState;
&nbsp;&nbsp; CNeuronMemory&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cMemory[<span class="number">class="num">2</span>];
&nbsp;&nbsp; CNeuronRelativeCrossAttention cCrossMemory;
&nbsp;&nbsp; CNeuronRelativeCrossAttention cMemoryToAccount;
&nbsp;&nbsp; CNeuronRelativeCrossAttention cActionToAccount;
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;feedForward(CNeuronBaseOCL *NeuronOCL) <span class="keyword">class="kw">override</span>&nbsp;&nbsp; { <span class="keyword">class="kw">return</span> <span class="keyword">false</span>; }
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) <span class="keyword">class="kw">override</span>;
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calcInputGradients(CNeuronBaseOCL *NeuronOCL) <span class="keyword">class="kw">override</span>&nbsp;&nbsp; { <span class="keyword">class="kw">return</span> <span class="keyword">false</span>; }
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = None) <span class="keyword">class="kw">override</span>;
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;updateInputWeights(CNeuronBaseOCL *NeuronOCL) <span class="keyword">class="kw">override</span>&nbsp;&nbsp; { <span class="keyword">class="kw">return</span> <span class="keyword">false</span>; }
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) <span class="keyword">class="kw">override</span>;
<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; CNeuronFinMem(<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;~CNeuronFinMem(<span class="keyword">class="type">void</span>) {};
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">class="kw">virtual</span> <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Init(<span class="keyword">class="type">uint</span> numOutputs, <span class="keyword">class="type">uint</span> myIndex, COpenCLMy *open_cl,
&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 class="keyword">class="type">uint</span> window, <span class="keyword">class="type">uint</span> window_key, <span class="keyword">class="type">uint</span> units_count, <span class="keyword">class="type">uint</span> heads,
&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 class="keyword">class="type">uint</span> accoiunt_descr, <span class="keyword">class="type">uint</span> nactions,
&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;ENUM_OPTIMIZATION optimization_type, <span class="keyword">class="type">uint</span> batch);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//---</span>

◍ 金融记忆神经元的接口与前向传播链路

CNeuronFinMem 类在派生时重写了 7 个虚函数:Type 直接返回 defNeuronFinMem 常量用于运行时识别;Save / Load 负责二进制句柄的序列化;WeightsUpdate 接收源神经元与 tau 学习率做权重更新;SetOpenCL 注入计算上下文;Clear 释放状态。这套接口决定了该神经元能无缝挂进 MT5 的 OCL 训练管线。 feedForward 的前向顺序很固定:先跑转置状态层 cTransposeState,再让两块 cMemory[0]、cMemory[1] 依次前向,其中后者吃转置层的输出。随后 cCrossMemory 交叉两块记忆,cMemoryToAccount 与 cActionToAccount 各自融合 SecondInput 账户特征,最后交换 Output / PrevOutput 缓冲并走相对交叉注意力。任意一步返回 false 即中断,说明单层失败会让整批样本报废。 calcInputGradients 反向时先校验 NeuronOCL、SecondInput、SecondGradient 非空,否则直接退出。它先对相对交叉注意力求梯度,再交换缓冲、把 Gradient 暂存到 temp,借 cMemoryToAccount 的上一步输出重置梯度指针,最后算隐藏层梯度。注意 temp 变量实际未被复用,属于可删的冗余行。 在 MT5 里把这段编译进 EA 后,可故意让 SecondInput 传空指针验证第 41 行防护是否生效;外汇与贵金属杠杆高,模型过拟合时回测漂亮实盘仍可能大幅回撤。

MQL5 / C++
class="kw">virtual class="type">int      Type(class="type">void) class="kw">override  const   { class="kw">return defNeuronFinMem; }
class=class="str">"cmt">//---
class="kw">virtual class="type">bool      Save(class="type">int const file_handle) class="kw">override;
class="kw">virtual class="type">bool      Load(class="type">int const file_handle) class="kw">override;
class=class="str">"cmt">//---
class="kw">virtual class="type">bool      WeightsUpdate(CNeuronBaseOCL *source, class="type">class="kw">float tau) class="kw">override;
class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
class=class="str">"cmt">//---
class="kw">virtual class="type">bool      Clear(class="type">void) class="kw">override;
};

class="type">bool CNeuronFinMem::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
   if(!cTransposeState.FeedForward(NeuronOCL))
      class="kw">return false; 
   if(!cMemory[class="num">0].FeedForward(NeuronOCL) ||
      !cMemory[class="num">1].FeedForward(cTransposeState.AsObject()))
      class="kw">return false;
   if(!cCrossMemory.FeedForward(cMemory[class="num">0].AsObject(), cMemory[class="num">1].getOutput()))
      class="kw">return false;
   if(!cMemoryToAccount.FeedForward(cCrossMemory.AsObject(), SecondInput))
      class="kw">return false;
   if(!cActionToAccount.FeedForward(this.AsObject(), SecondInput))
      class="kw">return false;
   if(!SwapBuffers(Output, PrevOutput))
      class="kw">return false;
   if(!CNeuronRelativeCrossAttention::feedForward(cActionToAccount.AsObject(), cMemoryToAccount.getOutput()))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
  }
class="type">bool CNeuronFinMem::calcInputGradients(CNeuronBaseOCL *NeuronOCL,
                                     CBufferFloat *SecondInput,
                                     CBufferFloat *SecondGradient,
                                     ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
   if(!NeuronOCL || !SecondInput || !SecondGradient)
      class="kw">return false;
   if(!CNeuronRelativeCrossAttention::calcInputGradients(cActionToAccount.AsObject(),
         cMemoryToAccount.getOutput(),
         cMemoryToAccount.getGradient(),
         (ENUM_ACTIVATION)cMemoryToAccount.Activation()))
      class="kw">return false;
   if(!SwapBuffers(Output, PrevOutput))
      class="kw">return false;
   CBufferFloat *temp = Gradient;
   if(!SetGradient(cMemoryToAccount.getPrevOutput(), false))
      class="kw">return false;
   if(!calcHiddenGradients(cActionToAccount.AsObject(), SecondInput, SecondGradient, SecondActivation))
      class="kw">return false;
   if(!SwapBuffers(Output, PrevOutput))

「反向传播里逐层梯度的串接顺序」

这段片段处在神经网络反向传播的中段,职责是把输出侧的误差沿隐藏层一路回传。任何一步 calcHiddenGradients 返回失败就直接 return false,整轮梯度计算中止,权重不会被更新。 从代码看,梯度回传不是一次性完成的:先处理 SecondInput 对应的交叉记忆梯度,再用 SumAndNormilize 把 SecondGradient 按窗口 1 做归一;随后对 cMemory[0]、cTransposeState、NeuronOCL 依次求隐藏梯度。注意 NeuronOCL 这里被临时存了梯度(temp),切换 prevOutput 后重算,再还原,说明同一层在不同上下文里梯度要分段累加。 在 MT5 里跑这套逻辑时,若你改了网络层数,必须保证 cMemory 数组下标与调用顺序一致,否则 cMemory[1] 取到的输出维度对不上,calcHiddenGradients 会静默返回 false。外汇与贵金属模型训练波动大,这种隐性失败在高杠杆品种上可能让回测曲线突然走平,属常见坑。

MQL5 / C++
  class="kw">return false;
  Gradient = temp;
  if(!cCrossMemory.calcHiddenGradients(cMemoryToAccount.AsObject(), SecondInput, cMemoryToAccount.getPrevOutput(),
SecondActivation))
    class="kw">return false;
  if(!SumAndNormilize(SecondGradient, cMemoryToAccount.getPrevOutput(), SecondGradient, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return false;
  if(!cMemory[class="num">0].calcHiddenGradients(cCrossMemory.AsObject(), cMemory[class="num">1].getOutput(), cMemory[class="num">1].getGradient(),
(ENUM_ACTIVATION)cMemory[class="num">1].Activation()))
    class="kw">return false;
  if(!cTransposeState.calcHiddenGradients(cMemory[class="num">1].AsObject()))
    class="kw">return false;
  if(!NeuronOCL.calcHiddenGradients(cMemory[class="num">0].AsObject()))
    class="kw">return false;
  temp = NeuronOCL.getGradient();
  if(!NeuronOCL.SetGradient(cTransposeState.getPrevOutput(), false) ||
    !NeuronOCL.calcHiddenGradients(cTransposeState.AsObject()) ||
    !NeuronOCL.SetGradient(temp, false) ||
    !SumAndNormilize(temp, cTransposeState.getPrevOutput(), temp, iWindow, false, class="num">0, class="num">0, class="num">0, class="num">1))
    class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }

智代模型的层栈怎么搭

可训练模型的结构全部在 CreateDescriptions 里落定,目前只训一个叫「智代」的模型,它是财务系统里的决策核心。框架本身还带了独有学习算法,但这次先不碰,重点看架构骨架。 输入先走全连接层吃原始数据,紧接着批归一化层(batch 设了 1e4),作用是压住数据尺度波动、让后续训练更稳。再往后接我们之前写的 FinMem 模块,它负责数据处理和决策形成的关键一层。 windows 数组里给了三个输入张量维度:单根 K 线描述、账户状态、智代动作。动作维度被设成常量 NActions 的两倍,前半段当分布均值、后半段当方差,交叉注意力会把主输入流劈成两个等长向量,从而在输出拿到成对的均值和方差。 分布内数值交给 VAE 潜态层处理,最后用卷积层(defNeuronConvSAMOCL,窗口与步长都是 3,激活 SIGMOID)把结果投回智代动作区间。外汇与贵金属波动剧烈,这类模型仅提供概率化决策倾向,实盘前务必在 MT5 用历史数据验证。 下面这段代码就是 CreateDescriptions 的实体,逐行看能直接抄进 EA 里改参。指针无效就 new 一个 CArrayObj;输入层维度 = HistoryBars * BarDescr,优化用 ADAM。批归一化层 count 沿用上层、batch=1e4。FinMem 层 windows 写死 {BarDescr, AccountDescr, 2*NActions},window_out=32、step=4 即 32 个头。VAE 层 count=NActions。卷积层 count=NActions/3,SIGMOID 收口。

MQL5 / C++
class="type">bool CreateDescriptions(CArrayObj *&actor)
  {
class=class="str">"cmt">//---
   CLayerDescription *descr;
class=class="str">"cmt">//---
   if(!actor)
     {
       actor = new CArrayObj();
       if(!actor)
         class="kw">return false;
     }
class=class="str">"cmt">//--- Actor
   actor.Clear();
class=class="str">"cmt">//--- Input layer
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBaseOCL;
   class="type">int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">1
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">2
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronFinMem;
class=class="str">"cmt">//--- Windows
     {
       class="type">int temp[] = {BarDescr, AccountDescr, class="num">2*NActions}; class=class="str">"cmt">//Window, Account description, N Actions
       if(ArrayCopy(descr.windows, temp) < class="type">int(temp.Size()))
         class="kw">return false;
     }
   descr.count = HistoryBars;
   descr.window_out = class="num">32;
   descr.step = class="num">4;                                        class=class="str">"cmt">// Heads
   descr.batch = class="num">1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">3
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronVAEOCL;
   descr.count = NActions;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
       class="kw">delete descr;
       class="kw">return false;
     }
class=class="str">"cmt">//--- layer class="num">4
   if(!(descr = new CLayerDescription()))
      class="kw">return false;
   descr.type = defNeuronConvSAMOCL;
   descr.count = NActions / class="num">3;
   descr.window = class="num">3;
   descr.step = class="num">3;
   descr.window_out = class="num">3;
   descr.activation = SIGMOID;

常见问题

它主要把行情波动按时间分层存进记忆栈,再在前馈里调用相关层做倾向判断;你可用它辅助识别某段走势是否被反复确认,但外汇高风险,结论仅作概率参考。
通常舍去跨品种长程语义检索,只保留单品种短中期记忆层与前馈闭环,避免过拟合和卡顿;保留梯度回传双流即可跑通基础版。
小布已内置类似 AIGC 分析,打开对应品种页就能看到记忆层倾向与预警,不用自己搭代码环境。
易错在记忆层读出权重未做归一,导致后续层输入量级漂移;逐层打印中间张量均值可快速定位。
实盘建议记忆层 2~3 层加 1 个决策前馈层,超过 4 层在普通终端上回测明显变慢且易过拟合。