Работа с таймсериями в библиотеке DoEasy (Часть 35): Объект "Бар" и список-таймсерия символа

Artyom Trishkin | 18 февраля, 2020

Содержание

С этой статьи мы начинаем новый раздел в описании создания библиотеки для удобного написания программ для терминалов MetaTrader5 и 4. Первая серия статей (34 части) была посвящена разработке концепции объектов библиотеки и их взаимосвязей, и на основании разработанной концепции был создан функционал для работы со счётом — его текущим состоянием и историей.

На текущий момент библиотека обладает следующим функционалом:

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

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

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

Концепция

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

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

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

Свойства объекта имеют три основных типа — целочисленные, вещественные и строковые свойства.

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

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

Основой всех объектов библиотеки является базовый объект стандартной библиотеки, поставляемой вместе с торговой платформой, а списком объектов (коллекцией объектов) является динамический массив указателей на экземпляры класса CObject и его наследников Стандартной библиотеки.

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

Каждый бар графика символа имеет определённый набор параметров, описанный в структуре MqlRates:

Помимо этих основных свойств объекта-бара мы можем задать дополнительные его свойства прямо в момент создания объекта:

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

Объект "Бар"

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

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Данные для работы с серийными данными                            |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Тип бара (тело свечи)                                            |
//+------------------------------------------------------------------+
enum ENUM_BAR_BODY_TYPE
  {
   BAR_BODY_TYPE_BULLISH,                                   // Бычий бар
   BAR_BODY_TYPE_BEARISH,                                   // Медвежий бар
   BAR_BODY_TYPE_NULL,                                      // Нулевой бар
   BAR_BODY_TYPE_CANDLE_ZERO_BODY,                          // Свеча с нулевым телом
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства бара                                      |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_INTEGER
  {
   BAR_PROP_INDEX = 0,                                      // Индекс бара в таймсерии
   BAR_PROP_TYPE,                                           // Тип бара (из перечисления ENUM_BAR_BODY_TYPE)
   BAR_PROP_PERIOD,                                         // Период бара (таймфрейм)
   BAR_PROP_SPREAD,                                         // Спред бара
   BAR_PROP_VOLUME_TICK,                                    // Тиковый объём бара
   BAR_PROP_VOLUME_REAL,                                    // Биржевой объём бара
   BAR_PROP_TIME,                                           // Время начала периода бара
   BAR_PROP_TIME_DAY_OF_YEAR,                               // Порядковый номер дня бара в году
   BAR_PROP_TIME_YEAR,                                      // Год, к которому относится бар
   BAR_PROP_TIME_MONTH,                                     // Месяц, к которому относится бар
   BAR_PROP_TIME_DAY_OF_WEEK,                               // День недели бара
   BAR_PROP_TIME_DAY,                                       // День месяца бара (число)
   BAR_PROP_TIME_HOUR,                                      // Час бара
   BAR_PROP_TIME_MINUTE,                                    // Минута бара
  }; 
#define BAR_PROP_INTEGER_TOTAL (14)                         // Общее количество целочисленных свойств бара
#define BAR_PROP_INTEGER_SKIP  (0)                          // Количество неиспользуемых в сортировке свойств бара
//+------------------------------------------------------------------+
//| Вещественные свойства бара                                       |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_DOUBLE
  {
//--- данные бара
   BAR_PROP_OPEN = BAR_PROP_INTEGER_TOTAL,                  // Цена открытия бара
   BAR_PROP_HIGH,                                           // Наивысшая цена за период бара
   BAR_PROP_LOW,                                            // Наименьшая цена за период бара
   BAR_PROP_CLOSE,                                          // Цена закрытия бара
//--- данные свечи
   BAR_PROP_CANDLE_SIZE,                                    // Размер свечи
   BAR_PROP_CANDLE_SIZE_BODY,                               // Размер тела свечи
   BAR_PROP_CANDLE_BODY_TOP,                                // Верх тела свечи
   BAR_PROP_CANDLE_BODY_BOTTOM,                             // Низ тела свечи
   BAR_PROP_CANDLE_SIZE_SHADOW_UP,                          // Размер верхней тени свечи
   BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,                        // Размер нижней тени свечи
  }; 
#define BAR_PROP_DOUBLE_TOTAL  (10)                         // Общее количество вещественных свойств бара
#define BAR_PROP_DOUBLE_SKIP   (0)                          // Количество неиспользуемых в сортировке свойств бара
//+------------------------------------------------------------------+
//| Строковые свойства бара                                          |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_STRING
  {
   BAR_PROP_SYMBOL = (BAR_PROP_INTEGER_TOTAL+BAR_PROP_DOUBLE_TOTAL), // Символ бара
  };
#define BAR_PROP_STRING_TOTAL  (1)                          // Общее количество строковых свойств бара
//+------------------------------------------------------------------+
//| Возможные критерии сортировки баров                              |
//+------------------------------------------------------------------+
#define FIRST_BAR_DBL_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP)
#define FIRST_BAR_STR_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BAR_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_BAR_INDEX = 0,                                   // Сортировать по индексу бара
   SORT_BY_BAR_TYPE,                                        // Сортировать по типу бара (из перечисления ENUM_BAR_BODY_TYPE)
   SORT_BY_BAR_PERIOD,                                      // Сортировать по периоду бара (таймфрейму)
   SORT_BY_BAR_SPREAD,                                      // Сортировать по спреду бара
   SORT_BY_BAR_VOLUME_TICK,                                 // Сортировать по тиковому объёму бара
   SORT_BY_BAR_VOLUME_REAL,                                 // Сортировать по биржевому объёму бара
   SORT_BY_BAR_TIME,                                        // Сортировать по времени начала периода бара
   SORT_BY_BAR_TIME_DAY_OF_YEAR,                            // Сортировать по порядковому номеру дня бара в году
   SORT_BY_BAR_TIME_YEAR,                                   // Сортировать по году, к которому относится бар
   SORT_BY_BAR_TIME_MONTH,                                  // Сортировать по месяцу, к которому относится бар
   SORT_BY_BAR_TIME_DAY_OF_WEEK,                            // Сортировать по дню недели бара
   SORT_BY_BAR_TIME_DAY,                                    // Сортировать по дню бара
   SORT_BY_BAR_TIME_HOUR,                                   // Сортировать по часу бара
   SORT_BY_BAR_TIME_MINUTE,                                 // Сортировать по минуте бара
//--- Сортировка по вещественным свойствам
   SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP,                   // Сортировать по цене открытия бара
   SORT_BY_BAR_HIGH,                                        // Сортировать по наивысшей цене за период бара
   SORT_BY_BAR_LOW,                                         // Сортировать по наименьшей цене за период бара
   SORT_BY_BAR_CLOSE,                                       // Сортировать по цене закрытия бара
   SORT_BY_BAR_CANDLE_SIZE,                                 // Сортировать по размеру свечи
   SORT_BY_BAR_CANDLE_SIZE_BODY,                            // Сортировать по размеру тела свечи
   SORT_BY_BAR_CANDLE_BODY_TOP,                             // Сортировать по верху тела свечи
   SORT_BY_BAR_CANDLE_BODY_BOTTOM,                          // Сортировать по низу тела свечи
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP,                       // Сортировать по размеру верхней тени свечи
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN,                     // Сортировать по размеру нижней тени свечи
//--- Сортировка по строковым свойствам
   SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP,                 // Сортировать по символу бара
  };
//+------------------------------------------------------------------+

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

Для вывода описаний свойств баров и некоторых иных сообщений библиотеки нам потребуются новые текстовые сообщения.
Допишем в файл Datas.mqh, хранящийся по пути \MQL5\Include\DoEasy\Datas.mqh, индексы новых сообщений:

   MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE,               // Код возврата вне заданного диапазона кодов ошибок
   MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ,               // Не удалось создать объект \"Пауза\"
   MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ,                 // Не удалось создать объект \"Бар\"
   MSG_LIB_SYS_FAILED_SYNC_DATA,                      // Не удалось синхронизировать данные с сервером

...

   MSG_LIB_TEXT_TIME_UNTIL_THE_END_DAY,               // Будет использоваться время действия ордера до конца текущего дня
   
   MSG_LIB_TEXT_JANUARY,                              // Январь
   MSG_LIB_TEXT_FEBRUARY,                             // Февраль
   MSG_LIB_TEXT_MARCH,                                // Март
   MSG_LIB_TEXT_APRIL,                                // Апрель
   MSG_LIB_TEXT_MAY,                                  // Май
   MSG_LIB_TEXT_JUNE,                                 // Июнь
   MSG_LIB_TEXT_JULY,                                 // Июль
   MSG_LIB_TEXT_AUGUST,                               // Август
   MSG_LIB_TEXT_SEPTEMBER,                            // Сентябрь
   MSG_LIB_TEXT_OCTOBER,                              // Октябрь
   MSG_LIB_TEXT_NOVEMBER,                             // Ноябрь
   MSG_LIB_TEXT_DECEMBER,                             // Декабрь
   
   MSG_LIB_TEXT_SUNDAY,                               // Воскресение

