ZigZag всему голова (Часть II): Примеры получения, обработки и отображения данных

Anatoli Kazharski | 17 февраля, 2019

Содержание


Введение

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

В качестве дополнения в этой статье будет представлена новая версия библиотеки для создания графических интерфейсов EasyAndFast

Основные темы статьи:


Индикаторы для определения характера поведения цены

Рассмотрим три индикатора для определения характера поведения цены. 

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


Индикатор FrequencyChangeZZ

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

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы                                    |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Получим данные ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Сохраним количество баров в наборе сегментов в индикаторный буфер
         segments_bars_total_buffer[i]=zz.SegmentsTotalBars();
         break;
        }
     }
  }

Во внешних параметрах индикатора укажем, что:

(1) рассчитать значения нужно на всех имеющихся данных на графике,
(2) минимальное отклонение для формирования нового сегмента индикатора ZigZag и
(3) количество экстремумов для получения финальных данных.

Такие же параметры будут у всех представленных в этой статье индикаторов.

 Рис. 1 – Внешние параметры индикатора.

Рис. 1 – Внешние параметры индикатора.

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

 Рис. 2 – Демонстрация работы индикатора FrequencyChangeZZ.

Рис. 2 – Демонстрация работы индикатора FrequencyChangeZZ.


Индикатор SumSegmentsZZ

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

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы                                    |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Получим данные ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Получим данные по сегментам
         segments_up_total_buffer[i] =zz.SumSegmentsUp();
         segments_dw_total_buffer[i] =zz.SumSegmentsDown();
         segments_average_buffer[i]  =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2;
         break;
        }
     }
  }

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

 Рис. 3 – Демонстрация работы индикатора SumSegmentsZZ.

Рис. 3 – Демонстрация работы индикатора SumSegmentsZZ.


Индикатор PercentageSegmentsZZ

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

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы                                    |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const datetime &time[])
  {
   int copy_total=1000;
   for(int t=0; t<10; t++)
     {
      if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total &&
         CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total &&
         CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total)
        {
         //--- Получим данные ZZ
         zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
         //--- Получим данные по сегментам
         double sum_up =zz.SumSegmentsUp();
         double sum_dw =zz.SumSegmentsDown();
         double sum    =sum_up+sum_dw;
         //--- Процентное соотношение и разница
         if(sum>0)
           {
            segments_up_total_buffer[i]   =zz.PercentSumSegmentsUp();
            segments_dw_total_buffer[i]   =zz.PercentSumSegmentsDown();
            segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]);
            break;
           }
        }
     }
  }

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

 Рис. 4 – Демонстрация работы индикатора PercentageSegmentsZZ.

Рис. 4 – Демонстрация работы индикатора PercentageSegmentsZZ.


Индикатор MultiPercentageSegmentsZZ

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

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

//--- Количество буферов
#property indicator_buffers 4
#property indicator_plots   4
//--- Цвета цветовых буферов
#property indicator_color1 clrSilver
#property indicator_color2 clrRed
#property indicator_color3 clrLimeGreen
#property indicator_color4 clrMediumPurple

Объявляем четыре экземпляра класса CZigZagModule:

#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;

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

input             int NumberOfBars    =0;         // Number of bars to calculate ZZ
input             int MinImpulseSize  =0;         // Minimum points in a ray
input             int CopyExtremum    =5;         // Copy extremums
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Higher timeframe

Главная функция для заполнения индикаторных буферов реализована следующим образом. Сначала получаем исходные данные со старшего таймфрейма, который указываем во внешних параметрах. Затем получаем финальные данные и запоминаем значение показателя. Далее последовательно получаем данные по трём сегментам индикатора со старшего таймфрейма. После этого заполняем все индикаторные буферы. Для того чтобы индикатор корректно рассчитывался на истории и на последнем баре в реал-тайме/в тестере, понадобилось написать два отдельных блока кода. 

//+------------------------------------------------------------------+
//| Заполняет индикаторные буферы                                    |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(const int i,const int total,const datetime &time[])
  {
   int index=total-i-1;
   int copy_total=1000;
   int h_buff=2,l_buff=3;
   datetime start_time_in =NULL;
   datetime stop_time_in  =NULL;
//--- Получим исходные данные со старшего таймфрейма
   datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total);
   CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp);
   CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp);
   CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp);
//--- Получим финальные данные со старшего таймфрейма
   zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp);
   double htf_value=zz_higher_tf.PercentSumSegmentsDifference();
