MQL4: работа с барами, поиск дивергенции по RSI

Здравствуйте, уважаемые коллеги программисты MQL4!

В сегодняшнем уроке мы разберем, как можно автоматизировать средствами MQL4 один из самых сильных и надежных паттернов – дивергенцию. Для этого будем писать скрипт, а из технических индикаторов нам поможет индекс относительной силы (RSI). Также разберем работу с анализом баров и напишем скрипты для разметки фракталов на истории.

MQL4 - Работа с барами, поиск Дивергенции по RSI

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

Бар в широком смысле – не только графическое изображение одного ценового периода, но и элемент анализа. Так, индикаторы рассчитываются побарно. Существуют и достаточно популярные торговые методики, основанные только на анализе баров: это и свечной анализ, и Price Action, где периодически повторяющиеся фигуры и комбинации баров выступают в качестве самостоятельных торговых сигналов. Также свечные формации используются для построения торговых уровней и фигур технического анализа.

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

Наиболее распространённым графическим представлением баров являются так называемые «японские свечи». Именно их мы будем использовать для иллюстраций и называть барами.

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

Массив баров на графике на всю глубину исторических данных называется таймсерией. Нумерация в этом массиве ведётся справа налево, то есть индекс 0 (ноль) в массиве-таймсерии означает данные текущего бара, который соответствует незавершенному промежутку времени на данном таймфрейме. Размер массива-таймсерии можно получить, вызвав функцию iBars, или обратившись к предопределённой переменной Bars.

Доступ к данным конкретного бара осуществляется с помощью функций:

iOpen – цена открытия бара;

iClose – цена закрытия бара;

iHigh – максимальная цена бара;

iLow – минимальная цена бара;

iTime – время открытия бара;

iVolume – тиковый объём или сколько раз цена меняла значение за время формирования бара.

Примеры работы с таймсериями

Сначала рассмотрим простой пример работы с таймсерией. Найдём на всей доступной истории свечу с наибольшим объёмом и одновременно посчитаем средний размер тел свечей:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ShowBarsParameters()
  {
   Comment("");
   long VolumeMax=0;  // максимальный объём
   int VolumeMaxIndex=0;// индекс бара с максимальным объёмом

   double BodySum=0;  // суммарная величина тел свечей

   for(int i=Bars-1; i>=0; i--)
     {
      if(VolumeMax<iVolume(NULL,0,i))
        {
         VolumeMax=iVolume(NULL,0,i);
         VolumeMaxIndex=i;
        }
      BodySum+=MathAbs(iOpen(NULL,0,i)-iClose(NULL,0,i));
     }
   Comment("На графике всего: ", Bars," баров","
",
           "Максимальное значение объема ",VolumeMax," получено ",
           iTime(NULL,0,VolumeMaxIndex)," на баре №",VolumeMaxIndex,"
",
           "Средняя величина тел свечей в пунктах: ", int(BodySum/_Point)/Bars);
  } 

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ShowBarsParametersExt(string symbol, int timeframe)
  {
   Comment("");
   long VolumeMax=0;  // максимальный объём
   int VolumeMaxIndex=0;// индекс бара с максимальным объёмом

   double BodySum=0;  // суммарная величина тел свечей

   for(int i=iBars(symbol,timeframe)-1; i>=0; i--)
     {
      if(VolumeMax<iVolume(symbol,timeframe,i))
        {
         VolumeMax=iVolume(symbol,timeframe,i);
         VolumeMaxIndex=i;
        }
      BodySum+=MathAbs(iOpen(symbol,timeframe,i)-iClose(symbol,timeframe,i));
     }
   Comment("На графике ",symbol,"(",timeframe,") всего: ", iBars(symbol,timeframe)," баров","
",
           "Максимальное значение объема ",VolumeMax," получено ",
            iTime(symbol,timeframe,VolumeMaxIndex)," на баре №",VolumeMaxIndex,"
",
           "Средняя величина тел свечей в пунктах: ", int(BodySum/_Point)/Bars);
  }

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

ShowBarsParameters("EURJPY",PERIOD_H1);

Разметка фракталов Билла Вильямса

