English 中文 Español Deutsch 日本語 Português
preview
Как обнаруживать тренды и графические паттерны с помощью MQL5

Как обнаруживать тренды и графические паттерны с помощью MQL5

MetaTrader 5Трейдинг | 27 сентября 2023, 09:35
1 354 1
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Введение

Будучи трейдерами, мы все имеем дело с графиками и стараемся правильно их читать, чтобы делать правильные прогнозы и принимать необходимые решения. На графике может выделить множество паттернов, которые предсказывают потенциальное движение цены. Полезные инструменты могли бы упростить нашу задачу. В статье рассматривается обнаружение наиболее важных ценовых паттернов.

Рассмотрены следующие темы: 

Прочитав эту статью, вы сможете определять максимумы и минимумы, определять типы трендов, двойную вершину и двойное дно. Напишите упомянутые коды самостоятельно, а также протестируйте и разработайте то, что вам нужно, чтобы получить лучшие результаты, прежде чем использовать их на реальном счете. Основная цель статьи — понять основную идею обнаружения максимумов и минимумов, а также паттернов графика, чтобы улучшать свой код.

В статье мы используем среду разработки MQL5, встроенную в торговую терминал MetaTrader 5. Прочтите раздел "Как написать MQL5-код в редакторе MetaEditor" в моей более ранней статье, если не знаете, как использовать MQL5, а также как загрузить и использовать MetaEditor.

Внимание! Все содержание настоящей статьи предоставляется "как есть", предназначено только для целей обучения и не является торговой рекомендацией. Статья не несет в себе каких-либо гарантий результатов. Все, что вы применяете на практике на основе этой статьи, вы делаете исключительно на свой страх и риск, автор не гарантирует никаких результатов.

Обнаружение максимумов и минимумов

В этой части мы начнем с определения максимумов и минимумов на графике с помощью 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) будет идентифицирована в возвращаемом значении с помощью оператора возврата, который завершает выполнение функции и возвращает значение move. Тернарный оператор (?:) состоит из трех выражений, первое из которых возвращает данные типа bool. Если оно истинно, будет выполнено второе выражение, а если оно ложно, будет выполнено третье выражение.

Укажем, равна ли переменная move максимуму в первом операторе. При true возвращается максимум (High), а при false - минимум (Low).

Для проверки того, является ли перемещение максимальным, мы будем использовать функции MODE_HIGH, которые являются одним из идентификаторов временных рядов, которые используются в функциях iHighest() и iLowest() для возврата максимальной цены. Параметры функций iHighest и iLowest для возврата индекса наибольшего и наименьшего значения:

  • symbol - текущий символ в виде строки const.
  • timeframe - использовать Period() для возврата текущего таймфрейма в виде ENUM_TIMEFRAMES.
  • type - использовать (ENUM_SERIESMODE) для возврата типа перемещения в качестве идентификатора временного ряда. Этот тип является максимальным для iHighest и минимальным для iLowest.
  • count - использовать целочисленную переменную (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;

Создадим новую целочисленную переменную (moveReturned) и присвоим ей созданную функции getNextMove со следующими параметрами (move, (count*2+1), currentBar-count)).

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

Создадим цикл, используя While, поскольку нам нужно проверить выражение. При true оператор будет выполнен. Здесь в выражении проверим, не равно ли moveReturned значению currentBar. При true нам необходимо выполнить следующие операторы:

  • Обновим переменную (currentBar) с помощью getNextMove с параметрами (move, count,currentBar+1).
  • Обновим переменную (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);
     }

Затем используем функцию возврата, чтобы завершить функцию, вернув значение currentBar.

return(currentBar);

Затем зайдем внутрь OnTick() и вызовем то, что помогло обнаружить максимумы и минимумы. Первым делом мы создадим три целочисленные переменные

   int checkBars= 5; 
   int move1;
   int move2;