//--- Данные первого сегмента
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in);
   zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Данные второго сегмента
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in);
   zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- Данные третьего сегмента
   zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in);
   zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in);
//--- На последнем баре
   if(i<total-1)
     {
      buffer_zz_higher_tf[i] =htf_value;
      buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
      buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
      buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
     }
//--- На истории
   else
     {
      //--- Если новый бар старшего таймфрейма
      if(new_bar_time!=t_zz_buffer_temp[0])
        {
         new_bar_time=t_zz_buffer_temp[0];
         //---
         if(i>2)
           {
            int f=1,s=2;
            buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s];
            buffer_segment_0[i-f]    =buffer_segment_0[i-s];
            buffer_segment_1[i-f]    =buffer_segment_1[i-s];
            buffer_segment_2[i-f]    =buffer_segment_2[i-s];
           }
        }
      else
        {
         buffer_zz_higher_tf[i] =htf_value;
         buffer_segment_0[i]    =zz_current0.PercentSumSegmentsDifference();
         buffer_segment_1[i]    =zz_current1.PercentSumSegmentsDifference();
         buffer_segment_2[i]    =zz_current2.PercentSumSegmentsDifference();
        }
     }
  }

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

//--- Внешние параметры
input            uint CopyExtremum    =3;         // Copy extremums
input             int MinImpulseSize  =0;         // Min. impulse size
input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Higher timeframe

...

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {

...

//--- Путь к индикатору ZZ
   string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5";
   string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5";
//--- Получаем хендлы индикаторов
   zz_handle_current   =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false);
   zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false);
   zz_handle           =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe);

...

   return(INIT_SUCCEEDED);
  }

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

 Рис. 5 – Демонстрация работы индикатора MultiPercentageSegmentsZZ.

Рис. 5 – Демонстрация работы индикатора MultiPercentageSegmentsZZ.

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


Эксперт для сбора и отображения статистики

В качестве дополнения к этой статье публикуется новая версия библиотеки для создания графических интерфейсов EasyAndFast. Перечислим здесь только то, какие возможности появились у пользователей этой библиотеки:

 Рис. 6 – Пример объединения элементов в группы.

Рис. 6 – Пример объединения элементов в группы.

Скачать новую версию библиотеки Вы можете в базе кода

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

Определимся с элементами управления графического интерфейса:

Как говорилось ранее, для более быстрого и удобного способа создания графического интерфейса, теперь к пользовательскому классу в качестве базового класса нужно подключать класс CWndCreate. Полная связь тогда будет такой: CWndContainer -> CWndEvents -> CWndCreate -> CProgram. Наличие в этой схеме класса CWndCreate добавляет возможность создавать элементы графического интерфейса одной строкой без создания в пользовательском классе отдельных методов, как это было ранее. Класс содержит разные шаблоны практически для всех элементов библиотеки. Его можно дополнять новыми шаблонами при возникновении такой необходимости. 

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

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndCreate.mqh>
//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndCreate
  {
private:
   //--- Окно
   CWindow           m_window;
   //--- Статусная строка
   CStatusBar        m_status_bar;
   //--- Выпадающие календари
   CDropCalendar     m_from_date;
   CDropCalendar     m_to_date;
   //--- Кнопки
   CButton           m_request;
   //--- Поля ввода
   CTextEdit         m_filter;
   CTextEdit         m_level;
   //--- Комбо-боксы
   CComboBox         m_data_type;
   //--- Таблицы
   CTable            m_table;
   //--- Индикатор выполнения
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Создаёт графический интерфейс
   bool              CreateGUI(void);
   //---
private:
   //--- Таблицы
   bool              CreateTable(const int x_gap,const int y_gap);
  };

Для создания графического интерфейса с таким содержанием достаточно просто вызвать нужные методы класса CWndCreate, указав в качестве аргументов значения свойств, как это показано в листинге кода ниже. Чтобы выяснить, к какому свойству относится тот или иной параметр метода, нужно установить в него текстовый курсор и нажать сочетание клавиш Ctrl + Shift + Space:

 Рис. 7 – Просмотр параметров метода.

Рис. 7 – Просмотр параметров метода.

Если нужно сразу установить, какие-то дополнительные свойства, то сделать это можно как в примере с полем ввода фильтра валют. Здесь указывается, что чек-бокс по умолчанию будет включен сразу после создания элемента.

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс                                    |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Создание формы для элементов управления
   if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true))
      return(false);
//--- Статусная строка
   string text_items[1];
   text_items[0]="For Help, press F1";
   int width_items[]={0};
   if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items))
      return(false);
