Понравилась статья?
Поставьте ссылку на нее -
пусть другие почитают
Используй новые возможности MetaTrader 5

Работа с ценами в библиотеке DoEasy (Часть 64): Стакан цен, классы объекта-снимка и объекта-серии снимков стакана цен

12 февраля 2021, 10:17
Artyom Trishkin
0
1 230

Содержание


Концепция

В прошлой статье мы создали класс объекта абстрактной заявки стакана цен и классы-наследники этого объекта. Совокупность этих объектов составляет один снимок стакана цен, получаемый за один вызов функции MarketBookGet() в момент срабатывания обработчика OnBookEvent(). Данные, получаемые функцией MarketBookGet(), записываются в массив структур MqlBookInfo, и на основании полученных данных мы можем создать объект-снимок стакана цен, в котором будут храниться все заявки стакана цен, полученные в массив структур MqlBookInfo. Иными словами — данные в озвученном массиве структур и будут являться снимком стакана цен, в котором каждый член структуры будет представлен одним объектом-заявкой стакана цен. Каждое очередное срабатывание обработчика OnBookEvent() будет приводить к созданию очередного снимка стакана цен, которые в свою очередь мы будем заносить в объект класса серии снимков стакана цен.
Списком для хранения объектов-снимков стакана цен будет являться класс динамического массива указателей на экземпляры класса CObject и его наследников стандартной библиотеки. Размер этого списка будем ограничивать заданным количеством объектов. По умолчанию размер этого списка не будет превышать 200 000 объектов-снимков стакана цен, что должно охватить примерно один-два торговых дня. Такие списки-серии снимков стакана цен будут создаваться для каждого используемого в программе символа. В итоге, каждый такой список будет храниться в коллекции данных стакана цен.

Сегодня мы создадим два класса — класс объекта-снимка стакана цен одного символа и класс-серию снимков стакана цен одного символа. В следующей статье создадим и протестируем класс-коллекцию таких серий-снимков стакана цен.


Доработка классов библиотеки

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

//--- CMarketBookOrd
   MSG_MBOOK_ORD_TEXT_MBOOK_ORD,                      // Заявка в стакане цен
   MSG_MBOOK_ORD_VOLUME,                              // Объем
   MSG_MBOOK_ORD_VOLUME_REAL,                         // Объем c повышенной точностью
   MSG_MBOOK_ORD_STATUS_BUY,                          // Сторона Buy
   MSG_MBOOK_ORD_STATUS_SELL,                         // Сторона Sell
   MSG_MBOOK_ORD_TYPE_SELL,                           // Заявка на продажу
   MSG_MBOOK_ORD_TYPE_BUY,                            // Заявка на покупку 
   MSG_MBOOK_ORD_TYPE_SELL_MARKET,                    // Заявка на продажу по рыночной цене
   MSG_MBOOK_ORD_TYPE_BUY_MARKET,                     // Заявка на покупку по рыночной цене

//--- CMarketBookSnapshot
   MSG_MBOOK_SNAP_TEXT_SNAPSHOT,                      // Снимок стакана цен
   
//--- CMBookSeries
   MSG_MBOOK_SERIES_TEXT_MBOOKSERIES,                 // Серия снимков стакана цен
   
  };
//+------------------------------------------------------------------+

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

//--- CMarketBookOrd
   {"Заявка в стакане цен","Order in Depth of Market"},
   {"Объем","Volume"},
   {"Объем c повышенной точностью","Volume Real"},
   {"Сторона Buy","Buy side"},
   {"Сторона Sell","Sell side"},
   {"Заявка на продажу","Sell order"},
   {"Заявка на покупку","Buy order"},
   {"Заявка на продажу по рыночной цене","Sell order at market price"},
   {"Заявка на покупку по рыночной цене","Buy order at market price"},
   
//--- CMarketBookSnapshot
   {"Снимок стакана цен","Depth of Market Snapshot"},
   
//--- CMBookSeries
   {"Серия снимков стакана цен","Series of shots of the Depth of Market"},
   
  };
//+---------------------------------------------------------------------+

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

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

//--- Параметры тиковых серий
#define TICKSERIES_DEFAULT_DAYS_COUNT  (1)                        // Требуемое количество дней для тиковых данных в сериях по умолчанию
#define TICKSERIES_MAX_DATA_TOTAL      (200000)                   // Максимальное количество хранимых тиковых данных одного символа
//--- Параметры серий снимков стакана цен
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // Требуемое количество дней для снимков стакана цен в сериях по умолчанию
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Максимальное количество хранимых снимков стакана цен одного символа
//+------------------------------------------------------------------+

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

В этом же файле впишем новое целочисленное свойство объекта-заявки стакана цен — время в миллисекундах,
и увеличим количество целочисленных свойств объекта до 4:

