Работа с ценами в библиотеке DoEasy (Часть 59): Объект для хранения данных одного тика

Artyom Trishkin | 19 декабря, 2020

Содержание


Концепция

С этой статьи начнём разработку функционала библиотеки для работы с тиковыми данными.

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

В концепции хранения тиковых данных минимальной величиной объёма данных будут значения структуры цены на одном тике. Такие значения описываются при помощи структуры для хранения последних цен по символу MqlTick. Объект для хранения таких значений будет иметь в своём составе дополнительные свойства: спред — разница цен Ask и Bid и символ, данные одного тика которого описываются объектом.

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

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


Подготовка данных

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

В файл \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:

//--- CSeriesDataInd
   MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS,            // Метод не предназначен для работы с программами-индикаторами
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA,      // Не удалось получить таймсерию индикаторных данных
   MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA,     // Не удалось получить текущие данные буфера индикатора
   MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ,            // Не удалось создать объект индикаторных данных
   MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST,          // Не удалось добавить объект индикаторных данных в список
   
//--- CTick
   MSG_TICK_TEXT_TICK,                                // Тик
   MSG_TICK_TIME_MSC,                                 // Время последнего обновления цен в миллисекундах
   MSG_TICK_TIME,                                     // Время последнего обновления цен
   MSG_TICK_VOLUME,                                   // Объем для текущей цены Last
   MSG_TICK_FLAGS,                                    // Флаги
   MSG_TICK_VOLUME_REAL,                              // Объем для текущей цены Last c повышенной точностью
   MSG_TICK_SPREAD,                                   // Спред
   MSG_LIB_TEXT_TICK_CHANGED_DATA,                    // Изменённые данные на тике:
   MSG_LIB_TEXT_TICK_FLAG_BID,                        // Изменение цены Bid
   MSG_LIB_TEXT_TICK_FLAG_ASK,                        // Изменение цены Ask
   MSG_LIB_TEXT_TICK_FLAG_LAST,                       // Изменение цены последней сделки
   MSG_LIB_TEXT_TICK_FLAG_VOLUME,                     // Изменение объема

  };
//+------------------------------------------------------------------+

и тексты сообщений, соответствующие вновь добавленным индексам:

//--- CSeriesDataInd
   {"Метод не предназначен для работы с программами-индикаторами","The method is not intended for working with indicator programs"},
   {"Не удалось получить таймсерию индикаторных данных","Failed to get indicator data timeseries"},
   {"Не удалось получить текущие данные буфера индикатора","Failed to get the current data of the indicator buffer"},
   {"Не удалось создать объект индикаторных данных","Failed to create indicator data object"},
   {"Не удалось добавить объект индикаторных данных в список","Failed to add indicator data object to the list"},
   
//--- CTick
   {"Тик","Tick"},
   {"Время последнего обновления цен в миллисекундах","Last price update time in milliseconds"},
   {"Время последнего обновления цен","Last price update time"},
   {"Объем для текущей цены Last","Volume for the current Last price"},
   {"Флаги","Flags"},
   {"Объем для текущей цены Last c повышенной точностью","Volume for the current \"Last\" price with increased accuracy"},
   {"Спред","Spread"},
   {"Изменённые данные на тике:","Changed data on a tick:"},
   {"Изменение цены Bid","Bid price change"},
   {"Изменение цены Ask","Ask price change"},
   {"Изменение цены последней сделки","Last price change"},
   {"Изменение объема","Volume change"},
   
  };
//+---------------------------------------------------------------------+


В файл \MQL5\Include\DoEasy\Defines.mqh впишем перечисления для указания целочисленных, вещественных и строковых свойств объекта тиковых данных:

//+------------------------------------------------------------------+
//| Данные для работы с тиковыми данными                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Целочисленные свойства тика                                      |
//+------------------------------------------------------------------+
enum ENUM_TICK_PROP_INTEGER
  {
   TICK_PROP_TIME_MSC = 0,                                  // Время последнего обновления цен в миллисекундах
   TICK_PROP_TIME,                                          // Время последнего обновления
   TICK_PROP_VOLUME,                                        // Объем для текущей цены Last
   TICK_PROP_FLAGS,                                         // Флаги тика
  }; 