//--- Поле ввода для фильтра валют
   if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK"))
      return(false);
   else
      m_filter.IsPressed(true);
//--- Выпадающие календари
   if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01'))
      return(false);
   if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent()))
      return(false);
//--- Поле ввода для указания уровня
   if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30))
      return(false);
//--- Кнопка
   if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70))
      return(false);
//--- Таблица
   if(!CreateTable(2,75))
      return(false);
//--- Индикатор выполнения
   if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3))
      return(false);
//--- Завершение создания GUI
   CWndEvents::CompletedGUI();
   return(true);
  }

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

//+------------------------------------------------------------------+
//| Создаёт таблицу                                                  |
//+------------------------------------------------------------------+
bool CProgram::CreateTable(const int x_gap,const int y_gap)
  {
#define COLUMNS1_TOTAL 4
#define ROWS1_TOTAL    1
//--- Сохраним указатель на главный элемент
   m_table.MainPointer(m_window);
//--- Массив ширины столбцов
   int width[COLUMNS1_TOTAL];
   ::ArrayInitialize(width,50);
   width[0]=80;
//--- Массив отступа текста в столбцах по оси X
   int text_x_offset[COLUMNS1_TOTAL];
   ::ArrayInitialize(text_x_offset,7);
//--- Массив выравнивания текста в столбцах
   ENUM_ALIGN_MODE align[COLUMNS1_TOTAL];
   ::ArrayInitialize(align,ALIGN_CENTER);
   align[0]=ALIGN_LEFT;
//--- Свойства
   m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL);
   m_table.TextAlign(align);
   m_table.ColumnsWidth(width);
   m_table.TextXOffset(text_x_offset);
   m_table.ShowHeaders(true);
   m_table.IsSortMode(true);
   m_table.IsZebraFormatRows(clrWhiteSmoke);
   m_table.AutoXResizeMode(true);
   m_table.AutoYResizeMode(true);
   m_table.AutoXResizeRightOffset(2);
   m_table.AutoYResizeBottomOffset(24);
//--- Создадим элемент управления
   if(!m_table.CreateTable(x_gap,y_gap))
      return(false);
//--- Заголовки
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
//--- Добавим объект в общий массив групп объектов
   CWndContainer::AddToElementsArray(0,m_table);
   return(true);
  }

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

class CProgram : public CWndCreate
  {
private:
   //--- Проверка символа по фильтру
   bool              CheckFilterText(const string symbol_name);
  };
//+------------------------------------------------------------------+
//| Проверка символа по фильтру                                      |
//+------------------------------------------------------------------+
bool CProgram::CheckFilterText(const string symbol_name)
  {
   bool check=false;
//--- Если включен фильтр названий символов
   if(!m_filter.IsPressed())
      return(true);
//--- Если введён текст
   string text=m_filter.GetValue();
   if(text=="")
      return(true);
//--- Разбить на подстроки
   string elements[];
   ushort sep=::StringGetCharacter(",",0);
   ::StringSplit(text,sep,elements);
//--- Проверяем на совпадение
   int elements_total=::ArraySize(elements);
   for(int e=0; e<elements_total; e++)
     {
      //--- Удалить крайние пробелы
      ::StringTrimLeft(elements[e]);
      ::StringTrimRight(elements[e]);
      //--- Если нашли совпадение
      if(::StringFind(symbol_name,elements[e])>-1)
        {
         check=true;
         break;
        }
     }
//--- Результат
   return(check);
  }

В методе CProgram::GetSymbols() в цикле проходимся по всем имеющимся на сервере символам и собираем в массив те, которые соответствуют указанным критериям. В общем цикле все символы удаляются из окна обзор рынка, и затем в это окно добавляются только те, которые содержатся в массиве

class CProgram : public CWndCreate
  {
private:
   //--- Массив символов
   string            m_symbols[];
   //---
private:
   //--- Получает символы
   void              GetSymbols(void);
  };
//+------------------------------------------------------------------+
//| Получает символы                                                 |
//+------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
//--- Прогресс
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(0,1);
//--- Очистить массив символов
   ::ArrayFree(m_symbols);
//--- Собираем массив форекс-символов
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Получим имя символа
      string symbol_name=::SymbolName(i,false);
      //--- Скроем его в окне Обзор рынка
      ::SymbolSelect(symbol_name,false);
      //--- Если не форекс-символ, перейти к следующему
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Если торговля запрещена, перейти к следующему
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED)
         continue;
      //--- Проверка символа по фильтру
      if(!CheckFilterText(symbol_name))
         continue;
      //--- Сохраним символ в массив
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1,1000);
      m_symbols[array_size]=symbol_name;
     }