...

   MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS,          // Добавлены условия активации отложенного запроса

//--- CBar
   MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA,              // Не удалось получить данные бара
   MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA,           // Не удалось получить данные таймсерии
   MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST,               // Не удалось добавить объект-бар в список
   MSG_LIB_TEXT_BAR,                                  // Бар
   MSG_LIB_TEXT_BAR_PERIOD,                           // Таймфрейм
   MSG_LIB_TEXT_BAR_SPREAD,                           // Спред
   MSG_LIB_TEXT_BAR_VOLUME_TICK,                      // Тиковый объём
   MSG_LIB_TEXT_BAR_VOLUME_REAL,                      // Биржевой объём
   MSG_LIB_TEXT_BAR_TIME,                             // Время начала периода
   MSG_LIB_TEXT_BAR_TIME_YEAR,                        // Год
   MSG_LIB_TEXT_BAR_TIME_MONTH,                       // Месяц
   MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR,                 // Порядковый номер дня в году
   MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK,                 // День недели
   MSG_LIB_TEXT_BAR_TIME_DAY,                         // День месяца
   MSG_LIB_TEXT_BAR_TIME_HOUR,                        // Час
   MSG_LIB_TEXT_BAR_TIME_MINUTE,                      // Минута
   MSG_LIB_TEXT_BAR_INDEX,                            // Индекс в таймсерии
   MSG_LIB_TEXT_BAR_HIGH,                             // Наивысшая цена за период
   MSG_LIB_TEXT_BAR_LOW,                              // Наименьшая цена за период
   MSG_LIB_TEXT_BAR_CANDLE_SIZE,                      // Размер свечи
   MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY,                 // Размер тела свечи
   MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP,            // Размер верхней тени свечи
   MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN,          // Размер нижней тени свечи
   MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP,                  // Верх тела свечи
   MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM,               // Низ тела свечи
   MSG_LIB_TEXT_BAR_TYPE_BULLISH,                     // Бычий бар
   MSG_LIB_TEXT_BAR_TYPE_BEARISH,                     // Медвежий бар
   MSG_LIB_TEXT_BAR_TYPE_NULL,                        // Нулевой бар
   MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY,            // Свеча с нулевым телом
   MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA,        // Сначала нужно установить требуемое количество данных при помощи SetAmountUsedData()
  
  };
//+------------------------------------------------------------------+

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

   {"Код возврата вне заданного диапазона кодов ошибок","Return code out of range of error codes"},
   {"Не удалось создать объект \"Пауза\"","Failed to create object \"Pause\""},
   {"Не удалось создать объект \"Бар\"","Failed to create object \"Bar\""},
   {"Не удалось синхронизировать данные с сервером","Failed to sync data with server"},

...

   {"Будет использоваться время действия ордера до конца текущего дня","The order validity time until the end of the current day will be used"},

   {"Январь","January"},
   {"Февраль","February"},
   {"Март","March"},
   {"Апрель","April"},
   {"Май","May"},
   {"Июнь","June"},
   {"Июль","July"},
   {"Август","August"},
   {"Сентябрь","September"},
   {"Октябрь","October"},
   {"Ноябрь","November"},
   {"Декабрь","December"},
   
   {"Воскресение","Sunday"},

...

   {"Добавлены условия активации отложенного запроса","Pending request activation conditions added"},
   
   {"Не удалось получить данные бара","Failed to get bar data"},
   {"Не удалось получить данные таймсерии","Failed to get timeseries data"},
   {"Не удалось добавить объект-бар в список","Failed to add the bar object to the list"},
   {"Бар","Bar"},
   {"Таймфрейм","Timeframe"},
   {"Спред","Spread"},
   {"Тиковый объём","Tick volume"},
   {"Биржевой объём","Real volume"},
   {"Время начала периода","Period start time"},
   {"Год","Year"},
   {"Месяц","Month"},
   {"Порядковый номер дня в году","Sequence day number in the year"},
   {"День недели","Day of week"},
   {"День месяца","Day od month"},
   {"Час","Hour"},
   {"Минута","Minute"},
   {"Индекс в таймсерии","Timeseries index"},
   {"Наивысшая цена за период","Highest price for the period"},
   {"Наименьшая цена за период","Lowest price for the period"},
   {"Размер свечи","Candle size"},
   {"Размер тела свечи","Candle body size"},
   {"Размер верхней тени свечи","Candle upper shadow size"},
   {"Размер нижней тени свечи","Candle lower shadow size"},
   {"Верх тела свечи","Top of the candle body"},
   {"Низ тела свечи","Bottom of the candle body"},
   
   {"Бычий бар","Bullish bar"},
   {"Медвежий бар","Bearish bar"},
   {"Нулевой бар","Zero-bar"},
   {"Свеча с нулевым телом","Candle with zero body"},
   {"Сначала нужно установить требуемое количество данных при помощи SetAmountUsedData()","First you need to set the required amount of data using SetAmountUsedData()"},
   
  };
//+---------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает наименования месяца                                   |
//+------------------------------------------------------------------+
string MonthDescription(const int month)
  {
   return
     (
      month==1    ?  CMessage::Text(MSG_LIB_TEXT_JANUARY)   :
      month==2    ?  CMessage::Text(MSG_LIB_TEXT_FEBRUARY)  :
      month==3    ?  CMessage::Text(MSG_LIB_TEXT_MARCH)     :
      month==4    ?  CMessage::Text(MSG_LIB_TEXT_APRIL)     :
      month==5    ?  CMessage::Text(MSG_LIB_TEXT_MAY)       :
      month==6    ?  CMessage::Text(MSG_LIB_TEXT_JUNE)      :
      month==7    ?  CMessage::Text(MSG_LIB_TEXT_JULY)      :
      month==8    ?  CMessage::Text(MSG_LIB_TEXT_AUGUST)    :
      month==9    ?  CMessage::Text(MSG_LIB_TEXT_SEPTEMBER) :
      month==10   ?  CMessage::Text(MSG_LIB_TEXT_OCTOBER)   :
      month==11   ?  CMessage::Text(MSG_LIB_TEXT_NOVEMBER)  :
      month==12   ?  CMessage::Text(MSG_LIB_TEXT_DECEMBER)  :
      (string)month
     );
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает описание таймфрейма                                   |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return StringSubstr(EnumToString(timeframe),7);
  }
//+------------------------------------------------------------------+

В функцию, возвращающую наименования месяца, передаётся номер месяца, и в соответствии с номером возвращается его текстовое описание.

В функцию, возвращающую наименование таймфрейма, передаётся таймфрейм, далее из текстового представления значения перечисления таймфрейма извлекается подстрока, начинающаяся с позиции 7 и до конца строки. Полученный результат возвращается в виде текста. Таким образом, например, из текстового представления часового таймфрейма PERIOD_H1 извлекается значение H1.

Для хранения классов объектов-баров создадим новую папку в каталоге объектов библиотеки \MQL5\Include\DoEasy\Objects\Series\, а в ней новый файл Bar.mqh класса CBar.

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

//+------------------------------------------------------------------+
//|                                                          Bar.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 "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Класс "Бар"                                                      |
//+------------------------------------------------------------------+
class CBar : public CObject
  {
private:
   MqlDateTime       m_dt_struct;                                 // Структура даты
   int               m_digits;                                    // Значение Digits символа
   string            m_period_description;                        // Строковое описание таймфрейма
   long              m_long_prop[BAR_PROP_INTEGER_TOTAL];         // Целочисленные свойства
   double            m_double_prop[BAR_PROP_DOUBLE_TOTAL];        // Вещественные свойства
   string            m_string_prop[BAR_PROP_STRING_TOTAL];        // Строковые свойства

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

//--- Возвращает тип бара (бычий/медвежий/нулевой)
   ENUM_BAR_BODY_TYPE BodyType(void)                              const;
//--- Рассчитывает и возвращает размер (1) свечи, (2) тела свечи,
//--- (3) верхней, (4) нижней тени свечи,
//--- (5) верх, (6) низ тела свечи
   double            CandleSize(void)                             const { return(this.High()-this.Low());                                    }
   double            BodySize(void)                               const { return(this.BodyHigh()-this.BodyLow());                            }
   double            ShadowUpSize(void)                           const { return(this.High()-this.BodyHigh());                               }
   double            ShadowDownSize(void)                         const { return(this.BodyLow()-this.Low());                                 }
   double            BodyHigh(void)                               const { return ::fmax(this.Close(),this.Open());                           }
   double            BodyLow(void)                                const { return ::fmin(this.Close(),this.Open());                           }

//--- Возвращает (1) год, (2) месяц, к которому относится бар, (3) день недели,
//--- (4) порядковый номер в году, (5) день, (6) час, (7) минуту бара,
   int               TimeYear(void)                               const { return this.m_dt_struct.year;                                      }
   int               TimeMonth(void)                              const { return this.m_dt_struct.mon;                                       }
   int               TimeDayOfWeek(void)                          const { return this.m_dt_struct.day_of_week;                               }
   int               TimeDayOfYear(void)                          const { return this.m_dt_struct.day_of_year;                               }
   int               TimeDay(void)                                const { return this.m_dt_struct.day;                                       }
   int               TimeHour(void)                               const { return this.m_dt_struct.hour;                                      }
   int               TimeMinute(void)                             const { return this.m_dt_struct.min;                                       }

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

//--- Возвращает флаг поддержания баром данного свойства
   virtual bool      SupportProperty(ENUM_BAR_PROP_INTEGER property)    { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_DOUBLE property)     { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_STRING property)     { return true; }
//--- Возвращает себя
   CBar             *GetObject(void)                                    { return &this;}
//--- Устанавливает (1) символ, таймфрейм и индекс бара, (2) параметры объекта-бар
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   void              SetProperties(const MqlRates &rates);

//--- Сравнивает объекты CBar между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-бара)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Сравнивает объекты CBar между собой по всем свойствам (для поиска равных объектов-баров)
   bool              IsEqual(CBar* compared_bar) const;
//--- Конструкторы
                     CBar(){;}
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates);
                     