#define TICK_PROP_INTEGER_TOTAL (4)                         // Общее количество целочисленных свойств тика
#define TICK_PROP_INTEGER_SKIP  (0)                         // Количество неиспользуемых в сортировке свойств тика
//+------------------------------------------------------------------+
//| Вещественные свойства тика                                       |
//+------------------------------------------------------------------+
enum ENUM_TICK_PROP_DOUBLE
  {
   TICK_PROP_BID = TICK_PROP_INTEGER_TOTAL,                 // Цена Bid тика
   TICK_PROP_ASK,                                           // Цена Ask тика
   TICK_PROP_LAST,                                          // Текущая цена последней сделки (Last)
   TICK_PROP_VOLUME_REAL,                                   // Объем для текущей цены Last c повышенной точностью
   TICK_PROP_SPREAD,                                        // Спред тика (Ask - Bid)
  }; 
#define TICK_PROP_DOUBLE_TOTAL  (5)                         // Общее количество вещественных свойств тика
#define TICK_PROP_DOUBLE_SKIP   (0)                         // Количество неиспользуемых в сортировке свойств тика
//+------------------------------------------------------------------+
//| Строковые свойства тика                                          |
//+------------------------------------------------------------------+
enum ENUM_TICK_PROP_STRING
  {
   TICK_PROP_SYMBOL = (TICK_PROP_INTEGER_TOTAL+TICK_PROP_DOUBLE_TOTAL), // Символ тика
  };
#define TICK_PROP_STRING_TOTAL  (1)                         // Общее количество строковых свойств тика
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки тиков                              |
//+------------------------------------------------------------------+
#define FIRST_TICK_DBL_PROP          (TICK_PROP_INTEGER_TOTAL-TICK_PROP_INTEGER_SKIP)
#define FIRST_TICK_STR_PROP          (TICK_PROP_INTEGER_TOTAL-TICK_PROP_INTEGER_SKIP+TICK_PROP_DOUBLE_TOTAL-TICK_PROP_DOUBLE_SKIP)
enum ENUM_SORT_TICK_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_TICK_TIME_MSC = 0,                               // Сортировать по времени последнего обновления цен в миллисекундах
   SORT_BY_TICK_TIM,                                        // Сортировать по времени последнего обновления цен
   SORT_BY_TICK_VOLUME,                                     // Сортировать по объему для текущей цены Last
   SORT_BY_TICK_FLAGS,                                      // Сортировать по флагам тика
//--- Сортировка по вещественным свойствам
   SORT_BY_TICK_BID = FIRST_TICK_DBL_PROP,                  // Сортировать по цене Bid тика
   SORT_BY_TICK_ASK,                                        // Сортировать по цене Ask тика
   SORT_BY_TICK_LAST,                                       // Сортировать по текущей цене последней сделки (Last)
   SORT_BY_TICK_VOLUME_REAL,                                // Сортировать по объему для текущей цены Last c повышенной точностью
   SORT_BY_TICK_SPREAD,                                     // Сортировать по спреду тика
//--- Сортировка по строковым свойствам
   SORT_BY_TICK_SYMBOL = FIRST_TICK_STR_PROP,               // Сортировать по символу тика
  };
//+------------------------------------------------------------------+


Ранее мы уже создавали объект "Новый тик" в статье 38, позволяющий отслеживать поступление нового тика на указанном символе.
Объект расположен в папке \MQL5\Include\DoEasy\Objects\Ticks\ каталога библиотеки в файле NewTickObj.mqh.
Доработаем класс объекта так, чтобы мы могли передавать в метод установки символа значение NULL (или пустую строку) для указания текущего символа объекту:

//--- Устанавливает символ
   void              SetSymbol(const string symbol)   { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);  }

И в конструкторе класса, в его списке инициализации установим значение false для переменной m_new_tick, хранящей флаг нового тика:

//+------------------------------------------------------------------+
//| Параметрический конструктор CNewTickObj                          |
//+------------------------------------------------------------------+
CNewTickObj::CNewTickObj(const string symbol) : m_symbol(symbol),m_new_tick(false)
  {
//--- Обнуляем структуры нового и прошлого тиков
   ::ZeroMemory(this.m_tick);
   ::ZeroMemory(this.m_tick_prev);
//--- Если удалось получить текущие цены в структуру тика -
//--- копируем данные полученного тика в данные прошлого тика и сбрасываем флаг первого запуска
  if(::SymbolInfoTick(this.m_symbol,this.m_tick))
     { 
      this.m_tick_prev=this.m_tick;
      this.m_first_start=false;
     }
  }