//--- Если массив пустой, установим текущий символ по умолчанию
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=_Symbol;
     }
//--- Покажем в окне Обзор рынка
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

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

Для этих целей предназначен метод CProgram::GetSymbolsData(). Метод принимает два параметра: символ и таймфрейм. Получив хендл индикатора, узнаем, сколько всего баров есть в указанном временном диапазоне. Диапазон дат можно указывать с помощью элементов управления в графическом интерфейсе приложения. Далее пробуем получить количество рассчитанных данных индикатора. Дело в том, что сразу после получения хендла расчёт индикатора может быть ещё не завершён. Поэтому, если функция BarsCalculated() вернула значение -1, снова делаем попытки получить корректное значение, пока оно не будет равно либо больше общего количества баров в указанном временном диапазоне.

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

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

В конце метода нужно удалить хендл индикатора, освободив тем самым его расчётную часть. Метод CProgram::GetSymbolsData() будет вызываться множество раз для выбранного списка символов и по нескольким таймфреймам. Расчёт для каждого нужно произвести всего один раз, и полученное значение отобразить в таблице графического интерфейса, поэтому хендлы больше не понадобятся и их можно удалять. 

class CProgram : public CWndCreate
  {
private:
   //--- Получает данные символов
   double            GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Получает данные символов                                         |
//+------------------------------------------------------------------+
double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period)
  {
   double result       =0.0;
   int    buffer_index =2;
//--- Получим хендл индикатора
   string path   ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5";
   int    handle =::iCustom(symbol,period,path,0,0,5);
   if(handle!=INVALID_HANDLE)
     {
      //--- Копируем данные в указанном диапазоне
      double   data[];
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      //--- Количество баров в указанном диапазоне
      int bars_total=::Bars(symbol,period,start_time,end_time);
      //--- Количество баров в указанном диапазоне
      int bars_calculated=::BarsCalculated(handle);
      if(bars_calculated<bars_total)
        {
         while(true)
           {
            ::Sleep(100);
            bars_calculated=::BarsCalculated(handle);
            if(bars_calculated>=bars_total)
               break;
           }
        }
      //--- Получим данные
      int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
      if(copied<1)
        {
         while(true)
           {
            ::Sleep(100);
            copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data);
            if(copied>=bars_total)
               break;
           }

        }
      //--- Выйти, если данные не получены
      int total=::ArraySize(data);
      if(total<1)
         return(result);
      //--- Считаем количество повторений
      int counter=0;
      for(int k=0; k<total; k++)
        {
         if(data[k]>(double)m_level.GetValue())
            counter++;
        }
      //--- Процентное соотношение
      result=((double)counter/(double)total)*100;
     }
//--- Освободить индикатор
   ::IndicatorRelease(handle);
//--- Вернуть значение
   return(result);
  }

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

class CProgram : public CWndCreate
  {
private:
   //--- Перестраивает таблицу
   void              RebuildingTables(void);
  };
//+------------------------------------------------------------------+
//| Перестраивает таблицу                                            |
//+------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Удалить все строки
   m_table.DeleteAllRows();
//--- Добавляем данные
   int symbols_total=::ArraySize(m_symbols);
   for(int i=1; i<symbols_total; i++)
      m_table.AddRow(i);
  }

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

class CProgram : public CWndCreate
  {
private:
   //--- Установка значений в указанный столбец
   void              SetData(const int column_index,const ENUM_TIMEFRAMES period);
   //--- Таймфрейм в строку
   string            GetPeriodName(const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Установка значений в указанный столбец                           |
//+------------------------------------------------------------------+
void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period)
  {
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
     {
      double value=GetSymbolsData(m_symbols[r],period);
      m_table.SetValue(column_index,r,string(value),2,true);
      m_table.Update();
      //--- Прогресс
      m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]...");
      m_progress_bar.Update(r,m_table.RowsTotal());
     }
  }
//+------------------------------------------------------------------+ 
//| Возвращает строковое значение периода                            |
//+------------------------------------------------------------------+ 
string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period)
  {
   return(::StringSubstr(::EnumToString(period),7));
  }

Главным методом для заполнения таблицы данными здесь является CProgram::SetDataToTable(). Здесь таблица сначала перестраивается. Затем в ней нужно установить заголовки и тип данных (TYPE_DOUBLE). В первый столбец установим собранные символы. После этого таблицу нужно перерисовать, чтобы сразу видеть изменения.

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

