English Русский Español Deutsch 日本語 Português
preview
MQL5 向导技巧须知(第27部分):移动平均线与攻击角度

MQL5 向导技巧须知(第27部分):移动平均线与攻击角度

MetaTrader 5交易系统 | 20 一月 2025, 10:23
447 0
Stephen Njuki
Stephen Njuki

概述

我们继续探讨一系列可借助MQL5向导快速测试和验证的交易布局和思路,这次我们从攻击角度的方向来考虑。广义上,攻击角度这一术语与战斗机起飞时的理想角度相关联,旨在优化以获得最大的升力和最小的燃油消耗。

然而,对于交易者来说,这个术语通常指的是趋势中证券价格的轨迹,普遍共识是陡峭的角度表明趋势强劲。因此,在本文开篇我们不仅要探讨这一观点,更重要的是采用测量指标或价格角度的方法。之后,我们会批判这种方法,指出其存在的一些问题,并提出一个可能更好的替代方案。最后,我们将一如既往地分享测试结果和报告。 

我们一如既往地使用一个自定义信号类的实例来测试我们关于如何测量攻击角度的假设,并且我们不是基于原始价格而是基于移动平均线来测量这个角度。我们使用衰减移动平均线作为测量和跟踪攻击角度重要性的指标。虽然也可以使用原始价格来监测攻击角度,但由于原始价格相比指标缓冲区(indicator buffer)的值更具波动性,因此我们选取了衰减移动平均线。其实,任何移动平均线都可以使用,但我们选择衰减移动平均线是因为它相对新颖,大多数交易者对其可能不太熟悉。


当前计算攻击角度的方法

计算攻击角度的传统方法首先是从价格或指标缓冲区中确定两个价格点,这两个价格点将用于测量角度。这个缓冲区可以是原始证券价格,但这些价格往往波动很大,因此更常见的是使用平滑后的缓冲区,如移动平均缓冲区。因此,如果我们在日线图上进行此操作,我们将获取今天的移动平均值和N天前的移动平均值。

接下来是确定每个周期内移动平均的变化量,在我们的实例中,是获取两个移动平均值所跨越的N个周期。首先要确定的是斜率m,可以通过以下公式计算得出:

其中:

  • MA(today)表示今天的移动平均值。
  • MA(N days ago)表示N天前的移动平均值。
  • N表示两个点之间的天数。

上述公式返回的m值代表斜率,并且与移动平均的攻击角度成正比。可以用其当前的形式作为衡量陡峭程度和价格动量的指标。然而,对于许多人来说,角度的概念往往指的是文章中的某物,而在这个价格行为中,它将在0到90度的范围内。

如果您想将这个斜率表示为角度,你可以使用反正切函数(即反三角函数中的正切函数的逆函数)来将斜率转换为角度:

返回值θ是以弧度为单位的角度。要将其转换为度数,您需要将其乘以:

为了看到其实际应用,让我们考虑一个非常简单的应用程序示例。假设您有以下10天的数据:

  • 10天前,MA为:100
  • 今天MA为:110

根据上面的简单公式,斜率m为:

由此得出,角度θ的弧度为:

如果我们将其转换为角度:

我们最终会得到一个在很多人看来非常陡峭的临界值。45度。如果所讨论的安全资产是日元外汇对,那么45度的概念或许可以接受或让人相信。然而,设想这样一种情况:价格代表的是一家科技公司,它在一天内就上涨了同样的幅度。如果我们已经按照上述方法进行过同样的计算,我们的角度会是84度,这已经接近90度了!不过,用这种方法来解释“攻击角度”的基本概念,会将移动平均线的斜率解读为提供了趋势强度和方向的洞察。斜率越陡峭(或角度越大),趋势就越明显。


当前方法与途径存在的问题

如上所述,在应用当前的攻击角度测量时,存在许多问题,主要源于对价格尺度的过度敏感以及在不同时间框架下角度结果的不一致性。为了说明这些问题,让我们先从价格敏感性问题开始。

如果我们交易的安全资产是非日元外汇对,比如GBPUSD,那么一个相当的10天价格变动(对于这对货币来说,大约是±0.10)会给出一个m值为0.01。如果我们将这个值乘以180,然后再将结果除以π,我们得到的角度是0.57度。我们都知道,从观察来看,45度和0.57度在幅度上相差甚远,然而这两种货币(日元对和GBPUSD)都有10000点的变动!您可能会认为这种角度差异是因为日元对波动更大,但是两个角度的比率却是成90倍!!大多数交易者都会同意,日元波动并不是比非日元对(如GBPUSD)波动成90倍。这显然是为了正确评估外汇对价格的变化,需要考虑点变动的最小计价单位(tick value)。并且为此,根据forex.com的数据,2023年波动率最高的货币对是AUDUSD和NZDUSD!