//+------------------------------------------------------------------+

Ранее эта переменная нигде не инициализировалась начальным значением, что неправильно.


Класс объекта данных тика

В папке расположения класса объекта "Новый тик" \MQL5\Include\DoEasy\Objects\Ticks\ создадим новый файл класса объекта тиковых данных DataTick.mqh.

Объект не будет из себя представлять чего-то нового для классов библиотеки — всё стандартно. Рассмотрим тело класса:

//+------------------------------------------------------------------+
//|                                                     DataTick.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\BaseObj.mqh"
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Класс "Тик"                                                      |
//+------------------------------------------------------------------+
class CDataTick : public CBaseObj
  {
private:
   MqlTick           m_tick;                                      // Структура для получения текущих цен
   int               m_digits;                                    // Значение Digits символа
   long              m_long_prop[TICK_PROP_INTEGER_TOTAL];        // Целочисленные свойства
   double            m_double_prop[TICK_PROP_DOUBLE_TOTAL];       // Вещественные свойства
   string            m_string_prop[TICK_PROP_STRING_TOTAL];       // Строковые свойства

//--- Возвращает индекс массива, по которому фактически расположено (1) double-свойство и (2) string-свойство тика
   int               IndexProp(ENUM_TICK_PROP_DOUBLE property)       const { return(int)property-TICK_PROP_INTEGER_TOTAL;                       }
   int               IndexProp(ENUM_TICK_PROP_STRING property)       const { return(int)property-TICK_PROP_INTEGER_TOTAL-TICK_PROP_DOUBLE_TOTAL;}

public:
//--- Устанавливает (1) целочисленное, (2) вещественное и (3) строковое свойство тика
   void              SetProperty(ENUM_TICK_PROP_INTEGER property,long value)  { this.m_long_prop[property]=value;                               }
   void              SetProperty(ENUM_TICK_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value;             }
   void              SetProperty(ENUM_TICK_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value;             }
//--- Возвращает из массива свойств (1) целочисленное, (2) вещественное и (3) строковое свойство тика
   long              GetProperty(ENUM_TICK_PROP_INTEGER property)    const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_TICK_PROP_DOUBLE property)     const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_TICK_PROP_STRING property)     const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Возвращает флаг поддержания тиком данного свойства
   virtual bool      SupportProperty(ENUM_TICK_PROP_INTEGER property)      { return true; }
   virtual bool      SupportProperty(ENUM_TICK_PROP_DOUBLE property)       { return true; }
   virtual bool      SupportProperty(ENUM_TICK_PROP_STRING property)       { return true; }
//--- Возвращает себя
   CDataTick        *GetObject(void)                                       { return &this;}

//--- Сравнивает объекты CDataTick между собой по указанному свойству (для сортировки списков по указанному свойству объекта)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CDataTick между собой по всем свойствам (для поиска равных объектов)
   bool              IsEqual(CDataTick* compared_obj) const;
//--- Конструкторы
                     CDataTick(){;}
                     CDataTick(const string symbol,const MqlTick &tick);
        
//+------------------------------------------------------------------+
//| Описания свойств объекта-тиковых данных                          |
//+------------------------------------------------------------------+
//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства тика
   string            GetPropertyDescription(ENUM_TICK_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_TICK_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_TICK_PROP_STRING property);

//--- Выводит в журнал описание свойств тика (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал краткое описание тика
   virtual void      PrintShort(void);
//--- Возвращает (1) краткое наименование, (2) описание флагов объекта тиковых данных
   virtual string    Header(void);
   string            FlagsDescription(void);
   
//+------------------------------------------------------------------+ 
//| Методы упрощённого доступа к свойствам объекта тиковых данных    |
//+------------------------------------------------------------------+
//--- Возвращает (1) Время (2) время в милисекундах, (3) объём, (4) флаги тика
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(TICK_PROP_TIME);           }
   long              TimeMSC(void)                                      const { return this.GetProperty(TICK_PROP_TIME_MSC);                 }
   long              Volume(void)                                       const { return this.GetProperty(TICK_PROP_VOLUME);                   }
   uint              Flags(void)                                        const { return (uint)this.GetProperty(TICK_PROP_FLAGS);              }
   