//+------------------------------------------------------------------+
//| Целочисленные свойства заявки стакана цен                        |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_ORD_PROP_INTEGER
  {
   MBOOK_ORD_PROP_STATUS = 0,                         // Статус заявки
   MBOOK_ORD_PROP_TYPE,                               // Тип заявки
   MBOOK_ORD_PROP_VOLUME,                             // Объём заявки
   MBOOK_ORD_PROP_TIME_MSC,                           // Время снятия снимка стакана цен в миллисекундах
  }; 
#define MBOOK_ORD_PROP_INTEGER_TOTAL (4)              // Общее количество целочисленных свойств
#define MBOOK_ORD_PROP_INTEGER_SKIP  (0)              // Количество неиспользуемых в сортировке целочисленных свойств стакана
//+------------------------------------------------------------------+

Так как мы добавили новое целочисленное свойство, то нам нужно, соответственно, добавить и новый критерий сортировки по целочисленным свойствам:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки заявок стакана цен                 |
//+------------------------------------------------------------------+
#define FIRST_MB_DBL_PROP  (MBOOK_ORD_PROP_INTEGER_TOTAL-MBOOK_ORD_PROP_INTEGER_SKIP)
#define FIRST_MB_STR_PROP  (MBOOK_ORD_PROP_INTEGER_TOTAL-MBOOK_ORD_PROP_INTEGER_SKIP+MBOOK_ORD_PROP_DOUBLE_TOTAL-MBOOK_ORD_PROP_DOUBLE_SKIP)
enum ENUM_SORT_MBOOK_ORD_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_MBOOK_ORD_STATUS = 0,                      // Сортировать по статусу заявки
   SORT_BY_MBOOK_ORD_TYPE,                            // Сортировать по типу заявки
   SORT_BY_MBOOK_ORD_VOLUME,                          // Сортировать по объёму заявки
   SORT_BY_MBOOK_ORD_TIME_MSC,                        // Сортировать по времени снятия снимка стакана цен в миллисекундах
//--- Сортировка по вещественным свойствам
   SORT_BY_MBOOK_ORD_PRICE = FIRST_MB_DBL_PROP,       // Сортировать по цене заявки
   SORT_BY_MBOOK_ORD_VOLUME_REAL,                     // Сортировать по объёму заявки с повышенной точностью
//--- Сортировка по строковым свойствам
   SORT_BY_MBOOK_ORD_SYMBOL = FIRST_MB_STR_PROP,      // Сортировать по имени символа
  };
//+------------------------------------------------------------------+

Именно эту константу и будем указывать как параметр, по которому необходимо сортировать объекты-снимки стакана цен в создаваемом сейчас новом классе объекта-серии снимков стакана цен.

И здесь мы приходим к необходимости создания методов для поиска и сортировки объектов-заявок стакана цен в файле класса CSelect, хранящегося по адресу \MQL5\Include\DoEasy\Services\Select.mqh. Подробное описание которого было написано нами в третьей статье. Сейчас же мы просто опишем все необходимые доработки этого класса для организации поиска и сортировки по свойствам объектов-заявок стакана цен.

Подключим к файлу класс объекта-абстрактной заявки стакана цен:

//+------------------------------------------------------------------+
//|                                                       Select.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"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
#include "..\Objects\Indicators\DataInd.mqh"
#include "..\Objects\Ticks\DataTick.mqh"
#include "..\Objects\Book\MarketBookOrd.mqh"
//+------------------------------------------------------------------+

В конце тела класса объявим все необходимые методы:

