您应当知道的 MQL5 向导技术(第 10 部分):非常规 RBM(基础篇)
用向导拼一个非常规 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 存最终激活。训练充分后,第一与第二隐藏层双精度数值即可捕获行情输入的概率分布属性,供后续规范化使用。外汇与贵金属市场波动剧烈、杠杆风险高,此类特征提取仅作分析参考,不预示方向。
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 跑同一段欧元分时,隐藏层激活稀疏度可能明显变化,值得自己验证。
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 跑通数值稳定性检查。
{ 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];
}
}