Обновим move1 и move2 с помощью нашей предварительно созданной функции getmove с параметрами (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");

Создадим новый объект topLine с помощью функции ObjectCreate. Параметры:

  • chart_id - использовать (0) для возврата типа long в качестве идентификатора графика.
  • name - использовать topLine, чтобы вернуть строковый тип в качестве имени объекта.
  • type - использовать OBJ_TREND для возврата типа ENUM_OBJECT или объекта.
  • nwin - использовать (0) для текущего графика в качестве индекса окна.
  • time1 - определить время точки привязки move2 и вернуть тип datetime. Мы используем iTime(Symbol(),Period(),move2)
  • price1 - определить цену точки привязки move2 и вернуть тип double. Мы используем iHigh(Symbol(),Period(),move2).
  • timeN=0 - определить время точки привязки move1 и вернуть тип datetime. Мы используем iTime(Symbol(),Period(),move1).
  • priceN=0 - определить цену точки привязки move1 и вернуть тип double. Мы используем 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 - 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 signal1

Как видим, эти две линии могут отображать на графике паттерн, а также указывать на движение цены. В приведенном примере мы видим, что у нас есть резкое движение вверх, поскольку у нас есть две восходящие линии и угол верхнего шире нижнего. Таким образом, они могут быть очень полезным инструментом для интерпретации ценового действия.

Ниже приведен еще один пример с другим паттерном на основе другого ценового действия:

 moveFinder signal2

Здесь мы имеем дело с другим ценовым действием. У нас есть две линии, которые движутся параллельно, но нижняя линия движется вверх, а верхняя линия движется вниз, указывая на наличие баланса между покупателями и продавцами, поскольку покупатели повышают цену, а продавцы одновременно снижают цену.

Ниже приведен пример другой модели ценового действия:

moveFinder signal3

Здесь у нас есть две параллельные нисходящие линии, которые могут указывать на силу продавцов, поскольку они способны толкать цену вниз.

Изучив, как мы можем обнаруживать максимумы и минимумы на графике в предыдущей части, мы можем разработать код для обнаружения трендов на графике. Мы научились обнаруживать два максимума и два минимума. Это то, что нам нужно для определения тренда. Мы усовершенствуем наш предыдущий код для максимально возможного обнаружения трендов на графике.

Говоря простыми словами, тренды – это движение цены, и это движение может быть вверх, вниз или не иметь четкого направления. Рассмотрим все три типа движения:

Восходящий тренд:

В этом случае сильной стороной на рынке являются покупатели. На графике мы видим, что цена четко формирует более высокие минимумы и более высокие максимумы. На следующем рисунке представлен график восходящего тренда:

Восходящий тренд

Нисходящий тренд:

В этом случае продавцы являются сильной стороной на рынке, толкая цены вниз. На графике мы видим, что цены формируют более низкие максимумы и более низкие минимумы.

Пример нисходящего тренда:

Нисходящий тренд

Боковое движение:

В этом случае определенного направления нет. Боковое движение имеет множество форм. Ниже представлены некоторые из них:

Отсутствие тренда  Отсутствие тренда 2  Отсутствие тренда 3

Теперь нам нужно создать MQL5-советник, который сможет определять, есть ли у нас тренд (вверх или вниз) или нет (боковое движение). Следующий код предназначен для создания такого советника:

//+------------------------------------------------------------------+
//|                                                  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;

Обновление двух значений максимумов (highVal1, highVal2) с помощью функции NormalizeDouble для округления результата значения максимумов. Его параметры:

  • value - число, которое нам нужно нормализовать. Мы будем использовать функцию iHigh для возврата максимальной цены, а ее параметрами являются текущий символ (_Symbol), текущий таймфрейм (_Period) и сдвиг индекса (high1 и high2).
  • digits - количество цифр после запятой (5).
   highVal1=NormalizeDouble(iHigh(_Symbol,_Period,high1),5);
   highVal2=NormalizeDouble(iHigh(_Symbol,_Period,high2),5);

Обновление двух минимальных значений (lowVal1, lowVal2) с помощью функции NormalizeDouble с теми же параметрами, которые мы упоминали ранее, со следующими отличиями:

  • value - используем функцию iLow, чтобы вернуть минимальную цену. Ее параметры те же, за исключением сдвига индекса, который будет low1 и low.
   lowVal1=NormalizeDouble(iLow(_Symbol,_Period,low1),5);
   lowVal2=NormalizeDouble(iLow(_Symbol,_Period,low2),5);

Условия, которые нам нужно установить для определения трендов, - мы будем использовать оператор if. Нам нужно, чтобы советник постоянно проверял четыре значения максимумов и минимумов, затем определял их позиции, связанные друг с другом, а затем решал, есть ли у нас тренд (вверх или вниз) или нет (боковое движение).

Условия восходящего тренда:

Если lowVal1 больше lowVal2 и в то же время highVal1 больше highVal2, мы имеем восходящий тренд и нам нужно, чтобы советник возвращал на график комментарий следующего содержания:

  • Uptrend (восходящий тренд)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   if(lowVal1>lowVal2&&highVal1>highVal2)
     {
      Comment("Uptrend",
              "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
              "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
     }

Условия нисходящего тренда:

Если highVal1 ниже highVal2 и в то же время lowVal1 ниже lowVal2, мы имеем нисходящий тренд и нам нужно, чтобы советник вернул на график комментарий следующего содержания:

  • Downtrend (нисходящий тренд)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   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);
        }

