ZigZag всему голова (Часть I). Разработка базового класса индикатора

19 января 2019, 16:35
Anatoli Kazharski
85
2 768

Содержание


Введение

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

Многие исследователи просто упускают такой момент, как определение характера поведения цены или не уделяют этому должного внимания. При этом используются сложные методы, которые очень часто являются просто «чёрными ящиками», такие как: машинное обучение или нейронные сети. В таких случаях самым важным является такой момент, как: «Какие данные подать на вход для обучения той или иной модели?». В этой статье расширим инструментарий для подобных исследований. Будет показано, как выбрать более подходящие символы для торговли ещё до процесса поиска оптимальных параметров. Для этих целей будет использоваться модифицированная версия индикатора ZigZag и класс кода, с помощью которого существенно облегчено получение и работа с данными индикаторов этого типа.

В этой серии статей реализуем:

  • модифицированную версию индикатора ZigZag
  • класс для получения данных ZigZaga’а
  • эксперта для теста получения данных
  • индикаторы, с помощью которых можно определить характер поведения цены
  • эксперта с графическим интерфейсом для сбора некоторой статистики поведения цены
  • торгового эксперта, торгующего по сигналам индикатора ZigZag


Расширенная версия индикатора ZigZag

Обычно индикаторы типа ZigZag строятся по максимумам и минимумам баров без учёта спреда. В этой статье будет показана модифицированная версия, когда спред будет учитываться в построении сегментов для нижних экстремумов ZigZag’а. Предполагается, что в торговой системе сделки будут осуществляться внутрь ценового канала. Это важно, так как очень часто бывает (например, в ночное время), что цена покупки (ask) существенно превышает цену продажи (bid), поэтому строить индикатор только по bid-ценам в данном случае будет неправильно. Ведь нет смысла строить нижние экстремумы индикатора по минимумам баров, если нет возможности осуществить покупку по этим ценам. Конечно же, спред можно учитывать в торговых условиях, но лучше, когда сразу на графике всё будет видно. Это упростит разработку торговой стратегии, так как всё изначально будет более правдоподобным.

Кроме этого, хотелось бы видеть все точки, на которых экстремумы ZigZaga’а обновлялись. Тогда картина будет ещё более полной. Далее рассмотрим код этого индикатора. Остановимся только на основных моментах и функциях.

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

Перечислим все индикаторные буферы:

  • Минимальная цена Ask. По этим значениям будут строится минимумы ZigZag’а
  • Максимальная цена Bid. По этим значениям будут строится максимумы ZigZag’а
  • Максимумы
  • Минимумы
  • Все зафиксированные максимумы сегмента направленного вверх
  • Все зафиксированные минимумы сегмента направленного вниз

#property indicator_chart_window
#property indicator_buffers 6
#property indicator_plots   5
//---
#property indicator_color1  clrRed
#property indicator_color2  clrCornflowerBlue
#property indicator_color3  clrGold
#property indicator_color4  clrOrangeRed
#property indicator_color5  clrSkyBlue

//--- Индикаторные буферы:
double low_ask_buffer[];    // Минимальная цена Ask
double high_bid_buffer[];   // Максимальная цена Bid
double zz_H_buffer[];       // Максимумы
double zz_L_buffer[];       // Минимумы
double total_zz_h_buffer[]; // Все максимумы
double total_zz_l_buffer[]; // Все минимумы

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

//--- Внешние параметры
input int   NumberOfBars   =0;       // Number of bars
input int   MinImpulseSize =100;     // Minimum points in a ray
input bool  ShowAskBid     =false;   // Show ask/bid
input bool  ShowAllPoints  =false;   // Show all points
input color RayColor       =clrGold; // Ray color

На глобальном уровне объявим вспомогательные переменные, которые понадобятся для расчёта экстремумов. Нужно запоминать индексы ранее рассчитанных экстремумов, отслеживать текущее направление сегмента, а также сохранять минимальную цену ask-цены и максимальную цену bid-цены.

//--- Переменные для ZZ
int    last_zz_max  =0;
int    last_zz_min  =0;
int    direction_zz =0;
double min_low_ask  =0;
double max_high_bid =0;

Для заполнения индикаторных буферов для минимальных ask-цен и максимальных bid-цен, используется функция FillAskBidBuffers(). Для bid-буфера сохраняем значения из массива high, а для ask-буфера значения из массива low с учётом спреда.

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы High Bid и Low Ask                 |
//+------------------------------------------------------------------+
void FillAskBidBuffers(const int i,const datetime &time[],const double &high[],const double &low[],const int &spread[])
  {
//--- Выйти, если не дошли до начальной даты
   if(time[i]<first_date)
      return;
//---
   high_bid_buffer[i] =high[i];
   low_ask_buffer[i]  =low[i]+(spread[i]*_Point);
  }

Функция FillIndicatorBuffers() предназначена для определения экстремумов индикатора ZigZag. Расчёты производятся только от указанной даты в зависимости от количества баров, указанных во внешнем параметре MinImpulseSize. В зависимости от того, какое направление сегмента было определено на предыдущем вызове функции, программа заходит в соответствующий блок кода.