class CProgram : public CWndCreate
  {
private:
   //--- Заполнение таблицы данными
   void              SetDataToTable(void);
  };
//+------------------------------------------------------------------+
//| Заполнение таблицы данными                                       |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable(void)
  {
//--- Прогресс
   m_progress_bar.LabelText("Data preparation...");
   m_progress_bar.Update(0,1);
//--- Перестроить таблицу
   RebuildingTable();
//--- Заголовки
   string headers[]={"Symbols","M5","H1","H8"};
   for(uint i=0; i<m_table.ColumnsTotal(); i++)
      m_table.SetHeaderText(i,headers[i]);
   for(uint i=1; i<m_table.ColumnsTotal(); i++)
      m_table.DataType(i,TYPE_DOUBLE);
//--- Установим значения в первый столбец
   for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
      m_table.SetValue(0,r,m_symbols[r],0,true);
//--- Показать таблицу
   m_table.Update(true);
//--- Заполним остальные столбцы данными
   SetData(1,PERIOD_M5);
   SetData(2,PERIOD_H1);
   SetData(3,PERIOD_H8);
  }

Перед тем как с помощью метода CProgram::GetData() начать процесс получения данных, нужно сделать видимым индикатор выполнения. Для этого используется метод CProgram::StartProgress(). После окончания процесса получения данных нужно скрыть индикатор выполнения и убрать фокус с нажатой кнопки. Для этих целей нужно вызвать метод CProgram::EndProgress()

class CProgram : public CWndCreate
  {
private:
   //--- Получение данных
   void              GetData(void);

   //--- (1) Начало и (2) окончание прогресса
   void              StartProgress(void);
   void              EndProgress(void);
  };
//+------------------------------------------------------------------+
//| Получение данных                                                 |
//+------------------------------------------------------------------+
void CProgram::GetData(void)
  {
//--- Начало прогресса
   StartProgress();
//--- Получение списка символов
   GetSymbols();
//--- Заполнение таблицы данными
   SetDataToTable();
//--- Окончание прогресса
   EndProgress();
  }
//+------------------------------------------------------------------+
//| Начало прогресса                                                 |
//+------------------------------------------------------------------+
void CProgram::StartProgress(void)
  {
   m_progress_bar.LabelText("Please wait...");
   m_progress_bar.Update(0,1);
   m_progress_bar.Show();
   m_chart.Redraw();
  }
//+------------------------------------------------------------------+
//| Окончание прогресса                                              |
//+------------------------------------------------------------------+
void CProgram::EndProgress(void)
  {
//--- Скрыть индикатор выполнения
   m_progress_bar.Hide();
//--- Обновить кнопку
   m_request.MouseFocus(false);
   m_request.Update(true);
   m_chart.Redraw();
  }

Когда пользователь нажимает кнопку Request, генерируется пользовательское событие ON_CLICK_BUTTON и по идентификатору элемента мы можем определить, какая кнопка была нажата. Если это кнопка Request, то запускаем процесс получения данных

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- События нажатия на кнопках
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      if(lparam==m_request.Id())
        {
         //--- Получение данных
         GetData();
         return;
        }
      //---
      return;
     }
//--- События отсортированной таблицы
   if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA)
     {
      if(lparam==m_table.Id())
        {
         m_table.Update(true);
         return;
        }
      //---
      return;
     }
  }

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

 Рис. 8 – Графический интерфейс MQL-приложения.

Рис. 8 – Графический интерфейс MQL-приложения.

Нажатие на кнопку Request запускает процесс получения данных:

 Рис. 9 – Процесс получения данных.

Рис. 9 – Процесс получения данных.

После того, как все данные получены Вы можете их отсортировать:

 Рис. 10 – Сортировка данных таблицы.

Рис. 10 – Сортировка данных таблицы.

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

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

 Рис. 11 – Цветовые шкалы в Excel.

Рис. 11 – Цветовые шкалы в Excel.

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

Внесём небольшие изменения и дополнения в MQL-приложение, которое рассматривали выше. Для установки уникального цвета к каждой ячейки таблицы нужно отключить форматирование в стиле «зебра». Закомментируем эту строку кода.

// m_table.IsZebraFormatRows(clrWhiteSmoke);

Теперь создадим метод для форматирования таблицы - CProgram::SetColorsToTable(). Для работы с цветом будем использовать класс CColors. Он уже есть в библиотеке для создания графических интерфейсов, поэтому подключение файла к проекту не требуется. Объявляем два массива для работы: (1) массив для получения цветов градиента и (2) массив цветов, из которых будет формироваться градиент. В данном случае создадим трёхцветный градиент. Чем меньше будет значение, тем к более красному оттенку (clrTomato) будет приближаться цвет. Чем значение больше — к более синему (clrCornflowerBlue). Чтобы отделить эти две цветовые зоны в качестве переходного (среднего) добавим белый цвет. 