Скомпилировав код и запустив советника, мы получим необходимые сигналы трендов. Ниже приведены примеры тестирования по типу тренда и условиям.

Восходящий тренд:

trendFinder - сигнал восходящего тренда

На рисунке мы можем видеть пример восходящего тренда, поскольку у нас есть более высокий минимум и более высокий максимум цены. В верхнем левом углу графика мы видим сигнал восходящего тренда.

Нисходящий тренд:

trendFinder - сигнал нисходящего тренда

На рисунке мы можем видеть пример нисходящего тренда, поскольку у нас есть более низкий максимум и более низкий минимум цены. В качестве комментария мы получаем сигнал нисходящего тренда.

Боковое движение:

trendFinder - сигнал бокового движения

У нас есть более низкий максимум и более высокий минимум. Это говорит о боковом движении. В качестве комментария мы получаем сигнал бокового движения.

Обнаружение двойной вершины

Теперь пора усовершенствовать наш код. Заставим его обнаруживать определенные ценовые паттерны, которые могут указывать на потенциальное движение.

В этой разделе приводятся примеры графических паттернов с небольшой доработкой кода для понимания основной идеи и возможного потенциала для обнаружения других важных паттернов. Мы рассмотрим один из популярных графических паттернов - двойную вершину.

Это фигура, состоящая из примерно одинаковых максимумов, что указывает на слабость покупателей и вероятность того, что цены пойдут вниз. Ниже приведен пример:

Потенциальная двойная вершина

Перед нами потенциальная двойная вершина. Она окончательно сформируется, если цена опустится ниже инимума между двумя максимумами, как показано на следующем рисунке:

Двойная вершина

Теперь нам нужно создать советник MQL5, который можно использовать для обнаружения этих двух паттернов в MetaTrader 5. Нам нужно, чтобы советник постоянно проверял два максимума и два минимума и определял их позиции относительно друг друга, а затем возвращал конкретный результат на основе определенного условия паттерна "Двойная вершина". Разумеется, в реальных условиях паттерн редко отображается идеально. Если текущий максимум ниже или равен предыдущему и в то же время текущий минимум выше предыдущего, это будет сигналом потенциальной двойной вершины. Если текущий максимум ниже или равен предыдущему и в то же время текущий минимум ниже предыдущего, это будет сигналом двойной вершины.

Ниже приведен полный код:

//+------------------------------------------------------------------+
//|                                             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 ниже или равен highVal2, а lowVal1 больше lowVal2, то нам необходимо получить сигнал в виде комментария на графике со следующими значениями:

  • Potential Double Top (потенциальная двойная вершина)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   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, то нам необходимо получить сигнал в виде комментария на графике со следующими значениями:

  • Double Top (двойная вершина)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   else
      if(highVal1<=highVal2&&lowVal1<lowVal2)
        {
         Comment("Double Top",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

В случае отсутствия паттернов никакого комментария не выводится.

      else
         Comment(" ");

После компиляции запускаем советник на графике.

Потенциальная двойная вершина:

DT patternFinder - потенциальная двойная вершина

Как видим, условия срабатывания сигналы удовлетворены: максимумы одинаковы, один минимум чуть выше другого.

Сформированная двойная вершина:

 DT patternFinder - сформированная двойная вершина

Условия срабатывания сигналы удовлетворены: более низкий или равный максимум и более низкий минимум.

Обнаружение двойного дна

Двойное дно является противоположностью двойной вершины. Это фигура, состоящая из примерно одинаковых минимумов, что указывает на слабость продавцов и вероятность того, что цены пойдут вверх. Ниже приведен пример:

 Потенциальное двойное дно

Двойное дно подтверждается, когда цены закроются выше максимума между двумя минимумами, как показано на следующем рисунке:

Двойное дно

Нам нужно создать еще один советник MQL5, который можно использовать для обнаружения двух предыдущих фигур в MetaTrader 5. Нам нужно, чтобы советник постоянно проверял два минимума и два максимума и определял их позиции относительно друг друга, а затем возвращал конкретный результат на основе определенного условия паттерна "Двойное дно". Если текущий минимум выше или равен предыдущему и в то же время текущий максимум ниже предыдущего, это будет сигналом потенциального двойного дна. Если текущий минимум выше или равен предыдущему и в то же время текущий максимум выше предыдущего, это будет сигналом двойной вершины.

Ниже приведен полный код:

//+------------------------------------------------------------------+
//|                                             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, то нам нужно получить сигнал в виде комментария на графике со следующими значениями:

  • Potential Double Bottom (потенциальное двойное дно)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   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, то нам нужно получить сигнал в виде комментария на графике со следующими значениями:

  • Double Bottom (двойное дно)
  • Current High (текущий максимум)
  • Previous High (предыдущий максимум)
  • Current Low (текущий минимум)
  • Previous Low (предыдущий минимум)
   else
      if(lowVal1>=lowVal2&&highVal1>highVal2)
        {
         Comment("Double Bottom",
                 "\nCurrent High ",highVal1,"\nPrevious High ",highVal2,
                 "\nCurrent Low ",lowVal1,"\nPrevious Low ",lowVal2);
        }

Компилируем код советника, и он появляется в окне Навигатора в терминале, откуда запускаем его на графике. Ниже показан пример работы:

Потенциальное двойное дно:

 DB patternFinder - потенциальное двойное дно

Условия срабатывания сигналы удовлетворены: более низкий максимум и равный или более высокий минимум.

Сформированное двойное дно:

Двойное дно

Условия срабатывания сигналы удовлетворены: более высокий максимум и равный или более высокий минимум.

Заключение

Глубокое понимание ценового действия позволяет трейдерам принимать более эффективные инвестиционные и торговые решения. Ценовое действие формирует множество паттернов, которые нам необходимо знать. Мы постарались упростить эту задачу путем создания MQL5-советников для использования в торговом терминале MetaTrader 5.

Научившись определять максимумы и минимумы, мы научились определять тренды (восходящий, нисходящий и боковой) и одну из популярных графических фигур — двойную вершину и противоположное ей двойное дно, а также создавать советники на их основе. Вы можете развить идеи, изложенные в статье, и разработать больше систем, обнаруживающих другие графические паттерны, такие как голова и плечи, треугольники, прямоугольники и т. д. Надеюсь, статья будет для вас полезной и поможет вам в разработке торговых систем и в достижении торговых целей.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/12479

Прикрепленные файлы |
moveFinder.mq5 (2.24 KB)
trendFinder.mq5 (3.18 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
JRandomTrader
JRandomTrader | 27 сент. 2023 в 10:06
Создавать графические объекты на каждом тике - не самая лучшая идея.
Разработка системы репликации - Моделирование рынка (Часть 12): Появление СИМУЛЯТОРА (II) Разработка системы репликации - Моделирование рынка (Часть 12): Появление СИМУЛЯТОРА (II)
Разработка симулятора может оказаться гораздо интереснее, чем кажется. Сегодня мы сделаем еще несколько шагов в этом направлении, потому что всё становится интереснее.
Разработка MQTT-клиента для MetaTrader 5: методология TDD Разработка MQTT-клиента для MetaTrader 5: методология TDD
Статья представляет собой первую попытку разработать нативный MQTT-клиент для MQL5. MQTT - это протокол обмена данными по принципу "издатель - подписчик". Он легкий, открытый, простой и разработан так, чтобы его было легко внедрить. Это позволяет применять его во многих ситуациях.
Разработка системы репликации - Моделирование рынка (Часть 13): Появление СИМУЛЯТОРА (III) Разработка системы репликации - Моделирование рынка (Часть 13): Появление СИМУЛЯТОРА (III)
Здесь мы немного упростим несколько элементов, связанных с работой в следующей статье. Я также объясню, как можно визуализировать то, что генерирует симулятор с точки зрения случайности.
Объектно-ориентированное программирование (ООП) в MQL5 Объектно-ориентированное программирование (ООП) в MQL5
Как разработчикам, нам необходимо научиться создавать и разрабатывать программное обеспечение, которое можно использовать многократно и гибко, без дублирования кода, особенно если у нас есть разные объекты с разным поведением. Это можно легко сделать, используя методы и принципы объектно-ориентированного программирования. В этой статье представлены основы объектно-ориентированного программирования в MQL5.