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

Artyom Trishkin | 6 марта, 2020

Содержание


Концепция

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

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

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


Доработка ранее созданных объектов таймсерий

Большинство объектов библиотеки являются наследниками базового объекта всех объектов библиотеки, который в свою очередь унаследован от базового класса для построения Cтандартной библиотеки MQL5.
С ростом потребностей библиотеки разросся и класс CBaseObj базового объекта библиотеки, и теперь, если наследовать от него новые объекты, то они получают дополнительные, в ряде случаев совсем не нужные методы.
Для решения этой проблемы разделим класс базового объекта на два:

Таким образом, те объекты, которым необходимы базовые свойства и методы, мы будем наследовать от CBaseObj, а объекты, которым необходим событийный функционал, будем наследовать от CBaseObjExt.

Как сделаем... Просто сначала переименуем класс базового объекта CBaseObj, находящийся в файле \MQL5\Include\DoEasy\Objects\BaseObj.mqh в класс с названием CBaseObjExt и скомпилируем файл главного объекта библиотеки CEngine, находящийся по адресу \MQL5\Include\DoEasy\Engine.mqh, что вызовет большой список ошибок компиляции (мы же переименовали класс базового объекта библиотеки).

Просто пройдём по списку всех ошибок, указывающих на отсутствие класса CBaseObj, и заменим в листингах классов все вхождения строк "CBaseObj" на "CBaseObjExt". Повторная компиляция с исправленными наименованиями класса базового объекта должна пройти успешно.

Теперь в листинге класса базового объекта добавим новый класс, который назовём CBaseObj, унаследуем его от базового объекта библиотеки MQL5, и затем перенесём из класса CBaseObjExt все переменные и методы, которые должны быть в новом классе базового объекта, а класс CBaseObjExt унаследуем от CBaseObj.

Звучит сложно, но если поглядеть на листинг классов, то всё станет понятно (полный листинг приводить нет смысла — всё можно посмотреть в прилагаемых к статье файлах):

//+------------------------------------------------------------------+
//| Класс базового объекта для всех объектов библиотеки              |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   ENUM_LOG_LEVEL    m_log_level;                              // Уровень логирования
   ENUM_PROGRAM_TYPE m_program;                                // Тип программы
   bool              m_first_start;                            // Флаг первого запуска
   bool              m_use_sound;                              // Флаг проигрывания установленного объекту звука
   bool              m_available;                              // Флаг использования объекта-наследника в программе
   int               m_global_error;                           // Код глобальной ошибки
   long              m_chart_id_main;                          // Идентификатор графика управляющей программы
   long              m_chart_id;                               // Идентификатор графика
   string            m_name;                                   // Наименование объекта
   string            m_folder_name;                            // Имя папки хранения объектов-наследников CBaseObj
   string            m_sound_name;                             // Имя звукового файла объекта
   int               m_type;                                   // Тип объекта (соответствует идентификаторам коллекций)

public:
//--- (1) Устанавливает, (2) возвращает уровень логирования ошибок
   void              SetLogLevel(const ENUM_LOG_LEVEL level)         { this.m_log_level=level;                 }
   ENUM_LOG_LEVEL    GetLogLevel(void)                         const { return this.m_log_level;                }
//--- (1) Устанавливает, (2) возвращает идентификатор графика управляющей программы
   void              SetMainChartID(const long id)                   { this.m_chart_id_main=id;                }
   long              GetMainChartID(void)                      const { return this.m_chart_id_main;            }
//--- (1) Устанавливает, (2) возвращает идентификатор графика
   void              SetChartID(const long id)                       { this.m_chart_id=id;                     }
   long              GetChartID(void)                          const { return this.m_chart_id;                 }
//--- (1) Устанавливает имя подпапки, (2) возвращает имя папки для хранения файлов объектов-наследников
   void              SetSubFolderName(const string name)             { this.m_folder_name=DIRECTORY+name;      }
   string            GetFolderName(void)                       const { return this.m_folder_name;              }
//--- (1) Устанавливает, (2) возвращает имя звукового файла объекта-наследника
   void              SetSoundName(const string name)                 { this.m_sound_name=name;                 }
   string            GetSoundName(void)                        const { return this.m_sound_name;               }
//--- (1) Устанавливает, (2) возвращает флаг проигрывания звуков объектов-наследников
   void              SetUseSound(const bool flag)                    { this.m_use_sound=flag;                  }
   bool              IsUseSound(void)                          const { return this.m_use_sound;                }
//--- (1) Устанавливает, (2) возвращает флаг использования объекта-наследника в программе
   void              SetAvailable(const bool flag)                   { this.m_available=flag;                  }
   bool              IsAvailable(void)                         const { return this.m_available;                }
//--- Возвращает код глобальной ошибки
   int               GetError(void)                            const { return this.m_global_error;             }
//--- Возвращает наименование объекта
   string            GetName(void)                             const { return this.m_name;                     }
//--- Возвращает тип объекта
   virtual int       Type(void)                                const { return this.m_type;                     }
//--- Конструктор
                     CBaseObj() : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)),
                                  m_global_error(ERR_SUCCESS),
                                  m_log_level(LOG_LEVEL_ERROR_MSG),
                                  m_chart_id_main(::ChartID()),
                                  m_chart_id(::ChartID()),
                                  m_folder_name(DIRECTORY),
                                  m_sound_name(""),
                                  m_name(__FUNCTION__),
                                  m_type(0),
                                  m_use_sound(false),
                                  m_available(true),
                                  m_first_start(true) {}
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс расширенного базового объекта для всех объектов библиотеки |
//+------------------------------------------------------------------+
#define  CONTROLS_TOTAL    (10)
class CBaseObjExt : public CBaseObj
  {
private:
   int               m_long_prop_total;
   int               m_double_prop_total;
   //--- Заполняет массив свойств объекта
   template<typename T> bool  FillPropertySettings(const int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],int &event_id);
protected:
   CArrayObj         m_list_events_base;                       // Список базовых событий объекта
   CArrayObj         m_list_events;                            // Список событий объекта
   MqlTick           m_tick;                                   // Структура тика для получения котировочных данных
   double            m_hash_sum;                               // Хэш-сумма данных объекта
   double            m_hash_sum_prev;                          // Хэш-сумма данных объекта на прошлой проверке
   int               m_digits_currency;                        // Число знаков после запятой валюты счёта
   bool              m_is_event;                               // Флаг события объекта
   int               m_event_code;                             // Код события объекта
   int               m_event_id;                               // Идентификатор события (равен значению свойства объекта)

//--- Данные для хранения, контроля и возврата отслеживаемых свойств:
//--- [Индекс свойства][0] Контролируемая величина прироста значения свойства
//--- [Индекс свойства][1] Контролируемая величина уменьшения значения свойства
//--- [Индекс свойства][2] Контрольный уровень значения свойства
//--- [Индекс свойства][3] Значение свойства
//--- [Индекс свойства][4] Величина изменения значения свойства
//--- [Индекс свойства][5] Флаг изменения значения свойства больше, чем на величину прироста
//--- [Индекс свойства][6] Флаг изменения значения свойства больше, чем на величину уменьшения
//--- [Индекс свойства][7] Флаг увеличения значения свойства больше контрольного уровня
//--- [Индекс свойства][8] Флаг уменьшения значения свойства меньше контрольного уровня
//--- [Индекс свойства][9] Флаг равенства значения свойства контрольному уровню
   long              m_long_prop_event[][CONTROLS_TOTAL];         // Массив для хранения величин целочисленных свойств объекта и контролируемых значений изменения свойств
   double            m_double_prop_event[][CONTROLS_TOTAL];       // Массив для хранения величин вещественных свойств объекта и контролируемых значений изменения свойств
   long              m_long_prop_event_prev[][CONTROLS_TOTAL];    // Массив для хранения величин контролируемых целочисленных свойств объекта на прошлой проверке
   double            m_double_prop_event_prev[][CONTROLS_TOTAL];  // Массив для хранения величин контролируемых вещественных свойств объекта на прошлой проверке

//--- Возвращает (1) время в милисекундах, (2) милисекунды из значения времени из MqlTick
   long              TickTime(void)                            const { return #ifdef __MQL5__ this.m_tick.time_msc #else this.m_tick.time*1000 #endif ;  }
   ushort            MSCfromTime(const long time_msc)          const { return #ifdef __MQL5__ ushort(this.TickTime()%1000) #else 0 #endif ;              }
//--- возвращает факт наличия кода события в событии объекта
   bool              IsPresentEventFlag(const int change_code) const { return (this.m_event_code & change_code)==change_code; }
//--- Возвращает число знаков после запятой валюты счёта
   int               DigitsCurrency(void)                      const { return this.m_digits_currency; }
//--- Возвращает количество знаков после запятой в double-значении
   int               GetDigits(const double value)             const;

//--- Устанавливает размер массива (1) целочисленных, (2) вещественных контролируемых свойств объекта
   bool              SetControlDataArraySizeLong(const int size);
   bool              SetControlDataArraySizeDouble(const int size);
//--- Проверяет размер массива свойств объекта
   bool              CheckControlDataArraySize(bool check_long=true);
   
//--- Проверяет список изменений свойств объекта и создаёт событие
   void              CheckEvents(void);
   
//--- (1) Упаковывает ushort-число в переданное long-число
   long              UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value);
   
protected:
//--- (1) преобразует ushort-значение в заданный байт long-числа
   long              UshortToByte(const ushort value,const uchar to_byte)  const;
   
public:
//--- Устанавливает величину контролируемого (1) приращения, (2) уменьшения, (3) контрольный уровень значения свойства объекта
   template<typename T> void  SetControlledValueINC(const int property,const T value);
   template<typename T> void  SetControlledValueDEC(const int property,const T value);
   template<typename T> void  SetControlledValueLEVEL(const int property,const T value);

//--- Возвращает установленную величину контролируемого приращения (1) целочисленных, (2) вещественных свойств объекта
   long              GetControlledLongValueINC(const int property)      const { return this.m_long_prop_event[property][0];                           }
   double            GetControlledDoubleValueINC(const int property)    const { return this.m_double_prop_event[property-this.m_long_prop_total][0];  }
//--- Возвращает установленную величину контролируемого уменьшения (1) целочисленных, (2) вещественных свойств объекта
   long              GetControlledLongValueDEC(const int property)      const { return this.m_long_prop_event[property][1];                           }
   double            GetControlledDoubleValueDEC(const int property)    const { return this.m_double_prop_event[property-this.m_long_prop_total][1];  }
//--- Возвращает установленный контрольный уровень (1) целочисленных, (2) вещественных свойств объекта
   long              GetControlledLongValueLEVEL(const int property)    const { return this.m_long_prop_event[property][2];                           }
   double            GetControlledDoubleValueLEVEL(const int property)  const { return this.m_double_prop_event[property-this.m_long_prop_total][2];  }

//--- Возвращает текущее значение (1) целочисленного, (2) вещественного свойства объекта
   long              GetPropLongValue(const int property)               const { return this.m_long_prop_event[property][3];                           }
   double            GetPropDoubleValue(const int property)             const { return this.m_double_prop_event[property-this.m_long_prop_total][3];  }
//--- Возвращает величину изменения контролируемого (1) целочисленного, (2) вещественного свойства объекта
   long              GetPropLongChangedValue(const int property)        const { return this.m_long_prop_event[property][4];                           }
   double            GetPropDoubleChangedValue(const int property)      const { return this.m_double_prop_event[property-this.m_long_prop_total][4];  }
//--- Возвращает Флаг изменения значения (1) целочисленного, (2) вещественного свойства больше, чем на величину прироста
   long              GetPropLongFlagINC(const int property)             const { return this.m_long_prop_event[property][5];                           }
   double            GetPropDoubleFlagINC(const int property)           const { return this.m_double_prop_event[property-this.m_long_prop_total][5];  }
//--- Возвращает Флаг изменения значения (1) целочисленного, (2) вещественного свойства больше, чем на величину уменьшения
   long              GetPropLongFlagDEC(const int property)             const { return this.m_long_prop_event[property][6];                           }
   double            GetPropDoubleFlagDEC(const int property)           const { return this.m_double_prop_event[property-this.m_long_prop_total][6];  }
//--- Возвращает Флаг увеличения значения (1) целочисленного, (2) вещественного свойства больше контрольного уровня
   long              GetPropLongFlagMORE(const int property)            const { return this.m_long_prop_event[property][7];                           }
   double            GetPropDoubleFlagMORE(const int property)          const { return this.m_double_prop_event[property-this.m_long_prop_total][7];  }
//--- Возвращает Флаг уменьшения значения (1) целочисленного, (2) вещественного свойства меньше контрольного уровня
   long              GetPropLongFlagLESS(const int property)            const { return this.m_long_prop_event[property][8];                           }
   double            GetPropDoubleFlagLESS(const int property)          const { return this.m_double_prop_event[property-this.m_long_prop_total][8];  }
//--- Возвращает Флаг равенства значений (1) целочисленного, (2) вещественного свойства и контрольного уровня
   long              GetPropLongFlagEQUAL(const int property)           const { return this.m_long_prop_event[property][9];                           }
   double            GetPropDoubleFlagEQUAL(const int property)         const { return this.m_double_prop_event[property-this.m_long_prop_total][9];  }

//--- Сбрасывает переменные (1) отслеживаемых, (2) контролируемых данных объекта (переназначить можно в наследниках)
   void              ResetChangesParams(void);
   virtual void      ResetControlsParams(void);
//--- Добавляет (1) событие объекта в список, (2) причину события объекта в список
   bool              EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam);
   bool              EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value);
