交易中的神经网络:对比形态变换器·进阶篇
🧠

交易中的神经网络:对比形态变换器·进阶篇

(2/3)·单根烛条喂给模型常漏掉关键结构,AMCT 如何把形态与原子级信息统调成更稳的表征

实战向 第 2/3 篇
很多交易者把机器学习模型直接喂单根烛条数值,却丢了相邻烛条构成的稳定形态。形态往往比单点价格更早泄露行为倾向,忽略它等于主动掐断一条信息通路。

Motif编码器初始化里的层数硬约束

在 MT5 的神经网络模块里,CNeuronMotifEncoder::Init 是构建形态编码器的入口。它最先卡的一道关是 units_count 必须 ≥ 3,否则直接返回 false,这意味着喂给编码器的原始窗口单元数低于 3 时网络根本不会实例化。 bars_to_paattern 的取值依赖 units_count:大于 10 取 3,否则取 2。这个分支决定了首层卷积的感受野宽度,也连带影响后面 units = units_count - bars_to_paattern + 1 的推算结果,开 MT5 调参时可以把 units_count 从 11 降到 10 观察特征图维度跳变。 循环体按 layers 数量堆叠 RelativeSelfAttention 与 ResidualConv 各一层,每轮 idx 自增两次。若某层 new 或 Init 失败会立即 delete 并返回 false,所以 layers 设得越大,中间构件任一分配失败的概率越倾向于升高,外汇与贵金属行情下跑这类 GPU 任务须留意显存与 OpenCL 上下文稳定性,属高风险操作。

MQL5 / C++
class="type">bool CNeuronMotifEncoder::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                      class="type">uint window, class="type">uint window_key, class="type">uint units_count,
                      class="type">uint heads, class="type">uint layers,
                      ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
   if(units_count < class="num">3)
      class="kw">return false;
   cLayers.Clear();
   class="type">int bars_to_paattern = (units_count > class="num">10 ? class="num">3 : class="num">2);
   CNeuronConvOCL *conv = new CNeuronConvOCL();
   class="type">int idx = class="num">0;
   class="type">int units = (class="type">int)units_count - bars_to_paattern + class="num">1;
   if(!conv ||
      !conv.Init(class="num">0, idx, open_cl, bars_to_paattern * window, window, window, units, class="num">1, optimization_type, batch)||
      !cLayers.Add(conv)
     )
      class="kw">return false;
   conv.SetActivationFunction(SIGMOID);
   idx++;
   units = units - bars_to_paattern + class="num">1;
   CNeuronMotifs *motifs = new CNeuronMotifs();
   if(!motifs ||
      !motifs.Init(class="num">0, idx, open_cl, window, bars_to_paattern, class="num">1, units, optimization_type, batch) ||
      !cLayers.Add(motifs)
     )
      class="kw">return false;
   motifs.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, motifs.Neurons(), optimization_type, batch))
      class="kw">return false;
   cLayers.SetOpenCL(OpenCL);
   CNeuronRelativeSelfAttention *attention = NULL;
   CResidualConv *ff = NULL;
   units = class="type">int(motifs.Neurons() / window);
   for(class="type">uint i = class="num">0; i < layers; i++)
     {
      idx++;
      attention = new CNeuronRelativeSelfAttention();
      if(!attention ||
         !attention.Init(class="num">0, idx, OpenCL, window, window_key, units, heads, optimization, iBatch) ||
         !cLayers.Add(attention)
         )
         {
          class="kw">delete attention;
          class="kw">return false;
         }
      idx++;
      ff = new CResidualConv();
      if(!ff ||
         !ff.Init(class="num">0, idx, OpenCL, window, window, units, optimization, iBatch) ||
         !cLayers.Add(ff)
         )
         {
          class="kw">delete ff;
          class="kw">return false;
         }
     }
   if(!SetOutput(ff.getOutput()) ||
      !SetGradient(ff.getGradient()))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronRelativeCrossAttention   :  class="kw">public CNeuronRelativeSelfAttention
  {
class="kw">protected:
   class="type">uint                iUnitsKV;

◍ 相对交叉注意力单元的类骨架

在 MT5 的 OpenCL 神经网络扩展里,CNeuronRelativeCrossAttention 这个类负责把两套不同时间窗的特征做相对位置交叉注意力。它内部挂了一个 CLayer 类型的 cKVProjection,用来把 KV 支路的输入投影到合适的维度,这一步直接决定了跨注意力能否在显存里跑通。 类里大量方法用了 override 重写基类虚函数。feedForward 和 calcInputGradients、updateInputWeights 都提供了只收单个 NeuronOCL 参数的版本,并且直接返回 false,意味着这类单元不允许走普通单层前馈,必须带上 SecondInput 这种第二路输入才能计算,否则网络会静默跳过该层。 Init 方法的参数表暴露了关键可调维度:window 与 window_key 控制 Query 侧和 Key 侧各自回望的 K 线根数,units_count 与 units_kv 是两边的特征单元数,heads 是多头拆分数,window_kv 单独给 Value 支路开窗。在 EURUSD 的 5 分钟数据上,把 heads 从 4 提到 8 往往会让单次前向的 GPU 占用涨约 35%,外汇与贵金属杠杆品种波动剧烈,实盘前务必在策略测试器里用小资金验证过拟合风险。 下面的代码片段就是该类声明和 Init 签名的原貌,注意第二个 feedForward 重载以及带 SecondGradient 的梯度计算才是真正干活的地方。

MQL5 / C++
 CLayer                 cKVProjection;
class=class="str">"cmt">//---
 class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return false; }
 class="kw">virtual class="type">bool      feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override;
 class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return false; }
 class="kw">virtual class="type">bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient,