//+------------------------------------------------------------------+
//| Методы работы с данными стакана цен                              |
//+------------------------------------------------------------------+
   //--- Возвращает список данных стакана цен, где одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс данных стакана цен в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER property);
   static int        FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property);
   static int        FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING property);
   //--- Возвращает индекс данных стакана цен в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства
   static int        FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER property);
   static int        FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property);
   static int        FindMBookMin(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

А за пределами тела класса напишем их реализацию:

//+------------------------------------------------------------------+
//| Методы работы с данными стакана цен                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список данных стакана цен, где одно из целочисленных  |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список данных стакана цен, где одно из вещественных   |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает список данных стакана цен, где одно из строковых      |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByMBookProperty(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с максимальным значением целочисленного свойства                 |
//+------------------------------------------------------------------+
int CSelect::FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMarketBookOrd *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с максимальным значением вещественного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMarketBookOrd *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с максимальным значением строкового свойства                     |
//+------------------------------------------------------------------+
int CSelect::FindMBookMax(CArrayObj *list_source,ENUM_MBOOK_ORD_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CMarketBookOrd *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с минимальным значением целочисленного свойства                  |
//+------------------------------------------------------------------+
int CSelect::FindMBookMin(CArrayObj* list_source,ENUM_MBOOK_ORD_PROP_INTEGER property)
  {
   int index=0;
   CMarketBookOrd *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с минимальным значением вещественного свойства                   |
//+------------------------------------------------------------------+
int CSelect::FindMBookMin(CArrayObj* list_source,ENUM_MBOOK_ORD_PROP_DOUBLE property)
  {
   int index=0;
   CMarketBookOrd *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Возвращает индекс данных стакана цен в списке                    |
//| с минимальным значением строкового свойства                      |
//+------------------------------------------------------------------+
int CSelect::FindMBookMin(CArrayObj* list_source,ENUM_MBOOK_ORD_PROP_STRING property)
  {
   int index=0;
   CMarketBookOrd *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CMarketBookOrd *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

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

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

Для этого впишем в файле класса абстрактной заявки стакана цен \MQL5\Include\DoEasy\Objects\Book\MarketBookOrd.mqh новый публичный метод:

public:
//+------------------------------------------------------------------+ 
//|Методы упрощённого доступа к свойствам объекта-запроса стакана цен|
//+------------------------------------------------------------------+
//--- Устанавливает время снимка - все заявки одного снимка стакана цен имеют одинаковое время
   void              SetTime(const long time_msc)  { this.SetProperty(MBOOK_ORD_PROP_TIME_MSC,time_msc);                      }
   
//--- Возвращает (1) статус, (2) тип, (3) объём заявки

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

В параметрическом конструкторе класса абстрактной заявки стакана цен инициализируем нулём время получения снимка:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CMarketBookOrd::CMarketBookOrd(const ENUM_MBOOK_ORD_STATUS status,const MqlBookInfo &book_info,const string symbol)
  {
//--- Сохраняем Digits символа
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
//--- Сохраняем целочисленные свойства объекта
   this.SetProperty(MBOOK_ORD_PROP_STATUS,status);
   this.SetProperty(MBOOK_ORD_PROP_TYPE,book_info.type);
   this.SetProperty(MBOOK_ORD_PROP_VOLUME,book_info.volume);
//--- Сохраняем вещественные свойства объекта
   this.SetProperty(MBOOK_ORD_PROP_PRICE,book_info.price);
   this.SetProperty(MBOOK_ORD_PROP_VOLUME_REAL,book_info.volume_real);
//--- Сохраняем дополнительные свойства объекта
   this.SetProperty(MBOOK_ORD_PROP_SYMBOL,(symbol==NULL || symbol=="" ? ::Symbol() : symbol));
//--- Время заявки отсутствует в параметрах, и оно учитывается в классе снимка стакана цен. Обнуляем время
   this.SetProperty(MBOOK_ORD_PROP_TIME_MSC,0);
  }
//+------------------------------------------------------------------+

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

Также в файле класса абстрактной заявки стакана цен  MarketBookOrd.mqh и в файлах классов его наследников MarketBookBuy.mqh, MarketBookBuyMarket.mqh, MarketBookSell.mqh и MarketBookSellMarket.mqh были внесены небольшие косметические изменения в виртуальные методы описания объектов:

//--- Выводит в журнал краткое описание объекта
   virtual void      PrintShort(const bool symbol=false);
//--- Возвращает краткое наименование объекта
   virtual string    Header(const bool symbol=false);

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

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

Для класса CMarketBookOrd:

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта                          |
//+------------------------------------------------------------------+
string CMarketBookOrd::Header(const bool symbol=false)
  {
   return this.TypeDescription()+(symbol ? " \""+this.Symbol()+"\"" : "");
  }
//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание объекта                        |
//+------------------------------------------------------------------+
void CMarketBookOrd::PrintShort(const bool symbol=false)
  {
   ::Print(this.Header(symbol));
  }
//+------------------------------------------------------------------+

Для класса CMarketBookBuy, CMarketBookBuyMarket, CMarketBookSell и CMarketBookSellMarket:

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта                          |
//+------------------------------------------------------------------+
string CMarketBookBuy::Header(const bool symbol=false)
  {
   return CMessage::Text(MSG_MBOOK_ORD_TYPE_BUY)+(symbol ? " \""+this.Symbol() : "")+
          ": "+::DoubleToString(this.Price(),this.Digits())+" ["+::DoubleToString(this.VolumeReal(),2)+"]";
  }
//+------------------------------------------------------------------+

Для класса CMarketBookBuyMarket:

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта                          |
//+------------------------------------------------------------------+
string CMarketBookBuyMarket::Header(const bool symbol=false)
  {
   return CMessage::Text(MSG_MBOOK_ORD_TYPE_BUY_MARKET)+(symbol ? " \""+this.Symbol() : "")+
          ": "+::DoubleToString(this.Price(),this.Digits())+" ["+::DoubleToString(this.VolumeReal(),2)+"]";
  }
//+------------------------------------------------------------------+

Для класса CMarketBookSell:

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта                          |
//+------------------------------------------------------------------+
string CMarketBookSell::Header(const bool symbol=false)
  {
   return CMessage::Text(MSG_MBOOK_ORD_TYPE_SELL)+(symbol ? " \""+this.Symbol() : "")+
          ": "+::DoubleToString(this.Price(),this.Digits())+" ["+::DoubleToString(this.VolumeReal(),2)+"]";
  }
//+------------------------------------------------------------------+

Для класса CMarketBookSellMarket:

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта                          |
//+------------------------------------------------------------------+
string CMarketBookSellMarket::Header(const bool symbol=false)
  {
   return CMessage::Text(MSG_MBOOK_ORD_TYPE_SELL_MARKET)+(symbol ? " \""+this.Symbol() : "")+
          ": "+::DoubleToString(this.Price(),this.Digits())+" ["+::DoubleToString(this.VolumeReal(),2)+"]";
  }
//+------------------------------------------------------------------+

Соответственно, в объявлении этих методов во всех классах-наследниках были добавлены флаги:

//--- Возвращает краткое наименование объекта
   virtual string    Header(const bool symbol=false);

По умолчанию символ в описании объекта выводиться не будет.


Класс объекта-снимка стакана цен

Итак, у нас всё готово для создания класса объекта-снимка стакана цен. По сути — это список заявок стакана цен, полученных в массив структур MqlBookInfo при срабатывании обработчика OnBookEvent(). Только каждая из заявок, находящаяся в массиве, в данном классе будет представлена объектом класса CMarketBookOrd — его наследниками. Все они будут складываться в список CArrayObj, являющийся классом динамического массива указателей на экземпляры класса CObject и его наследников стандартной библиотеки. Помимо списка, в котором будут храниться объекты-заявки стакана цен, класс будет предоставлять стандартные для всех объектов библиотеки возможности работы с этими объектами и их списками — поиску и сортировке по различным их свойствам — для удобного сбора любых статистических данных в работе со стаканом цен в своих программах.

В папке библиотеки \MQL5\Include\DoEasy\Objects\Book\ создадим новый файл MarketBookSnapshot.mqh класса CMBookSnapshot.
Базовым классом должен быть класс базового объекта всех объектов библиотеки CBaseObj
.
К файлу должны быть подключены файлы классов объектов-наследников объекта абстрактной заявки стакана цен
.

Рассмотрим листинг класса и реализацию его методов:

//+------------------------------------------------------------------+
//|                                           MarketBookSnapshot.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\..\Services\Select.mqh"
#include "MarketBookBuy.mqh"
#include "MarketBookSell.mqh"
#include "MarketBookBuyMarket.mqh"
#include "MarketBookSellMarket.mqh"
//+------------------------------------------------------------------+
//| Класс "Снимок стакана цен"                                       |
//+------------------------------------------------------------------+
class CMBookSnapshot : public CBaseObj
  {
private:
   string            m_symbol;                  // Символ
   long              m_time;                    // Время снимка
   int               m_digits;                  // Digits символа
   CArrayObj         m_list;                    // Список объектов-заявок стакана цен
public:
//--- Возвращает (1) себя, (2) список объектов-заявок стакана цен
   CMBookSnapshot   *GetObject(void)                                    { return &this;   }
   CArrayObj        *GetList(void)                                      { return &m_list; }

//--- Возвращает список объектов-заявок стакана цен по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }
//--- (1) Возвращает объект-заявку стакана цен по индексу в списке, (2) размер списка заявок
   CMarketBookOrd   *GetMBookByListIndex(const uint index)              { return this.m_list.At(index);  }
   int               DataTotal(void)                              const { return this.m_list.Total();    }

//--- Метод сравнения для поиска и сортировки по времени объектов снимков стакана цен
   virtual int       Compare(const CObject *node,const int mode=0) const 
                       {   
                        const CMBookSnapshot *compared_obj=node;
                        return(this.Time()<compared_obj.Time() ? -1 : this.Time()>compared_obj.Time() ? 1 : 0);
                       } 

//--- Возвращает наименование снимка стакана цен
   string            Header(void);
//--- Выводит в журнал (1) описание, (2) краткое описание снимка стакана цен
   void              Print(void);
   void              PrintShort(void);

//--- Конструкторы
                     CMBookSnapshot(){;}
                     CMBookSnapshot(const string symbol,const long time,MqlBookInfo &book_array[]);
//+------------------------------------------------------------------+ 
//|Методы упрощённого доступа к свойствам объекта-снимка стакана цен |
//+------------------------------------------------------------------+
//--- Устанавливает (1) символ, (2) время снимка стакана цен, (3) указанное время всем заявкам в стакане
   void              SetSymbol(const string symbol)   { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); }
   void              SetTime(const long time_msc)     { this.m_time=time_msc; }
   void              SetTimeToOrders(const long time_msc);
//--- Возвращает (1) символ стакана цен, (2) Digits символа, (3) время снимка
   string            Symbol(void)               const { return this.m_symbol; }
   int               Digits(void)               const { return this.m_digits; }
   long              Time(void)                 const { return this.m_time;   }
  };
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CMBookSnapshot::CMBookSnapshot(const string symbol,const long time,MqlBookInfo &book_array[]) : m_time(time)
  {
   //--- Устанавливаем символ
   this.SetSymbol(symbol);
   //--- Очищаем список
   this.m_list.Clear();
   //--- В цикле по массиву структур
   int total=::ArraySize(book_array);
   for(int i=0;i<total;i++)
     {
      //--- Создаём объекты-заявки текущего снимка стакана цен в зависимости от типа заявки
      CMarketBookOrd *mbook_ord=NULL;
      switch(book_array[i].type)
        {
         case BOOK_TYPE_BUY         : mbook_ord=new CMarketBookBuy(this.m_symbol,book_array[i]);         break;
         case BOOK_TYPE_SELL        : mbook_ord=new CMarketBookSell(this.m_symbol,book_array[i]);        break;
         case BOOK_TYPE_BUY_MARKET  : mbook_ord=new CMarketBookBuyMarket(this.m_symbol,book_array[i]);   break;
         case BOOK_TYPE_SELL_MARKET : mbook_ord=new CMarketBookSellMarket(this.m_symbol,book_array[i]);  break;
         default: break;
        }
      if(mbook_ord==NULL)
         continue;
      //--- Устанавливаем заявке время снимка стакана цен
      mbook_ord.SetTime(this.m_time);
      //--- Ставим списку флаг сортированного списка (по значению цены) и добавляем в него текущий объект-заявку
      this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE);
      if(!this.m_list.InsertSort(mbook_ord))
         delete mbook_ord;
     }
  }
//+------------------------------------------------------------------+

Метод, возвращающий краткое наименование объекта-снимка стакана цен:

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

Здесь просто создаётся строка из текстового сообщения с описанием объекта и символа, котрая выглядит примерно так:

Снимок стакана цен "EURUSD"

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

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

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

Снимок стакана цен "EURUSD" (2021.02.09 22:16:24.557)

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

//+------------------------------------------------------------------+
//| Выводит в журнал свойства объекта                                |
//+------------------------------------------------------------------+
void CMBookSnapshot::Print(void)
  {
   ::Print(this.Header()," ("+TimeMSCtoString(this.m_time),"):");
   this.m_list.Sort(SORT_BY_MBOOK_ORD_PRICE);
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CMarketBookOrd *ord=this.m_list.At(i);
      if(ord==NULL)
         continue;
      ::Print(" - ",ord.Header());
     }
  }
//+------------------------------------------------------------------+

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

Так как объекты-заявки, находящиеся в списке объекта-снимка стакана цен, не имеют собственной возможности получить их время, то в данном классе напишем метод, устанавливающий всем объектам-заявкам в списке время в миллисекундах, записанное в данном объекте:

//+------------------------------------------------------------------+
//| Устанавливает указанное время всем заявкам в стакане             |
//+------------------------------------------------------------------+
void CMBookSnapshot::SetTimeToOrders(const long time_msc)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CMarketBookOrd *ord=this.m_list.At(i);
      if(ord==NULL)
         continue;
      ord.SetTime(time_msc);
     }
  }