//--- Устанавливает/Возвращает флаг произошедшего события в данных объекта
   void              SetEvent(const bool flag)                       { this.m_is_event=flag;                   }
   bool              IsEvent(void)                             const { return this.m_is_event;                 }
//--- Возвращает (1) список событий, (2) код события объекта, (3) код глобальной ошибки
   CArrayObj        *GetListEvents(void)                             { return &this.m_list_events;             }
   int               GetEventCode(void)                        const { return this.m_event_code;               }
//--- Возвращает (1) объект-событие, (2) базовое событие по его номеру в списке
   CEventBaseObj    *GetEvent(const int shift=WRONG_VALUE,const bool check_out=true);
   CBaseEvent       *GetEventBase(const int index);
//--- Возвращает количество (1) событий объекта
   int               GetEventsTotal(void)                      const { return this.m_list_events.Total();      }
//--- Обновляет данные объекта для поиска изменений в данных объекта (Вызов из наследников: CBaseObj::Refresh())
   virtual void      Refresh(void);
//--- Возвращает описание события объекта
   string            EventDescription(const int property,
                                      const ENUM_BASE_EVENT_REASON reason,
                                      const int source,
                                      const string value,
                                      const string property_descr,
                                      const int digits);

//--- Расположение данных в int-значении магика
      //-----------------------------------------------------------
      //  bit   32|31       24|23       16|15        8|7         0|
      //-----------------------------------------------------------
      //  byte    |     3     |     2     |     1     |     0     |
      //-----------------------------------------------------------
      //  data    |   uchar   |   uchar   |         ushort        |
      //-----------------------------------------------------------
      //  descr   |pend req id| id2 | id1 |          magic        |
      //-----------------------------------------------------------
      
//--- Устанавливает идентификатор (1) первой группы, (2) второй группы, (3) отложенного запроса в значение магика
   void              SetGroupID1(const uchar group,uint &magic)            { magic &=0xFFF0FFFF; magic |= uint(this.ConvToXX(group,0)<<16);  }
   void              SetGroupID2(const uchar group,uint &magic)            { magic &=0xFF0FFFFF; magic |= uint(this.ConvToXX(group,1)<<16);  }
   void              SetPendReqID(const uchar id,uint &magic)              { magic &=0x00FFFFFF; magic |= (uint)id<<24;                      }
//--- Конветирует значение 0 - 15 в нужные биты uchar-числа (0 - младшие, 1 - старшие)
   uchar             ConvToXX(const uchar number,const uchar index)  const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));   }
//--- Возвращает (1) заданный магический номер, идентификатор (2) первой группы, (3) второй группы, (4) отложенного запроса из значения магика
   ushort            GetMagicID(const uint magic)                    const { return ushort(magic & 0xFFFF);                                  }
   uchar             GetGroupID1(const uint magic)                   const { return uchar(magic>>16) & 0x0F;                                 }
   uchar             GetGroupID2(const uint magic)                   const { return uchar((magic>>16) & 0xF0)>>4;                            }
   uchar             GetPendReqID(const uint magic)                  const { return uchar(magic>>24) & 0xFF;                                 }

//--- Конструктор
                     CBaseObjExt();
  };
//+------------------------------------------------------------------+

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

   bool              m_use_sound;                              // Флаг проигрывания установленного объекту звука
   bool              m_available;                              // Флаг использования объекта-наследника в программе
   string            m_sound_name;                             // Имя звукового файла объекта

и добавлены соответствующие методы установки и возврата значений этих переменных:

//--- (1) Устанавливает, (2) возвращает имя звукового файла объекта-наследника
   void              SetSoundName(const string name)                 { this.m_sound_name=name;                 }
   string            GetSoundName(void)                        const { return this.m_sound_name;               }
//--- (1) Устанавливает, (2) возвращает флаг проигрывания звуков объектов-наследников
   void              SetUseSound(const bool flag)                    { this.m_use_sound=flag;                  }
   bool              IsUseSound(void)                          const { return this.m_use_sound;                }
//--- (1) Устанавливает, (2) возвращает флаг использования объекта-наследника в программе
   void              SetAvailable(const bool flag)                   { this.m_available=flag;                  }
   bool              IsAvailable(void)                         const { return this.m_available;                }

Имя звукового файла для объекта-наследника позволит задать (SetSoundName()) или получить (GetSoundName()) имя звукового файла объекта, который может быть воспроизведён при наличии какого-либо условия, контролирующего свойства этого объекта.

Помимо того, что имя может быть задано объекту, можно будет включать/выключать (SetUseSound()) разрешение на проигрывание этого файла и получать флаг установленного разрешения на проигрывание файла (IsUseSound()).

Что означает "флаг использования объекта-наследника в программе", и для чего нужно его устанавливать и получать...
Например, у нас есть объекты таймсерий символа для периодов М5, М30, Н1 и D1. Но мы в какой-то момент времени не хотим обрабатывать таймсерию М5. Вот установкой/снятием этого флага мы и можем управлять необходимость контроля библиотекой событий, например, нового бара для таймсерии М5.
Наличие такого флага в базовом объекте всех объектов библиотеки позволит нам гибко управлять необходимостью обработки состояний свойств таких объектов. Иными словами: если нужно контролировать и использовать объект в программе — установили флаг, если такая возможность отпала — сняли флаг.

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

Какие классы теперь унаследованы от расширенного базового объекта CBaseObjExt:

  • CAccountsCollection в файле \MQL5\Include\DoEasy\Collections\AccountsCollection.mqh
  • CEventsCollection в файле \MQL5\Include\DoEasy\Collections\EventsCollection.mqh
    (заменены строки "CBaseObj::EventAdd" на "CBaseObjExt::EventAdd")
  • CSymbolsCollection в файле \MQL5\Include\DoEasy\Collections\SymbolsCollection.mqh
  • CAccount в файле \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh
    (заменены строки "CBaseObj::Refresh()" на "CBaseObjExt::Refresh()")
  • COrder в файле \MQL5\Include\DoEasy\Objects\Orders\Order.mqh
  • CPendRequest в файле \MQL5\Include\DoEasy\Objects\PendRequest\PendRequest.mqh
    (заменены строки "return CBaseObj::GetMagicID" на "return CBaseObjExt::GetMagicID",
     CBaseObj::GetGroupID1" на "return CBaseObjExt::GetGroupID1" и CBaseObj::GetGroupID2" на "return CBaseObjExt::GetGroupID2")
  • CSymbol в файле \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh
    (заменены строки "CBaseObj::Refresh()" на "CBaseObjExt::Refresh()")
  • CTradeObj в файле \MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh
    (удалена переменная bool m_use_sound и методы SetUseSound() и IsUseSound() — теперь они в базовом классе)
  • CTrading в файле \MQL5\Include\DoEasy\Trading.mqh
    (удалена переменная bool m_use_sound и метод IsUseSounds() — теперь они в базовом классе)

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

//+------------------------------------------------------------------+
//|                                                        Datas.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"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define INPUT_SEPARATOR                (",")          // Разделитель в строке входных параметров
#define TOTAL_LANG                     (2)            // Количество используемых языков
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Режимы работы с символами                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+
//| Режимы работы с таймфреймами                                     |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов
  };
//+------------------------------------------------------------------+
   MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING,            // Ошибка. Строка предопределённых символов пустая, будет использоваться
   MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY,        // Не удалось подготовить массив используемых символов. Ошибка 
   MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING,            // Ошибка. Строка предопределённых периодов пустая, будет использоваться
   MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY,        // Не удалось подготовить массив используемых периодов. Ошибка 
   MSG_LIB_SYS_INVALID_ORDER_TYPE,                    // Неправильный тип ордера:

...

//--- CTimeSeries
   MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL,              // Сначала нужно установить символ при помощи SetSymbol()
   MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME,            // Неизвестный таймфрейм
   MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ,             // Не удалось получить объект-таймсерию
   MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH,            // Запрошенная глубина истории
   MSG_LIB_TEXT_TS_ACTUAL_DEPTH,                      // Фактическая глубина истории
   MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA,               // Создано исторических данных
   MSG_LIB_TEXT_TS_HISTORY_BARS,                      // Баров истории на сервере
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES,            // Таймсерии символа
   MSG_LIB_TEXT_TS_TEXT_TIMESERIES,                   // Таймсерия
   MSG_LIB_TEXT_TS_TEXT_REQUIRED,                     // Запрошено
   MSG_LIB_TEXT_TS_TEXT_ACTUAL,                       // Фактически
   MSG_LIB_TEXT_TS_TEXT_CREATED,                      // Создано
   MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS,                 // На сервере
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_FIRSTDATE,             // Самая первая дата по символу-периоду
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_LASTBAR_DATE,          // Время открытия последнего бара по символу-периоду
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_SERVER_FIRSTDATE,      // Самая первая дата в истории по символу на сервере
   MSG_LIB_TEXT_TS_TEXT_SYMBOL_TERMINAL_FIRSTDATE,    // Самая первая дата в истории по символу в клиентском терминале
  
};
//+------------------------------------------------------------------+

...

   {"Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols is empty, the Symbol will be used: "},
   {"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create an array of used symbols. Error "},
   {"Ошибка. Строка предопределённых периодов пустая, будет использоваться ","Error. String of predefined periods is empty, the Period will be used: "},
   {"Не удалось подготовить массив используемых периодов. Ошибка ","Failed to create an array of used periods. Error "},
   {"Неправильный тип ордера: ","Invalid order type: "},
...
   {"Сначала нужно установить символ при помощи SetSymbol()","First you need to set the Symbol using SetSymbol()"},
   {"Неизвестный таймфрейм","Unknown timeframe"},
   {"Не удалось получить объект-таймсерию ","Failed to get timeseries object "},
   {"Запрошенная глубина истории: ","Required history depth: "},
   {"Фактическая глубина истории: ","Actual history depth: "},
   {"Создано исторических данных: ","Total historical data created: "},
   {"Баров истории на сервере: ","Server history Bars number: "},
   {"Таймсерия символа","Symbol time series"},
   {"Таймсерия","Timeseries"},
   {"Запрошено","Required"},
   {"Фактически","Actual"},
   {"Создано","Created"},
   {"На сервере","On server"},
   {"Самая первая дата по символу-периоду","The very first date for the symbol-period"},
   {"Время открытия последнего бара по символу-периоду","Open time of the last bar of the symbol-period"},
   {"Самая первая дата в истории по символу на сервере","The very first date in the history of the symbol on the server"},
   {"Самая первая дата в истории по символу в клиентском терминале","The very first date in the history of the symbol in the client terminal"},
  
};
//+---------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Класс "Таймсерия"                                                |
//+------------------------------------------------------------------+
class CSeries : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Таймфрейм
   string            m_symbol;                                          // Символ
   string            m_period_description;                              // Строковое описание таймфрейма
   datetime          m_firstdate;                                       // Самая первая дата по символу-периоду на данный момент
   datetime          m_lastbar_date;                                    // Время открытия последнего бара по символу-периоду
   uint              m_amount;                                          // Количество используемых данных таймсерии
   uint              m_required;                                        // Требуемое количество используемых данных таймсерии
   uint              m_bars;                                            // Количество баров в истории по символу и таймфрейму
   bool              m_sync;                                            // Флаг синхронизированности данных
   CArrayObj         m_list_series;                                     // Список-таймсерия
   CNewBarObj        m_new_bar_obj;                                     // Объект "Новый бар"
//--- Устанавливает самую первую дата по символу-периоду на данный момент и новое время открытия последнего бара по символу-периоду
   void              SetServerDate(void)
                       {
                        this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
                        this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
                       }
public:

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

Переменная m_firstdate будет хранить самую первую дату по символу-периоду на данный момент, получаемую посредством функции SeriesInfoInteger() с идентификатором свойства SERIES_FIRSTDATE.
Переменная m_lastbar_date будет хранить время открытия последнего бара по символу-периоду, получаемое посредством функции SeriesInfoInteger() с идентификатором свойства SERIES_LASTBAR_DATE.
Обе переменные будут устанавливаться посредством вызова метода SetServerDate() только в момент создания объекта класса или изменения данных на новом баре, а также при установке нового символа или таймфрейма объекту таймсерии.