//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта-ордера            |
//+------------------------------------------------------------------+
//--- Возвращает (1) тип, (2) период, (3) спред, (4) тиковый, (5) биржевой объём,
//--- (6) время начала периода бара, (7) год, (8) месяц, к которому относится бар
//--- (9) номер недели от начала года, (10) номер недели от начала месяца
//--- (11) день, (12) час, (13) минута, (14) индекс бара
   ENUM_BAR_BODY_TYPE   TypeBody(void)                                  const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE);  }
   ENUM_TIMEFRAMES   Period(void)                                       const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD);   }
   int               Spread(void)                                       const { return (int)this.GetProperty(BAR_PROP_SPREAD);               }
   long              VolumeTick(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_TICK);               }
   long              VolumeReal(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_REAL);               }
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(BAR_PROP_TIME);            }
   long              Year(void)                                         const { return this.GetProperty(BAR_PROP_TIME_YEAR);                 }
   long              Month(void)                                        const { return this.GetProperty(BAR_PROP_TIME_MONTH);                }
   long              DayOfWeek(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK);          }
   long              DayOfYear(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR);          }
   long              Day(void)                                          const { return this.GetProperty(BAR_PROP_TIME_DAY);                  }
   long              Hour(void)                                         const { return this.GetProperty(BAR_PROP_TIME_HOUR);                 }
   long              Minute(void)                                       const { return this.GetProperty(BAR_PROP_TIME_MINUTE);               }
   long              Index(void)                                        const { return this.GetProperty(BAR_PROP_INDEX);                     }

//--- Возвращает цену (1) Open, (2) High, (3) Low, (4) Close бара,
//--- размер (5) свечи, (6) тела, (7) верх, (8) низ свечи,
//--- размер (9) верхней, (10) нижней тени свечи
   double            Open(void)                                         const { return this.GetProperty(BAR_PROP_OPEN);                      }
   double            High(void)                                         const { return this.GetProperty(BAR_PROP_HIGH);                      }
   double            Low(void)                                          const { return this.GetProperty(BAR_PROP_LOW);                       }
   double            Close(void)                                        const { return this.GetProperty(BAR_PROP_CLOSE);                     }
   double            Size(void)                                         const { return this.GetProperty(BAR_PROP_CANDLE_SIZE);               }
   double            SizeBody(void)                                     const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_BODY);          }
   double            TopBody(void)                                      const { return this.GetProperty(BAR_PROP_CANDLE_BODY_TOP);           }
   double            BottomBody(void)                                   const { return this.GetProperty(BAR_PROP_CANDLE_BODY_BOTTOM);        }
   double            SizeShadowUp(void)                                 const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP);     }
   double            SizeShadowDown(void)                               const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN);   }
   
//--- Возвращает символ бара
   string            Symbol(void)                                       const { return this.GetProperty(BAR_PROP_SYMBOL);                    }
  
//+------------------------------------------------------------------+
//| Описания свойств объекта-бара                                    |
//+------------------------------------------------------------------+
//--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства бара
   string            GetPropertyDescription(ENUM_BAR_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_BAR_PROP_STRING property);

//--- Возвращает описание типа бара
   string            BodyTypeDescription(void)  const;
//--- Выводит в журнал описание свойств бара (full_prop=true - все свойства, false - только поддерживаемые)
   void              Print(const bool full_prop=false);
//--- Выводит в журнал краткое описание бара
   virtual void      PrintShort(void);
//--- Возвращает краткое наименование объекта-бара
   virtual string    Header(void);
//---
  };
//+------------------------------------------------------------------+

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

В приватной секции класса расположены:

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

В публичной секции класса расположены:

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

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

Класс имеет три конструктора:

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

2. Первый параметрический конструктор получает три параметра — символ, таймфрейм и индекс бара, и на их основе получает из таймсерии все свойства одного объекта-бара посредством первой формы функции CopyRates():

//+------------------------------------------------------------------+
//| Конструктор 1                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   MqlRates rates_array[1];
   this.SetSymbolPeriod(symbol,timeframe,index);
   ::ResetLastError();
//--- Если не получилось записать в данные бара в массив по индексу, или произошла ошибка установки времени в структуру времени
//--- выводим сообщение об ошибке, создаём и заполняем структуру нулями и записываем её в массив rates_array
   if(::CopyRates(symbol,timeframe,index,1,rates_array)<1 || !::TimeToStruct(rates_array[0].time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code));
      MqlRates err={0};
      rates_array[0]=err;
     }
//--- Устанавливаем свойства бара
   this.SetProperties(rates_array[0]);
  }
//+------------------------------------------------------------------+

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

3. Второй параметрический конструктор служит для создания объекта-бара из уже готового массива структур MqlRates.
Т.е. подразумевается проход в цикле по массиву структур MqlRates и созданию объекта по индексу этого массива:

//+------------------------------------------------------------------+
//| Конструктор 2                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates)
  {
   this.SetSymbolPeriod(symbol,timeframe,index);
   ::ResetLastError();
//--- Если произошла ошибка установки времени в структуру времени, выводим сообщение об ошибке,
//--- создаём и заполняем структуру нулями, устанавливаем свойства бара из этой структуры и выходим
   if(!::TimeToStruct(rates.time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code));
      MqlRates err={0};
      this.SetProperties(err);
      return;
     }
//--- Устанавливаем свойства бара
   this.SetProperties(rates);
  }
//+------------------------------------------------------------------+

Здесь в конструктор помимо символа, таймфрейма и индекса передаётся ссылка на структуру MqlRates. На основании этих данных и создаётся объект-бар.

Виртуальный метод Compare() предназначен для сравнения двух объектов по указанному свойству. Определён в классе базового объекта стандартной библиотеки CObject, и должен возвращать ноль, если значения равны, и 1/-1 если одно из сравниваемых значений больше/меньше. Для поиска и сортировки используется в методе Search() Стандартной библиотеки, и должен переопределяться в классах-наследниках:

//+------------------------------------------------------------------+
//| Сравнивает объекты CBar между собой по всем возможным свойствам  |
//+------------------------------------------------------------------+
int CBar::Compare(const CObject *node,const int mode=0) const
  {
   const CBar *bar_compared=node;
//--- сравнение целочисленных свойств двух баров
   if(mode<BAR_PROP_INTEGER_TOTAL)
     {
      long value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_BAR_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение вещественных свойств двух баров
   else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL)
     {
      double value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_BAR_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- сравнение строковых свойств двух баров
   else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL+BAR_PROP_STRING_TOTAL)
     {
      string value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_BAR_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Сравнивает объекты CBar между собой по всем свойствам            |
//+------------------------------------------------------------------+
bool CBar::IsEqual(CBar *compared_bar) const
  {
   int beg=0, end=BAR_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; 
     }
   beg=end; end+=BAR_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; 
     }
   beg=end; end+=BAR_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_STRING prop=(ENUM_BAR_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает символ, таймфрейм и индекс бара                    |
//+------------------------------------------------------------------+
void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index)
  {
   this.SetProperty(BAR_PROP_INDEX,index);
   this.SetProperty(BAR_PROP_SYMBOL,symbol);
   this.SetProperty(BAR_PROP_PERIOD,timeframe);
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   this.m_period_description=TimeframeDescription(timeframe);
  }
//+------------------------------------------------------------------+

Помимо установки трёх перечисленных свойств, метод устанавливает количество знаков после запятой в значении цены символа в переменную m_digits и текстовое описание таймфрейма в переменную m_period_description — достаточно их указать один раз при создании объекта-бара.

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

//+------------------------------------------------------------------+
//| Устанавливает параметры объекта-бар                              |
//+------------------------------------------------------------------+
void CBar::SetProperties(const MqlRates &rates)
  {
   this.SetProperty(BAR_PROP_SPREAD,rates.spread);
   this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume);
   this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume);
   this.SetProperty(BAR_PROP_TIME,rates.time);
   this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear());
   this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear());
   this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek());
   this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay());
   this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour());
   this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute());
