您应当知道的 MQL5 向导技术(第 57 部分):搭配移动平均和随机振荡器的监督训练(基础篇)
📘

您应当知道的 MQL5 向导技术(第 57 部分):搭配移动平均和随机振荡器的监督训练(基础篇)

第 1/3 篇

用向导拼一个均线与随机共振模型

MQL5 向导从 build 2875 起支持把监督训练直接挂进 EA 生成流程,这次演示的组合是移动平均(MA)做趋势门限、随机振荡器(Stochastic)做入场触发。两者在向导里以独立信号模块加载,训练样本默认取当前品种最近 5000 根Bar。 这种搭配的逻辑在于:MA 斜率过滤掉逆势噪声,Stochastic 的 %K 上穿 %D 且处在超卖区才允许多单信号。外汇与贵金属杠杆高,信号共振不代表胜率保证,历史样本过拟合概率偏大,需上 MT5 用不同品种重跑。 打开 MT5 的「文件→新建→智能交易系统(向导)」,勾选 Moving Average 与 Stochastic 两个信号,把训练模式设为 Supervised,便能直接编译出可回测的 ea。先拿 EURUSD 的 H1 跑一遍 2024 全年,观察 equity 曲线是否单调塌陷。

◍ 向导智能系统只是试炼场

MQL 向导拼出来的智能系统,核心价值是快速验证想法,而不是拿去实盘硬扛。手工写的 EA 要熬过长期测试才敢上真实账户,向导版代码量少、能立刻跑,适合先把创意跑通。 本篇把机器学习当背景板,主线仍是成熟指标形态。三篇文章拆三个学习分支,开篇只盯监督学习:用趋势类的移动平均线和动量类的随机指标配对,各塞进独立神经网络。 Python 负责编码和训练效率更高,交叉验证也好做;但光比测试亏损和训练损耗还不够看权重偏差。所以我们把导出的 ONNX 网络丢进 MT5 策略测试器跑前向漫游。 数据起点是 EURJPY 的 2023 年日线,从 MT5 发到 Python 训,前向测试换 2024 年同品种日线。MA 加随机振荡器能变出多种信号,本篇只抠出交易者能用的前 10 个顶级形态做探索。外汇贵金属波动剧烈,这类形态仅作概率参考,实盘前务必自测。

「监督、强化与推断三段式怎么接上行情」

把机器学习流水线拆开看,其实是三个前后咬合的阶段:监督负责从历史标注数据里学形态,强化让模型在动态环境里靠反馈调决策,推断把前两步的产出搬到没见过的数据或品种上用。对外汇、贵金属这类高波动序列,这三段若断档,模型很容易在实盘里失效。 监督阶段的核心是最小化预测与实际的差。以金融时间序列为例,输入是历史价格加特征工程得到的指标,模型常用 LSTM,损失函数取 MSE,跑完得到一个能按过往观察推下一根 K 线的权重。模型选择和超参数调谐(学习率、批次规模、激活类型、层尺寸)要在测试集交叉验证后再定,不然只是样本内自嗨。 强化阶段解决的是「训好了但活环境变了怎么办」。把监督网络输出的价格变化当状态,买入/卖出/中立当动作,评论者网络按预期盈亏给长期奖励,策略网络在探索与利用间找平衡。难点在真实数据流太慢——若监督吃了 10 年历史,强化纸面跑 1 年就得耗 1 年,所以得用历史数据模拟实时条件提速,否则贵金属夜盘跳空这类转移根本覆盖不到。 推断阶段计算负载最轻,自编码器、聚类和图形网络在这儿挑大梁,不用标注就能抓未见形态。把前两步模型直接切到另一个交易品种,就是典型的跨域普适。结合移动平均线与随机振荡器生成的信号变体,我们验证了前 10 个形态供手动备用,但这只是特征组合的起点,真要进强化还得先过监督这关。

均线交叉叠随机极值怎么喂给网络