Переменная m_required будет хранить требуемое (последнее запрошенное) количество используемых данных таймсерии. В момент запроса нужного количества баров таймсерии может оказаться, что запрошенного количества данных для создания таймсерии попросту нет на сервере. В таком случае таймсерия создаётся в количестве данных, равных количеству доступной истории на сервере. А в этой переменной всегда будет храниться последнее запрошенное количество данных, независимо от того, сколько данных на самом деле удалось получить и создать. И, к слову, исходя из того, что мы используем понятие "запрошенные данные", в классе изменилось название методов, в которых присутствовало "Amount" (количество) — теперь оно заменено на "Required" (требуемый, запрошенный).

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

public:
//--- Возвращает (1) себя, (2) список-таймсерию
   CSeries          *GetObject(void)                                    { return &this;         }
   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) таймфрейм, (3) символ и таймфрейм, (4) количество используемых данных таймсерии
   void              SetSymbol(const string symbol);
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe);
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool              SetRequiredUsedData(const uint required,const uint rates_total);

//--- Возвращает (1) символ, (2) таймфрейм, количество (3) используемых, (4) запрошенных данных таймсерии,
//--- (5) количество баров в таймсерии, (6) самую первую дату, (7) время открытия последнего бара по символу-периоду,
//--- флаг нового бара с (8) автоматическим, (9) ручным управлением временем
   string            Symbol(void)                                          const { return this.m_symbol;                            }
   ENUM_TIMEFRAMES   Timeframe(void)                                       const { return this.m_timeframe;                         }
   ulong             AvailableUsedData(void)                               const { return this.m_amount;                            }
   ulong             RequiredUsedData(void)                                const { return this.m_required;                          }
   ulong             Bars(void)                                            const { return this.m_bars;                              }
   datetime          FirstDate(void)                                       const { return this.m_firstdate;                         }
   datetime          LastBarDate(void)                                     const { return this.m_lastbar_date;                      }
   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) в таймсерии, (3) реальный размер списка
   CBar             *GetBarByListIndex(const uint index);
   CBar             *GetBarBySeriesIndex(const uint index);
   int               DataTotal(void)                                       const { return this.m_list_series.Total();               }
//--- Возвращает (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);

//--- (1) Устанавливает, (2) возвращает имя звукового файла события таймсерии "Новый бар"
   void              SetNewBarSoundName(const string name)                       { this.m_new_bar_obj.SetSoundName(name);           }
   string            NewBarSoundName(void)                                 const { return this.m_new_bar_obj.GetSoundName();        }

//--- Сохраняет время нового бара при ручном управлении временем
   void              SaveNewBarTime(const datetime time)                         { this.m_new_bar_obj.SaveNewBarTime(time);         }
//--- Синхронизирует данные по символу и таймфрейму с данными на сервере
   bool              SyncData(const uint required,const uint rates_total);
//--- (1) Создаёт, (2) обновляет список-таймсерию
   int               Create(const uint required=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);
                             
//--- Возвращает наименование таймсерии
   string            Header(void);
//--- Выводит в журнал (1) описание таймсерии, (2) краткое описание таймсерии
   void              Print(void);
   void              PrintShort(void);


//--- Конструкторы
                     CSeries(void);
                     CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
  };
//+------------------------------------------------------------------+

Метод GetObject() возвращает в управляющую программу указатель на весь объект-таймсерию. Позволяет получить целиком объект-таймсерию и работать с ним в своей программе.

Метод RequiredUsedData() возвращает в вызывающую программу значение переменной m_required, рассмотренной выше.

Методы FirstDate() и LastBarDate() возвращают соответственно значения переменных m_firstdate и m_lastbar_date, рассмотренных нами выше.  

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

Метод Header() создаёт и возвращает краткое наименование объекта-таймсерии:

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

Из метода возвращается строковое описание таймсерии в виде

Таймсерия "SYMBOL" TIMEFRAME_DESCRIPTION

Например:

Таймсерия "AUDUSD" M15

Метод Print() выводит в журнал полное описание таймсерии:

//+------------------------------------------------------------------+
//| Выводит в журнал описание таймсерии                              |
//+------------------------------------------------------------------+
void CSeries::Print(void)
  {
   string txt=
     (
      CMessage::Text(MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH)+(string)this.RequiredUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_ACTUAL_DEPTH)+(string)this.AvailableUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA)+(string)this.DataTotal()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_HISTORY_BARS)+(string)this.Bars()
     );
   ::Print(this.Header(),": ",txt);
  }
//+------------------------------------------------------------------+

Печатает в журнал данные таймсерии в виде

HEADER: HISTORY_DEPTH: XXXX, ACTUAL_DEPTH: XXXX, AMOUNT_HISTORY_DATA: XXXX, HISTORY_BARS: XXXX

Например:

Таймсерия "AUDUSD" W1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 1400
Таймсерия "AUDUSD" MN1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 322, Создано исторических данных: 322, Баров истории на сервере: 322

Метод PrintShort() выводит в журнал краткое описание таймсерии:

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание таймсерии                      |
//+------------------------------------------------------------------+
void CSeries::PrintShort(void)
  {
   string txt=
     (
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_REQUIRED)+": "+(string)this.RequiredUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_ACTUAL)+": "+(string)this.AvailableUsedData()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED)+": "+(string)this.DataTotal()+", "+
      CMessage::Text(MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS)+": "+(string)this.Bars()
     );
   ::Print(this.Header(),": ",txt);
  }
//+------------------------------------------------------------------+

Печатает в журнал данные таймсерии в виде

HEADER: REQUIRED: XXXX, ACTUAL: XXXX, CREATED: XXXX, HISTORY_BARS: XXXX

Например:

Таймсерия "USDJPY" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2562
Таймсерия "USDJPY" MN1: Запрошено: 1000, Фактически: 589, Создано: 589, На сервере: 589

В оба конструктора класса допишем сохранение описания таймфрейма и установку дат таймсерии:

//+------------------------------------------------------------------+
//| Конструктор 1 (таймсерия текущего символа и периода)             |
//+------------------------------------------------------------------+
CSeries::CSeries(void) : m_bars(0),m_amount(0),m_required(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(NULL,(ENUM_TIMEFRAMES)::Period());
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+
//| Конструктор 2 (таймсерия указанных символа и периода)            |
//+------------------------------------------------------------------+
CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(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.SetRequiredUsedData(required,0);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+

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

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

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

В методе установки таймфрейма допишем проверку на тот же самый таймфрейм и установку дат таймсерии:

//+------------------------------------------------------------------+
//| Устанавливает таймфрейм                                          |
//+------------------------------------------------------------------+
void CSeries::SetTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_timeframe==timeframe)
      return;
   this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe);
   this.m_new_bar_obj.SetPeriod(this.m_timeframe);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
   this.SetServerDate();
  }
//+------------------------------------------------------------------+

Здесь: если в метод передан уже используемый в объекте таймфрейм, то ничего устанавливать не нужно — уходим из метода.

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

//+------------------------------------------------------------------+
//| Устанавливает символ и таймфрейм                                 |
//+------------------------------------------------------------------+
void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   if(this.m_symbol==symbol && this.m_timeframe==timeframe)
      return;
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
  }
//+------------------------------------------------------------------+

Здесь: если в метод переданы те же самые символ и таймфрейм, то ничего менять не нужно — уходим из метода.
Далее вызываем методы установки символа и таймфрейма.

В методе установки требуемой глубины истории таймсерии сохраним запрашиваемое значение глубины истории:

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

Если передано нулевое значение required, то запрашиваем историю в количестве по умолчанию (1000 баров), заданное макроподстановкой SERIES_DEFAULT_BARS_COUNT в файле Define.mqh, иначе - переданное значение из required.

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

//+------------------------------------------------------------------+
//| Обновляет список и данные тайм-серии                             |
//+------------------------------------------------------------------+
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)
  {
//--- Если таймсерия не используется - выходим
   if(!this.m_available)
      return;
   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;
        }
      //--- Записываем самую первую дата по символу-периоду на данный момент и новое время открытия последнего бара по символу-периоду 
      this.SetServerDate();
      //--- если размер таймсерии стал больше запрашиваемого количества баров - удаляем самый ранний бар
      if(this.m_list_series.Total()>(int)this.m_required)
         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]);
  }
//+------------------------------------------------------------------+

И дополнительно: если в таймсерии появился новый бар, то обновляем даты таймсерии.

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

С классом CSeries на данный момент завершили.

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

Для получения индекса таймфрейма в списке, хранящем таймсерии соответствующих периодов графика и таймфрейма по индексу списка, у нас в листинге класса есть соответствующие методы: IndexTimeframe() и TimeframeByIndex(). Методы достаточно специфичны, так как построены на том, что индекс минимально-возможного таймфрейма (PERIOD_M1) содержится в нулевом индексе списка. А вот в составе перечисления ENUM_TIMEFRAMES индекс периода М1 уже будет равен единице, так как в нулевом индексе содержится константа PERIOD_CURRENT. Т.е., все индексы смещены на 1 относительно нулевого значения.
Подумав немного, решил, что нам могут пригодиться такие функции, которые бы возвращали индекс константы периода графика в составе перечисления ENUM_TIMEFRAMES, и наоборот — по периоду графика возвращали его константу из перечисления. Поэтому мы создадим функции, выполняющие такие задачи, в файле сервисных функций — IndexEnumTimeframe() и TimeframeByEnumIndex(), а в листинге класса CTimeSeries удалим реализацию методов IndexTimeframe() и TimeframeByIndex(), дописав в теле класса реализацию — вызов функций IndexEnumTimeframe() и TimeframeByEnumIndex() со смещением в единичку.

Итак, в файле сервисных функций DELib.mqh напишем три функции:

//+------------------------------------------------------------------+
//| Возвращает индекс таймфрейма в перечислении ENUM_TIMEFRAMES      |
//+------------------------------------------------------------------+
char IndexEnumTimeframe(ENUM_TIMEFRAMES timeframe)
  {
   int statement=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   switch(statement)
     {
      case PERIOD_M1    :  return 1;
      case PERIOD_M2    :  return 2;
      case PERIOD_M3    :  return 3;
      case PERIOD_M4    :  return 4;
      case PERIOD_M5    :  return 5;
      case PERIOD_M6    :  return 6;
      case PERIOD_M10   :  return 7;
      case PERIOD_M12   :  return 8;
      case PERIOD_M15   :  return 9;
      case PERIOD_M20   :  return 10;
      case PERIOD_M30   :  return 11;
      case PERIOD_H1    :  return 12;
      case PERIOD_H2    :  return 13;
      case PERIOD_H3    :  return 14;
      case PERIOD_H4    :  return 15;
      case PERIOD_H6    :  return 16;
      case PERIOD_H8    :  return 17;
      case PERIOD_H12   :  return 18;
      case PERIOD_D1    :  return 19;
      case PERIOD_W1    :  return 20;
      case PERIOD_MN1   :  return 21;
      default           :  Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает таймфрейм по индексу перечисления ENUM_TIMEFRAMES     |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TimeframeByEnumIndex(const uchar index)
  {
   if(index==0) return(ENUM_TIMEFRAMES)Period();
   switch(index)
     {
      case 1   :  return PERIOD_M1;
      case 2   :  return PERIOD_M2;
      case 3   :  return PERIOD_M3;
      case 4   :  return PERIOD_M4;
      case 5   :  return PERIOD_M5;
      case 6   :  return PERIOD_M6;
      case 7   :  return PERIOD_M10;
      case 8   :  return PERIOD_M12;
      case 9   :  return PERIOD_M15;
      case 10  :  return PERIOD_M20;
      case 11  :  return PERIOD_M30;
      case 12  :  return PERIOD_H1;
      case 13  :  return PERIOD_H2;
      case 14  :  return PERIOD_H3;
      case 15  :  return PERIOD_H4;
      case 16  :  return PERIOD_H6;
      case 17  :  return PERIOD_H8;
      case 18  :  return PERIOD_H12;
      case 19  :  return PERIOD_D1;
      case 20  :  return PERIOD_W1;
      case 21  :  return PERIOD_MN1;
      default  :  Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_DATAS),"... ",CMessage::Text(MSG_SYM_STATUS_INDEX),": ",(string)index); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает таймфрейм по его описанию                             |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES TimeframeByDescription(const string timeframe)
  {
   return
     (
      timeframe=="M1"   ?  PERIOD_M1   :
      timeframe=="M2"   ?  PERIOD_M2   :
      timeframe=="M3"   ?  PERIOD_M3   :
      timeframe=="M4"   ?  PERIOD_M4   :
      timeframe=="M5"   ?  PERIOD_M5   :
      timeframe=="M6"   ?  PERIOD_M6   :
      timeframe=="M10"  ?  PERIOD_M10  :
      timeframe=="M12"  ?  PERIOD_M12  :
      timeframe=="M15"  ?  PERIOD_M15  :
      timeframe=="M20"  ?  PERIOD_M20  :
      timeframe=="M30"  ?  PERIOD_M30  :
      timeframe=="H1"   ?  PERIOD_H1   :
      timeframe=="H2"   ?  PERIOD_H2   :
      timeframe=="H3"   ?  PERIOD_H3   :
      timeframe=="H4"   ?  PERIOD_H4   :
      timeframe=="H6"   ?  PERIOD_H6   :
      timeframe=="H8"   ?  PERIOD_H8   :
      timeframe=="H12"  ?  PERIOD_H12  :
      timeframe=="D1"   ?  PERIOD_D1   :
      timeframe=="W1"   ?  PERIOD_W1   :
      timeframe=="MN1"  ?  PERIOD_MN1  :
      PERIOD_CURRENT
     );
  }
//+------------------------------------------------------------------+

В файле класса CTimeSeries удалим из листинга класса реализацию методов IndexTimeframe() и TimeframeByIndex():

//+------------------------------------------------------------------+
//| Возвращает индекс таймфрейма в списке                            |
//+------------------------------------------------------------------+
char CTimeSeries::IndexTimeframe(ENUM_TIMEFRAMES timeframe) const
  {
   int statement=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
   switch(statement)
     {
      case PERIOD_M1    :  return 0;
      case PERIOD_M2    :  return 1;
      case PERIOD_M3    :  return 2;
      case PERIOD_M4    :  return 3;
      case PERIOD_M5    :  return 4;
      case PERIOD_M6    :  return 5;
      case PERIOD_M10   :  return 6;
      case PERIOD_M12   :  return 7;
      case PERIOD_M15   :  return 8;
      case PERIOD_M20   :  return 9;
      case PERIOD_M30   :  return 10;
      case PERIOD_H1    :  return 11;
      case PERIOD_H2    :  return 12;
      case PERIOD_H3    :  return 13;
      case PERIOD_H4    :  return 14;
      case PERIOD_H6    :  return 15;
      case PERIOD_H8    :  return 16;
      case PERIOD_H12   :  return 17;
      case PERIOD_D1    :  return 18;
      case PERIOD_W1    :  return 19;
      case PERIOD_MN1   :  return 20;
      default           :  ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает таймфрейм по индексу                                  |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CTimeSeries::TimeframeByIndex(const uchar index) const
  {
   switch(index)

     {
      case 0   :  return PERIOD_M1;
      case 1   :  return PERIOD_M2;
      case 2   :  return PERIOD_M3;
      case 3   :  return PERIOD_M4;
      case 4   :  return PERIOD_M5;
      case 5   :  return PERIOD_M6;
      case 6   :  return PERIOD_M10;
      case 7   :  return PERIOD_M12;
      case 8   :  return PERIOD_M15;
      case 9   :  return PERIOD_M20;
      case 10  :  return PERIOD_M30;
      case 11  :  return PERIOD_H1;
      case 12  :  return PERIOD_H2;
      case 13  :  return PERIOD_H3;
      case 14  :  return PERIOD_H4;
      case 15  :  return PERIOD_H6;
      case 16  :  return PERIOD_H8;
      case 17  :  return PERIOD_H12;
      case 18  :  return PERIOD_D1;
      case 19  :  return PERIOD_W1;
      case 20  :  return PERIOD_MN1;
      default  :  ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_DATAS),"... ",CMessage::Text(MSG_SYM_STATUS_INDEX),": ",(string)index); return WRONG_VALUE;
     }
  }
//+------------------------------------------------------------------+

Вместо этих методов теперь будем вызывать функции из файла сервисных функций DELib.mqh:

//--- Возвращает (1) индекс таймфрейма в списке, (2) таймфрейм по индексу списка
   char              IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1;                            }
   ENUM_TIMEFRAMES   TimeframeByIndex(const uchar index)             const { return TimeframeByEnumIndex(uchar(index+1));                       }

Так как список таймсерий класса содержит все возможные периоды графиков, то в нулевом индексе списка содержится таймсерия периода графика М1, а в перечислении ENUM_TIMEFRAMES в нулевом индексе содержится PERIOD_CURRENT, а М1 — в первом, поэтому нам нужно для получения правильного индекса в списке смещать значение индекса на 1, что мы тут и делаем.

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

//+------------------------------------------------------------------+
//| Класс "Таймсерии символа"                                        |
//+------------------------------------------------------------------+
class CTimeSeries : public CBaseObj
  {
private:
   string            m_symbol;                                             // Символ таймсерий
   CArrayObj         m_list_series;                                        // Список таймсерий по таймфреймам
   datetime          m_server_firstdate;                                   // Самая первая дата в истории по символу на сервере
   datetime          m_terminal_firstdate;                                 // Самая первая дата в истории по символу в клиентском терминале
//--- Возвращает (1) индекс таймфрейма в списке, (2) таймфрейм по индексу списка
   char              IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1;                            }
   ENUM_TIMEFRAMES   TimeframeByIndex(const uchar index)             const { return TimeframeByEnumIndex(uchar(index+1));                       }
//--- Устанавливает самую первую дату в истории по символу на сервере и в клиентском терминале
   void              SetTerminalServerDate(void)
                       {
                        this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE);
                        this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE);
                       }
public:

В переменной m_server_firstdate будет храниться самая первая дата в истории по символу на сервере, получаемая посредством функции SeriesInfoInteger() с идентификатором свойства SERIES_SERVER_FIRSTDATE.

В переменной m_terminal_firstdate будет храниться самая первая дата в истории по символу в терминале, получаемая посредством функции SeriesInfoInteger() с идентификатором свойства SERIES_TERMINAL_FIRSTDATE.

В публичной секции класса добавлены шесть новых методов и параметрический конструктор:

public:
//--- Возвращает (1) себя, (2) полный список таймсерий, (3) указанный объект-таймсерию, (4) объект-таймсерию по индексу
   CTimeSeries      *GetObject(void)                                       { return &this;                                                      }
   CArrayObj        *GetListSeries(void)                                   { return &this.m_list_series;                                        }
   CSeries          *GetSeries(const ENUM_TIMEFRAMES timeframe)            { return this.m_list_series.At(this.IndexTimeframe(timeframe));      }
   CSeries          *GetSeriesByIndex(const uchar index)                   { return this.m_list_series.At(index);                               }
//--- Устанавливает/возвращает символ таймсерии
   void              SetSymbol(const string symbol)                        { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol);  }
   string            Symbol(void)                                    const { return this.m_symbol;                                              }
//--- Устанавливает глубину истории (1) указанной таймсерии, (2) всех используемых таймсерий символа
   bool              SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SetRequiredAllUsedData(const uint required=0,const int rates_total=0);
//--- Возвращает флаг синхронизации данных с данными на сервере (1) указанной таймсерии, (2) всех таймсерий
   bool              SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool              SyncAllData(const uint required=0,const int rates_total=0);
//--- Возвращает Самую первую дату в истории по символу (1) на сервере, (2) в клиентском терминале
   datetime          ServerFirstDate(void)                           const { return this.m_server_firstdate;                                    }
   datetime          TerminalFirstDate(void)                         const { return this.m_terminal_firstdate;                                  }
//--- Создаёт (1) указанный список-таймсерию, (2) все списки-таймсерии
   bool              Create(const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool              CreateAll(const uint required=0);
//--- Обновляет (1) указанный список-таймсерию, (2) все списки-таймсерии
   void              Refresh(const ENUM_TIMEFRAMES timeframe,
                             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);
   void              RefreshAll(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);
//--- Сравнивает объекты CTimeSeries между собой (по символу)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Выводит в журнал (1) описание, (2) краткое описание таймсерий символа
   void              Print(const bool created=true);
   void              PrintShort(const bool created=true);
   
//--- Конструкторы
                     CTimeSeries(void){;}
                     CTimeSeries(const string symbol);
  };
//+------------------------------------------------------------------+

Метод GetObject() возвращает указатель на объект класса. Позволяет получить объект класса таймсерий символа и работать с ним в своей программе.

Методы ServerFirstDate() и TerminalFirstDate() возвращают значения переменных m_server_firstdate и m_terminal_firstdate соответственно, рассмотренные выше.

Виртуальный метод Compare() позволяет сравнивать два объекта-таймсерии по наименованию символа таймсерии:

//+------------------------------------------------------------------+
//| Сравнивает объекты CTimeSeries между собой                       |
//+------------------------------------------------------------------+
int CTimeSeries::Compare(const CObject *node,const int mode=0) const
  {
   const CTimeSeries *compared_obj=node;
   return(this.Symbol()>compared_obj.Symbol() ? 1 : this.Symbol()<compared_obj.Symbol() ? -1 : 0);
  }
//+------------------------------------------------------------------+

Метод возвращает ноль в случае равенства символов двух сравниваемых объектов-таймсерий. Иначе возвращается +/- 1. Метод объявлен в классе CObject стандартной библиотеки и должен переопределяться в его наследниках.

Метод Print() выводит в журнал полные описания всех таймсерий символа:

//+------------------------------------------------------------------+
//| Выводит в журнал описания всех таймсерий символа                 |
//+------------------------------------------------------------------+
void CTimeSeries::Print(const bool created=true)
  {
   ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": ");
   for(int i=0;i<this.m_list_series.Total();i++)
     {
      CSeries *series=this.m_list_series.At(i);
      if(series==NULL || (created && series.DataTotal()==0))
         continue;
      series.Print();
     }
  }
//+------------------------------------------------------------------+

В журнал выводится список всех созданных (created=true) или всех созданных и объявленных (created=false) таймсерий символа в формате, например

created=true:

Таймсерия символа GBPUSD: 
Таймсерия "GBPUSD" M1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 6296
Таймсерия "GBPUSD" M5: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3921
Таймсерия "GBPUSD" M15: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3227
Таймсерия "GBPUSD" M30: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3053
Таймсерия "GBPUSD" H1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 6187
Таймсерия "GBPUSD" H4: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 5298
Таймсерия "GBPUSD" D1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 5288
Таймсерия "GBPUSD" W1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 1398
Таймсерия "GBPUSD" MN1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 321, Создано исторических данных: 321, Баров истории на сервере: 321

created=false:

Таймсерия символа GBPUSD: 
Таймсерия "GBPUSD" M1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 6296
Таймсерия "GBPUSD" M2: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 5483
Таймсерия "GBPUSD" M3: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 4616
Таймсерия "GBPUSD" M4: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 4182
Таймсерия "GBPUSD" M5: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3921
Таймсерия "GBPUSD" M6: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 3748
Таймсерия "GBPUSD" M10: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 3401
Таймсерия "GBPUSD" M12: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 3314
Таймсерия "GBPUSD" M15: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3227
Таймсерия "GBPUSD" M20: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 3140
Таймсерия "GBPUSD" M30: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 3053
Таймсерия "GBPUSD" H1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 6187
Таймсерия "GBPUSD" H2: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 5047
Таймсерия "GBPUSD" H3: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 5031
Таймсерия "GBPUSD" H4: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 5298
Таймсерия "GBPUSD" H6: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 6324
Таймсерия "GBPUSD" H8: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 6301
Таймсерия "GBPUSD" H12: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 0, Баров истории на сервере: 5762
Таймсерия "GBPUSD" D1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 5288
Таймсерия "GBPUSD" W1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 1000, Создано исторических данных: 1000, Баров истории на сервере: 1398
Таймсерия "GBPUSD" MN1: Запрошенная глубина истории: 1000, Фактическая глубина истории: 321, Создано исторических данных: 321, Баров истории на сервере: 321

Метод PrintShort() выводит в журнал сокращённые описания всех таймсерий символа:

//+------------------------------------------------------------------+
//| Выводит в журнал краткие описания всех таймсерий символа         |
//+------------------------------------------------------------------+
void CTimeSeries::PrintShort(const bool created=true)
  {
   ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": ");
   for(int i=0;i<this.m_list_series.Total();i++)
     {
      CSeries *series=this.m_list_series.At(i);
      if(series==NULL || (created && series.DataTotal()==0))
         continue;
      series.PrintShort();
     }
  }
//+------------------------------------------------------------------+

В журнал выводится список всех созданных (created=true) или всех созданных и объявленных (created=false) таймсерий символа в формате, например

created=true:

Таймсерия символа USDJPY: 
Таймсерия "USDJPY" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2880
Таймсерия "USDJPY" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3921
Таймсерия "USDJPY" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3227
Таймсерия "USDJPY" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3053
Таймсерия "USDJPY" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5095
Таймсерия "USDJPY" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5023
Таймсерия "USDJPY" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5305
Таймсерия "USDJPY" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2562
Таймсерия "USDJPY" MN1: Запрошено: 1000, Фактически: 589, Создано: 589, На сервере: 589

created=false:

Таймсерия символа USDJPY: 
Таймсерия "USDJPY" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2880
Таймсерия "USDJPY" M2: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 3608
Таймсерия "USDJPY" M3: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 4616
Таймсерия "USDJPY" M4: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 4182
Таймсерия "USDJPY" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3921
Таймсерия "USDJPY" M6: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 3748
Таймсерия "USDJPY" M10: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 3401
Таймсерия "USDJPY" M12: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 3314
Таймсерия "USDJPY" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3227
Таймсерия "USDJPY" M20: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 3140
Таймсерия "USDJPY" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3053
Таймсерия "USDJPY" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5095
Таймсерия "USDJPY" H2: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 5047
Таймсерия "USDJPY" H3: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 5031
Таймсерия "USDJPY" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5023
Таймсерия "USDJPY" H6: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 6390
Таймсерия "USDJPY" H8: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 6352
Таймсерия "USDJPY" H12: Запрошено: 1000, Фактически: 1000, Создано: 0, На сервере: 5796
Таймсерия "USDJPY" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5305
Таймсерия "USDJPY" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2562
Таймсерия "USDJPY" MN1: Запрошено: 1000, Фактически: 589, Создано: 589, На сервере: 589

