English Русский Español Deutsch 日本語 Português
preview
如何利用 MQL5 检测趋势和图表形态

如何利用 MQL5 检测趋势和图表形态

MetaTrader 5交易 | 19 十月 2023, 16:28
2 565 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

概述

作为交易者,我们都会与图表打交道,并尝试正确阅读它们,从而能够在某些方面拥有优势,譬如理解价格行为中可能发生的不同场景,并做出正确的决策。 因为图表包含许多可能出现的形态,如果我们意识到它们,就有助于预测潜在的价格走势。 故此,如果我们有实用工具可以帮助我们轻松、准确地做到这一点,我相信这将是一件美事。 在本文中,我将尝试提供一些适合这种关联环境的实用工具,为此我将提供如何检测图表上出现的东西,哪些价格形态我们需要读取,这些形态如同趋势或图表形态,可由价格行为形成。

我们将涵盖以下主题: 

阅读本文后,您将能够检测高点和低点、识别趋势类型、相应地双顶和双底。 故此,您必须尝试自行编写提及的代码,并且您必须测试和开发所需的内容,以便在将其用于真实帐户之前获得更多的真知灼见。 本文的主要目的是理解检测高点和低点的主要思想以及图表形态,供您开发越来越多的代码,来检测您对已知或未知重要形态的需求,因为图表上可以看到许多有意义的形态,如果您明白如何从中受益,这些形态可以改变您的交易游戏规则。

在本文中,我们将使用内置在 MetaTrader 5 交易终端中的 MQL5(MetaQuotes 语言)IDE。 如果您还不知道如何使用 MQL5,并且想学习如何下载 MetaTrader 5,并使用 MQL5 编写代码,您可以阅读我上一篇文章中的在 MetaEditor 中编写 MQL5 代码主题。

免责声明:所有信息仅按"原样"提供,仅用于教学目的,并非准备用于交易目的或建议。 该信息不保证任何类型的结果。 如果您选择在您的任何交易账户上使用这些素材,您应自行承担风险,并且您是唯一的责任人。

高点和低点检测

在这一部分中,我们将首先通过 MQL5 检测图表上的高点和低点,然后我们将以此为基础,根据每个图表形态声明我们的条件。 首先,众所周知,高点和低点的定义如下:

高点:

高点意味着由于买家的力量而上涨到特定价位,然后卖方出现,并将价格从这个高价位压低。 下图是一个示例。   

高点

低点:

低点意味着由于卖方的力量而下跌到特定价位,然后买方出现,并将价格从这个低价位推高。 下图是一个示例。

低点

在辨别这两个重要的价位之后,我们需要创建一个 MQL5 程序、或智能系统来检测这些走势类型。 有许多方法可完成此任务,我们将通过以下代码行提供其中一种方法。

我们需要判定具体的价位(高点和低点),然后我们将转到其它特定价(高点和低点),并分别比较两个高点和两个低点,从而判定我们是否遇到另一个高点或低点。 为此,我们需要执行如下的特定步骤。

在 OnTick() 局限之外创建一个函数,返回高点或低点。 我们将它命名为(getNextMove)返回整数型变量,我们需要为此函数设置的参数是:

  • Int move: 确定移动到高点还是低点。
  • int count: 确定与 startPos 变量相关的计数。
  • int startPos: 确定我们需要开始的起始位置。
int getNextMove(int move, int count, int startPos)

在这个函数内部,我们需要进行以下检查,即通过 if 语句来识别函数参数值,我们需要检查(startPos)是否小于零,我们需要将 startPos 值加到计数值当中,并将 startPos 更新为零值,即从当前柱线开始。

   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }

现在,我们在函数中辨别了(count)和(startPos)变量。 (move) 变量将在返回值中辨别,方法是使用 return 运算符终止函数执行,并返回移动值,并使用由三个表达式组成的三元运算符 (?:),第一个返回布尔类型的数据,如果为 true,则执行第二个表达式,如果为 false,则执行第三个表达式。

如此,我们在第一个运算符中指定移动变量是否等于高点,如果为 true,则将返回最大值,即(High),如果为 false,则将返回最低值即(Low)。

