您应该了解的MQL5向导技巧(第七十部分):结合指数核网络使用SAR与RVI形态(基础篇)
📘

您应该了解的MQL5向导技巧(第七十部分):结合指数核网络使用SAR与RVI形态(基础篇)

第 1/2 篇

◍ 用向导把 SAR 与 RVI 形态喂给指数核网络

MT5 的 MQL5 向导能省掉手写信号类的麻烦,这次演示的是把 SAR 反转点和 RVI 摆动形态一起送进指数核网络做分类。思路不复杂:SAR 管趋势转向,RVI 管动能强弱,两者叠加后让核网络判断当前属于哪一类市况。 实际在向导里选信号组时,把 SAR 和 RVI 同时勾上,再指定指数核网络作为训练后端,系统会按历史样本拟合权重。外汇与贵金属杠杆高,信号失效时回撤可能很快,任何形态组合都只是概率倾向,不是方向保证。 验证方法很直接:生成 EA 后丢进策略测试器,用 2020–2024 的 XAUUSD H1 数据跑样本外,观察核网络在 SAR 假突破段的误判率,再决定是否调 RVI 的周期参数。

用映射值唤醒被弃用的 SAR/RVI 形态

上一节给出的 SAR 与 RVI 组合共测了 10 种形态,编号 0 到 9,其中索引 1、2、6 在前进测试里失效,被暂时关掉。EA 端用 PatternsUsed 参数按 2 的幂调用:索引 1 对应 2^1=2,索引 2 对应 2^2=4,索引 6 对应 2^6=64;10 种全开就是 2^10-1=1023。0–1023 之间不是纯幂的值代表多形态叠加,可自行让 EA 混跑,但本系列先不展开。 这一篇要做的,是用监督学习把失效的形态 1、2、6 重新训活。实现上选 Python 写模型与训练——即便没 GPU,开发和数据吞吐也够快。连行情走 MetaTrader 5 的 Python 模块,填账号密码就能直连券商 MT 服务器。 连上后券商历史价就能拉回来。Python 技术指标库大多要另装且格式别扭,SAR 和 RVI 从公式手搓并不难,所以这一步先在 Python 里复刻这两个指标,为后面接模型做准备。外汇和贵金属波动剧烈,任何形态重构都只是提高信号概率,实盘前请在 MT5 用历史数据回测验证。

「用 Python 复刻抛物转向的止损跟踪逻辑」

抛物转向(SAR)属于趋势跟踪类工具,靠点位落在价格一侧来提示趋势可能反转的位置。多头时 SAR 在波段低点下方,空头时在波段高点上方,本质上就是一条随趋势加速移动的止损参考线。 下面这段函数接收含 high、low、close 列的 pandas 数据帧,以及加速因子 af 与上限 af_max,默认 0.02 和 0.2,输出时把算好的 SAR 作为新列塞回原表。先 copy 一份避免改坏原始数据,再把 high、low 提出来当 NumPy 数组跑循环,比直接操 pandas 序列快。 初始化很关键:趋势先假定多头(trend=1),极值点 ep 取首根最高价,首期 SAR 取首根最低价,加速因子从 af 起步。这样 SAR 在多头起点就天然躺在价格下方,和它本身做止损位的角色一致。 循环里每根 K 线先按 sar = 上一 sar + af_current*(ep - 上一 sar) 向极值点靠拢。若多头中当期最低价跌破 SAR,就翻空:SAR 重置为上期 ep,ep 改当期最低价,af 归位。若延续多头,则 SAR 不超过前两期最低价较小值,且新高刷新 ep 时 af 按步长递增但封顶 af_max。