//---
   this.SetProperty(BAR_PROP_OPEN,rates.open);
   this.SetProperty(BAR_PROP_HIGH,rates.high);
   this.SetProperty(BAR_PROP_LOW,rates.low);
   this.SetProperty(BAR_PROP_CLOSE,rates.close);
   this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize());
   this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh());
   this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize());
   this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize());
//---
   this.SetProperty(BAR_PROP_TYPE,this.BodyType());
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Выводит в журнал свойства бара                                   |
//+------------------------------------------------------------------+
void CBar::Print(const bool full_prop=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   int beg=0, end=BAR_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=BAR_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=BAR_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_BAR_PROP_STRING prop=(ENUM_BAR_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");
  }
//+------------------------------------------------------------------+

В трёх циклах по массивам свойств объекта выводятся описания каждого очередного свойства. Если свойство не поддерживается, то оно не выводится в журнал в случае, если входной параметр метода full_prop имеет значение false (по умолчанию).

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

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание бара                           |
//+------------------------------------------------------------------+
void CBar::PrintShort(void)
  {
   int dg=(this.m_digits>0 ? this.m_digits : 1);
   string params=
     (
      ::TimeToString(this.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+", "+
      "O: "+::DoubleToString(this.Open(),dg)+", "+
      "H: "+::DoubleToString(this.High(),dg)+", "+
      "L: "+::DoubleToString(this.Low(),dg)+", "+
      "C: "+::DoubleToString(this.Close(),dg)+", "+
      "V: "+(string)this.VolumeTick()+", "+
      (this.VolumeReal()>0 ? "R: "+(string)this.VolumeReal()+", " : "")+
      this.BodyTypeDescription()
     );
   ::Print(this.Header(),": ",params);
  }
//+------------------------------------------------------------------+

Метод выводит описание бара в формате

Bar "SYMBOL" H4[INDEX]: YYYY.MM.DD HH:MM:SS, O: X.XXXXX, H: X.XXXXX, L: X.XXXXX, C: X.XXXXX, V: XXXX, BAR_TYPE

Например:

Бар "EURUSD" H4[6]: 2020.02.06 20:00:00, O: 1.09749, H: 1.09828, L: 1.09706, C: 1.09827, V: 3323, Бычий бар

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

//+------------------------------------------------------------------+
//| Возвращает краткое наименование объекта-бара                     |
//+------------------------------------------------------------------+
string CBar::Header(void)
  {
   return
     (
      CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+
      TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.GetProperty(BAR_PROP_INDEX)+"]"
     );
  }
//+------------------------------------------------------------------+

Выводит наименование бара в формате

Bar "SYMBOL" H4[INDEX]

Например:

Бар "EURUSD" H4[6]

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

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства бара                 |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property)
  {
   return
     (
      property==BAR_PROP_INDEX               ?  CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TYPE                ?  CMessage::Text(MSG_ORD_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.BodyTypeDescription()
         )  :
      property==BAR_PROP_PERIOD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.m_period_description
         )  :
      property==BAR_PROP_SPREAD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_TICK         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_REAL         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TIME                ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==BAR_PROP_TIME_YEAR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.Year()
         )  :
      property==BAR_PROP_TIME_MONTH          ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+MonthDescription((int)this.Month())
         )  :
      property==BAR_PROP_TIME_DAY_OF_YEAR    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.DayOfYear(),3,'0')
         )  :
      property==BAR_PROP_TIME_DAY_OF_WEEK    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek())
         )  :
      property==BAR_PROP_TIME_DAY ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Day(),2,'0')
         )  :
      property==BAR_PROP_TIME_HOUR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Hour(),2,'0')
         )  :
      property==BAR_PROP_TIME_MINUTE         ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Minute(),2,'0')
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

В метод передаётся целочисленное свойство, и в зависимости от его значения возвращается его текстовое описание, заданное в файле Datas.mqh.

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

//+------------------------------------------------------------------+
//| Возвращает описание вещественного свойства бара                  |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property)
  {
   int dg=(this.m_digits>0 ? this.m_digits : 1);
   return
     (
      property==BAR_PROP_OPEN                ?  CMessage::Text(MSG_ORD_PRICE_OPEN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_HIGH                ?  CMessage::Text(MSG_LIB_TEXT_BAR_HIGH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_LOW                 ?  CMessage::Text(MSG_LIB_TEXT_BAR_LOW)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CLOSE               ?  CMessage::Text(MSG_ORD_PRICE_CLOSE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_SIZE         ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_SIZE_BODY    ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_SIZE_SHADOW_UP  ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_SIZE_SHADOW_DOWN   ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_BODY_TOP     ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      property==BAR_PROP_CANDLE_BODY_BOTTOM  ?  CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::DoubleToString(this.GetProperty(property),dg)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства бара                     |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_STRING property)
  {
   return(property==BAR_PROP_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL)+": \""+this.GetProperty(property)+"\"" : "");
  }
//+------------------------------------------------------------------+

Метод, возвращающий тип бара:

//+------------------------------------------------------------------+
//| Возвращает тип бара (бычий/медвежий/нулевой)                     |
//+------------------------------------------------------------------+
ENUM_BAR_BODY_TYPE CBar::BodyType(void) const
  {
   return
     (
      this.Close()>this.Open() ? BAR_BODY_TYPE_BULLISH : 
      this.Close()<this.Open() ? BAR_BODY_TYPE_BEARISH : 
      (this.ShadowUpSize()+this.ShadowDownSize()==0 ? BAR_BODY_TYPE_NULL : BAR_BODY_TYPE_CANDLE_ZERO_BODY)
     );
  }
//+------------------------------------------------------------------+

Здесть всё просто: если цена закрытия бара больше цены открытия, то это бычий бар, если цена закрытия бара меньше цены открытия, то это медвежий бар, иначе если обе тени свечи нулевые, то это бар с нулевым телом, иначе — это свеча с нулевым телом.

Метод, возвращающий описание типа бара:

//+------------------------------------------------------------------+
//| Возвращает описание типа бара                                    |
//+------------------------------------------------------------------+
string CBar::BodyTypeDescription(void) const
  {
   return
     (
      this.BodyType()==BAR_BODY_TYPE_BULLISH          ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BULLISH)          : 
      this.BodyType()==BAR_BODY_TYPE_BEARISH          ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BEARISH)          : 
      this.BodyType()==BAR_BODY_TYPE_CANDLE_ZERO_BODY ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY) :
      CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_NULL)
     );
  }
//+------------------------------------------------------------------+

В зависимости от типа бара метод возвращает его текстовое описание, прописанное в файле Datas.mqh.

Класс объекта "Бар" готов. Теперь мы можем для каждого требуемого бара нужной таймсерии создать объект-бар. Но сам по себе он не может дать нам какого-либо значимого преимущества перед обычным получением данных при помощи запроса бара таймсерии посредством CopyRates().

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

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

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

Объект "Новый бар"

Для определения факта открытия нового бара достаточно сравнить время открытия текущего бара с запомненным прошлым временем его открытия. Если эти времена не совпадают — имеем факт открытия нового бара. В этом случае нужно сохранить новое время открытия как прошлое для последующего сравнения:

NewBar = false;
if(PrevTime != Time)
  {
   NewBar = true;
   PrevTime = Time;
  }

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

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

NewBar = false;
if(PrevTime != Time)
  {
   NewBar = true;
   // ... команды, которые
   // ... должны быть выполнены
   // ... в момент рождения нового бара
   PrevTime = Time;
  }

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

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

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

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

В индикаторах в параметрах обработчика OnCalculate() уже есть нужные нам предопределённые переменные:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  • rates_total — количество доступной истории в таймсериях (аналог функции Bars() без параметров),
  • prev_calculated — количество уже посчитанных данных на прошлом вызове,
  • time[] — массив-таймсерия с данными о времени баров.

В индикаторах можно отслеживать изменения исторических данных при помощи простого расчёта:

  • если (rates_total - prev_calculated) больше 1, то это означает подгрузку истории и индикатор нужно перерисовать полностью,
  • если (rates_total - prev_calculated) равно 1 — это означает открытие нового бара на текущем символе-таймфрейме.
  • в обычном состоянии на каждом новом тике значение выражения (rates_total - prev_calculated) равно 0.

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

В папке библиотеки \MQL5\Include\DoEasy\Objects\Series\ создадим новый файл NewBarObj.mqh класса CNewBarObj:

//+------------------------------------------------------------------+
//|                                                    NewBarObj.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
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Класс объекта "Новый бар"                                        |
//+------------------------------------------------------------------+
class CNewBarObj
  {
private:
   string            m_symbol;                                    // Символ
   ENUM_TIMEFRAMES   m_timeframe;                                 // Таймфрейм
   datetime          m_new_bar_time;                              // Время нового бара для автоматического управления временем
   datetime          m_prev_time;                                 // Прошлое время для автоматического управления временем
   datetime          m_new_bar_time_manual;                       // Время нового бара для ручного управления временем
   datetime          m_prev_time_manual;                          // Прошлое время для ручного управления временем
//--- Возвращает дату текущего бара
   datetime          GetLastBarDate(const datetime time);
public:
//--- Устанавливает (1) символ, (2) таймфрейм
   void              SetSymbol(const string symbol)               { this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);                     }
   void              SetPeriod(const ENUM_TIMEFRAMES timeframe)   { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); }
//--- Сохраняет время нового бара при ручном управлении временем
   void              SaveNewBarTime(const datetime time)          { this.m_prev_time_manual=this.GetLastBarDate(time);                                      }
//--- Возвращает (1) символ, (2) таймфрейм
   string            Symbol(void)                           const { return this.m_symbol;       }
   ENUM_TIMEFRAMES   Period(void)                           const { return this.m_timeframe;    }
//--- Возвращает (1) время нового бара
   datetime          TimeNewBar(void)                       const { return this.m_new_bar_time; }
//--- Возвращает флаг открытия нового бара при (1) автоматическом, (2) ручном управлении временем
   bool              IsNewBar(const datetime time);
   bool              IsNewBarManual(const datetime time);
//--- Конструкторы
                     CNewBarObj(void) : m_symbol(::Symbol()),
                                        m_timeframe((ENUM_TIMEFRAMES)::Period()),
                                        m_prev_time(0),m_new_bar_time(0),
                                        m_prev_time_manual(0),m_new_bar_time_manual(0) {}
                     CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe);
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),m_timeframe(timeframe)
  {
   this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает флаг открытия нового бара                             |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBar(const datetime time)
  {
//--- Получаем время текущего бара
   datetime tm=this.GetLastBarDate(time);
//--- Если прошлое и текущее время равны нулю - это первый запуск
   if(this.m_prev_time==0 && this.m_new_bar_time==0)
     {
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем время прошлого бара как текущее и возвращаем false
      this.m_new_bar_time=this.m_prev_time=tm;
      return false;
     }
//--- Если прошлое время не равно времени открытия текущего бара - это новый бар
   if(this.m_prev_time!=tm)
     {
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем прошлое время как текущее и возвращаем true
      this.m_new_bar_time=this.m_prev_time=tm;
      return true;
     }
//--- в остальных случаях возвращаем false
   return false;
  }
//+------------------------------------------------------------------+

Метод единожды при каждом открытии нового бара на символе и таймфрейме, заданными для объекта, возвращает true.

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

//+------------------------------------------------------------------+
//| Возвращает флаг открытия нового бара при ручном управлении       |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBarManual(const datetime time)
  {
//--- Получаем время текущего бара
   datetime tm=this.GetLastBarDate(time);
//--- Если прошлое и текущее время равны нулю - это первый запуск
   if(this.m_prev_time_manual==0 && this.m_new_bar_time_manual==0)
     {
      //--- устанавливаем время открытия нового бара,
      //--- устанавливаем время прошлого бара как текущее и возвращаем false
      this.m_new_bar_time_manual=this.m_prev_time_manual=tm;
      return false;
     }
//--- Если прошлое время не равно времени открытия текущего бара - это новый бар
   if(this.m_prev_time_manual!=tm)
     {
      //--- устанавливаем время открытия нового бара и возвращаем true
      //--- Предыдущее время сохранять как текущее необходимо самостоятельно из программы методом SaveNewBarTime()
      //--- До тех пор, пока принудительно из программы не будет установлено прошлое время как текущее,
      //--- метод будет возвращать флаг нового бара, позволяя завершить все необходимые действия на новом баре.
      this.m_new_bar_time=tm;
      return true;
     }
//--- в остальных случаях возвращаем false
   return false;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Возвращает время текущего бара                                   |
//+------------------------------------------------------------------+
datetime CNewBarObj::GetLastBarDate(const datetime time)
  {
   return
     (
      ::MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? time :
      (datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE)
     );
  }
//+------------------------------------------------------------------+

Если это индикатор и символ и таймфрейм объекта "Новый бар" совпадают с текущими символом и таймфреймом, то возвращается время, переданное в метод (в индикаторах это время есть в параметрах OnCalculate() в массиве time[], и именно из этого массива нужно передавать время в методы определения нового бара), иначе — получаем время последнего бара при помощи SeriesInfoInteger() — в данном случае вместо времени можно передать любое значение.

Для текущих потребностей класс объекта "Новый бар" готов. Приступим к созданию списка объектов-баров.

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

Список объектов-баров, поиск и сортировка

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

Каждый список объектов-баров будет иметь определённое пользователем количество объектов-баров (глубину истории). По умолчанию глубина истории всех списков будет иметь размерность 1000 баров. И каждый список, прежде чем будет построен, должен учитывать факт синхронизации данных с торговым сервером. Каждый список каждой таймсерии будет иметь значение, указывающее количество доступных баров истории. Эту величину возвращает функция Bars() без параметров. Если она вернула ноль, это значит, что история ещё не синхронизирована. И мы будем делать несколько попыток с небольшим ожиданием между ними в ожидании синхронизации данных с сервером.

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

//--- Идентификаторы типов отложенных запросов
#define PENDING_REQUEST_ID_TYPE_ERR    (1)                        // Тип отложенного запроса, созданного по коду возврата сервера
#define PENDING_REQUEST_ID_TYPE_REQ    (2)                        // Тип отложенного запроса, созданного по запросу
//--- Параметры таймсерий
#define SERIES_DEFAULT_BARS_COUNT      (1000)                     // Требуемое количество данных таймсерий по умолчанию
#define PAUSE_FOR_SYNC_ATTEMPTS        (16)                       // Количество милисекунд паузы между попытками синхронизации
#define ATTEMPTS_FOR_SYNC              (5)                        // Количество попыток получения факта синхронизации с сервером
//+------------------------------------------------------------------+
//| Структуры                                                        |
//+------------------------------------------------------------------+

Для быстрого поиска и сортировки списков-коллекций мы уже создавали функционал в классе CSelect,
описанный в папке сервисных функций и классов \MQL5\Include\DoEasy\Services\ в файле Select.mqh.

Добавим в класс методы для поиска и сортировки в списках объектов-баров.
Подключим к листингу файл класса объекта "Бар":

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, 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\Bar.mqh"
//+------------------------------------------------------------------+

Впишем в тело класса определения методов поиска и сортировки по свойствам объекта "Бар":

//+------------------------------------------------------------------+
//| Класс для выборки объектов, удовлетворяющих критерию             |
//+------------------------------------------------------------------+
class CSelect
  {
private:
   //--- Метод сравнения двух величин
   template<typename T>
   static bool       CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode);
public:
//+------------------------------------------------------------------+
//| Методы работы с ордерами                                         |
//+------------------------------------------------------------------+
   //--- Возвращает список ордеров, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс ордера в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
   //--- Возвращает индекс ордера в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property);
   static int        FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с событиями                                        |
//+------------------------------------------------------------------+
   //--- Возвращает список событий, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property);
   static int        FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с аккаунтами                                       |
//+------------------------------------------------------------------+
   //--- Возвращает список аккаунтов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс события в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
   //--- Возвращает индекс события в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства события
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property);
   static int        FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с символами                                        |
//+------------------------------------------------------------------+
   //--- Возвращает список символов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс символа в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property);
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property);
   static int        FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property);
   //--- Возвращает индекс символа в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property);
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property);
   static int        FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с отложенными запросами                            |
//+------------------------------------------------------------------+
   //--- Возвращает список отложенных запросов, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс отложенного запроса в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property);
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property);
   static int        FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property);
   //--- Возвращает индекс отложенного запроса в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property);
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property);
   static int        FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property);
//+------------------------------------------------------------------+
//| Методы работы с барами таймсерии                                 |
//+------------------------------------------------------------------+
   //--- Возвращает список баров, у которых одно из (1) целочисленных, (2) вещественных и (3) строковых свойств удовлетворяет заданному критерию
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Возвращает индекс отложенного запроса в списке с максимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property);
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property);
   static int        FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property);
   //--- Возвращает индекс отложенного запроса в списке с минимальным значением (1) целочисленного, (2) вещественного и (3) строкового свойства ордера
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property);
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property);
   static int        FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

И за пределами тела класса напишем реализацию добавленных методов:

//+------------------------------------------------------------------+
//| Методы работы со списками баров таймсерии                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Возвращает список баров, у которых одно из целочисленных         |
//| свойств удовлетворяет заданному критерию                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_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++)
     {
      CBar *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::ByBarProperty(CArrayObj *list_source,ENUM_BAR_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++)
     {
      CBar *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::ByBarProperty(CArrayObj *list_source,ENUM_BAR_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++)
     {
      CBar *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::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CBar *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CBar *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CBar *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_INTEGER property)
  {
   int index=0;
   CBar *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_DOUBLE property)
  {
   int index=0;
   CBar *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_STRING property)
  {
   int index=0;
   CBar *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CBar *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;
  }
//+------------------------------------------------------------------+

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

Создадим в папке \MQL5\Include\DoEasy\Objects\Series\ новый файл Series.mqh класса CSeries и подключим к нему файл класса CSelect и созданные новые классы объекта "Новый Бар" и объекта "Бар":

//+------------------------------------------------------------------+
//|                                                       Series.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 "..\..\Services\Select.mqh"
#include "NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+

Теперь пропишем в теле класса все необходимые переменные-члены класса и объявим методы класса:

//+------------------------------------------------------------------+
//|                                                       Series.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 "..\..\Services\Select.mqh"
#include "NewBarObj.mqh"
#include "Bar.mqh"
//+------------------------------------------------------------------+
//| Класс "Таймсерия"                                                |
//+------------------------------------------------------------------+
class CSeries : public CObject
  {
private:
   ENUM_PROGRAM_TYPE m_program;                                         // Тип программы
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм
   string            m_symbol;                                          // Символ
   uint              m_amount;                                          // Количество используемых данных таймсерии
   uint              m_bars;                                            // Количество баров в истории по символу и таймфрейму
   bool              m_sync;                                            // Флаг синхронизированности данных
   CArrayObj         m_list_series;                                     // Список-таймсерия
   CNewBarObj        m_new_bar_obj;                                     // Объект "Новый бар"
public:
//--- Возвращает список-таймсерию
   CArrayObj*        GetList(void)                                      { return &m_list_series;}
//--- Возвращает список баров по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию
   CArrayObj*        GetList(ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByBarProperty(this.GetList(),property,value,mode); }
   CArrayObj*        GetList(ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); }

//--- Устанавливает (1) символ и таймфрейм, (2) количество используемых данных таймсерии
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool              SetAmountUsedData(const uint amount,const uint rates_total);

//--- Возвращает (1) символ, (2) таймфрейм, (3) количество используемых данных таймсерии,
//--- (4) количество баров в таймсерии, флаг нового бара с (5) автоматическим, (6) ручным управлением временем
   string            Symbol(void)                                          const { return this.m_symbol;                            }
   ENUM_TIMEFRAMES   Period(void)                                          const { return this.m_timeframe;                         }
   uint              AmountUsedData(void)                                  const { return this.m_amount;                            }
   uint              Bars(void)                                            const { return this.m_bars;                              }
   bool              IsNewBar(const datetime time)                               { return this.m_new_bar_obj.IsNewBar(time);        }
   bool              IsNewBarManual(const datetime time)                         { return this.m_new_bar_obj.IsNewBarManual(time);  }
//--- Возвращает объект-бар по индексу (1) в списке, (2) в таймсерии
   CBar             *GetBarByListIndex(const uint index);
   CBar             *GetBarBySeriesIndex(const uint index);
//--- Возвращает (1) Open, (2) High, (3) Low, (4) Close, (5) время, (6) тиковый объём, (7) реальный объём, (8) спред бара по индексу
   double            Open(const uint index,const bool from_series=true);
   double            High(const uint index,const bool from_series=true);
   double            Low(const uint index,const bool from_series=true);
   double            Close(const uint index,const bool from_series=true);
   datetime          Time(const uint index,const bool from_series=true);
   long              TickVolume(const uint index,const bool from_series=true);
   long              RealVolume(const uint index,const bool from_series=true);
   int               Spread(const uint index,const bool from_series=true);

//--- Сохраняет время нового бара при ручном управлении временем
   void              SaveNewBarTime(const datetime time)                         { this.m_new_bar_obj.SaveNewBarTime(time);         }
//--- Синхронизирует данные по символу с данными на сервере
   bool              SyncData(const uint amount,const uint rates_total);
//--- (1) Создаёт, (2) обновляет список-таймсерию
   int               Create(const uint amount=0);
   void              Refresh(const datetime time=0,
                             const double open=0,
                             const double high=0,
                             const double low=0,
                             const double close=0,
                             const long tick_volume=0,
                             const long volume=0,
                             const int spread=0);
                             
//--- Конструкторы
                     CSeries(void);
                     CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0);
  };
//+------------------------------------------------------------------+

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

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

Первый конструктор класса не имеет параметров и служит для создания списка для текущих символа и таймфрейма:

//+------------------------------------------------------------------+
//| Конструктор 1 (таймсерия текущего символа и периода)             |
//+------------------------------------------------------------------+
CSeries::CSeries(void) : m_bars(0),m_amount(0),m_sync(false)
  {
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   this.SetSymbolPeriod(::Symbol(),(ENUM_TIMEFRAMES)::Period());
  }
//+------------------------------------------------------------------+

В списке инициализации обнуляются значения количества доступных баров таймсерии, количества баров, хранящихся в списке, и флаг синхронизированности данных с сервером.
Затем устанавливается тип программы, очищается список объектов-баров и ему задаётся флаг сортировки по индексу баров, после чего для списка устанавливаются текущие символ и таймфрейм.
После создания списка объектов-баров обязательно нужно установить для него количество используемых баров при помощи метода SetAmountUsedData() или SyncData(), в состав которого входит метод SetAmountUsedData(). Для индикаторов в метод обязательно вторым параметром нужно передать rates_total.

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

//+------------------------------------------------------------------+
//| Конструктор 2 (таймсерия указанных символа и периода)            |
//+------------------------------------------------------------------+
CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0) : m_bars(0), m_amount(0),m_sync(false)
  {
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetAmountUsedData(amount,0);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает символ и таймфрейм                                 |
//+------------------------------------------------------------------+
void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe);
   this.m_new_bar_obj.SetSymbol(this.m_symbol);
   this.m_new_bar_obj.SetPeriod(this.m_timeframe);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает количество требуемых данных                        |
//+------------------------------------------------------------------+
bool CSeries::SetAmountUsedData(const uint amount,const uint rates_total)
  {
//--- Установим количество доступных баров таймсерии
   this.m_bars=
     (
      //--- Если это индикатор и работа на текущем символе и таймфрейме,
      //--- то записываем переданное в метод значение rates_total,
      //--- иначе - получаем количество из окружения
      this.m_program==PROGRAM_INDICATOR && 
      this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? rates_total : 
      ::Bars(this.m_symbol,this.m_timeframe)
     );
//--- Если удалось записать количество доступных баров истории - установим количество данных в списке:
   if(this.m_bars>0)
     {
      //--- если передано нулевое значение amount,
      //--- то используем либо значение по умолчанию (1000 баров), либо количество доступных баров истории - меньшее из них
      //--- если передано не нулевое значение amount,
      //--- то используем либо значение amount, либо количество доступных баров истории - меньшее из них
      this.m_amount=(amount==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(amount,this.m_bars));
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

В метод передаются необходимое количество данных для списка объектов-баров и общее количество баров текущей таймсерии (для индикаторов).
Затем проверяется тип программы и выбирается, откуда получаем количество доступной истории в переменную m_bars — либо из переданного в метод значения (для индикатора на текущем символе и таймфрейме), либо из окружения. Далее определяем, какое значение должно быть установлено для переменной m_amount — исходя из размера доступной и требуемой истории.

Метод синхронизации данных по символу и таймфрейму с данными на сервере:

//+------------------------------------------------------------------+
//|Синхронизирует данные по символу и таймфрейму с данными на сервере|
//+------------------------------------------------------------------+
bool CSeries::SyncData(const uint amount,const uint rates_total)
  {
//--- Если удалось получить доступное количество баров в таймсерии
//--- и установить размер списка объектов-баров - возвращаем true
   this.m_sync=this.SetAmountUsedData(amount,rates_total);
   if(this.m_sync)
      return true;

//--- Данные ещё не синхронизированы с сервером
//--- Создаём объект-паузу
   CPause *pause=new CPause();
   if(pause==NULL)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ));
      return false;
     }
//--- Устанавливаем длительность паузы в 16 милисекунд (PAUSE_FOR_SYNC_ATTEMPTS) и инициализируем счётчик тиков
   pause.SetWaitingMSC(PAUSE_FOR_SYNC_ATTEMPTS);
   pause.SetTimeBegin(0);
//--- Сделаем пять (ATTEMPTS_FOR_SYNC) попыток получить доступное количество баров в таймсерии
//--- и установить размер списка объектов-баров
   int attempts=0;
   while(attempts<ATTEMPTS_FOR_SYNC && !::IsStopped())
     {
      //--- Если данные синхронизированы с сервером на данный момент
      if(::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED))
        {
         //--- если удалось получить доступное количество баров в таймсерии
         //--- и установить размер списка объектов-баров - прерываем цикл
         this.m_sync=this.SetAmountUsedData(amount,rates_total);
         if(this.m_sync)
            break;
        }
      //--- Данные ещё не синхронизированы.
      //--- Если пауза в 16 мсек завершена
      if(pause.IsCompleted())
        {
         //--- устанавливаем объекту-паузе новое начало следующего ожидания
         //--- и увеличиваем счётчик попыток
         pause.SetTimeBegin(0);
         attempts++;
        }
     }
