
您应当知道的 MQL5 向导技术(第 38 部分):布林带
概述
布林带是 John Bollinger 于 1987 年开发的一款广受欢迎的技术指标,由 3 条线(或数据缓冲区)组成。它的主要功能是通过识别交易证券的超买和超卖价位,找到一种量化行情波动的途径。布林带由于行情波动而膨胀和收缩,如果波动性增加,两条外轨会分叉更大,而如果波动率收窄,两条外轨会靠得更近。
交易者利用这些扩张和收缩来分别预测价格突破、或价格盘整的区间。布林带的上轨和下轨也充当动态支撑位和阻力位,这是因为价格往往会从这些价位反弹,为逆转或延续提供潜在线索。布林带通常支持均值回归策略,其中预计价格在触及上轨或下轨后将返回均值(中轨)。当价格超出外轨时,也会判定为超买和超卖条件,预示着潜在的逆转点。
此外,当价格始终接近上轨时,表明看涨趋势即将到来,而主要处于下轨位置则通常意味着下行趋势跌跌不休。突破(或脱离)这些轨迹线可能预示着新趋势、或转折点的开始。最后,当上轨和下轨在给定时间延展范围内彼此非常接近时,就会发生布林带挤压,如此这般波动性很低,突破机会的可能性迫在眉睫。交易员密切关注这一点,通常按照突破方向作为交易方向的提示。3 个数据缓冲区的公式很简单,可按以下方程式来捕捉:
其中:
- MB 是中轨缓冲区
- SMA 是一条简单移动平均函数
- P 代表收盘价的历史
- n 是计算简单移动平均线的周期
其中:
- UB 是上轨缓冲区
- k 是一个可调因子,默认通常分配为 2
- Sigma 是平均周期内的标准差。
其中:
- LB 是下轨缓冲器
本文的目的不仅是逐项列出布林带能够生成的不同信号,而且还一如既往地展示如何将它们集成到单一自定义信号类文件之中。通常,这些文章的结构首先会探讨思路背后的科学,然后在最后呈现策略测试和结果。不过,正如我们之前那样,在本文中,我们将分享每个所述信号设置的测试结果,以及信号设置的代码。这意味着我们会在整篇文章中展示多个测试结果,而非将它们放在最后。我们会看到多达 8 个可能的布林带信号,这些信号都可供交易者所用。
自定义信号类不仅是开发综合化和健壮信号的关键,因为它们允许组合不同的策略,而且原则上,极简的编码方式允许在短时间内快速测试、并交叉验证思路。本文底部附带的代码旨在利用 MQL5 向导汇编智能系统,新读者可在此处和此处找到有关如何执行该操作的指南。一旦思路经过交叉验证,交易者就拥有了部署汇编好的智能系统、或重新编码的选择权,在保持基本策略的同时定制化交易执行。
在我们浏览这些信号之前,如果我们演示如何准备自定义信号类来轮流处理或掌控所有这 8 个信号,可能会很有指导意义。以下是自定义信号类接口:
//+------------------------------------------------------------------+ //| Class CSignalBollingerBands. | //| Purpose: Class of generator of trade signals based on | //| the 'BollingerBands' indicator. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSignalBollingerBands : public CExpertSignal { protected: CiBands m_bands; // object-indicator .... //--- "weights" of market models (0-100) int m_pattern_0; // model 0 "Price Crossing the Upper Band or the Lower Band" int m_pattern_1; // model 1 "Price Bouncing Off Lower Band or Upper Band " int m_pattern_2; // model 2 "Price Squeeze Followed by a Breakout Above Upper Band or Below Lower Band " int m_pattern_3; // model 3 "Price Double Bottoms Near Lower Band or Double Top Near Upper Band " int m_pattern_4; // model 4 "Price Bounces Off the Middle Band from Above & Bounce Off from Below " int m_pattern_5; // model 5 "Volume Divergence at Lower Band or Upper Band " int m_pattern_6; // model 6 "Bands Widening After Downtrend or After Uptrend " int m_pattern_7; // model 7 "Bands Orientation and Angle Changes " uchar m_patterns_used; // bit-map integer for used pattens public: CSignalBollingerBands(void); ~CSignalBollingerBands(void); .... protected: ... //--- methods to check for patterns bool IsPattern_0(ENUM_POSITION_TYPE T); bool IsPattern_1(ENUM_POSITION_TYPE T); bool IsPattern_2(ENUM_POSITION_TYPE T); bool IsPattern_3(ENUM_POSITION_TYPE T); bool IsPattern_4(ENUM_POSITION_TYPE T); bool IsPattern_5(ENUM_POSITION_TYPE T); bool IsPattern_6(ENUM_POSITION_TYPE T); bool IsPattern_7(ENUM_POSITION_TYPE T); };
它遵循大多数自定义信号的标准方式,本文主要新增代码中高亮列出的 'm_pattern_XX' 变量。这些变量表示一个权重(0–100 范围内的数值),在由向导汇编的自定义信号类中很普通。这方面的快速示例是轨道线信号类、和 RSI 信号类。这两个类都使用这些形态变量,其预设常量权重通常小于 100。为它们分配一个恒定的权重本身没有问题,因为原则上每个类都旨在配合其它信号类一起使用,如此得以优化或调整整个智能系统信号每个类的对应权重。
因此,优化它们的打算有点奇怪,若所测试品种或交易证券过度拟合会存在巨大风险,并且使用这些形态往往需假设交易者非常熟悉每种形态的相对重要性和权重。他不需要优化器来告诉他这一点。此外,如果事件中用到多个信号类,且它们都有自己的形态,则对此的优化需求将达到顶峰。这就是为什么将它们作为 “基于知识” 的常数总归更好,而非经训练得出的权重。
不过,在本文中,我们将探讨优化这些形态的选项。我们这样做的主要原因在于这个类仅在向导内汇编。它的权重不会与其它信号矛盾。因此,该信号的加权参数将维持在 1.0,如同我们在本系列中测试的大多数信号类一样。除此之外,通常在分配常量形态值时,只有其中一个形态得以使用,而其它形态处于休眠状态。这通常意味着,即因信号类是与其它信号一起汇编,这意味着其权重得到了优化,所选形态经优化与其它信号一起工作,而其它未选择的形态处于空闲状态。
在本文中,我们想要使用所有形态,只要它们存在。这些形态性质上是成对的,即对于做多和做空条件,不太可能同时表示任何两种不同类型的形态。(我们的意思是,比如说形态 1 和形态 2 可以同时发出信号,而不是针对单一形态同时显示做多和做空条件)故此,同时使用意味着一种形态可能表示做多,而在不同时间的不同形态表示做空、或我们之前开仓的平仓。这些自定义信号类具有平仓阈值和开仓阈值,如此当给定形态存在时,根据其优化值,可以简单地平仓、或平仓之后再反向开仓。因此,我们的智能系统将在短窗口内针对单个品种优化这些形态权重,看看这能否给我们带来任何有趣的东西。
我们的自定义信号类继承自 'CExpertSignal' 类,拥有 'PatternsUsage' 函数,该函数针对自定义信号类中所用形态,用一个整数的比特位掩码作为输入。由于我们最多可以使用 8 个信号形态,因此我们的比特位-映射大小范围从 0 直至 2 的 8 次幂,即 255。这意味着,除了判定单个形态阈值(这些阈值设定平仓和开仓之处)之外,我们还将从 8 种形态中选择最适合的,作为运用布林带的策略。这种选择形态组合的并发用法,显示在本文末尾的最终测试者报告中(由于文章篇幅,这将推迟到下一篇文章中出现)。对于每种可用的形态,下面的测试结果是智能系统使用单一形态时的结果。
尽管,从测试来看,分配所用形态的编号并不像人们预期的那样正确地启用定义函数 'IS_PATTERN_USAGE()'。似乎默认分配 -1 作为所用形态的编号,这意味着所有形态都可使用,无论我们提供什么输入,它都始终如此实现。为了解决这个问题,我们使用我们自己的按位实现,它允许将输入的哈希映射整数转换为 0 和 1,从中可以读取所选择的形态。下表可以作为指南,了解 8 个独立形态的键值映射是什么,以及我们如何在代码中解读它们:
输入映射 (m_patterns_used) | 暗示比特 | 字节检查器 |
1 | 00000001 | 0x01 |
2 | 00000010 | 0x02 |
4 | 00000100 | 0x04 |
8 | 00001000 | 0x08 |
16 | 00010000 | 0x10 |
32 | 00100000 | 0x20 |
64 | 01000000 | 0x40 |
128 | 10000000 | 0x80 |
如是所见,列出的 8 个映射中的每项都暗示使用单一形态。这在隐含位中得到了最佳勾勒,其中,在包含 8 个字符的字符串中,除了其中一个之外,其余都是零。输入实际上可以分配从 0 到 255 的任何值,这就是为什么它是一个无符号字符(uchar 数据类型)。
价格穿过上轨或下轨
故此,我们的第一个信号是价格穿过上轨或下轨的地方。当价格从上轨上方下穿时,我们将其解释为看跌信号。相较之,当价格从下轨下方上穿,并收于其上方时,我们会将其解释为看涨信号。我们在函数中实现了检查形态 0 的逻辑,如下所示:
//+------------------------------------------------------------------+ //| Check for Pattern 0. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_0(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); if(T == POSITION_TYPE_BUY && Close(StartIndex() + 1) < Lower(StartIndex() + 1) && Close(StartIndex()) > Lower(StartIndex())) { return(true); } else if(T == POSITION_TYPE_SELL && Close(StartIndex() + 1) > Upper(StartIndex() + 1) && Close(StartIndex()) < Upper(StartIndex())) { return(true); } return(false); }
我们的代码非常简单,部分归功于 MQL5 的标准库代码,其中针对布林带指标的编码被最小化,如此只需为指标声明了一个类实例,就像这样:
//+------------------------------------------------------------------+ //| Class CSignalBollingerBands. | //| Purpose: Class of generator of trade signals based on | //| the 'BollingerBands' indicator. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSignalBollingerBands : public CExpertSignal { protected: CiBands m_bands; // object-indicator ... ... };
它的初始化如下:
//+------------------------------------------------------------------+ //| Create indicators. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::InitIndicators(CIndicators *indicators) { //--- check pointer if(indicators == NULL) return(false); //--- initialization of indicators and timeseries of additional filters if(!CExpertSignal::InitIndicators(indicators)) return(false); //--- create and initialize MA indicator if(!InitMA(indicators)) return(false); //--- ok return(true); }
我们所要做的就是按照上面函数中的指示刷新它。我们不担心缓冲区数量,因为满足上轨、下轨、和主线(或中线)的函数都在类中构建。还有,主要品种的开盘价、最高价、最低价和收盘价都在引用的类中初始化,前提是在类构造函数中声明它们,如下所示:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSignalBollingerBands::CSignalBollingerBands(void) ... { //--- initialization of protected data m_used_series = USE_SERIES_OPEN + USE_SERIES_HIGH + USE_SERIES_LOW + USE_SERIES_CLOSE; ... }
这同样只需在检索当前价格之前刷新每根柱线上的相应价格句柄。在我们的例子中,我们正用收盘价,故这就是我们需要刷新的全部内容。我们据向导汇编的智能系统运行测试,我们将形态所用的输入比特位映射分配为 1,因为我们仅用第一个形态。仅用形态 0 时,部分优化测试结果为我们给出以下报告:
以上结果来自 USDCHF 的 2023 年日线时间帧测试运行。因为我们只用形态 0,故所用形态的输入哈希映射整数为 1。我们在检查该形态时按本文底部代码中分享的做多和做空条件,其中一并还有所有形态的测试结果。
价格从下轨或上轨反弹
我们的下一个信号是价格在任一条外轨处反弹。如果价格触及上轨,然后从上轨回落,则被视为看跌信号。相较之,如果价格跌至布林带下轨然后回升,则示意看涨信号。这种解释源于上轨和下轨是关键的逆转区域。这反过来可以用市场心理来解释,交易员认为当价格往任何给定方向上涨过度时,就应该进行修正。回想一下,布林带不仅致力于基线(或中轨)缓冲区跟踪趋势,而且还密切关注波动性。故此,当价格接近任何外轨、或它们变宽时,隐含波动性。
确认工具可以是备选指标,理想情况下与布林带一起使用。为了实现在 MQL5 中检查从外轨反弹,我们用到以下源码:
//+------------------------------------------------------------------+ //| Check for Pattern 1. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_1(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); if(T == POSITION_TYPE_BUY && Close(StartIndex() + 2) > Lower(StartIndex() + 2) && Close(StartIndex() + 1) <= Lower(StartIndex() + 1) && Close(StartIndex()) > Lower(StartIndex())) { return(true); } else if(T == POSITION_TYPE_SELL && Close(StartIndex() + 2) < Upper(StartIndex() + 2) && Close(StartIndex() + 1) >= Upper(StartIndex() + 1) && Close(StartIndex()) < Upper(StartIndex())) { return(true); } return(false); }
我们最多可用 8 个形态,从上面分享的表格中可以看出,第二个形态的输入映射(所用的形态输入)是 2。这确保了当我们检查做多和做空条件时,我们仅用这个形态,即上轨和下轨处的反弹。我们据 USDCHF 货币对,2023 年日线时间帧进行了测试运行,并得到以下结果:
价格挤压,随后突破上轨上方、或跌至下轨下方。
我们的形态类型 2 是价格挤压,随后突破。价格挤压的定义典型是宽区间的较低波动性,其伴随的特点是布林带上轨和下轨之间的间隙标志性收窄。这个间隙需要多长是一个主观事宜。出于我们的目,我们有点过于简单化,因为我们将挤压解释为上轨和下轨两者同时朝布林带基线收缩。这意味着,如果挤压仅有一根柱线长,我们就用它。下面是 MQL5 的实现。
//+------------------------------------------------------------------+ //| Check for Pattern 2. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_2(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); if(Upper(StartIndex()) > Upper(StartIndex() + 1) && Upper(StartIndex() + 1) < Upper(StartIndex() + 2) && Lower(StartIndex()) < Lower(StartIndex() + 1) && Lower(StartIndex() + 1) > Lower(StartIndex() + 2)) { if(T == POSITION_TYPE_BUY && Close(StartIndex()) >= Upper(StartIndex())) { return(true); } else if(T == POSITION_TYPE_SELL && Close(StartIndex()) <= Lower(StartIndex())) { return(true); } } return(false); }
事实上,在我们的代码中,挤压持续时间严格定为 1 根柱线。我们的代码甚至没有考虑上轨和下轨之间的间隙有多宽,当然这是定义时可以考虑的另一个关键因素。据上面我们的源代码,任何上轨挖坑,同时发生下轨尖峰,都相当于挤压。如果这次挤压后的价格位于或接近上轨,则信号示意看涨。另一方面,如果在这次挤压之后价格位于下轨,那么我们取其作为看跌信号。仅用该形态的测试运行为我们给出以下结果:
上面的是依据 USDCHF 品种 2023 年日线时间帧的简要优化结果,没有经过交叉验证,我仅用来示意形态 2 的潜力。
靠近下轨的双底或靠近上轨的双顶
该形态也在我们的列表中,坦率地说,它与形态 1(上面的第二个形态)有点相似。主要区别在于,此处我们得到不止一次的外轨反弹,而非我们在形态 1 中的独次反弹。故此,这个标记为形态 3 的形态可被当作带有确认的形态 1。有因于此,仅独靠该形态,没有很多的交易设置输入。它的 MQL5 实现如下:
//+------------------------------------------------------------------+ //| Check for Pattern 3. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_3(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); m_high.Refresh(-1); m_low.Refresh(-1); if ( T == POSITION_TYPE_BUY && Close(StartIndex() + 4) < Close(StartIndex() + 3) && Close(StartIndex() + 3) > Close(StartIndex() + 2) && Close(StartIndex() + 2) < Close(StartIndex() + 1) && Close(StartIndex() + 1) > Close(StartIndex()) && Close(m_close.MinIndex(StartIndex(), 5)) - Upper(StartIndex()) >= -1.0 * Range(StartIndex()) ) { return(true); } else if ( T == POSITION_TYPE_SELL && Close(StartIndex() + 4) > Close(StartIndex() + 3) && Close(StartIndex() + 3) < Close(StartIndex() + 2) && Close(StartIndex() + 2) > Close(StartIndex() + 1) && Close(StartIndex() + 1) < Close(StartIndex()) && Lower(StartIndex()) - Close(m_close.MaxIndex(StartIndex(), 5)) >= -1.0 * Range(StartIndex()) ) { return(true); } return(false); }
输入映射里约束我们仅能用该形态进行交易的是位图 4。从上面的参考表格中可见,观察隐含的比特位,当从右侧开始向左计数时,除了代表我们的形态 3 的第 4 个字符外,所有内容都为零。我们按上面类似的设置进行测试,USDCHF,日线时间帧,2023 年,为我们给出以下结果:
如是清晰的表现,没有太多下单交易,因为无论上下轨,双顶/双底并不常遇到。这大概意味着该形态可被单独使用,而无需配对另外指标,这与我们刚看过的一些形态不同。
从上方至中轨反弹,及从下方至中轨反弹
我们的下一个也是第五个形态是形态 4,其演变围绕趋势中期延续。故此,每当价格趋向任何给定方向,往往沿途需要几次暂歇。在这些突破期间,更多的是拉锯动作,而非确定趋势,不过在恢复趋势之前,价格往往(但并非总是)会测试阻力位(如果趋势向下)、或支撑位(如果趋势向上)。这些价位的测试可用布林带的基线缓冲来标示,因为它也与上下轨一样,往往充当动态支撑/阻力位。
在该形态中,如果价格从上方测试基线(中轨缓冲区),然后从基线向上反弹,则解释为看涨形态,如果同样的事情反向发生,则解释为看跌形态。从下方反弹。MQL5 实现如下:
//+------------------------------------------------------------------+ //| Check for Pattern 4. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_4(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); if(T == POSITION_TYPE_BUY && Close(StartIndex() + 2) > Base(StartIndex() + 2) && Close(StartIndex() + 1) <= Base(StartIndex() + 1) && Close(StartIndex()) > Base(StartIndex())) { return(true); } else if(T == POSITION_TYPE_SELL && Close(StartIndex() + 2) < Base(StartIndex() + 2) && Close(StartIndex() + 1) >= Base(StartIndex() + 1) && Close(StartIndex()) < Base(StartIndex())) { return(true); } return(false); }
按与上述类似的环境设置进行测试,我们得到以下结果:
我们的布林带指标在本文的所有测试运行中都采用相同的设置,其中指标周期为 20,偏差为 2。
下轨或上轨的交易量背离
接下来是我们的第六种形态,它旨在跟踪交易量和波动率之间的背离。现在很多交易者,尤其是在这个平台上交易外汇对,由于难以在多个经纪商之间整合这些信息,故很少有可靠的交易量数据。故此,在跟踪或测量交易量时,我们所用的实现可以说是次优替品。价格柱线范围。对此的论调是,当众多买家为给定货币对出价时,该货币对必然会朝着出价的方向反弹很多。当然,这并不完全正确,因为在该动作的另一端有同等数量的做空合约会抑制价格变动,尽管,所涉及的总交易量不会反映在柱线的成品价格范围内。
所以,这只是一个妥协。当价格柱线范围连续 3 根价格柱线系统性地递减,同时收盘价处于、或低于布林带的下轨,我们解读为看涨信号。类似地,当 3 根柱线的价格范围下降,并且收盘价等于或高于上轨缓冲区时,我们亦发出看跌信号。这背后的原因可能与先前趋势的耗竭有关,其中价格范围末端极端的“成交量”下降,则示意逆转、或至少是回调。MQL5 实现如下:
//+------------------------------------------------------------------+ //| Check for Pattern 5. | //+------------------------------------------------------------------+ bool CSignalBollingerBands::IsPattern_5(ENUM_POSITION_TYPE T) { m_bands.Refresh(-1); m_close.Refresh(-1); m_high.Refresh(-1); m_low.Refresh(-1); if(Range(StartIndex()) < Range(StartIndex() + 1) && Range(StartIndex() + 1) < Range(StartIndex() + 2)) { if(T == POSITION_TYPE_BUY && fabs(m_close.GetData(StartIndex()) - Lower(StartIndex())) < fabs(m_close.GetData(StartIndex()) - Base(StartIndex()))) { return(true); } else if(T == POSITION_TYPE_SELL && fabs(m_close.GetData(StartIndex()) - Upper(StartIndex())) < fabs(m_close.GetData(StartIndex()) - Base(StartIndex()))) { return(true); } } return(false); }
其测试仅用该模式运行,采用与之前测试类似的运行环境设置,为我们给出以下结果:
结束语
我本打算涵盖布林带指标的所有 8 种形态,如所附代码所示,但事实证明这篇文章有点太长了。因此,我将很快在后续文章中涵盖其余部分。
不过,总而言之,我们已验证了利用布林带交易时可用的至少 8 种信号形态中的 5 种。该指标不仅捕捉到价格的总体趋势,而且归功于其两条外轨带缓冲区,还能跟踪波动性。正如我们所见,当这些信息与市场心理学相结合时,可从该指标中生成各种各样的信号。虽然这些形态中的每一个都可以单独使用,就像我们在本文中所做的那样,因为篇幅必须截短,但所用形态的输入参数实际上可以允许多个形态选择,如此可以组合和优化,从而提出更动态的设置,更好地适应不同类型的行情。希望我们能在下一篇文章中涵盖这一点,以及更多内容。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15803



干得漂亮你能分享这些结果的设置文件吗?
不,我不保留这些文件。它们太特殊了。
感谢斯蒂芬写的这篇好文章。