交易中的神经网络:多智代自适应模型(MASA)·进阶篇
📘

交易中的神经网络:多智代自适应模型(MASA)·进阶篇

第 2/2 篇

市场观测神经元的初始化链路

CNeuronMarketObserver 的 Init 方法把行情观测封装成一套多层 OpenCL 计算图,入参里 window 是回溯窗口、forecast 是预测步长、heads 与 layers 控制自注意力头数与堆叠深度,batch 决定训练批大小。 父类 CNeuronBaseOCL 先用 window * forecast 的总维度做基底初始化,失败直接返回 false;随后 cLayers 数组清空并绑定 OpenCL 上下文,这是后续所有子层挂载的容器。 链路依次挂了 5 类子层:转置层(CNeuronTransposeOCL)重排输入、分段线性层(CNeuronPLROCL)做非线性映射、变量自注意力层(CNeuronRMAT,用 window_key 限定键长)、残差卷积(CResidualConv)做预测映射、最后再转置回原始形态。任一层 new 或 Init 失败都 delete 并返回 false,只有全部通过才 SetOutput 暴露输出。 在 MT5 里调这套时,建议先单独打印 lay_count 确认五层索引为 0~4;若某层返回 false,多半是 units_count 与 window 的乘积超出显存分配。外汇与贵金属行情波动剧烈,此类模型仅作概率参考,实盘前务必用历史数据回测验证。

MQL5 / C++
class="type">uint heads, class="type">uint layers, class="type">uint forecast,
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  class="kw">const   {  class="kw">return defNeuronMarketObserver; }
};
class="type">bool CNeuronMarketObserver::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, class="type">uint forecast,
                  ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
{
class=class="str">"cmt">//--- Init parent object
  if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * forecast,
optimization_type, batch))
     class="kw">return false;
class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array
  cLayers.Clear();
  cLayers.SetOpenCL(OpenCL);
class=class="str">"cmt">//--- Tranpose input data
  class="type">int lay_count = class="num">0;
  CNeuronTransposeOCL *transp = new CNeuronTransposeOCL();
  if(!transp ||
     !transp.Init(class="num">0, lay_count, OpenCL, units_count, window, optimization, iBatch) ||
     !cLayers.Add(transp))
   {
     class="kw">delete transp;
     class="kw">return false;
   }
class=class="str">"cmt">//--- Piecewise linear representation
  lay_count++;
  CNeuronPLROCL *plr = new CNeuronPLROCL();
  if(!plr ||
     !plr.Init(class="num">0, lay_count, OpenCL, units_count, window, false, optimization, iBatch) ||
     !cLayers.Add(plr))
   {
     class="kw">delete plr;
     class="kw">return false;
   }
class=class="str">"cmt">//--- Self-Attention for Variables
  lay_count++;
  CNeuronRMAT *att = new CNeuronRMAT();
  if(!att ||
     !att.Init(class="num">0, lay_count, OpenCL, units_count, window_key, window, heads, layers,
optimization, iBatch) ||
     !cLayers.Add(att))
   {
     class="kw">delete att;
     class="kw">return false;
   }
class=class="str">"cmt">//--- Forecast mapping
  lay_count++;
  CResidualConv *conv = new CResidualConv();
  if(!conv ||
     !conv.Init(class="num">0, lay_count, OpenCL, units_count, forecast, window, optimization, iBatch) ||
     !cLayers.Add(conv))
   {
     class="kw">delete conv;
     class="kw">return false;
   }