//--- Удаляем объект-паузу и возвращаем значение m_sync
   delete pause;
   return this.m_sync;
  }
//+------------------------------------------------------------------+

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

Методы создания и обновления списка объектов-баров тоже подробно прокомментированы в листинге, и чтобы не занимать много места для их описания, просто рассмотрим их целиком. Если возникнут какие-либо вопросы по этим методам, то их всегда можно задать в обсуждении к статье:

//+------------------------------------------------------------------+
//| Создаёт список-таймсерию                                         |
//+------------------------------------------------------------------+
int CSeries::Create(const uint amount=0)
  {
//--- Если ещё не установлена требуемая глубина истории для списка
//--- выводим об этом сообщение и возвращаем ноль,
   if(this.m_amount==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return 0;
     }
//--- иначе, если переданное значение amount больше нуля, не равно уже установленному, 
//--- и при этом переданное значение amount меньше доступного количества баров,
//--- устанавливаем новое значение требуемой глубины истории для списка
   else if(amount>0 && this.m_amount!=amount && amount<this.m_bars)
     {
      //--- Если не удалось установить новое значение - возвращаем ноль
      if(!this.SetAmountUsedData(amount,0))
         return 0;
     }
//--- Для массива rates[], в который будем получать исторические данные,
//--- установим признак направленности как в таймсерии,
//--- очистим список объектов-баров и установим ему флаг сортировки по индексу бара
   MqlRates rates[];
   ::ArraySetAsSeries(rates,true);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
   ::ResetLastError();
//--- Получим в массив rates[] исторические данные структуры MqlRates, начиная от текущего бара в количестве m_amount,
//--- и если получить данные не удалось - выводим об этом сообщение и возвращаем ноль
   int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,this.m_amount,rates),err=ERR_SUCCESS;
   if(copied<1)
     {
      err=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                   CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
      return 0;
     }

//--- Исторические данные получены в массив rates[]
//--- В цикле по массиву rates[]
   for(int i=0; i<copied; i++)
     {
      //--- создаём новый обект-бар из данных текущей структуры MqlRates из массива rates[] по индексу цикла
      ::ResetLastError();
      CBar* bar=new CBar(this.m_symbol,this.m_timeframe,i,rates[i]);
      if(bar==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError()));
         continue;
        }
      //--- Если не удалось добавить новый объект-бар в список
      //--- выволим об этом сообщение в журнал с описанием ошибки
      if(!this.m_list_series.Add(bar))
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header(),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
        }
     }
//--- Возвращаем размер созданного списка объектов-баров
   return this.m_list_series.Total();
  }
//+------------------------------------------------------------------+
//| Обновляет список и данные тайм-серии                             |
//+------------------------------------------------------------------+
void CSeries::Refresh(const datetime time=0,
                      const double open=0,
                      const double high=0,
                      const double low=0,
                      const double close=0,
                      const long tick_volume=0,
                      const long volume=0,
                      const int spread=0)
  {
   MqlRates rates[1];
//--- Устанавливаем флаг сортировки списка баров по индексу
   this.m_list_series.Sort(SORT_BY_BAR_INDEX);
//--- Если есть новый бар на символе и периоде
   if(this.IsNewBarManual(time))
     {
      //--- создаём новый объект-бар и добавляем его в конец списка
      CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,0);
      if(new_bar==NULL)
         return;
      if(!this.m_list_series.Add(new_bar))
        {
         delete new_bar;
         return;
        }
      //--- если установленный размер таймсерии больше одного бара - удаляем самый ранний бар
      if(this.m_list_series.Total()>1)
         this.m_list_series.Delete(0);
      //--- сохраняем новое время бара как прошлое для последующей проверки на новый бар
      this.SaveNewBarTime(time);
     }
//--- Получаем индекс последнего бара в списке и объект-бар по этому индексу
   int index=this.m_list_series.Total()-1;
   CBar *bar=this.m_list_series.At(index);
//--- если работа в индикаторе, и таймсерия принадлежит текущему символу и таймфрейму,
//--- копируем в структуру цен бара переданные в метод извне параметры цен
   int copied=1;
   if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period())
     {
      rates[0].time=time;
      rates[0].open=open;
      rates[0].high=high;
      rates[0].low=low;
      rates[0].close=close;
      rates[0].tick_volume=tick_volume;
      rates[0].real_volume=volume;
      rates[0].spread=spread;
     }
//--- иначе - получаем данные в структуру цен бара из окружения
   else
      copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates);
//--- Если цены получены - устанавливаем объекту-бару новые свойства из структуры цен
   if(copied==1)
      bar.SetProperties(rates[0]);
  }
//+------------------------------------------------------------------+

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

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

Рассмотрим два этих метода.

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

