
您应当知道的 MQL5 向导技术(第 24 部分):移动平均
概述
我们继续 MQL5 向导的系列文章,探察移动平均指标,以及如何将其以令一些交易者耳目一新的方式添加到可供使用的工具库之中。移动平均有很多变体,作为可附加到图表的单一时间序列,但也有其它变体当作振荡器,甚至有些变体当作轨道线指标。
我们将在称为毕达哥拉斯(Pythagorean)均值的 3 条移动平均(MA)这个特殊分类中探索其多种应用、或变体。该名称下的 3 个 MA 是算术平均值(AM)、几何平均值(GM)、和调和平均值(HM)。其中第一个,算术均值,是每个人在提到 MA 时首先想到的。它只是一个任意数量的数值集合的平均值。维基百科中对所有这 3 种均值进行了非常有趣的图解总结。它分享如下:
上面表述的是一个半圆,其直径分为不均匀地两段,其值为 a 和 b。这两个值的算术平均值在图中标记为红色的 A,这等价于半圆的半径,正如人们所期望的那样。为了完整起见,算术平均值的公式分享如下:

其中
- AM 是算术平均值
- x 是集合中计算其平均值的数值
- n 是集合的大小
现在,几何平均值由公式给出:

其中
- GM 是几何平均值
- x 和 n 含义与上面的 AM 相同
上面半圆的 GM 值在长度上与标记为 G 的蓝色弦相同,这令其权重比之较大的 a 更朝向较小的 b 值。还有,值得注意的是,即使所有数值都为负值,GM 也始终为正数值!如果集合完全为负数,则可为计算出的 GM 赋予一个负值来解决这种场景,但如果它是负值和正值的混合,那么得出真正的 GM 就会变成问题。
对于调和平均值,半圆表示是标记为 H 的线,其长度等于 a 和 b 的均值。其公式表示为:

其中
- HM 当然是调和平均值
- x 和 n 表示与上述相同
如果出于某种原因,数值 b 为零,从半圆图解中可以看出,几何平均值和调和平均值都将为零。(尽管在计算调和平均值时遇到了“除零”)。有趣的很,无论计算哪个均值,都与数字的计数(即如果它们大于 2)无关;只要这些数字中的任何一个为零,那么所有值的几何平均值、及调和平均值都为零。
该属性实质上衡量集合中哪个最小值接近零。是故,这对交易者意味着什么?这可能意味着很多事情,具体取决于正在研究的移动平均数据。
如果它只是证券的价格,那么移动平均能够充当支持的优秀代表。为什么?它(GM 和 HM)更注重低价格。这就意味着,举例,价格跌破这些平均线比跌破常规移动平均线更显要。故此,在大多数价格行动支撑端,GM 和 HM 能作为援助。阻力位方面呢?我们可从以下公式中得到 GM 和 HM 两者的镜像等效项:
HM’ = AM + (AM - HM)
GM’ = AM + (AM - GM)
其中:
- HM' 是反射的调和平均值
- GM' 是几何平均反射
- AM,GM & HM 与上述相同。
通过引入 GM 和 HM 的镜像等价项或反射,我们在某种意义上为平均值增加了一种平衡,因为这些反射的权重必然会朝向正在计算平均值的任意数值集合中的较大值。这也意味着,为了回答阻力位的问题,我们现在能用 GM' 或 HM' 作为更高的价格权重平均值,在定义阻力位时作为更有效代表。
出于我们的目的,GM 与 HM 的重要性仅在于程度。这是因为两者的权重朝向较小的数值,主要区别在于 HM 朝向零的权重高于 GM。
自定义实现
现在我们看看这些简单平均如何放在 MQL5 中,从而驾驭它们的独特属性。首先,是基础的算术平均值。这 3 种当中,它这是最普通的,因为它采取的是十分基本的平均值,无需任何修改,且用该平均值的最简单方式应当就是监控价格交叉。在跟踪价格结构时,移动平均线有很多应用,这可以说是最直接和最常用的。故此,此处所述应用主要是出于与上面曾讲述的 GM 和 HM 的其它不太常见的用法进行比较的目的。
基准测试和比较是由 MQL5 向导组装的智能系统固有的,尤其是对于信号类,因为向导中每个所选信号类都可以被分配一个权重,或者说,在判定所交易证券的做多和做空条件时。幸好有向量数据类型,我们可以轻松地从该函数中获取 AM 缓冲区:
//+------------------------------------------------------------------+ //| Arithmetic Mean | //+------------------------------------------------------------------+ double CSignalAM::AM(int Index, int Mask = 8) { vector _am; _am.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_fast); return(_am.Mean()); }
而且,我们可以衡量当前价格间隙,这是跟踪移动平均线交叉的关键,按以下函数:
double CrossOver(int Index) { m_close.Refresh(-1); return(AM(Index) - m_close.GetData(Index)); }
典型情况下,某人正在手工交易,将 GM 和 HM 编码为自定义指标就具意义。我们并未手工交易,因为这是针对智能系统的,故此函数能访问内置类的价格缓冲区就足矣了。
HM 平均值就像 GM,权重朝向较小值,正如我们在上面所见,这可以当作镜像,来创建另一个平均值 HM',即权重朝向较高值。故以,HM & HM' 均值的权重朝向极端,它们自身提供了捕捉背离的能力。现在,当人们想到背离时,首先出现在脑海的应当就是振荡器与其证券价格之间的趋势差异。这些对抗发生在很短的区间内,人们需要时刻留意才能捕捉到它们。要不然,它可能是一个证券的价格在不同的时间帧内完全不同,出现背离,像是在一小时的时间帧里价格下跌,而在周线时间帧内却是强劲看涨趋势。
然而,为了利用调和平均值“乖离”,我们将查看高点价格和低点价格的背离。具体上,进有高点价格的变化方向与低点价格的变化方向有别时,我们才会考虑开仓。只是,这仍然可从双向进行探索。我们要么在下跌的高点、及上涨的低点开仓,要么在上涨的高点、及下跌的低点开仓。至于本文,我们将探索后者,但鉴于所有源码都附在本文末尾,因此读者可以自定义它,并尝试选择我们尚未研究的更流行的吞噬背离。
是故,我们将寻找最高价调和平均值的上升、同时最低价调和平均值下降的那一刻开仓。帮助指导做多或做空开仓的后续指标可于此处自定义 ,但我们在后续指标里简单地采用收盘价的变化。如果我们发现前一根柱线背离,之后收盘价上涨,那么我们做多,反之亦然。
正如我们在 AM 中看到的那样,实现这一点的代码也是双叠的。首先,我们拥有 HM 函数,及其镜像,如下所示:
//+------------------------------------------------------------------+ //| Harmonic Mean | //+------------------------------------------------------------------+ double CSignalHM::HM(int Index, int Mask = 8) { vector _hm, _hm_i; _hm_i.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_slow); _hm = (1.0 / _hm_i); return(m_slow / _hm.Sum()); } //+------------------------------------------------------------------+ //| Inverse Harmonic Mean | //+------------------------------------------------------------------+ double CSignalHM::HM_(int Index, int Mask = 8) { double _am = AM(Index, Mask); double _hm = HM(Index, Mask); return(_am + (_am - _hm)); }
然后我们得到背离函数,如下所示:
double Divergence(int Index) { return((HM_(Index, 2) - HM_(Index + 1, 2)) - (HM(Index, 4) - HM(Index + 1, 4))); }
在使用向量来复制和加载数据时,“兑换率掩码”索引是必不可少的,因为它允许我们在各种价格(OHLC)之间快速切换,同时使用向量数据类型可令内置统计函数节省大量编码的需要。此外,我们对这些毕达哥拉斯均值的测试函数用到两个移动平均周期,一个快速周期、和一个慢速周期。这是常用的做法,尤其是在使用交叉策略来判定入场点和出场点之时。对于调和平均值和几何均值两者的缓冲区,我们都依靠慢周期来计算我们的值。快速周期仅用于算术平均值缓冲区。
当然,这可以修改或调整,从而更好地适配一个人的策略和方式,但我们于此坚持用它,纯粹是出于测试目的。
最后,就像调和平均值,几何平均值将类似于布林带那样应用。它就像调和平均值,较小数值权重更大,而且程度稍大。正是如此加权令其成为类似布林带指标的理想选择,因为众所周知,布林带是移动平均线加上 2 个标准差。不过,在我们开始实现之前,获取几何平均值、及其镜像(最高价数值加权等效值)的代码将如下所示:
//+------------------------------------------------------------------+ //| Geometric Mean | //+------------------------------------------------------------------+ double CSignalGM::GM(int Index, int Mask = 8) { vector _gm; _gm.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_slow); return(pow(_gm.Prod(), 1.0 / m_slow)); } //+------------------------------------------------------------------+ //| Inverse Geometric Mean | //+------------------------------------------------------------------+ double CSignalGM::GM_(int Index, int Mask = 8) { double _am = AM(Index, Mask); double _gm = GM(Index, Mask); return(_am + (_am - _gm)); }
再一次,我们正使用向量数据类型、及其内置函数来加速编码过程。波带缓冲区为两个,由一条上轨和一条下轨组成。这些也能从下面的两个函数清单中索取:
double BandsUp(int Index) { vector _bu; _bu.CopyRates(m_symbol.Name(), m_period, 2, Index, m_slow); return(GM_(Index, 2) + (2.0 * _bu.Std())); } double BandsDn(int Index) { vector _bd; _bd.CopyRates(m_symbol.Name(), m_period, 4, Index, m_slow); return(GM(Index, 4) - (2.0 * _bd.Std())); }
这两个函数分别简单地返回函数 'BandsUp' 和 'BandsDn' 的上轨价格和下轨价格。这些返回的数值可以很容易地重构至并行缓冲区,以便以多种形式进行分析。我们只在交叉形式里用到它们,设定我们是否有可能开立多头或空头持仓。为了检查多头开仓,我们需要确认价格已从下方上穿下轨,即它曾经低于下轨,但现在高于下轨。类似地,为了检查空头开仓,我们需要确认价格从上方下穿上轨,即价格曾经高于上轨,但在随后的价格柱线中目前低于上轨。
自定义信号类
这 3 个毕达哥拉斯 MA 中的每一个都可以组合进单一类,并带有一个附加参数,允许在智能系统中选择其中之一使用。然而,我们会把这些实现为单独的信号类,因为我们欲通过优化每个平均值的理想权重,来探索信号类的权重设置,如此这般得到这些信号的感知度,以及平均值的延伸度,在我们的智能系统里这些对于预测和下单更实用。
不过,在我们得到相对重要性的概念之前,先单独针对每个信号类运行独立测试也许更敬业,这样我们最终得到的任何相对权重,都可作为我们所做的第一次测试运行的验证(或反驳)。故此,我们从为 3 个平均值中的每一个开发一款智能系统开始,并独立测试它们,从而评估它们自身的性能。一旦我们有了这些结果,我们就会凭借智能系统运行测试,其将结合所有三个平均值,我们将优化每个平均值的相对权重。
为了依据算术平均信号类开发做多和做空条件,我们只需检查 'Crossover' 函数返回的交叉值变化,其代码已在上面分享。我们的做多和做空条件代码都很短,下面分享这两段代码:
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalAM::LongCondition(void) { int result = 0; if(CrossOver(StartIndex()) > 0.0 && CrossOver(StartIndex()+1) < 0.0) { result = int(round(100.0 * ((CrossOver(StartIndex()) - CrossOver(StartIndex()+1))/fmax(fabs(CrossOver(StartIndex()))+fabs(CrossOver(StartIndex()+1)),fabs(CrossOver(StartIndex()+1))+fabs(CrossOver(StartIndex()+2)))))); } return(result); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalAM::ShortCondition(void) { int result = 0; if(CrossOver(StartIndex()) < 0.0 && CrossOver(StartIndex()+1) > 0.0) { result = int(round(100.0 * ((CrossOver(StartIndex()+1) - CrossOver(StartIndex()))/fmax(fabs(CrossOver(StartIndex()))+fabs(CrossOver(StartIndex()+1)),fabs(CrossOver(StartIndex()+1))+fabs(CrossOver(StartIndex()+2)))))); } return(result); }
如常,可见症结在于您得到潜在信号的情况下,对结果值进行归一化。对于 AM,我们正用交叉值的当前变化,除以先前交叉值的最大幅度。显然,这是一个可以进行大量自定义的领域,欢迎读者自行实现,这一选择尽管倾向于利用算术平均值,且因此而被使用。
调和平均值做多和做空条件,反过来调用 'Divergence' 函数,当作做多和做空潜在开仓的过滤器。我们所定的做空和做多条件,如下所示:
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalHM::LongCondition(void) { int result = 0; m_close.Refresh(-1); if(Divergence(StartIndex()+1) > 0.0 && m_close.GetData(StartIndex()) > m_close.GetData(StartIndex()+1)) { result = int(round(100.0 * (Divergence(StartIndex()+1)/(fabs(Divergence(StartIndex()))+fabs(Divergence(StartIndex()+1)))))); } return(result); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalHM::ShortCondition(void) { int result = 0; m_close.Refresh(-1); if(Divergence(StartIndex()+1) > 0.0 && m_close.GetData(StartIndex()) < m_close.GetData(StartIndex()+1)) { result = int(round(100.0 * (Divergence(StartIndex()+1)/(fabs(Divergence(StartIndex()))+fabs(Divergence(StartIndex()+1)))))); } return(result); }
配以调和平均值,我们正在寻找任何正背离点,其处高点上升、而低点下降,且后随收涨阳线、或收跌阴线。这两个事件按顺序发生,而非位于同一根柱线上。这种背离在许多方面与更流行的吞噬形态相反,后者通常以高点下降、及低点上升为特征。
一旦我们得到开仓,无论是做多、亦或做空,下一个问题是判定结果的整数值,它始终是这些信号类函数的做多和做空条件的输出。再一次,此处可以采取若干种方式来量化结果,其中若干种方式可能与除了调和平均值之外的其它指标相关。然而,出于我们的目的,我们希望在搭确立结果量化时更加依赖调和平均值,这就是为什么我们采用当前背离、与先前值的量级的比率来得到 0 - 100 范围内的整数值。
因此,这个结果简单地意味着当前背离越大,我们就要更看涨、或看跌。在这个结果比率的分母中(我们通过百分比归一化到 0 - 100 范围)是现值与前值背离值。这导致我们应用几何平均值。
GM 是基于 GM 缓冲区计算布林带上轨和下轨值来实现的,如上所述。为了将其转化为动作信号,如上所述,我们将分别检查看涨和看跌设置的下轨价格交叉、和上轨价格交叉。做多和做空条件的编码如下:
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalGM::LongCondition(void) { int result = 0; m_close.Refresh(-1); if(m_close.GetData(StartIndex()) > m_close.GetData(StartIndex() + 1) && m_close.GetData(StartIndex()) > BandsDn(StartIndex()) && m_close.GetData(StartIndex() + 1) < BandsDn(StartIndex() + 1)) { result = int(round(100.0 * ((m_close.GetData(StartIndex()) - m_close.GetData(StartIndex()+1))/(fabs(m_close.GetData(StartIndex()) - m_close.GetData(StartIndex()+1)) + fabs(BandsUp(StartIndex()) - BandsDn(StartIndex())))))); } return(result); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalGM::ShortCondition(void) { int result = 0; m_close.Refresh(-1); if(m_close.GetData(StartIndex()) < m_close.GetData(StartIndex() + 1) && m_close.GetData(StartIndex()) < BandsUp(StartIndex()) && m_close.GetData(StartIndex() + 1) > BandsUp(StartIndex() + 1)) { result = int(round(100.0 * ((m_close.GetData(StartIndex()+1) - m_close.GetData(StartIndex()))/(fabs(m_close.GetData(StartIndex()) - m_close.GetData(StartIndex()+1)) + fabs(BandsUp(StartIndex()) - BandsDn(StartIndex())))))); } return(result); }
结果大小也如我们遵循的算术平均值和调和平均值,也将更倾向于几何平均值,而不会用到其它指标。故此,配以 GM,结果是收盘价变化、与我们衍生的布林带的上轨和下轨之间豁口的比率。再一次,这个结果意味着相对于波带豁口的压缩,更大的价格移动应当代表更强的入场或平盘信号。平仓信号,因为做多和做空条件不仅设置了开仓阈值,而且还决定了它们的逆向持仓的平仓阈值。故此,在输入设置中,我们始终有一个开仓阈值和一个平仓阈值。后者应当小于前者,因为您想在采取任何做空开仓之前,将多头持仓平仓,反之亦然。
此外,GM 衍生出的布林带可能替代实现,在建立信号时,能够查验的一些选项,如跟踪上下轨之间豁口大小,配以基线平均值的斜率,以及少量其它迭代。我们此处的应用并非可供使用、或查看布林带的唯一方式。
策略测试与性能评估
故此,我们欲首先在智能系统中针对每条毕达哥拉斯移动平均线单独执行独立测试运行,一旦我们获得了每条移动平均线的独立结果,我们就对包含所有 3 个毕达哥拉斯平均信号的智能系统进行统筹测试运行,并优化该智能系统,从而找到每个均值的相对权重。
出于一致性,我们将依据品种 EURJPY 的 2023 年 20-分钟时间帧上进行测试。对于算术平均值,我们得到以下结果:
对于调和平均值,我们得到以下内容:
最后,对于几何平均值,我们得到:
从独立性能来看,几何平均值看似具有很大的影响力,紧跟其后是算术平均值简单交叉,落后的是调和平均值背离。当然,我们的结果受到以下事实的影响:我们依据一个非常小的窗口进行测试,并且针对每种均值如何解释和实现入场信号执行了特殊的自定义。为了得出相对性能的结论,显然需要更多的测试,但这表明性能的可变性相对较宽,在初步测试中这可能是一个有前景的信号。
如果我们现在使用所有三个平均值执行测试,并尝试优化它们的相对权重,我们会得到以下更佳结果之一:
显然,几何平均值的独立结果仍然是最准确的,当然前提是经过更长时间的测试。具有讽刺意味的是,为了让所有 3 个信号一同执行,需要为最佳独立执行者赋予 0.4 的最小权重。调和平均值和算术均值的独立落后表现者,却被赋予了 1.0 和 0.9 的更重权重,这或许可解释为什么所有三个均值的总体性能加起来不仅小于几何均值的独立性能,而且即使加上算术均值和调和平均值的独立性能,但 GM 的性能仍就更好。组合智能系统的设置分享如下:
结束语
过去的性能并不能保证未来的结果,如前所述,扩展测试最好覆盖更长的时间,这样才有保障,而且比之我们此处所做的少量测试更安全。如常,我们按照遵照 此处 和 此处 文章中分享的指南,为这些信号汇集代码。在本文中研究的三个均值中,值越小权重越大的几何平均值在布林带设置中展现出前景,不过我们在类似波带设置中还未查看调和平均值,以及针对这种相对表现得出任何更明确的结论。此外,除了布林带、AM 交叉、或 HM 背离之外,还有其它振荡器形式的移动平均线实现,像是 OSMA、或 TRIX,我们还未曾探讨过。在权衡毕达哥拉斯均值的相对潜力时,可以研究这些、及其它更多方法。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15135