Определим размер диапазона значений от минимального к максимальному. Именно такого размера будет массив градиента. Установка размера массива и его заполнение осуществляется в методе CColors::Gradient(). В завершающем цикле устанавливаются цвета ячейкам таблицы. Для того чтобы не выйти за пределы диапазона массива, индекс рассчитывается, как значение ячейки минус минимальное значение диапазона. В конце метода обязательное обновление таблицы для отображения внесённых изменений.

class CProgram : public CWndCreate
  {
private:
   //--- Заполнение таблицы цветом фона для ячеек
   void              SetColorsToTable(void);
  };
//+------------------------------------------------------------------+
//| Форматирование таблицы                                           |
//+------------------------------------------------------------------+
void CProgram::SetColorsToTable(void)
  {
//--- Для работы с цветом
   CColors clr;
//--- Массив для получения градиента
   color out_colors[];
//--- Трёхцветный градиент
   color colors[3]={clrTomato,clrWhite,clrCornflowerBlue};
//--- Найдём минимальное и максимальное значение в таблице
   double max =0;
   double min =100;
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         max =::fmax(max,(double)m_table.GetValue(c,r));
         min =::fmin(min,(double)m_table.GetValue(c,r));
        }
     }
//--- Приведём к ближайшему целому снизу
   max =::floor(max);
   min =::floor(min);
//--- Получим диапазон
   int range =int(max-min)+1;
//--- Получим градиентный массив цветов
   clr.Gradient(colors,out_colors,range);
//--- Установим цвет фона ячейкам
   for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++)
     {
      for(uint r=0; r<(uint)m_table.RowsTotal(); r++)
        {
         int index=(int)m_table.GetValue(c,r)-(int)min;
         m_table.BackColor(c,r,out_colors[index],true);
        }
     }
//--- Обновить таблицу
   m_table.Update();
  }

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

 Рис. 12 – Цветовая шкала для визуализации данных в таблице.

Рис. 12 – Цветовая шкала для визуализации данных в таблице.

Учитывайте, что чем больше вы устанавливаете диапазон дат, тем больше используется данных и, соответственно, тем больше времени понадобится для формирования данных и расчётов показателей. Если каких-то данных не хватает, будет произведена попытка их закачки с сервера. 


Считаем количество сегментов по размеру

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

 Рис. 13 – Программа для подсчёта количества сегментов по размеру.

Рис. 13 – Программа для подсчёта количества сегментов по размеру.

Кнопка Request запрашивает список символов по указанному фильтру. При нажатии на кнопку Calculate собираются данные из указанного временного диапазона, которые затем распределяются во второй таблице. 

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

class CProgram : public CWndCreate
  {
private:
   //--- Получает данные индикатора
   void              GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period);
  };
//+------------------------------------------------------------------+
//| Получает данные индикатора                                       |
//+------------------------------------------------------------------+
void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period)
  {
//--- Получим хендл индикатора
   string path   ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5";
   int    handle =::iCustom(symbol,period,path,0,0);
   if(handle!=INVALID_HANDLE)
     {
      //--- Копируем данные в указанном диапазоне
      datetime start_time =m_from_date.SelectedDate();
      datetime end_time   =m_to_date.SelectedDate();
      m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time);
      //--- Покажем информацию в статусной строке
      string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal();
      m_status_bar.SetValue(0,text);
      m_status_bar.GetItemPointer(0).Update(true);
     }
//--- Освободить индикатор
   ::IndicatorRelease(handle);
  }

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

class CProgram : public CWndCreate
  {
private:
   //--- Массив диапазонов
   int               m_levels_array[];
   //---
private:
   //--- Получает уровни
   void              GetLevels(void);
  };
//+------------------------------------------------------------------+
//| Получает уровни                                                  |
//+------------------------------------------------------------------+
void CProgram::GetLevels(void)
  {
//--- Освободить массив
   ::ArrayFree(m_levels_array);
//--- Получим максимальный размер сегмента
   int max_value=int(m_zz.LargestSegment()/m_symbol.Point());
//--- Заполним массив уровнями
   int counter_levels=0;
   while(true)
     {
      int size=::ArraySize(m_levels_array);
      ::ArrayResize(m_levels_array,size+1);
      m_levels_array[size]=counter_levels;
      //---
      if(counter_levels>max_value)
         break;
      //---
      counter_levels+=(int)m_step.GetValue();
     }
  }