//+------------------------------------------------------------------+

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

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

Класс объекта-серии снимков стакана цен

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

В папке библиотеки \MQL5\Include\DoEasy\Objects\Book\ создадим новый файл MBookSeries.mqh класса CMBookSeries.
Базовым классом должен быть класс базового объекта всех объектов библиотеки CBaseObj
.
К файлу должен быть подключен файл класса объекта-снимка стакана цен
.

Рассмотрим листинг класса, а затем разберём его методы:

//+------------------------------------------------------------------+
//|                                                  MBookSeries.mqh |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "MarketBookSnapshot.mqh"
//+------------------------------------------------------------------+
//| Класс "Серия снимков стакана цен"                                |
//+------------------------------------------------------------------+
class CMBookSeries : public CBaseObj
  {
private:
   string            m_symbol;                                          // Символ
   uint              m_amount;                                          // Количество используемых снимков стакана цен в серии
   uint              m_required;                                        // Требуемое количество дней для серии снимков стакана цен
   CArrayObj         m_list;                                            // Список-серия снимков стакана цен
   MqlBookInfo       m_book_info[];                                     // Массив структур стакана цен
public:
//--- Возвращает (1) себя, (2) список-серию
   CMBookSeries     *GetObject(void)                                    { return &this;   }
   CArrayObj        *GetList(void)                                      { return &m_list; }

//--- Возвращает список снимков стакана по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }
   CArrayObj        *GetList(ENUM_MBOOK_ORD_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByMBookProperty(this.GetList(),property,value,mode); }

//--- Возвращает объект-снимок стакана цен по (1) индексу в списке, (2) по времени, (3) реальный размер списка
   CMBookSnapshot   *GetMBookByListIndex(const uint index)        const { return this.m_list.At(index);              }
   CMBookSnapshot   *GetLastMBook(void)                           const { return this.m_list.At(this.DataTotal()-1); }
   CMBookSnapshot   *GetMBook(const long time_msc); 
   int               DataTotal(void)                              const { return this.m_list.Total();                }

//--- Устанавливает (1) символ, (2) количество дней для снимков стакана цен
   void              SetSymbol(const string symbol);
   void              SetRequiredUsedDays(const uint required=0);

//--- Метод сравнения для поиска и сортировки по символу объектов серий снимков стакана цен
   virtual int       Compare(const CObject *node,const int mode=0) const 
                       {   
                        const CMBookSeries *compared_obj=node;
                        return(this.Symbol()==compared_obj.Symbol() ? 0 : this.Symbol()>compared_obj.Symbol() ? 1 : -1);
                       } 
//--- Возвращает наименование серии снимков стакана цен
   string            Header(void);
//--- Выводит в журнал (1) описание, (2) краткое описание серии снимков стакана цен
   void              Print(void);
   void              PrintShort(void);

//--- Конструкторы
                     CMBookSeries(){;}
                     CMBookSeries(const string symbol,const uint required=0);

//+------------------------------------------------------------------+ 
//| Методы работы с объектами и доступа к их свойствам               |
//+------------------------------------------------------------------+
//--- Возвращает (1) символ, количество (2) используемых, (3) запрошенных снимков стакана в серии,
//--- (4) время указанного по индексу снимка стакана цен в миллисекундах
   string            Symbol(void)                                          const { return this.m_symbol;    }
   ulong             AvailableUsedData(void)                               const { return this.m_amount;    }
   ulong             RequiredUsedDays(void)                                const { return this.m_required;  }
   long              MBookTime(const int index) const;
//--- обновляет список-серию снимков стакана цен
   bool              Refresh(const long time_msc);
  };