Для определения направления проверяются следующие условия:

  • Текущее направление сегмента вверх
    • Текущий максимальный Bid превышает последний максимум:
      • Если это условие исполняется, то (1) обнуляем предыдущий максимум, (2) запоминаем текущий индекс массива данных  и (3) присваиваем текущее значение максимального Bid текущим элементам индикаторных буферов.
      • Если же это условие не исполняется, то значит направление сегмента изменилось и нужно проверить условия на формирование нижнего экстремума:
        • Текущий минимальный Ask меньше последнего максимума
        • Расстояние между текущим минимальным Ask и последним максимумом ZigZag превышает указанный порог (MinImpulseSize).
          • Если эти условия выполняются, то (1) запоминаем текущий индекс массива данных, (2) сохраняем в переменной новое направление сегмента (вниз) и (3) присваиваем текущее значение минимального Ask текущим элементам индикаторных буферов.
  • Текущее направление сегмента вниз
    • Текущий минимальный Ask ниже последнего минимума:
      • Если это условие исполняется, то (1) обнуляем предыдущий минимум, (2) запоминаем текущий индекс массива данных  и (3) присваиваем текущее значение минимального Ask текущим элементам индикаторных буферов.
      • Если же это условие не исполняется, то значит направление сегмента изменилось и нужно проверить условия на формирование верхнего экстремума:
        • Текущий максимальный Bid больше последнего минимума
        • Расстояние между текущим максимальным Bid и последним минимумом ZigZag превышает указанный порог (MinImpulseSize).
          • Если эти условия выполняются, то (1) запоминаем текущий индекс массива данных, (2) сохраняем в переменной новое направление сегмента (вверх) и (3) присваиваем текущее значение максимального Bid текущим элементам индикаторных буферов.

Ниже можно подробнее изучить код функции FillIndicatorBuffers():

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы ZZ                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   if(time[i]<first_date)
      return;
//--- Если направление ZZ вверх
   if(direction_zz>0)
     {
      //--- Если новый максимум
      if(high_bid_buffer[i]>=max_high_bid)
        {
         zz_H_buffer[last_zz_max] =0;
         last_zz_max              =i;
         max_high_bid             =high_bid_buffer[i];
         zz_H_buffer[i]           =high_bid_buffer[i];
         total_zz_h_buffer[i]     =high_bid_buffer[i];
        }
      //--- Если направление изменилось (вниз)
      else
        {
         if(low_ask_buffer[i]<max_high_bid && 
            fabs(low_ask_buffer[i]-zz_H_buffer[last_zz_max])>MinImpulseSize*_Point)
           {
            last_zz_min          =i;
            direction_zz         =-1;
            min_low_ask          =low_ask_buffer[i];
            zz_L_buffer[i]       =low_ask_buffer[i];
            total_zz_l_buffer[i] =low_ask_buffer[i];
           }
        }
     }
//--- Если направление ZZ вниз
   else
     {
      //--- Если новый минимум
      if(low_ask_buffer[i]<=min_low_ask)
        {
         zz_L_buffer[last_zz_min] =0;
         last_zz_min              =i;
         min_low_ask              =low_ask_buffer[i];
         zz_L_buffer[i]           =low_ask_buffer[i];
         total_zz_l_buffer[i]     =low_ask_buffer[i];
        }
      //--- Если направление изменилось (вверх)
      else
        {
         if(high_bid_buffer[i]>min_low_ask && 
            fabs(high_bid_buffer[i]-zz_L_buffer[last_zz_min])>MinImpulseSize*_Point)
           {
            last_zz_max          =i;
            direction_zz         =1;
            max_high_bid         =high_bid_buffer[i];
            zz_H_buffer[i]       =high_bid_buffer[i];
            total_zz_h_buffer[i] =high_bid_buffer[i];
           }
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],
                const double &open[],const double &high[],const double &low[],const double &close[],
                const long &tick_volume[],const long &volume[],const int &spread[])
  {
//--- Предотвращение расчёта на каждом тике
   if(prev_calculated==rates_total)
      return(rates_total);
//--- Если это первый расчёт
   if(prev_calculated==0)
     {
      //--- Обнулим индикаторные буферы
      ZeroIndicatorBuffers();
      //--- Обнуление переменных
      ZeroIndicatorData();
      //--- Проверяет количество доступных данных
      if(!CheckDataAvailable())
         return(0);
      //--- Если данных для копирования указано больше, чем есть, будем использовать столько, сколько есть
      DetermineNumberData();
      //--- Определим, с какого бара начинать отрисовку для каждого символа
      DetermineBeginForCalculate(rates_total);
     }
   else
     {
      //--- Считаем только последнее значение
      start=prev_calculated-1;
     }
//--- Заполним индикаторные буферы High Bid и Low Ask
   for(int i=start; i<rates_total; i++)
      FillAskBidBuffers(i,time,high,low,spread);
//--- Заполним индикаторные буферы данными
   for(int i=start; i<rates_total-1; i++)
      FillIndicatorBuffers(i,time);
//--- Вернём размер массива данных
   return(rates_total);
  }

Ниже показано, как индикатор выглядит на дневном графике EURUSD:

 Рис. 1 – Модифицированный индикатор ZigZag на графике EURUSD с таймфреймом D1.

Рис. 1 – Модифицированный индикатор ZigZag на графике EURUSD с таймфреймом D1.

На следующем скриншоте индикатор показан на графике EURMXN с таймфреймом M5. Здесь показан участок графика, когда спред сильно расширяется в ночное время. Видно, что индикатор корректно рассчитывается с учётом спреда.

 Рис. 2 - Модифицированный индикатор ZigZag на графике EURMXN с таймфреймом M5.

Рис. 2 - Модифицированный индикатор ZigZag на графике EURMXN с таймфреймом M5.

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


Класс для получения данных индикатора ZigZag