波动率最高的日元对排在第三位,尽管AUDUSD和NZDUSD的交易都是以5位小数表示的,这意味着我们上面提到的0.10点的变动会产生相同的角度,即0.57度。

根据forex.com的列表,AUDUSD由于平均每日变动1.04%而位居波动率榜首。以大约0.68150的价格,10天内每天变动1.04%将累计变动0.070876。这令m值(或斜率)为0.0070876,以及在10天内的角度为0.406度。这比我们之前用GBPUSD估计的0.507度要小,但更重要的是,排在第7位的GBPJPY据报道平均价格变动为0.81%。在相似的10天内,以147.679为基准价格,这个价格变动将计算出50.1度的角度!

当然,攻击角度并不是用来衡量波动性的,但我认为大多数读者和交易者都会期望预估的攻击角度的幅度能够在某种程度上衡量最终安全资产价格变动的规模。显然,事实并非如此,这使得这种测量角度的方法存在风险。除了由于价格敏感性导致的不一致之外,图表/分析时间框架的变化也会极大地影响攻击角度的大小。

例如,在我们上面的例子中,如果我们从日线图切换到4小时线图。在第一个示例中,我们得到45度的角度,在那个时间框架下,这个值将变为0.95度!这引发了一个问题:为什么仅仅“详细化”我们测量角度的时间段,而不改变价格尺度,我们就突然得到一个非常平缓、小于1度的角度?对这个问题的直接解决方案是:我们延长了底边(即时间长度),因此与相同的高度相比,角度上升得更少,但是既然底边的长度在时间上测量是相同的(而不是以单位测量),这种扭曲就不应该存在。


探索可替代的方法

然而,对时间框架变化所带来的问题进行更细致地考察,确实能为我们提供一个可能的解决方案。在任何三角形中,当我们需要求解角度大小,并且已知这个直角三角形的底和高时,我们可以通过计算底除以高的反正切值来得到其中一个角度(以度为单位)。

来源

因此,根据我们上面的图像,较大直角三角形的底实际上是1(它大于cosθ),这就是为什么我们的对边高度为tanθ。这里需要注意的关键点是,邻边(底)的单位与对边(高)的单位是相似的。当这种情况成立时,无论您计算的是反正切还是反余弦,您都能得到角度θ的有意义的度数值。

虽然以上共享图表提供了“过多的信息”,但它强调了正切被定义为比例的概念。正弦对余弦。或者同类相比。如果要角度θ有意义,您需要令水平轴的单位与垂直轴的单位相同。这就引出了一个问题:如何将价格和时间的两个轴协调成具有相同的单位?

在这种标准化中所面临的选择是,要么把时间轴和价格轴都转换为时间单位,要么把两者都转换为价格单位。这个转换概念一开始可能看起来有些激进,但一旦我们开始查看和比较结果,它就会变得有意义。因为价格是关键,价格向错误方向的变动会导致你的资金缩水,所以我们将价格行为的“垂直”和“水平”轴都用价格单位来标记。这又引出了一个问题:我们如何将时间轴重新缩放到价格?

有多种方法可以做到这一点,而本文的目的不是列举所有方法,也不一定采用“最好”的方法。不过,我们将使用的是一个可变比例尺。当给出任意两个在时间距离D上的价格点时,距离D将被转换为前一个距离D之前的价格范围的价格单位。这似乎很简单,但它确实能生成相对一致的结果来表示攻击角度的大小。在MQL5中,我们将按如下方式实现这一点:

//+------------------------------------------------------------------+
//| Get Angle function
//+------------------------------------------------------------------+
double CSignalAA::Angle(int Index)
{  double _angle = 0.0;
   double _price = DM(Index) - DM(Index+m_length_period);
   double _max = DM(Index+m_length_period+1);
   double _min = DM(Index+m_length_period+1);
   for(int i=Index+m_length_period+2;i<Index+(2*m_length_period);i++)
   {  double _dm = DM(i);
      _max = fmax(_max, _dm);
      _min = fmin(_min, _dm);
   }
   double _time = fmax(m_symbol.Point(), _max - _min);
   _angle = (180.0 / M_PI) * MathArctan(fabs(_price) / _time);
   if(_price < 0.0)
   {  _angle *= -1.0;
   }
   return(_angle);
}