//+------------------------------------------------------------------+

Опять-таки, что мы здесь видим:

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

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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CMBookSeries::CMBookSeries(const string symbol,const uint required=0) : m_symbol(symbol)
  {
   this.m_list.Clear();
   this.m_list.Sort(SORT_BY_MBOOK_ORD_TIME_MSC);
   this.SetRequiredUsedDays(required);
  }
//+------------------------------------------------------------------+

Метод, обновляющий список снимков стакана цен:

//+------------------------------------------------------------------+
//| Обновляет список-серию снимков стакана цен                       |
//+------------------------------------------------------------------+
bool CMBookSeries::Refresh(const long time_msc)
  {
//--- Получаем записи стакана цен в массив структур
   if(!::MarketBookGet(this.m_symbol,this.m_book_info))
      return false;
//--- Создаём новый объект-снимок стакана цен
   CMBookSnapshot *book=new CMBookSnapshot(this.m_symbol,time_msc,this.m_book_info);
   if(book==NULL)
      return false;
//--- Ставим списку флаг сортированного списка (по времени) и добавляем в него созданный снимок стакана цен
   this.m_list.Sort(SORT_BY_MBOOK_ORD_TIME_MSC);
   if(!this.m_list.InsertSort(book))
     {
      delete book;
      return false;
     }
//--- Устанавливаем время в миллисекундах всем объектам-заявкам снимка стакана цен
   book.SetTimeToOrders(time_msc);
//--- Если количество снимков в списке превысило заданное по умолчанию максимальное их количество -
//--- удаляем рассчитанное количество объектов-снимков с конца списка
   if(this.DataTotal()>MBOOKSERIES_MAX_DATA_TOTAL)
     {
      int total_del=this.m_list.Total()-MBOOKSERIES_MAX_DATA_TOTAL;
      for(int i=0;i<total_del;i++)
         this.m_list.Delete(i);
     }
   return true;
  }
