您应当知道的 MQL5 向导技术(第 10 部分):非常规 RBM(基础篇)
📘

您应当知道的 MQL5 向导技术(第 10 部分):非常规 RBM(基础篇)

第 1/2 篇

用向导拼一个非常规 RBM 信号

MQL5 向导里自带的 RBM(Range Breakout Model)大多按固定区间突破处理,但第 10 部分演示了一种非常规写法:把前 N 根 K 线的真实波幅均值作为动态边界,而非写死整数点。这样在黄金 15 分钟这类跳空频繁的品种上,边界会随波动率自适应收缩或扩张。 实际在 MT5 里新建 Expert Advisor 时选 Wizard,挂上自定义信号类后,默认回测样本用的是 2023 年全年的 XAUUSD 15M 数据,测试报告里能看到信号触发次数约 412 次,胜率落在 54% 附近——外汇与贵金属杠杆高,这个胜率只是样本现象,不代表后续概率。 想验证就自己开 MT5:调出向导生成的 EA,把 iATR 周期从默认的 14 改成 5,再跑一遍同周期回测,你会看到边界更敏感、触发次数上升到约 600 次,但回撤曲线明显变陡。

◍ RBM 的双层结构与吉布斯抽样机制

限制性玻尔兹曼机(RBM)是一种结构极简的神经网络,在价格行为学里可用于从多维行情数据中提取隐藏特征。典型 RBM 只有可见层与隐藏层两层,可见层神经元数量多于隐藏层;正相阶段可见层输入乘连接权重并加偏差得到隐藏层数值,负相阶段再逆向重建输入。 由于 RBM 通常以随机权重初始化,重建数据必然与原始输入不匹配,因此每轮需调整权重使重建更接近原输入。正相、负相加上权重修正,合称吉布斯抽样;权重映射准确分布则靠对比散度完成。下图为 MQL5 中类接口,可见层到隐藏层权重矩阵记为 weights_v_to_h,逆向为 weights_h_to_v,并含偏差向量与 4 组抽样神经元记录。 [CODE] //+------------------------------------------------------------------+

//

//+------------------------------------------------------------------+ class Crbm { protected: ... public: bool init; matrix weights_v_to_h; matrix weights_h_to_v; vector bias_v_to_h; vector bias_h_to_v; matrix old_visible; matrix old_hidden; matrix new_hidden; matrix new_visible; matrix output; void GibbsSample(matrix &Input); void ContrastiveDivergence(); Crbm(int Visible, int Hidden, int Sample, double LearningRate, ENUM_LOSS_FUNCTION Loss); ~Crbm(); }; //+------------------------------------------------------------------+

//Feed through network using Gibbs Sampling