Цена движется хаотично и непредсказуемо. Боковые движения, когда цена часто меняет своё направление, могут резко сменится долгими однонаправленными безоткатными трендами. Нужно постоянно мониторить текущее состояние, но нужны инструменты, которые помогут правильно интерпретировать характер поведения цены. Для этих целей был написан класс кода CZigZagModule, в котором есть все необходимые методы для работы с данными индикатора ZigZag. Далее подробнее рассмотрим, как устроен этот класс.

Так как работать можно будет сразу с несколькими экземплярами класса, например, с данными ZigZag’а с разных таймфреймов, то может понадобится визуализировать полученные сегменты с помощью трендовых линий разных цветов. Поэтому к файлу с классом CZigZagModule подключаем файл ChartObjectsLines.mqh из стандартной библиотеки. Из этого файла нам понадобится класс CChartObjectTrend для работы с трендовыми линиями. Цвет трендовым линиям можно задать публичным методом CZigZagModule::LinesColor(). По умолчанию установлен серый цвет (clrGray).

//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| Класс для получения данных индикатора ZigZag                     |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- Линии сегментов
   CChartObjectTrend m_trend_lines[];

   //--- Цвет линий сегментов
   color             m_lines_color;
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);
   //---
public:
   //--- Цвет линий
   void              LinesColor(const color clr) { m_lines_color=clr; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_lines_color(clrGray)
  {
// ...
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }

Перед тем как получать данные индикатора ZigZag, нужно установить, какое количество экстремумов нужно для работы. Для этого нужно вызвать метод CZigZagModule::CopyExtremums(). Для хранения (1) цен экстремумов, (2) индексов баров экстремумов и (3) их времени баров, а также (4) количество сегментов для построения трендовых линий на графике объявлены отдельные динамические массивы. Их размеры устанавливаются в этом же методе.

Количество сегментов рассчитывается автоматически от количества указанных экстремумов. Например, если передать в метод CZigZagModule::CopyExtremums() значение 1, то будут получены данные одного максимума и одного минимума. В таком случае это всего один сегмент индикатора ZigZag. Если же передать значение больше 1, то количество сегментов всегда будет как количество копируемых экстремумов умноженное на 2 и минус 1. То есть количество сегментов будет всегда нечётным:

  • По одному экстремуму – 1 сегмент
  • По два экстремума – 3 сегмента
  • По три экстремума – 5 сегментов и т.д.
class CZigZagModule
  {
protected:
   int               m_copy_extremums;    // Количество запоминаемых минимумов/максимумов
   int               m_segments_total;    // Количество сегментов
   //--- Цены экстремумов
   double            m_zz_low[];
   double            m_zz_high[];
   //--- Номера баров экстремумов
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- Время баров экстремумов
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //---
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1)
  {
   CopyExtremums(m_copy_extremums);
  }

//+------------------------------------------------------------------+
//| Количество экстремумов для работы                                |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }

Перед тем как начать работать с данными индикатора ZigZag, нужно поместить их в рассмотренные выше массивы класса, чтобы этим было удобно пользоваться. Понадобятся вспомогательные поля, которые будут использоваться как счётчики экстремумов. 

Для получения данных предназначен метод CZigZagModule::GetZigZagData(). В него нужно передавать исходные массивы данных индикатора ZigZag, а также массив времени. Эти исходные данные можно получить с помощью функций CopyBuffer() и CopyTime(). Перед тем как получить из исходных данных нужные значения, обязательно обнуляются все поля и массивы. Затем, в главном цикле метода получаем указанное количество (1) цен экстремумов, (2) индексов баров экстремумов и (3) время экстремумов. 

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

class CZigZagModule
  {
protected:
   int               m_direction;         // Направление
   int               m_counter_lows;      // Счётчик минимумов
   int               m_counter_highs;     // Счётчик максимумов
   //---
public:
   //--- Получение данных
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   //--- Обнуление структуры
   void              ZeroZigZagData(void);
  };
//+------------------------------------------------------------------+
//| Получает данные zig zag                                          |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- Обнуление переменных ZZ
   ZeroZigZagData();
//--- Пройдёмся в цикле по скопированным значениям ZZ
   for(int i=0; i<total; i++)
     {
      //--- Если уже получили нужное количество максимумов и минимумов ZZ, выйдем из цикла
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- Контроль выхода за пределы массива
      if(i>=h_total || i>=l_total)
         break;
      //--- Заполняем массив максимумов пока не скопируем нужное количество
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //---
         m_counter_highs++;
        }
      //--- Заполняем массив минимумов пока не скопируем нужное количество
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //---
         m_counter_lows++;
        }
     }
//--- Определим направление движения цены
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])? 1 : -1;
  }

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

class CZigZagModule
  {
public:
   //--- Цена экстремумов по указанному индексу
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- Номер бара экстремумов по указанному индексу
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- Время бара экстремумов по указанному индексу
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);
  };
//+------------------------------------------------------------------+
//| Значение минимума по указанному индексу                          |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }

Если необходимо получить размер сегмента, нужно вызвать метод CZigZagModule::SegmentSize(), указав в качестве единственного параметра индекс сегмента. Здесь в зависимости от того, является ли указанный индекс чётным или нечётным числом, соответствующим образом определяются индексы экстремумов, на которых вычисляется размер сегмента. Всё дело в том, что если число индекса чётное, то индексы экстремумов совпадают и их не нужно вычислять в зависимости от направления сегмента.