Для заполнения второй таблицы данными предназначен метод CProgram::SetDataToTable2(). В самом начале проверяется, выделен ли символ в списке первой таблицы. Если оказывается что нет, то программа выходит из метода, отправив сообщение в журнал экспертов. Если какая-то строка в первой таблице выделена, то определяем символ и получаем по нему данные. После этого вызываются методы, которые рассматривали выше, для получения данных индикатора и расчёта уровней. Данные индикатора получаем с тем же таймфреймом, на котором загружен эксперт.

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

class CProgram : public CWndCreate
  {
private:
   //--- Заполнение таблицы 2 данными
   void              SetDataToTable2(void);
  };
//+------------------------------------------------------------------+
//| Заполнение таблицы 2 данными                                     |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable2(void)
  {
//--- Выйти, если строка не выделена
   if(m_table1.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a symbol in the table on the left!");
      return;
     }
//--- Начало прогресса
   StartProgress();
//--- Скрыть таблицу
   m_table2.Hide();
//--- Получим символ из первой таблицы
   string symbol=m_table1.GetValue(0,m_table1.SelectedItem());
   m_symbol.Name(symbol);
//--- Получаем данные индикатора
   GetIndicatorData(symbol,_Period);
//--- Получает уровни
   GetLevels();
//--- Перестроить таблицу
   RebuildingTable2();
//--- Установим диапазоны в первый столбец
   for(uint r=0; r<(uint)m_table2.RowsTotal(); r++)
      m_table2.SetValue(0,r,(string)m_levels_array[r],0);
//--- Получим значения во второй столбец
   int items_total=::ArraySize(m_levels_array);
   int segments_total=m_zz.SegmentsTotal();
   for(int i=0; i<items_total-1; i++)
     {
      //--- Прогресс
      m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]...");
      m_progress_bar.Update(i,m_table2.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size=int(m_zz.SegmentSize(s)/m_symbol.Point());
         if(size>m_levels_array[i] && size<m_levels_array[i+1])
           {
            int value=(int)m_table2.GetValue(1,i)+1;
            m_table2.SetValue(1,i,(string)value,0);
           }
        }
     }
//--- Показать таблицу
   m_table2.Update(true);
//--- Окончание прогресса
   EndProgress();
  }

Для примера получим размеры сегментов по символу EURUSD с 2010 года по текущий момент на пятиминутном графике. Диапазоны выставим с шагом 100 пятизначных пунктов. Результат показан на скриншоте ниже.

Общее количество сегментов — 302145. Видно, что максимальное количество сегментов в диапазоне от нуля до 100. Далее от уровня к уровню количество сегментов уменьшается. За указанный временной период максимальный размер сегмента достигал 2400 пятизначных пунктов. 

 Рис. 14 – Результат подсчёта количества сегментов по размеру.

Рис. 14 – Результат подсчёта количества сегментов по размеру.


Считаем количество сегментов по длительности

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

 Рис. 15 – Программа для подсчёта количества сегментов по длительности.

Рис. 15 – Программа для подсчёта количества сегментов по длительности.

Последовательность действий для получения данных во всех таблицах будет такой:

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

class CProgram : public CWndCreate
  {
private:
   //--- Заполнение таблицы 3 данными
   void              SetDataToTable3(void);
  };
//+------------------------------------------------------------------+
//| Заполнение таблицы 3 данными                                     |
//+------------------------------------------------------------------+
void CProgram::SetDataToTable3(void)
  {
//--- Выйти, если строка не выделена
   if(m_table2.SelectedItem()==WRONG_VALUE)
     {
      ::Print(__FUNCTION__," > Select a range in the table on the left!");
      return;
     }
//--- Начало прогресса
   StartProgress();
//--- Скрыть таблицу
   m_table3.Hide();
//--- Получим выделенную строку
   int selected_row_index=m_table2.SelectedItem();
//--- Диапазон
   int selected_range=(int)m_table2.GetValue(0,selected_row_index);
//--- Перестроить таблицу
   RebuildingTable3();
//--- Установим значения в первый столбец
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
      m_table3.SetValue(0,r,(string)(r+1),0);
//--- Получим значения во второй столбец
   int segments_total=m_zz.SegmentsTotal();
   for(uint r=0; r<(uint)m_table3.RowsTotal(); r++)
     {
      //--- Прогресс
      m_progress_bar.LabelText("Get data ["+(string)r+"]...");
      m_progress_bar.Update(r,m_table3.RowsTotal());
      //---
      for(int s=0; s<segments_total; s++)
        {
         int size =int(m_zz.SegmentSize(s)/m_symbol.Point());
         int bars =m_zz.SegmentBars(s);
         //---
         if(size>selected_range && 
            size<selected_range+(int)m_step.GetValue() && 
            bars==r+1)
           {
            int value=(int)m_table3.GetValue(1,r)+1;
            m_table3.SetValue(1,r,(string)value,0);
           }
        }
     }
//--- Показать таблицу
   m_table3.Update(true);
//--- Окончание прогресса
   EndProgress();
  }

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- События нажатия на строках
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Нажали на строку таблицы
      if(lparam==m_table2.Id())
        {
         //--- Получение данных в третью таблицу
         SetDataToTable3();
         return;
        }
      //---
      return;
     }