我们的函数仅接收两个索引占位符,并通过它们来确定从哪里测量攻击角度,同时如上所述,还确定在历史数据中回溯多远以寻找作为时间度量或水平轴值的价格范围。在所选历史中的价格范围可能为零,这就是为什么我们用一个确定交易安全的最小价格点作为最小范围的原因。读者可以将此值更改为当时的价格差或任何值,只要避免除以0即可。我们接下来要介绍的是衰减移动平均函数(DMA)。


衰减移动平均(DMA)

这种移动平均赋予指数衰减的权重,其下降速度比传统指数移动平均更快。其定义如下式所示:

其中

  • n代表平均样本的大小
  • i代表样本内的位置索引
  • P代表时间i时的价格

与我们近期在本系列文章中研究的一些新型移动平均线一样,这个指标可以通过函数或自定义指标来实现,具体取决于你是打算手动交易还是使用它进行交易。自定义指标在缓冲方面表现优秀,可以为EA增加额外的功效。然而,出于测试目的,我们坚持使用函数方法,因为自定义指标意味着我们编译EA将有额外的要求。

有很多指数平均线会给近期价格赋予更多权重,我认为衰减移动平均线(decaying moving average)在这方面做得更加极致。我们可以在 MQL5 中按照如下方式实现:

//+------------------------------------------------------------------+
//| Decaying Mean                                                   |
//+------------------------------------------------------------------+
double CSignalAA::DM(int Index, int Mask = 8)
{  double _dm = 0.0;
   vector _r;
   if(_r.CopyRates(m_symbol.Name(), m_period, Mask, Index, m_length_period))
   {  //vectors are not series
      double _weight = 0.0;
      for(int i = 0; i < m_length_period; i++)
      {  _dm += (1.0/pow(2.0, m_length_period-i))*_r[i];
         _weight += (1.0/pow(2.0, m_length_period-i));
      }
      if(_weight != 0.0)
      {  _dm /= _weight;
      }
   }
   return(_dm);
}

我们只是在被平均的样本中的每个价格上赋予一个权重,这里的主要注意事项是向量不会按序列复制价格。这意味着我们需要知道最高索引价格是最新的价格,或者是从我们开始复制的那个索引开始的最接近的价格。这表明由于我们在for循环中向上计数,我们的权重(2为分母的指数)将被反置。


信号类

为了将这些组合成一个类,我们将衰减均值和角度函数添加到一个信号类的实例中,当然还要根据下面所示修改多头和空头条件:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalAA::LongCondition(void)
{  int result = 0;
   double _angle = Angle(StartIndex());
   if(_angle >= m_threshold)
   {  result = int(round(100.0 * ((_angle) / (90.0))));
   }
   return(result);
}
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int CSignalAA::ShortCondition(void)
{  int result = 0;
   double _angle = Angle(StartIndex());
   if(_angle <= -m_threshold)
   {  result = int(round(100.0 * (fabs(_angle) / (90.0))));
   }
   return(result);
}

我们的条件非常简单,它们只是检查从输入距离测量的角度在幅度上是否超过或等于输入阈值。这意味着我们的条件本质上是跟随趋势的。“角度”函数确实会返回一个正值或负值,因此,在开盘之前,多头和空头条件会检查这一点。在角度过陡的情况下,趋势跟随策略可能会被反转。这是本文未探讨的内容,但留给读者自己去研究,看看是否具有一些优点。另一个值得关注的点是,我们在条件中如何确定“结果”值的大小。由于我们的角度函数现在返回的是与传统方法相比更“一致”的值,因此我们可以确信,我们得到的角度幅度不会超过甚至接近90度。

这就是为什么我们将角度的绝对值除以90进行归一化,并将其重新缩放到所需的0–100范围内,以便将它作为结果。当然,也可以探索其他归一化方法,但这种方法往往会给本文主题(即攻击角度的值)赋予最大的权重。


两种方法的策略测试与报告

如果我们对2023年(从2023年1月1日至2024年1月1日)的GBPCHF以4小时时间框架进行测试,我们将得到以下结果:

r1

c1

它们确实体现出一定的潜力,但也可以说交易次数不够多。当然,除了这些测试仅限于一年外,它们还没有利用策略测试器的逐步前进功能,该功能可以快速筛选出哪些策略有效,哪些无效。然而,我们已经使用了衰减移动平均斜率代替价格斜率,并且由于这种特定的平均值严重偏向于最近的价格,因此我们肯定会看到信号的大幅波动。