Давайте научимся определять простейшие свечные формации. Так как мы собираемся искать дивергенции осциллятора RSI с ценой, нам надо решить задачу с определением локальных экстремумов на графике. По этим опорным точкам можно будет провести трендовую линию, которая покажет направление движения цены. Самым простым способом определения таких точек является разметка фракталов Билла Вильямса. Фракталы представляют собой формации из пяти свечей. Если High центральной свечи расположен выше максимальных цен двух свечей слева и двух свечей справа – имеет место «фрактал вверх»:

Если же Low центрального бара ниже минимумов двух баров слева и двух баров справа, то перед нами «фрактал вниз»:

В терминале есть встроенный индикатор фракталов:

Мы напишем скрипт, который будет делать разметку фракталов на исторических данных, подобно тому, как это делает встроенный индикатор Fractals.

С помощью кнопки в главной панели MetaEditor создадим скрипт «Фракталы.mq4»:

Скрипт более удобен для демонстрации, так как изначально состоит из одной главной функции OnStart(), которая запускается, как только скрипт был добавлен к графику валютной пары. После однократного выполнения OnStart() скрипт выгружается из памяти. Весь рабочий код мы поместим в функции, которые потом можно будет без труда перенести в ваши советники и индикаторы.
//+------------------------------------------------------------------+
//|                                                     Фракталы.mq4 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
  }

Для отображения фракталов будем использовать графические объекты «стрелка» (OBJ_ARROW). Нам понадобится функция, которая будет создавать такие объекты на графике цены. Воспользуемся готовой функцией ArrowCreate из справочника MQL, идущего в комплекте с терминалом:

//+------------------------------------------------------------------+ 
//| Создает стрелку                                                  | 
//+------------------------------------------------------------------+ 
bool ArrowCreate(const long              chart_ID=0,           // ID графика 
                 const string            name="Arrow",         // имя стрелки 
                 const int               sub_window=0,         // номер подокна 
                 datetime                time=0,               // время точки привязки 
                 double                  price=0,              // цена точки привязки 
                 const uchar             arrow_code=252,       // код стрелки 
                 const ENUM_ARROW_ANCHOR anchor=ANCHOR_BOTTOM, // положение точки привязки 
                 const color             clr=clrRed,           // цвет стрелки 
                 const ENUM_LINE_STYLE   style=STYLE_SOLID,    // стиль окаймляющей линии 
                 const int               width=3,              // размер стрелки 
                 const bool              back=false,           // на заднем плане 
                 const bool              selection=false,      // выделить для перемещений 
                 const bool              hidden=true,          // скрыт в списке объектов 
                 const long              z_order=0)            // приоритет на нажатие мышью 

Создадим функцию, которая получает индекс бара и определяет, сформирован ли на нём фрактал:

//+------------------------------------------------------------------+
//| Проверяет, сформирован ли на баре с индексом index               |
//| фрактал                                                          |
//|                                                                  |
//| is_up - эта переменная принимает значение true, если сформирован  |
//| фрактал вверх                                                 |
//| is_down - эта переменная принимает значение true, если сформирован|
//| фрактал вниз                                                  |
//| Если фрактал не сформирован, то обеим переменным                 |
//| присваивается значение false                                     |
//+------------------------------------------------------------------+
void GetFractalType(int index, bool &is_up, bool &is_down)
  {
// сбрасываем значения переменных
   is_up=is_down=false;

// проверяем, выше ли High (максимум) центральной свечи двух предыдущих и двух последующих
   is_up=(iHigh(_Symbol,_Period,index+1)<iHigh(_Symbol,_Period,index) &&
          iHigh(_Symbol,_Period,index+2)<iHigh(_Symbol,_Period,index) &&
          iHigh(_Symbol,_Period,index-1)<iHigh(_Symbol,_Period,index) &&
          iHigh(_Symbol,_Period,index-2)<iHigh(_Symbol,_Period,index));

// проверяем, ниже ли Low (минимум) центральной свечи двух предыдущих и двух последующих
   is_down=(iLow(_Symbol,_Period,index+1)>iLow(_Symbol,_Period,index) &&
            iLow(_Symbol,_Period,index+2)>iLow(_Symbol,_Period,index) &&
            iLow(_Symbol,_Period,index-1)>iLow(_Symbol,_Period,index) &&
            iLow(_Symbol,_Period,index-2)>iLow(_Symbol,_Period,index));
  }

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

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

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ShowFractals()
  {
   // пропускаем по два бара в конце и в начале таймсерии
   for(int i=Bars-2; i>1; i--)
     {
      string Name;// имя объекта
      bool IsUp, IsDown;
      // получаем тип фрактала, если он сформирован на данном баре
      GetFractalType(i,IsUp,IsDown);

      // сформирован фрактал вверх
      if(IsUp)
        {
         Name="FRCTL_UP_"+_Symbol+"_"+IntegerToString(iTime(_Symbol,_Period,i));
         // рисуем над High свечи синюю точку
         ArrowCreate(0,Name,0,iTime(_Symbol,_Period,i),iHigh(_Symbol,_Period,i),
         159,ANCHOR_BOTTOM,clrBlue,STYLE_SOLID,2,true,false);
        }
      // сформирован фрактал вниз
      if(IsDown)
        {
         Name="FRCTL_DOWN_"+_Symbol+"_"+IntegerToString(iTime(_Symbol,_Period,i));
         // рисуем под Low свечи красную точку
         ArrowCreate(0,Name,0,iTime(_Symbol,_Period,i),iLow(_Symbol,_Period,i),
         159,ANCHOR_BOTTOM,clrRed,STYLE_SOLID,2,true,false);
        }
     }
  }

