您应当知道的 MQL5 向导技术(第 57 部分):搭配移动平均和随机振荡器的监督训练
概述
我们继续考察经由 MQL 向导汇编的智能系统实现的简单形态。其主要目的始终是试点或测试创意。手工汇编的智能系统必须要经历长时间测试后,才能最终在实盘账户里部署并运行,但由向导汇编的智能系统允许快速测试运行,且预需代码更少。
机器学习当下风靡一时,我们在本系列之前的文章中已涵盖一些具体层面。我们将继续在本篇及未来文章中涵盖一些更技术特征,不过它们仅是一幕背景,因为我们将更专注于知名度更佳、及成熟的指标形态。
进而,在机器学习的语境下,我们将在一轮三篇文章中涵盖三个主要学习分支。开篇我们将考察监督训练或监督学习,我们的指标形态将来自配对的趋势和动量指标。我们将考察移动平均和随机指标。
在监督学习中,我们将寻求在分离的神经网络中实现每种形态。正如近期文章中的论调,以 Python 编码及训练,要比 MQL5 更好。所获效率爆表。Python 还可轻松支持训练后的交叉验证测试,我们能就此针对每个形态执行这些测试。
而 Python 分享的交叉验证,是取测试运行的亏损值,与前一训练局次的损耗值比较,单此一项虽重要,若评估网络当前权重和偏差的交叉验证却略有不足。
因此,我们将在 MetaTrader 5 策略测试器中执行前向漫游测试,搭配导出的 ONNX 网络。至于本篇,定价、或 x 和 y 数据集都是从 MetaTrader 5 发送到 python,开始训练则是依据 EURJPY 货币对的 2023 年历史数据。因此,前向漫游测试将针对同一品种,但年度则是 2024 年。我们是在日线时间帧上执行分析。
结合移动平均线(MA)与随机振荡器,能够生成一种交易信号变体。出于我们的测试和探索目的,我们仅研究这些指标组合后交易者能用的 10 个顶级信号形态。
机器学习类型的方式
机器学习流水线能视作三个相互关联的阶段:监督、强化、与推理。我们回忆一下各自的定义。
监督学习(又称训练阶段)— 是模型基于标注数据(独立或输入数据往往标注为 x;以及用于预测的独立数据,往往标注为 y)学习历史形态。
期间,强化学习(可视为正在使用学习和优化阶段)是模型经由与环境互动改进自身,并优化其动作,从而获得长期奖励(几乎类似于前向漫游测试期间的反向传播)。
最后,推理学习(又称无监督学习阶段)— 是指模型自以往的学习加以推广,并将所学应用于新的数据和新问题。
纯为概括,我们来考察这三个阶段在处置不同问题时如何链接。我们将研究气象信息系统、及金融时间序列预报的案例。
监督:
监督学习的目标是从标注数据生成一个预测模型。该过程涉及对历史数据的收集和预处理。后随特征提取,取历史数据在某个范围的归一化形式,或能被输入到神经网络、或训练模型的格式。之后,模型就能在 LSTMs、或 XGBoost、或变换器上使用标注数据进行训练。标注意味着我们用不同的标签标记独立数据(前置数据已知,并用于预测),以及依赖数据(我们打算预测的数据)。此处的初步目标是损失最小化,在预测与实际之间的差值按梯度降序尽可能最小。
在监督学习中,可能会涉及交叉验证,其结果可以为充分部署该模型提供案例。然而,按照我们此处探讨的三个岔道,监督学习的最后阶段是模型选择与超参数调谐,其本质是基于最佳工效选择最优学习率和训练批次规模。还有,有关激活类型架构、甚至层尺寸这些选项,在测试阶段也会加以测试。
概括而言,在气象预报系统中:数据是以往的温度、湿度、或气压;该模型可以是随机森林模型、或基于 CNN 的模型;其成果是一个预测温度倾向的系统。若配以金融时间序列,预测数据将是历史价格,配合特征工程来给出指标值;该模型可为 LSTM;损失函数 MSE;且其成果是一个训练有素的模型,能基于以往的观察预测接下来的价格。
强化:
据大量标注历史数据执行监督学习之后,问题就变为模型能否在实时或未来数据上执行?甚或如果未来条件相较训练环境发生了改变,它还能适配吗?为了回答这一点,强化学习已被投入应用。静态模型通常在动态环境中卡顿,这也是为何强化学习有助于优化决策制定过程向前迈进。
因此,强化学习的目标是基于反馈来优化决策制定,从而提升预测。抛开差异,就说一旦我们得到一个预测价格变化的神经网络,我们就会继续开发政策和价值网络,能把这些价格变化用作状态。因此,我们正处于一个位置,于此能把价格变化与交易者动作分离,化为状态和动作。
这些示例针对金融时间序列预测,如果是我们上面概括的气象,那么状态和动作可能会是降雨量,以及主要农作物的播种/种植额度。金融时间序列预测的更具体、且期望的结果,往往是遵循网络预测时所采取动作带来的盈亏。针对我们的气象概括,这可能是主要农作物的产量。
正如我们之前介绍的强化学习过程,不仅涉及训练和更新评论者网络,如此更好地预测盈利/亏损仓位,还提供了进一步优调政策网络权重和偏差的增量。
强化学习过程,回顾一下,涉及:智代环境设置,其中定义了状态、动作、和奖励。如前所述,这些数据是流动的,或由已在监督阶段训练过的模型判定(如以上概括所示)。还有政策学习,其中一名参与者网络、或等效的优化算法将状态映射到所需动作。还有一种基于奖励的优化机制,其采取数值算法形式、或评论者网络,基于长期盈利能力调整预测。最后,由一位智代来平衡探索(按学习新知识为目标,尝试不同政策设置 )与利用(坚持以往优良工效)。
如果我们能用示例来展示监督阶段的气象和财务预报中发生了什么;在监督阶段的气象预报中,初始模型基于温度、湿度等,并输出我们搜寻的预测降雨量。因为降雨是我们判定是否种植农作物的关键因素,强化学习引入了决策解释层。降雨预报输出变成了状态投射。如果在不同区域或时间点,这些状态预测出不同降雨,那么它们的解释在判定是否种植时就可转化为优化奖励,在这种情况下就是农作物产量。其成果是一个能够响应现实气象转移自我提升的预报模型。故此,如果监督阶段是学校,那么强化则是就职培训。
至于金融预测,我们会在监督阶段得到来自模型的预测价格变化,权当我们的状态。举例来讲,如果这些变化涵盖多个时间帧,它们仍可是多维向量。这些动作是指交易者、或智能系统采取的买入、卖出、或中立行为。基于每个状态(价格变化)采取的动作,我们随后遵照评论者网络、或算法得出的预期盈利,将每个状态与动作映射。一旦这步完成,政略(状态映射到动作)就能按探索与利用的平衡值来渐次更新,从而更好地解释模型在监督阶段的输出(我们将输出投射为状态)。
尽然,这样的在职培训意味着默认情况下数据发生非常缓慢,因为实况数据的流淌速度处于温和配速。故若您的监督训练涵盖了 10 年历史时区,强化训练为期 1 年,纸面上也要花费 1 年!因此,这需要模拟、或更精良的环境类。
在一组历史数据上模拟实时数据条件,我们不仅加快了强化学习的配速,还涵盖了更多数据,从而为我们提供更具韧性的模型。
故此,对于金融序列预测,这意味着我们能在监督中取用相同数据进行测试运行,但现在,要根据监督阶段网络做出的状态(或价格变化)来预测将采取的动作,优化强化学习网络来盈利。
推断:
在获得两个模型后,一个用于进行基本预测(在监督学习中),一个用于更好地解释和应用这些预测(作为强化),然后我们来到第三阶段 — 推断,本质上我们将把这些知识和模型应用到不同的领域,又或许像我们这种状况,涉及不同的交易品种。
这也被称为在未见数据上的普适。将学到的知识应用到真实世界设定,随时间推移不断完善,并无需用到标注数据即可检测未见形态。图形和自编码器在该阶段扮演着非常关键的角色,聚类亦如此。
在这三个阶段中,每推进到下一阶段,计算负载随之会减轻。本质上再次证明这些阶段的合理性。迄今为止,在本系列中已考察了众多参与无监督学习的方法,不过我或许会很快重新造访它们,亲眼看它们如何自强化学习中“运营”。尽管强化学习能用来自主预测,但概括的这些阶段,是为寻求更有效的与其它学习模式结合。
结合移动平均线(MA)与随机振荡器,能够生成一种交易信号变体。我们验证这些指标组合产生的前 10 个信号形态,供交易者备用。
移动平均线交叉 + 随机超买/超卖
这种形态由趋势跟随指标(MA)和动量振荡器(stochastic)组合而来,偏重提供高概率的设定。买入信号是看涨交叉(快速均线上穿滞后均线),确认潜在的上升趋势,同时随机振荡器位于 20 以下。这通常意味着资产被超卖,价格准备逆转。卖出信号则是看跌交叉。这标示快速均线从上方下穿滞后均线,跌至其下方,其本身就是潜在地下行趋势指标。由于这一点也得到随机振荡器高于 80 水平的支持,是超买条件的迹象,故它形成一个强势卖出信号。
配合我们略微修改的信号类格式,其中我们把指标读数用作网络的特征,我们现在得到一个单独的形态函数,代码如下:
//+------------------------------------------------------------------+ //| Check for Pattern. | //+------------------------------------------------------------------+ double CSignal_MA_STO::IsPattern(int Index, ENUM_POSITION_TYPE T) { vectorf _x = Get(Index, m_time.GetData(X()), m_close, m_ma, m_ma_lag, m_sto); vectorf _y(1); _y.Fill(0.0); ResetLastError(); if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, _x, _y)) { printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError()); return(double(_y[0])); } //printf(__FUNCSIG__+" y: "+DoubleToString(_y[0],2)); if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } return(double(_y[0])); }
我们包含一个名为 “57_X.mqh” 的文件,它有一个函数,提取移动平均和随机振荡器数值,这些都会输入我们的网络。我们正在研究多达 10 个不同的形态。这意味着我们将用到多达 10 个不同的网络。在该包含文件中,我们的函数 “Get” 也会返回最多 10 个不同的数据集,每个网络对应一个。
“Get” 返回一个浮点向量(vectorf),我们能够轻松地将其输入到 ONNX 网络之中。同样的函数也能才用一种替代方案,即在 MetaTrader 5 导入 Python 函数库,在网络取其作为输入之前,需要将价格归一化为类似此处提供的格式。我们的 Python 版本网络非常直截了当,能够如下编写:
class SimpleNeuralNetwork(nn.Module): def __init__(self): super(SimpleNeuralNetwork, self).__init__() self.fc1 = nn.Linear(feature_size, 256) # Input layer to hidden layer 1 self.fc2 = nn.Linear(256, 256) # Hidden layer 1 to hidden layer 2 self.fc3 = nn.Linear(256, state_size) # Hidden layer 2 to output layer self.sigmoid = nn.Sigmoid() def forward(self, x): x = self.sigmoid(self.fc1(x)) # Activation for hidden layer 1 x = self.sigmoid(self.fc2(x)) # Activation for hidden layer 2 x = self.fc3(x) # Output layer (no activation) return x
参数 “feature_size” 和 “state_size” 分别是我们网络的输入和输出大小。跨全部 10 个形态的输出都是标准的,在于我们的目标值在 0.0 到 1.0 范围。任何低于 0.5 的预测之解释为负面变化,任何高于 0.5 的预测值为正面变化,0.5 则为持平。
特征设置不会涉及输入的标准向量大小,因为每个形态会有不同的条件数量。至于形态,我们的第一个,它们是 4。因此,我们在 “Get” 函数中为网络分配输入向量,如下。
if(Index == 0) { if(CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(M_LAG.Handle(), 0, T, 2, _ma_lag) >= 2 && CopyBuffer(S.Handle(), 0, T, 1, _sto_k) >= 1) { _v[0] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[0] <= 20.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[0] >= 80.0) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[1] < _ma[1] && _ma_lag[0] > _ma[0]) ? 1.0f : 0.0f); } }
为了拆解,看涨形态的条件有 2 个,每个指标对应一个,看跌形态同样适用。在设置输入向量时,我们简单地检查是否满足每个条件。给定镜像、或这些条件的布尔性质,任何时刻可满足的最大值为 2。
无论如何,所有这些统统都在矢量之中,在于这能令网络增长见识。此外,在形态-0 中,这 4 个条件能进一步切分为 6 个,因为第一个条件包含两个参数,第四个也是。该源码附在文章末尾,读者能自行实证。
在 Python 中的训练和测试运行会记录以下两次运行的亏损值:
Epoch 10/10, Train Loss: 0.2498
Test Loss: 0.2593 测试亏损值低于第一局次的初始亏损值(未标示),但仍高于第十局次的亏损值。用于训练和验证的价格数据是 2023 年全年。因此,我们在依据 2023 年数据优化形态-0 的相应开仓/平仓/形态阈值之后,依据 2024 年数据进行前向漫游测试。这为我们给出以下报告。