class CZigZagModule
  {
public:
   //--- Размер сегмента по указанному индексу
   double            SegmentSize(const int index);
  };
//+------------------------------------------------------------------+
//| Возвращает размер сегмента по индексу                            |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- Если чётное число
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- Если нечётное число
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }

Для получения суммы всех сегментов предназначен метод CZigZagModule::SegmentsSum(). Здесь всё просто, так как проходя в цикле по всем сегментам, вызывается рассмотренный выше метод CZigZagModule::SegmentSize().

class CZigZagModule
  {
public:
   //--- Сумма всех сегментов
   double            SegmentsSum(void);
  };
//+------------------------------------------------------------------+
//| Сумма размеров всех сегментов                                    |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }

Также может понадобится получить сумму всех сегментов направленных только вверх или только вниз. Ниже для примера показан код для сегментов направленных вверх. Здесь всё зависит от направления текущего сегмента. Если он направлен вверх, то для вычислений используются текущие индексы в цикле. Если же текущее направление вниз, то вычисления нужно начинать с первого индекса со смещением на один элемент назад для максимумов. Для случая, когда нужно получить сумму всех сегментов направленных вниз, используется практически такой же метод, но отличие только в том, что если текущее направление вверх, то смещение делается для минимумов.

class CZigZagModule
  {
public:
   //--- Сумма сегментов направленных (1) вверх и (2) вниз
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);
  };
//+------------------------------------------------------------------+
//| Возвращает размер всех сегментов направленных вверх              |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }

Может оказаться полезным иметь возможность получать процентное соотношение сумм однонаправленных сегментов относительно суммы всех сегментов в наборе. Для этих целей используйте методы CZigZagModule::PercentSumSegmentsUp() и CZigZagModule::PercentSumSegmentsDown(). С помощью таких методов открывается возможность получить процентную разницу этих соотношений — метод CZigZagModule::PercentSumSegmentsDifference(), что в свою очередь может сказать, в какую сторону сейчас смещается цена (тренд). Если же разница небольшая, то это будет означать, что цена равномерно колеблется в обе стороны (флэт). 

class CZigZagModule
  {
public:
   //--- Процентное соотношение сумм сегментов относительно общей суммы всех сегментов в наборе
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- Разница между суммами сегментов
   double            PercentSumSegmentsDifference(void);
  };
//+------------------------------------------------------------------+
//| Возвращает процент суммы всех сегментов направленных вверх       |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| Возвращает процент суммы всех сегментов направленных вниз        |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| Возвращает разницу в процентах суммы всех сегментов              |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }

Для определения характера поведения цены понадобятся методы для получения длительности как отдельных сегментов, так и всего полученного набора. Метод CZigZagModule::SegmentBars() предназначен для получения количества баров в указанном сегменте. Логика кода этого метода такая же, как и у ранее рассмотренного для получения размера сегмента — метод CZigZagModule::SegmentSize(), поэтому не будем приводить здесь его код. 

Для получения общего количество баров в полученном наборе данных используйте метод CZigZagModule::SegmentsTotalBars(). Здесь определяется начальный и конечный индексы баров в наборе и возвращается разница. По такому же принципу работает метод CZigZagModule::SegmentsTotalSeconds(), только здесь возвращается количество секунд в наборе. 

class CZigZagModule
  {
public:
   //--- Количество баров в указанном сегменте
   int               SegmentBars(const int index);
   //--- (1) Количество баров и (2) секунд в наборе сегментов
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);
  };
//+------------------------------------------------------------------+
//| Количество баров всех сегментов                                  |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])? m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])? m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| Количество секунд всех сегментов                                 |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])? m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])? m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }

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

class CZigZagModule
  {
public:
   //--- (1) Минимальное и (2) максимальное значение в наборе
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- Размер ценового диапазона
   double            PriceRange(void);
  };
//+------------------------------------------------------------------+
//| Минимальное значение в наборе                                    |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Максимальное значение в наборе                                   |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| Ценовой диапазон                                                 |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }

Ещё один набор методов класса CZigZagModule позволяет получить такие значения, как:

  • SmallestSegment() – возвращает наименьший размер сегмента в полученных данных.
  • LargestSegment() – возвращает наибольший размер сегмента в полученных данных. 
  • LeastNumberOfSegmentBars() – возвращает наименьшее количество баров в сегменте в полученных данных.
  • MostNumberOfSegmentBars() – возвращает наибольшее количество баров в сегменте в полученных данных.

В классе уже есть методы для получения размеров сегментов и количества баров сегментов по указанному индексу. Поэтому код методов из списка выше будет легко понять. У всех отличие только в вызываемых в них методах, поэтому приведём код только двух — CZigZagModule::SmallestSegmen() и CZigZagModule::MostNumberOfSegmentBars().

class CZigZagModule
  {
public:
   //--- Наименьший сегмент в наборе
   double            SmallestSegment(void);
   //--- Наибольший сегмент в наборе
   double            LargestSegment(void);
   //--- Наименьшее количество баров сегмента в наборе
   int               LeastNumberOfSegmentBars(void);
   //--- Наибольшее количество баров сегмента в наборе 
   int               MostNumberOfSegmentBars(void);
  };
//+------------------------------------------------------------------+
//| Наименьший сегмент в наборе                                      |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)? size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| Наибольшее количество баров сегмента в наборе                    |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)? bars : max_bars;
     }
//---
   return(max_bars);
  }

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

class CZigZagModule
  {
public:
   //--- Отклонение в процентах
   double            PercentDeviation(const int index);
  };
