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

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

第 2/2 篇

RBM 为什么从能量差里长出来

把 RBM 看成基于能量的模型,核心就一句话:给数据集的每一种配置算一个标量能量,训练就是改能量函数,让“理想配置”能量低。这里的能量不是物理单位,而是输入数据经过一次变换再还原后,和原始输入之间的差值——差值越小,能量越低,模型越认可这种配置。 普通玻尔兹曼机所有神经元全连接、还没隐藏层,梯度难算到爆炸。RBM 把它受限成“可见层只连隐藏层、同层不连”,才让马尔可夫链蒙特卡罗采样有了落地可能。训练时在负对数似然上做随机梯度下降,梯度分正相和负相:正相压低训练数据的自由能量(提概率),负相降低模型自己生成的样本被当真的可能性。 对外汇和贵金属这种高噪声、高风险的序列,直接拿 RBM 当信号源不现实;但它这种“用隐藏变量补未知层面”的思路,值得在特征降维时验证——MT5 里接 Python 跑个两隐藏层的 RBM,看重构误差能不能稳在 0.05 以下,比盲信指标更有用。

◍ 三层 RBM 的节点配置与无监督训练落点

这套网络按 3 层结构搭:输入层和输出层各 5 个节点,隐藏层 3 个节点。输入取 5 个指标读数——移动平均线、MACD、随机震荡、威廉姆斯百分比范围、相对活力指数;你有权替换来源,但本文固定用这 5 个。常规化输出会同时捕获第一和第二隐藏层的值,所以输出向量长度 = 隐藏层大小 ×2,也就是 6。 权重乘积和激活全交给 ALGLIB 类处理,单个神经元可定制,本文先用默认参数跑通流程。等深入调参时再动不迟,现阶段目标是把数据概率分布抽出来看。反向传播走的是多元链式规则梯度下降,和我们做的对比离散度不是一回事:前者计算更密,得到的权重分布也可能和常规 RBM 差很远。 训练时网络把权重往「输出逼近输入」的方向调。因为输入即目标,这个改过的 RBM 仍算无监督。新数据进来后,隐藏层吐出的数组长度是 6(3 节点 ×2 层)。如果你喂 8 个指标、隐藏层仍留 3 节点,就发生了降维;本文 5 进 6 出,不算降维,只是把 5 个指标值压成一种常规化表达。 这 6 个值可当分类向量直接用。若在每组指标后补上「价格最终变化」做监督,就能拿余弦相似性当权重,去比对未知向量与已知变化向量的加权均值,对下一步变化给出倾向性预测。开 MT5 把隐藏层节点改到 3、输出数组打印出来,验证长度是否为 6 即可。

「把受限玻尔兹曼机塞进 MT5 类里」