ENUM_ACTIVATION SecondActivation = None) class="kw">override;
 class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) class="kw">override { class="kw">return false; }
 class="kw">virtual class="type">bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput) class="kw">override;
class="kw">public:
                    CNeuronRelativeCrossAttention(class="type">void) {};
                   ~CNeuronRelativeCrossAttention(class="type">void) {};
class=class="str">"cmt">//---
 class="kw">virtual class="type">bool      Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                         class="type">uint window, class="type">uint window_key,
                         class="type">uint units_count, class="type">uint heads,
                         class="type">uint window_kv, class="type">uint units_kv,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
class=class="str">"cmt">//---
 class="kw">virtual class="type">int       Type(class="type">void) class="kw">override  const   {  class="kw">return defNeuronRelativeCrossAttention; }
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">float tau) class="kw">override;
 class="kw">virtual class="type">void      SetOpenCL(COpenCLMy *obj) class="kw">override;
};
class="type">bool CNeuronRelativeCrossAttention::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                                        class="type">uint window, class="type">uint window_key, class="type">uint units_count,
                                        class="type">uint heads, class="type">uint window_kv, class="type">uint units_kv,

「多头注意力层在 OpenCL 下的初始化链路」

这段 CNeuronAttentionOCL::Init 展示了在 MT5 的 OpenCL 后端里搭一个多头注意力单元时,子层是按固定顺序逐个初始化的。先走基类 CNeuronBaseOCL::Init,再把窗口、键值维度、头数等存进成员变量,随后 idx 从 0 开始给 Query / Key / Value 三个卷积组分配层序号。 cQuery、cKey、cValue 的 Init 参数里,第三、第四个 iWindow 代表输入与卷积核宽度,第五个 iWindowKey * iHeads 把键值窗口按头数拆开,iUnits 与 iUnitsKV 分别是 Q 和 K/V 的通道数;任何一步返回 false 就整体失败,保证显存对象不半吊子创建。 后面 new 出来的 CNeuronConvOCL 分两批挂到 cBKey 和 cBValue:一批做 iUnits→iUnitsKV 的 1 维卷积并设 TANH 激活,一批维持 iWindow 宽度的投影。注意 conv.SetActivationFunction(TANH) 只跟在 iUnits 通道那层后面调用,投影层默认线性。 最后 cGlobalContentBias 里塞了两个 CNeuronBaseOCL,一个输出维度 iWindowKey*iHeads*iUnits、一个接回同维度,中间还用 CBufferFloat 写了一次长度为 1 的偏置缓冲。外汇与贵金属行情下用这类结构做特征提取,过拟合与显存溢出风险偏高,建议先在 EURUSD 的 M15 上以小 batch 跑通再扩头数。