//+------------------------------------------------------------------+
//| Отклонение в процентах                                           |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }

Для рассмотрим как визуализировать полученные данные, а также как использовать класс CZigZagModule в своих проектах.


Визуализация полученного набора данных

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

Публичные методы CZigZagModule::ShowSegments() и CZigZagModule::DeleteSegments() позволяют отобразить и удалить графические объекты.

class CZigZagModule
  {
public:
   //--- (1) Показ и (2) удаление объектов
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);
   //---
private:
   //--- Создание объектов
   void              CreateSegment(const int segment_index,const string suffix="");
  };
//+------------------------------------------------------------------+
//| Показывает сегменты ZZ на графике                                |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| Удаляет сегменты                                                 |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }

Для быстрого получения базовой информации по полученным данным индикатора в класс добавлены методы для вывода комментариев на график. Для примера ниже приведён код метода краткого вывода информации с рассчитанными данными индикатора.

 class CZigZagModule
  {
public:
   //--- Комментарий на графике
   void              CommentZigZagData();
   void              CommentShortZigZagData();
  };
//+------------------------------------------------------------------+
//| Вывод данных ZigZag в комментарии на графике                     |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }

Далее создадим приложение, в котором будем получать и визуализировать полученные данные.


Эксперт для теста полученных результатов

Напишем простого тестового эксперта для получения и визуализации данных индикатора ZigZag. Не будем делать дополнительные проверки, чтобы по максимуму упростить код. Для примера достаточно показать сам принцип получения данных, и как это работает. 

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

//+------------------------------------------------------------------+
//|                                                    TestZZ_01.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ZigZagModule.mqh>
CZigZagModule zz_current;

//--- Внешние параметры
input int CopyExtremum   =3;
input int MinImpulseSize =0;

//--- Массивы для исходных данных
double   l_zz[];
double   h_zz[];
datetime t_zz[];

//--- Хендл индикатора ZZ
int zz_handle_current=WRONG_VALUE;

В функции OnInit() (1) получаем хендл индикатора, (2) устанавливаем количество экстремумов для формирования итоговых данных и цвет линий сегментов из полученного набора, (3) массивам для исходных установим обратный порядок индексации.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Путь к индикатору ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Получаем хендл индикатора
   zz_handle_current=::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,true,true);
//--- Установим цвет для сегментов и количество экстремумов, которые нужно получить
   zz_current.LinesColor(clrRed);
   zz_current.CopyExtremums(CopyExtremum);
//--- Установим обратный порядок индексации (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

Теперь в функции OnTick() сначала получаем исходные данные индикатора по его хендлу и время открытия баров. Затем подготавливаем финальные данные вызовом метода CZigZagModule::GetZigZagData(). И наконец, визуализируем сегменты полученных данных индикатора ZigZag и выводим эту информацию в виде комментария на график.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- Получим исходные данные
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
//--- Получим итоговые данные
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
//--- Визуализируем сегменты на графике
   zz_current.ShowSegments();
//--- Покажем значения данных в комментарии на графике
   zz_current.CommentZigZagData();
  }

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

 Рис. 3 – Демонстрация в режиме визуализации (один ZigZag).

Рис. 3 – Демонстрация в режиме визуализации (один ZigZag).

Если нужно получать данные индикатора ZigZag одновременно с разных таймфреймов, то код тестового эксперта нужно немного дополнить. Рассмотрим пример, когда нужно получить данные с трёх таймфреймов. В этом случае нужно объявить три экземпляра класса CZigZagModule. Первый таймфрейм будет с текущего графика, на котором запущен эксперт, а два других для примера пусть будут M15 и H1.

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_current;
CZigZagModule zz_m15;
CZigZagModule zz_h1;

Для каждого индикатора своя переменная для получения хендла:

//--- Хендлы индикатора ZZ
int zz_handle_current =WRONG_VALUE;
int zz_handle_m15     =WRONG_VALUE;
int zz_handle_h1      =WRONG_VALUE;

Далее в функции OnInit() получаем хендлы для каждого индикатора отдельно и устанавливаем цвета и количество экстремумов:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Путь к индикатору ZZ
   string zz_path="Custom\\ZigZag\\ExactZZ_Plus.ex5";
//--- Получаем хендлы индикатора
   zz_handle_current =::iCustom(_Symbol,_Period,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_m15     =::iCustom(_Symbol,PERIOD_M15,zz_path,10000,MinImpulseSize,false,false);
   zz_handle_h1      =::iCustom(_Symbol,PERIOD_H1,zz_path,10000,MinImpulseSize,false,false);
//--- Установим цвет для сегментов
   zz_current.LinesColor(clrRed);
   zz_m15.LinesColor(clrCornflowerBlue);
   zz_h1.LinesColor(clrGreen);
//--- Установим количество экстремумов, которые нужно получить
   zz_current.CopyExtremums(CopyExtremum);
   zz_m15.CopyExtremums(CopyExtremum);
   zz_h1.CopyExtremums(CopyExtremum);
//--- Установим обратный порядок индексации (... 3 2 1 0)
   ::ArraySetAsSeries(l_zz,true);
   ::ArraySetAsSeries(h_zz,true);
   ::ArraySetAsSeries(t_zz,true);
   return(INIT_SUCCEEDED);
  }

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   ::CopyTime(_Symbol,_Period,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_current,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_current,3,0,copy_total,l_zz);
   zz_current.GetZigZagData(h_zz,l_zz,t_zz);
   zz_current.ShowSegments("_current");
   zz_current.CommentShortZigZagData();
