您应该了解的MQL5向导技巧(第七十六部分):利用AO形态与包络通道进行监督学习建模
📘

您应该了解的MQL5向导技巧(第七十六部分):利用AO形态与包络通道进行监督学习建模

第 1/2 篇

从形态检验到神经网络过滤

前两篇文章里,动量震荡叠加包络通道的组合策略在两年前向测试窗口中,10类形态有7至8类通过了步进检验。这个通过率说明形态本身有边际有效性,但仍有近三成形态在前向样本上失效。 既然指标组合已经筛过一轮,下一个自然问题是:机器学习能不能替我们把剩下的噪声信号再压一压。本文挑了第4、8、9类形态做对象,用监督学习网络当信号过滤器,看它能不能改善这三类的交易表现。 具体用的是CNN模型,靠跨时间注意力点积核去自适应调卷积核和特征通道的维度参数。外汇和贵金属市场高风险,这类优化只代表历史样本上的概率倾向,实盘仍需自行验证。

◍ 用跨时间点积给CNN卷积核瘦身扩容

跨时间注意力点积核干的事很直接:拿两组时序特征 X=[x1…xT] 和 Y=[y1…yS],对每一对 (xi, yj) 算 Scorei,j = xi·yj,再对 i 或 j 做 Softmax 拿到权重,拿去重新加权融合跨时间特征。除投影参数外不训额外参数,能啃变长序列、非均匀采样,也能让模型盯着关键时序节点而不是卷积累积的局部邻域。 把它塞进 CNN 不是为了炫技。CNN 天生盯局部,点积核能按数据自身的时序节律自适应放大或收缩有效感受野;在通道维还能筛出谁被持续忽略——一直没权重的通道就是冗余,可直接剪掉压缩规模,若注意力散得一塌糊涂则说明维度不够,反向指导加通道。思路和我们先前动态 CNN 的路线一致:别拿一套固定核/通道硬套非平稳的贵金属、外汇行情。 代价也得摊开说。复杂度随序列长走 O(N²),传统静态 CNN 才 O(N),长时序下显卡吃不消;注意力分数叠上动态核选择,解释性反而更糊。实测里纯局部任务(如图像边缘)传统 CNN 仍胜出,且这机制训起来比传统 CNN 更饿数据,动态调控过头容易把行情里的随机扰动当规律拟合,过拟合风险偏高——外汇/贵金属杠杆品种本就高波动,这么搞更得留神。 真要换路子,空洞卷积不涨核就能扩感受野,动态卷积按输入调权重,深度可分离卷积只动通道换效率,SE 模块、TCN 也都是现成改良。开 MT5 接历史 tick 跑一遍 O(N²) 开销对比,比纸上谈参数实在。

「动态卷积加交叉时间注意力的混合骨架」

这套 1D 时序网络把 CNN 局部提取和两种注意力机制揉在一起:常规多头注意力负责全局跨步依赖,自写的点积核负责显式计算时间步两两相似度。初始化时不再写死卷积尺寸,而是用 _design_architecture 按规则生成——代码里循环 10 层,核宽按 3,5,7,9 周期轮转,通道数从 32 倍增到 320,相当于每层学更抽象表征。 层堆叠用 nn.ModuleList 实现可变深度,填充取 kernel_size//2,所以绝大多数层输入输出长度对齐,时序任务里这能保住每个时间步不错位。每隔一层(i%2==0)插一个 4 头 MultiheadAttention,交替而不是逐层插,明显省算力,也让局部和全局推理能切换。 _dot_product_kernel 里先把形状 permute 成 (B,L,C),用 torch.bmm 算 (B,L,L) 的点积分数,再除以嵌入维平方根做 softmax 温度缩放,最后用注意力权重回混特征并恢复原形。这个跨时间感知是单独卷积拿不到的。外汇和贵金属行情用这类模型预测趋势属于高风险,历史适配不代表后续胜率。 前向里先 unsqueeze(1) 补通道维满足 1D 卷积要求,每层卷积后若有注意力层就 permute 成 (L,B,C) 喂给 PyTorch 多头接口,出来再 permute 回去并叠一次点积核。头部用 AdaptiveAvgPool1d(1) 把任意长度压成定长,展平后三层全连接加 dropout,Sigmoid 吐单值:<0.5 倾向跌,>0.5 倾向涨。想验证就直接把下面代码丢进 MT5 的 Python 环境跑 forward,看不同 input_length 下输出是否稳定。

C++
class="kw">import torch

class="kw">import torch.nn as nn
class="kw">import torch.nn.functional as F