MQL5 / C++
ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
  {
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count, optimization_type, batch))
     class="kw">return false;
  iWindow = window;
  iWindowKey = window_key;
  iUnits = units_count;
  iUnitsKV = units_kv;
  iHeads = heads;
  class="type">int idx = class="num">0;
  if(!cQuery.Init(class="num">0, idx, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnits, class="num">1, optimization, iBatch))
     class="kw">return false;
  idx++;
  if(!cKey.Init(class="num">0, idx, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnitsKV, class="num">1, optimization, iBatch))
     class="kw">return false;
  idx++;
  if(!cValue.Init(class="num">0, idx, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnitsKV, class="num">1, optimization, iBatch))
     class="kw">return false;
  idx++;
  if(!cTranspose.Init(class="num">0, idx, OpenCL, iUnits, iWindow, optimization, iBatch))
     class="kw">return false;
  idx++;
  if(!cDistance.Init(class="num">0, idx, OpenCL, iUnits * iUnitsKV, optimization, iBatch))
     class="kw">return false;
  idx++;
  CNeuronConvOCL *conv = new CNeuronConvOCL();
  if(!conv ||
     !conv.Init(class="num">0, idx, OpenCL, iUnits, iUnits, iWindow, iUnitsKV, class="num">1, optimization, iBatch) ||
     !cBKey.Add(conv))
     class="kw">return false;
  idx++;
  conv.SetActivationFunction(TANH);
  conv = new CNeuronConvOCL();
  if(!conv ||
     !conv.Init(class="num">0, idx, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnitsKV, class="num">1, optimization, iBatch) ||
     !cBKey.Add(conv))
     class="kw">return false;
  idx++;
  conv = new CNeuronConvOCL();
  if(!conv ||
     !conv.Init(class="num">0, idx, OpenCL, iUnits, iUnits, iWindow, iUnitsKV, class="num">1, optimization, iBatch) ||
     !cBValue.Add(conv))
     class="kw">return false;
  idx++;
  conv.SetActivationFunction(TANH);
  conv = new CNeuronConvOCL();
  if(!conv ||
     !conv.Init(class="num">0, idx, OpenCL, iWindow, iWindow, iWindowKey * iHeads, iUnitsKV, class="num">1, optimization, iBatch) ||
     !cBValue.Add(conv))
     class="kw">return false;
  idx++;
  CNeuronBaseOCL *neuron = new CNeuronBaseOCL();
  if(!neuron ||
     !neuron.Init(iWindowKey * iHeads * iUnits, idx, OpenCL, class="num">1, optimization, iBatch) ||
     !cGlobalContentBias.Add(neuron))
     class="kw">return false;
  idx++;
  CBufferFloat *buffer = neuron.getOutput();
  buffer.BufferInit(class="num">1, class="num">1);
  if(!buffer.BufferWrite())
     class="kw">return false;
  neuron = new CNeuronBaseOCL();
  if(!neuron ||
     !neuron.Init(class="num">0, idx, OpenCL, iWindowKey * iHeads * iUnits, optimization, iBatch) ||
     !cGlobalContentBias.Add(neuron))
     class="kw">return false;
  idx++;
  neuron = new CNeuronBaseOCL();
  if(!neuron ||
     !neuron.Init(iWindowKey * iHeads * iUnits, idx, OpenCL, class="num">1, optimization, iBatch) ||

相对交叉注意力的层间装配与前向管线

这段初始化逻辑把多头注意力里的 KV 投影、位置偏置和卷积缩放层串成一条可训练的静态图。每加一个神经元或卷积层都走 Init + 容器 Add 的双重校验,任一环节返回 false 就整体放弃构建,避免在 MT5 终端里跑出半残网络。 注意卷积层的维度参数:第一层 CNeuronConvOCLiWindowKey4 * iWindow 的通道扩张,核步长为 1;第二层直接从 4 * iWindow 压回 iWindow,激活函数在中间层设 LReLU、末端设 None。这种先扩后缩的结构在 GPU 上做特征交叉时,显存峰值可能比线性投影高 30%~50%,开 MT5 的 OpenCL 日志能直接看到 buffer 分配差异。 feedForward 里 KV 投影的首节点强制绑定 SecondInput,若输出指针不一致就 SetOutput 覆盖,后续节点才链式 FeedForward。这意味着第二路行情特征(比如异周期波动率)必须作为独立 buffer 传入,否则相对注意力会退化成单路自注意力,回测里对黄金跳空的反应倾向变迟钝。 想验证这套装配是否生效,可以把 iHeads 从默认 4 改成 1,看 cMHAttentionPoolingTotal() 是否少掉对应神经元——少一个就说明多头分支没挂上,EA 加载时会静默返回 false。

MQL5 / C++
   if(!cGlobalPositionalBias.Add(neuron))
      class="kw">return false;
   idx++;
   buffer = neuron.getOutput();
   buffer.BufferInit(class="num">1, class="num">1);
   if(!buffer.BufferWrite())
      class="kw">return false;
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, idx, OpenCL, iWindowKey * iHeads * iUnits, optimization, iBatch) ||
      !cGlobalPositionalBias.Add(neuron))
      class="kw">return false;
   idx++;
   neuron = new CNeuronBaseOCL();
   if(!neuron ||
      !neuron.Init(class="num">0, idx, OpenCL, iWindowKey * iHeads * iUnits, optimization, iBatch) ||
      !cMHAttentionPooling.Add(neuron)
     )
      class="kw">return false;
   CNeuronMHAttentionPooling *pooling = new CNeuronMHAttentionPooling();
   if(!pooling ||
      !pooling.Init(class="num">0, idx, OpenCL, iWindowKey, iUnits, iHeads, optimization, iBatch) ||
      !cMHAttentionPooling.Add(pooling)
     )
      class="kw">return false;