//---
   ::CopyTime(_Symbol,PERIOD_M15,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_m15,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_m15,3,0,copy_total,l_zz);
   zz_m15.GetZigZagData(h_zz,l_zz,t_zz);
   zz_m15.ShowSegments("_m15");
//---
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,2,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,3,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
  }

Ниже показано как это выглядит:

 Рис. 4 – Демонстрация в режиме визуализации (три ZigZag’а).

Рис. 4 – Демонстрация в режиме визуализации (три ZigZag’а).

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


Продолжаем развивать класс CZigZagModule

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

До этого мы получали данные индикатора ZigZag, начиная от самого последнего сформированного бара и уходя вглубь исторических данных. Но может понадобиться также получить данные в определённом временном диапазон. Для этих целей напишем ещё один метод CZigZagModule::GetZigZagData(), но уже с другим набором параметров. В этой версии исходные данные будем получать внутри метода, поэтому в качестве параметров понадобятся хендл индикатора, символ, таймфрейм и временной диапазон (начальная и конечная даты).

Далее нужно посчитать отдельно количество минимумов и максимумов в полученных данных. Количество экстремумов для дальнейшей работы тогда будет определяться минимальным количеством между этими счётчиками

В самом конце здесь вызывается одноимённый метод CZigZagModule::GetZigZagData() с другим набором параметров, который рассматривали ранее, когда для получения финальных данных нужно передать в качестве параметров массивы с исходными данными.

class CZigZagModule
  {
private:
   //--- Массивы для получения исходных данных
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
   //--- Получение данных
   void              GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time);
  };
//+------------------------------------------------------------------+
//| Получает данные ZZ из переданного хендла                         |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,const string symbol,const ENUM_TIMEFRAMES period,const datetime start_time,const datetime stop_time)
  {
//--- Получим исходные данные
   ::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   ::CopyBuffer(handle,2,start_time,stop_time,m_zz_highs_temp);
   ::CopyBuffer(handle,3,start_time,stop_time,m_zz_lows_temp);
//--- Счётчики
   int lows_counter  =0;
   int highs_counter =0;
//--- Считаем максимумы
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- Считаем минимумы
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- Получим количество экстремумов
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- Пройдёмся в цикле по скопированным значениям ZZ
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }

Для получения времени наименьшего и наибольшего экстремумов в полученном наборе данных используйте методы CZigZagModule::SmallestMinimumTime() и CZigZagModule::LargestMaximumTime(). 

class CZigZagModule
  {
public:
   //--- Время наименьшего минимума
   datetime          SmallestMinimumTime(void);
   //--- Время наибольшего максимума
   datetime          LargestMaximumTime(void);
  };
//+------------------------------------------------------------------+
//| Время наименьшего минимума                                       |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Время наибольшего максимума                                      |
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }

Также расширим список методов для работы с сегментами ZigZag’а. Может быть удобным получить сразу несколько значений в переменные переданные по ссылкам. В классе есть три подобных метода:

  • SegmentBars() — возвращает начальный и конечный индексы бара указанного сегмента.
  • SegmentPrices() — возвращает начальную и конечную цены указанного сегмента.
  • SegmentTimes() — возвращает начальное и конечное время указанного сегмента.

Похожая схема есть уже в других ранее рассмотренных методах, поэтому в качестве примера здесь приведён код только одного из вышеперечисленных. 

class CZigZagModule
  {
public:
   //--- Возвращает начальный и конечный бар указанного сегмента
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- Возвращает начальную и конечную цены указанного сегмента
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- Возвращает начальное и конечное время указанного сегмента
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Возвращает начальный и конечный бар указанного сегмента          |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- Если чётное число
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)? m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)? m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- Если нечётное число
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }

Допустим, у нас открыт пятиминутный график (M5) и мы получаем данные с часового таймфрейма (H1). Мы ищем паттерны с часового таймфрейма и нам нужно определить характер поведения цены того или иного сегмента ZigZag с часового таймфрейма на текущем таймфрейме. Другими словами, мы хотим знать, как формировался указанный сегмент на младшем таймфрейме.

Как было показано в предыдущем разделе, экстремумы сегментов со старших таймфреймов отображаются на текущем таймфрейме по времени открытия старших таймфреймов. У нас уже есть метод CZigZagModule::SegmentTimes(), который возвращает время начала и окончания указанного сегмента. Если воспользоваться этим временным диапазоном для получения данных ZigZag с младшего таймфрейма, то мы в большинстве случаев будем получать много лишних сегментов, которые на самом деле относятся к другим сегментам старшего таймфрейма. Для случаев, когда нужна большая точность, напишем ещё один метод CZigZagModule::SegmentTimes(), но с другим набором параметров. Кроме этого, понадобятся несколько приватных вспомогательных методов, для получения (1) исходных данных, а также (2) индексов  минимального и максимального значения в переданных массивах. 

class CZigZagModule
  {
private:
   //--- Копирует исходные данные в переданные массивы
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- Возвращает индекс (1) минимального и (2) максимального значения из переданного массива
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);
  };
//+------------------------------------------------------------------+
//| Копирует исходные данные в переданные массивы                    |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс максимального значения из переданного массива  |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| Возвращает индекс минимального значения из переданного массива   |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }

Ещё один метод CZigZagModule::SegmentTimes() предназначен для получения начального и конечного времени указанного сегмента с учётом младшего таймфрейма. Здесь требуются некоторые пояснения. В метод передаются следующие параметры:

  • handle — хендл индикатора ZigZag с младшего таймфрейма.
  • highs_buffer_index — индекс индикаторного буфера, где содержатся максимальные экстремумы.
  • lows_buffer_index — индекс индикаторного буфера, где содержатся минимальные экстремумы.
  • symbol — символ младшего таймфрейма.
  • period — период старшего таймфрейма.
  • in_period — период младшего таймфрейма.
  • index — индекс сегмента старшего таймфрейма.

Возвращаемые значения параметров по ссылке:

  • start_time — время начала сегмента с учётом младшего таймфрейма.
  • stop_time — время окончания сегмента с учётом младшего таймфрейма.

Сначала нужно получить время открытия первого и последнего баров указанного сегмента. Для этого нужно вызвать первый метод CZigZagModule::SegmentTimes(), который рассматривали ранее. 

Далее с помощью метода CZigZagModule::CopyData() получаем данные экстремумов и времени баров. В зависимости от того, какое направление имеет сегмент, получаем данные в определённой последовательности. Если направление вверх, то сначала получаем данные минимумов ZigZag’а младшего таймфрейма, которые входят в состав первого бара сегмента на старшем таймфрейме. А затем получаем данные максимумов ZigZag’а младшего таймфрейма, которые входят в состав последнего бара сегмента на старшем таймфрейме. В случае, когда направление сегмента вниз, то последовательность действий обратная. Сначала нужно получить данные максимумов, а затем данные минимумов. 

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

class CZigZagModule
  {
public:
   //--- Возвращает начальное и конечное время указанного сегмента с учётом младшего таймфрейма
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);
  };
//+------------------------------------------------------------------+
//| Возвращает начальное и конечное время указанного сегмента        |
//| с учётом младшего таймфрейма                                     |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- Получаем время без учёта текущего таймфрейма
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- Получаем исходные данные, если направление вверх
   if(SegmentDirection(index)>0)
     {
      //--- Данные первого бара старшего таймфрейма
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- Данные последнего бара старшего таймфрейма
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- Получаем исходные данные, если направление вниз
   else
     {
      //--- Данные первого бара старшего таймфрейма
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- Данные последнего бара старшего таймфрейма
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- Ищем индекс максимального значения
   int max_index =GetMaxValueIndex(zz_highs);
//--- Ищем индекс минимального значения
   int min_index =GetMinValueIndex(zz_lows);
//--- Получаем время начала и окончания сегмента
   start_time =(SegmentDirection(index)>0)? zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)? zz_highs_time[max_index] : zz_lows_time[min_index];
//--- Успешно
   return(true);
  }

Теперь давайте напишем эксперта для тестов. Текущий таймфрейм будет M5. На этот таймфрейм загрузим эксперта в тестере в режиме визуализации. Будем получать данные с часового таймфрейма (H1) и с текущего. Код эксперта похож на тот, который рассматривали ранее, поэтому приведём здесь только содержание функции OnTick().

Сначала получаем данные для часового таймфрейма первым способом и показываем сегменты на графике для наглядности. Далее для примера получим данные ZigZag’а с текущего таймфрейма (M5) на временном промежутке третьего (индекс 2) сегмента ZigZag’а с часового таймфрейма. Для этого сначала получим начало и окончание сегмента с учётом текущего таймфрейма.

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Первый способ получения данных
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   int      segment_index =2;
   int      start_bar     =0;
   int      stop_bar      =0;
   double   start_price   =0.0;
   double   stop_price    =0.0;
   datetime start_time    =NULL;
   datetime stop_time     =NULL;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//---
   zz_h1.SegmentBars(segment_index,start_bar,stop_bar);
   zz_h1.SegmentPrices(segment_index,start_price,stop_price);
   zz_h1.SegmentTimes(segment_index,start_time,stop_time);
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,segment_index,start_time_in,stop_time_in);
   
//--- Второй способ получения данных
   zz_current.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current.ShowSegments("_current");
   