为了检查移动是否为高点,我们将使用 MODE_HIGH 函数,它是 iHighest() 和 iLowest() 函数中所用的时间序列标识符之一,用以返回最高价。 iHighest 和 iLowest 函数,返回的是最高价和最低价的索引,它们的参数如下:

  • symbol: 我们将调用 Symbol() 返回当前品种名称作为常量字符串。
  • timeframe: 我们将调用 Period() 返回当前时间帧作为 ENUM_TIMEFRAMES。
  • type: 我们将采用(ENUM_SERIESMODE)返回的走势类型作为时序标识符。 此类型将为 iHighest 提供高高点,对于 iLowest,则为低点。
  • count: 我们将调用 integer(count) 变量返回元素的数量。
  • start: 我们将采用整数型(startPos)变量来返回索引。
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));

创建此函数后,即将返回下一步走势,我们将创建另一个整数型函数,其将成为当前移动的高点或低点的主要函数。 它的名称将是(getmove),带有三个整数型变量作为参数(move,count 和 startPos)

int getmove(int move, int count, int startPos)

在这个函数中,我们需要检查走势是否等于 MODE_HIGH、或 MODE_LOW、亦或返回值是否为(-1)。

if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);

创建一个新的整数型(currentBar)变量并为其分配(startPos)。

int currentBar=startPos;

创建一个新的整数型(moveReturn)变量,并把已创建的带有以下参数(move, (count*2+1), currentBar-count)的 getNextMove 函数返回值分配给它。

int moveReturned=getNextMove(move,count*2+1,currentBar-count);

使用 While 创建一个循环,因为我们需要检查一个表达式,如果它为 true,则执行操作。 此处我们需要检查的表达式是 moveReturned 是否不等于当前柱线,如果它为 true,我们需要执行的操作是:

  • 使用 getNextMove 更新 (currentBar) 变量,参数为 (move, count,currentBar+1)。
  • 调用参数为(move,count*2+1,currentBar-count)的 getNextMove 函数更新(moveReturn) 变量。
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }

然后函数返回 currentBar 值,把控制权交还给终端函数

return(currentBar);

之后,我们将进入 OnTick() 内部,并调用有助于检测高点和低点的内容。 最先要办之事,我们创建三个整数型变量

   int checkBars= 5; 
   int move1;
   int move2;

调用我们预先创建的 getmove 函数更新 move1 和 move2,函数参数 (MODE_HIGH, checkBars,0)对应 move1,以及参数(MODE_HIGH, checkBars, move1+1)对应 move2,分别检测两个高点

   move1=getmove(MODE_HIGH,checkBars,0);
   move2=getmove(MODE_HIGH,checkBars,move1+1);

通过以下步骤在这两个高点上方创建一个直线对象:

调用(ObjectDelete) 删除任何现有直线,即删除具有名称的对象。 此函数有几个参数,第一个参数 chart_id 是确定图表标识符,我们将采用 0 对应当前图表。 第二个参数是确定对象名称,我们将取 topLine 作为字符串。

ObjectDelete(0,"topLine");

调用 ObjectCreate 函数创建新的 topLine 对象,该函数创建一个新对象。 其参数为:

  • chart_id: 我们将采用(0)返回一个长整型作为图表标识符。
  • name: 我们将取 “topLine” 返回一个字符串类型作为对象的名称。
  • type: 我们将采用 OBJ_TREND 返回 ENUM_OBJECT 类型或对象类型。
  • nwin: 我们将采用当前图表的(0)作为窗口索引。
  • time1: 确定 move2 锚点的时间,并返回日期时间类型,我们将调用 iTime(Symbol(), Period(), move2)
  • price1: 确定 move2 锚点,并返回双精度类型的价格,我们将调用 iHigh(Symbol(), Period(), move2)。
  • timeN=0: 确定 move1 锚点的时间,并返回日期时间类型,我们将调用 iTime(Symbol(), Period(), move1)。
  • priceN=0: 确定 move1 锚点的价格,并返回双精度类型,我们将调用 iHigh(Symbol(), Period(), move1)。

正如我们所看到的,iHigh 函数返回柱线的最高价,其参数是交易品种、时间帧和移位。 iTime 函数返回柱线的开盘时间,其参数与 iHigh 函数相同。

ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1));

调用 ObjectSetInteger 函数该已创建对象设置颜色、指定宽度和线条类型。 其参数为:

  • chart_id: 确定图表标识符,它将是(0)。
  • name: 用于对象名称,高点为 “TopLine”。
  • prop_id: 确定对象属性,它应是 OBJPROP_COLOR 颜色,OBJPROP_WIDTH 宽度,OBJPROP_RAY_RIGHT 线条类型。
  • prop_value: 确定所需的值,颜色为 clrRed,宽度为 3,线条类型为 true。
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);