class=class="str">"cmt">//--- Back transpose forecast
  lay_count++;
  transp = new CNeuronTransposeOCL();
  if(!transp ||
     !transp.Init(class="num">0, lay_count, OpenCL, window, forecast, optimization, iBatch) ||
     !cLayers.Add(transp))
   {
     class="kw">delete transp;
     class="kw">return false;
   }
  if(!SetOutput(transp.getOutput(), true) ||

◍ 强化学习智能体的类结构与初始化链路

在 MT5 的神经网络模块里,CNeuronRLAgent 继承自 CNeuronRMAT,对外只暴露默认构造、析构以及一个重写的 Type 方法,返回 defNeuronRLAgent 常量用于运行时类型识别。 它的 Init 方法是整个 agent 的装配中心:先调用父类 CNeuronBaseOCL::Init 完成基础张量分配,任意一步失败直接返回 false,避免半初始化对象进入训练循环。 随后清空 cLayers 数组并绑定 OpenCL 上下文,接着按 layers 参数循环 new 出 CNeuronPSformer 实例——每一层都传入 window、units_count、segments、rho 等状态观测超参,任一层 Add 失败就 delete 并回退。 循环结束后追加一个 CNeuronConvSAMOCL 卷积层(核尺寸 window×window,步长 1),显式设激活函数为 GELU,再接一个 CNeuronBaseSAMOCL 扁平层做动作维度对齐。 实盘接这套结构做贵金属策略时,layers 设 3、window 设 64 是常见的起步组合,但外汇与贵金属杠杆高、滑点突变频繁,回测通过不代表实盘概率占优,建议先在 MT5 策略测试器用 Tick 级数据跑一遍梯度检查。

MQL5 / C++
if(!SetGradient(transp.getGradient(), true))
   class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronRLAgent :  class="kw">public  CNeuronRMAT
   {
class="kw">public:
                     CNeuronRLAgent(class="type">void) {};
                    ~CNeuronRLAgent(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 units_count, class="type">uint segments, class="type">float rho,
                         class="type">uint layers, class="type">uint n_actions,
                         ENUM_OPTIMIZATION optimization_type, class="type">uint batch);
   class=class="str">"cmt">//---
   class="kw">virtual class="type">int       Type(class="type">void)  class="kw">const  class="kw">override {  class="kw">return defNeuronRLAgent;  }
   };
class="type">bool CNeuronRLAgent::Init(class="type">uint numOutputs, class="type">uint myIndex, COpenCLMy *open_cl,
                        class="type">uint window, class="type">uint units_count, class="type">uint segments,
                        class="type">float rho, class="type">uint layers, class="type">uint n_actions,
                        ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
   {
class=class="str">"cmt">//--- Init parent object
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, n_actions, optimization_type, batch))
      class="kw">return false;
class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array
   cLayers.Clear();
   cLayers.SetOpenCL(OpenCL);
class=class="str">"cmt">//--- State observation
   class="type">int lay_count = class="num">0;
   for(class="type">uint i = class="num">0; i < layers; i++)
    {
     CNeuronPSformer *psf = new CNeuronPSformer();
     if(!psf ||
       !psf.Init(class="num">0, lay_count, OpenCL, window, units_count, segments, rho, optimization,iBatch)||
       !cLayers.Add(psf))
       {
        class="kw">delete psf;
        class="kw">return false;
       }
      lay_count++;
    }
   CNeuronConvSAMOCL *conv = new CNeuronConvSAMOCL();
   if(!conv ||
     !conv.Init(n_actions, lay_count, OpenCL, window, window, class="num">1, units_count,
class="num">1, optimization, iBatch) ||
     !cLayers.Add(conv))
     {
      class="kw">delete conv;
      class="kw">return false;
     }
   conv.SetActivationFunction(GELU);
   lay_count++;
   CNeuronBaseSAMOCL *flat = new CNeuronBaseSAMOCL();
   if(!flat ||
     !flat.Init(class="num">0, lay_count, OpenCL, n_actions, optimization, iBatch) ||
     !cLayers.Add(flat))
    {
      class="kw">delete flat;

「控制代理类的双输入前向与初始化」

CNeuronControlAgent 继承自 CNeuronRMAT,但它把单输入版本的 feedForward 和 calcInputGradients、updateInputWeights 全部 override 成直接 return false,只保留带 SecondInput 的重载。这意味着这个类在设计上强制走双输入通道,单输入调用会被静默拒绝,MT5 里若误用父类单参接口不会报错只会返回失败。 初始化函数 Init 先调 CNeuronBaseOCL::Init,传入的窗口维度被乘上 units_count(window * units_count),再清空 cLayers 数组并绑定 OpenCL 上下文。lay_count 从 0 起步,后续由外部循环按 layers 参数逐层推入,外汇与贵金属行情下用 GPU 跑这类代理网络需注意显存占用随 window 与 units 乘积放大,杠杆品种高风险。 代码里还定义了 Type() 返回 defNeuronControlAgent,用作运行时类型辨识。若你要自己扩展控制代理,复制这段类壳后只需补完带 SecondInput 的三个虚函数体,单输入版本保持返回 false 即可避免歧义调用。

MQL5 / C++
   class="kw">return false;
   }
   SetActivationFunction(SIGMOID);
   if(!SetOutput(flat.getOutput(), true) ||
      !SetGradient(flat.getGradient(), true))
      class="kw">return false;
class=class="str">"cmt">//---
   class="kw">return true;
   }