这套非常规 RBM 用 MQL5 的 class 封装了感知器最小接口,比前一篇多了个 losses 双精度数组,专门记录输入层与输出层每根柱线、每一数据行的余弦相似性。它的用途很直接:跑测试时,如果当前低于阈值的余弦相似度比上次写文件时更小,就把网络权重导出,方便后续 EA 上生产环境直接加载,不必每次从随机权重重训。 阈值参数叫 loss,默认 0.9,可手动调。权重和输入相乘没必要自己写复杂循环,MQL5 已内嵌 matrixvector 类型,直接乘矩阵省内存也少占计算资源。 网络训练走 ALGLIB 类,开箱即用但代码偏重、场景泛化导致略显晦涩;自己硬编码感知器在测试与部署时效率更高。激活函数和乖离度这类基础定制对性能影响可能很显著,初始探索阶段用 ALGLIB 足够了。 每来一根新 H4 柱或新价格点就重训一次,输入分类权重随验算细化。也可改成季调或半年调,前提是先拿几年数据预训——原文没做,但路子是通的。 用 GBPUSD 的 H4 数据跑了实证:2023.07.01–2023.10.01 优化信号类,2023.10.01–2023.12.25 前向演练,报告看著有前途。外汇高风险,可靠结论应基于更长历史与经纪商真实跳价,纯 ALGLIB 源跑长历史可能是个挑战。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class Crbm
{
class="kw">protected:
   class="type">int               visible;   class=class="str">"cmt">// 可见层节点数:输入特征维度
   class="type">int               hidden;    class=class="str">"cmt">// 隐藏层节点数:提取的隐含特征数
   class="type">int               sample;    class=class="str">"cmt">// 采样次数:每次训练迭代的采样轮数
   class="type">class="kw">double            loss;      class=class="str">"cmt">// 余弦相似度阈值,默认0.class="num">9,低于才考虑写权重
   class="type">class="kw">string            file;      class=class="str">"cmt">// 权重存档文件名
class="kw">public:
   class="type">bool              init;      class=class="str">"cmt">// 初始化标志
...
   CArrayDouble      losses;    class=class="str">"cmt">// 记录每根柱线输入输出余弦差异的动态数组
   class="type">void              Train(matrix &W);      class=class="str">"cmt">// 训练函数,W为权重矩阵引用
   class="type">void              Process(matrix &W, matrix &XY, class="type">bool Compare = false); class=class="str">"cmt">// 处理/预测,XY为数据矩阵
   class="type">bool              ReadWeights();  class=class="str">"cmt">// 从文件读权重
   class="type">bool              WriteWeights(); class=class="str">"cmt">// 写权重到文件
   class="type">bool              Writer();      class=class="str">"cmt">// 通用写盘封装
   Crbm(class="type">int Visible, class="type">int Hidden, class="type">int Sample, class="type">class="kw">double Loss, class="type">class="kw">string File); class=class="str">"cmt">// 构造:传节点数、采样、阈值、文件名
   ~Crbm();         class=class="str">"cmt">// 析构
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Train Data Matrix                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Crbm::Train(matrix &W)
{  for(class="type">int s = class="num">0; s < sample; s++)

RBM 新版前向推理与信号取数

上面这段把训练完的受限玻尔兹曼机拿去跑新样本,核心在 Crbm::Process:先按行取权重矩阵 W 的每一行当输入向量,调 base.MLPProcess 得到隐层输出 _y,再把模型神经元下标 6 到 visible+6 这一段拷进 XY 矩阵。注意这里输入维度是 visible,输出只截 7 列,和 GetOutput 里 _xy.Init(m_sample,7) 完全对得上。 对比模式 Compare=true 时会逐维打印输入与输出,并用 LOSS_COSINE 算余弦损失。实盘里建议先开 Compare 跑几百根历史 K 线,看 losses 数组的余弦损失是否稳定在 0.1 以下,再决定要不要接信号。外汇与贵金属波动剧烈,模型偏差放大时亏损概率显著上升,任何输出只作概率参考。 CSignalRBM::GetOutput 负责把实时指标刷进矩阵:m_close、MA、MACD、STOCH、WPR、RVI 全部 Refresh(-1) 取最新值,再判 RBM.init 避免未训练就调用。想验证就直接把这段挂到 MT5 脚本里,打印 _w 和 _xy 的维度,确认和你的样本数 m_sample、可见层 __VISIBLE 一致。

MQL5 / C++
  {  for(class="type">int i = class="num">0; i < visible; i++)
      {  xy.Set(s, i, W[s][i]);
         xy.Set(s, i + visible, W[s][i]);
      }
   }
   train.MLPTrainLM(model, xy, sample, class="num">0.001, class="num">2, info, report);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  Process New Vector                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Crbm::Process(matrix &W, matrix &XY, class="type">bool Compare = false)
{  for(class="type">int w = class="num">0; w < class="type">int(W.Rows()); w++)
   {  CRowDouble _x = CRowDouble(W.Row(w)), _y;
      base.MLPProcess(model, _x, _y);
      for(class="type">int i = class="num">6; i < visible + class="num">7; i++)
      {  XY[w][i - class="num">6] = model.m_neurons[i];
      }
      class=class="str">"cmt">//Comparison
      vector _input = _x.ToVector();
      vector _output = _y.ToVector();
      if(Compare)
      {  for(class="type">int i = class="num">0; i < class="type">int(_input.Size()); i++)
         {  printf(__FUNCSIG__ +
                  " at: " + IntegerToString(i) +
                  " we&class="macro">#x27;ve class="kw">input: " + DoubleToString(_input[i]) +
                  " & y: " + DoubleToString(_y[i])
            );
         }
         class=class="str">"cmt">//Loss
         printf(__FUNCSIG__ +
                " loss is: " + DoubleToString(_output.Loss(_input, LOSS_COSINE))
            );
      }
      losses.Add(_output.Loss(_input, LOSS_COSINE));
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| RBM Output.                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignalRBM::GetOutput(class="type">void)
   {
      m_close.Refresh(-class="num">1);
      
      MA.Refresh(-class="num">1);
      MACD.Refresh(-class="num">1);
      STOCH.Refresh(-class="num">1);
      WPR.Refresh(-class="num">1);
      RVI.Refresh(-class="num">1);
      
      class="type">class="kw">double _output = class="num">0.0;
      
      class="type">int _i=StartIndex();
      
      matrix _w; _w.Init(m_sample,__VISIBLE);
      
      matrix _xy; _xy.Init(m_sample,class="num">7);
      
      if(RBM.init)
      {

◍ 把多指标快照喂进受限玻尔兹曼机

这段代码干的事很直接:用双重循环把每个样本时刻的 5 个指标值塞进矩阵 _w,再算相邻收盘价差当作监督标签。指标槽位是硬编码的——i==0 取 MA,i==1 取 MACD,i==2 取 WPR,i==3 取 STOCH,i==4 取 RVI,顺序一动后面全错。 样本数由 m_sample 控制,循环里 s 从 0 到 m_sample-1;当 s>0 时才填标签 _xy[s][2*__HIDDEN],等于当前收盘减前一收盘,所以第 0 个样本没有价格变动标签。 喂完数据后 RBM.Train(_w) 先训模型,RBM.Process(_w,_xy) 再做推断。重点在加权融合那段:取第 0 行当基准向量 _x0,对 s>=1 的每行算余弦损失的绝对值当权重,权重乘标签值 _xy[s][6] 累加,最后除以权重和得到 _output。 外汇和贵金属市场波动剧烈、杠杆风险高,这套加权输出只是概率倾向,实盘前务必在 MT5 策略测试器里把 m_sample 从默认改到 200~500 跑一遍,看 _output 分布是否稳定。

MQL5 / C++
for(class="type">int s=class="num">0;s<m_sample;s++)
  {
    for(class="type">int i=class="num">0;i<class="num">5;i++)
    {
      if(i==class="num">0){ _w[s][i] = MA.GetData(class="num">0,_i+s); }
      else if(i==class="num">1){ _w[s][i] = MACD.GetData(class="num">0,_i+s); }
      else if(i==class="num">2){ _w[s][i] = WPR.GetData(class="num">0,_i+s); }
      else if(i==class="num">3){ _w[s][i] = STOCH.GetData(class="num">0,_i+s); }
      else if(i==class="num">4){ _w[s][i] = RVI.GetData(class="num">0,_i+s); }
    }
    
    if(s>class="num">0){ _xy[s][class="num">2*__HIDDEN] = m_close.GetData(_i+s)-m_close.GetData(_i+s+class="num">1); }
  }
  
  RBM.Train(_w);
  RBM.Process(_w,_xy);
  
  class="type">class="kw">double _w=class="num">0.0,_w_sum=class="num">0.0;
  
  vector _x0=_xy.Row(class="num">0); _x0.Resize(class="num">6);
  
  for(class="type">int s=class="num">1;s<m_sample;s++)
  {
    vector _x=_xy.Row(s); _x.Resize(class="num">6);
    class="type">class="kw">double _weight=fabs(class="num">1.0+_x.Loss(_x0,LOSS_COSINE));
    
    _w+=(_weight*_xy[s][class="num">6]);
    _w_sum+=_weight;
  }
  
  if(_w_sum>class="num">0.0){ _w/=_w_sum; }
  
  _output=_w;
  }
  
  class="kw">return(_output);
}

「把工具请下神坛」

RBM 变体在 MQL5 里被我们拆成了带输出权重的概率分布,后向与前向测试都指向同一个事实:它更像一枚需要调参的零件,而非开箱即赢的圣杯。随附的 Expert.mq5 与 RBM_r4--.mqh 等文件,和向导组装后即可跑,但外汇与贵金属的高杠杆属性意味着任何信号都只是概率倾斜。 前文已演示过向导装配流程,新手照着做一遍就能在 MT5 策略测试器里看见 6.5 KB 的 EA 跑出回测曲线。真正要花时间的,是对数据输入做筛选——原文提到这可能要更广泛的测试与微调,而非直接实盘。 写这套的人来自乌干达,文章归个人观点,平台不背书准确性。你拿去验证时,记住它只是把监督学习的壳套在受限玻尔兹曼机的简化版上,别神话。

常见问题

能量差反映可见层与隐藏层重构的偏差,偏差越大说明当前市场状态越偏离历史常态,可据此判断异动概率,需在图表上观察偏差阈值。
常见配置为输入层按指标数、隐藏层逐层减半(如 12→6→3),无监督训练落点在各层权重矩阵,可用小样本快照先跑通再扩样本。
小布盯盘的 AIGC 已内置这类非常规模型诊断,打开对应品种页即可看到能量差与重构偏差的实时标注,不用自己写类。
需做缺失值填充与归一化到 0~1,快照长度固定对齐时间轴,否则前向推理会取空值,建议先打印维度核对。
将重构误差映射为强度条,超过历史 90 分位倾向视为关注区,外汇贵金属波动高风险,仅作概率参考不代替止损。