В конструкторе класса добавлена установка дат таймсерии:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort();
   for(int i=0;i<21;i++)
     {
      ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i);
      CSeries *series_obj=new CSeries(this.m_symbol,timeframe);
      this.m_list_series.Add(series_obj);
     }
   this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обновляет указанный список-таймсерию                             |
//+------------------------------------------------------------------+
void CTimeSeries::Refresh(const ENUM_TIMEFRAMES timeframe,
                          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 *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0)
      return;
   series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
   if(series_obj.IsNewBar(time))
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

В методе обновления всех таймсерий тоже добавлено обновление дат таймсерии:

//+------------------------------------------------------------------+
//| Обновляет все списки-таймсерии                                   |
//+------------------------------------------------------------------+
void CTimeSeries::RefreshAll(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)
  {
   bool upd=false;
   for(int i=0;i<21;i++)
     {
      CSeries *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || series_obj.DataTotal()==0)
         continue;
      series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread);
      if(series_obj.IsNewBar(time))
         upd &=true;
     }
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

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

Доработка класса CTimeSeries на этом завершена. Незначительные исправления мы тут не описываем — всё можно посмотреть в прикреплённых в конце статьи файлах.

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

  1. "Бар одного периода одного символа" CBar, включает в себя данные одного бара установленного символа установленного периода;
  2. "Таймсерия одного периода одного символа" CSeries, включает в себя список-коллекцию баров (1) одного периода одного символа;
  3. "Таймсерии всех периодов одного символа" CTimeSeries, включает в себя список таймсерий (2) для каждого периода одного символа

Теперь создадим коллекцию таймсерий, представляющую из себя список-коллекцию таймсерий (3) каждого используемого в программе символа.

Класс-коллекция объектов-таймсерий по символам и периодам

Коллекция таймсерий будет представлять из себя динамический массив указателей на экземпляры класса CObject и его наследников (указатели на объекты класса CTimeSeries).

В папке библиотеки \MQL5\Include\DoEasy\Collections\ создадим новый файл TimeSeriesCollection.mqh класса CTimeSeriesCollection.
Базовым объектом класса будет являться базовый объект для построения стандартной библиотеки CObject.

Посмотрим на листниг класса и постепенно разберём его устройство:

//+------------------------------------------------------------------+
//|                                         TimeSeriesCollection.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 "..\Objects\Series\TimeSeries.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
//+------------------------------------------------------------------+
//| Коллекция таймсерий символов                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CObject
  {
private:
   CArrayObj               m_list;                    // Список используемых таймсерий символов
//--- Возвращает индекс таймсерии по имени символа
   int                     IndexTimeSeries(const string symbol);
public:
//--- Возвращает (1) себя, (2) список таймсерий
   CTimeSeriesCollection  *GetObject(void)            { return &this;         }
   CArrayObj              *GetList(void)              { return &this.m_list;  }
   
//--- Создаёт список-коллекцию таймсерий символов
   bool                    CreateCollection(const CArrayObj *list_symbols);
//--- Устанавливает флаг использования (1) указанной таймсерии указанного символа, (2) указанной таймсерии всех символов
//--- (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   void                    SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true);
   void                    SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true);
   void                    SetAvailable(const string symbol,const bool flag=true);
   void                    SetAvailable(const bool flag=true);
//--- Возвращает флаг использования (1) указанной таймсерии указанного символа, (2) указанной таймсерии всех символов
//--- (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   bool                    IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe);
   bool                    IsAvailable(const ENUM_TIMEFRAMES timeframe);
   bool                    IsAvailable(const string symbol);
   bool                    IsAvailable(void);

//--- Устанавливает глубину истории (1) указанной таймсерии указанного символа, (2) указанной таймсерии всех символов
//--- (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   bool                    SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0);
   bool                    SetRequiredUsedData(const uint required=0,const int rates_total=0);
//--- Возвращает флаг синхронизации данных с данными на сервере (1) указанной таймсерии указанного символа,
//--- (2) указанной таймсерии всех символов, (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   bool                    SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0);
   bool                    SyncData(const string symbol,const uint required=0,const int rates_total=0);
   bool                    SyncData(const uint required=0,const int rates_total=0);
//--- Создаёт (1) указанную таймсерию указанного символа, (2) указанную таймсерии всех символов,
//--- (3) все таймсерии указанного символа, (4) все таймсерии всех символов
   bool                    CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool                    CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0);
   bool                    CreateSeries(const string symbol,const uint required=0);
   bool                    CreateSeries(const uint required=0);
//--- Обновляет (1) указанную таймсерию указанного символа, (2) указанную таймсерии всех символов,
//--- (3) все таймсерии указанного символа, (4) все таймсерии всех символов
   void                    Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                   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);
   void                    Refresh(const ENUM_TIMEFRAMES timeframe,
                                   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);
   void                    Refresh(const string symbol,
                                   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);
   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);

//--- Выводит в журнал (1) полное, (2) краткое описание коллекции
   void                    Print(const bool created=true);
   void                    PrintShort(const bool created=true);
   
   
//--- Конструктор
                           CTimeSeriesCollection();
  };
//+------------------------------------------------------------------+

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

Массив указателей на объекты класса CObject m_list будет содержать в себе указатели на объекты класса CTimeSeries. Из этого списка мы будем получать данные нужных таймсерий и работать с ними.

Метод IndexTimeSeries(), возвращающий индекс таймсерии по имени символа, позволяет получить доступ к нужному объекту CTimeSeries по имени символа:

//+------------------------------------------------------------------+
//| Возвращает индекс таймсерии по имени символа                     |
//+------------------------------------------------------------------+
int CTimeSeriesCollection::IndexTimeSeries(const string symbol)
  {
   CTimeSeries *tmp=new CTimeSeries(symbol);
   if(tmp==NULL)
      return WRONG_VALUE;
   this.m_list.Sort();
   int index=this.m_list.Search(tmp);
   delete tmp;
   return index;
  }
//+------------------------------------------------------------------+

Создаём новый временный объект-таймсерию со значением символа, переданным в метод,
устанавливаем списку m_list флаг сортированного списка
и
при помощи метода Searsh() получаем индекс такого объекта в списке.
Обязательно удаляем временный объект-таймсерию и
возвращаем полученный индекс
.
Если такого объекта с указанным символом нет в списке — метод возвращает -1, иначе — значение индекса найденного объекта.

Метод GetObject() возвращает указатель на объект-коллекцию таймсерий в управляющую программу. Позволяет получить весь объект-коллекцию и работать с ним в своей программе.

Метод GetList() возвращает указатель на список-коллекцию таймсерий CTimeSeries. Позволяет получить список всех таймсерий всех символов и работать с ним в своей программе.

Метод CreateCollection() создаёт пустую коллекцию объектов-таймсерий:

//+------------------------------------------------------------------+
//| Создаёт список-коллекцию таймсерий символов                      |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateCollection(const CArrayObj *list_symbols)
  {
//--- Если передан пустой список объектов-символов - выходим
   if(list_symbols==NULL)
      return false;
//--- Получаем количество объектов-символов в переданном списке
   int total=list_symbols.Total();
//--- Очищаем список-коллекцию таймсерий
   this.m_list.Clear();
//--- В цикле по всем объектам-символам
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной объект-символ
      CSymbol *symbol_obj=list_symbols.At(i);
      //--- если объект-символ получить не удалось - переходим к следующему в списке
      if(symbol_obj==NULL)
         continue;
      //--- Создаём новый объект-таймсерию с именем текущего символа
      CTimeSeries *timeseries=new CTimeSeries(symbol_obj.Name());
      //--- Если объект-таймсерию создать не удалось - переходим к следующему символу в списке
      if(timeseries==NULL)
         continue;
      //--- Списку-коллекции таймсерий устанавливаем флаг сортированного списка
      this.m_list.Sort();
      //--- Если объект с таким именем символа уже есть в списке-коллекции таймсерий - удаляем объект-таймсерию
      if(this.m_list.Search(timeseries)>WRONG_VALUE)
         delete timeseries;
      //--- иначе - если объект-таймсерию не удалось добавить в список-коллекцию - удаляем объект-таймсерию
      else 
         if(!this.m_list.Add(timeseries))
            delete timeseries;
     }
//--- Возвращаем флаг того, что созданный список-коллекция имеет размер больше нуля
   return this.m_list.Total()>0;
  }
//+-----------------------------------------------------------------------+

Каждая строка метода прокомментирована в его листинге.
В метод передаётся ранее созданный список всех используемых символов в программе, и в цикле по этому списку создаются объекты-таймсерии CTimeSeries, для которых сразу при создании указывается имя символа. Таким образом мы получаем список-коллекцию таймсерий по количеству используемых в программе символов. Все остальные данные создаваемых объектов-таймсерий остаются пустыми — их нужно будет устанавливать отдельно.
Сделано так для того, чтобы в библиотеке всегда имелся список-коллекция пустых таймсерий в количестве, равным количеству заданных для работы символов. А вот таймфреймы, и соответственно — их объекты-таймсерии, с которыми в программе нужно будет работать, задаются уже на следующем шаге, или по мере необходимости.
Но лучше создавать их в обработчике OnInit() программы, либо сразу же после начала её работы, так как создание большого количества таймсерий требует времени, особенно при "холодном" запуске программы.

Четыре перегруженных метода SetAvailable() служат для установки флага необходимости работы в программе с указанными таймсериями.

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

//+-----------------------------------------------------------------------+
//|Устанавливает флаг использования указанной таймсерии указанного символа|
//+-----------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return;
   series.SetAvailable(flag);
  }
//+------------------------------------------------------------------+

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

Сначала получаем индекс таймсерий CTimeSeries в списке m_list по символу при помощи метода IndexTimeSeries(), рассмотренного выше, получаем по этому индексу таймсерию из списка. Из полученного объекта-таймсерии получаем нужную таймсерию CSeries указанного периода графика методом GetSeries(), рассмотренного нами в прошлой статье, и устанавливаем для неё переданный в метод флаг методом SetAvailable() класса CBaseObj.

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

//+------------------------------------------------------------------+
//|Устанавливает флаг использования указанной таймсерии всех символов|
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      series.SetAvailable(flag);
     }
  }
//+------------------------------------------------------------------+

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

В цикле по списку всех таймсерий символов получаем по индексу цикла очередную таймсерию CTimeSeries, из полученного объекта-таймсерии получаем указанную таймсерию CSeries указанного периода графика методом GetSeries(), рассмотренного нами в прошлой статье, и устанавливаем для неё переданный в метод флаг методом SetAvailable() класса CBaseObj.

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

//+------------------------------------------------------------------+
//|Устанавливает флаг использования всех таймсерий указанного символа|
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const string symbol,const bool flag=true)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      series.SetAvailable(flag);
     }
  }
//+------------------------------------------------------------------+

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

Сначала получаем индекс таймсерий CTimeSeries в списке m_list по символу при помощи метода IndexTimeSeries(), рассмотренного выше, получаем по этому индексу таймсерию CTimeSeries из списка. Из полученного объекта-таймсерии методом GetListSeries() получаем полный список всех таймсерий CSeries. В цикле по полученному списку получаем очередную таймсерию CSeries и устанавливаем для неё переданный в метод флаг методом SetAvailable() класса CBaseObj.

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

//+------------------------------------------------------------------+
//| Устанавливает флаг использования всех таймсерий всех символов    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::SetAvailable(const bool flag=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         series.SetAvailable(flag);
        }
     }
  }
//+--------------------------------------------------------------------+

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

В цикле по списку таймсерий получаем очередной объект-таймсерию CTimeSeries по индексу цикла, из полученного объекта методом GetListSeries() получаем список всех его таймсерий CSeries, в цикле по списку таймсерий CSeries получаем очередную таймсерию по индексу цикла и устанавливаем для неё переданный в метод флаг методом SetAvailable() класса CBaseObj.

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

//+--------------------------------------------------------------------+
//|Возвращает флаг использования указанной таймсерии указанного символа|
//+--------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.IsAvailable();
  }
//+------------------------------------------------------------------+
//| Возвращает флаг использования указанной таймсерии всех символов  |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const ENUM_TIMEFRAMES timeframe)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.IsAvailable();
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Возвращает флаг использования всех таймсерий указанного символа  |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(const string symbol)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.IsAvailable();
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Возвращает флаг использования всех таймсерий всех символов       |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::IsAvailable(void)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.IsAvailable();
        }
     }
   return res;
  }
//+--------------------------------------------------------------------+

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