//--- Возвращает цену тика (1) Bid, (2) Ask, (3) Last, (4) объем c повышенной точностью, (5) спред тика
//--- размер (9) верхней, (10) нижней тени свечи
   double            Bid(void)                                          const { return this.GetProperty(TICK_PROP_BID);                      }
   double            Ask(void)                                          const { return this.GetProperty(TICK_PROP_ASK);                      }
   double            Last(void)                                         const { return this.GetProperty(TICK_PROP_LAST);                     }
   double            VolumeReal(void)                                   const { return this.GetProperty(TICK_PROP_VOLUME_REAL);              }
   double            Spread(void)                                       const { return this.GetProperty(TICK_PROP_SPREAD);                   }
   
//--- Возвращает символ тика
   string            Symbol(void)                                       const { return this.GetProperty(TICK_PROP_SYMBOL);                   }

//--- Возвращает (1) время, (2) индекс бара на указанном таймфрейме, в который попадает время тика
   datetime          TimeBar(const ENUM_TIMEFRAMES timeframe)const
                       { return ::iTime(this.Symbol(),timeframe,this.Index(timeframe));                                                      }
   int               Index(const ENUM_TIMEFRAMES timeframe)  const
                       { return ::iBarShift(this.Symbol(),(timeframe==PERIOD_CURRENT ? ::Period() : timeframe),this.Time());                 }  

//--- Возвращает флаг смены цены (1) Bid, (2) Ask, (3) Last, (4) объёма, сделка на (5) покупку, (6) продажу
   bool              IsChangeBid()                                      const { return((this.Flags() & TICK_FLAG_BID)==TICK_FLAG_BID);       }
   bool              IsChangeAsk()                                      const { return((this.Flags() & TICK_FLAG_ASK)==TICK_FLAG_ASK);       }
   bool              IsChangeLast()                                     const { return((this.Flags() & TICK_FLAG_LAST)==TICK_FLAG_LAST);     }
   bool              IsChangeVolume()                                   const { return((this.Flags() & TICK_FLAG_VOLUME)==TICK_FLAG_VOLUME); }
   bool              IsChangeBuy()                                      const { return((this.Flags() & TICK_FLAG_BUY)==TICK_FLAG_BUY);       }
   bool              IsChangeSell()                                     const { return((this.Flags( )& TICK_FLAG_SELL)==TICK_FLAG_SELL);     }
//---
  };
//+------------------------------------------------------------------+

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

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

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

Там же расположены методы для вывода описаний свойств объекта и методы для упрощённого доступа к свойствам объекта.

Теперь рассмотрим реализацию методов класса.

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CDataTick::CDataTick(const string symbol,const MqlTick &tick)
  {
//--- Сохраняем Digits символа
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
//--- Сохраняем целочисленные свойства тика
   this.SetProperty(TICK_PROP_TIME,tick.time);
   this.SetProperty(TICK_PROP_TIME_MSC,tick.time_msc);
   this.SetProperty(TICK_PROP_VOLUME,tick.volume);
   this.SetProperty(TICK_PROP_FLAGS,tick.flags);
//--- Сохраняем вещественные свойства тика
   this.SetProperty(TICK_PROP_BID,tick.bid);
   this.SetProperty(TICK_PROP_ASK,tick.ask);
   this.SetProperty(TICK_PROP_LAST,tick.last);
   this.SetProperty(TICK_PROP_VOLUME_REAL,tick.volume_real);
//--- Сохраняем дополнительные свойства тика
   this.SetProperty(TICK_PROP_SPREAD,tick.ask-tick.bid);
   this.SetProperty(TICK_PROP_SYMBOL,(symbol==NULL || symbol=="" ? ::Symbol() : symbol));
  }
//+------------------------------------------------------------------+

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

Метод сравнения двух параметров объектов по указанному свойству:

//+------------------------------------------------------------------+