//+------------------------------------------------------------------+
//| Возвращает объект-бар по индексу в списке                        |
//+------------------------------------------------------------------+
CBar *CSeries::GetBarByListIndex(const uint index)
  {
   return this.m_list_series.At(this.m_list_series.Total()-index-1);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает объект-бар по индексу в таймсерии                     |
//+------------------------------------------------------------------+
CBar *CSeries::GetBarBySeriesIndex(const uint index)
  {
   CArrayObj *list=this.GetList(BAR_PROP_INDEX,index);
   return(list==NULL || list.Total()==0 ? NULL : list.At(0));
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает Open бара по индексу                                  |
//+------------------------------------------------------------------+
double CSeries::Open(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.Open() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает High бара по индексу таймсерии или списка баров       |
//+------------------------------------------------------------------+
double CSeries::High(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.High() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает Low бара по индексу таймсерии или списка баров        |
//+------------------------------------------------------------------+
double CSeries::Low(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.Low() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает Close бара по индексу таймсерии или списка баров      |
//+------------------------------------------------------------------+
double CSeries::Close(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.Close() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает время бара по индексу таймсерии или списка баров      |
//+------------------------------------------------------------------+
datetime CSeries::Time(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.Time() : 0);
  }
//+-------------------------------------------------------------------+
//|Возвращает тиковый объём бара по индексу таймсерии или списка баров|
//+-------------------------------------------------------------------+
long CSeries::TickVolume(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+--------------------------------------------------------------------+
//|Возвращает реальный объём бара по индексу таймсерии или списка баров|
//+--------------------------------------------------------------------+
long CSeries::RealVolume(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Возвращает спред бара по индексу таймсерии или списка баров      |
//+------------------------------------------------------------------+
int CSeries::Spread(const uint index,const bool from_series=true)
  {
   CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index));
   return(bar!=NULL ? bar.Spread() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

В методы передаются индекс бара и флаг, указывающий на то, что запрашиваемый индекс соответствует (true) направлению индексации как в таймсерии.
Исходя из значения этого флагаполучаем объект-бар либо при помощи метода GetBarBySeriesIndex(), либо при помощи метода GetBarByListIndex(), а затем возвращаем значение запрашиваемого в методе свойства.

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

Чтобы проверить то, что сегодня сделали, нам необходимо сделать так, чтобы внешняя программа знала о созданных классах. Для этого достаточно подключить файл класса CSeries к файлу главного объекта библиотеки CEngine:

//+------------------------------------------------------------------+
//|                                                       Engine.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 "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "TradingControl.mqh"
#include "Objects\Series\Series.mqh"
//+------------------------------------------------------------------+

Теперь в тестовом советнике можно определить переменную с типом класса CSeries чтобы создать и использовать (в данной реализации — не полноценно, а только для тестирования) списки таймсерий с заданным количеством объектов-баров. Что сейчас мы и проверим.

Тест

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

Как будем тестировать...
Создадим в советнике два временных (для теста) объекта класса CSeries — один для минутного таймфрейма (2 бара), а второй — для текущего (10 баров). В обработчике OnInit() установим для них все необходимые параметры и выведем три списка:

  1. список всех баров текущей таймсерии, отсортированный по размеру свечей (от High до Low бара) — выведем короткие описания объектов-баров;
  2. список всех баров текущей таймсерии, отсортированный по индексам баров (от бара 0 до бара 9 ) — выведем короткие описания объектов-баров;
  3. список всех свойств объекта-бара, соответствующего индексу 1 (предыдущий бар) текущей таймсерии — выведем полный список всех свойств бара.

А в обработчике OnTick() на каждом тике будем обновлять обе таймсерии и выводить в журнал запись об открытии нового бара на каждом из таймфреймов этих двух списков-таймсерий — на таймфрейме М1 и на текущем. Для текущей таймсерии при открытии нового бара, помимо вывода записи в журнал, проиграем стандартный звук "news.wav".

В списке глобальных переменных советника определим две переменные с типом класса CSeriesпеременная списка-таймсерии текущего таймфрейма и переменная списка-таймсерии таймфрейма М1:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart35.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>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_SET_STOP_LOSS,
   BUTT_SET_TAKE_PROFIT,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_TRAILING_ALL
  };
#define TOTAL_BUTT   (20)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;   // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds
//--- global variables
CEngine        engine;
CSeries        series;
CSeries        series_m1;
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         used_symbols;
string         array_used_symbols[];
bool           testing;
uchar          group1;
uchar          group2;
double         g_point;
int            g_digits;
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Вызов данной функции выводит в журнал список констант перечисления, 
//--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант
   //EnumNumbersTest();

//--- Установка глобальных переменных советника
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq);
   bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq);
   g_point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
//--- Инициализация случайных номеров групп
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Инициализация библиотеки DoEasy
   OnInitDoEasy();
   
//--- Проверка и удаление неудалённых графических объектов советника
   if(IsPresentObects(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Создание панели кнопок
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Установка состояния кнопки активизации трейлингов
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- Сброс состояний кнопок активизации работы отложенными запросами
   for(int i=0;i<14;i++)
     {
      ButtonState(butt_data[i].name+"_PRICE",false);
      ButtonState(butt_data[i].name+"_TIME",false);
     }

//--- Проверка воспроизведения стандартного звука по макроподстановке и пользовательского звука по описанию
   engine.PlaySoundByDescription(SND_OK);
   Sleep(600);
   engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"));

//--- Установка параметров объекта-таймсерии М1
   series_m1.SetSymbolPeriod(Symbol(),PERIOD_M1);
//--- Если данные по символу и минутному таймфрейму синхронизированы
   if(series_m1.SyncData(2,0))
     {
      //--- создадим список-таймсерию из двух баров (текущий и прошлый),
      //--- и если таймсерия создана, выведем об этом сообщение в журнал
      int total=series_m1.Create(2);
      if(total>0)
         Print(TextByLanguage("Создана таймсерия М1 с размером ","Created timeseries M1 with size "),(string)total);
     }
//--- Проверка заполнения ценовых данных по текущему символу и таймфрейму
   series.SetSymbolPeriod(Symbol(),(ENUM_TIMEFRAMES)Period());
//--- Если данные по символу и текущему таймфрейму синхронизированы
   if(series.SyncData(10,0))
     {
      //--- создадим список-таймсерию из десяти баров (бары 0 - 9),
      //--- и если таймсерия создана, выведем три списка:
      //--- 1. список баров, отсортированных по размеру свечей (от High до Low баров)
      //--- 2. список баров, отсортированных по индексам баров (в порядке следования их в таймсерии)
      //--- 3. полный список всех свойств предыдущего объекта-бара (свойства бара с индексом таймсерии 1)
      int total=series.Create(10);
      if(total>0)
        {
         CArrayObj *list=series.GetList();
         CBar *bar=NULL;
         //--- Вывод кратких свойств списка баров по размеру свечи
         Print("\n",TextByLanguage("Бары, сортированные по размеру свечи от High до Low:","Bars, sorted by size candle from High to Low:"));
         list.Sort(SORT_BY_BAR_CANDLE_SIZE);
         for(int i=0;i<total;i++)
           {
            bar=series.GetBarByListIndex(i);
            if(bar==NULL)
               continue;
            bar.PrintShort();
           }
         //--- Вывод кратких свойств списка баров по индексу в таймсерии
         Print("\n",TextByLanguage("Бары, сортированные по индексу таймсерии:","Bars, sorted by timeseries index:"));
         list.Sort(SORT_BY_BAR_INDEX);
         for(int i=0;i<total;i++)
           {
            bar=series.GetBarByListIndex(i);
            if(bar==NULL)
               continue;
            bar.PrintShort();
           }
         //--- Вывод всех свойств бара 1
         Print("");
         list=CSelect::ByBarProperty(list,BAR_PROP_INDEX,1,EQUAL);
         if(list.Total()==1)
           {
            bar=list.At(0);
            bar.Print();
           }
        }
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();       // Работа в таймере
      PressButtonsControl();  // Контроль нажатия кнопок
      EventsHandling();       // Работа с событиями
     }
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();    // Трейлинг позиций
      TrailingOrders();       // Трейлинг отложенных ордеров
     }
//--- Проверка обновления текущей таймсерии и таймсерии М1
   series.Refresh();
   if(series.IsNewBar(0))
     {
      Print("New bar on ",series.Symbol()," ",TimeframeDescription(series.Period())," ",TimeToString(series.Time(0)));
      engine.PlaySoundByDescription(SND_NEWS);
     }
   series_m1.Refresh();
   if(series_m1.IsNewBar(0))
     {
      Print("New bar on ",series_m1.Symbol()," ",TimeframeDescription(series_m1.Period())," ",TimeToString(series_m1.Time(0)));
     }
  }
//+------------------------------------------------------------------+

Скомпилируем советник и запустим его на графике символа.
В журнал будут выведены записи сначала о создании списка-таймсерии для таймфрейма М1 в количестве двух баров, затем будет выведен список баров, сортированный по размеру свечей, далее список баров, сортированный в порядке индексов баров в таймсерии, и наконец — все свойства объекта-бара с индексом 1 в таймсерии:

Account 15585535: Artyom Trishkin (MetaQuotes Software Corp.) 9999.40 USD, 1:100, Demo account MetaTrader 5
Work only with the current symbol. The number of symbols used: 1
Created timeseries M1 with size 2
 
Bars, sorted by size candle from High to Low:
Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar
Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar
Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar
Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar
Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar
Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar
Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar
Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar
Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar
Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar
 
Bars, sorted by timeseries index:
Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar
Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar
Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar
Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar
Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar
Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar
Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar
Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar
Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar
Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar
 
============= The beginning of the event parameter list (Bar "EURUSD" H1[1]) =============
Timeseries index: 1
Type: Bearish bar
Timeframe: H1
Spread: 1
Tick volume: 1753
Real volume: 0
Period start time: 2020.02.12 11:00:00
Sequence day number in the year: 042
Year: 2020
Month: February
Day of week: Wednesday
Day od month: 12
Hour: 11
Minute: 00
------
Price open: 1.09215
Highest price for the period: 1.09232
Lowest price for the period: 1.09114
Price close: 1.09202
Candle size: 0.00118
Candle body size: 0.00013
Top of the candle body: 1.09215
Bottom of the candle body: 1.09202
Candle upper shadow size: 0.00017
Candle lower shadow size: 0.00088
------
Symbol: "EURUSD"
============= End of the parameter list (Bar "EURUSD" H1[1]) =============

Теперь запустим советник в визуальном режиме тестера на таймфрейме М5 и посмотрим на сообщения журнала тестера об открытии новых баров:

Как видим каждое пятое сообщение — об открытии нового бара на М5, а между ними — сообщения об открытии нового бара на М1.

Что дальше

В следующей статье создадим класс-коллекцию списков баров. 

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

К содержанию

Статьи предыдущей серии:

Часть 1. Концепция, организация данных
Часть 2. Коллекция исторических ордеров и сделок
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска
Часть 4. Торговые события. Концепция
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу
Часть 6. События на счёте с типом неттинг
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций
Часть 8. События модификации ордеров и позиций
Часть 9. Совместимость с MQL4 — Подготовка данных
Часть 10. Совместимость с MQL4 — События открытия позиций и активации отложенных ордеров
Часть 11. Совместимость с MQL4 — События закрытия позиций
Часть 12. Класс объекта "аккаунт", коллекция объектов-аккаунтов
Часть 13. События объекта "аккаунт"
Часть 14. Объект "Символ"
Часть 15. Коллекция объектов-символов
Часть 16. События коллекции символов
Часть 17. Интерактивность объектов библиотеки
Часть 18. Интерактивность объекта-аккаунт и любых других объектов библиотеки
Часть 19. Класс сообщений библиотеки
Часть 20. Создание и хранение ресурсов программы
Часть 21. Торговые классы — Базовый кроссплатформенный торговый объект
Часть 22. Торговые классы — Основной торговый класс, контроль ограничений
Часть 23. Торговые классы — Основной торговый класс, контроль допустимых параметров
Часть 24. Торговые классы — Основной торговый класс, автоматическая коррекция ошибочных параметров
Часть 25. Торговые классы — Основной торговый класс, обработка ошибок, возвращаемых торговым сервером
Часть 26. Работа с отложенными торговыми запросами - первая реализация (открытие позиций)
Часть 27. Работа с отложенными торговыми запросами - выставление отложенных ордеров
Часть 28. Работа с отложенными торговыми запросами - закрытие, удаление, модификации
Часть 29. Работа с отложенными торговыми запросами - классы объектов-запросов
Часть 30. Работа с отложенными запросами - управление объектами-запросами
Часть 31. Работа с отложенными запросами - открытие позиций по условиям
Часть 32. Работа с отложенными запросами - установка отложенных ордеров по условиям
Часть 33. Работа с отложенными запросами - закрытие позиций (полное, частичное и встречное) по условиям
Часть 34. Работа с отложенными запросами - удаление ордеров, модификация ордеров и позиций по условиям