Четыре метода для установки требуемой глубины истории либо указанным таймсериям, либо сразу для всех таймсерий:

//+--------------------------------------------------------------------+
//|Устанавливает глубину истории указанной таймсерии указанного символа|
//+--------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.SetRequiredUsedData(required,rates_total);
  }
//+------------------------------------------------------------------+
//| Устанавливает глубину истории указанной таймсерии всех символов  |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.SetRequiredUsedData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Устанавливает глубину истории всех таймсерий указанного символа  |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.SetRequiredUsedData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Устанавливает глубину истории всех таймсерий всех символов       |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SetRequiredUsedData(const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.SetRequiredUsedData(required,rates_total);
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает флаг синхронизации данных с данными на сервере        |
//| указанной таймсерии указанного символа                           |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CSeries *series=timeseries.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.SyncData(required,rates_total);
  }
//+------------------------------------------------------------------+
//| Возвращает флаг синхронизации данных с данными на сервере        |
//| указанной таймсерии всех символов                                |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CSeries *series=timeseries.GetSeries(timeframe);
      if(series==NULL)
         continue;
      res &=series.SyncData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Возвращает флаг синхронизации данных с данными на сервере        |
//| всех таймсерий указанного символа                                |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const string symbol,const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   CArrayObj *list=timeseries.GetListSeries();
   if(list==NULL)
      return false;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CSeries *series=list.At(i);
      if(series==NULL)
         continue;
      res &=series.SyncData(required,rates_total);
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Возвращает флаг синхронизации данных с данными на сервере        |
//| всех таймсерий всех символов                                     |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::SyncData(const uint required=0,const int rates_total=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         CSeries *series=list.At(j);
         if(series==NULL)
            continue;
         res &=series.SyncData(required,rates_total);
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

Работа методов идентична вышерассмотренным. Возвращается результат проверки синхронизации таймсерий методом SyncData() класса CSeries.

Четыре метода для создания указанных или всех таймсерий.

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

//+------------------------------------------------------------------+
//| Создаёт указанную таймсерию указанного символа                   |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   return timeseries.Create(timeframe,required);
  }
//+------------------------------------------------------------------+

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

Получаем индекс таймсерии в списке по наименованию символа методом IndexTimeSeries(), по полученному индексу получаем таймсерию CTimeSeries  из списка и возвращаем результат создания указанной таймсерии методом Create() класса CTimeSeries.

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

//+------------------------------------------------------------------+
//| Создаёт указанную таймсерию всех символов                        |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      res &=timeseries.Create(timeframe,required);
     }
   return res;
  }
//+------------------------------------------------------------------+

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

В цикле по всем объектам таймсерий всех символов получаем очередной объект-таймсерию CTimeSeries по индексу цикла, в переменную res добавляем результат создания указанной таймсерии методом Create() класса CTimeSeries. По окончании цикла возвращаем результат создания указанной таймсерии для всех символов. Если хоть одна таймсерия не было создана, результатом будет false.

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

//+------------------------------------------------------------------+
//| Создаёт все таймсерии указанного символа                         |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const string symbol,const uint required=0)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return false;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return false;
   return timeseries.CreateAll(required);
  }
//+------------------------------------------------------------------+

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

Получаем индекс таймсерии в списке по наименованию символа методом IndexTimeSeries(), по полученному индексу получаем таймсерию CTimeSeries  из списка и возвращаем результат создания всех таймсерий методом CreateAll() класса CTimeSeries.

Метод для создания всех таймсерий всех символов:

//+------------------------------------------------------------------+
//| Создаёт все таймсерии всех символов                              |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CreateSeries(const uint required=0)
  {
   bool res=true;
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      res &=timeseries.CreateAll(required);
     }
   return res;
  }
//+------------------------------------------------------------------+

В метод передаётся только количество создаваемой глубины истории (как и во всех вышерассмотренных методах). По умолчанию передаётся ноль, что означает глубину истории в 1000 баров, заданную макроподстановкой SERIES_DEFAULT_BARS_COUNT в файле Defines.mqh.

В цикле по списку таймсерий получаем очередной объект-таймсерию CTimeSeries по индексу цикла и добавляем в переменную res результат создания всех таймсерий для символа текущего объекта CTimeSeries методом CreateAll(), возвращающего флаг создания всех таймсерий символа объекта CTimeSeries.
По окончании цикла возвращаем результат создания всех таймсерий для всех символов. Если хоть одна таймсерия не было создана, результатом будет false.

Четыре метода для обновления указанных таймсерий указанного символа или всех таймсерий:

//+------------------------------------------------------------------+
//| Обновляет указанную таймсерию указанного символа                 |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                    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)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Обновляет указанную таймсерию всех символов                      |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const ENUM_TIMEFRAMES timeframe,
                                    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)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+
//| Обновляет все таймсерии указанного символа                       |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,
                                    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)
  {
   int index=this.IndexTimeSeries(symbol);
   if(index==WRONG_VALUE)
      return;
   CTimeSeries *timeseries=this.m_list.At(index);
   if(timeseries==NULL)
      return;
   timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
  }
//+------------------------------------------------------------------+
//| Обновляет все таймсерии всех символов                            |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::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)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread);
     }
  }
//+------------------------------------------------------------------+

Здесь во всех методах мы тем, или иным образом (все способы были рассмотрены в предыдущих методах) получаем требуемый объект-таймсерию CTimeSeries и вызываем методы обновления одной указанной таймсерии Refresh() или сразу всех таймсерий RefreshAll() класса CTimeSeries.

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

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

//+------------------------------------------------------------------+
//| Выводит в журнал полное описание коллекции                       |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Print(const bool created=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.Print(created);
     }
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание коллекции                      |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::PrintShort(const bool created=true)
  {
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      timeseries.PrintShort(created);
     }
  }
//+------------------------------------------------------------------+

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

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

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

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

Любая программа получает доступ к методам библиотеки из главного объекта библиотеки CEngine.
Впишем в файл класса CEngine доступ к работе с коллекцией таймсерий.
Откроем файл Engine.mqh из каталога библиотеки \MQL5\Include\DoEasy\ и внесём в него необходимые изменения.

Так как в прошлой версии к классу CEngine был полключен файл класса CTimeSeries только для проверки его работы, то удалим его из списка подключаемых файлов

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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\TimeSeries.mqh"
//+------------------------------------------------------------------+

и подключим файл класса-коллекции таймсерий:

//+------------------------------------------------------------------+
//|                                                       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 "Collections\TimeSeriesCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CSymbolsCollection   m_symbols;                       // Коллекция символов
   CTimeSeriesCollection m_series;                       // Коллекция таймсерий
   CResourceCollection  m_resource;                      // Список ресурсов
   CTradingControl      m_trading;                       // Объект управления торговлей
   CArrayObj            m_list_counters;                 // Список счётчиков таймера

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

//--- Устанавливает список (1) используемых символов
   bool                 SetUsedSymbols(const string &array_symbols[])   { return this.m_symbols.SetUsedSymbols(array_symbols);}

Этот метод в данном исполнении вызывает одноимённый метод установки списка символов для класса-коллекции символов. И вот как раз в этом же месте нам будет удобно сразу после установки списка символов в коллекции символов, создать и коллекцию таймсерий на основании созданного списка коллекции символов.

Оставим здесь только объявление метода:

//--- Устанавливает список используемых символов в коллекции символов и создаёт коллекцию таймсерий символов
   bool                 SetUsedSymbols(const string &array_symbols[]);

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

//+------------------------------------------------------------------+
//| Устанавливает список используемых символов в коллекции символов  |
//| и создаёт коллекцию таймсерий символов                           |
//+------------------------------------------------------------------+
bool CEngine::SetUsedSymbols(const string &array_symbols[])
  {
   bool res=this.m_symbols.SetUsedSymbols(array_symbols);
   CArrayObj *list=this.GetListAllUsedSymbols();
   if(list==NULL)
      return false;
   res&=this.m_series.CreateCollection(list);
   return res;
  }
//+------------------------------------------------------------------+

Здесь: объявляем переменную res и инициализируем её результатом работы метода установки списка символов в коллекции символов.

И далее: получаем список используемых символов из класса коллекции символов, если список не создан, то возвращаем false,
иначе — добавляем к переменной res результат создания списка коллекции таймсерий на основании списка коллекции символов.
Итоговый результат возвращаем из метода
.

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

//--- Возвращает список отложенных запросов
   CArrayObj           *GetListPendingRequests(void)                          { return this.m_trading.GetListRequests();                        }

//--- Возвращает (1) коллекцию таймсерий, (2) список таймсерий из коллекции таймсерий
   CTimeSeriesCollection *GetTimeSeriesCollection(void)                       { return &this.m_series;                                          }
   CArrayObj           *GetListTimeSeries(void)                               { return this.m_series.GetList();                                 }
//--- Устанавливает флаг использования (1) указанной таймсерии указанного символа, (2) указанной таймсерии всех символов
//--- (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   void                 SeriesSetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true)
                          { this.m_series.SetAvailable(symbol,timeframe,flag);}
   void                 SeriesSetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true)
                          { this.m_series.SetAvailable(timeframe,flag);       }
   void                 SeriesSetAvailable(const string symbol,const bool flag=true)
                          { this.m_series.SetAvailable(symbol,flag);          }
   void                 SeriesSetAvailable(const bool flag=true)              
                          { this.m_series.SetAvailable(flag);                 }
//--- Устанавливает глубину истории (1) указанной таймсерии указанного символа, (2) указанной таймсерии всех символов
//--- (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   bool                 SeriesSetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(symbol,timeframe,required,rates_total);}
   bool                 SeriesSetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(timeframe,required,rates_total);       }
   bool                 SeriesSetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(symbol,required,rates_total);          }
   bool                 SeriesSetRequiredUsedData(const uint required=0,const int rates_total=0)
                          { return this.m_series.SetRequiredUsedData(required,rates_total);                 }
//--- Возвращает флаг синхронизации данных с данными на сервере (1) указанной таймсерии указанного символа,
//--- (2) указанной таймсерии всех символов, (3) всех таймсерий указанного символа, (4) всех таймсерий всех символов
   bool                 SeriesSyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(symbol,timeframe,required,rates_total);  }
   bool                 SeriesSyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(timeframe,required,rates_total);         }
   bool                 SeriesSyncData(const string symbol,const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(symbol,required,rates_total);            }
   bool                 SeriesSyncData(const uint required=0,const int rates_total=0)
                          { return this.m_series.SyncData(required,rates_total);                   }
//--- Создаёт (1) указанную таймсерию указанного символа, (2) указанную таймсерии всех символов,
//--- (3) все таймсерии указанного символа, (4) все таймсерии всех символов
   bool                 SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,timeframe,required);          }
   bool                 SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0)
                          { return this.m_series.CreateSeries(timeframe,required);                 }
   bool                 SeriesCreate(const string symbol,const uint required=0)
                          { return this.m_series.CreateSeries(symbol,required);                    }
   bool                 SeriesCreate(const uint required=0)
                          { return this.m_series.CreateSeries(required);                           }
//--- Обновляет (1) указанную таймсерию указанного символа, (2) указанную таймсерии всех символов,
//--- (3) все таймсерии указанного символа, (4) все таймсерии всех символов
   void                 SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      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)
                          { this.m_series.Refresh(symbol,timeframe,time,open,high,low,close,tick_volume,volume,spread); }
   void                 SeriesRefresh(const ENUM_TIMEFRAMES timeframe,
                                      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)
                          { this.m_series.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread);        }
   void                 SeriesRefresh(const string symbol,
                                      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)
                          { this.m_series.Refresh(symbol,time,open,high,low,close,tick_volume,volume,spread);           }
   void                 SeriesRefresh(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)
                          { this.m_series.Refresh(time,open,high,low,close,tick_volume,volume,spread);                  }

Метод GetTimeSeriesCollection() возвращает в вызывающую программу указатель на объект-коллекцию таймсерий. Позволяет получить в программе указатель на коллекцию и полноценно с ней работать.

Метод GetListTimeSeries() возвращает в вызывающую программу указатель на список таймсерий коллекции. Позволяет получить в программе указатель на список объектов-таймсерий CTimeSeries и полноценно с ним работать.

Перегруженные методы SeriesSetAvailable() дают доступ к методам SetAvailable() класса коллекции таймсерий CTimeSeriesCollection(), рассмотренные нами выше.

Перегруженные методы SeriesSetRequiredUsedData() дают доступ к методам SetRequiredUsedData() класса коллекции таймсерий CTimeSeriesCollection(), рассмотренные нами выше.

Перегруженные методы SeriesSyncData() дают доступ к методам SyncData() класса коллекции таймсерий CTimeSeriesCollection(), рассмотренные нами выше.

Перегруженные методы SeriesCreate() дают доступ к методам CreateSeries() класса коллекции таймсерий CTimeSeriesCollection(), рассмотренные нами выше.

Перегруженные методы SeriesRefresh() дают доступ к методам Refresh() класса коллекции таймсерий CTimeSeriesCollection(), рассмотренные нами выше.