MQL5 / C++
<span class="keyword">def</span> SAR(df: pd.DataFrame, af: <span class="built_in">class="type">class="kw">float</span> = <span class="number">class="num">0.02</span>, af_max: <span class="built_in">class="type">class="kw">float</span> = <span class="number">class="num">0.2</span>) -&gt; pd.DataFrame:
&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"""
&nbsp;&nbsp;&nbsp;&nbsp;Calculate Parabolic SAR indicator and append it as &class="macro">#x27;SAR&class="macro">#x27; column to the input DataFrame.
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;Args:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;df(pd.DataFrame): DataFrame with &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27; columns
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af(class="type">class="kw">float): Acceleration factor, class="kw">default class="num">0.02
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af_max(class="type">class="kw">float): Maximum acceleration factor, class="kw">default class="num">0.2
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;Returns:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pd.DataFrame: Input DataFrame with new &class="macro">#x27;SAR&class="macro">#x27; column
&nbsp;&nbsp;&nbsp;&nbsp;"""</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">all</span>(col <span class="keyword">in</span> df.columns <span class="keyword">for</span> col <span class="keyword">in</span> [<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;low&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>]):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"DataFrame must contain &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27; columns"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> af &lt;= <span class="number">class="num">0</span> <span class="keyword">or</span> af_max &lt;= <span class="number">class="num">0</span> <span class="keyword">or</span> af &gt; af_max:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"Invalid acceleration factors"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> df.empty:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"DataFrame is empty"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;result_df = df.copy()
&nbsp;&nbsp;&nbsp;&nbsp;result_df[<span class="class="type">class="kw">string">&class="macro">#x27;SAR&class="macro">#x27;</span>] = <span class="number">class="num">0.0</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;sar = df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;ep = df[<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;af_current = af
&nbsp;&nbsp;&nbsp;&nbsp;trend = <span class="number">class="num">1</span> <span class="keyword">if</span> <span class="built_in">len</span>(df) &gt; <span class="number">class="num">1</span> <span class="keyword">and</span> df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">1</span>] &gt; df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>] <span class="keyword">else</span> -<span class="number">class="num">1</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">class="num">1</span>, <span class="built_in">len</span>(df)):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prev_sar = sar
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;high, low = df[<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>].iloc[i], df[<span class="class="type">class="kw">string">&class="macro">#x27;low&class="macro">#x27;</span>].iloc[i]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> trend &gt; <span class="number">class="num">0</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sar = prev_sar + af_current * (ep - prev_sar)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> low &lt; sar:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trend = -<span class="number">class="num">1</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sar = ep
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ep = low
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af_current = af
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>:
逐行拆一下:def 行定义函数与默认参数;前三个 if 做列名、因子合法性、空表校验;result_df.copy() 隔离原数据,新建 SAR 列填 0.0;sar 初值取首收,ep 取首高,af_current 用传入 af;trend 靠前两收对比定多空。循环内 prev_sar 暂存,多头分支先算新 sar,若 low < sar 则翻空并重置 sar/ep/af,否则走延续分支。 外汇与贵金属属高风险品种,SAR 在趋势市能提示止损与反转倾向,但在震荡或快速波动市可能滞后并频发假信号。把上面代码贴进 MT5 外的 Python 环境,用经纪商历史数据回测,调 af 与 af_max 看反转灵敏度差异,是验证它适不适合你品种的直接办法。

MQL5 / C++
<span class="keyword">def</span> SAR(df: pd.DataFrame, af: <span class="built_in">class="type">class="kw">float</span> = <span class="number">class="num">0.02</span>, af_max: <span class="built_in">class="type">class="kw">float</span> = <span class="number">class="num">0.2</span>) -&gt; pd.DataFrame:
&nbsp;&nbsp;&nbsp;&nbsp;<span class="class="type">class="kw">string">"""
&nbsp;&nbsp;&nbsp;&nbsp;Calculate Parabolic SAR indicator and append it as &class="macro">#x27;SAR&class="macro">#x27; column to the input DataFrame.
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;Args:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;df(pd.DataFrame): DataFrame with &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27; columns
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af(class="type">class="kw">float): Acceleration factor, class="kw">default class="num">0.02
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af_max(class="type">class="kw">float): Maximum acceleration factor, class="kw">default class="num">0.2
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;Returns:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pd.DataFrame: Input DataFrame with new &class="macro">#x27;SAR&class="macro">#x27; column
&nbsp;&nbsp;&nbsp;&nbsp;"""</span>
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">all</span>(col <span class="keyword">in</span> df.columns <span class="keyword">for</span> col <span class="keyword">in</span> [<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;low&class="macro">#x27;</span>, <span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>]):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"DataFrame must contain &class="macro">#x27;high&class="macro">#x27;, &class="macro">#x27;low&class="macro">#x27;, &class="macro">#x27;close&class="macro">#x27; columns"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> af &lt;= <span class="number">class="num">0</span> <span class="keyword">or</span> af_max &lt;= <span class="number">class="num">0</span> <span class="keyword">or</span> af &gt; af_max:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"Invalid acceleration factors"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> df.empty:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">raise</span> ValueError(<span class="class="type">class="kw">string">"DataFrame is empty"</span>)
&nbsp;&nbsp;&nbsp;&nbsp;result_df = df.copy()
&nbsp;&nbsp;&nbsp;&nbsp;result_df[<span class="class="type">class="kw">string">&class="macro">#x27;SAR&class="macro">#x27;</span>] = <span class="number">class="num">0.0</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;sar = df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;ep = df[<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>]
&nbsp;&nbsp;&nbsp;&nbsp;af_current = af
&nbsp;&nbsp;&nbsp;&nbsp;trend = <span class="number">class="num">1</span> <span class="keyword">if</span> <span class="built_in">len</span>(df) &gt; <span class="number">class="num">1</span> <span class="keyword">and</span> df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">1</span>] &gt; df[<span class="class="type">class="kw">string">&class="macro">#x27;close&class="macro">#x27;</span>].iloc[<span class="number">class="num">0</span>] <span class="keyword">else</span> -<span class="number">class="num">1</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">class="num">1</span>, <span class="built_in">len</span>(df)):
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prev_sar = sar
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;high, low = df[<span class="class="type">class="kw">string">&class="macro">#x27;high&class="macro">#x27;</span>].iloc[i], df[<span class="class="type">class="kw">string">&class="macro">#x27;low&class="macro">#x27;</span>].iloc[i]
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> trend &gt; <span class="number">class="num">0</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sar = prev_sar + af_current * (ep - prev_sar)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> low &lt; sar:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trend = -<span class="number">class="num">1</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sar = ep
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ep = low
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;af_current = af
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span>:

◍ 空头回补时 SAR 的翻转判定

上面这段逻辑处理的是价格在空头波段中触及 SAR 之后的状态切换。当 high 大于当前极值 ep 时,说明空方推动失效,系统先把 ep 刷新为 high,并把加速因子 af_current 按步长 af 累加,但封顶在 af_max,避免点数扩张过快。 若 high 没有越过 sar,则继续用 prev_sar + af_current * (ep - prev_sar) 推算本根 SAR;一旦后续 high 反过来站上 sar,趋势标回 1(多头),sar 直接锚定 ep,ep 重置为 high,af_current 回到初始 af。这种翻转在 EURUSD 15 分钟图上平均约每 40~60 根才触发一次,频率低但错位成本高。 若 high 始终没破 sar,则检查 low 是否低于 ep:低于就更新 ep 为更低低点,并同样累加加速因子(上限 af_max)。最后把算出的 sar 写回结果表的 SAR 列,函数返回整张 DataFrame 供绘图或告警。 开 MT5 把 af 从 0.02 调到 0.05、af_max 设 0.2,能明显看到翻转变密,但假信号也随之外汇贵金属的高风险被放大,验证时建议先用历史数据跑一遍再上模拟盘。

MQL5 / C++
if high > ep:
    ep = high
    af_current = min(af_current + af, af_max)
else:
    sar = prev_sar + af_current * (ep - prev_sar)
    if high > sar:
        trend = class="num">1
        sar = ep
        ep = high
        af_current = af
    else:
        if low < ep:
            ep = low
            af_current = min(af_current + af, af_max)

    result_df.loc[i, &class="macro">#x27;SAR&class="macro">#x27;] = sar

class="kw">return result_df

常见问题

在向导的信号组合步骤勾选 SAR 与 RVI 形态模块,将二者输出映射为网络输入特征,生成基础信号类后即可编译使用。
给弃用形态分配归一化映射值(如趋势强度 0~1),作为特征接入指数核网络输入层,网络便能基于这些值参与决策。
小布可加载该组合模板并实时标注形态触发与网络置信度,你只需打开对应品种页查看诊断,不必手动搭向导。
按 SAR 递推公式写循环:每根 K 线用加速因子更新极点并跟踪止损价,穿透则翻转并重置步长,代码量约三十行。
当价格上穿当前 SAR 且 RVI 同步上拐,才确认翻转;单看 SAR 穿透在震荡里容易假信号,需结合量能或 RVI 过滤。