临界攻击角度

临界角度是我们从空气动力学中借鉴的一个概念。到目前为止,我们的设置一直是仅根据衰减移动平均角度的幅度来寻找多头或空头条件。从临界攻击角度出发引入一个理念,即我们基于其做出开盘决策的这个角度,不是由一个单一的阈值来定义的,而是更好地定义在一个区间或特定范围内。为了测试和利用这一点,我们通过引入一个额外参数来修改我们的自定义信号类。

我们添加到信号类的参数‘m_band’是双精度类型值,它有助于设置触发角度的外围范围,因为我们已经有了阈值。这一修改将在以下的多头和空头条件函数中反映出来:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalAA::LongCondition(void)
{  int result = 0;
   double _angle = Angle(StartIndex());
   if(_angle >= m_threshold && _angle <= m_threshold+m_band)
   {  result = int(round(100.0 * ((_angle) / (90.0))));
   }
   return(result);
}
//+------------------------------------------------------------------+
//| "Voting" that price will fall.                                   |
//+------------------------------------------------------------------+
int CSignalAA::ShortCondition(void)
{  int result = 0;
   double _angle = Angle(StartIndex());
   if(_angle <= -m_threshold && _angle >= -(m_threshold+m_band))
   {  result = int(round(100.0 * (fabs(_angle) / (90.0))));
   }
   return(result);
}

主要的变化是,除了像我们的第一个信号那样检查角度是否超过阈值,还要检查它是否低于由频带参数定义的上限。如果我们使用这个信号文件进行测试,一旦将其组装成EA(对于新手,可以在此处此处找到相关指南),我们将从与上面类似的测试运行中得到以下结果:

r2

c2

与我们之前的结果进行快速比较可以明显看出,我们的交易频率没有那么高了,这或许是在预料之中的,因为我们现在不仅要求角度超过阈值,还要求其保持在一定的范围内。从大多数指标来看,如利润因子、恢复因子、回撤百分比等,整体表现都有所提升。因此,在得出其有效性的结论之前,可以通过更长时间的测试和逐笔交易数据对其进行进一步的探索。


结论

综上所述,我们从传统角度和新颖角度两个方面研究了攻击角度作为金融时间序列的一个指标。传统角度是简单地基于价格和原始时间变化来计算角度,而新颖角度则是将时间序列的时间轴转换为价格单位。由于我们在测量攻击角度时发现了很多不一致之处,因此本文没有展示基于传统方法的测试结果。尝试进行策略测试器运行似乎徒劳无功且浪费计算资源。

因此,我们在计算攻击角度之前,采用了一种新颖的方法,即先将水平或时间轴以时间单位划分,这种方法已经取得了一些令人欣喜的结果。我们用这种方法进行了两种模式的测试。首先,我们仅使用角度阈值来筛选应该开设哪些头寸;其次,我们采用了临界角度的概念,即要开设头寸,攻击角度不仅必须超过阈值,还必须足够接近阈值,即在一定的范围内。


本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15241

附加的文件 |
SignalWZ_27_.mqh (6.67 KB)
attack_angle.mq5 (6.52 KB)
SignalWZ_27_c.mqh (6.96 KB)
利用季节性因素进行外汇价差交易 利用季节性因素进行外汇价差交易
本文探讨了在外汇价差交易中利用季节性因素生成并提供报告数据的可能性。
开发多币种 EA 交易 (第 13 部分):自动化第二阶段 — 分组选择 开发多币种 EA 交易 (第 13 部分):自动化第二阶段 — 分组选择
我们已经实现了自动化优化的第一阶段。我们根据若干标准对不同的交易品种和时间框架进行优化,并将每次通过的结果信息存储在数据库中。现在我们将从第一阶段找到的参数集中选择最佳组。
化学反应优化(CRO)算法(第一部分):在优化中处理化学 化学反应优化(CRO)算法(第一部分):在优化中处理化学
在本文的第一部分中,我们将深入化学反应的世界并发现一种新的优化方法!化学反应优化 (CRO,Chemical reaction optimization) 利用热力学定律得出的原理来实现有效的结果。我们将揭示分解、合成和其他化学过程的秘密,这些秘密成为了这种创新方法的基础。
神经网络变得简单(第 87 部分):时间序列补片化 神经网络变得简单(第 87 部分):时间序列补片化
预测在时间序列分析中扮演重要角色。在新文章中,我们将谈谈时间序列补片化的益处。