class CNeuronControlAgent  :  class="kw">public CNeuronRMAT
   {
class="kw">protected:
   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:
                     CNeuronControlAgent(class="type">void) {};
                    ~CNeuronControlAgent(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, class="type">uint layers,
                         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   class="kw">const   {  class="kw">return defNeuronControlAgent; }
   };
class="type">bool CNeuronControlAgent::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, class="type">uint layers,
                            ENUM_OPTIMIZATION optimization_type, class="type">uint batch)
   {
class=class="str">"cmt">//--- Init parent object
   if(!CNeuronBaseOCL::Init(numOutputs, myIndex, open_cl, window * units_count,
optimization_type, batch))
      class="kw">return false;
class=class="str">"cmt">//--- Clear layers&class="macro">#x27; array
   cLayers.Clear();
   cLayers.SetOpenCL(OpenCL);
   class="type">int lay_count = class="num">0;

堆叠自注意力与交叉注意力的层装配

在 MT5 用 OpenCL 搭控制智能体时,先以 CNeuronBaseOCL 做展平层,再接 CNeuronTransposeOCL 做维度置换,二者都通过 Init 挂到 cLayers 容器,lay_count 每层 +1 用于后续索引定位。 若 units_count 大于 1,循环里优先建 CNeuronRelativeSelfAttention 捕捉单序列内部依赖;随后无条件接 CNeuronRelativeCrossAttention 融合外部键值序列(窗口 window_kv、单元 units_kv),最后补 CResidualConv 做残差卷积前馈。每一层失败都 delete 指针并返回 false,避免显存泄漏。 循环跑完 layers 次后,末端接 CNeuronConvSAMOCL 做空间压缩,Init 第 5 参数填 1 表示输出通道收敛;随后 SetActivationFunction(SIGMOID) 把输出压到 0~1 区间,适合表示动作概率。 feedForward 入口先校验 SecondInput 非空,再取 cLayers[0] 作为外部分支首层,任一为空直接返回 false——实盘跑前应在策略测试器用 EURUSD 5 分钟、2023 全年数据验证该网络前向不报空指针,外汇与贵金属杠杆交易高风险,回测通过不代表实盘盈利。

MQL5 / C++
  CNeuronBaseOCL *flat = new CNeuronBaseOCL();
  if(!flat ||
     !flat.Init(class="num">0, lay_count, OpenCL, window_kv * units_kv, optimization, iBatch) ||
     !cLayers.Add(flat))
     {
       class="kw">delete flat;
       class="kw">return false;
     }
  lay_count++;
  CNeuronTransposeOCL *transp = new CNeuronTransposeOCL();
  if(!transp ||
     !transp.Init(class="num">0, lay_count, OpenCL, units_kv, window_kv, optimization, iBatch) ||
     !cLayers.Add(transp))
     {
       class="kw">delete transp;
       class="kw">return false;
     }
  lay_count++;
class=class="str">"cmt">//--- Attention Action To Observation
  for(class="type">uint i = class="num">0; i < layers; i++)
     {
       if(units_count > class="num">1)
         {
          CNeuronRelativeSelfAttention *self = new CNeuronRelativeSelfAttention();
          if(!self ||
             !self.Init(class="num">0, lay_count, OpenCL, window, window_key, units_count, heads,
optimization, iBatch) ||
             !cLayers.Add(self))
             {
               class="kw">delete self;
               class="kw">return false;
             }
          lay_count++;
         }
       CNeuronRelativeCrossAttention *cross = new CNeuronRelativeCrossAttention();
       if(!cross ||
          !cross.Init(class="num">0, lay_count, OpenCL, window, window_key, units_count, heads,
units_kv, window_kv, optimization, iBatch) ||
          !cLayers.Add(cross))
          {
            class="kw">delete cross;
            class="kw">return false;
          }
       lay_count++;
       CResidualConv *ffn = new CResidualConv();
       if(!ffn ||
          !ffn.Init(class="num">0, lay_count, OpenCL, window, window, units_count, optimization, iBatch) ||
          !cLayers.Add(ffn))
          {
            class="kw">delete ffn;
            class="kw">return false;
          }
       lay_count++;
     }
  CNeuronConvSAMOCL *conv = new CNeuronConvSAMOCL();
  if(!conv ||
     !conv.Init(class="num">0, lay_count, OpenCL, window, window, window, units_count, class="num">1,
optimization, iBatch) ||
     !cLayers.Add(conv))
     {
       class="kw">delete conv;
       class="kw">return false;
     }
  SetActivationFunction(SIGMOID);
class=class="str">"cmt">//---
  if(!SetOutput(conv.getOutput(), true) ||
     !SetGradient(conv.getGradient(), true))
       class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronControlAgent::feedForward(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput)
  {
  if(!SecondInput)
     class="kw">return false;
  CNeuronBaseOCL *second = cLayers[class="num">0];
  if(!second)
     class="kw">return false;

◍ 控制代理的前向推演与梯度回传骨架

这段 CNeuronControlAgent 的两个方法暴露了多层神经网络在 MT5 端的运转顺序:先正向把各层输出串起来,再反向把梯度从末层算回输入层。外汇与贵金属行情噪声大,用这类结构做信号模型时回测过拟合概率偏高,属高风险尝试。 前向部分从第二层开始接管,先给 second 层喂入 SecondInput 并做 SetOutput,失败直接 return false;随后循环 i=2 到 cLayers.Total(),每一层 main 都依赖前一层 first 与 second 的输出做 FeedForward,first 在每次迭代后被替换为当前 main。 反向的 calcInputGradients 更讲究:先把 SecondGradient 塞给第 0 层并设激活函数,再从倒数第二层往回走到索引 2。若下一层是 defNeuronRelativeCrossAttention 类型,就走 calcHiddenGradients 加 SumAndNormilize 的跨注意力路径,否则走普通隐藏层梯度;最后第 0 层拿 second 的对象补一次梯度。 在 MT5 里把这段直接贴进自定义神经层类,改 cLayers 的层数跑一次前反向,能验证你自己的注意力层是否真的参与了梯度汇总。

MQL5 / C++
  if(!second.SetOutput(SecondInput, true))
        class="kw">return false;
  second = cLayers[class="num">1];
  if(!second || !second.FeedForward(cLayers[class="num">0]))
      class="kw">return false;
  CNeuronBaseOCL *first = NeuronOCL;
  CNeuronBaseOCL *main = NULL;
  for(class="type">int i = class="num">2; i < cLayers.Total(); i++)
    {
      main = cLayers[i];
      if(!main ||
         !main.FeedForward(first, second.getOutput()))
         class="kw">return false;
      first = main;
    }
class=class="str">"cmt">//---
  class="kw">return true;
  }
class="type">bool CNeuronControlAgent::calcInputGradients(CNeuronBaseOCL *NeuronOCL, CBufferFloat *SecondInput,
CBufferFloat *SecondGradient, ENUM_ACTIVATION SecondActivation = -class="num">1)
  {
  if(!NeuronOCL || !SecondGradient)
      class="kw">return false;
  CNeuronBaseOCL *main = cLayers[class="num">0];
  if(!main)
      class="kw">return false;
  if(!main.SetGradient(SecondGradient, true))
      class="kw">return false;
  main.SetActivationFunction(SecondActivation);
class=class="str">"cmt">//---
  CNeuronBaseOCL *second = cLayers[class="num">1];
  if(!second)
      class="kw">return false;
  second.SetActivationFunction(SecondActivation);
  CBufferFloat *second_out = second.getOutput();
  CBufferFloat *second_gr = second.getGradient();
  CBufferFloat *temp = second.getPrevOutput();
  if(!second_gr.Fill(class="num">0))
      class="kw">return false;
  for(class="type">int i = cLayers.Total() - class="num">2; i >= class="num">2; i--)
    {
      main = cLayers[i];
      if(!main)
         class="kw">return false;
      if(cLayers[i + class="num">1].Type() == defNeuronRelativeCrossAttention)
        {
         if(!main.calcHiddenGradients(cLayers[i + class="num">1], second_out, temp, SecondActivation) ||
            !SumAndNormilize(temp, second_gr, second_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
            class="kw">return false;
        }
      else
        {
         if(!main.calcHiddenGradients(cLayers[i + class="num">1]))
            class="kw">return false;
        }
    }
  if(!NeuronOCL.calcHiddenGradients(main.AsObject(), second_out, temp, SecondActivation))
      class="kw">return false;
  if(main.Type() == defNeuronRelativeCrossAttention)
    {
     if(!SumAndNormilize(temp, second_gr, second_gr, class="num">1, false, class="num">0, class="num">0, class="num">0, class="num">1))
        class="kw">return false;
    }
  main = cLayers[class="num">0];
  if(!main.calcHiddenGradients(second.AsObject()))
      class="kw">return false;
class=class="str">"cmt">//---
  class="kw">return true;
  }

「一点提醒」

前面几节把 MASA 框架拆成了收益优化、风险最小化、趋势观察三个独立智代,并各自给出了 MQL5 模块:Research.mq5 与 ResearchRealORL.mq5 负责采样,Study.mq5 训练,Test.mq5 回测,Trajectory.mqh / NeuroNet.mqh / NeuroNet.cl 提供状态结构与网络底层。下一步是把它们接进同一套系统跑真实历史,这一步没做之前,任何单模块结论都只是组件级验证。 外汇与贵金属市场高杠杆、高波动,多智代框架即便回测漂亮,实盘也可能因滑点、断连、过拟合而失效。开 MT5 把 MQL5.zip(约 2.2 MB)里的 Test.mq5 先单独跑一遍,确认你本地环境能复现文中所说采样与训练流程,再谈集成。 下一篇若真把系统拼起来,重点看的是各智代在实数据下的耦合损耗,而不是单个 RL 收益曲线。这点搞清楚,才不会被‘框架创新’的叙事带偏。

常见问题

按输入特征维度做带轻微噪声的随机初始化,再接一层归一化,避免初期梯度被极端值冲垮。
通常拆成智能体类、环境交互类和策略网络类,初始化时先绑维度再载历史缓冲。
可以,小布能按你给的品种和周期自动装配注意力层并输出推演诊断,你只管看信号强弱。
一路是市场观测编码,一路是持仓状态向量,两者拼接后过共享隐层再出动作概率。
偏大,贵金属和高波动外汇品种建议先堆 2 层试,层数多了回测易漂亮实盘易崩。