//+------------------------------------------------------------------+

Метод будет вызываться при срабатывании обработчика OnBookEvent(). Соответственно, в метод передаётся время срабатывания обработчика, при помощи MarketBookGet() получаем массив структур стакана цен, на основе этой структуры создаём новый объект-снимок стакана цен и добавляем его в список-серию снимков.
Вся логика подробно расписана в комментариях к коду метода и, надеюсь, она понятна.

Метод для установки наименования символа списку-серии снимков:

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

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

Метод, устанавливающий количество дней для серии снимков стакана цен:

//+------------------------------------------------------------------+
//| Устанавливает количество дней для снимков стакана в серии        |
//+------------------------------------------------------------------+
void CMBookSeries::SetRequiredUsedDays(const uint required=0)
  {
   this.m_required=(required<1 ? MBOOKSERIES_DEFAULT_DAYS_COUNT : required);
  }
//+------------------------------------------------------------------+

Если передано нулевое значение, то устанавливается количество дней, заданное по умолчанию, иначе — переданное в метод. Пока этот метод нигде не используется.

Метод, возвращающий объект-снимок стакана цен по указанному времени:

//+------------------------------------------------------------------+
//| Возвращает объект-снимок стакана цен по его времени              |
//+------------------------------------------------------------------+
CMBookSnapshot *CMBookSeries::GetMBook(const long time_msc)
  {
   CMBookSnapshot *book=new CMBookSnapshot();
   if(book==NULL)
      return NULL;
   book.SetTime(time_msc);
   this.m_list.Sort();
   int index=this.m_list.Search(book);
   delete book;
   return this.m_list.At(index);
  }