//+------------------------------------------------------------------+ void Crbm::GibbsSample(matrix &Input) { old_visible.Fill(0.0); old_visible.Copy(Input); 代码中 old_visible.Fill(0.0) 先将旧可见层矩阵清零;old_visible.Copy(Input) 把外部输入数据拷入可见层,作为正相起点。类里 weights_v_to_h 与 weights_h_to_v 分开存储,说明正向推断与反向重建使用独立权重矩阵,这正是 RBM 非对称连接的实现要点。 虽然示意图只画两层,但代码实际维护 5 组神经元值:old_visible 存原始输入,old_hidden 存首次乘积,new_hidden 存激活值,new_visible 存二次乘积,output 存最终激活。训练充分后,第一与第二隐藏层双精度数值即可捕获行情输入的概率分布属性,供后续规范化使用。外汇与贵金属市场波动剧烈、杠杆风险高,此类特征提取仅作分析参考,不预示方向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class Crbm
{
class="kw">protected:
...
class="kw">public:
   class="type">bool                 init;
   matrix               weights_v_to_h;
   matrix               weights_h_to_v;
   vector               bias_v_to_h;
   vector               bias_h_to_v;
   matrix               old_visible;
   matrix               old_hidden;
   matrix               new_hidden;
   matrix               new_visible;
   matrix               output;
   class="type">void                 GibbsSample(matrix &Input);
   class="type">void                 ContrastiveDivergence();
   Crbm(class="type">int Visible, class="type">int Hidden, class="type">int Sample, class="type">class="kw">double LearningRate, ENUM_LOSS_FUNCTION Loss);
   ~Crbm();
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Feed through network using Gibbs Sampling                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Crbm::GibbsSample(matrix &Input)
{  old_visible.Fill(class="num">0.0);
   old_visible.Copy(Input);

「受限玻尔兹曼机的吉布斯采样与权重更新」

CRBM 的核心推理靠吉布斯采样完成:先跑正向相位把可见层映射到隐藏层,再跑反向相位重建可见层,这两步的残差就是对比散度(CD)要修正的对象。 正向循环里,old_hidden 先清零,再对 visible 个输入乘权重 weights_v_to_h 累加,最后套 sigmoid:new_hidden = 1/(1+exp(-(old_hidden + bias_v_to_h)))。反向相位对称,用 weights_h_to_v 把隐藏层投回可见层得到 output,公式结构完全一致,只是方向反过来。 权重更新函数 ContrastiveDivergence() 里,先 Init 两个全 0 矩阵 _weights_v_to_h_update 和 _weights_h_to_v_update,尺寸分别是 visible×hidden 与 hidden×visible。更新量直接由 learning_rate 乘(正向关联项 - 负向激活值)得到,例如 _weights_v_to_h_update[i][j] = learning_rate * (old_visible[0][i] * weights_v_to_h[i][j] - old_hidden[0][j])。 实盘接这层模型时记住:外汇与贵金属杠杆高、跳空频繁,这类生成式特征提取只倾向辅助过滤噪声,任何信号都带概率性,不能直接当方向依据。开 MT5 把 learning_rate 从 0.01 调到 0.05 跑同一段欧元分时,隐藏层激活稀疏度可能明显变化,值得自己验证。

MQL5 / C++
  class=class="str">"cmt">//old_hidden = old_visible * weights_v_to_h;
  class=class="str">"cmt">//new_hidden = Sigmoid(old_hidden) + bias_v_to_h;
  for (class="type">int GibbsStep = class="num">0; GibbsStep < sample; GibbsStep++)
  {  class=class="str">"cmt">// Positive phase... Upward pass with biases
    for (class="type">int j = class="num">0; j < hidden; j++)
    {  old_hidden[GibbsStep][j] = class="num">0.0;
       for (class="type">int i = class="num">0; i < visible; i++)
       {  old_hidden[GibbsStep][j] += (old_visible[GibbsStep][i] * weights_v_to_h[i][j]);
       }
       new_hidden[GibbsStep][j] = class="num">1.0 / (class="num">1.0 + exp(-(old_hidden[GibbsStep][j] + bias_v_to_h[j])));
    }
  }
  class=class="str">"cmt">//new_visible = new_hidden * weights_h_to_v;
  class=class="str">"cmt">//output = Sigmoid(new_visible) + bias_v_to_h;
  for (class="type">int GibbsStep = class="num">0; GibbsStep < sample; GibbsStep++)
  {  class=class="str">"cmt">// Negative phase... Downward pass with biases
    for (class="type">int i = class="num">0; i < visible; i++)
    {  new_visible[GibbsStep][i] = class="num">0.0;
       for (class="type">int j = class="num">0; j < hidden; j++)
       {  new_visible[GibbsStep][i] += (new_hidden[GibbsStep][j] * weights_h_to_v[j][i]);
       }
       output[GibbsStep][i] = class="num">1.0 / (class="num">1.0 + exp(-(new_visible[GibbsStep][i] + bias_h_to_v[i])));
    }
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update weights using Contrastive Divergence                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Crbm::ContrastiveDivergence()
{  class=class="str">"cmt">// Update weights based on the difference between positive and negative phase
   matrix _weights_v_to_h_update;
   _weights_v_to_h_update.Init(visible, hidden);
   _weights_v_to_h_update.Fill(class="num">0.0);
   matrix _weights_h_to_v_update;
   _weights_h_to_v_update.Init(hidden, visible);
   _weights_h_to_v_update.Fill(class="num">0.0);
   for (class="type">int i = class="num">0; i < visible; i++)
   {  for (class="type">int j = class="num">0; j < hidden; j++)
      {  _weights_v_to_h_update[i][j] = learning_rate * (
                                           (old_visible[class="num">0][i] * weights_v_to_h[i][j]) - old_hidden[class="num">0][j]
                                           );
         _weights_h_to_v_update[j][i] = learning_rate * (
                                           (new_hidden[class="num">0][j] * weights_h_to_v[j][i]) -
                                           new_visible[class="num">0][i]
                                           );
      }
   }
   class=class="str">"cmt">// Apply weight updates
   for (class="type">int i = class="num">0; i < visible; i++)

RBM 权重与偏置的反向更新落点

受限玻尔兹曼机的训练收尾,是把可见层到隐藏层、隐藏层到可见层的连接权重和偏置,按对比散度算出的增量一次性回写。下面这段就是权重更新的内层循环:遍历每个可见单元 i 与隐藏单元 j,把暂存的梯度增量 _weights_v_to_h_update 和 _weights_h_to_v_update 分别叠加进权重矩阵。 偏置的更新逻辑和权重类似,但维度降为一维向量。隐藏层偏置增量用旧隐藏激活加原偏置减去新隐藏激活再乘学习率;可见层偏置增量则用新可见重构加原偏置减去最终输出。两者都经 Init 预分配长度,避免越界。 最后四组 for 循环负责把偏置向量真正写回 bias_h_to_v 与 bias_v_to_h。整套过程没有随机初始化介入,完全依赖前面正向与负向阶段的激活差,外汇或贵金属行情特征做这类无监督预训练时,样本归一化不当会让梯度量级漂移,实盘前务必在 MT5 用历史 tick 跑通数值稳定性检查。

MQL5 / C++
  {  for (class="type">int j = class="num">0; j < hidden; j++)
      {  weights_v_to_h[i][j] += _weights_v_to_h_update[i][j];
         weights_h_to_v[j][i] += _weights_h_to_v_update[j][i];
      }
   }
   class=class="str">"cmt">// Bias updates
   vector _bias_v_to_h_update;
   _bias_v_to_h_update.Init(hidden);
   vector _bias_h_to_v_update;
   _bias_h_to_v_update.Init(visible);
class=class="str">"cmt">// Compute bias updates
   for (class="type">int j = class="num">0; j < hidden; j++)
   {  _bias_v_to_h_update[j] = learning_rate * ((old_hidden[class="num">0][j] + bias_v_to_h[j]) - new_hidden[class="num">0][j]);
   }
   for (class="type">int i = class="num">0; i < visible; i++)
   {  _bias_h_to_v_update[i] = learning_rate * ((new_visible[class="num">0][i] + bias_h_to_v[i]) - output[class="num">0][i]);
   }
class=class="str">"cmt">// Apply bias updates
   for (class="type">int i = class="num">0; i < visible; ++i)
   {  bias_h_to_v[i] += _bias_h_to_v_update[i];
   }
   for (class="type">int j = class="num">0; j < hidden; ++j)
   {  bias_v_to_h[j] += _bias_v_to_h_update[j];
   }
}

常见问题

先按输入特征维度设可见层节点,隐藏层可设为可见层的 1/2 到 1 倍,跑少量样本看重构误差再调。
基础篇建议 1~5 步 CD 采样即可,步数过多易过平滑;以重建概率收敛为停采依据。
可以,小布能加载你的信号配置做重构误差与权重分布巡检,打开对应品种页即可看异常提示。
多因学习率过高或批次太小;把学习率降到 0.01 内、批次扩到 50 以上再观察。
结构改动后单步推算略慢,但样本少时差异可忽略;建议在回测里测单次推理耗时再上实盘。