//--- Вывод данных в комментариях на графике
   string comment="Current direction : "+string(zz_h1.Direction())+"\n"+
                  "\n---\n"+
                  "Direction > segment["+string(segment_index)+"]: "+string(zz_h1.SegmentDirection(segment_index))+
                  "\n---\n"+
                  "Start bar > segment["+string(segment_index)+"]: "+string(start_bar)+"\n"+
                  "Stop bar > segment["+string(segment_index)+"]: "+string(stop_bar)+
                  "\n---\n"+
                  "Start price > segment["+string(segment_index)+"]: "+::DoubleToString(start_price,_Digits)+"\n"+
                  "Stop price > segment["+string(segment_index)+"]: "+::DoubleToString(stop_price,_Digits)+
                  "\n---\n"+
                  "Start time > segment["+string(segment_index)+"]: "+::TimeToString(start_time,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time > segment["+string(segment_index)+"]: "+::TimeToString(stop_time,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Start time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(start_time_in,TIME_DATE|TIME_MINUTES)+"\n"+
                  "Stop time (in tf) > segment["+string(segment_index)+"]: "+::TimeToString(stop_time_in,TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "Extremums copy: "+string(zz_current.CopyExtremums())+"\n"+
                  "SmallestMinimumTime(): "+string(zz_current.SmallestMinimumTime())+"\n"+
                  "LargestMaximumTime(): "+string(zz_current.LargestMaximumTime());
//---
   ::Comment(comment);
  }

Вот так это выглядит:

 Рис. 5 – Демонстрация получения данных внутри указанного сегмента.

Рис. 5 – Демонстрация получения данных внутри указанного сегмента.

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

В начале файла нужно теперь объявить четыре экземпляра класса CZigZagModule. Один для старшего таймфрейма (H1) и три для текущего таймфрейма. В данном случае мы проводим тесты на пятиминутном графике. 

CZigZagModule zz_h1;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

Для лучшей наглядности сегменты младшего таймфрейма внутри сегментов старшего таймфрейма будем отображать разными цветами:

//--- Установим цвет для сегментов
   zz_current0.LinesColor(clrRed);
   zz_current1.LinesColor(clrLimeGreen);
   zz_current2.LinesColor(clrMediumPurple);
   zz_h1.LinesColor(clrCornflowerBlue);

В функции OnTick() получаем сначала данные часового таймфрейма, а затем поочерёдно получаем данные с младшего таймфрейма для первого, второго и третьего сегментов. В комментарии на графике выведем информацию по каждой группе полученных сегментов младшего таймфрейма и отдельно для старшего. В данном случае это разница процентных соотношений сумм сегментов. Этот показатель можно получить с помощью метода CZigZagModule::PercentSumSegmentsDifference(). 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   int copy_total=1000;
   int h_buff=2,l_buff=3;
//--- Первый способ получения данных
   ::CopyTime(_Symbol,PERIOD_H1,0,copy_total,t_zz);
   ::CopyBuffer(zz_handle_h1,h_buff,0,copy_total,h_zz);
   ::CopyBuffer(zz_handle_h1,l_buff,0,copy_total,l_zz);
   zz_h1.GetZigZagData(h_zz,l_zz,t_zz);
   zz_h1.ShowSegments("_h1");
//---
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Данные первого сегмента
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current0.ShowSegments("_current0");
//--- Данные второго сегмента
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current1.ShowSegments("_current1");
//--- Данные третьего сегмента
   zz_h1.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,PERIOD_H1,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
   zz_current2.ShowSegments("_current2");
//--- Вывод данных в комментариях на графике
   string comment="H1: "+::DoubleToString(zz_h1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[0]: "+::DoubleToString(zz_current0.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[1]: "+::DoubleToString(zz_current1.PercentSumSegmentsDifference(),2)+"\n"+
                  "segment[2]: "+::DoubleToString(zz_current2.PercentSumSegmentsDifference(),2);
//---
   ::Comment(comment);
  }

Вот как это выглядит на графике:

 Рис. 6 – Демонстрация получения данных внутри трёх указанных сегментов.

Рис. 6 – Демонстрация получения данных внутри трёх указанных сегментов.

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

  • Цена, время и номер бара каждого отдельного экстремума.
  • Размер каждого отдельного сегмента.
  • Длительность каждого сегмента в барах.
  • Размер ценового диапазона всего набора полученных сегментов.
  • Длительность формирования всего набора сегментов (в барах).
  • Суммы однонаправленных сегментов.
  • Соотношения сумм разнонаправленных сегментов и т.д. 

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


Заключение

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

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

Наименование файлаКомментарий
MQL5\Indicators\Custom\ZigZag\ExactZZ_Plus.mq5Модифицированная версия индикатора ZigZag
MQL5\Experts\ZigZag\TestZZ_01.mq5Эксперт для теста одного набора данных
MQL5\Experts\ZigZag\TestZZ_02.mq5Эксперт для теста трёх наборов данных с разных таймфреймов
MQL5\Experts\ZigZag\TestZZ_03.mq5Эксперт для теста получения данных внутри указанного сегмента старшего таймфрейма
MQL5\Experts\ZigZag\TestZZ_04.mq5Эксперт для теста получения данных внутри трёх указанных сегментов старшего таймфрейма


Прикрепленные файлы |
MQL5.zip (18.86 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (85)
Sergey Voytsekhovsky
Sergey Voytsekhovsky | 3 фев 2019 в 22:12
Алексей Тарабанов:
Может, кто-нибудь объяснит человеку, что такое перегрузка? Я не программирую на С. 

Буду крайне благодарен, если еще и ссылку подкинете, где почитать в более развернутом виде чем в справке  к МТ5, то будет просто великолепно.

Алексей Тарабанов
Алексей Тарабанов | 3 фев 2019 в 22:18
https://ru.wikipedia.org/wiki/C_Sharp
Sergey Voytsekhovsky
Sergey Voytsekhovsky | 3 фев 2019 в 22:38
Алексей Тарабанов:
https://ru.wikipedia.org/wiki/C_Sharp

Спасибо.

Sergey Voytsekhovsky
Sergey Voytsekhovsky | 4 фев 2019 в 08:52
Большое спасибо автору за терпеливое отношение и понимание. Все версии советника установились, компилировались и работают.
Sergey Voytsekhovsky
Sergey Voytsekhovsky | 4 фев 2019 в 08:56
Век живи, век учись.... Оказывается я неверно размещал файлы из архива в своем терминале.
Мартингейл как основа долгосрочной торговой стратегии Мартингейл как основа долгосрочной торговой стратегии

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

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

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

Утилита для отбора и навигации на MQL5 и MQL4: добавляем автоматичекий поиск паттернов с показом найденных символов Утилита для отбора и навигации на MQL5 и MQL4: добавляем автоматичекий поиск паттернов с показом найденных символов

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

Исследование методов свечного анализа (Часть I): Проверка существующих паттернов Исследование методов свечного анализа (Часть I): Проверка существующих паттернов

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