И это все необходимые на сегодня задачи доработки всех классов для тестирования нового класса-коллекции таймсерий.

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

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

Для выбора режима работы программы с символами у нас есть перечисление в файле Datas.mqh

//+------------------------------------------------------------------+
//| Режимы работы с символами                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+

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

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list

На основании значения этой переменной создаётся массив символов, который передаётся в библиотеку при её инициализации в функции советника OnInitDoEasy() и далее уже в библиотеке создаётся список рабочих символов.

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

//+------------------------------------------------------------------+
//|                                                        Datas.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"
//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define INPUT_SEPARATOR                (",")          // Разделитель в строке входных параметров
#define TOTAL_LANG                     (2)            // Количество используемых языков
//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Режимы работы с символами                                        |
//+------------------------------------------------------------------+
enum ENUM_SYMBOLS_MODE
  {
   SYMBOLS_MODE_CURRENT,                              // Работа только с текущим символом
   SYMBOLS_MODE_DEFINES,                              // Работа с заданным списком символов
   SYMBOLS_MODE_MARKET_WATCH,                         // Работа с символами из окна "Обзор рынка"
   SYMBOLS_MODE_ALL                                   // Работа с полным списком символов
  };
//+------------------------------------------------------------------+
//| Режимы работы с таймфреймами                                     |
//+------------------------------------------------------------------+
enum ENUM_TIMEFRAMES_MODE
  {
   TIMEFRAMES_MODE_CURRENT,                           // Работа только с текущим таймфреймом
   TIMEFRAMES_MODE_LIST,                              // Работа с заданным списком таймфреймов
   TIMEFRAMES_MODE_ALL                                // Работа с полным списком таймфреймов
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Подготавливает массив символов для коллекции символов            |
//+------------------------------------------------------------------+
bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[])
  {
   //--- Если работа с текущим символом
   if(mode_used_symbols==SYMBOLS_MODE_CURRENT)
     {
      //--- Запишем в единственную ячейку массива имя текущего символа
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=Symbol();
      return true;
     }
   //--- Если работа с предопределённым набором символов (из строки defined_used_symbols)
   else if(mode_used_symbols==SYMBOLS_MODE_DEFINES)
     {
      //--- Установим разделитель - запятая
      string separator=",";
      //--- Заменим ошибочные разделители на верный
      if(StringFind(defined_used_symbols,";")>WRONG_VALUE)  StringReplace(defined_used_symbols,";",separator);   
      if(StringFind(defined_used_symbols,":")>WRONG_VALUE)  StringReplace(defined_used_symbols,":",separator); 
      if(StringFind(defined_used_symbols,"|")>WRONG_VALUE)  StringReplace(defined_used_symbols,"|",separator);   
      if(StringFind(defined_used_symbols,"/")>WRONG_VALUE)  StringReplace(defined_used_symbols,"/",separator); 
      if(StringFind(defined_used_symbols,"\\")>WRONG_VALUE) StringReplace(defined_used_symbols,"\\",separator);  
      if(StringFind(defined_used_symbols,"'")>WRONG_VALUE)  StringReplace(defined_used_symbols,"'",separator); 
      if(StringFind(defined_used_symbols,"-")>WRONG_VALUE)  StringReplace(defined_used_symbols,"-",separator);   
      if(StringFind(defined_used_symbols,"`")>WRONG_VALUE)  StringReplace(defined_used_symbols,"`",separator);
      //--- Пока есть пробелы - удаляем
      while(StringFind(defined_used_symbols," ")>WRONG_VALUE && !IsStopped()) 
         StringReplace(defined_used_symbols," ","");
      //--- Пока есть двойные разделители (после удаления между ними пробелов) - меняем на разделитель
      while(StringFind(defined_used_symbols,separator+separator)>WRONG_VALUE && !IsStopped())
         StringReplace(defined_used_symbols,separator+separator,separator);
      //--- Если остался один разделитель перед первым символом в строке - заменяем на пробел
      if(StringFind(defined_used_symbols,separator)==0) 
         StringSetCharacter(defined_used_symbols,0,32);
      //--- Если остался один разделитель после последнего символа в строке - заменяем на пробел
      if(StringFind(defined_used_symbols,separator)==StringLen(defined_used_symbols)-1)
         StringSetCharacter(defined_used_symbols,StringLen(defined_used_symbols)-1,32);
      //--- Убираем всё лишнее слева и справа
      #ifdef __MQL5__
         StringTrimLeft(defined_used_symbols);
         StringTrimRight(defined_used_symbols);
      //---  __MQL4__
      #else 
         defined_used_symbols=StringTrimLeft(defined_used_symbols);
         defined_used_symbols=StringTrimRight(defined_used_symbols);
      #endif 
      //--- Подготавливаем массив 
      ArrayResize(used_symbols_array,0);
      ResetLastError();
      //--- разделяем строку по разделителям (запятая) и вписываем все найденные подстроки в массив
      int n=StringSplit(defined_used_symbols,StringGetCharacter(separator,0),used_symbols_array);
      //--- если ничего не нашли - выведем об этом сообщение (при этом автоматически будет выбрана работа с текущим символом)
      if(n<1)
        {
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_STRING)+Symbol() :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)GetLastError()
           );
         Print(err);
         return false;
        }
     }
   //--- Если работа с окном "Обзор рынка" или с полным списком
   else
     {
      //--- В единственную ячейку массива впишем наименование режима работы (mode_used_symbols)
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=EnumToString(mode_used_symbols);
     }
   return true;
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Подготавливает переданную строку параметров                      |
//+------------------------------------------------------------------+
int StringParamsPrepare(string defined_used,string separator,string &array[])
  {
//--- Заменим ошибочные разделители на верный
   if(separator!=";" && StringFind(defined_used,";")>WRONG_VALUE)  StringReplace(defined_used,";",separator);   
   if(separator!=":" && StringFind(defined_used,":")>WRONG_VALUE)  StringReplace(defined_used,":",separator); 
   if(separator!="|" && StringFind(defined_used,"|")>WRONG_VALUE)  StringReplace(defined_used,"|",separator);   
   if(separator!="/" && StringFind(defined_used,"/")>WRONG_VALUE)  StringReplace(defined_used,"/",separator); 
   if(separator!="\\"&& StringFind(defined_used,"\\")>WRONG_VALUE) StringReplace(defined_used,"\\",separator);  
   if(separator!="'" && StringFind(defined_used,"'")>WRONG_VALUE)  StringReplace(defined_used,"'",separator); 
   if(separator!="-" && StringFind(defined_used,"-")>WRONG_VALUE)  StringReplace(defined_used,"-",separator);   
   if(separator!="`" && StringFind(defined_used,"`")>WRONG_VALUE)  StringReplace(defined_used,"`",separator);
//--- Пока есть пробелы - удаляем
   while(StringFind(defined_used," ")>WRONG_VALUE && !IsStopped()) 
      StringReplace(defined_used," ","");
//--- Пока есть двойные разделители (после удаления между ними пробелов) - меняем на разделитель
   while(StringFind(defined_used,separator+separator)>WRONG_VALUE && !IsStopped())
      StringReplace(defined_used,separator+separator,separator);
//--- Если остался один разделитель перед первым символом в строке - заменяем на пробел
   if(StringFind(defined_used,separator)==0) 
      StringSetCharacter(defined_used,0,32);
//--- Если остался один разделитель после последнего символа в строке - заменяем на пробел
   if(StringFind(defined_used,separator)==StringLen(defined_used)-1)
      StringSetCharacter(defined_used,StringLen(defined_used)-1,32);
//--- Убираем всё лишнее слева и справа
   #ifdef __MQL5__
      StringTrimLeft(defined_used);
      StringTrimRight(defined_used);
//---  __MQL4__
   #else 
      defined_used=StringTrimLeft(defined_used);
      defined_used=StringTrimRight(defined_used);
   #endif 
//--- Подготавливаем массив 
   ArrayResize(array,0);
   ResetLastError();
//--- разделяем строку по разделителям (запятая), вписываем все найденные подстроки в массив и возвращаем количество полученных подстрок
   return StringSplit(defined_used,StringGetCharacter(separator,0),array);
  }
//+------------------------------------------------------------------+

И тогда функция для создания массива используемых символов примет такой вид:

//+------------------------------------------------------------------+
//| Подготавливает массив символов для коллекции символов            |
//+------------------------------------------------------------------+
bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[])
  {
//--- Если работа с текущим символом
   if(mode_used_symbols==SYMBOLS_MODE_CURRENT)
     {
      //--- Запишем в единственную ячейку массива имя текущего символа
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=Symbol();
      return true;
     }
//--- Если работа с предопределённым набором символов (из строки defined_used_symbols)
   else if(mode_used_symbols==SYMBOLS_MODE_DEFINES)
     {
      //--- Установим разделитель - запятая (определён в файле Datas.mqh, стр 11)
      string separator=INPUT_SEPARATOR;
      int n=StringParamsPrepare(defined_used_symbols,separator,used_symbols_array);
      //--- если ничего не нашли - выведем об этом сообщение (при этом автоматически будет выбрана работа с текущим символом)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING)+Symbol() :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         return false;
        }
     }
//--- Если работа с окном "Обзор рынка" или с полным списком
   else
     {
      //--- В единственную ячейку массива впишем наименование режима работы (mode_used_symbols)
      ArrayResize(used_symbols_array,1);
      used_symbols_array[0]=EnumToString(mode_used_symbols);
     }
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+

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

И теперь точно так же создадим функцию для подготовки массива используемых в программе таймфреймов:

//+------------------------------------------------------------------+
//| Подготавливает массив таймфреймов для коллекции таймсерий        |
//+------------------------------------------------------------------+
bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[])
  {
//--- Если работа с текущим периодом графика - установим флаг текущего таймфрейма
   if(mode_used_periods==TIMEFRAMES_MODE_CURRENT)
     {
      ArrayResize(used_periods_array,1,21);
      used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
      return true;
     }
//--- Если работа с предопределённым набором периодов графиков (из строки defined_used_periods)
   else if(mode_used_periods==TIMEFRAMES_MODE_LIST)
     {
      //--- Установим разделитель - запятая (определён в файле Datas.mqh, стр 11)
      string separator=INPUT_SEPARATOR;
      //--- Заполним массив параметров из строки с предопределёнными таймфреймами
      int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array);
      //--- если ничего не нашли - выведем об этом сообщение (при этом автоматически будет выбрана работа с текущим периодом)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         //--- Установим текущий период в массив
         ArrayResize(used_periods_array,1,21);
         used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         return false;
        }
     }
//--- Если работа с полным списком таймфреймов - заполним массив строками описания всех таймфреймов
   else
     {
      ArrayResize(used_periods_array,21,21);
      for(int i=0;i<21;i++)
         used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1)));
     }
//--- Всё успешно
   return true;
  }
//+------------------------------------------------------------------+

Здесь точно так же вызывается функция для подготовки строки параметров — эти коды в двух функциях CreateUsedSymbolsArray() и CreateUsedTimeframesArray() — одинаковые.

Там же — в файле DELib.mqh у нас есть функция, выводящая время с милисекундами:

//+------------------------------------------------------------------+
//| Возвращает время с милисекундами                                 |
//+------------------------------------------------------------------+
string TimeMSCtoString(const long time_msc)
  {
   return TimeToString(time_msc/1000,TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"."+IntegerToString(time_msc%1000,3,'0');
  }
//+------------------------------------------------------------------+

Функция всегда выводит время в формате YYYY.MM.DD HH:MM:SS.MSC

Сделаем возможность выбора формата вывода времени:

//+------------------------------------------------------------------+
//| Возвращает время с милисекундами                                 |
//+------------------------------------------------------------------+
string TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS)
  {
   return TimeToString(time_msc/1000,flags)+"."+IntegerToString(time_msc%1000,3,'0');
  }
//+------------------------------------------------------------------+

Теперь можно будет задавать формат вывода времени + милисекунды.

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

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   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds

InpModeUsedTFs позволяет выбрать режим использования таймфреймов

При выборе второго пункта в программе будет использоваться список таймфреймов, описанных в строке, заданной входным параметром переменной InpUsedTFs.

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

//--- global variables
CEngine        engine;
CTimeSeries    timeseries;
SDataButt      butt_data[TOTAL_BUTT];

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

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

Из обработчика OnInit() советника удалим код создания двух таймсерий, оставшийся от прошлых тестов:

//+------------------------------------------------------------------+
//| 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"));

//--- Установка символа создаваемым таймсериям
   timeseries.SetSymbol(Symbol());
//#define TIMESERIES_ALL
//--- Создание двух таймсерий
   #ifndef TIMESERIES_ALL
      timeseries.SyncData(PERIOD_CURRENT,10);
      timeseries.Create(PERIOD_CURRENT);
      timeseries.SyncData(PERIOD_M15,2);
      timeseries.Create(PERIOD_M15);
//--- Создание всех таймсерий
   #else 
      timeseries.SyncAllData();
      timeseries.CreateAll();
   #endif 
