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

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

第 2/2 篇

包络通道的基线构造逻辑

包络通道的核心是先用收盘价算一条简单移动平均线(SMA)作为重心,再按偏差比例在上下方展开两条轨。SMA 周期越长,通道越平滑、假突破越少,但滞后也越明显;默认 20 周期配合 2.5% 偏差(0.025)是通用起点,波动大的品种可放到 5%。 函数入口做了三道防火墙:必须含 close 列,周期须为正,偏差不能为负,否则直接抛 ValueError 终止,避免垃圾数据进模型。随后复制原数据框再计算,防止篡改上游数据。 中线由上下轨平均得出,价格刺穿上轨倾向超买、跌破下轨倾向超卖,但外汇与贵金属波动剧烈,此类信号仅作概率参考,实盘须自担高风险。 下面这段 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 逐行看:第 1 行定义函数并给 period、deviation 设默认;required_cols 检查 close 是否存在,缺失即报错;period<=0 或 deviation<0 同样终止。result_df 是副本,rolling(window=period).mean() 算 SMA;上下轨等于 SMA 乘 (1±偏差),中线取二者均值后随表返回。

C++
def Envelope_Channels(df: pd.DataFrame, period: class="type">int = class="num">20, deviation: class="type">float = class="num">0.025) -> pd.DataFrame:

    """

    Calculate Envelope Channels(Upper & Lower Bands) and append to the class="kw">input DataFrame.



    Envelope Channels = SMA(close, period) * (class="num">1 ± deviation)



    Args:

        df(pd.DataFrame): DataFrame with &class="macro">#x27;close&class="macro">#x27; column.

        period(class="type">int): Period for SMA calculation(class="kw">default class="num">20).

        deviation(class="type">float): Deviation as a decimal(e.g., class="num">0.025 for class="num">2.5%, class="kw">default class="num">0.025).



    Returns:

        pd.DataFrame: Input DataFrame with &class="macro">#x27;Envelope_Upper&class="macro">#x27; and &class="macro">#x27;Envelope_Lower&class="macro">#x27; columns added.

    """

    required_cols = {&class="macro">#x27;close&class="macro">#x27;}

    if not required_cols.issubset(df.columns):

        raise ValueError("DataFrame must contain &class="macro">#x27;close&class="macro">#x27; column")

    if period <= class="num">0 or deviation < class="num">0:

        raise ValueError("Period must be positive and deviation non-negative")



    result_df = df.copy()

    sma = result_df[&class="macro">#x27;close&class="macro">#x27;].rolling(window=period).mean()

    result_df[&class="macro">#x27;Envelope_Upper&class="macro">#x27;] = sma * (class="num">1 + deviation)

    result_df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;] = sma * (class="num">1 - deviation)

    result_df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;] = class="num">0.5 * (result_df[&class="macro">#x27;Envelope_Upper&class="macro">#x27;] + result_df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;])



    class="kw">return result_df

◍ 把信号形态当特征喂给网络

前一篇里列了10种信号形态,这次只挑其中3种拿来做验证。我们内部把它们叫“特征”,本质就是网络的输入向量,由两个指标信号拼成,是一串0和1的位序列。 曾经试过把特征维度从2扩到覆盖每个指标的独立多空检查,回测结果比只用整体看涨/看跌信号差了一截,所以本次回到特征4、8、9这三种老结构。 这三个特征函数输出都是形状为(数据行数, 2)的2D数组:每行[0]位是买入形态标记,[1]位是卖出形态标记,1代表形态成立、0代表不成立。组合来源是AO与包络通道,多K线比对靠shift[n]偏移,因此每个加载的数据框必须留足历史长度,否则靠前几行会直接空窗。 外汇与贵金属行情跳空频繁,这类基于移位比对的形态在流动性断层处容易误触发,实盘前建议在MT5用历史数据跑一遍特征命中率。

「AO与包络线共振的反转信号写法」

这套逻辑把AO的局部形态和包络线位置绑在一起:AO在零轴上方走出V形低谷,且价格落在包络线下半区,判为看涨;AO在零轴下方走出峰值,价格卡在包络线上半区,判为看跌。本质是把动量探底回升和回调位叠加,倾向当作趋势恢复或反向切入点,外汇与贵金属这类高波动品种上需警惕假突破。 代码先用 np.zeros 开一个两列矩阵,默认全0代表无信号。第一列走看涨分支:shift(1) 的AO同时小于前后两根,构成V形且大于0;同时连续三根close都介于Envelope_Mid和Envelope_Lower之间,说明价格只是回踩下半带。第二列是镜像,AO峰值在零轴下、价格贴着上半带。两列互斥,任一置1另一列基本是0。 feature[0]和feature[1]强行清0,是因为shift跨两根会在序列头产生NaN,不处理会漏出脏信号。我们拿USDJPY的30分钟线、用2023和2024年窗口做前向测试,接上CNN过滤后,特征4相比前一篇前向步进有改善,但期间仍有亏损,说明它只是概率倾斜而非胜率保证。

C++
def feature_4(df):

    """

class=class="str">"cmt">//+------------------------------------------------------------------+

class=class="str">"cmt">//| Check for Pattern class="num">4.                                             |

class=class="str">"cmt">//+------------------------------------------------------------------+



    """
    feature = np.zeros((len(df), class="num">2))
    
    feature[:, class="num">0] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) > df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) < df[&class="macro">#x27;AO&class="macro">#x27;]) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) > class="num">0.0) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) >= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) <= df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) >= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) <= df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] >= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;]) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] <= df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;])).astype(class="type">int)
    
    feature[:, class="num">1] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) < df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) > df[&class="macro">#x27;AO&class="macro">#x27;]) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) < class="num">0.0) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) <= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) >= df[&class="macro">#x27;Envelope_Upper&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) <= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) >= df[&class="macro">#x27;Envelope_Upper&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] <= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;]) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] >= df[&class="macro">#x27;Envelope_Upper&class="macro">#x27;])).astype(class="type">int)
    
    feature[class="num">0, :] = class="num">0
    feature[class="num">1, :] = class="num">0
    
    class="kw">return feature

AO加速脱离下轨的双向判定

看涨一侧要求 AO 连续三根 Bar 站在零轴上且逐根抬高,同时收盘价始终位于包络下轨之上且自身也在爬升;看跌一侧则是 AO 前两根在零轴下、第二根比第一根更低,但第三根出现回升,价格却延续跌破下轨的惯性。这种结构想过滤掉死猫反弹,靠的是价格与 AO 的背离确认而非单根反转。 下面这段 Python 实现把上述逻辑直接向量化:第一列标记看涨,第二列标记看跌,前两根强行归零避免边界噪声。注意 shift(2) 代表往前数两根,确保是「连续」而非「任意」满足条件。 回测里特征8同样没跑出盈利的前向步进结果,说明单纯用 AO 加速 + 下轨距离做信号,在样本外大概率失效。外汇与贵金属波动受消息扰动大,这类形态只适合当过滤条件,别单独开仓。

C++
def feature_8(df):

    """
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern class="num">8.                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
    """
    feature = np.zeros((len(df), class="num">2))
    
    feature[:, class="num">0] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) > class="num">0.0) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) > df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;] > df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) > df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) > df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] > df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1))).astype(class="type">int)
    
    feature[:, class="num">1] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) < class="num">0.0) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) < df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;] > df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) < df[&class="macro">#x27;Envelope_Lower&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) < df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] < df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1))).astype(class="type">int)
    
    feature[class="num">0, :] = class="num">0
    feature[class="num">1, :] = class="num">0
    
    class="kw">return feature

◍ AO贴零轴不破的中线反弹信号

这套形态的核心不看价格穿越,只看AO与 envelopes 中线的相对位置。看涨侧要求AO连续三根回落但始终站在零轴上方,同时收盘价从中线上方跌回、再收回中线之上;看跌侧反之,AO三连升却不破零轴下方,价格碰中线后掉回。外汇与贵金属波动大,此类信号只是概率倾斜,不等于反向必败。 代码里用 shift(2)、shift(1)、当前根三层错位来锁定‘连续三根’的节奏,feature[:,0] 装看涨标签,feature[:,1] 装看跌标签,前两行强制置0避免前视泄漏。 回测报告里这个特征的前向步进已经接近正期望,说明单拎出来也有微弱优势,但实盘仍要配合风控。打开 MT5 把 AO 与中线叠加,手动比对几段 EURUSD H1,能直观确认这种‘不破零轴’的结构频率。

C++
def feature_9(df):

    """
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for Pattern class="num">9.                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
    """
    feature = np.zeros((len(df), class="num">2))
    
    feature[:, class="num">0] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) > df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) > df[&class="macro">#x27;AO&class="macro">#x27;]) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;] > class="num">0.0) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) > df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) <= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] > df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;])).astype(class="type">int)
    
    feature[:, class="num">1] = ((df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">2) < df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;].shift(class="num">1) < df[&class="macro">#x27;AO&class="macro">#x27;]) &
                     (df[&class="macro">#x27;AO&class="macro">#x27;] < class="num">0.0) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">2) < df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">2)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;].shift(class="num">1) >= df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;].shift(class="num">1)) &
                     (df[&class="macro">#x27;close&class="macro">#x27;] < df[&class="macro">#x27;Envelope_Mid&class="macro">#x27;])).astype(class="type">int)
    
    feature[class="num">0, :] = class="num">0
    feature[class="num">1, :] = class="num">0
    
    class="kw">return feature

「别急着下结论」

把点积核和跨时间注意力塞进 CNN 之后,特征 4 和特征 9 的前向步进从原先的惨淡曲线变成了亏损更可控的运行,这说明非平稳市场里刚性模型容易翻车,而能按时间相关性调核大小与通道维度的结构确实更扛揍。 特征 8 是个反例:它重度依赖持续方向性动量,即便叠了卷积神经网络的适应机制,回测依旧没能转正,提醒我们信号底子不行时,堆模型层数也救不回来。外汇与贵金属属高风险品类,这类过滤改进只降低回撤幅度,不承诺胜率。 后续若换强化学习重训这几组特征,建议先在 MT5 用历史中心模式跑一遍特征 9 的对照,再决定要不要上实盘观察。

常见问题

常见的基线偏移在0.5%~2%之间,偏低适合趋势市,偏高适合震荡市;建议先在对应品种历史数据里回测再定参数。
核心取AO柱体的斜率、相对包络线的位置、零轴穿越次数三类字段;其余可补振幅和连续同向计数,避免维度爆炸。
可以,小布能在对应品种页直接标注AO触下轨且包络收口的共振区,并给出概率倾向提示,省去手动画线。
写判定时加一条「连续N根AO绝对值小于阈值且不反向破零」,配合价格未破前低,才视为反弹倾向成立。
优先按价格确认,AO单独加速只作预警;等价格实体收破近期结构位再动手,贵金属和高杠杆外汇风险偏高。