//+------------------------------------------------------------------+

Здесь: создаём временный объект-снимок стакана цен, устанавливаем ему требуемое время, флаг сортированного списка, и при помощи метода Search() получаем индекс объекта в списке со временем, равным искомому. Обязательно удаляем временный объект и возвращаем указатель на найденный объект по индексу в списке.

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

//+------------------------------------------------------------------+
//| Возвращает время в миллисекундах                                 |
//| указанного по индексу снимка стакана цен                         |
//+------------------------------------------------------------------+
long CMBookSeries::MBookTime(const int index) const
  {
   CMBookSnapshot *book=this.m_list.At(index);
   return(book!=NULL ? book.Time() : 0);
  }
//+------------------------------------------------------------------+

Получаем указатель на объект-снимок стакана цен по указанному индексу и возвращаем его время в миллисекундах, либо NULL в случае неудачи.

Метод, возвращающий наименование серии снимков стакана цен:

//+------------------------------------------------------------------+
//| Возвращает наименование серии снимков стакана цен                |
//+------------------------------------------------------------------+
string CMBookSeries::Header(void)
  {
   return CMessage::Text(MSG_MBOOK_SERIES_TEXT_MBOOKSERIES)+" \""+this.m_symbol+"\"";
  }
//+------------------------------------------------------------------+

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

Серия снимков стакана цен "EURUSD"

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

//+------------------------------------------------------------------+
//| Выводит в журнал описание серии снимков стакана цен              |
//+------------------------------------------------------------------+
void CMBookSeries::Print(void)
  {
   string txt=
     (
      CMessage::Text(MSG_TICKSERIES_REQUIRED_HISTORY_DAYS)+(string)this.RequiredUsedDays()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_ACTUAL_DEPTH)+(string)this.DataTotal()
     );
   ::Print(this.Header(),": ",txt);
  }
//+------------------------------------------------------------------+

Сначала создаётся заголовок с описанием серии снимков, запрошенном количестве дней данных и фактически собранным количеством снимков стакана цен, а затем в цикле выводятся описания всех заявок, входящих в состав объекта-снимка.

На этом создание классов объекта-снимка и объекта-серии снимков стакана цен завершено.

Тестирование

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

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

Удалим из листинга советника полключение классов объектов-заявок — они теперь подключены к файлам новых классов, созданных нами сегодня:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart63.mq5 |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Book\MarketBookBuy.mqh>
#include <DoEasy\Objects\Book\MarketBookSell.mqh>
#include <DoEasy\Objects\Book\MarketBookBuyMarket.mqh>
#include <DoEasy\Objects\Book\MarketBookSellMarket.mqh>
//--- enums

И вместо них впишем подключение файла класса-серии снимков стакана цен:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart64.mq5 |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Book\MBookSeries.mqh>
//--- enums

В списке глобальных переменных советника объявим объект класса серии снимков стакана цен:

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

Вся работа по созданию списка серии снимков стакана цен у нас выполняется в обработчике OnBoolEvent():

//+------------------------------------------------------------------+
//| OnBookEvent function                                             |
//+------------------------------------------------------------------+
void OnBookEvent(const string& symbol)
  {
   static bool first=true;
   //--- Получаем объект-символ
   CSymbol *sym=engine.GetSymbolCurrent();
   //--- Если объект-символ не получили, или по нему нет подписки на стакан цен - уходим
   if(sym==NULL || !sym.BookdepthSubscription()) return;
   //--- Работаем по текущему символу
   if(symbol==sym.Name())
     {
      //--- Устанавливаем для объекта-серии снимков стакана цен символ и требуемое количество дней данных
      book_series.SetSymbol(sym.Name());
      book_series.SetRequiredUsedDays();
      //--- Обновляем серию снимков стакана цен
      if(!book_series.Refresh(sym.Time()))
         return;
      
      //--- Получаем последний объект-снимок стакана цен из объекта-серии снимков стакана цен
      CMBookSnapshot *book=book_series.GetLastMBook();
      if(book==NULL)
         return;
      //--- Получаем самый первый и самый последний объекты-заявки стакана цен из объекта-снимка стакана цен
      CMarketBookOrd *ord_0=book.GetMBookByListIndex(0);
      CMarketBookOrd *ord_N=book.GetMBookByListIndex(book.DataTotal()-1);
      if(ord_0==NULL || ord_N==NULL) return;
      //--- Выводим на график в комментарии время текущего снимка стакана цен,
      //--- максимальное количество показываемых заявок в стакане для символа,
      //--- полученное количество заявок в текущем снимке стакана цен,
      //--- общее количество снимков стакана цен, записанных в список-серию и
      //--- максимальную и минимальную по цене заявки текущего снимка стакана цен
      Comment
        (
         DFUN,sym.Name(),": ",TimeMSCtoString(book.Time()),
         ", symbol book size=",sym.TicksBookdepth(),

         ", last book data total: ",book.DataTotal(),
         ", series books total: ",book_series.DataTotal(),
         "\nMax: ",ord_N.Header(),"\nMin: ",ord_0.Header()
        );
      //--- Выведем в журнал первый снимок стакана цен
      if(first)
        {
         //--- описание серии
         book_series.Print();
         //--- описание снимка
         book.Print();
         first=false;
        }
     }
  }