//--- Проверка созданных таймсерий
   CArrayObj *list=timeseries.GetList();
   Print(TextByLanguage("Данные созданных таймсерий:","Data of created timeseries:"));
   for(int i=0;i<list.Total();i++)
     {
      CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i);
      if(series_obj==NULL || series_obj.AmountUsedData()==0 || series_obj.DataTotal()==0)
         continue;
      Print(
            DFUN,i,": ",series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe()),
            ": AmountUsedData=",series_obj.AmountUsedData(),", DataTotal=",series_obj.DataTotal(),", Bars=",series_obj.Bars()
           );
     }
   Print("");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

А из обработчика OnTick() советника удалим код обновления созданных таймсерий (обновление таймсерий будем делать в следующей статье):

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если работа в тестере
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();       // Работа в таймере
      PressButtonsControl();  // Контроль нажатия кнопок
      EventsHandling();       // Работа с событиями
     }
//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();    // Трейлинг позиций
      TrailingOrders();       // Трейлинг отложенных ордеров
     }
//--- Обновление созданных таймсерий
   CArrayObj *list=timeseries.GetList();
   for(int i=0;i<list.Total();i++)
     {
      CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i);
      if(series_obj==NULL || series_obj.DataTotal()==0)
         continue;
      series_obj.Refresh();
      if(series_obj.IsNewBar(0))
        {
         Print(TextByLanguage("Новый бар на ","New bar on "),series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe())," ",TimeToString(series_obj.Time(0)));
         if(series_obj.Timeframe()==Period())
            engine.PlaySoundByDescription(SND_NEWS);
        }
     }
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Инициализация библиотеки DoEasy                                  |
//+------------------------------------------------------------------+
void OnInitDoEasy()
  {
//--- Проверка на выбор работы с полным списком
   used_symbols_mode=InpModeUsedSymbols;
   if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
     {
      int total=SymbolsTotal(false);
      string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов.";
      string en_n="\nThe number of symbols on server "+(string)total+".\nMaximal number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols.";
      string caption=TextByLanguage("Внимание!","Attention!");
      string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
      string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
      string message=TextByLanguage(ru,en);
      int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
      int mb_res=MessageBox(message,caption,flags);
      switch(mb_res)
        {
         case IDNO : 
           used_symbols_mode=SYMBOLS_MODE_CURRENT; 
           break;
         default:
           break;
        }
     }
//--- Установим начало отсчёта счётчика для замера примерного времени инициализации библиотеки
   ulong begin=GetTickCount();
   Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---"));
//--- Заполнение массива используемых символов
   CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols);
//--- Установка типа используемого списка символов в коллекции символов и заполнение списка таймсерий символов
   engine.SetUsedSymbols(array_used_symbols);
//--- Отображение в журнале выбранного режима работы с коллекцией объектов-символов
   string num=
     (
      used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : 
      TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal()
     );
   Print(engine.ModeSymbolsListDescription(),num);
//--- Вывод списка используемых символов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#ifdef __MQL5__
   if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT)
     {
      string array_symbols[];
      CArrayObj* list_symbols=engine.GetListAllUsedSymbols();
      for(int i=0;i<list_symbols.Total();i++)
        {
         CSymbol *symbol=list_symbols.At(i);
         if(symbol==NULL)
            continue;
         ArrayResize(array_symbols,ArraySize(array_symbols)+1,1000);
         array_symbols[ArraySize(array_symbols)-1]=symbol.Name();
        }
      ArrayPrint(array_symbols);
     }
#endif   
//--- Установка используемых таймфреймов
   CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods);
//--- Отображение выбранного режима работы с коллекцией объектов-таймсерий
   string mode=
     (
      InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT   ? 
         TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period())   :
      InpModeUsedTFs==TIMEFRAMES_MODE_LIST      ? 
         TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:")                                              :
      TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:")
     );
   Print(mode);
//--- Вывод списка используемых таймфреймов сделаем только для MQL5 - в MQL4 нет функции ArrayPrint()
#ifdef __MQL5__
   if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT)
      ArrayPrint(array_used_periods);
#endif 
//--- Создание таймсерий всех используемых символов
   CArrayObj *list_timeseries=engine.GetListTimeSeries();
   int total=list_timeseries.Total();
   for(int i=0;i<total;i++)
     {
      CTimeSeries *timeseries=list_timeseries.At(i);
      if(list_timeseries==NULL)
         continue;
      int total_periods=ArraySize(array_used_periods);
      for(int j=0;j<total_periods;j++)
        {
         ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]);
         engine.SeriesSyncData(timeseries.Symbol(),timeframe);
         engine.SeriesCreate(timeseries.Symbol(),timeframe);
        }
     }
//--- Проверка созданных таймсерий - выводим в журнал описания всех созданных таймсерий
//--- (true - только созданные, false - созданные и объявленные)
   engine.GetTimeSeriesCollection().PrintShort(true); // Краткие описания
   //engine.GetTimeSeriesCollection().Print(false);   // Полные описания

//--- Создание тестовых файлов ресурсов
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","The sound of a falling coin 1"),sound_array_coin_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Sound fallen coins"),sound_array_coin_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Sound of coins"),sound_array_coin_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2"),sound_array_coin_04);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Click on the button sound 1"),sound_array_click_01);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Click on the button sound 1"),sound_array_click_02);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Click on the button sound 1"),sound_array_click_03);
   engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","The sound of the cash machine"),sound_array_cash_machine_01);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green);
   engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red);

//--- Передача в торговый класс всех имеющихся коллекций
   engine.TradingOnInit();

//--- Установка магика по умолчанию для всех используемых инструментов
   engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
//--- Установка синхронной передачи приказов для всех используемых символов
   engine.TradingSetAsyncMode(false);
//--- Установка количества торговых попыток при ошибке
   engine.TradingSetTotalTry(InpTotalAttempts);
//--- Установка корректных типов истечения и заливки ордера всем торговым объектам
   engine.TradingSetCorrectTypeExpiration();
   engine.TradingSetCorrectTypeFilling();

//--- Установка стандартных звуков торговым объектам всех используемых символов
   engine.SetSoundsStandart();
//--- Установка общего флага использования звуков
   engine.SetUseSounds(InpUseSounds);
//--- Установка множителя спреда торговым объектам символов в коллекции символов
   engine.SetSpreadMultiplier(InpSpreadMultiplier);
      
//--- Установка контрольных значений для символов
   //--- Получаем список всех символов коллекции
   CArrayObj *list=engine.GetListAllUsedSymbols();
   if(list!=NULL && list.Total()!=0)
     {
      //--- В цикле по списку устанавливаем нужные значения для отслеживаемых свойств символов
      //--- По умолчанию всем свойствам установлены значения LONG_MAX, что означает "Не отслеживать данное свойство" 
      //--- Включить или выключить (задать величину меньше LONG_MAX или наоборот - установить значение LONG_MAX) можно в любое время в любом месте программы
      /*
      for(int i=0;i<list.Total();i++)
        {
         CSymbol* symbol=list.At(i);
         if(symbol==NULL)
            continue;
         //--- Установка контроля увеличения цены символа на 100 пунктов
         symbol.SetControlBidInc(100000*symbol.Point());
         //--- Установка контроля уменьшения цены символа на 100 пунктов
         symbol.SetControlBidDec(100000*symbol.Point());
         //--- Установка контроля увеличения спреда символа на 40 пунктов
         symbol.SetControlSpreadInc(400);
         //--- Установка контроля уменьшения спреда символа на 40 пунктов
         symbol.SetControlSpreadDec(400);
         //--- Установка контроля размера спреда по значению 40 пунктов
         symbol.SetControlSpreadLevel(400);
        }
      */
     }
//--- Установка контрольных значений для текущего аккаунта
   CAccount* account=engine.GetAccountCurrent();
   if(account!=NULL)
     {
      //--- Установка контроля увеличения значения прибыли на 10
      account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0);
      //--- Установка контроля увеличения значения средств на 15
      account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0);
      //--- Установка контрольного уровня прибыли на 20
      account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0);
     }
//--- Получим конец отсчёта времени инициализации библиотеки и выведем его в журнал
   ulong end=GetTickCount();
   Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS));
  }
//+------------------------------------------------------------------+

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

--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом: "EURUSD"
Работа только с текущим таймфреймом: H4
Таймсерия символа EURUSD: 
Таймсерия "EURUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5330
Время инициализации библиотеки: 00:00:00.141

Зададим в настройках использование текущего символа и заданного списка таймфреймов (в списке указаны основные таймфреймы).
В журнал будут выведены сообщения:

--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом: "EURUSD"
Работа с заданным списком таймфреймов:
"M1"  "M5"  "M15" "M30" "H1"  "H4"  "D1"  "W1"  "MN1"
Таймсерия символа EURUSD: 
Таймсерия "EURUSD" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3286
Таймсерия "EURUSD" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3566
Таймсерия "EURUSD" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3109
Таймсерия "EURUSD" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2894
Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5505
Таймсерия "EURUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5330
Таймсерия "EURUSD" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5087
Таймсерия "EURUSD" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2564
Таймсерия "EURUSD" MN1: Запрошено: 1000, Фактически: 590, Создано: 590, На сервере: 590
Время инициализации библиотеки: 00:00:00.032

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

--- Инициализация библиотеки "DoEasy" ---
Работа только с текущим символом: "EURUSD"
Работа с полным списком таймфреймов:
"M1"  "M2"  "M3"  "M4"  "M5"  "M6"  "M10" "M12" "M15" "M20" "M30" "H1"  "H2"  "H3"  "H4"  "H6"  "H8"  "H12" "D1"  "W1"  "MN1"
Таймсерия символа EURUSD: 
Таймсерия "EURUSD" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3390
Таймсерия "EURUSD" M2: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5626
Таймсерия "EURUSD" M3: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4713
Таймсерия "EURUSD" M4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4254
Таймсерия "EURUSD" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3587
Таймсерия "EURUSD" M6: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4805
Таймсерия "EURUSD" M10: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4035
Таймсерия "EURUSD" M12: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3842
Таймсерия "EURUSD" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3116
Таймсерия "EURUSD" M20: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3457
Таймсерия "EURUSD" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2898
Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5507
Таймсерия "EURUSD" H2: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6303
Таймсерия "EURUSD" H3: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 6263
Таймсерия "EURUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5331
Таймсерия "EURUSD" H6: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5208
Таймсерия "EURUSD" H8: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5463
Таймсерия "EURUSD" H12: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5205
Таймсерия "EURUSD" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5087
Таймсерия "EURUSD" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2564
Таймсерия "EURUSD" MN1: Запрошено: 1000, Фактически: 590, Создано: 590, На сервере: 590
Время инициализации библиотеки: 00:00:00.094

Зададим в настройках использование заданного списка символов и зададим в списке три символа EURUSD,AUDUSD,EURAUD, и заданного списка таймфреймов (в списке указаны основные таймфреймы).
В журнал будут выведены сообщения:

--- Инициализация библиотеки "DoEasy" ---
Работа с предопределённым списком символов. Количество используемых символов: 3
"AUDUSD" "EURUSD" "EURAUD"
Работа с заданным списком таймфреймов:
"M1"  "M5"  "M15" "M30" "H1"  "H4"  "D1"  "W1"  "MN1"
Таймсерия символа AUDUSD: 
Таймсерия "AUDUSD" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3394
Таймсерия "AUDUSD" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4024
Таймсерия "AUDUSD" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3262
Таймсерия "AUDUSD" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3071
Таймсерия "AUDUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5104
Таймсерия "AUDUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5026
Таймсерия "AUDUSD" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5289
Таймсерия "AUDUSD" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 1401
Таймсерия "AUDUSD" MN1: Запрошено: 1000, Фактически: 323, Создано: 323, На сервере: 323
Таймсерия символа EURAUD: 
Таймсерия "EURAUD" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3393
Таймсерия "EURAUD" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4025
Таймсерия "EURAUD" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3262
Таймсерия "EURAUD" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3071
Таймсерия "EURAUD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5104
Таймсерия "EURAUD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5026
Таймсерия "EURAUD" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 4071
Таймсерия "EURAUD" W1: Запрошено: 1000, Фактически: 820, Создано: 820, На сервере: 820
Таймсерия "EURAUD" MN1: Запрошено: 1000, Фактически: 189, Создано: 189, На сервере: 189
Таймсерия символа EURUSD: 
Таймсерия "EURUSD" M1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3394
Таймсерия "EURUSD" M5: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3588
Таймсерия "EURUSD" M15: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 3116
Таймсерия "EURUSD" M30: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2898
Таймсерия "EURUSD" H1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5507
Таймсерия "EURUSD" H4: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5331
Таймсерия "EURUSD" D1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 5087
Таймсерия "EURUSD" W1: Запрошено: 1000, Фактически: 1000, Создано: 1000, На сервере: 2564
Таймсерия "EURUSD" MN1: Запрошено: 1000, Фактически: 590, Создано: 590, На сервере: 590
Время инициализации библиотеки: 00:00:00.266

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

Что дальше

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

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

К содержанию

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

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