...
  }

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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- События нажатия на кнопках
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Нажали на кнопку 'Request'
      if(lparam==m_request.Id())
        {
         //--- Получение данных в первую таблицу
         SetDataToTable1();
         //--- Очистка таблиц от неактуальных данных
         m_table2.DeleteAllRows(true);
         m_table3.DeleteAllRows(true);
         return;
        }
      //--- Нажали на кнопку 'Calculate'
      if(lparam==m_calculate.Id())
        {
         //--- Получение данных во вторую таблицу
         SetDataToTable2();
         //--- Очистка таблиц от неактуальных данных
         m_table3.DeleteAllRows(true);
        }
      //---
      return;
     }
...
  }

Запускаем описанного выше эксперта на графике и получаем результат, как показано ниже. В данном случае был сформирован список валютных пар, в которых есть доллар (USD). Затем были получены данные по символу GBPUSD от начала 2018 года и сформирован список диапазонов (вторая таблица) с шагом 100 и подсчитанными сегментами для каждого из них. Для примера во второй таблице выделена строка с диапазоном  200 и количеством сегментов 1922 (от 200 до 300). В третьей таблице показаны длительности всех сегментов из выделенного во второй таблице диапазона. Например, видно, что за этот период по GBPUSD было только 11 сегментов длительностью 10 баров из указанного диапазона.

 Рис. 16 – Результат подсчёта количества сегментов по длительности.

Рис. 16 – Результат подсчёта количества сегментов по длительности.


Некоторые тонкости работы с графическим интерфейсом

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

Правильно это делается  следующим образом:

В главном классе своей программы создаём поле для хранения последней причины деинициализации программы:

class CProgram : public CWndCreate
  {
private:
   //--- Последняя причина деинициализации
   int               m_last_deinit_reason;
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE)
  {
  }

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

//+------------------------------------------------------------------+
//| Деинициализация                                                  |
//+------------------------------------------------------------------+
void CProgram::OnDeinitEvent(const int reason)
  {
//--- Запомним последнюю причину деинициализации
   m_last_deinit_reason=reason;
//--- Удалить GUI, если причина не связана с изменением символа и периода
   if(reason!=REASON_CHARTCHANGE)
     {
      CWndEvents::Destroy();
     }
  }

Так как графический интерфейс создаётся в момент инициализации программы вызовом метода CProgram::CreateGUI(), то достаточно теперь проверить последнюю причину деинициализации. Если причина в том, что был изменён символ или таймфрейм, то не нужно создавать графический интерфейс — просто выходим из метода, сообщив, что всё в порядке.

//+------------------------------------------------------------------+
//| Создаёт графический интерфейс                                    |
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- Выйти, если сменили график или таймфрейм
   if(m_last_deinit_reason==REASON_CHARTCHANGE)
      return(true);
...
   return(true);
  }


Заключение

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

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

Наименование файла Комментарий
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 Индикатор для измерения частоты образования разнонаправленных сегментов индикатора ZigZag
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 Индикатор суммы сегментов из полученного набора и их среднее
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 Индикатор процентного соотношения сумм сегментов и их разница
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 Индикатор для определения характера формирования нескольких сегментов со старшего таймфрейма с помощью показателя разницы процентных соотношений сумм разнонаправленных сегментов
MQL5\Experts\ZigZag\TestZZ_05.mq5 Эксперт для теста индикатора MultiPercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 Эксперт для сбора статистики по индикатору PercentageSegmentsZZ
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 Эксперт для подсчёта сегментов в разных ценовых диапазонах
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5  Эксперт для подсчёта сегментов в разных ценовых диапазонах и с разной длительностью