class DotProductAttentionConv1D(nn.Module):

    def __init__(self, input_length=class="num">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 = class="num">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 class=class="str">"cmt">// class="num">2),
                nn.BatchNorm1d(out_channels),
                nn.ReLU(),
                nn.Dropout(class="num">0.2))
            self.conv_layers.append(conv_layer)
            
            # Attention block(cross-time attention)
            if i % class="num">2 == class="num">0:  # Apply attention to every other layer
                attn_layer = nn.MultiheadAttention(embed_dim=out_channels, num_heads=class="num">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(class="num">1),
            nn.Flatten(),
            nn.Linear(in_channels, class="num">128),
            nn.ReLU(),
            nn.Dropout(class="num">0.2),
            nn.Linear(class="num">128, class="num">64),
            nn.ReLU(),
            nn.Linear(class="num">64, class="num">1),
            nn.Sigmoid()
        )


    def _dot_product_kernel(self, x):
        """Dot product similarity kernel with positional encoding"""
        # x shape: (B, C, L)
        x = x.permute(class="num">0, class="num">2, class="num">1)  # (B, L, C)
        # Compute dot product attention
        attention_scores = torch.bmm(x, x.transpose(class="num">1, class="num">2))  # (B, L, L)
        attention_weights = F.softmax(attention_scores / (x.size(-class="num">1) ** class="num">0.5), dim=-class="num">1)
        class="kw">return torch.bmm(attention_weights, x).permute(class="num">0, class="num">2, class="num">1)  # (B, C, L)


    def _design_architecture(self):
        # Simulate attention response pattern
        num_layers = class="num">10
        kernel_sizes = [class="num">3 + (i % class="num">4) * class="num">2 for i in range(num_layers)]  # cyclic class="num">3, class="num">5, class="num">7, class="num">9
        channels = [class="num">32 * (i + class="num">1) for i in range(num_layers)]  # class="num">32, class="num">64, ..., class="num">320
        
        class="kw">return kernel_sizes, channels


    def forward(self, x):
        x = x.unsqueeze(class="num">1)  # (B, class="num">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(class="num">2, class="num">0, class="num">1)  # (L, B, C)
                attn_output, _ = attn_layer(attn_input, attn_input, attn_input)
                x = attn_output.permute(class="num">1, class="num">2, class="num">0)  # (B, C, L)
                
                # Also apply dot product kernel
                x = self._dot_product_kernel(x)
        
        class="kw">return self.head(x)

在 Python 里手写 AO 动量震荡函数

MT5 的 Python 接口不会自带 MQL5 那套指标函数,想用比尔·威廉姆斯的动量震荡指标(AO)只能自己造轮子。上面这段函数用 pandas 做时间序列滚动窗口,NumPy 在底层被隐式调用撑住数值效率,函数签名里 short_period 默认 5、long_period 默认 34,和经典设置一致。 进函数先卡两道防御:必须有 high、low 列,周期必须是正整数,否则直接抛带信息的 ValueError 早失败。外部数据永远别信,漏掉这步后面 NaN 会咬人。 接着 df.copy() 非破坏性复制,保住原始 K 线。中位数价取 (high+low)/2 而非收盘价,避开开收震荡噪音;再用 rolling().mean() 算长短两条 SMA,差值写进新列 AO。AO 是零线驱动——短期 SMA 减长期 SMA 为正偏多、为负偏空,交叉动作比绝对值更关键。 外汇与贵金属波动剧烈,AO 仅作概率倾向参考,实盘前请在 MT5 Python 环境用历史数据回测验证。

C++
def Awesome_Oscillator(df: pd.DataFrame, short_period: class="type">int = class="num">5, long_period: class="type">int = class="num">34) -> pd.DataFrame:

    """

    Calculate the Bill Williams Awesome Oscillator(AO) and append it to the class="kw">input DataFrame.

    

    AO = SMA(Median Price, short_period) - SMA(Median Price, long_period)



    Args:

        df(pd.DataFrame): DataFrame with &class="macro">#x27;high&class="macro">#x27; and &class="macro">#x27;low&class="macro">#x27; columns.

        short_period(class="type">int): Short period for SMA(class="kw">default class="num">5).

        long_period(class="type">int): Long period for SMA(class="kw">default class="num">34).



    Returns:

        pd.DataFrame: Input DataFrame with &class="macro">#x27;AO&class="macro">#x27; column added.

    """

    required_cols = {&class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;}

    if not required_cols.issubset(df.columns):

        raise ValueError("DataFrame must contain &class="macro">#x27;high&class="macro">#x27; and &class="macro">#x27;low&class="macro">#x27; columns")

    if not all(p > class="num">0 for p in [short_period, long_period]):

        raise ValueError("Period values must be positive integers")



    result_df = df.copy()

    median_price = (result_df[&class="macro">#x27;high&class="macro">#x27;] + result_df[&class="macro">#x27;low&class="macro">#x27;]) / class="num">2

    short_sma = median_price.rolling(window=short_period).mean()

    long_sma = median_price.rolling(window=long_period).mean()

    result_df[&class="macro">#x27;AO&class="macro">#x27;] = short_sma - long_sma



    class="kw">return result_df

常见问题

先用历史K线回看形态出现后的价格偏移分布,统计胜率和平均位移;只有偏移显著异于随机才值得建模,否则直接弃用。
把相邻时间步特征做点积压缩冗余维度,保留方差最大的通道;实操中先跑消融实验,核参减少30%内精度掉不超1%就可用。
可以,小布内置AIGC诊断能把你写的Python形态函数接进去,自动出跨周期回测和卷积骨架对比,省去手写训练流水线。
概率偏高,建议按年度切分做滚动验证;注意力权重若集中单月说明泄漏,需加dropout和通道正则。
自己写能改平滑窗口和零点阈值,方便接监督标签;若只做标准5/34周期可复用,要喂模型就建议手写可控版本。