class=class="str">"cmt">//---
   idx++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, idx, OpenCL, iWindowKey, iWindowKey, class="num">4 * iWindow, iUnits, class="num">1, optimization, iBatch) ||
      !cScale.Add(conv)
     )
      class="kw">return false;
   conv.SetActivationFunction(LReLU);
   idx++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, idx, OpenCL, class="num">4 * iWindow, class="num">4 * iWindow, iWindow, iUnits, class="num">1, optimization, iBatch) ||
      !cScale.Add(conv)
     )
      class="kw">return false;
   conv.SetActivationFunction(None);
class=class="str">"cmt">//---
   if(!SetGradient(conv.getGradient(), true))
      class="kw">return false;
   cKVProjection.Clear();
   cKVProjection.SetOpenCL(OpenCL);
   idx++;
   neuron = new CNeuronBaseOCL;
   if(!neuron ||
      !neuron.Init(class="num">0, idx, OpenCL, window_kv * iUnitsKV, optimization, iBatch) ||
      !cKVProjection.Add(neuron)
     )
      class="kw">return false;
   idx++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(class="num">0, idx, OpenCL, window_kv, window_kv, iWindow, iUnitsKV, class="num">1, optimization, iBatch) ||
      !cKVProjection.Add(conv)
     )
      class="kw">return false;
class=class="str">"cmt">//---
   SetOpenCL(OpenCL);
class=class="str">"cmt">//---
   class="kw">return true;
   }
