您应当知道的 MQL5 向导技术(第 40 部分):抛物线止损和反转(PSAR)
概述
我们继续本系列,考察不同的交易设置和思路,借助 MQL5 向导,能够快速开发和测试它们。在最后 2 篇文章中,我们专注于非常基本的指标和振荡器,例如 IDE 中向导类附带的指标和振荡器。如此行事,我们利用每个所研究指标提供的各种形态,独立对其进行了测试,并优化了多种形态选择设置,如此这般我们就能比较独立/集体形态与优化设置的测试结果。
在本文中,我们坚持这种格式,在得出结论之前,我们逐个遍历抛物线 SAR 的形态、以及结合多种形态的测试运行,就像我们在上一篇文章中所做的那样。抛物线 SAR 几乎是在每根新柱线上独立计算的,因为其公式中的一些参数需要调整,我们将在下面看到。尽然,该特性令其对价格变化和趋势非常敏感,而反过来这正是其在自定义信号类内的所为。至于本文,我们将探索该指标的 10 种独立形态,通过独立测试每个形态,最后如最近的文章一样,结合这些形态进行测试。
本文末尾附带的源代码意欲在 MQL5 向导中汇编智能系统所用。这里和这里都有关于新读者如何执行该操作的指南。
定义抛物线-SAR
抛物线 SAR 是一个数值缓冲区,这些数值是距离当前趋势的极值再加/减增量(或步长)的偏移量,直至预设阈值。这听起来可能很拗口,但这不过是一种简单的动态方式,用来指示当前趋势,并印证给定趋势可能的逆转点。抛物线 SAR 公式非常流畅;看涨和看跌趋势不同。对于看涨,我们有:
其中:
- SAR n+1 是下一个周期的 SAR 值。
- SAR n 是当前 SAR 值。
- EP(极值点)是当前趋势中的最高价。
- α 是加速系数(AF),通常从 0.02 开始,每次达到新的 EP 时增加 0.02,最大值为 0.20(可能因用户设置而异)。
还值得注意的是,在上升趋势中:
- EP 是从趋势开始以来的最高高点。
- SAR 值将随着趋势的继续而递增,一直调整从而跟踪价格走势。
对于看跌,我们有:
其中:
- EP 是当前下跌趋势中的最低价。
同样值得注意的是,在下降趋势中:
- EP 是从趋势开始以来的最低低点。
- SAR 值将随着时间的推移而递减,并遵循下降趋势。
故此,随着趋势的进展,SAR 的递增或递减(看跌情况)往往会将其朝价格压缩,这反过来又令趋势的折返、或变化更加迫在眉睫。在 MQL5 中该实现是由内置指标和标准库类处理,故在本文中,我们将简单地引用它们。现在我们来进一步研究 SAR 已提供的不同形态。
逆转豁口穿插
我们的第一个形态 0 是豁口穿插,其中 SAR 的指标点在牛势情况下从高于价格高点跳空切换到低于低点,或者在熊势中从低于低点跳空切换到高于高点。通常,抛物线 SAR 点与最近价格点之间的豁口大小(其应看涨豁口的低位、或看跌豁口的高位),表明信号的强度。这种豁口越宽,新趋势就越强。
不过,还应考虑市场宏观条件,因为这些豁口穿插可能会非常频繁地发生,尤其是在波动性非常高的行情中,这会导致许多假信号。故此,人们更希望在波动性最小的行情中依赖这个信号。在依据 SAR 调整止损的实例下,这些穿插点并非平仓之处,而是简单地把止损位拉近 SAR,实际的平仓和反转则取决于其它信号。
为了实现我们的形态 0,我们的自定义信号类中,用到以下函数:
//+------------------------------------------------------------------+ //| Check for Pattern 0. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_0(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && Base(StartIndex() + 1) > High(StartIndex() + 1) && Base(StartIndex()) < Low(StartIndex())) { return(true); } else if(T == POSITION_TYPE_SELL && Base(StartIndex() + 1) < Low(StartIndex() + 1) && Base(StartIndex()) > High(StartIndex())) { return(true); } return(false); }
而且,向导仅用形态 0 汇编智能系统的测试运行,为我们提供了以下结果:
SAR 压缩区
我们的下一个形态是压缩区,它可以说是形态 0 的改进。顾名思义,它的主要区别在于 SAR 指标折返之前需要压缩价格(翻译为:先前的趋势波动性低)。如前所述,SAR 示意当前倾向的趋势(在看涨和看跌之间),故如果先前趋势的牵引力微不足道,则可将其解释为压缩。“微不足道” 的量化或许意味着我们需要添加另一个输入参数来定义这个值;不过,我们选择通过压缩函数来实现这一点,如下所示:
bool Compression(ENUM_POSITION_TYPE T, double &Out) { Out = 0.0; int _i = StartIndex() + 1, _c = 0; double _last = Base(StartIndex() + 1); double _first = 0.0; if ( T == POSITION_TYPE_BUY && Base(StartIndex()) < Low(StartIndex()) && Base(_i) < Close(StartIndex()) && Base(_i) > High(_i) ) { while(Base(_i) > High(_i) && _c < __COMPRESSION_LIMIT) { _first = Base(_i); _i++; _c++; } if(_c > 0) { Out = fabs(_first - _last)/_c; return(true); } } else if ( T == POSITION_TYPE_SELL && Base(StartIndex()) > High(StartIndex()) && Base(_i) > Close(StartIndex()) && Base(_i) < Low(_i) ) { while(Base(_i) < Low(_i) && _c < __COMPRESSION_LIMIT) { _first = Base(_i); _i++; _c++; } if(_c > 0) { Out = fabs(_first - _last)/_c; return(true); } } return(false); }
这反过来意味着我们的形态 0 函数会如下处理:
//+------------------------------------------------------------------+ //| Check for Pattern 1. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_1(ENUM_POSITION_TYPE T) { double _compression = 0.0; if(Compression(T, _compression)) { if(T == POSITION_TYPE_BUY && _compression < 0.02*fabs(Base(StartIndex())-Low(StartIndex()))) { return(true); } else if(T == POSITION_TYPE_SELL && _compression < 0.02*fabs(Base(StartIndex())-High(StartIndex()))) { return(true); } } return(false); }
该函数量化之前趋势的幅度,指标值时刻保持调整。我们定义微不足道作所用的阈值,会作为输入值,而 SAR 已有了步长输入。故此,如果初始 SAR 与价格的差距的步长分数大于 SAR 值相对于先前趋势的平均变化,那么我们得到压缩。由于该形态条件只是趋势的压缩和折返,我们将根据上面已经分享的形态 0 趋势折返条件来开仓。测试我们用向导汇编的智能系统,专门针对形态 1,为我们给出以下结果:
我们正在依据 EURJPY 的 2023 年日线时间帧进行测试。作为一个压缩形态,我们已严格定义按照步长输入作为我们的分数来限制整体趋势,故不会大量下单。尽管,可通过引入另一个参数来调节这一点。该形态用法的输入为 2。
扩展趋势 SAR
这种形态是一种延续形态,可在初始趋势折返被抑制的情况下采用,例如,在开始时 SAR 与价格的差距非常小的情况下。这相当直截了当,SAR 点之间的敞口扩大表示看涨形态,而 SAR 指标仍停留在低点之下,在逆反状况下示意看跌信号,其中点敞口也增加,而 SAR 仍停留在高位之上。有些人可能称其为落后者,但在得出该类结论之前,先对其进行测试总是更好的。我们的 MQL5 实现如下:
//+------------------------------------------------------------------+ //| Check for Pattern 2. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_2(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && Base(StartIndex()) - Base(StartIndex() + 1) > Base(StartIndex() + 1) - Base(StartIndex() + 2) && Base(StartIndex() + 1) - Base(StartIndex() + 2) > Base(StartIndex() + 2) - Base(StartIndex() + 3) && Base(StartIndex() + 2) - Base(StartIndex() + 3) > Base(StartIndex() + 3) - Base(StartIndex() + 4) ) { return(true); } else if(T == POSITION_TYPE_SELL && Base(StartIndex() + 1) - Base(StartIndex()) > Base(StartIndex() + 2) - Base(StartIndex() + 1) && Base(StartIndex() + 2) - Base(StartIndex() + 1) > Base(StartIndex() + 3) - Base(StartIndex() + 2) && Base(StartIndex() + 3) - Base(StartIndex() + 2) > Base(StartIndex() + 4) - Base(StartIndex() + 3) ) { return(true); } return(false); }
为了只测试形态 2,我们采用 4 作为形态输入映射。仅用该形态进行测试,并采用与上面相同的设置,我们得到以下结果:
SAR 折返假输出
顾名思义,这种形态是指新趋势的变化,几乎立即恢复到先前的趋势而折返。这往往是由 SAR 价格图表上的一个或两个点趋势表示,在趋势中,同时位于它们的另一侧伴随这些点,表明交易者应当要注意了。故此,对于看涨信号,将有一个常规的看涨趋势,然后是仅持续 1-2 根价格柱线的折返,然后恢复长期趋势,信号是趋势的恢复。同样,看跌形态将从下跌趋势开始,在恢复下跌之前,在一两根价格柱线上短暂转为看涨。我们将按如下方式编写该形态:
//+------------------------------------------------------------------+ //| Check for Pattern 3. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_3(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && Base(StartIndex()) < Low(StartIndex()) && Base(StartIndex() + 1) > High(StartIndex() + 1) && Base(StartIndex() + 2) < Low(StartIndex() + 2)) { return(true); } else if(T == POSITION_TYPE_SELL && Base(StartIndex()) > High(StartIndex()) && Base(StartIndex() + 1) < Low(StartIndex() + 1) && Base(StartIndex() + 2) > High(StartIndex() + 2)) { return(true); } return(false); }
这是我们的第 4 个形态,我们其索引为形态 3 意味着在仅依赖该信号的情况下进行交易,我们的形态输入映射必须为 8。测试运行与上述形态设置,及测试区间类似,但没有下单交易,故无法分享结果。无论如何,这些折返-假输出的常见原因您一般会怀疑:行情波动/横盘整理、低波动性、或市场噪音。这对于非质疑交易者的影响可能是巨大的,这就是为什么需要第二个指标(如 MACD)、价格行为分析(是指支撑和阻力)、或交易量分析(如果这些信息可用),来辅助解决这个问题。话虽如此,该信号应当比单纯折返更可靠,譬如形态 0。
双 SAR 折返配合趋势延续
该形态,就是我们的形态 3 加上 2 次以上的折返。它的结果是延续,就像形态 3 一样,可以说形态 3 比形态 0 强,这个形态 4 比形态 3 更可靠、或更强壮。我们提供的代码实现如下所示,不过我们没有据其运行测试,而是将其留给读者去深入探索,理想情况下,其测试区间应当超出我们正在研究的一年期限。由于 EURJPY 的 2023 年日线时间帧上形态 3 没有交易,我们不期望形态 4 会有任何信号、以及交易。
//+------------------------------------------------------------------+ //| Check for Pattern 4. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_4(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && Base(StartIndex()) < Low(StartIndex()) && Base(StartIndex() + 1) > High(StartIndex() + 1) && Base(StartIndex() + 2) < Low(StartIndex() + 2) && Base(StartIndex() + 3) > High(StartIndex() + 4) && Base(StartIndex() + 4) < Low(StartIndex() + 5) ) { return(true); } else if(T == POSITION_TYPE_SELL && Base(StartIndex()) > High(StartIndex()) && Base(StartIndex() + 1) < Low(StartIndex() + 1) && Base(StartIndex() + 2) > High(StartIndex() + 2) && Base(StartIndex() + 3) < Low(StartIndex() + 4) && Base(StartIndex() + 4) > High(StartIndex() + 5) ) { return(true); } return(false); }
SAR 背离配合移动平均线
形态 5,源于背离。因为价格和 SAR 之间的背离非常普遍,所以移动平均线指标可以作为确认。故此,对于看涨信号,价格正下跌,而 SAR 在上升,移动平均线低于或等于 SAR。相较之,对于看跌形态,价格在上涨,SAR 在下降,而移动平均线仍高于或等于两者。衡量上涨或下跌所需的步数可以自行决定(或通过测试),不过出于我们的目的,我们简单地取其三个。因此,我们实现了调用这些形态的函数,如下所示:
//+------------------------------------------------------------------+ //| Check for Pattern 5. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_5(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && MA(StartIndex()) <= Base(StartIndex()) && Base(StartIndex()) > Base(StartIndex() + 1) && Base(StartIndex() + 1) > Base(StartIndex() + 2) && Close(StartIndex()) < Close(StartIndex() + 1) && Close(StartIndex() + 1) < Close(StartIndex() + 2) ) { return(true); } else if(T == POSITION_TYPE_SELL && MA(StartIndex()) >= Base(StartIndex()) && Base(StartIndex()) < Base(StartIndex() + 1) && Base(StartIndex() + 1) < Base(StartIndex() + 2) && Close(StartIndex()) > Close(StartIndex() + 1) && Close(StartIndex() + 1) > Close(StartIndex() + 2) ) { return(true); } return(false); }
运行我们的智能系统仅测试该形态,我们得到以下结果:
值得注意的是,我们于此要应对三个数据缓冲区,即价格、移动平均线、和 SAR。我们选择的这些背离是在价格和 SAR 之间,不过也可参考其它背离,例如移动平均线和 SAR 之间的背离。然而,由于移动平均线的滞后效应,与我们在本文中实现的价格-SAR 背离相比,这种背离也必然会有点滞后。另一折面,它也必然会少一些噪音,因为价格行为在短期内产生了很多动作,而从长期来看,这些动作往往不会变得太显耀。故此,它可能有一些用途,也欢迎读者探索这条途径。该形态的用法输入为 32。
抛物线 SAR 通道
抛物线 SAR 通道形态将价格动作与当前 SAR 趋势相结合,从而生成信号。价格通道在价格图表上相对容易理解,但尝试将该逻辑放入代码中则通常比最初设想的要复杂得多。故此,立即定义一个基本函数或许是一个好主意,该函数定义价格通道的当前上限和下限,其范围由历史上要查看的价格柱数量设置。我们将这个函数命名为 'Channel',它在接口中的逻辑分享如下:
bool Channel(ENUM_POSITION_TYPE T) { vector _max,_max_i; vector _min,_min_i; _max.Init(2); _max.Fill(High(0)); _max_i.Init(2); _max_i.Fill(0.0); _min.Init(2); _min.Fill(Low(0)); _min_i.Init(2); _min_i.Fill(0.0); for(int i=0;i<m_ma_period;i++) { if(High(i) > _max[0]) { _max[0] = High(i); _max_i[0] = i; } if(Low(i) < _min[0]) { _min[0] = Low(i); _min_i[0] = i; } } double _slope = (Close(0) - Close(m_ma_period-1))/m_ma_period; double _upper_scale = fabs(_slope); double _lower_scale = fabs(_slope); for(int i=0;i<m_ma_period;i++) { if(i == _max_i[0]) { continue; } else { double _i_slope = (High(i) - _max[0])/(i - _max_i[0]); if((_i_slope > 0.0 && _slope > 0.0)||(_i_slope < 0.0 && _slope < 0.0)) { if(fabs(_i_slope-_slope) < _upper_scale) { _max[1] = High(i); _max_i[1] = i; } } } } for(int i=0;i<m_ma_period;i++) { if(i == _min_i[0]) { continue; } else { double _i_slope = (Low(i) - _min[0])/(i - _min_i[0]); if((_i_slope > 0.0 && _slope > 0.0)||(_i_slope < 0.0 && _slope < 0.0)) { if(fabs(_i_slope-_slope) < _lower_scale) { _min[1] = Low(i); _min_i[1] = i; } } } } vector _projections; _projections.Init(4); _projections[0] = _max[0] + (_max_i[0]*_slope); _projections[1] = _min[0] + (_min_i[0]*_slope); _projections[2] = _max[1] + (_max_i[1]*_slope); _projections[3] = _min[1] + (_min_i[1]*_slope); if(T == POSITION_TYPE_BUY && Close(0) < Close(m_ma_period) && Close(0) < _projections.Mean()) { return(true); } else if(T == POSITION_TYPE_SELL && Close(0) > Close(m_ma_period) && Close(0) > _projections.Mean()) { return(true); } return(false); }
该通道的主输出将是,给定一个仓位类型,该通道指示是否有可能的逆转?为了回答这个问题,我们首先需要判定哪些价格点定义了上线和下线。尽管从常规价格图表上这很容易直观挑选出来,但在代码中,它很容易被吸引到依赖分形。而如果使用分形指标真的很好,这可以起作用,但我发现关注给定回溯期的整体斜率,能提供更通普适的解决方案。
故此,为了定义我们的通道,首先我们要了解整个回溯期的斜率。我们的回溯周期设置为等于移动平均线指标所用的周期,在上面的形态 5 中高亮显示。读者可以创建自己的辅助参数来定义这一点,但我总觉得输入参数越少,模型就越普适。故此,一旦我们得到斜率,我们就需要在定义的回溯期内获得与该斜率最一致的两个高点。
典型地,回溯期的最高点总是预期沿通道的上线,因此,如果我们从获得这个最大值开始,那么我们需要梳理所有其它高点,直到我们得出第二个高点值,其高点值都来自我们的最高点斜率,这是最符合整体趋势的斜率。当然,此处的总体趋势是受跨越整个回顾期内的收盘价变化约束。
一旦我们得到这两个点,我们将再次对通道的下边界执行相同的事情,找到最低的低点和另一个在连接到最低点时最符合趋势斜率的低点。两对点定义两条线,因此,有了它们,您会得到一个通道,仅基于回溯期。在查看价格图表时,肯定不会采用这种机械方法,因为固定的回顾历史不太可能总是有足够的数据点来定义这一点。这就是为什么简单地连接这些点,并尝试从中推断必然会产生许多野性或随机的通道。固定的回顾历史通常无法捕获所有关键的历史价格点进行分析。
这就是为什么我们将通道定义为两条,而不仅仅是一条上边线和下边线。这些线中的每一条都将经过我们上面已经定义的 4 个点。我们在形态 6 的价格看涨信号,位于通道的下半部分,抛物线 SAR 也表明长期趋势。相较之,对于看跌,价格将位于通道的上半部分,而 SAR 表示看跌趋势。为了判定我们当前价格位于通道的哪一半,我们只需取所有四个预测价格点的平均值。这些预测只是穿过我们的两个高点和两个低点,直到当前指数的线的延伸,同时保持整体趋势的相同斜率。我们按如下方式实现该形态的 MQL5 版本:
//+------------------------------------------------------------------+ //| Check for Pattern 6. | //+------------------------------------------------------------------+ bool CSignalSAR::IsPattern_6(ENUM_POSITION_TYPE T) { if(T == POSITION_TYPE_BUY && Base(StartIndex()) < Low(StartIndex())) { return(Channel(T)); } else if(T == POSITION_TYPE_SELL && Base(StartIndex()) > High(StartIndex())) { return(Channel(T)); } return(false); }
采用我们于上 EURJPY 的相同设置,依据 2023 年的日线测试,为我们给出以下结果:
该形态用法的输入为 32。
结束语
我们已考察了抛物线 SAR 的 10 种可能形态中的 7 种,为这篇文章不那么冗长,我们现在只留这些在此。我们将在后续文章中研究形态抛物线 SAR 配合成交量背离,在较高时间帧上倒置 SAR,以及 SAR 与 RSI 的重叠。本文中涵盖的每种形态都可进一步利用,并以各种途径和格式实现。为此,本文和最后两篇类似,我们依赖于信号类文件的内置形态方法,我们的自定义信号类均继承了该方法。在过去的两篇文章中,我们声明并使用了一个参数 'm_patterns_used',该参数是重复且不必要的,因为我们的父类已有参数 'm_patterns_usage'。后者将我们的编码要求最小化,且在使用时,还会提供更简洁的结果,因为实际的导入映射得以正确使用。
这些事情不应读者来做,他们应该相应地更改这两篇最新文章所附的代码。另外,要注意,也许作为本文的要点,在智能系统中实现价格通道并不很常见,这就是为什么在一个独立片段中,我能考察这如何也能当作一个信号类。带有通道的价格图表很容易直观阅读,因为上限和下限的定义点可以很容易地直观判定,但在代码中这样做却不是一回事,故这也是我们在以后的文章中要考虑的事情。
最后,在这些形态系列中,我们正在做的一些的事情,与标准库附带的一些基形态的内置信号背道而驰。我们正在针对每个形态的理想阈值条件进行优化。这违背了交易者在处理指标时根据自己的经验和观察来预设这些阈值的惯例。虽然我们的测试结果看起来确实很有前景,因为我们用到多种形态,但可以说它们更难普适,因此更加难以交叉验证。因此,可以建议交易者在要采用多个形态的情况下,预先分配这些阈值权重。如果只是要用一种形态,一些东西我们可以容忍,正如在之前的文章中分享的那样,其中列出了每个单独形态的输入映射值,那么就可以针对该阈值进行优化。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15887
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