//| Сравнивает объекты CDataTick между собой по указанному свойству  |
//+------------------------------------------------------------------+
int CDataTick::Compare(const CObject *node,const int mode=0) const
  {
   const CDataTick *obj_compared=node;
//--- сравнение целочисленных свойств двух объектов
   if(mode<TICK_PROP_INTEGER_TOTAL)
     {
      long value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_TICK_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение вещественных свойств двух объектов
   else if(mode<TICK_PROP_DOUBLE_TOTAL+TICK_PROP_INTEGER_TOTAL)
     {
      double value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_TICK_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение строковых свойств двух объектов
   else if(mode<TICK_PROP_DOUBLE_TOTAL+TICK_PROP_INTEGER_TOTAL+TICK_PROP_STRING_TOTAL)
     {
      string value_compared=obj_compared.GetProperty((ENUM_TICK_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_TICK_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+

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

В зависимости от переданного режима сравнения объектов сравниваем эти свойства у двух объектов и возвращаем 1/-1/0, если у текущего объекта значение его свойства больше/меньше или равно значению свойства сравниваемого объекта, соответственно.

Метод для сравнения двух объектов на идентичность:

//+------------------------------------------------------------------+
//| Сравнивает объекты CDataTick между собой по всем свойствам       |
//+------------------------------------------------------------------+
bool CDataTick::IsEqual(CDataTick *compared_obj) const
  {
   int beg=0, end=TICK_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_INTEGER prop=(ENUM_TICK_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=TICK_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_DOUBLE prop=(ENUM_TICK_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=TICK_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_STRING prop=(ENUM_TICK_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

Здесь: в метод передаётся указатель на объект для сравнения. Затем в трёх циклах по каждой группе свойств сравниваем каждое очередное свойство двух объектов. Если хоть одно из свойств не равно такому же у сравниваемого объекта, то возвращается false. По окончании всех трёх циклов возвращается true — все свойства двух объектов равны, а значит — объекты идентичны.

Метод для вывода в журнал всех свойств объекта:

//+------------------------------------------------------------------+
//| Выводит в журнал свойства тика                                   |
//+------------------------------------------------------------------+
void CDataTick::Print(const bool full_prop=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   int beg=0, end=TICK_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_INTEGER prop=(ENUM_TICK_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=TICK_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_DOUBLE prop=(ENUM_TICK_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=TICK_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_TICK_PROP_STRING prop=(ENUM_TICK_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n");
  }
//+------------------------------------------------------------------+

Здесь: в трёх циклах по каждой из групп свойств объекта выводится в журнал описание каждого очередного свойства при помощи соответствующего метода GetPropertyDescription(). Если в параметрах метода передано значение false в переменной full_prop, то выводятся только поддерживаемые объектом свойства. Для неподдерживаемого свойства в журнал пишется, что свойство не поддерживается, хотя в данном объекте поддерживаются все свойства, но это можно изменить в объектах-наследниках класса.

Методы, возвращающие описание указанного целочисленного, вещественного и строкового свойства объекта:

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства тика                 |
//+------------------------------------------------------------------+
string CDataTick::GetPropertyDescription(ENUM_TICK_PROP_INTEGER property)
  {
   return
     (
      property==TICK_PROP_TIME_MSC           ?  CMessage::Text(MSG_TICK_TIME_MSC)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TimeMSCtoString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==TICK_PROP_TIME               ?  CMessage::Text(MSG_TICK_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==TICK_PROP_VOLUME             ?  CMessage::Text(MSG_TICK_VOLUME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==TICK_PROP_FLAGS              ?  CMessage::Text(MSG_TICK_FLAGS)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)+"\n"+CMessage::Text(MSG_LIB_TEXT_TICK_CHANGED_DATA)+this.FlagsDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание вещественного свойства тика                  |
//+------------------------------------------------------------------+
string CDataTick::GetPropertyDescription(ENUM_TICK_PROP_DOUBLE property)
  {
   int dg=(this.m_digits>0 ? this.m_digits : 1);
   return
     (
      property==TICK_PROP_BID                ?  CMessage::Text(MSG_LIB_PROP_BID)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==TICK_PROP_ASK                ?  CMessage::Text(MSG_LIB_PROP_ASK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==TICK_PROP_LAST               ?  CMessage::Text(MSG_LIB_PROP_LAST)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==TICK_PROP_VOLUME_REAL        ?  CMessage::Text(MSG_TICK_VOLUME_REAL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),2)
         )  :
      property==TICK_PROP_SPREAD             ?  CMessage::Text(MSG_TICK_SPREAD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства тика                     |
//+------------------------------------------------------------------+
string CDataTick::GetPropertyDescription(ENUM_TICK_PROP_STRING property)
  {
   return(property==TICK_PROP_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL)+": \""+this.GetProperty(property)+"\"" : "");
  }
//+------------------------------------------------------------------+

Здесь: в зависимости от переданного в метод свойства возвращается его строковое описание.

Метод, возвращающий строку с описанием всех флагов тика:

//+------------------------------------------------------------------+
//| Выводит описание флагов                                          |
//+------------------------------------------------------------------+
string CDataTick::FlagsDescription(void)
  {
   string flags=
     (
      (this.IsChangeAsk()     ? "\n - "+CMessage::Text(MSG_LIB_TEXT_TICK_FLAG_ASK)     : "")+
      (this.IsChangeBid()     ? "\n - "+CMessage::Text(MSG_LIB_TEXT_TICK_FLAG_BID)     : "")+
      (this.IsChangeLast()    ? "\n - "+CMessage::Text(MSG_LIB_TEXT_TICK_FLAG_LAST)    : "")+
      (this.IsChangeVolume()  ? "\n - "+CMessage::Text(MSG_LIB_TEXT_TICK_FLAG_VOLUME)  : "")+
      (this.IsChangeBuy()     ? "\n - "+CMessage::Text(MSG_DEAL_TO_BUY)                : "")+
      (this.IsChangeSell()    ? "\n - "+CMessage::Text(MSG_DEAL_TO_SELL)               : "")
     );
  return flags; 
  }
//+------------------------------------------------------------------+

У каждого тика в его свойствах есть переменная, хранящая набор флагов. Эти флаги описывают события, повлёкшие за собой этот тик:

Из справки по MqlTick

Чтобы узнать, какие именно данные изменились с текущим тиком, анализируйте его флаги:

У нас есть шесть методов (по количеству флагов), возвращающих флаг наличия в составе переменной каждого из флагов:

//--- Возвращает флаг смены цены (1) Bid, (2) Ask, (3) Last, (4) объёма, сделка на (5) покупку, (6) продажу
   bool IsChangeBid()    const { return((this.Flags() & TICK_FLAG_BID)==TICK_FLAG_BID);       }
   bool IsChangeAsk()    const { return((this.Flags() & TICK_FLAG_ASK)==TICK_FLAG_ASK);       }
   bool IsChangeLast()   const { return((this.Flags() & TICK_FLAG_LAST)==TICK_FLAG_LAST);     }
   bool IsChangeVolume() const { return((this.Flags() & TICK_FLAG_VOLUME)==TICK_FLAG_VOLUME); }
   bool IsChangeBuy()    const { return((this.Flags() & TICK_FLAG_BUY)==TICK_FLAG_BUY);       }
   bool IsChangeSell()   const { return((this.Flags() & TICK_FLAG_SELL)==TICK_FLAG_SELL);     }

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

Метод, выводящий в журнал краткое описание объекта тиковых данных:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание тика                           |
//+------------------------------------------------------------------+
void CDataTick::PrintShort(void)
  {
   ::Print(this.Header());
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта тиковых данных           |
//+------------------------------------------------------------------+
string CDataTick::Header(void)
  {
   return
     (
      CMessage::Text(MSG_TICK_TEXT_TICK)+" \""+this.Symbol()+"\" "+TimeMSCtoString(TimeMSC())
     );
  }
//+------------------------------------------------------------------+

На этом создание объекта тиковых данных завершено.


Тестирование объекта данных тика

Для тестирования возьмём советник из прошлой статьи
и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part59\ под новым именем TestDoEasyPart59.mq5.

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

Так как пока ещё нет никакой связи советника с этим новым объектом библиотеки, то мы просто подключим файл его класса к советнику:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart59.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Ticks\DataTick.mqh>

В области глобальных переменных советника вместо массивов параметров пользовательских индикаторов

//--- Массивы параметров пользовательских индикаторов
MqlParam       param_ma1[];
MqlParam       param_ma2[];
//+------------------------------------------------------------------+

объявим объект класса "Новый тик" — он нам нужен будет для имитации работы в составе будущих классов-коллекций тиковых данных библиотеки:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pressed_pending_buy;
bool           pressed_pending_buy_limit;
bool           pressed_pending_buy_stop;
bool           pressed_pending_buy_stoplimit;
bool           pressed_pending_close_buy;
bool           pressed_pending_close_buy2;
bool           pressed_pending_close_buy_by_sell;
bool           pressed_pending_sell;
bool           pressed_pending_sell_limit;
bool           pressed_pending_sell_stop;
bool           pressed_pending_sell_stoplimit;
bool           pressed_pending_close_sell;
bool           pressed_pending_close_sell2;
bool           pressed_pending_close_sell_by_buy;
bool           pressed_pending_delete_all;
bool           pressed_pending_close_all;
bool           pressed_pending_sl;
bool           pressed_pending_tp;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         array_used_symbols[];
string         array_used_periods[];
bool           testing;
uchar          group1;
uchar          group2;
double         g_point;
int            g_digits;

//--- Объект "Новый тик"
CNewTickObj    check_tick;
//+------------------------------------------------------------------+

В обработчике OnInit() удалим блок создания индикаторов:

//--- Создание индикаторов
   ArrayResize(param_ma1,4);
   //--- Имя индикатора 1
   param_ma1[0].type=TYPE_STRING;
   param_ma1[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma1[1].type=TYPE_INT;
   param_ma1[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma1[2].type=TYPE_INT;
   param_ma1[2].integer_value=0;
   //--- Метод сглаживания
   param_ma1[3].type=TYPE_INT;
   param_ma1[3].integer_value=MODE_SMA;
   //--- Создаём индикатор 1
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,1,INDICATOR_GROUP_TREND,param_ma1);
   
   ArrayResize(param_ma2,5);
   //--- Имя индикатора 2
   param_ma2[0].type=TYPE_STRING;
   param_ma2[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Период расчёта
   param_ma2[1].type=TYPE_INT;
   param_ma2[1].integer_value=13;
   //--- Горизонтальное смещение
   param_ma2[2].type=TYPE_INT;
   param_ma2[2].integer_value=0;
   //--- Метод сглаживания
   param_ma2[3].type=TYPE_INT;
   param_ma2[3].integer_value=MODE_SMA;
   //--- Цена расчёта
   param_ma2[4].type=TYPE_INT;
   param_ma2[4].integer_value=PRICE_OPEN;
   //--- Создаём индикатор 2
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,1,INDICATOR_GROUP_TREND,param_ma2);
   
   //--- Создаём индикатор 3
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA1);
   //--- Создаём индикатор 4
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA2,14);
   
   //--- Выводим описания созданных индикаторов
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();

В самом конце OnInit() установим объекту "Новый тик" текущий символ в качестве рабочего:

//--- Установим текущий символ объекту "Новый тик"
   check_tick.SetSymbol(Symbol());
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

В обработчике OnTick() советника впишем блок кода для определения нового тика (в качестве имитации работы в составе будущего класса-коллекции тиков) и создания нового объекта тиковых данных с выводом его описания на график и в журнал:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Обработка события NewTick в библиотеке
   engine.OnTick(rates_data);

//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Работа в таймере
      PressButtonsControl();        // Контроль нажатия кнопок
      engine.EventsHandling();      // Работа с событиями
     }

//--- Создаём временный список для хранения объектов "Данные тика",
//--- переменную для получения данных тика и
//--- переменную для подсчёта поступающих тиков
   static int tick_count=0;
   CArrayObj list;
   MqlTick tick_struct;
//--- Проверяем новый тик на текущем символе
   if(check_tick.IsNewTick())
     {
      //--- Если цены получить не удалось - уходим
      if(!SymbolInfoTick(Symbol(),tick_struct))
         return;
      //--- Создаём новый объект тиковых данных
      CDataTick *tick_obj=new CDataTick(Symbol(),tick_struct);
      if(tick_obj==NULL)
         return;
      //--- Увеличиваем счётчик тиков (просто для вывода на экран - другого смысла нету)
      tick_count++;
      //--- Ограничиваем количество тиков в подсчёте ста тысячами (тоже без какого-либо смысла)
      if(tick_count>100000) tick_count=1;
      //--- Выводим в комментарии на графике номер тика и его краткое описание
      Comment("--- №",IntegerToString(tick_count,5,'0'),": ",tick_obj.Header());
      //--- Если это первый тик (следующий за первым запуском советника), выведем его полное описание в журнал
      if(tick_count==1)
         tick_obj.Print();
      //--- Если созданный объект тиковых данных не удалось разместить в списке - удалим его
      if(!list.Add(tick_obj))
         delete tick_obj;
     }
   
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
  }
//+------------------------------------------------------------------+

Вся логика подробно расписана в комментариях в листинге и, думаю, там всё понятно и просто.

Скомпилируем советник и запустим его на графике, предварительно выставив в настройках использовать текущие символ и таймфрейм. После запуска и прихода нового тика, в журнал будет выведено описание объекта тиковых данных (пришедшего тика):

Счёт 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10426.13 USD, 1:100, Hedge, Демонстрационный счёт MetaTrader 5
--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом: "EURUSD"
Работа только с текущим таймфреймом: H1
Таймсерия символа EURUSD: 
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5153
Время инициализации библиотеки: 00:00:00.000
============= Начало списка параметров (Тик "EURUSD" 2020.12.16 13:22:32.822) =============
Время последнего обновления цен в миллисекундах: 2020.12.16 13:22:32.822
Время последнего обновления цен: 2020.12.16 13:22:32
Объем для текущей цены Last: 0
Флаги: 6
Изменённые данные на тике:
 - Изменение цены Ask
 - Изменение цены Bid
------
Цена Bid: 1.21927
Цена Ask: 1.21929
Цена Last: 0.00000
Объем для текущей цены Last c повышенной точностью: 0.00
Спред: 0.00002
------
Символ: "EURUSD"
============= Конец списка параметров (Тик "EURUSD" 2020.12.16 13:22:32.822) =============

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



Что дальше

В следующей статье начнём создавать коллекцию тиковых данных для одного символа.

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

К содержанию

Статьи этой серии:

Работа с таймсериями в библиотеке DoEasy (Часть 35): Объект "Бар" и список-таймсерия символа
Работа с таймсериями в библиотеке DoEasy (Часть 36): Объект таймсерий всех используемых периодов символа
Работа с таймсериями в библиотеке DoEasy (Часть 37): Коллекция таймсерий - база данных таймсерий по символам и периодам
Работа с таймсериями в библиотеке DoEasy (Часть 38): Коллекция таймсерий - реалтайм обновление и доступ к данным из программы
Работа с таймсериями в библиотеке DoEasy (Часть 39): Индикаторы на основе библиотеки - подготовка данных и события таймсерий
Работа с таймсериями в библиотеке DoEasy (Часть 40): Индикаторы на основе библиотеки - реалтайм обновление данных
Работа с таймсериями в библиотеке DoEasy (Часть 41): Пример мультисимвольного мультипериодного индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 42): Класс объекта абстрактного индикаторного буфера
Работа с таймсериями в библиотеке DoEasy (Часть 43): Классы объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 44): Класс-коллекция объектов индикаторных буферов
Работа с таймсериями в библиотеке DoEasy (Часть 45): Мультипериодные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 46): Мультипериодные, мультисимвольные индикаторные буферы
Работа с таймсериями в библиотеке DoEasy (Часть 47): Мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 48): Мультипериодные мультисимвольные индикаторы на одном буфере в подокне
Работа с таймсериями в библиотеке DoEasy (Часть 49): Мультипериодные мультисимвольные многобуферные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 50): Мультипериодные мультисимвольные стандартные индикаторы со смещением
Работа с таймсериями в библиотеке DoEasy (Часть 51): Составные мультипериодные мультисимвольные стандартные индикаторы
Работа с таймсериями в библиотеке DoEasy (Часть 52): Кроссплатформенность мультипериодных мультисимвольных однобуферных стандартных индикаторов
Работа с таймсериями в библиотеке DoEasy (Часть 53): Класс абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 54): Классы-наследники абстрактного базового индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов
Работа с таймсериями в библиотеке DoEasy (Часть 56): Объект пользовательского индикатора, получение данных от объектов-индикаторов в коллекции
Работа с таймсериями в библиотеке DoEasy (Часть 57): Объект данных буфера индикатора
Работа с таймсериями в библиотеке DoEasy (Часть 58): Таймсерии данных буферов индикаторов