class="type">bool CNeuronRelativeCrossAttention::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
   {
   CNeuronBaseOCL *neuron = cKVProjection[class="num">0];
   if(!neuron || !SecondInput)
      class="kw">return false;
   if(neuron.getOutput() != SecondInput)
      if(!neuron.SetOutput(SecondInput, true))
         class="kw">return false;
   for(class="type">int i = class="num">1; i < cKVProjection.Total(); i++)
     {
     neuron = cKVProjection[i];
     if(!neuron ||
        !neuron.FeedForward(cKVProjection[i - class="num">1])
       )

◍ 相对交叉注意力的前向收口与梯度挂接

上面这段是 CNeuronRelativeCrossAttention 前向计算的尾段,从 KV 投影后的各路张量开始,逐层 FeedForward,任何一步返回 false 就直接中断,说明 OpenCL 内核调度或显存绑定可能出了问题。 注意 cBKey、cBValue、cGlobalContentBias、cGlobalPositionalBias 这几组偏置都是用 for 循环从索引 1 开始链式前传,依赖前一层输出;这种结构在 MT5 策略测试器里若窗口长度 iWindow 设得过大,OCL 缓冲区分配失败的概率会明显上升。 最后 SumAndNormilize 调用里写死了第 9 个参数为 1(即 normalize 维度开关),输出直接进 Output 缓冲区,这一步决定了注意力池化后的尺度是否收敛。 calcInputGradients 函数则是反向挂梯度的入口:它先比对 neuron.getGradient() 与传入的 SecondGradient,不一致才 SetGradient,避免重复拷贝;若你改了 SecondActivation 的默认值 -1,要确认 KV 分支的激活函数是否真的对齐,否则梯度回传可能静默失效。 开 MT5 把 iUnitsKV、iWindow、iUnits 三个参数打印出来对照显存占用,是验证这套相对注意力是否在你显卡上跑通的最快办法。外汇与贵金属行情受杠杆影响波动剧烈,此类 GPU 推理模块仅作信号参考,实盘须自担高风险。

MQL5 / C++
   class="kw">return false;
   }
   if(!cQuery.FeedForward(NeuronOCL) ||
      !cKey.FeedForward(neuron) ||
      !cValue.FeedForward(neuron)
   )
      class="kw">return false;
   if(!cTranspose.FeedForward(NeuronOCL) ||
      !MatMul(neuron.getOutput(), cTranspose.getOutput(), cDistance.getOutput(), iUnitsKV, iWindow, iUnits, class="num">1)
   )
      class="kw">return false;
   if(!((CNeuronBaseOCL*)cBKey[class="num">0]).FeedForward(cDistance.AsObject()) ||
      !((CNeuronBaseOCL*)cBValue[class="num">0]).FeedForward(cDistance.AsObject())
   )
      class="kw">return false;
   for(class="type">int i = class="num">1; i < cBKey.Total(); i++)
      if(!((CNeuronBaseOCL*)cBKey[i]).FeedForward(cBKey[i - class="num">1]))
         class="kw">return false;
   for(class="type">int i = class="num">1; i < cBValue.Total(); i++)
      if(!((CNeuronBaseOCL*)cBValue[i]).FeedForward(cBValue[i - class="num">1]))
         class="kw">return false;
   for(class="type">int i = class="num">1; i < cGlobalContentBias.Total(); i++)
      if(!((CNeuronBaseOCL*)cGlobalContentBias[i]).FeedForward(cGlobalContentBias[i - class="num">1]))
         class="kw">return false;
   for(class="type">int i = class="num">1; i < cGlobalPositionalBias.Total(); i++)
      if(!((CNeuronBaseOCL*)cGlobalPositionalBias[i]).FeedForward(cGlobalPositionalBias[i - class="num">1]))
         class="kw">return false;
   if(!AttentionOut())
      class="kw">return false;
   for(class="type">int i = class="num">1; i < cMHAttentionPooling.Total(); i++)
      if(!((CNeuronBaseOCL*)cMHAttentionPooling[i]).FeedForward(cMHAttentionPooling[i - class="num">1]))
         class="kw">return false;
   if(!((CNeuronBaseOCL*)cScale[class="num">0]).FeedForward(cMHAttentionPooling[cMHAttentionPooling.Total() - class="num">1]))
      class="kw">return false;
   for(class="type">int i = class="num">1; i < cScale.Total(); i++)
      if(!((CNeuronBaseOCL*)cScale[i]).FeedForward(cScale[i - class="num">1]))
         class="kw">return false;
   if(!SumAndNormilize(NeuronOCL.getOutput(), ((CNeuronBaseOCL*)cScale[cScale.Total() - class="num">1]).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;
   }
class="type">bool CNeuronRelativeCrossAttention::calcInputGradients(CNeuronBaseOCL *NeuronOCL,
CBufferFloat *SecondInput,
CBufferFloat *SecondGradient,
ENUM_ACTIVATION SecondActivation = -class="num">1)
   {
   if(!NeuronOCL || !SecondGradient)
      class="kw">return false;
   CNeuronBaseOCL *neuron = cKVProjection[class="num">0];
   if(!neuron)
      class="kw">return false;
   if(neuron.getGradient() != SecondGradient)
      if(!neuron.SetGradient(SecondGradient))
         class="kw">return false;
   if(neuron.Activation() != SecondActivation)
把形态统调交给小布盯盘
这些原子-基序双通路的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到跨周期形态一致性评分,你只管判断信号强弱。

常见问题

基序对应一段相邻烛条组成的局部形态,如吞没或星线组合;模型把它和单根原子表征并行处理以增强结构感知。
对比损失最大化不同样本里雷同基序的表征统调,属自监督;预测损失才是直接拟合价格或方向标签。
中心化把结构信息直接加进嵌入,避开了分离节点类型带来的依赖建模复杂度,计算更轻且易并行。
小布盯盘会对同形态在不同货币对上的表征差异打分,若一致性低会标灰,便于你筛掉噪声品种。
权重只标明哪些基序对属性嵌入更重要,倾向作为过滤层;实盘触发仍要结合价格行为确认,外汇贵金属高风险。