双线 MA 金叉叠加 Stochastic 低于 20,是偏顺势反转的买入设定;死叉叠加 Stochastic 高于 80,则倾向给出偏强的卖出信号。这类组合把趋势跟随和动量超买超卖拧在一起,比单指标噪点少一些,但外汇与贵金属波动跳空多,仍属高风险配置,实盘前务必在 MT5 用历史数据自验。 把上面这套逻辑接进 ONNX 网络时,作者用 57_X.mqh 里的 Get 函数把 MA 快慢线、Stochastic K 值抽成 vectorf 直接送模型。形态-0 里总共 4 个布尔条件(涨/跌各 2 个,分别来自两个指标),镜像拆分后最多同时命中 2 个,向量化后网络能吃到全部组合。 Python 侧网络很直白:输入 feature_size 进两层 256 宽 sigmoid 隐藏层,输出 state_size 落在 0~1。预测值低于 0.5 解读为负面变化,高于 0.5 为正面,等于 0.5 算持平。训练用 2023 全年数据,前向漫游只跑多头,训练权重取 2023 年 80% 时段,再铺到整个 2024 年测,报告里测试亏损低于首局初始亏损但仍高于第十局——说明过拟合同样可能发生。 下面这段 MQL5 与 PyTorch 混合代码就是形态判定和网路结构的落地,注意 IsPattern 里把 _y[0] 做 2*(_y[0]-0.5) 的偏移,只为把多头信号强度从 0.5~1 拉伸到 0~1 区间。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern.                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double CSignal_MA_STO::IsPattern(class="type">int Index, class="type">ENUM_POSITION_TYPE T)
{  vectorf _x = Get(Index, m_time.GetData(X()), m_close, m_ma, m_ma_lag, m_sto);
   vectorf _y(class="num">1);
   _y.Fill(class="num">0.0);
   ResetLastError();
   if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, _x, _y))
   {  printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError());
      class="kw">return(class="type">class="kw">double(_y[class="num">0]));
   }
   class=class="str">"cmt">//printf(__FUNCSIG__+" y: "+DoubleToString(_y[class="num">0],class="num">2));
   if(T == POSITION_TYPE_BUY && _y[class="num">0] > class="num">0.5f)
   {  _y[class="num">0] = class="num">2.0f * (_y[class="num">0] - class="num">0.5f);
   }
   else if(T == POSITION_TYPE_SELL && _y[class="num">0] < class="num">0.5f)
   {  _y[class="num">0] = class="num">2.0f * (class="num">0.5f - _y[class="num">0]);
   }
   class="kw">return(class="type">class="kw">double(_y[class="num">0]));
}
class SimpleNeuralNetwork(nn.Module):
    def __init__(self):
        super(SimpleNeuralNetwork, self).__init__()
        self.fc1 = nn.Linear(feature_size, class="num">256)   # Input layer to hidden layer class="num">1
        self.fc2 = nn.Linear(class="num">256, class="num">256) # Hidden layer class="num">1 to hidden layer class="num">2
        self.fc3 = nn.Linear(class="num">256, state_size)   # Hidden layer class="num">2 to output layer
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        x = self.sigmoid(self.fc1(x))   # Activation for hidden layer class="num">1
        x = self.sigmoid(self.fc2(x))   # Activation for hidden layer class="num">2
        x = self.fc3(x)                 # Output layer(no activation)
        class="kw">return x