我们的漫游测试,尽管只有多头交易。在大数据上进行训练是必要的,这样才能看出空头交易是否也如此发挥。此外,前向漫游测试所用训练权重覆盖 2023 年 80% 的时间段,而非全年。不过,这些应用于整个 2024 年。
价格交叉均线 + 随机振荡器确认趋势
我们的下一个形态采用移动平均线价格交叉逻辑,以及随机振荡器方向来设定多头、空头条件。买入信号是当价格上穿均线,且随机振荡器 %K 上穿或高于 %D。卖出信号则是价格跌破均线,而 %K 低于 %D。
一旦导入并格式化价格数据,然后输入我们的网络,输入向量将类似于我们 “Get” 函数的输出,如下所示:
else if(Index == 1) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_c[1] < _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < _sto_d[1] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > _sto_d[1] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); _v[3] = ((_c[1] > _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
我们使用大小 4,但总共会超过 4 个条件,因为如上所述,有些条件参数会超过 1 个。在这种情况下,所有 4 个条件都包含 2 个参数,这意味着针对网络我们本可用一个大小为 8 的输入。
网络的训练通过以下的 Train 函数完成:
# Train function def Train(model, train_loader, optimizer, loss_fn, epochs): device = T.device('cuda:0' if T.cuda.is_available() else 'cpu') model.to(device) for epoch in range(epochs): model.train() train_loss = 0.0 for batch_idx, (data, target) in enumerate(tqdm(train_loader, desc=f"Epoch {epoch + 1}/{epochs} (Train)")): # Step 1: Ensure proper tensor dimensions data = data.view(-1, 1, feature_size) # Step 2: Verify dimensions expected_shape = [feature_size] actual_shape = list(data.shape[2:]) if actual_shape != (expected_shape): raise RuntimeError(f"Invalid spatial dimensions after reshaping. Got {data.shape[2:]}, expected {[x_data.shape[1]]}") # Step 3: Move to device and forward pass data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) target = target.view(-1, 1, state_size) loss = loss_fn(output, target) # Step 4: Backpropagation loss.backward() optimizer.step() train_loss += loss.item() train_loss /= len(train_loader) print(f"Epoch {epoch + 1}/{epochs}, Train Loss: {train_loss:.4f}")
训练覆盖 2023 年,呈现以下损失函数日志:
Epoch 10/10, Train Loss: 0.2491
Test Loss: 0.2592 2024 年的前向漫游测试为我们给出以下报告:

我们得到了一个颇具潜力的前向漫游测试,不过与多头相比,空头交易稍少。更多的训练应当能解决这一点。该形态捕捉了早期趋势变动,因为目标是价格穿越移动平均线,这可能是趋势转移的迹象,且滞后应当低于移动平均线交叉之后 2 根柱线,如我们在形态-0 中所见。由于用到随机振荡器来确认,它也过滤掉许多假突破,这有助于避免仓促交易。
这种形态也有其弱点和局限性。首先,移动平均线的滞后性质,尤其是当周期较长时,会令进场交易迟钝。我们默认在日线时间帧采用周期 8 ,但这是一个自定义信号类别的可优化参数,故读者可优调至最佳工作状态。其二,随机振荡器以起伏不定而声名不佳,尤其是在范围内波动市场。这能导致很多虚假确认。此外,在资产趋势已走强的状况下,等待价格穿越移动平均线可能导致错过大量早期入场机会。
就优化指标周期而言,周期在 20 以下范围的短期移动平均线对价格变化的反应通常比周期 50 至 200 的较长周期更快速。故此,用户需要考虑他们所采用的时间帧因素,来决定接受变化之前,他们是想要快速反应,还是想要稳定和更多确定性。
类似地,随机振荡器(5,3,3)提供更快的设置,但生成的信号必然会有噪音。比如较慢的设置(14,5,5)可能觉得更合适,但这还应一并参考时间帧。我们在测试中所用的自定义信号类,采用的是移动平均线和随机振荡器指标的标准周期。这些都可自定义,但概括而言,如果我们的 MA 周期是 N,那么随机振荡器是(N,3,3),而慢速或滞后 MA 是 2 x N。
成交量激刺也能用来进一步确认均线交叉、或突破强度。支撑和阻力水平也能用来进一步强化形态的可靠性。
移动平均斜率 + 随机振荡器趋势确认
该形态通过均线斜率专注趋势方向,同时配合随机振荡器指标确认动量。买入信号是当均线向上倾斜,且随机振荡器指数超过 50,且还在上升。卖出信号则正好相反。若移动平均线倾斜向下,而随机振荡器指标低于 50,且还在下降。这就是我们的 “Get” 函数如何检索输入向量,检查以下条件:
else if(Index == 2) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_ma[1] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < _sto_k[0] && _sto_k[1] >= 50.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > _sto_k[0] && _sto_k[1] <= 50.0) ? 1.0f : 0.0f); _v[3] = ((_ma[1] > _ma[0]) ? 1.0f : 0.0f); } }
这是 10 个形态中的第一个,我们用到随机振荡器的第二个缓冲区,也就是 %K。它总是滞后主缓冲区(%D),于此使用它是帮助确认振荡器的斜率。一旦将 EURJPY 的 2023 年数据导出到 Python,我们就调用上述的 “Train” 函数清单来训练简单网络。测试或交叉验证会由一个与我们上面列出的 “Train” 非常相似的 ‘Test’ 函数执行。其 Python 清单如下:
# Test function def Test(model, test_loader, optimizer, loss_fn, epochs): device = T.device('cuda:0' if T.cuda.is_available() else 'cpu') model.to(device) with T.no_grad(): test_loss = 0.0 for batch_idx, (data, target) in enumerate(tqdm(test_loader, desc=f"Loss at {test_loss} in (Test)")): # Step 1: Ensure proper tensor dimensions data = data.view(-1, 1, feature_size) ... # Step 3: Move to device and forward pass data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) target = target.view(-1, 1, 1) #print(f"target: {target.shape}, plus output: {output.shape}") loss = loss_fn(output, target) test_loss += loss.item() test_loss /= len(test_loader) print(f"Test Loss: {test_loss:.4f}")
比较训练和测试损失的得分,与上述形态-0 和形态-1 的报告一致,因此不再于此贴出。MetaTrader 策略测试器依据 2024 年数据的前向漫游测试呈现如下报告:

我们的形态能够给出偏侧漫游测试,因为仅有多头开仓。在更大数据集上训练应当能解决这个问题。然而形态-2 是个适合范围波动行情的过滤器。线斜率确保仅有在趋势明显时才会下单交易。随后,趋势强度由随机振荡器确认,因为相对于 50 阈值的位置能验证盛行趋势中的动量。总体来说,滞后也有所降低,因为不再像上述形态那样等待移动平均线交叉。这意味着该形态对趋势加速是好兆头。
其主要弱点包括对趋势逆转的反应迟缓,因为移动平均斜率在趋势逆转后,需要时间调整或转移。此外,随机振荡器在这些状况下可能会延迟给出信号。如果趋势已起作用,等待随机振荡器确认可能导致错过早期进场。一般而言,由于该形态高度依赖趋势,不适合窄幅横盘行情。
若与附加过滤器配对,,譬如 ADX(平均方向指数),则有助于确认强劲趋势。此外,检查价格动作突破、或回调应能提升准确性。实际应用场景可能包括外汇(我们于此进行的测试就是、或者股票,因为这种形态能有助于识别小幅回撤后的持续趋势;或者加密货币与大宗商品,因为它在捕捉黄金、或比特币等资产的动量时很实用。
MA 反弹与随机振荡器背离
该形态工作的原理,结合了移动平均价格反弹的动态支撑/阻力、与随机振荡器的动量背离,在逆转发生前识别出可能的形态。买入信号是当价格自向上倾斜的移动平均线(此时该移动平均线作为支撑)反弹,随机振荡器显示多头背离,即价格出现更低的低点,而对应振荡器则是一个更高的低点。
卖出信号是价格在向下倾斜的移动平均线处一触即溃,该移动平均线权当阻力,随机振荡器示意空头背离,即价格出现更高的高点,而对应随机振荡器则更低的高点。网络输入定义,及处理该形态的 MQL5 版本实现如下:
else if(Index == 3) { _v.Init(6); _v.Fill(0.0); if(C.GetData(T, 3, _c) >= 3 && CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(S.Handle(), 0, T, 3, _sto_k) >= 3) { _v[0] = ((_c[2] > _c[1] && _c[1] < _c[0] && _c[0] < _c[2] && _ma[1] >= _c[1]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[2] > _sto_k[1] && _sto_k[1] < _sto_k[0] && _sto_k[1] >= 40.0) ? 1.0f : 0.0f); _v[2] = ((_ma[2] > _ma[0]) ? 1.0f : 0.0f); _v[3] = ((_ma[2] < _ma[0]) ? 1.0f : 0.0f); _v[4] = ((_sto_k[2] < _sto_k[1] && _sto_k[1] > _sto_k[0] && _sto_k[1] <= 60.0) ? 1.0f : 0.0f); _v[5] = ((_c[2] < _c[1] && _c[1] > _c[0] && _c[0] > _c[2] && _ma[1] <= _c[1]) ? 1.0f : 0.0f); } }
训练和测试验证损失函数的得分,与前两种形态的日志差别不大,这样的话日志就共享。测试损失函数小于训练中第一局的损失函数,但仍小于第十局的损失函数。在 2023 年数据上训练后,在 2024年数据上的前向漫游测试报告如下:

正面的前向漫游测试总归是好兆头,能以平衡的姿态提供多空两者开仓。但此处情况并非如此。然而,形态-3 在早期逆转检测方面表现良好,因为随机振荡器背离通常在价格反应之前就预兆趋势逆转。它也避免了被动追逐趋势,这样交易者能在接近动态支撑/阻力位入场,而不会太迟。它在趋势行情中亦工作良好,因为均线反弹会确认趋势,而背离则是疲软的预兆。
其弱点和局限性则是当行情处于强劲趋势时,出现的假信号。这是因为在这些状况下,单靠背离往往不足以逆转。此外 MA 滞后效应,即因为 MA 反弹或许不总是与背离的时序完美对齐,故需要确认以便提升准确性,这些也是缺点。
形态-3 能通过在 MA 层面与烛条形态(如针棒或吞噬棒)配对来优化,从而加强设定。交易量激刺也能作为确认背离的补充。
移动平均线作为动态支撑/阻力 + 随机振荡器交叉
我们的形态-4 策略结合了趋势跟随(移动平均线作为方向,及支撑/阻力)和动量确认(检查随机振荡器交叉),以便生成实用的买卖信号。
买入信号是当价格守在一条向上倾斜移动平均线上方,且随机振荡器 %K 上穿 %D。该看涨形态有助于确保一定数量的多头开仓关键前提条件。首先,它当作趋势确认,因为一条向上倾斜的移动平均线确保所下单交易与当前行情方向保持一致。
其次,随机振荡器的交叉提供与动量时刻对应的入场信号。这是因为动量的转移往往确认买盘压力换新。避免在任何 MA 触点的过早进场,因为随机振荡器交叉确保买入前已存在动量。假信号因此会减少。潜在缺点是它或许会错过早期突破,因为它要等待这两个条件,结局就是入场略迟。
卖出信号是当价格停留在向下倾斜移动平均线下方,且随机振荡器 %K 下穿 %D。类似地,就像看涨对手方,提供趋势确认,是基于动量进场或离场,没有过早进场,且弱信号均被滤除。潜在缺点是起伏不定行情,价格在重续下跌之前可能会先突破移动平均线。这尤为关键,因为对于大多数交易品种,它们的降幅趋于比看涨趋势更为剧烈。我们的 MQL5 版本实现如下:
else if(Index == 4) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_sto_k[1] < _sto_d[1] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[1] = ((_ma[1] < _c[1] && _ma[0] < _c[0] && _ma[1] < _ma[0]) ? 1.0f : 0.0f); _v[2] = ((_ma[1] > _c[1] && _ma[0] > _c[0] && _ma[1] > _ma[0]) ? 1.0f : 0.0f); _v[3] = ((_sto_k[1] > _sto_d[1] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); } }
我们将输入网络的向量结合起来,按一个四维结构处理该形态。如上早期形态所示,把条件内的每个参数作为输入的一部分,能更明确地制定该向量。仅在形态-4 上训练的网络前向游走测试,为我们给出以下结果:

该形态在趋势跟踪行情下应当还好,不过在区间反弹场景下必然会吃力。尽管我们得到有利的前想漫游测试,但在更大数据上进行测试仍很重要,确保我们能够同时交易多头和空头两者,而不只如上报告里仅有多头。
在 MA 附近的随即振荡器超买/超卖逆转
形态-5 揉合了趋势跟随,以移动平均线作为动态支撑/阻力,且按随机振荡器作为动量逆转。买入信号是当随机振荡器上穿 20 阈值,而价格正得到均线支撑。该形态提供了动态支撑验证,因为均线权当支撑区域,确保价格在现行上涨趋势内反弹。随机振荡器上穿 20 也是看跌动量疲软的迹象,买方正在入场。使用两个指标不仅避免了过早入场,还是一种高概率的设置。潜在缺点是,在强劲下跌趋势中,价格或许在移动平均线处暂留,随后再下破。
卖出信号是当随机振荡器下穿 80,而价格接近向下倾斜移动平均线的阻力位。这也权当动态阻力验证,因为移动平均线作为阻力区域,其可证明价格爬高失败。超买逆转确认信号表明多头动量减弱,卖方正在进场。运用双指标再次避免了过早的入场/离场,并滤除了弱回调。形态-5 看跌信号的一个潜在缺点是,在强劲下跌趋势中,价格下破之前或许会先在移动平均线上方盘整,这可能导致离场晚点、或错失点数。
我们实现的 MQL5 版本如下:
else if(Index == 5) { if(C.GetData(T, 3, _c) >= 3 && CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_sto_k[1] < 20.0 && _sto_k[0] > 20.0) ? 1.0f : 0.0f); _v[1] = ((_c[2] > _c[1] && _c[1] < _c[0] && _c[1] >= _ma[1]) ? 1.0f : 0.0f); _v[2] = ((_c[2] < _c[1] && _c[1] > _c[0] && _c[1] <= _ma[1]) ? 1.0f : 0.0f); _v[3] = ((_sto_k[1] > 80.0 && _sto_k[0] < 80.0) ? 1.0f : 0.0f); } }
本文实现的 MQL5 版本形态,旨在为神经网络设定或定义输入向量。我们将这些输入向量定义为 0 和 1 的集合,其中 1 表示满足特定的做多、或做空条件。做多和做空条件的索引是分开的,这在技术上意味着输入向量不可能填满 1,因为做多、做空条件总是相互镜像。
此外,每个索引处的条件能通过让该条件中的每个参数各自占据自己的索引来进一步详细或延长。此外,每个索引处的条件可让每个参数占有自己的索引来进一步细化或扩展。这将导致输入向量更长,不过我们尚未探讨它。既然我们是在 2023 年上进行测试和训练,那前向漫游测试就依据 2024 年。它为我们给出以下报告:

我们能用形态-5 进行前向漫游测试,但有一个告诫。仅有多头开仓交易!这主要是因为训练在有限、或小数据集上,如此网络输出在小样本上变得偏颇。用更大数据集训练应能补救这一点。
金叉/死叉 + 随机振荡器确认
形态-6 采用金叉/死叉,其中较短周期的移动平均线与滞后移动平均线交叉,且动量也由随机振荡器与 50 水平交叉得以验证。买入信号的金叉,是较短周期移动平均线从下穿越到较长周期均线上方,而随机振荡器位于 50 以上,且还在上升。卖出信号的死叉,是较短周期的移动平均线从上方穿越到较长周期均线下方,收盘价低于其下方,而随机振荡器则低于 50,且还在下降。我们针对形态-6 网络设置输入如下:
else if(Index == 6) { if(CopyBuffer(M.Handle(), 0, T, 2, _ma_lag) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_d) >= 2) { _v[0] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); _v[1] = ((50.0 <= _sto_d[0] && _sto_k[0] > _sto_d[0]) ? 1.0f : 0.0f); _v[2] = ((50.0 >= _sto_d[0] && _sto_k[0] < _sto_d[0]) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[1] > _ma[1] && _ma_lag[0] < _ma[0]) ? 1.0f : 0.0f); } }
在我们所用的源码中,句柄 “_ma” 是较短周期移动平均,在我们的训练中,我们赋值周期为 8。较长周期均线使用 “_ma_lag” 的句柄,其周期是短周期的两倍,长度为 16。在 2023 年上训练后,在 2024 年上的前向漫游测试为我们给出以下报告:

形态-6 漫游测试不仅在盈利能力上失效,在多空之间的平衡上亦然。不过,在被放弃之前,通过充分的训练和测试,或许还能进一步提升。
随机振荡器极端逆转 + 移动平均线趋势确认
形态-7 采用极端水平的随机振荡器拐点,结合价格距均线的相对位置来定义入场信号。买入信号是当随机振荡器低于 10,且价格于向上倾斜均线上方开始翻上。卖出信号是当价格低于向下倾斜均线,价格在 90 以上水平翻下。我们的 MQL5 版本将此如下映射到网络:
else if(Index == 7) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 3, _sto_k) >= 3) { _v[0] = ((_ma[0] > _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[0] > _sto_k[1] && _sto_k[1] < _sto_k[2] && _sto_k[2] <= 10.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[0] < _sto_k[1] && _sto_k[1] > _sto_k[2] && _sto_k[2] >= 90.0) ? 1.0f : 0.0f); _v[3] = ((_ma[0] < _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
我们于此严格使用随即振荡器 %K 句柄,我们所做的全部只是在极端水平检查看涨条件的 n-形转向,或在极端水平处观察看跌条件的 u-形逆转。前向漫游测试,为我们给出以下报告:

据我们的以上结果,该形态也无法基于往年测试通过一年的前向漫游测试。而且它只下单空头交易。在前向漫游测试中单侧下单交易会令人担忧。由于我们的输出是 0.0 到 1.0 范围的标量浮点值,这意味着所有预测都低于 0.5。在更大数据集上进行测试和训练,对于纠正这一点非常重要。
随机振荡器突破与均线确认
我们的倒数第二个形态-8,结合了随机振荡器阈值 50,与价格-移动平均线的交叉。买入信号是当随机振荡器从下方突破 50 阈值上方收盘,同时价格以类似姿态穿越移动平均线上方收盘,动量如信号所示回升。卖出则相反,振荡器跌破 50,而价格跌破移动平均线支撑。如果我们类似之前文章那样行事,那应用该形态不会产生太多交易。不过,我们现在分别查看看涨和俺爹信号的移动平均线和随机条件。MQL5 版本如下行事:
else if(Index == 8) { if(C.GetData(T, 2, _c) >= 2 && CopyBuffer(M.Handle(), 0, T, 2, _ma) >= 2 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2 && CopyBuffer(S.Handle(), 1, T, 2, _sto_d) >= 2) { _v[0] = ((_c[1] < _ma[1] && _c[0] > _ma[0]) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] < 50.0 && _sto_k[0] > 50.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] > 50.0 && _sto_k[0] < 50.0) ? 1.0f : 0.0f); _v[3] = ((_c[1] > _ma[1] && _c[0] < _ma[0]) ? 1.0f : 0.0f); } }
形态-8 网络的输入向量现在更有可能具有或登记至少一个看涨或看跌条件,这有助于训练,也有助部署网络的适应性。这是因为除了处理两个指标外,如果仅有其一登记目标形态,它还会给出预测。前向漫游测试为我们给出以下结果:

看似该形态能够走通,这令人鼓舞。于此有几点关键告诫值得一提。首先,这些测试运行采用目标价(止盈)了结,且无止损。确实,止损从不保证离场价格,但需要一些策略来应对不成功的交易。其次,我们的训练是在最近一年之上,而测试则在次年上进行,这其实是一个相对较短的测试窗口。更长的测试周期、和高品质经纪商数据往往是首选。
移动平均线挤压与随机振荡器突破
我们的最后一个形态-9,利用移动平均线挤压来感知低波动性条件,结合随机振荡器动量,把握突破这些条件的时刻。买入信号是当一条周期较短、一条周期较长的两条移动平均线,在一段宽阔时段内挤压在一起,之后随机振荡器记录的动量大幅穿过 50。看跌信号也表现出相同条件,第一个变化是看涨挤压时,较短的移动平均线高于较长的移动平均线,而卖出信号则相反,随机振荡器会大幅跌过 50(取代上升)
我们在 MQL5 版本中检索网络输入向量如下:
else if(Index == 9) { if(CopyBuffer(M.Handle(), 0, T, 3, _ma) >= 3 && CopyBuffer(M_LAG.Handle(), 0, T, 3, _ma_lag) >= 3 && CopyBuffer(S.Handle(), 0, T, 2, _sto_k) >= 2) { _v[0] = ((_ma_lag[0] < _ma[0] && fabs(fabs(_ma_lag[2] - _ma[2]) - fabs(_ma_lag[0] - _ma[0])) <= fabs(_ma[2] - _ma[0])) ? 1.0f : 0.0f); _v[1] = ((_sto_k[1] <= 45.0 && _sto_k[0] >= 55.0) ? 1.0f : 0.0f); _v[2] = ((_sto_k[1] >= 55.0 && _sto_k[0] <= 45.0) ? 1.0f : 0.0f); _v[3] = ((_ma_lag[0] > _ma[0] && fabs(fabs(_ma_lag[2] - _ma[2]) - fabs(_ma_lag[0] - _ma[0])) <= fabs(_ma[2] - _ma[0])) ? 1.0f : 0.0f); } }
其前向漫游测试结果如下:

形态-9 看似也没有分腿,尽管它下单做多和做空交易都行。在等权基础上,不应进一步研究,因为我们以上一些形态已得出有利的漫游测试,但这个决定取决于交易者,以及他愿意做多少测试。
结束语
我们并未尝试将形态组合,或挑选不同形态来组建统一系统。正如我们在过去的文章中所言,尤其是最后一篇中所观察到的,这必然存在危险。使用多种形态需要交易者精通它们,因为它们可能会彼此取消对方的交易。如果在跟踪中使用“魔幻数字”,这可能会有所帮助,但保证金限制仍会带来挑战。在下一篇章中,我们将考察强化学习如何基于我们此处已有内容搭建。
| 文件 | 描述 |
|---|---|
| 57_0.onnx | 形态-0 网络 |
| 57_1.onnx | 形态-1 |
| 57_2.onnx | 形态-2 |
| 57_3.onnx | 形态-3 |
| 57_4.onnx | 形态-4 |
| 57_5.onnx | 形态-5 |
| 57_6.onnx | 形态-6 |
| 57_7.onnx | 形态-7 |
| 57_8.onnx | 形态-8 |
| 57_9.onnx | 形态-9 |
| SignalWZ_57.mqh | 信号类文件 |
| 57_X.mqh | 处理网络输入的信号文件 |
| wz_57.mq5 | 包含显示在汇编的智能系统用到的文件 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17479
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第 22 部分):相关性仪表盘
MQL5交易策略自动化(第十七部分):借助动态仪表盘精通网格马丁格尔(Grid-Mart)短线交易策略
利用 MQL5 经济日历进行交易(第 8 部分):通过智能事件过滤和定向日志优化新闻驱动策略的回测
外汇套利交易:分析合成货币的走势及其均值回归
您好,有一个附件缺少 SignalWZ_57.mqh 文件。
是的,我也遇到了同样的问题,缺少 SignalWZ_57.mqh 文件。