通过更新 move1 和 move2 变量来获得两个低点,就像我们对高点所做的那样,但作为时间序列标识符,其模式应是 MODE_LOW。

   move1=getmove(MODE_LOW,checkBars,0);
   move2=getmove(MODE_LOW,checkBars,move1+1);

删除和创建这两个低点下方的线对象与我们对高点所做的相同,但对象名称存在一些差异,因为它应是 “bottomLine” 和绿色。

   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);

以下是在一个代码模块中的完整代码:

//+------------------------------------------------------------------+
//|                                                   moveFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5; 
   int move1;
   int move2;
   move1=getmove(MODE_HIGH,checkBars,0);
   move2=getmove(MODE_HIGH,checkBars,move1+1);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iHigh(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iHigh(Symbol(),Period(),move1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   move1=getmove(MODE_LOW,checkBars,0);
   move2=getmove(MODE_LOW,checkBars,move1+1);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),move2),iLow(Symbol(),Period(),move2),iTime(Symbol(),Period(),move1),iLow(Symbol(),Period(),move1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

编译此代码,在没有错误的情况下执行它,我们就可在图表上得到两条直线,检测到的两个高点,上面有一条红线,两个低点下面是一条绿线。 以下是测试示例:

moveFinder 信号1

正如我们在前面的例子中所见,这两条线也可以在图表上显示一个形态来指示价格的走势,我们可以在上例中看到我们有一个急剧的上升走势,因为我们有两条向上的线,上面一条线的角度比下面一条线更陡峭。 故此,它们可作为解释价格行为的非常实用的工具。

以下是根据不同的价格行为产生不同形态的另一个示例:

 moveFinder 信号2

正如我们在上一张图表中看到的一样,我们有不同的价格行为,表明不同的走势,因为我们有两条并行移动线,但下面的线向上移动,上面的线向下移动,表明买方和卖方之间存在平衡,因为买方推高价格,卖家同时压低价格。

以下是另一种价格行为形态的示例:

moveFinder 信号3

正如我们在上一张图表中所见,我们有不同的图表形态,因为我们有两条并行的下降线,可以指示卖家的实力,因为他们能够压低价格。

在上一部分学习了如何检测图表上的高点和低点之后,我们可以开发代码来检测图表上的趋势,因为我们已能检测到两个高点和两个低点,这正是我们识别趋势所需要的。 本文的以下这一部分,内容是关于开发我们以前的代码,以便尽可能通过以前的代码检测图表上三种类型的趋势,但也有一些差异。

简单地说,趋势是价格行为的走势,这种走势可以是向上的、向下的,或者没有明确的方向,既不上也不下。 这些是三种类型的趋势,与以下内容相同:

上行趋势:

这种类型的价格走势导致价格继续朝上移动,达成更高的价格,根据市场上的这种类型,推断买家是强势一方。 因此,我们可以在图表上清楚地发现价格形成了更高的低点和更高的高点。 下图是此类型的图表:

上行趋势

下行趋势:

这种类型的趋势与上行趋势类型相反,因为在这个下降趋势类型中,卖家比买家更强,并将价格压低,从而达成价格走低。 因此,我们可以在图表上看到价格形成更低的高点和更低的低点。

以下是从视觉角度描述它的图表:

下行趋势

横盘整理

在这种类型中,我们找不到可以描述为上行趋势或下行趋势的价格走势。 因此,这种类型是除上行趋势或下行趋势之外的任何形式,它有多种形式,下图是其中一些形式:

无趋势  无趋势2  无趋势3

现在,我们需要创建一个 MQL5 EA,它可以检测我们是否有趋势(向上或向下)、或无趋势(横盘整理)。 以下代码即可创建此类 EA:

//+------------------------------------------------------------------+
//|                                                  trendFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(lowVal1>lowVal2&&highVal1>highVal2)
     {
      Comment("Uptrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }
   else
      if(highVal1<highVal2&&lowVal1<lowVal2)
        {
         Comment("Downtrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
        {
         Comment("Sideways",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

以下是此代码中用于检测趋势的差异。

在 OnTick 函数的局限内为两个高点和两个低点创建四个整数型变量,为两个高点值和两个低点值创建另外四个双精度型变量:

   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;

调用 NormalizeDouble 函数更新两个高点值(highVal1, highVal2),高点值的舍入结果,及其参数值为:

  • value: 我们需要常规化的数字,我们将调用 iHigh 函数返回最高价,其参数是当前交易品种(_Symbol),当前时间帧(_Period),索引的偏移为 high1 和 high2。
  • digits: 小数点后的位数为 5。
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);

调用我们之前提到的相同参数的 NormalizeDouble 函数更新两个低点值(lowVal1, lowVal2),但存在以下差异:

  • value: 调用 iLow 函数返回最低价,其参数相同,只是索引的偏移将为 low1 和 low2。
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);

我们需要设置条件来识别趋势,我们将使用 if 语句,我们需要让 EA 连续检查高点和低点的四个值,然后判定它们彼此的相对位置,然后决定我们是否有趋势(向上或向下)、或无趋势(横盘整理)。

上行趋势的条件:

如果 lowVal1 大于 lowVal2,同时 highVal1 大于 highVal2,则我们有一个上行趋势,我们需要 EA 在图表上返回如下注释:

  • 上行趋势
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   if(lowVal1>lowVal2&&highVal1>highVal2)
     {
      Comment("Uptrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

下行趋势的条件:

如果 highVal1 低于 highVal2,同时 lowVal1 低于 lowVal2,则我们有一个下行趋势,我们需要 EA 在图表上返回如下注释:

  • 下行趋势
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   else
      if(highVal1<highVal2&&lowVal1<lowVal2)
        {
         Comment("Downtrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

横盘整理的条件:

如果四个值的位置除了上行趋势和下行趋势的条件之外是任何东西,我们得到一个横盘整理,我们需要 EA 返回以下内容作为图表上的注释:

      else
        {
         Comment("Sideways",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

编译代码且无错误,并执行其 EA 后,我们可以根据需要接收趋势信号。 以下是根据趋势类型及其条件进行测试的示例。

上行趋势:

trendFinder - 上行趋势信号

我们可以在上图中看到,我们有一个上升趋势示例,因为我们在此图表的价格行为上有一个更高的低点和更高的高点。 因此,我们在图表的左上角收到了一个上升趋势信号作为注释。

下行趋势:

trendFinder - 下行趋势信号

正如我们在上一张图表中清楚地看到的那样,我们有一个下行趋势,因为我们根据价格行为有一个更低的高点和更低的低点。 因此,我们得到了一个下行趋势信号作为图表上的注释。

横盘整理

trendFinder - 横盘整理信号

正如我们在前面的例子中看到的,我们有一个不同于上行趋势和下行趋势的形式,我们有一个更低的高点和更较高的低点,这是横盘整理。 因此,我们得到了一个横盘整理信号作为图表上的注释。

图表双顶检测

在我们学会了如何检测高点和低点之后,然后就该基于检测高点和低点的基础代码,开发检测趋势的代码。 故此,如果我们考虑可在代码中开发更多内容,从而尝试检测指示潜在走势的特定图表或价格行为形态。

在这一部分中,我将提供这些图表形态的示例,这些图表形态在代码中只开发了一点点,旨在了解主要思想,并开发检测更多重要的形态,尤其是若您在代码中合并了一些实用的技术工具。 我们将在本文的这一部分中看到可以在图表上看到的流行图表形态之一,即双顶。

双顶是我们可以在图表上看到的一种图表形态,由大半相同的高点组成,这表明购买力疲软,价格有可能下跌,这里有许多细节很重要,但若我们只提它的形式,我们会认为它与我们曾提到的相同。 下图是潜在双顶形态的直示例:

潜在双顶

如果您注意到我们在前面的示例中曾提到,这是一个潜在的模式,当价格突破并收于两个高点之间的低点以下时,它如下图所示:

DT

现在,我们需要创建一个 MQL5 EA,它能检测 MetaTrader 5 中的这两个图例。 我们需要 EA 连续检查两个高点和两个低点,并判定它们彼此的相对位置,然后根据特定条件,即双顶形态的条件,返回特定结果。 在此,我们将尝试以简单的方式讲述一个实用的形态,因为它可以带有略低或更高的高点,而不仅仅是相同的高点,因此,如果当前高点低于或等于前一个高点,同时当前低点大于前一个, 这就是潜在双顶的信号。 如果当前高点低于或等于前一个,同时当前低点低于前一个,这就是双顶信号。

以下是执行此操作的完整代码:

//+------------------------------------------------------------------+
//|                                             DT patternFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(highVal1<=highVal2&&lowVal1>lowVal2)
     {
      Comment("Potential Double Top",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

   else
      if(highVal1<=highVal2&&lowVal1<lowVal2)
        {
         Comment("Double Top",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
         Comment(" ");
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

此代码中的区别是形态的条件。

在潜在双顶的情况下,如果 highVal1 低于或等于 highVal12,且 lowVal1大于 lowVal2,那么我们需要在图表上得到一个信号作为注释,含有以下值:

  • 潜在的双顶
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   if(highVal1<=highVal2&&lowVal1>lowVal2)
     {
      Comment("Potential Double Top",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

在双顶的情况下,如果 highVal1 低于或等于 highVal2,且 lowVal1 低于 lowVal2,那么我们需要在图表上得到一个信号作为注释,含有以下值:

  • 双顶
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   else
      if(highVal1<=highVal2&&lowVal1<lowVal2)
        {
         Comment("Double Top",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

在没有潜在双顶形态的情况下,不返回任何注释

      else
         Comment(" ");

编译此代码,若无错误,则执行其 EA 后,我们可以从信号测试中找到以下示例。

潜在双顶的情况下:

DT patternFinder 潜在信号

正如我们在上一个图表中一样,我们有一个潜在的双顶信号,因为与预设条件匹配,即更高的低点和均等的高点。

在双顶的情况下:

 DT patternFinder 双顶信号

正如我们在上一个图表中一样,我们有一个双顶信号,因为与预设条件匹配,即更低或均等的高点,和更低的低点。

图表双底检测

在这一部分中,我们将学到如何检测双顶的相反形态,即双底形态。 双底是我们可以在图表上看到的一种图表形态,由大半相同的低点组成,这表明卖方力量疲软,并且有可能价格上涨,有许多细节也很重要,但如果我们只看其形式,我们会发现它与我们曾提到的相同。 下图是潜在双底形态的直观示例:

 潜在双底

当价格突破,并收于两个低点之间的高点上方时,之前的潜在双底形态将得到确认,如下图所示:

DB

我们需要创建另一个 MQL5 EA,用于检测 MetaTrader 5 中的前两个图例。 我们需要 EA 连续检查两个低点和两个高点,并判定它们彼此的相关位置,然后根据双底形态的条件返回特定结果。 代码中同样简单地开发对应相反的情况,以接近具有略高或略低的实际形态,不仅只针对相同的高点,如此,如果当前低点高于或等于前一个,同时当前高点低于前一个, 那么这就是潜在双底的信号。 如果当前低点大于或等于前一个,同时当前高点高于前一个,则这是双底信号。

以下是执行此操作的完整代码:

//+------------------------------------------------------------------+
//|                                             DB patternFinder.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
void OnTick()
  {
   int checkBars= 5;
   int high1, high2, low1, low2;
   double highVal1, highVal2, lowVal1, lowVal2;
   high1=getmove(MODE_HIGH,checkBars,0);
   high2=getmove(MODE_HIGH,checkBars,high1+1);
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);
   ObjectDelete(0,"topLine");
   ObjectCreate(0,"topLine",OBJ_TREND,0,iTime(Symbol(),Period(),high2),iHigh(Symbol(),Period(),high2),iTime(Symbol(),Period(),high1),iHigh(Symbol(),Period(),high1));
   ObjectSetInteger(0,"topLine",OBJPROP_COLOR,clrRed);
   ObjectSetInteger(0,"topLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"topLine",OBJPROP_RAY_RIGHT,true);
   low1=getmove(MODE_LOW,checkBars,0);
   low2=getmove(MODE_LOW,checkBars,low1+1);
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);
   ObjectDelete(0,"bottomLine");
   ObjectCreate(0,"bottomLine",OBJ_TREND,0,iTime(Symbol(),Period(),low2),iLow(Symbol(),Period(),low2),iTime(Symbol(),Period(),low1),iLow(Symbol(),Period(),low1));
   ObjectSetInteger(0,"bottomLine",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"bottomLine",OBJPROP_WIDTH,3);
   ObjectSetInteger(0,"bottomLine",OBJPROP_RAY_RIGHT,true);
   if(lowVal1>=lowVal2&&highVal1<highVal2)
     {
      Comment("Potential Double Bottom",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }
   else
      if(lowVal1>=lowVal2&&highVal1>highVal2)
        {
         Comment("Double Bottom",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }
      else
         Comment(" ");
  }
int getmove(int move, int count, int startPos)
  {
   if(move!=MODE_HIGH && move!=MODE_LOW)
      return (-1);
   int currentBar=startPos;
   int moveReturned=getNextMove(move,count*2+1,currentBar-count);
   while(moveReturned!=currentBar)
     {
      currentBar=getNextMove(move,count,currentBar+1);
      moveReturned=getNextMove(move,count*2+1,currentBar-count);
     }
   return(currentBar);
  }
int getNextMove(int move, int count, int startPos)
  {
   if(startPos<0)
     {
      count +=startPos;
      startPos =0;
     }
   return((move==MODE_HIGH)?
          iHighest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos):
          iLowest(Symbol(),Period(),(ENUM_SERIESMODE)move,count,startPos));
  }

此代码中的区别是形态的条件。

在潜在双底的情况下,如果 lowVal1 大于或等于 lowVal2,且 highVal1 低于 highVal2,那么我们需要在图表上得到一个信号作为注释,含有以下值:

  • 潜在的双底
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   if(lowVal1>=lowVal2&&highVal1<highVal2)
     {
      Comment("Potential Double Bottom",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

在双底的情况下,如果 lowVal1 大于或等于 lowVal2,且 highVal1 大于 highVal2,那么我们需要在图表上得到一个信号作为注释,具有以下值:

  • 双底
  • 当前高点
  • 前高点
  • 当前低点
  • 前低点
   else
      if(lowVal1>=lowVal2&&highVal1>highVal2)
        {
         Comment("Double Bottom",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

编译此代码,无错误,那执行其 EA 后,我们可以从测试中获得以下信号作为示例。

在潜在双底的情况下:

 DB patternFinder 潜在信号

正如我们在上一个图表中一样,我们有一个潜在的双底信号,因为与预设条件匹配,即有更低的高点,和相等或更高的低点。

在双底的情况下:

DB

正如我们在上一个图表中一样,我们有一个双底信号,因为与预设条件匹配,即存在更高的高点和等效或更高的低点。

结束语

价格行为对于交易者来说是最重要的,因为他们是在理解这种价格行为的基础上进行交易,如果他们非常了解它,他们可以做出更好的投资或交易决策。 价格行为形成了众多我们需要读取和理解的形态·。 在本文中,我们尝试提供令此任务更容易的方法,即在 MetaTrader 5 交易终端上利用 MQL5 创建系统。

在学习如何检测高点和低点之后,我们学习了如何检测趋势(上行趋势、下行趋势和横盘整理),以及一种流行的图表形态,即双顶及其相反的双底。 我们还为每个趋势概念提供了良好的相应图表类型基础,以便能够根据您的相应条件开发上述程序或系统。 此外,在掌握了创建检测高点和低点的系统主要概念之后,您可以越来越深入地开发该系统,以便能够检测更多的图表形态,如头肩、旗形、箱型......等。 我希望您发现本文对您开发相应的交易系统,并从交易业务中获得更好的结果有所帮助。

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

附加的文件 |
moveFinder.mq5 (2.24 KB)
trendFinder.mq5 (3.18 KB)
神经网络变得轻松(第三十八部分):凭借分歧进行自我监督探索 神经网络变得轻松(第三十八部分):凭借分歧进行自我监督探索
强化学习中的一个关键问题是环境探索。 之前,我们已经见识到基于内在好奇心的研究方法。 今天我提议看看另一种算法:凭借分歧进行探索。
首次启动MetaTrader VPS:分步说明 首次启动MetaTrader VPS:分步说明
使用EA交易或订阅信号的每个交易者几乎都会认识到,需要为自己的交易平台租用一个可靠的24/7全天候主机服务器。出于多种原因,我们建议使用MetaTrader VPS。您可以通过MQL5.community账户方便地支付服务费用和管理订阅。
开发回放系统 — 市场模拟(第 07 部分):首次改进(II) 开发回放系统 — 市场模拟(第 07 部分):首次改进(II)
在上一篇文章中,我们针对复现系统进行了一些修复并加入了测试,以确保可能的最佳稳定性。 我们还着手为这个系统创建和使用配置文件。
在莫斯科交易所(MOEX)里使用破位挂单的自动兑换网格交易 在莫斯科交易所(MOEX)里使用破位挂单的自动兑换网格交易
本文探讨在莫斯科交易所(MOEX)里基于破位挂单的网格交易方法如何在 MQL5 智能系统中实现。 在市场上进行交易时,最简单的策略之一是设计“捕捉”市场价格的订单网格。