Также нам понадобится функция, которая будет удалять графические объекты, которые могли остаться на графике от предыдущего запуска нашего скрипта. Эта функция будет искать графические объекты, имя которых начинается с определённого набора символов (так называемый префикс), и удалять только их. Ранее мы позаботились, чтобы создаваемые объекты имели такой префикс: «FRCTL». Удалять объекты мы будем перед вызовом функции ShowFractals.

//+------------------------------------------------------------------+ 
//| Удаляет графические объекты с префиксом prfx                     | 
//+------------------------------------------------------------------+
void DeleteObjects(string prfx)
  {
   for(int i=ObjectsTotal()-1; i>=0; i--)
     {
      string nm=ObjectName(0,i);
      if(StringSubstr(nm,0,StringLen(prfx))==prfx)
         ObjectDelete(0,nm);
     }
  }

В итоге функция OnStart() будет выглядеть следующим образом:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   DeleteObjects("FRCTL");
   ShowFractals();
  }

Результат работы нашего скрипта:

Автоматический поиск дивергенции на примере осциллятора RSI

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

Различают медвежью и бычью дивергенции.

Медвежья дивергенция – это ситуация, когда цена формирует всё более высокие максимумы при восходящем тренде, а каждая новая вершина индикатора RSI оказывается ниже предыдущей:

Медвежья дивергенция служит сигналом высокой вероятности смены тренда на нисходящий (медвежий).

Бычья дивергенция – это ситуация, когда цена формирует всё более низкие минимумы при нисходящем тренде, а каждая новая впадина индикатора RSI оказывается выше предыдущей:

Бычья дивергенция служит сигналом высокой вероятности смены тренда на восходящий (бычий).

Реже используются сигналы конвергенции или, как её чаще называют, «скрытой» дивергенции. Конвергенция сигнализирует о продолжении существующего тренда.

Скрытая медвежья дивергенция:

Скрытая бычья дивергенция:

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

С помощью кнопки «Создать» в главной панели MetaEditor создадим скрипт «Дивергенция.mq4».

В этом скрипте нам понадобятся входные параметры, поэтому добавим директиву script_show_inputs:

#property script_show_inputs

Теперь скрипт не будет выполняться сразу после добавления его на график, а откроет окно параметров, подобно советнику или индикатору.

Наш скрипт будет использовать следующие параметры:

input int  RSIPeriod             = 9;     // Период RSI
input bool ShowFracrals          = false; // Показывать фракталы
input bool ShowHiddenDivergence  = false; // Показывать "скрытую" дивергенцию 

Определим строковую константу-префикс для имён графических объектов. Она понадобится нам при поиске объектов для их удаления:

#define PREFIX    "OBJ_DIV_"

Опишем структуру для хранения данных о фрактале и объявим два массива таких структур для фракталов вверх и для фракталов вниз:

struct fractal
  {
   datetime          time;      // время открытия бара, на котором был сформирован фрактал
   double            peak;      // максимум или минимум бара в зависимости от типа фрактала
   double            indicator; // значение индикатора RSI на баре
  } FractalsUp[],FractalsDown[];

