您应该了解的MQL5向导技巧(第七十六部分):利用AO形态与包络通道进行监督学习建模
引言
我们在前一篇文章中介绍了动量震荡指标与包络通道的组合指标策略。在两年的测试窗口中,这10种形态里有7至8种在前向测试中的表现通过了前向步进检验。在介绍完各类指标组合后,我们通常会进一步探究:机器学习能否对这些指标信号的交易表现起到优化作用。本文也将延续这一研究思路,重点分析第4、8、9类指标形态,研究通过监督学习网络作为信号过滤器,对三类形态的交易信号产生的优化效果与影响。本次研究采用CNN模型,该模型通过跨时间注意力点积核自适应调整卷积核与特征通道的维度参数。
跨时间注意力点积核
该注意力核是一种注意力机制的形式:输入两组时序序列(通常取自不同时间步或网络层的特征数据),通过点积运算计算所有特征两两之间的注意力得分。该方法能够突出跨时间关系,并反映过去或未来特征与当前特征的相关程度。其工作原理如下,假设存在以下两组时序序列:
X=[x1,x2,...,xT]
Y=[y1,y2,...,yS]
针对每一组特征对(xi,yj),可通过如下公式计算点积得分:
Scorei,j = xi⋅yj
此外,可对j或i执行Softmax运算,得到注意力权重,再利用该权重对跨时间维度的特征进行加权融合与重新赋值。该机制的优势在于参数效率极高,除投影参数外,无需额外训练学习参数。同时具备极强灵活性,可适配变长序列、非均匀采样数据以及跨模态、跨时间的特征交互场景。其表现力也十分出色:模型会聚焦关键时序节点,而非局限于卷积操作的局部邻域特征。该机制广泛应用于各类时序任务,重视滞后效应的时间序列预测、存在帧时序关联的视频分析、语音识别,以及所有存在复杂时间依赖关系的研究领域。
那么,我们为何选用该注意力核来动态定义CNN的卷积核与通道尺寸?原因在于其同时具备空间特征与时序特征双重属性。首先,注意力核能够识别时间步长,区分不同时序节点的重要程度。对于小尺寸、聚焦局部的卷积核,该效果并不突出;但随着卷积核感受野扩大,模型能够捕获更丰富的全局时序上下文信息。CNN本质上聚焦局部特征,而注意力机制可根据数据序列的时序动态规律,自适应调整卷积核大小,从而扩大或收缩有效感受野。
在特征通道层面,注意力核可以精准筛选出关注度最高的特征通道与维度。这一特性可用于通道剪枝优化,如果部分通道始终被注意力机制忽略,说明其属于无效冗余参数,可直接删减,压缩整体通道规模。同时也可指导通道扩容,如果注意力分布分散、无明确聚焦通道,说明现有特征维度不足,需要增加通道数量,构建更丰富的特征表征。因此,与我们往期文章中优化CNN模型的思路一致:不再采用一套参数适用于所有场景的固定卷积核/通道尺寸方案,而采用动态自适应CNN架构。我们在模型训练与推理阶段引入注意力机制重构CNN结构,旨在适配非平稳的金融市场数据,构建更高效、更精准的预测模型。
尽管本文采用的动态建模方法旨在提升模型的适应性与预测性能,但仍存在若干需要提到的缺陷。首先是计算开销问题。该方法的计算复杂度随输入序列长度呈O(N^2)增长,而传统静态卷积神经网络的复杂度通常仅为O(N)。在处理长时序数据时,该计算成本极高。其次是可解释性悖论。注意力分数虽然“不错”,但将其与动态核/通道选择机制相结合,确实会增加模型解释的复杂性,难以从中提取有意义的推论应用到其他场景。
与此同时,该方法并非全能最优。相关测试表明,在图像边缘检测等纯局部特征任务中,传统CNN的表现反而优于高度依赖注意力机制的模型。此外,注意力核为学习有效的跨时间依赖关系,在训练与优化过程中,对数据量的需求远高于传统CNN。最后,存在过拟合风险。当网络结构完全由注意力机制动态调控时,模型过度适配数据特征,极易捕捉并拟合训练数据中的噪声或随机扰动,最终引发过拟合问题。
本文方法的可行替代方案包括:空洞卷积:无需增大卷积核尺寸即可扩大感受野,适配大范围、稀疏的时序差异特征;动态/自适应卷积:根据输入数据特征或注意力信号动态调整卷积核权重;深度可分离卷积:仅通过调整通道维度优化模型,主打计算效率提升;压缩激励模块(SE模块);时序卷积网络(TCN)。以上各类方案均是对本文方法的创新性改良,各有优劣。本文列举这些方案,旨在完整展示CNN的主流优化调整思路。
网络
在简要介绍完点积跨时间注意力核的原理后,让我们对代码进行详细解析。将该网络的主要代码看作一个类,呈现如下:
import torch import torch.nn as nn import torch.nn.functional as F class DotProductAttentionConv1D(nn.Module): def __init__(self, input_length=100): super(DotProductAttentionConv1D, self).__init__() self.input_length = input_length # Deeper and wider design with attention self.kernel_sizes, self.channels = self._design_architecture() self.conv_layers = nn.ModuleList() self.attention_layers = nn.ModuleList() # For cross-time attention in_channels = 1 for i, (out_channels, kernel_size) in enumerate(zip(self.channels, self.kernel_sizes)): # Convolutional block conv_layer = nn.Sequential( nn.Conv1d(in_channels, out_channels, kernel_size=kernel_size, padding=kernel_size // 2), nn.BatchNorm1d(out_channels), nn.ReLU(), nn.Dropout(0.2)) self.conv_layers.append(conv_layer) # Attention block (cross-time attention) if i % 2 == 0: # Apply attention to every other layer attn_layer = nn.MultiheadAttention(embed_dim=out_channels, num_heads=4) self.attention_layers.append(attn_layer) else: self.attention_layers.append(None) in_channels = out_channels # Fully connected head (same as original) self.head = nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(in_channels, 128), nn.ReLU(), nn.Dropout(0.2), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1), nn.Sigmoid() ) def _dot_product_kernel(self, x): """Dot product similarity kernel with positional encoding""" # x shape: (B, C, L) x = x.permute(0, 2, 1) # (B, L, C) # Compute dot product attention attention_scores = torch.bmm(x, x.transpose(1, 2)) # (B, L, L) attention_weights = F.softmax(attention_scores / (x.size(-1) ** 0.5), dim=-1) return torch.bmm(attention_weights, x).permute(0, 2, 1) # (B, C, L) def _design_architecture(self): # Simulate attention response pattern num_layers = 10 kernel_sizes = [3 + (i % 4) * 2 for i in range(num_layers)] # cyclic 3, 5, 7, 9 channels = [32 * (i + 1) for i in range(num_layers)] # 32, 64, ..., 320 return kernel_sizes, channels def forward(self, x): x = x.unsqueeze(1) # (B, 1, L) for conv_layer, attn_layer in zip(self.conv_layers, self.attention_layers): x = conv_layer(x) if attn_layer is not None: # Reshape for attention (MultiheadAttention expects seq_len first) attn_input = x.permute(2, 0, 1) # (L, B, C) attn_output, _ = attn_layer(attn_input, attn_input, attn_input) x = attn_output.permute(1, 2, 0) # (B, C, L) # Also apply dot product kernel x = self._dot_product_kernel(x) return self.head(x)
首先,该网络类继承自nn.Module,这是PyTorch搭建神经网络的标准基类。参数input_length用于设定模型预期的输入序列长度,经过适配调整后,该类依然支持接收任意长度的可变序列输入。super父类初始化指令用于完成基类的初始化,这是保障PyTorch网络正常运行的核心步骤,绝对不能省略。完成初始化配置后,我们开始搭建网络架构,实现卷积核与通道的尺寸配置。
如上所述,本模型采用动态架构设计。模型不再使用固定硬编码的尺寸参数,而是通过算法自适应选取/设定卷积核与通道尺寸,让每一轮前向传播都能适配输入数据的注意力分布特征。优化建议:可将历史迭代的注意力统计特征或元学习结果,与每一层的卷积核/通道选择机制相关联,进一步提升适配精度。
执行完架构设计函数后,即可进入网络层的构建阶段。这里,通过ModuleList函数搭建可变深度网络,这是动态堆叠网络层的关键步骤。接下来,通过循环完成网络层堆叠,对每一层卷积层单独分配专属卷积核尺寸,可根据数据特征自适应适配宽窄不同的卷积滤波器,最终由注意力核判定最优尺寸参数。模型的填充尺寸由卷积核尺寸整除2得到,该设置可保证绝大多数卷积层的输入与输出序列长度一致。这一特性在时序任务中至关重要,能够精准对齐所有时间步长的数据特征。最后,引入批量归一化、ReLU激活函数与Dropout正则化等经典深度学习模块,用于稳定训练过程、加速模型收敛、抑制过拟合。
在层堆叠的循环结构中,除上述卷积模块外,还嵌入了注意力模块。模型交替添加多头注意力层(Transformer的核心组件),用于捕捉全局特征依赖关系。采用交替添加而非逐层添加的设计,主要为了节约计算资源,能让模型在局部CNN与全局推理之间灵活切换。每一层的输入通道数会更新为上一层的输出通道数,使各通道特征图统一为嵌入维度,实现CNN与注意力特征的维度对齐、表征融合。
完成网络类初始化的主体架构后,我们搭建全连接输出头。这里,模型通过自适应池化层,将任意长度的时序维度压缩至固定维度1,再对特征展平,为全连接层计算做准备。堆叠的全连接层通过多层非线性变换,最终经由Sigmoid激活函数输出单值结果。该输出可视为二分类结果或归一化得分,用于金融价格趋势预测:数值小于0.5代表看跌,大于0.5代表看涨。全连接层的Dropout机制可有效抑制该模块的过拟合问题。
在介绍完网络类的初始化结构后,轮到点积核函数的实现逻辑。计算时,首先将输入维度转换为批次-长度-通道(B-L-C)的格式,便于对时序向量序列执行批量点积运算。再执行批量矩阵乘法(BMM),高效计算单个批次内所有特征两两之间的点积相似度,GPU可进一步加速该运算过程。接下来,我们执行Softmax归一化缩放,这是注意力核的标准操作,其温度系数由输入向量大小或嵌入维度的平方根确定。
随后,我们执行最终的批量矩阵乘法(BMM),其中根据注意力权重混合不同时间点的特征。不仅如此,我们还会重新排列乘法输出,以将其恢复为输入向量的形状,并为其后续使用做准备。该函数为网络赋予了跨时间感知能力,这是常规卷积单独无法实现的,我们在网络的前向传播函数中调用它。
网络类中的下一个特殊函数是架构设计辅助函数。我们在往期文章中定制CNN时有过类似的函数,在这种情况下,我们所做的是循环遍历一系列核大小,以模拟"注意力知情"的变体。我们还稳步增加通道数 —— 这是经典的深度学习方法,旨在在后续层中学习更抽象和更高维的表示。
网络类的最后一个函数,像往常一样,是前向传播函数。我们在这里做的第一件事是通过添加额外的中间维度或通道来预处理输入向量,这是一维卷积的要求。完成此操作后,对于每一层,我们首先进行卷积,从输入中提取局部特征。接下来,我们处理条件注意力,即检查是否存在注意力层。回想一下,在该类的初始化函数中,我们交替分配这些注意力层,而不是在每个位置都分配。
如果我们有注意力层,我们会对其进行重塑,因为PyTorch的多头注意力期望特定格式(通道、批次、长度),而我们有的是(批次、通道、长度)。得到注意力输出后,我们通过调用上面定义的函数执行点积核。顺便补充一下,我们在前向传播中同时使用了Transformer和点积核,但也可以只使用其中一种作为替代方案。判断究竟是哪种算法对模型精度的贡献更大。最终输出然后被送入头部进行分类/回归。
分模块代码摘要
| 信息块 | 功能说明 | 重要性 |
|---|---|---|
| __init__ + super | 基类设置 | PyTorch必要功能 |
| self.kernel_sizes,通道 | 动态CNN设计 | 定制、自适应特征提取 |
| ModuleList层 | 模块化、可扩展架构 | 可扩展性、实验性 |
| 卷积块 | 局部特征提取 | 传统CNN优势、批量归一化等 |
| MultiheadAttention块 | 全局跨时间依赖关系 | 捕捉长距离依赖关系 |
| _dot_product_kernel | 显式注意力计算 | 跨时间链接的冗余/互连信号 |
| 全连接头 | 聚合、输出 | 任务灵活性(分类、回归等) |
| 前向逻辑 | 网络中的数据流 | 确保每项操作的正确应用 |
| _design_architecture | 核/通道的选择方式 | 可由数据或注意力驱动 |
动量震荡指标函数
在Python中使用MetaTrader 5模块时,不会导入MQL5中使用的指标函数。因此,我们总是需要使用现有库或自行编写代码。我们一直坚持自行编写代码,并在Python中实现我们的动量震荡指标函数,如下所示:
def Awesome_Oscillator(df: pd.DataFrame, short_period: int = 5, long_period: int = 34) -> pd.DataFrame: """ Calculate the Bill Williams Awesome Oscillator (AO) and append it to the input DataFrame. AO = SMA(Median Price, short_period) - SMA(Median Price, long_period) Args: df (pd.DataFrame): DataFrame with 'high' and 'low' columns. short_period (int): Short period for SMA (default 5). long_period (int): Long period for SMA (default 34). Returns: pd.DataFrame: Input DataFrame with 'AO' column added. """ required_cols = {'high', 'low'} if not required_cols.issubset(df.columns): raise ValueError("DataFrame must contain 'high' and 'low' columns") if not all(p > 0 for p in [short_period, long_period]): raise ValueError("Period values must be positive integers") result_df = df.copy() median_price = (result_df['high'] + result_df['low']) / 2 short_sma = median_price.rolling(window=short_period).mean() long_sma = median_price.rolling(window=long_period).mean() result_df['AO'] = short_sma - long_sma return result_df
我们为此函数导入的模块包括pandas,用于类表格/时间序列操作 —— 它擅长处理数据框以及滚动窗口运算。我们同时导入了NumPy —— 作为高速数值计算的核心库,它在底层被大量调用,即便此处我们并未显式直接调用它。def之后的文本行,是函数清晰的签名,提示了调用该函数所需的输入数据类型。这种清晰度与IDE自动补全相结合,因为平均周期的默认值已预定义。这些与比尔·威廉姆斯的经典设置一致。
跳过签名,我们从验证函数的数据输入开始,首先是必需项,即数据框,需包含high和low列。这是完整性检查,防止如果列缺失时后续出现难以理解的错误。此外,我们校验输入的周期值是否为有效的正整数。通过采用用户友好的错误信息,我们强制早期失败,这有助于进行有意义的诊断。这种检查输入数据的防御性立场对于避免NaN值非常重要。永远不要信任外部数据。
接下来,我们复制输入数据框以确保原始数据保持其原始状态。这是"非破坏性的",对于指标函数分层叠加的流水线来说至关重要。然后我们从复制的数据框计算中位数价格。这是AO的核心输入,反映了该K线高低价的中间价格 —— 不仅仅是收盘价,旨在避免开盘价/收盘价震荡。之后我们计算短期和长期的平滑移动平均线。使用rolling().mean()创建的是平滑趋势信号的移动平均线。短期与长期的比较是震荡指标逻辑的核心。它将动量衡量为快速趋势与慢速趋势之间的距离。
最后,我们用新列AO扩展复制的数据框。这正是实际的AO值。当正值短期动量高于长期时,这是看涨信号。当负值短期动量较低时,这是看跌信号,表明反弹正在消退。正如前一篇文章所强调的,AO是零线驱动的,因此交叉很重要。
包络通道函数
我们在Python中实现包络线的逻辑如下:
def Envelope_Channels(df: pd.DataFrame, period: int = 20, deviation: float = 0.025) -> pd.DataFrame: """ Calculate Envelope Channels (Upper & Lower Bands) and append to the input DataFrame. Envelope Channels = SMA(close, period) * (1 ± deviation) Args: df (pd.DataFrame): DataFrame with 'close' column. period (int): Period for SMA calculation (default 20). deviation (float): Deviation as a decimal (e.g., 0.025 for 2.5%, default 0.025). Returns: pd.DataFrame: Input DataFrame with 'Envelope_Upper' and 'Envelope_Lower' columns added. """ required_cols = {'close'} if not required_cols.issubset(df.columns): raise ValueError("DataFrame must contain 'close' column") if period <= 0 or deviation < 0: raise ValueError("Period must be positive and deviation non-negative") result_df = df.copy() sma = result_df['close'].rolling(window=period).mean() result_df['Envelope_Upper'] = sma * (1 + deviation) result_df['Envelope_Lower'] = sma * (1 - deviation) result_df['Envelope_Mid'] = 0.5 * (result_df['Envelope_Upper'] + result_df['Envelope_Lower']) return result_df
与AO一样,签名带有类型提示,并为周期和偏差输入使用"合理"的默认值。我们对输入数据框的验证主要围绕检查是否包含close列。这是我们防止垃圾进垃圾出的唯一要求。我们还检查确保输入周期不为零且偏差非负。如果不满足这些要求,我们会得到一个终止脚本运行的值错误。
我们还复制输入数据框,称之为结果数据框,这是一种安全的做法,如上所述及过往文章中所论证的。我们最先计算的是收盘价的简单移动平均线(SMA)基于此基线构建包络线。更平滑的SMA意味着更少的震荡,但也伴随着更多的滞后。它作为通道的重心。基于这条基线,我们继续计算上下包络缓冲区,也就是通道。
价格向上突破上轨可能表示超买情况,而跌破下轨则可能意味着超卖。偏差以小数形式使用,默认值为2.5%或0.025。偏差值的选择应对所交易的资产最优,因为它往往是一个非常敏感的值。波动性更大的资产可以使用高达5%的偏差。我们追加到数据框的额外列是包络中线,其值由上下轨的平均值计算得出。
特征
我们来测试前一篇文章中介绍的10种信号形态中的3种。在本文中,我们称这些信号形态为特征,因为它们作为网络的输入,但就我们的目的而言,这两个名称可以互换使用。从前一篇文章以及本系列文章的情况来看,每个特征是一个将两个指标信号汇集在一起的向量。它是一个由0和1组成的位序列。我们在过往文章中探讨过将特征大小从仅2个扩展,使其映射到每个指标的看涨或看跌信号检查。与专注于整体看涨和看跌信号时获得的结果相比,这样的测试结果相当糟糕。
我们重新访问特征4、特征8和特征9。因此,我们在此采用的这些特征函数的一般结构与一直使用的没什么区别。每个函数输出一个2D NumPy 数组,其形状为数据框的行数,列数为两列。每行的[0]索引是独特的看涨或买入形态;而每行的[1]是独特的看跌或卖出标记。在我们的格式中,1表示存在买入/卖出形态,而0表示不存在。每个形态是两个指标信号的组合,即AO和包络通道。我们在进行多K线比较时使用每个数据框中的shift[n]。因此,每个加载的数据框需要有足够的数据。
特征4
回顾一下,该特征的核心逻辑是:当AO在零轴上方形成低谷且价格处于包络线下半部分时,我们标记为看涨信号。相反,当AO在零轴下方形成峰值且价格处于包络线上半部分时,则为看跌信号。在Python中的具体实现如下:
def feature_4(df): """ //+------------------------------------------------------------------+ //| Check for Pattern 4. | //+------------------------------------------------------------------+ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['AO'].shift(2) > df['AO'].shift(1)) & (df['AO'].shift(1) < df['AO']) & (df['AO'].shift(1) > 0.0) & (df['close'].shift(2) >= df['Envelope_Mid'].shift(2)) & (df['close'].shift(2) <= df['Envelope_Lower'].shift(2)) & (df['close'].shift(1) >= df['Envelope_Mid'].shift(1)) & (df['close'].shift(1) <= df['Envelope_Lower'].shift(1)) & (df['close'] >= df['Envelope_Mid']) & (df['close'] <= df['Envelope_Lower'])).astype(int) feature[:, 1] = ((df['AO'].shift(2) < df['AO'].shift(1)) & (df['AO'].shift(1) > df['AO']) & (df['AO'].shift(1) < 0.0) & (df['close'].shift(2) <= df['Envelope_Mid'].shift(2)) & (df['close'].shift(2) >= df['Envelope_Upper'].shift(2)) & (df['close'].shift(1) <= df['Envelope_Mid'].shift(1)) & (df['close'].shift(1) >= df['Envelope_Upper'].shift(1)) & (df['close'] <= df['Envelope_Mid']) & (df['close'] >= df['Envelope_Upper'])).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
我们在上面的代码中首先准备输出数组,通过用0填充并确保它有两列来反映/假设没有信号。之后通过检查所有看涨指标要求是否都已满足,为第一列分配索引。AO和包络线要求都需要满足,任何列才能收到1。AO条件是它形成V形,这是动量探底回升或看涨反转线索的迹象。这个V形还需要完全在零轴上方或在看涨区域。包络线条件是价格徘徊在包络中线附近或下方,但不过度超卖。这可以等同于回调而非崩溃,适合反向操作或趋势恢复。
将任何列赋值为1几乎肯定意味着另一列将为0,因为这些信号彼此互为镜像。因此,我们的看跌逻辑是上述逻辑的反转。它需要AO在零轴下方的"峰值"且价格卡在包络线上半部分。我们在特征4函数中最后做的是将前两行赋值为0,因为我们执行的比较跨越了最多两个索引。如过去所论证的,这可以防止序列开头由shift引起的NaN导致的意外信号。
当我们用上面的CNN作为过滤器补充特征4时,我们执行优化和前向运行测试。回顾前一篇文章,我们的交易品种是美元兑日元(USDJPY),时间框架是30分钟,一如既往,至少今年,测试窗口是2023年和2024年。特征4相比前一篇文章前向步进有所提升,尽管有亏损。报告如下:


特征8
如上一篇文章所述,该信号形态的看涨信号是AO和价格持续上涨,AO在零轴上方且价格逐步远离包络下轨。看跌逻辑是AO和价格向下暴跌,AO在零轴下方,且价格跌破下轨。该特征在Python中的实现如下:
def feature_8(df): """ //+------------------------------------------------------------------+ //| Check for Pattern 8. | //+------------------------------------------------------------------+ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['AO'].shift(2) > 0.0) & (df['AO'].shift(1) > df['AO'].shift(2)) & (df['AO'] > df['AO'].shift(1)) & (df['close'].shift(2) > df['Envelope_Lower'].shift(2)) & (df['close'].shift(1) > df['close'].shift(2)) & (df['close'] > df['close'].shift(1))).astype(int) feature[:, 1] = ((df['AO'].shift(2) < 0.0) & (df['AO'].shift(1) < df['AO'].shift(2)) & (df['AO'] > df['AO'].shift(1)) & (df['close'].shift(2) < df['Envelope_Lower'].shift(2)) & (df['close'].shift(1) < df['close'].shift(2)) & (df['close'] < df['close'].shift(1))).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
因此,从上面的代码来看,如果AO持续上涨且价格从正区域明显加速,我们给第一个索引赋值为1,确认看涨信号存在。我们还需要确认价格保持在包络下轨上方,且这不是一次死猫反弹。如果AO开始为负值并持续下跌,但随后可能在一个回调中回升,而价格在强劲下跌趋势中继续跌破下轨,则第二个索引为1。然后我们对前两个索引进行归零处理,如同特征4一样。在测试特征8时,我们仍然未能获得盈利的前向步进。报告如下:


特征9
我们最终的信号形态核心逻辑,如前一篇文章所述,是AO回落但仍保持在零轴上方,同时价格下探至中线并反弹,形成看涨信号。对于看跌信号,AO回升但仍保持在零轴下方,价格反弹至中线,但随后未能突破。在Python中的具体实现如下:
def feature_9(df): """ //+------------------------------------------------------------------+ //| Check for Pattern 9. | //+------------------------------------------------------------------+ """ feature = np.zeros((len(df), 2)) feature[:, 0] = ((df['AO'].shift(2) > df['AO'].shift(1)) & (df['AO'].shift(1) > df['AO']) & (df['AO'] > 0.0) & (df['close'].shift(2) > df['Envelope_Mid'].shift(2)) & (df['close'].shift(1) <= df['Envelope_Mid'].shift(1)) & (df['close'] > df['Envelope_Mid'])).astype(int) feature[:, 1] = ((df['AO'].shift(2) < df['AO'].shift(1)) & (df['AO'].shift(1) < df['AO']) & (df['AO'] < 0.0) & (df['close'].shift(2) < df['Envelope_Mid'].shift(2)) & (df['close'].shift(1) >= df['Envelope_Mid'].shift(1)) & (df['close'] < df['Envelope_Mid'])).astype(int) feature[0, :] = 0 feature[1, :] = 0 return feature
我们的实现理念与上面涵盖的两个函数一致,因此我不会重复已经涵盖的要点。测试结果显示,该特征的前向步进表现已接近有利。报告如下所示:


结论
综上所述,我们探讨了机器学习的集成,特别是通过使用由点积核引导并带有跨时间注意力的CNN,以改进AO和包络通道的信号。我们的测试运行聚焦于先前测试过的三种特征(特征4、特征8和特征9),以确定添加更高级的神经网络过滤机制是否能增强其预测性能。
动态CNN方法确实是富有成效的,特别是对于特征4和特征9,我们将先前惨淡的前向步进转变为亏损更受控制的运行。核的注意力机制允许网络基于时间相关性动态调整核大小和通道维度,有助于捕捉到市场行为中更深层的模式。这种适应性对我们很有帮助,因为我们处理的是非平稳金融市场。这里的情况是,刚性模型通常因波动率、趋势动态或市场机制的变化而表现不佳。
然而,并非所有结果都是积极的。严重依赖持续方向性动量的特征8,即使借助卷积神经网络的改进适应性,也未能实现盈利。这表明某些信号无论模型如何增强都是有限的,凸显了交易策略中指标和信号选择的重要性。除了本文探讨的监督学习外,我们可以重新审视强化学习作为改进信号的手段。在下一篇或后续文章中,可以针对此处讨论的特征探索这一点。
| 名称 | 描述 |
|---|---|
| WZ-76.mq5 | 向导组装式EA,其头文件显示组装中所包含的文件 |
| SignalWZ-76.mqh | 向导组装中使用的自定义信号类文件 |
| 76-4.onnx | 信号形态4的导出网络 |
| 76-8.onnx | 信号形态8的导出网络 |
| 76-9.onnx | 信号形态9的导出网络 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18878
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
市场模拟(第 24 部分):SQL 入门(七)
从基础到中级:函数指针
数据科学与机器学习(第四十六部分):在Python中使用N-BEATS进行股票市场预测