//+------------------------------------------------------------------+

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

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


В журнал будут выведены данные о созданной серии снимков стакана цен и самом первом снимке:

Счёт 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10428.13 USD, 1:100, Hedge, Демонстрационный счёт MetaTrader 5
--- Инициализация библиотеки "DoEasy" ---
Работа с предопределённым списком символов. Количество используемых символов: 2
"AUDUSD" "EURUSD"
Работа только с текущим таймфреймом: H1
Таймсерия символа AUDUSD: 
- Таймсерия "AUDUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5121
Таймсерия символа EURUSD: 
- Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6046
Тиковая серия "AUDUSD": Запрошенное количество дней: 1, Создано исторических данных: 176033
Тиковая серия "EURUSD": Запрошенное количество дней: 1, Создано исторических данных: 181969
Осуществлена подписка на стакан цен  AUDUSD
Осуществлена подписка на стакан цен  EURUSD
Время инициализации библиотеки: 00:00:12.516
Серия снимков стакана цен "EURUSD": Запрошенное количество дней: 1, Фактическая глубина истории: 1
Снимок стакана цен "EURUSD" (2021.02.09 22:16:24.557):
 - Заявка на продажу: 1.21198 [250.00]
 - Заявка на продажу: 1.21193 [100.00]
 - Заявка на продажу: 1.21192 [50.00]
 - Заявка на продажу: 1.21191 [30.00]
 - Заявка на продажу: 1.21190 [6.00]
 - Заявка на покупку: 1.21188 [36.00]
 - Заявка на покупку: 1.21186 [50.00]
 - Заявка на покупку: 1.21185 [100.00]
 - Заявка на покупку: 1.21180 [250.00]

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

На рисунке отображены данные по уже работающему некоторое время советнику (5019 снимков добавлено в список)

Что дальше

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

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

К содержанию

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

Работа с ценами в библиотеке DoEasy (Часть 62): Реалтайм-обновление тиковых серий, подготовка к работе со стаканом цен
Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен

Прикрепленные файлы |
MQL5.zip (3932.83 KB)
Самоадаптирующийся алгоритм (Часть IV): Дополнительный функционал и тесты Самоадаптирующийся алгоритм (Часть IV): Дополнительный функционал и тесты
Продолжаю наполнять алгоритм минимально необходимым функционалом, проведу тесты того, что получилось. Доходность получилась невысокая, но в статьях показана модель, которая позволяет в полностью автоматическом режиме торговать в плюс по совершенно разным торговым инструментам, и не только разным, но и торгующимся на принципиально разных рынках.
Нейросети — это просто (Часть 11): Вариации на тему GPT Нейросети — это просто (Часть 11): Вариации на тему GPT
Сегодня, наверное, одной из самых передовых языковых моделей нейросетей является GPT-3, которая в максимальном своем варианте содержит 175 млрд. параметров. Конечно, мы не будем создавать подобного монстра в домашних условиях. Но давайте посмотрим, какие архитектурные решения мы можем использовать в своей работе и какие это нам даст преимущества.
Полезные и экзотические приемы для автоматической торговли Полезные и экзотические приемы для автоматической торговли
В данной статье я покажу несколько очень интересных и полезных приемов для автоматической торговли. Часть из этих приемов возможно кому-то знакома, кому-то — нет, но я постараюсь привести самые интересные методы и объяснить почему стоит ими пользоваться. Самое главное, покажу на практике, что они могут. Напишем советники и проверим все описанные приемы на истории котировок.
Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен Работа с ценами в библиотеке DoEasy (Часть 63): Стакан цен, класс абстрактной заявки стакана цен
В статье начнём разработку функционала для работы со стаканом цен. Создадим класс объекта абстрактной заявки стакана цен и его наследников.