Добавим в код функцию DeleteObjects, которую мы написали ранее, и вызовем её в обработчике OnStart(). Тем самым мы удалим с графика объекты, которые могли остаться после предыдущего запуска скрипта.  Установим нулевой размер у массивов фракталов и определим направление индексации в массивах, как в таймсериях (справа налево).

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   // удалим объекты, возможно оставшиеся от предыдущего запуска скрипта
   DeleteObjects(PREFIX);

   // установим нулевой размер у массивов фракталов
   ArrayResize(FractalsUp,0);
   ArrayResize(FractalsDown,0);

   // определим направление индексации в массивах фракталов как в таймсериях(справо налево)
   ArraySetAsSeries(FractalsUp,true);
   ArraySetAsSeries(FractalsDown,true);
  }

//+------------------------------------------------------------------+
//| Удаляет графические объекты с префиксом prfx                     |
//+------------------------------------------------------------------+
void DeleteObjects(string prfx)
  {
   for(int i=ObjectsTotal()-1; i>=0; i--)
     {
      string nm=ObjectName(0,i);
      if(StringSubstr(nm,0,StringLen(prfx))==prfx)
         ObjectDelete(0,nm);
     }
  }

Напишем функцию, которая заполняет массивы FractalsUp[] и FractalsDown[] найденными на истории фракталами. При этом, если параметр ShowFracrals = true, то найденные фракталы будут отображаться на графике цветными точками. Функцию отрисовки графических объектов возьмём из комплектного руководства MQL, а функцию GetFractalType, определяющую тип фракталов – из написанного нами ранее скрипта «Фракталы.mq4».

//+------------------------------------------------------------------+
//| Находит на истории все фракталы и заполняет соответствующие      |
//| массивы фракталов.                                               |
//| В зависимости от параметра ShowFracrals отображает найденные     |
//| фракталы на графике                                              |  
//+------------------------------------------------------------------+  
void ShowFractalsAndFillArrays()
{
// пропускаем по два бара в конце и в начале таймсерии
   int to=Bars-2;
   for(int i=2; i<to; i++)
     {
      string Name;// имя объекта
      bool IsUp, IsDown;
      double rsi;

      // получаем тип фрактала, если он сформирован на данном баре
      GetFractalType(i,IsUp,IsDown);

      // сформирован фрактал вверх
      if(IsUp)
        {
         rsi=iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i);
         // также есть максимум на RSI
         if(iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i+1)<rsi &&
            iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i-1)<rsi)
           {
            // добавляем данные о фрактале в массив
            int ind=AddFractalToArray(FractalsUp,iTime(_Symbol,_Period,i),
            iHigh(_Symbol,_Period,i),rsi);
 
           // рисуем над High свечи синюю точку
            if(ShowFracrals)
            {
              Name=PREFIX+_Symbol+"_FRCTL_UP_"+IntegerToString(FractalsUp[ind].time);
              ArrowCreate(0,Name,0,FractalsUp[ind].time,FractalsUp[ind].peak,159,
              ANCHOR_BOTTOM,clrBlue,STYLE_SOLID,2,true,false);
            }   
           }
        }
      // сформирован фрактал вниз
      if(IsDown)
        {
         rsi=iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i);
         // также есть минимум на RSI
         if(iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i+1)>rsi && 
            iRSI(_Symbol,_Period,RSIPeriod,PRICE_CLOSE,i-1)>rsi)
           {
            // добавляем данные о фрактале в массив
            int ind=AddFractalToArray(FractalsDown,iTime(_Symbol,_Period,i),
            iLow(_Symbol,_Period,i),rsi);
            
            // рисуем под Low свечи красную точку
            if(ShowFracrals)
            {
             Name=PREFIX+_Symbol+"_FRCTL_DOWN_"+IntegerToString(FractalsDown[ind].time);
             ArrowCreate(0,Name,0,FractalsDown[ind].time,FractalsDown[ind].peak,159,
             ANCHOR_TOP,clrRed,STYLE_SOLID,2,true,false);
            }   
           }
        }
     }
}

После того, как оба массива фракталов заполнены, нам потребуется функция, которая найдёт и разметит линиями возможные дивергенции цены и осциллятора RSI:

//+------------------------------------------------------------------+
//| Ищет и размечает трендовыми линиями дивергенции на               |
//| графике цены и осцилляторе RSI                                   |
//+------------------------------------------------------------------+
void ShowDivergence()
  {
   int size;
   string Name=PREFIX+_Symbol+"TLINE_";
// ----------- рисуем линии медвежьей дивергенции по локальным максимумам
   size=ArraySize(FractalsUp);
   for(int i=0; i<size-1; i++)
     {
      int Style=EMPTY;
      // медвежья дивергенция – цена вверх, RSI вниз
      if(FractalsUp[i].peak>FractalsUp[i+1].peak &&
         FractalsUp[i].indicator<FractalsUp[i+1].indicator)
         Style=STYLE_SOLID;
      else
         // скрытая медвежья дивергенция (конвергенция) – цена вниз, RSI вверх
         if(ShowHiddenDivergence && FractalsUp[i].peak<FractalsUp[i+1].peak &&
            FractalsUp[i].indicator>FractalsUp[i+1].indicator)
            Style=STYLE_DOT;
      if(Style>EMPTY)
        {
         TrendCreate(0,Name+"BEAR_BAR_"+IntegerToString(FractalsUp[i].time),0,
         FractalsUp[i+1].time,FractalsUp[i+1].peak,FractalsUp[i].time,FractalsUp[i].peak,
         clrMaroon,ENUM_LINE_STYLE(Style));
         TrendCreate(0,Name+"BEAR_RSI_"+IntegerToString(FractalsUp[i].time),1,
         FractalsUp[i+1].time,FractalsUp[i+1].indicator,FractalsUp[i].time,
         FractalsUp[i].indicator,clrMaroon,ENUM_LINE_STYLE(Style));
        }
     }
// ----------- рисуем линии бычьей дивергенции по локальным минимумам
   size=ArraySize(FractalsDown);
   for(int i=0; i<size-1; i++)
     {
      int Style=EMPTY;
      // бычья дивергенция – цена вниз, RSI вверх
      if(FractalsDown[i].peak<FractalsDown[i+1].peak &&    
         FractalsDown[i].indicator>FractalsDown[i+1].indicator)
         Style=STYLE_SOLID;
      else
         // скрытая бычья дивергенция (конвергенция) – цена вверх, RSI вниз
         if(ShowHiddenDivergence && FractalsDown[i].peak>FractalsDown[i+1].peak && 
            FractalsDown[i].indicator<FractalsDown[i+1].indicator)
            Style=STYLE_DOT;
      if(Style>EMPTY)
        {
          TrendCreate(0,Name+"BULL_BAR_"+IntegerToString(FractalsDown[i].time),0,
          FractalsDown[i+1].time,FractalsDown[i+1].peak,FractalsDown[i].time,
          FractalsDown[i].peak, clrGreen,ENUM_LINE_STYLE(Style));
          TrendCreate(0,Name+"BULL_RSI_"+IntegerToString(FractalsDown[i].time),1,
          FractalsDown[i+1].time,FractalsDown[i+1].indicator,FractalsDown[i].time,
          FractalsDown[i].indicator,clrGreen,ENUM_LINE_STYLE(Style));
        }
     }
  }

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

Добавим вызов функций в обработчик OnStart():

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   // удалим объекты, возможно оставшиеся от предыдущего запуска скрипта
   DeleteObjects(PREFIX);

   // установим нулевой размер у массивов фракталов
   ArrayResize(FractalsUp,0);
   ArrayResize(FractalsDown,0);

   // определим направление индексации в массивах фракталов как в таймсериях(справо налево)
   ArraySetAsSeries(FractalsUp,true);
   ArraySetAsSeries(FractalsDown,true);
   
   ShowFractalsAndFillArrays();
   ShowDivergence();
  }

Наш скрипт готов. Запустим его на часовом графике EURUSD, предварительно прикрепив к графику индикатор RSI с таким же периодом, как в скрипте:

Скрипт можно запускать и без индикатора RSI. В таком случае линии дивергенции будут построены только на графике цены.

Включим отображение фракталов (цветные точки) и скрытой дивергенции (пунктирные линии):

Заключение

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

Тема на форуме

С уважением, Юрий Лосев aka lsv107
Tlap.com

Уроки по MQL4 , , , ,