if(Index == class="num">0)
{  if(CopyBuffer(M.Handle(), class="num">0, T, class="num">2, _ma) >= class="num">2 && CopyBuffer(M_LAG.Handle(), class="num">0, T, class="num">2, _ma_lag) >= class="num">2 && CopyBuffer(S.Handle(), class="num">0, T, class="num">1, _sto_k) >= class="num">1)

◍ 把均线与随机指标压成 0/1 特征向量

这段逻辑把两条技术线索转成布尔化的浮点特征,方便后续模型直接吃数。_v[0] 抓快线由下穿上:前一根 lag 均线大于当期均线、且当前 lag 均线小于当期均线时置 1.0,否则 0.0;_v[3] 正好反过来,捕捉死叉。 _sto_k 的上下极值被硬切成两个通道:_v[1] 在 K 值 ≤20.0 时记 1.0,视为超卖候选;_v[2] 在 K 值 ≥80.0 时记 1.0,视为超买候选。四个分量拼起来就是一帧市场状态快照。 训练跑满 10 轮后,Train Loss 停在 0.2498,Test Loss 0.2593,二者差约 0.01,过拟合迹象暂时不明显。外汇与贵金属波动剧烈,这类特征仅描述状态、不预示方向,实盘需配合风控。

MQL5 / C++
   {   _v[class="num">0] = ((_ma_lag[class="num">1] > _ma[class="num">1] && _ma_lag[class="num">0] < _ma[class="num">0]) ? class="num">1.0f : class="num">0.0f);
       _v[class="num">1] = ((_sto_k[class="num">0] <= class="num">20.0) ? class="num">1.0f : class="num">0.0f);
       _v[class="num">2] = ((_sto_k[class="num">0] >= class="num">80.0) ? class="num">1.0f : class="num">0.0f);
       _v[class="num">3] = ((_ma_lag[class="num">1] < _ma[class="num">1] && _ma_lag[class="num">0] > _ma[class="num">0]) ? class="num">1.0f : class="num">0.0f);
   }
Epoch class="num">10/class="num">10, Train Loss: class="num">0.2498
Test Loss: class="num">0.2593

「均线穿越叠随机确认的双条件入场」

价格上穿均线且随机 %K 上穿或高于 %D,构成多头触发;价格下破均线且 %K 低于 %D,构成空头触发。该逻辑在 2023 年做训练、2024 年做前向漫游测试,结果显示空头成交比多头略少,增加训练量可能改善这一偏差。 滞后通常低于「双均线交叉后 2 根柱线」的形态,因为目标本身就是价格穿越单根均线,属于更早的趋势变动信号;叠加随机确认能过滤一批假突破,减少仓促进场。外汇与贵金属属高风险品种,此类信号仅作概率倾向参考。 弱点也明显:长周期均线滞后会让进场变钝,日线默认 MA 周期 8 是可优化项;随机振荡器在震荡市起伏不定,容易给出虚假确认。若资产已强趋势,等价格穿均线可能错过早期段。 周期 20 以下的短期 MA 对价格反应快于 50–200 的长周期;随机(5,3,3)更快但噪音多,(14,5,5)更稳,需结合时间帧。经验配比:MA 周期 N 时,随机取(N,3,3),慢速滞后 MA 取 2×N。成交量激增与支撑阻力可再加一层确认。 下方代码片段是 Index==1 时的条件抽取与训练函数开头,直接反映了双条件向量化方式。

MQL5 / C++
else if(Index == class="num">1)
{  if(C.GetData(T, class="num">2, _c) >= class="num">2 && CopyBuffer(M.Handle(), class="num">0, T, class="num">2, _ma) >= class="num">2 && CopyBuffer(S.Handle(), class="num">0, T, class="num">2, _sto_k) >= class="num">2 && CopyBuffer(S.Handle(), class="num">1, T, class="num">2, _sto_d) >= class="num">2)
   {  _v[class="num">0] = ((_c[class="num">1] < _ma[class="num">1] && _c[class="num">0] > _ma[class="num">0]) ? class="num">1.0f : class="num">0.0f);
      _v[class="num">1] = ((_sto_k[class="num">1] < _sto_d[class="num">1] && _sto_k[class="num">0] > _sto_d[class="num">0]) ? class="num">1.0f : class="num">0.0f);
      _v[class="num">2] = ((_sto_k[class="num">1] > _sto_d[class="num">1] && _sto_k[class="num">0] < _sto_d[class="num">0]) ? class="num">1.0f : class="num">0.0f);
      _v[class="num">3] = ((_c[class="num">1] > _ma[class="num">1] && _c[class="num">0] < _ma[class="num">0]) ? class="num">1.0f : class="num">0.0f);
   }
}
# Train function
def Train(model, train_loader, optimizer, loss_fn, epochs):
    device = T.device(&class="macro">#x27;cuda:class="num">0&class="macro">#x27; if T.cuda.is_available() else &class="macro">#x27;cpu&class="macro">#x27;)
    model.to(device)
    for epoch in range(epochs):
        model.train()
        train_loss = class="num">0.0
        
        for batch_idx, (data, target) in enumerate(tqdm(train_loader, desc=f"Epoch {epoch + class="num">1}/{epochs} (Train)")):
            # Step class="num">1: Ensure proper tensor dimensions
            data = data.view(-class="num">1, class="num">1, feature_size)
            
            # Step class="num">2: Verify dimensions
            expected_shape = [feature_size]
            actual_shape = list(data.shape[class="num">2:])
            if actual_shape != (expected_shape):
                raise RuntimeError(f"Invalid spatial dimensions after reshaping. Got {data.shape[class="num">2:]}, expected {[x_data.shape[class="num">1]]}")
            # Step class="num">3: Move to device and forward pass
            data, target = data.to(device), target.to(device)
            optimizer.zero_grad()
            output = model(data)
            target = target.view(-class="num">1, class="num">1, state_size)

常见问题

先用默认周期跑通流程,再拿历史段做外推检验;周期别调太极端,否则样本少、泛化差。
把特征向量在多个不重叠时间段回放,看双条件触发后的胜率和回撤,只信能复现的结果。
可以,小布能按你设的均线穿越叠随机确认条件盯盘口,触发时直接推送,不用自己守屏幕。
向导生成物只是试炼场,用来理解特征映射;真上线前必须手调参数并过风控,外汇贵金属高风险。
0/1 向量作为监督段输入,强化段用 Reward 修正权重,推断段按训练后阈值输出买卖倾向,非必然。