Прочие классы в библиотеке DoEasy (Часть 69): Класс-коллекция объектов-чартов

Artyom Trishkin | 2 апреля, 2021

Содержание


Концепция

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


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

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

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

   MSG_CHART_OBJ_CHART_WINDOW,                        // Главное окно графика
   MSG_CHART_OBJ_CHART_SUBWINDOW,                     // Подокно графика
   MSG_CHART_OBJ_CHART_SUBWINDOWS_NUM,                // Подокон
   MSG_CHART_OBJ_INDICATORS_MW_NAME_LIST,             // Индикаторы в главном окне графика
   MSG_CHART_OBJ_INDICATORS_SW_NAME_LIST,             // Индикаторы в окне графика
   MSG_CHART_OBJ_INDICATOR,                           // Индикатор
   MSG_CHART_OBJ_INDICATORS_TOTAL,                    // Индикаторов
   MSG_CHART_OBJ_WINDOW_N,                            // Окно
   MSG_CHART_OBJ_INDICATORS_NONE,                     // Отсутствуют
  
//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Коллекция чартов
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Не удалось создать новый объект-чарт
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Не удалось добавить объект-чарт в коллекцию
  
  };
//+------------------------------------------------------------------+

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

   {"Главное окно графика","Main chart window"},
   {"Подокно графика","Chart subwindow"},
   {"Подокон","Subwindows"},
   {"Индикаторы в главном окне графика","Indicators in the main chart window"},
   {"Индикаторы в окне графика","Indicators in the chart window"},
   {"Индикатор","Indicator"},
   {"Индикаторов","Indicators total"},
   {"Окно","Window"},
   {"Отсутствуют","No indicators"},
   
//--- CChartObjCollection
   {"Коллекция чартов","Chart collection"},
   {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
   {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
   
  };
//+---------------------------------------------------------------------+

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

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

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

В файле \MQL5\Include\DoEasy\Defines.mqh в разделе "Макроподстановки" пропишем параметры счётчика таймера коллекции объектов-чартов и идентификатор списка коллекции объектов-чартов:

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
//--- "Описание функции с номером строки ошибки"
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Стр. " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Описание функции"
#define COUNTRY_LANG                   ("Russian")                // Язык страны
#define END_TIME                       (D'31.12.3000 23:59:59')   // Конечная дата для запросов данных истории счёта
#define TIMER_FREQUENCY                (16)                       // Минимальная частота таймера библиотеки в миллисекундах
#define TOTAL_TRADE_TRY                (5)                        // Количество торговых попыток по умолчанию
#define IND_COLORS_TOTAL               (64)                       // Максимально возможное количество цветов индикаторного буфера
#define IND_BUFFERS_MAX                (512)                      // Максимально возможное количество буферов индикатора
//--- Стандартные звуки
#define SND_ALERT                      "alert.wav"
#define SND_ALERT2                     "alert2.wav"
#define SND_CONNECT                    "connect.wav"
#define SND_DISCONNECT                 "disconnect.wav"
#define SND_EMAIL                      "email.wav"
#define SND_EXPERT                     "expert.wav"
#define SND_NEWS                       "news.wav"
#define SND_OK                         "ok.wav"
#define SND_REQUEST                    "request.wav"
#define SND_STOPS                      "stops.wav"
#define SND_TICK                       "tick.wav"
#define SND_TIMEOUT                    "timeout.wav"
#define SND_WAIT                       "wait.wav"
//--- Параметры таймера коллекции ордеров и сделок
#define COLLECTION_ORD_PAUSE           (250)                      // Пауза таймера коллекции ордеров и сделок в миллисекундах
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера коллекции ордеров и сделок
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Идентификатор счётчика таймера коллекции ордеров и сделок
//--- Параметры таймера коллекции аккаунтов
#define COLLECTION_ACC_PAUSE           (1000)                     // Пауза таймера коллекции аккаунтов в миллисекундах
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера аккаунтов
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Идентификатор счётчика таймера аккаунтов
//--- Параметры таймера1 коллекции символов
#define COLLECTION_SYM_PAUSE1          (100)                      // Пауза таймера1 коллекции символов в миллисекундах (для сканирования символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Шаг приращения счётчика таймера1 символов
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Идентификатор счётчика таймера1 символов
//--- Параметры таймера2 коллекции символов
#define COLLECTION_SYM_PAUSE2          (300)                      // Пауза таймера2 коллекции символов в миллисекундах (для событий списка символов в обзоре рынка)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Шаг приращения счётчика таймера2 символов
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Идентификатор счётчика таймера2 символов
//--- Параметры таймера торгового класса
#define COLLECTION_REQ_PAUSE           (300)                      // Пауза таймера торгового класса в миллисекундах
#define COLLECTION_REQ_COUNTER_STEP    (16)                       // Шаг приращения счётчика таймера торгового класса
#define COLLECTION_REQ_COUNTER_ID      (5)                        // Идентификатор счётчика таймера торгового класса
//--- Параметры таймера коллекции таймсерий
#define COLLECTION_TS_PAUSE            (64)                       // Пауза таймера коллекции таймсерий в миллисекундах
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Шаг приращения счётчика таймера таймсерий
#define COLLECTION_TS_COUNTER_ID       (6)                        // Идентификатор счётчика таймера таймсерий
//--- Параметры таймера коллекции таймсерий индикаторных данных
#define COLLECTION_IND_TS_PAUSE        (64)                       // Пауза таймера коллекции таймсерий индикаторных данных в миллисекундах
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера таймсерий индикаторных данных
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // Идентификатор счётчика таймера таймсерий индикаторных данных
//--- Параметры таймера коллекции тиковых серий
#define COLLECTION_TICKS_PAUSE         (64)                       // Пауза таймера коллекции тиковых серий в миллисекундах
#define COLLECTION_TICKS_COUNTER_STEP  (16)                       // Шаг приращения счётчика таймера тиковых серий
#define COLLECTION_TICKS_COUNTER_ID    (8)                        // Идентификатор счётчика таймера тиковых серий
//--- Параметры таймера коллекции чартов
#define COLLECTION_CHARTS_PAUSE        (500)                      // Пауза таймера коллекции чартов в миллисекундах
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Шаг приращения счётчика таймера чартов
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Идентификатор счётчика таймера чартов
//--- Идентификаторы списков коллекций
#define COLLECTION_HISTORY_ID          (0x777A)                   // Идентификатор списка исторической коллекции
#define COLLECTION_MARKET_ID           (0x777B)                   // Идентификатор списка рыночной коллекции
#define COLLECTION_EVENTS_ID           (0x777C)                   // Идентификатор списка коллекции событий
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Идентификатор списка коллекции аккаунтов
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Идентификатор списка коллекции символов
#define COLLECTION_SERIES_ID           (0x777F)                   // Идентификатор списка коллекции таймсерий
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Идентификатор списка коллекции индикаторных буферов
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Идентификатор списка коллекции индикаторов
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Идентификатор списка коллекции индикаторных данных
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Идентификатор списка коллекции тиковых серий
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // Идентификатор списка коллекции серий стаканов цен
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // Идентификатор списка коллекции mql5-сигналов
#define COLLECTION_CHARTS_ID           (0x7786)                   // Идентификатор списка коллекции чартов
//--- Параметры данных для файловых операций

В перечислении целочисленных свойств чарта удалим константу CHART_PROP_WINDOW_IS_VISIBLE, так как я не нашёл практического применения данного свойства для объекта. Соответственно, уменьшим на 1 значение количества целочисленных свойств (с 67 до 66):

#define CHART_PROP_INTEGER_TOTAL (66)                 // Общее количество целочисленных свойств

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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки чартов                             |
//+------------------------------------------------------------------+
#define FIRST_CHART_DBL_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP)
#define FIRST_CHART_STR_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP+CHART_PROP_DOUBLE_TOTAL-CHART_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CHART_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_CHART_ID = 0,                              // Сортировать по идентификатору графика
   SORT_BY_CHART_TIMEFRAME,                           // Сортировать по таймфрейму графика
   SORT_BY_CHART_SHOW,                                // Сортировать по признаку отрисовки ценового графика
   SORT_BY_CHART_IS_OBJECT,                           // Сортировать по признаку идентификации объекта "График" (OBJ_CHART)

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

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

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

Подключим к файлу класса CSelect файл класса объекта-чарта:

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

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

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

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

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

Работа этих методов подробно рассматривалась нами в статье 3 в разделе "Организация поиска".

При изменении количества окон, присоединённых к графику, индексы окон меняются, поэтому в некоторых случаях нам может потребоваться устанавливать новый индекс окна. В файле класса объекта-окна чарта \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh в публичной секции класса напишем новый метод для установки индекса окна:

//+------------------------------------------------------------------+
//| Класс объекта-индикатора окна графика                            |
//+------------------------------------------------------------------+
class CWndInd : public CObject
  {
private:
   long              m_chart_id;                         // Идентификатор графика
   string            m_name;                             // Короткое имя индикатора
   int               m_index;                            // Индекс окна на графике
   int               m_handle;                           // Хэндл индикатора
public:
//--- Возвращает себя
   CWndInd          *GetObject(void)                     { return &this;         }
//--- Возвращает (1) имя индикатора, (2) индекс окна, (3) хэндл индикатора
   string            Name(void)                    const { return this.m_name;   }
   int               Index(void)                   const { return this.m_index;  }
   int               Handle(void)                  const { return this.m_handle; }
//--- Устанавливает индекс окна на графике
   void              SetIndex(const int index)           { this.m_index=index;   }
   
//--- Выводит в журнал описание свойств объекта (dash=true - дефис перед описанием, false - только описание)
   void              Print(const bool dash=false)        { ::Print((dash ? "- " : "")+this.Header());                      }
//--- Возвращает краткое наименование объекта
   string            Header(void)                  const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); }
   
//--- Сравнивает объекты CWndInd между собой по указанному свойству
   virtual int       Compare(const CObject *node,const int mode=0) const;
   
//--- Конструкторы
                     CWndInd(void);
                     CWndInd(const int handle,const string name,const int index) : m_handle(handle),m_name(name),m_index(index) {}
  };
//+------------------------------------------------------------------+

Немного доработаем класс объекта-чарта, расположенный в файле \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh.

Из списка приватных методов для установки свойств объекта удалим объявление метода SetVisible(), так как мы решили избавиться от свойства, устанавливаемого этим методом, не несущего нам никакой полезной нагрузки:

//--- Методы установки значений свойств
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetVisible(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);

За пределами тела класса найдём его реализацию и тоже удалим:

//+------------------------------------------------------------------+
//|  Устанавливает свойство                                          |
//| "Общее количество окон графика, включая подокна индикаторов"     |
//+------------------------------------------------------------------+
void CChartObj::SetWindowsTotal(void)
  {
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));
  }
//+------------------------------------------------------------------+
//| Устанавливает свойство "Видимость подокна"                       |
//+------------------------------------------------------------------+
void CChartObj::SetVisible(void)
  {
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));
  }
//+------------------------------------------------------------------+
//| Устанавливает свойство "Номер первого видимого бара на графике"  |
//+------------------------------------------------------------------+
void CChartObj::SetFirstVisibleBars(void)
  {
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));
  }
//+------------------------------------------------------------------+

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

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

//--- Методы установки значений свойств
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);
   
//--- Создаёт список окон графика
   void              CreateWindowsList(void);

public:

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

//--- Обновляет объект-чарт и его список окон индикаторов
   void              Refresh(void);
   
//--- Конструкторы
                     CChartObj(){;}
                     CChartObj(const long chart_id);

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

В методе WindowsTotal() впишем сначала запрос и установку свойства количества окон графика, осуществляемых в методе SetWindowsTotal(), а затем возврат только что полученного и сохранённого в свойствах объекта значения. Реализацию метода Visible() удалим:

//--- Возвращает количество баров на графике, доступных для отображения
   int               VisibleBars(void)                               const { return (int)this.GetProperty(CHART_PROP_VISIBLE_BARS);       }

//--- Возвращает общее количество окон графика, включая подокна индикаторов
   int               WindowsTotal(void) 
                       { 
                        this.SetWindowsTotal();
                        return (int)this.GetProperty(CHART_PROP_WINDOWS_TOTAL);
                       }

//--- Возвращает видимость окна
   bool              Visible(void)                                   const { return (bool)this.GetProperty(CHART_PROP_WINDOW_IS_VISIBLE); }

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

//--- Эмулирует тик (обновления графика - аналогично команде Refresh в терминале)
   void              EmulateTick(void)                                     { ::ChartSetSymbolPeriod(this.ID(),this.Symbol(),this.Timeframe());}

//--- Возвращает флаг, что объект-чарт принадлежит графику программы
   bool              IsMainChart(void)                               const { return(this.m_chart_id==CBaseObj::GetMainChartID());            }
//--- Возвращает указанное по индексу окно графика
   CChartWnd        *GetWindowByIndex(const int index)               const { return this.m_list_wnd.At(index);                               }

В базовом объекте всех объектов библиотеки CBaseObj есть переменная m_chart_id_main, хранящая идентификатор графика, на котором запущена программа. В его конструкторе этой переменной устанавливается значение, возвращаемое функцией ChartID(), и возвращается значение идентификатора текущего графика методом GetMainChartID() класса CBaseObj, который возвращает значение, записанное в переменную m_chart_id_main. Таким образом мы возвращаем флаг того, что идентификатор текущего графика совпадает с идентификатором главного окна программы. При совпадении идентификаторов метод возвращает true, иначе — false.

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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Установка идентификатора графика в базовый объект
   CBaseObj::SetChartID(chart_id);
//--- Установка целочисленных свойств
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Идентификатор графика
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Таймфрейм графика
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Признак отрисовки ценового графика
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Признак идентификации объекта "График"
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Показ графика поверх всех других
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Доступ к контекстному меню по нажатию правой клавиши мышки
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Доступ к инструменту "Перекрестие" по нажатию средней клавиши мышки
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Прокрутка графика левой кнопкой мышки по горизонтали
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Отправка всем mql5-программам на графике сообщений о событиях колёсика мышки
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Отправка всем mql5-программам на графике сообщений о событиях перемещения и нажатия кнопок мышки
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Отправка всем mql5-программам на графике сообщений о событии создания графического объекта
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Отправка всем mql5-программам на графике сообщений о событии уничтожения графического объекта
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Тип графика (свечи, бары или линия
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Ценовой график на переднем плане
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Режим отступа ценового графика от правого края
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // Режим автоматического перехода к правому краю графика
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Разрешение на управление графиком с помощью клавиатуры
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Разрешение на перехват графиком нажатий клавиш Space и Enter для активации строки быстрой навигации
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Масштаб
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Режим фиксированного масштаба
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // Режим масштаба 1:1
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Режим указания масштаба в пунктах на бар
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Отображение в левом верхнем углу тикера символа
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Отображение в левом верхнем углу значений OHLC
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Отображение значения Bid горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Отображение значения Ask горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Отображение значения Last горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Отображение вертикальных разделителей между соседними периодами
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Отображение сетки на графике
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Отображение объемов на графике
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Отображение текстовых описаний объектов
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Количество баров на графике, доступных для отображения
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // Общее количество окон графика, включая подокна индикаторов
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));// Видимость окна
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Хэндл окна графика
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Дистанция в пикселях по вертикальной оси Y между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Номер первого видимого бара на графике
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Ширина графика в барах
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Ширина графика в пикселях
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Высота графика в пикселях
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Цвет фона графика
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Цвет осей, шкалы и строки OHLC
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Цвет сетки
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Цвет объемов и уровней открытия позиций
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Цвет бара вверх, тени и окантовки тела бычьей свечи
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Цвет бара вниз, тени и окантовки тела медвежьей свечи
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Цвет линии графика и японских свечей "Доджи"
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Цвет тела бычьей свечи
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Цвет тела медвежьей свечи
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Цвет линии Bid-цены
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Цвет линии Ask-цены
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Цвет линии цены последней совершенной сделки (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Цвет уровней стоп-ордеров (Stop Loss и Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Отображение на графике торговых уровней (уровни открытых позиций, Stop Loss, Take Profit и отложенных ордеров)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Разрешение на перетаскивание торговых уровней на графике с помощью мышки
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Отображение на графике шкалы времени
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Отображение на графике ценовой шкалы
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Отображение на графике панели быстрой торговли
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Окно графика развернуто
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Окно графика свернуто
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Окно графика закреплено
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Левая координата открепленного графика относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Верхняя координата открепленного графика относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Правая координата открепленного графика  относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Нижняя координата открепленного графика  относительно виртуального экрана
//--- Установка вещественных свойств
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Размер отступа нулевого бара от правого края в процентах
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Положение фиксированной позиции графика от левого края в процентах
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Фиксированный максимум графика
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Фиксированный минимум графика
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Значение масштаба в пунктах на бар
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Минимум графика
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Максимум графика
//--- Установка строковых свойств
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Текст комментария на графике
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // Имя эксперта, запущенного на графике
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // Имя скрипта, запущенного на графике
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Символ графика
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);

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

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   int total=this.WindowsTotal();
   for(int i=0;i<total;i++)
     {
      CChartWnd *wnd=new CChartWnd(m_chart_id,i);
      if(wnd==NULL)
         continue;
      m_list_wnd.Sort();
      if(!m_list_wnd.Add(wnd))
         delete wnd;
     }

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

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.CreateWindowsList();

В итоге параметрический конструктор теперь будет таким:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Установка идентификатора графика в базовый объект
   CBaseObj::SetChartID(chart_id);
//--- Установка целочисленных свойств
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Идентификатор графика
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Таймфрейм графика
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Признак отрисовки ценового графика
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Признак идентификации объекта "График"
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Показ графика поверх всех других
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Доступ к контекстному меню по нажатию правой клавиши мышки
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Доступ к инструменту "Перекрестие" по нажатию средней клавиши мышки
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Прокрутка графика левой кнопкой мышки по горизонтали
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Отправка всем mql5-программам на графике сообщений о событиях колёсика мышки
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Отправка всем mql5-программам на графике сообщений о событиях перемещения и нажатия кнопок мышки
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Отправка всем mql5-программам на графике сообщений о событии создания графического объекта
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Отправка всем mql5-программам на графике сообщений о событии уничтожения графического объекта
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Тип графика (свечи, бары или линия
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Ценовой график на переднем плане
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Режим отступа ценового графика от правого края
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // Режим автоматического перехода к правому краю графика
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Разрешение на управление графиком с помощью клавиатуры
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Разрешение на перехват графиком нажатий клавиш Space и Enter для активации строки быстрой навигации
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Масштаб
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Режим фиксированного масштаба
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // Режим масштаба 1:1
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Режим указания масштаба в пунктах на бар
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Отображение в левом верхнем углу тикера символа
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Отображение в левом верхнем углу значений OHLC
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Отображение значения Bid горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Отображение значения Ask горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Отображение значения Last горизонтальной линией на графике
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Отображение вертикальных разделителей между соседними периодами
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Отображение сетки на графике
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Отображение объемов на графике
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Отображение текстовых описаний объектов
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Количество баров на графике, доступных для отображения
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // Общее количество окон графика, включая подокна индикаторов
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Хэндл окна графика
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Дистанция в пикселях по вертикальной оси Y между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Номер первого видимого бара на графике
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Ширина графика в барах
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Ширина графика в пикселях
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Высота графика в пикселях
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Цвет фона графика
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Цвет осей, шкалы и строки OHLC
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Цвет сетки
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Цвет объемов и уровней открытия позиций
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Цвет бара вверх, тени и окантовки тела бычьей свечи
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Цвет бара вниз, тени и окантовки тела медвежьей свечи
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Цвет линии графика и японских свечей "Доджи"
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Цвет тела бычьей свечи
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Цвет тела медвежьей свечи
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Цвет линии Bid-цены
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Цвет линии Ask-цены
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Цвет линии цены последней совершенной сделки (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Цвет уровней стоп-ордеров (Stop Loss и Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Отображение на графике торговых уровней (уровни открытых позиций, Stop Loss, Take Profit и отложенных ордеров)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Разрешение на перетаскивание торговых уровней на графике с помощью мышки
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Отображение на графике шкалы времени
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Отображение на графике ценовой шкалы
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Отображение на графике панели быстрой торговли
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Окно графика развернуто
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Окно графика свернуто
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Окно графика закреплено
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Левая координата открепленного графика относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Верхняя координата открепленного графика относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Правая координата открепленного графика  относительно виртуального экрана
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Нижняя координата открепленного графика  относительно виртуального экрана
//--- Установка вещественных свойств
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Размер отступа нулевого бара от правого края в процентах
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Положение фиксированной позиции графика от левого края в процентах
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Фиксированный максимум графика
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Фиксированный минимум графика
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Значение масштаба в пунктах на бар
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Минимум графика
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Максимум графика
//--- Установка строковых свойств
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Текст комментария на графике
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // Имя эксперта, запущенного на графике
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // Имя скрипта, запущенного на графике
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Символ графика
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Из метода GetPropertyDescription(), возвращающего описание целочисленного свойства объекта, удалим блок кода, в котором создаётся описание параметра видимости окна — это свойство мы удалили из объекта:

      property==CHART_PROP_WINDOWS_TOTAL  ?  CMessage::Text(MSG_CHART_OBJ_WINDOWS_TOTAL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CHART_PROP_WINDOW_IS_VISIBLE  ?  CMessage::Text(MSG_CHART_OBJ_WINDOW_IS_VISIBLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==CHART_PROP_WINDOW_HANDLE  ?  CMessage::Text(MSG_CHART_OBJ_WINDOW_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

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

//+------------------------------------------------------------------+
//| Выводит в журнал данные всех индикаторов всех окон графика       |
//+------------------------------------------------------------------+
void CChartObj::PrintWndIndicators(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.PrintIndicators(true);
     }
  }
//+------------------------------------------------------------------+
//| Выводит в журнал свойства всех окон графика                      |
//+------------------------------------------------------------------+
void CChartObj::PrintWndParameters(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.PrintParameters(true);
     }
  }
//+------------------------------------------------------------------+

Ранее циклы считались до значения, возвращаемого методом WindowsTotal(), что не совсем верно — этот метод возвращает реальное значение, полученное от графика, в то время как в списках может быть либо меньше, либо больше объектов, чем есть на самом деле. Это происходит при изменении их количества на графике в терминале. Поэтому цикл по неправильному количеству объектов в списке чреват ошибками, что мы здесь и исправили.

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

//+------------------------------------------------------------------+
//| Обновляет объект-чарт и список его окон                          |
//+------------------------------------------------------------------+
void CChartObj::Refresh(void)
  {
   int change=this.WindowsTotal()-this.m_list_wnd.Total();
   if(change==0)
      return;
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

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

Напишем реализацию метода, создающего список объектов-окон графика, принадлежащих чарту:

//+------------------------------------------------------------------+
//| Создаёт список окон графика                                      |
//+------------------------------------------------------------------+
void CChartObj::CreateWindowsList(void)
  {
   this.m_list_wnd.Clear();
   int total=this.WindowsTotal();
   for(int i=0;i<total;i++)
     {
      CChartWnd *wnd=new CChartWnd(m_chart_id,i);
      if(wnd==NULL)
         continue;
      this.m_list_wnd.Sort();
      if(!m_list_wnd.Add(wnd))
         delete wnd;
     }
  }
//+------------------------------------------------------------------+

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

Доработка классов библиотеки завершена.

Приступим к созданию класса-коллекции объектов-чартов.


Класс-коллекция объектов-чартов

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

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

В папке библиотеки \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh создадим новый файл класса CChartObjCollection. Класс должен быть унаследован от класса базового объекта всех объектов библиотеки CBaseObj, а файл объекта-чарта должен быть подключен к файлу класса-коллекции объектов-чартов:

//+------------------------------------------------------------------+
//|                                           ChartObjCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
//+------------------------------------------------------------------+
//| Коллекция объектов-mql5-сигналов                                 |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
  }

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

//+------------------------------------------------------------------+
//| Коллекция объектов-mql5-сигналов                                 |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // Список объектов-чартов
   int                     m_charts_total_prev;                      // Прошлое количество чартов в терминале
   //--- Возвращает количество чартов в терминале
   int                     ChartsTotal(void) const;
   //--- Возвращает флаг существования (1) объекта-чарта, (2) чарта
   bool                    IsPresentChartObj(const long chart_id);
   bool                    IsPresentChart(const long chart_id);
   //--- Создаёт новый объект-чарт и добавляет его в список
   bool                    CreateNewChartObj(const long chart_id,const string source);
   //--- Находит отсутствующий объект-чарт, создаёт его и добавляет в список-коллекцию
   bool                    FindAndCreateMissingChartObj(void);
   //--- Находит и удаляет из списка объект-чарт, отсутствующий в терминале
   void                    FindAndDeleteExcessChartObj(void);
public:
  }

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

public:
//--- Возвращает (1) себя, (2) список-коллекцию объектов-чартов
   CChartObjCollection    *GetObject(void)                                 { return &this;                  }
   CArrayObj              *GetList(void)                                   { return &this.m_list;           }
   //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj              *GetList(ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
//--- Возвращает количество объектов-чартов в списке
   int                     DataTotal(void)                           const { return this.m_list.Total();    }
//--- Выводит в журнал (1) полное, (2) краткое описание коллекции
   void                    Print(void);
   void                    PrintShort(void);
//--- Конструктор
                           CChartObjCollection();

и добавим вспомогательные методы:

//--- Возвращает указатель на объект-чарт (1) по идентификатору, (2) по индексу в списке
   CChartObj              *GetChart(const long id);
   CChartObj              *GetChart(const int index)                       { return this.m_list.At(index);  }
//--- Возвращает список объектов-чартов по (1) символу, (2) таймфрейму
   CArrayObj              *GetChartsList(const string symbol)              { return this.GetList(CHART_PROP_SYMBOL,symbol,EQUAL);         }
   CArrayObj              *GetChartsList(const ENUM_TIMEFRAMES timeframe)  { return this.GetList(CHART_PROP_TIMEFRAME,timeframe,EQUAL);   }
//--- Возвращает идентификатор графика с программой
   long                    GetMainChartID(void)                      const { return CBaseObj::GetMainChartID();   }
//--- Создаёт список-коллекцию объектов-чартов
   bool                    CreateCollection(void);
//--- Обновляет (1) список-коллекцию объектов-чартов, (2) указанный объект-чарт
   void                    Refresh(void);
   void                    Refresh(const long chart_id);

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

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

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CChartObjCollection::CChartObjCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_CHARTS_ID);
   this.m_charts_total_prev=this.ChartsTotal();
  }
//+------------------------------------------------------------------+

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

//--- переменные для идентификаторов графиков
   long currChart,prevChart=ChartFirst();
   int i=0,limit=100;
   Print("ChartFirst = ",ChartSymbol(prevChart)," ID = ",prevChart);
   while(i<limit)// у нас наверняка не больше 100 открытых графиков
     {
      currChart=ChartNext(prevChart); // на основании предыдущего получим новый график
      if(currChart<0) break;          // достигли конца списка графиков
      Print(i,ChartSymbol(currChart)," ID =",currChart);
      prevChart=currChart;// запомним идентификатор текущего графика для ChartNext()
      i++;// не забудем увеличить счетчик
     }

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

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

//+------------------------------------------------------------------+
//| Создаёт список-коллекцию объектов-чартов                         |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateCollection(void)
  {
   //--- Очистим список и установим ему флаг сортировки по идентификатору графика
   m_list.Clear();
   m_list.Sort(SORT_BY_CHART_ID);
   //--- Объявим переменные и получим идентификатор первого графика
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- Создадим первый объект-чарт и добавим его в список
   if(!this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- В цикле по общему количеству графиков терминала (не более 100)
   while(i<CHARTS_MAX)
     { 
      //--- на основании предыдущего получим новый график
      curr_chart=::ChartNext(prev_chart);
      //--- Если достигли конца списка графиков - завершаем цикл
      if(curr_chart<0) break;
      //--- Создаём объект-чарт на основании идентификатора текущего графика в цикле и добавим его в список
      if(!this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- запомним идентификатор текущего графика для ChartNext() и увеличим счётчик цикла
      prev_chart=curr_chart;
      i++;
     }
   //--- Успешно заполнили список
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Обновляет список-коллекцию объектов-чартов                       |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(void)
  {
   //--- Получаем количество открытых графиков в терминале и
   int charts_total=this.ChartsTotal();
   //--- рассчитываем разницу между количеством открытых графиков в терминале
   //--- и объектов-чартов в списке-коллекции, эти значения выводим в комментарии на графике
   int change=charts_total-this.m_list.Total();
   Comment(DFUN,", list total=",DataTotal(),", charts total=",charts_total,", change=",change);
   //--- Если нет изменений - уходим
   if(change==0)
      return;
   //--- Если добавлен график в терминале
   if(change>0)
     {
      //--- Находим недостающий объект-чарт, создаём и добавляем его в список-коллекцию
      this.FindAndCreateMissingChartObj();
      //--- Получаем текущий график и возвращаемся к нему т. к.
      //--- добавление нового графика переключает фокус на него
      CChartObj *chart=this.GetChart(GetMainChartID());
      if(chart!=NULL)
         chart.SetBringToTopON(true);
     }
   //--- Если удалён график в терминале
   else if(change<0)
    {
     //--- Находим лишний объект-чарт в списке-коллекции и удаляем его из списка
     this.FindAndDeleteExcessChartObj();
    }
   //--- В цикле по количеству объектов-чартов в списке
   for(int i=0;i<this.m_list.Total();i++)
     {
      //--- получаем очередной объект-чарт и
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      //--- обновляем его
      chart.Refresh();
     }
  }
//+------------------------------------------------------------------+

Вся логика метода расписана в комментариях к коду. Вкратце: сначала проверяем изменение количества открытых графиков в клиентском терминале. Если график добавлен, то вызываем метод FindAndCreateMissingChartObj(), в котором ищется недостающий объект-чарт, создаётся и добавляется в список-коллекцию, после чего фокус переключается на текущий график с программой (так как добавление нового графика в терминал переключает фокус на него). Если график удалён из клиентского терминала, то вызываем метод, в котором ищется лишний объект-чарт и удаляется из списка. В завершение все объекты-чарты обновляются — метод Refresh() объекта-чарта проверяет изменение количества окон, присоединённых к чарту, и изменяет их количество в своём списке окон, если были найдены какие-либо изменения.

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

//+------------------------------------------------------------------+
//| Обновляет указанный объект-чарт                                  |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(const long chart_id)
  {
   CChartObj *chart=this.GetChart(chart_id);
   if(chart==NULL)
      return;
   chart.Refresh();
  }
//+------------------------------------------------------------------+

Здесь: получаем из списка-коллекции объект-чарт по идентификатору и обновляем его.

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

//+------------------------------------------------------------------+
//| Выводит в журнал полное описание коллекции                       |
//+------------------------------------------------------------------+
void CChartObjCollection::Print(void)
  {
   //--- Выводим в журнал заголовок и делаем полную распечатку всех объектов-чартов
   ::Print(CMessage::Text(MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      chart.Print();
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Выводит в журнал краткое описание коллекции                      |
//+------------------------------------------------------------------+
void CChartObjCollection::PrintShort(void)
  {
   //--- Выводим в журнал заголовок и делаем краткую распечатку всех объектов-чартов
   ::Print(CMessage::Text(MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      chart.PrintShort(true);
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает количество чартов в терминале                         |
//+------------------------------------------------------------------+
int CChartObjCollection::ChartsTotal(void) const
  {
   //--- Объявим переменные и получим идентификатор первого графика
   long currChart,prevChart=::ChartFirst(); 
   int res=1; // Один чарт точно есть - текущий
   //--- В цикле по общему количеству графиков терминала (не более 100)
   while(res<CHARTS_MAX)
     { 
      //--- на основании предыдущего получим новый график
      currChart=::ChartNext(prevChart);
      //--- Если достигли конца списка графиков - завершаем цикл
      if(currChart<0) break;
      prevChart=currChart;
      res++;
     }
   //--- Возвращаем полученный счётчик цикла
   return res;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый объект-чарт и добавляет его в список               |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateNewChartObj(const long chart_id,const string source)
  {
   ::ResetLastError();
   CChartObj *chart_obj=new CChartObj(chart_id);
   if(chart_obj==NULL)
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,true);
      return false;
     }
   this.m_list.Sort(SORT_BY_CHART_ID);
   if(!this.m_list.InsertSort(chart_obj))
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,true);
      delete chart_obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Здесь: в метод передаётся идентификатор графика (chart_id), объект-чарт которого необходимо создать, и наименование (source) метода, из которого вызван данный метод. Создаём новый объект-чарт, и если создать его не удалось — выводим об этом сообщение и возвращаем false. Если объект успешно создан, то устанавливаем списку-коллекции флаг сортированного по идентификаторам графиков списка и пытаемся добавить объект в сортированный список. Если добавить объект в список не удалось, то выводим об этом сообщение, удаляем вновь созданный объект и возвращаем false. Если всё прошло успешно — возвращаем true.

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

//+------------------------------------------------------------------+
//| Возвращает указатель на объект-чарт по идентификатору            |
//+------------------------------------------------------------------+
CChartObj *CChartObjCollection::GetChart(const long id)
  {
   CArrayObj *list=CSelect::ByChartProperty(GetList(),CHART_PROP_ID,id,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает флаг существования объекта-чарта                      |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChartObj(const long chart_id)
  {
   return(this.GetChart(chart_id)!=NULL);
  }
//+------------------------------------------------------------------+

Здесь: если объект с указанным идентификатором получить из списка-коллекции возможно (результат запроса не равен NULL), то возвращается true. Иначе — возвращается false.

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

//+------------------------------------------------------------------+
//| Возвращает флаг существования чарта                              |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChart(const long chart_id)
  {
   //--- Объявим переменные и получим идентификатор первого графика
   long curr_chart,prev_chart=::ChartFirst();
   //--- Если идентификаторы совпадают - возвращаем true
   if(prev_chart==chart_id)
      return true;
   int i=0; 
   //--- В цикле по общему количеству графиков терминала (не более 100)
   while(i<CHARTS_MAX)
     { 
      //--- на основании предыдущего получим новый график
      curr_chart=::ChartNext(prev_chart);
      //--- Если достигли конца списка графиков - завершаем цикл
      if(curr_chart<0) break;
      //--- Если идентификаторы совпадают - возвращаем true
      if(curr_chart==chart_id)
         return true;
      //--- запомним идентификатор текущего графика для ChartNext() и увеличим счётчик цикла
      prev_chart=curr_chart;
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Находит отсутствующий объект-чарт,                               |
//| создаёт его и добавляет в список-коллекцию                       |
//+------------------------------------------------------------------+
bool CChartObjCollection::FindAndCreateMissingChartObj(void)
  {
   //--- Объявим переменные и получим идентификатор первого графика
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- Если объекта первого графика нет в списке - пытаемся создать и добавить его в список
   if(!this.IsPresentChartObj(prev_chart) && !this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- В цикле по общему количеству графиков терминала (не более 100) ищем остальные графики
   while(i<CHARTS_MAX)
     { 
      //--- на основании предыдущего получим новый график
      curr_chart=::ChartNext(prev_chart);
      //--- Если достигли конца списка графиков - завершаем цикл
      if(curr_chart<0) break;
      //--- Если объекта нет в списке - пытаемся создать и добавить его в список
      if(!this.IsPresentChartObj(curr_chart) && !this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- запомним идентификатор текущего графика для ChartNext() и увеличим счётчик цикла
      prev_chart=curr_chart;
      i++;
     }
   return true;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|Находит и удаляет из списка объект-чарт, отсутствующий в терминале|
//+------------------------------------------------------------------+
void CChartObjCollection::FindAndDeleteExcessChartObj(void)
  {
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      if(!this.IsPresentChart(chart.ID()))
        {
         m_list.Delete(i);
        }
     }
  }
//+------------------------------------------------------------------+

Здесь: в обратном цикле по всем объектам-чартам в списке коллекции получаем очередной объект-чарт, и если графика с идентификатором как у объекта-чарта нет в клиентском терминале, то удаляем этот объект из списка-коллекции.

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

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

В файле \MQL5\Include\DoEasy\Engine.mqh класса CEngine подключим файл только что созданного класса-коллекции объектов-чартов:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "Collections\ChartObjCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Класс-основа библиотеки                                          |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Коллекция исторических ордеров и сделок
   CMarketCollection    m_market;                        // Коллекция рыночных ордеров и сделок
   CEventsCollection    m_events;                        // Коллекция событий
   CAccountsCollection  m_accounts;                      // Коллекция аккаунтов
   CSymbolsCollection   m_symbols;                       // Коллекция символов
   CTimeSeriesCollection m_time_series;                  // Коллекция таймсерий
   CBuffersCollection   m_buffers;                       // Коллекция индикаторных буферов
   CIndicatorsCollection m_indicators;                   // Коллекция индикаторов
   CTickSeriesCollection m_tick_series;                  // Коллекция тиковых серий
   CMBookSeriesCollection m_book_series;                 // Коллекция серий стаканов цен
   CMQLSignalsCollection m_signals_mql5;                 // Коллекция сигналов сервиса сигналов mql5.com
   CChartObjCollection  m_charts;                        // Коллекция чартов
   CResourceCollection  m_resource;                      // Список ресурсов
   CTradingControl      m_trading;                       // Объект управления торговлей
   CPause               m_pause;                         // Объект "Пауза"
   CArrayObj            m_list_counters;                 // Список счётчиков таймера

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

//--- Выводит в журнал (1) полное, (2) краткое описание коллекции, (3) параметры настроек копирования сигналов
   void                 SignalsMQL5Print(void)                                         { m_signals_mql5.Print();                             }
   void                 SignalsMQL5PrintShort(const bool list=false,const bool paid=true,const bool free=true)
                                                                                       { m_signals_mql5.PrintShort(list,paid,free);          }
   void                 SignalsMQL5CurrentSubscriptionParameters(void)                 { this.m_signals_mql5.CurrentSubscriptionParameters();}
   
//--- Создаёт коллекцию чартов
   bool                 ChartCreateCollection(void)                                    { return this.m_charts.CreateCollection();            }
//--- Возвращает (1) коллекцию чартов, (2) список чартов из коллекции чартов
   CChartObjCollection *GetChartObjCollection(void)                                    { return &this.m_charts;                              }
   CArrayObj           *GetListCharts(void)                                            { return this.m_charts.GetList();                     }

//--- Возвращает (1) указанный объект-чарт, (2) объект-чарт с программой
   CChartObj           *ChartGetChartObj(const long chart_id)                          { return this.m_charts.GetChart(chart_id);            }
   CChartObj           *ChartGetMainChart(void)                                        { return this.m_charts.GetChart(this.m_charts.GetMainChartID());}

//--- Обновляет (1) указанный по идентификатору чарт, (2) всех чарты
   void                 ChartRefresh(const long chart_id)                              { this.m_charts.Refresh(chart_id);                    }
   void                 ChartsRefreshAll(void)                                         { this.m_charts.Refresh();                            }

//--- Возвращает список объектов-чартов по (1) символу, (2) таймфрейму
   CArrayObj           *ChartGetChartsList(const string symbol)                        { return this.m_charts.GetChartsList(symbol);         }
   CArrayObj           *ChartGetChartsList(const ENUM_TIMEFRAMES timeframe)            { return this.m_charts.GetChartsList(timeframe);      }
   
//--- Возвращает (1) коллекцию буферов, (2) список буферов из коллекции 

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

В конструкторе класса добавим создание нового счётчика — для класса-коллекции объектов-чартов:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE);
   this.CreateCounter(COLLECTION_CHARTS_COUNTER_ID,COLLECTION_CHARTS_COUNTER_STEP,COLLECTION_CHARTS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

В таймере класса добавим блок кода для работы с коллекцией объектов-чартов:

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- Если это не тестер - работаем с событиями коллекций по таймеру
   if(!this.IsTester())
     {
   //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Таймер коллекции аккаунтов
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Таймер1 коллекции символов (обновление котировочных данных символов в коллекции)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- Если пауза завершилась - обновляем котировочные данные всех символов в коллекции
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Таймер2 коллекции символов (обновление всех данных всех символов в коллекции и отслеживание событий символов и списка символов в окне обзора рынка)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- Если пауза завершилась
         if(cnt4.IsTimeDone())
           {
            //--- обновляем данные и работаем с событиями всех символов в коллекции
            this.SymbolEventsControl();
            //--- Если работаем со списком из обзора рынка - проверяем события окна обзора рынка
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Таймер торгового класса
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком отложенных запросов
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Таймер коллекции таймсерий
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий (обновляем все кроме текущей)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Таймер коллекции таймсерий данных индикаторных буферов
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий индикаторных данных (обновляем все кроме текущей)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Таймер коллекции тиковых серий
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком тиковых серий (обновляем все кроме текущей)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
   //--- Таймер коллекции чартов
      index=this.CounterIndex(COLLECTION_CHARTS_COUNTER_ID);
      CTimerCounter* cnt9=this.m_list_counters.At(index);
      if(cnt9!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком чартов
         if(cnt9.IsTimeDone())
            this.ChartsRefreshAll();
        }
     }
//--- Если тестер - работаем с событиями коллекций по тику
   else
     {
      //--- работаем с событиями коллекций ордеров, сделок и позиций по тику
      this.TradeEventsControl();
      //--- работаем с событиями коллекции аккаунтов по тику
      this.AccountEventsControl();
      //--- обновляем котировочные данные всех символов в коллекции по тику
      this.m_symbols.RefreshRates();
      //--- работаем с событиями всех символов в коллекции по тику
      this.SymbolEventsControl();
      //--- работаем со списком отложенных запросов по тику
      this.m_trading.OnTimer();
      //--- работаем со списком таймсерий по тику
      this.SeriesRefresh(data_calculate);
      //--- работаем со списком таймсерий индикаторных буферов по тику
      this.IndicatorSeriesRefreshAll();
      //--- работаем со списком тиковых серий по тику
      this.TickSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

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

Протестируем работу созданного сегодня класса.


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

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

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

В первую очередь удалим подключение файла класса объекта-чарта из кода советника:

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

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

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

//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)

sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 

input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   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   ENUM_INPUT_YES_NO InpUseBook           =  INPUT_YES;                       // Use Depth of Market
sinput   ENUM_INPUT_YES_NO InpUseMqlSignals     =  INPUT_YES;                       // Use signal service
sinput   ENUM_INPUT_YES_NO InpUseCharts         =  INPUT_YES;                       // Use Charts control
sinput   ENUM_INPUT_YES_NO InpUseSounds         =  INPUT_YES;                       // Use sounds

//--- global variables

Из обработчика OnTick() советника удалим блок кода, в котором мы создавали список объектов-чартов и выводили их описания в журнал:

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

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

//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
   
//--- Если первый запуск
   static bool done=false;
   if(!done)
     {
      //--- Создадим объект-список для хранения объектов-чартов
      CArrayObj *list=new CArrayObj();
      if(list==NULL)
         return;
      //--- Объявим переменные и получим идентификатор первого графика
      long currChart,prevChart=ChartFirst(); 
      int i=0; 
      //--- Создадим объект-чарт и добавим его в список
      CChartObj *chart_first=new CChartObj(prevChart);
      list.Add(chart_first);
      //--- В цикле по общему количеству графиков терминала (не более 100)
      while(i<CHARTS_MAX)
        { 
         //--- на основании предыдущего получим новый график
         currChart=ChartNext(prevChart);
         //--- Если достигли конца списка графиков - завершаем цикл
         if(currChart<0) break;
         //--- Создаём объект-чарт на основании идентификатора текущего графика в цикле и добавим его в список
         CChartObj *chart=new CChartObj(currChart);
         list.Add(chart);
         //--- запомним идентификатор текущего графика для ChartNext() и увеличим счётчик цикла
         prevChart=currChart;
         i++;
        }
      Print("");
      //--- Из заполненного списка в цикле получим очередной объект-чарт и выведем его краткое описание
      int total=list.Total();
      for(int j=0;j<total;j++)
        {
         CChartObj *chart_obj=list.At(j);
         if(chart_obj!=NULL)
            chart_obj.PrintShort();
        }
      Print("");
      //--- Выведем полное описание текущего графика: в цикле по всем объектам созданного списка
      for(int j=0;j<total;j++)
        {
         //--- получим очередной объект-чарт и
         CChartObj *chart_obj=list.At(j);
         //--- если его символ совпадает с символом текущего графика - выведем в журнал его полное описание
         if(chart_obj!=NULL && chart_obj.Symbol()==Symbol())
            chart_obj.Print();
        }
      //--- Уничтожим список объектов-чартов
      delete list;
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Теперь всё то же самое у нас будет выполняться этими строчками кода:

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

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

//--- Если установлен флаг трейлинга
   if(trailing_on)
     {
      TrailingPositions();          // Трейлинг позиций
      TrailingOrders();             // Трейлинг отложенных ордеров
     }
   
//--- Если первый запуск
   static bool done=false;
   if(!done)
     {
      //--- Получим из коллекции чарт с советником и выведем его полное описание
      CChartObj *chart_main=engine.ChartGetMainChart();
      Print("");
      chart_main.Print();
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Здесь у нас выводится только полное описание чарта, на котором запущен советник. Краткие же описания всех объектов-чартов коллекции мы будем выводить в функции OnInitDoEasy() в этом блоке кода:

//--- Если работа с сигналами не разрешена или не удалось создать коллекцию сигналов - 
//--- устанавливаем запрет на копирование сделок по подписке
   else
      engine.SignalsMQL5CurrentSetSubscriptionEnableOFF();

//--- Создание коллекции чартов
//--- Если работа с чартами и коллекция чартов создана
   if(InpUseCharts && engine.ChartCreateCollection())
     {
      //--- Проверка созданных объектов-чартов - выводим в журнал короткое описание коллекции
      engine.GetChartObjCollection().PrintShort();
     }

//--- Создание тестовых файлов ресурсов

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

//+------------------------------------------------------------------+
//| OnBookEvent function                                             |
//+------------------------------------------------------------------+
void OnBookEvent(const string& symbol)
  {
   static bool first=true;
   //--- Если не удалось обновить серию снимков стакана символа - уходим
   if(!engine.OnBookEvent(symbol))
      return;
   //--- Работаем по текущему символу
   if(symbol==Symbol())
     {
      //--- Получаем серию снимков стакана цен текущего символа
      CMBookSeries *book_series=engine.GetMBookSeries(symbol);
      if(book_series==NULL)
         return;
      //--- Получаем последний объект-снимок стакана цен из объекта-серии снимков стакана цен
      CMBookSnapshot *book=book_series.GetLastMBook();
      if(book==NULL)
         return;
      //--- Получаем самый первый и самый последний объекты-заявки стакана цен из объекта-снимка стакана цен
      CMarketBookOrd *ord_0=book.GetMBookByListIndex(0);
      CMarketBookOrd *ord_N=book.GetMBookByListIndex(book.DataTotal()-1);
      if(ord_0==NULL || ord_N==NULL) return;
      //--- Выводим на график в комментарии время текущего снимка стакана цен,
      //--- максимальное количество показываемых заявок в стакане для символа,
      //--- полученное количество заявок в текущем снимке стакана цен,
      //--- общее количество снимков стакана цен, записанных в список-серию и
      //--- максимальную и минимальную по цене заявки текущего снимка стакана цен
      //Comment
      //  (
      //   DFUN,book.Symbol(),": ",TimeMSCtoString(book.Time()),
      //   //", symbol book size=",sym.TicksBookdepth(),
      //   ", last book data total: ",book.DataTotal(),
      //   ", series books total: ",book_series.DataTotal(),
      //   "\nMax: ",ord_N.Header(),"\nMin: ",ord_0.Header()
      //  );
      //--- Выведем в журнал первый снимок стакана цен
      if(first)
        {
         //--- описание серии
         book_series.Print();
         //--- описание снимка
         book.Print();
         first=false;
        }
     }
  }
//+------------------------------------------------------------------+

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

На чарт с советником в его главное окно добавим индикатор фракталов + добавим окно индикатора, например, DeMarker, в который разместим ещё один, например, AMA, рассчитываемый на данных DeMarker.
На втором графике расположим окно стохастика...

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

Chart collection:
- Main chart window EURUSD H4 ID: 131733844391938630, HWND: 920114, Subwindows: 1
- Main chart window GBPUSD H4 ID: 131733844391938633, HWND: 592798, Subwindows: No
- Main chart window AUDUSD H1 ID: 131733844391938634, HWND: 527424, Subwindows: 2
- Main chart window USDRUB H4 ID: 131733844391938635, HWND: 920468, Subwindows: No
Subscribed to Depth of Market EURUSD
Library initialization time: 00:00:05.922
 
============= The beginning of the parameter list (Main chart window EURUSD H4) =============
Chart ID: 131733844391938630
Timeframe: H4
Drawing attributes of a price chart: Yes
Object "Chart": No
Chart on top of other charts: No
Accessing the context menu by pressing the right mouse button: Yes
Accessing the "Crosshair tool" by pressing the middle mouse button: Yes
Scrolling the chart horizontally using the left mouse button: Yes
Sending messages about mouse wheel events to all mql5 programs on a chart: No
Send notifications of mouse move and mouse click events to all mql5 programs on a chart: No
Send a notification of an event of new object creation to all mql5-programs on a chart: No
Send a notification of an event of object deletion to all mql5-programs on a chart: No
Chart type: Display as Japanese candlesticks
Price chart in the foreground: No
Price chart indent from the right border: Yes
Automatic moving to the right border of the chart: Yes
Managing the chart using a keyboard: Yes
Allowed to intercept Space and Enter key presses on the chart to activate the quick navigation bar: Yes
Scale: 2
Fixed scale mode: No
Scale 1:1 mode: No
Scale to be specified in points per bar: No
Display a symbol ticker in the upper left corner: Yes
Display OHLC values in the upper left corner: Yes
Display Bid values as a horizontal line in a chart: Yes
Display Ask values as a horizontal line in a chart: Yes
Display Last values as a horizontal line in a chart: No
Display vertical separators between adjacent periods: No
Display grid in the chart: No
Display volume in the chart: Trade volumes
Display textual descriptions of objects: Yes
The number of bars on the chart that can be displayed: 137
The total number of chart windows, including indicator subwindows: 2
Chart window handle: 920114
Number of the first visible bar in the chart: 136
Chart width in bars: 168
Chart width in pixels: 670
 Main chart window:
 - Chart height in pixels: 293
 Chart subwindow 1:
 - The distance between the upper frame of the indicator subwindow and the upper frame of the main chart window: 295
 - Chart height in pixels: 21
Chart background color: clrWhite
Color of axes, scales and OHLC line: clrBlack
Grid color: clrSilver
Color of volumes and position opening levels: clrGreen
Color for the up bar, shadows and body borders of bull candlesticks: clrBlack
Color for the down bar, shadows and body borders of bear candlesticks: clrBlack
Line chart color and color of "Doji" Japanese candlesticks: clrBlack
Body color of a bull candlestick: clrWhite
Body color of a bear candlestick: clrBlack
Bid price level color: clrLightSkyBlue
Ask price level color: clrCoral
Line color of the last executed deal price (Last): clrSilver
Color of stop order levels (Stop Loss and Take Profit): clrOrangeRed
Displaying trade levels in the chart (levels of open positions, Stop Loss, Take Profit and pending orders): Yes
Permission to drag trading levels on a chart with a mouse: Yes
Showing the time scale on a chart: Yes
Showing the price scale on a chart: Yes
Showing the "One click trading" panel on a chart: No
Chart window is maximized: Yes
Chart window is minimized: No
The chart window is docked: Yes
The left coordinate of the undocked chart window relative to the virtual screen: 2028
The top coordinate of the undocked chart window relative to the virtual screen: 100
The right coordinate of the undocked chart window relative to the virtual screen: 2654
The bottom coordinate of the undocked chart window relative to the virtual screen: 329
------
The size of the zero bar indent from the right border in percents: 18.93
Chart fixed position from the left border in percent value: 0.00
Fixed  chart maximum: 1.21320
Fixed  chart minimum : 1.16950
Scale in points per bar: 1.00
Chart minimum: 1.16950
Chart maximum: 1.21320
------
Text of a comment in a chart: ""
The name of the Expert Advisor running on the chart: "TestDoEasyPart69"
The name of the script running on the chart: ""
Indicators in the main chart window:
- Indicator Fractals
Indicators in the chart window 1:
- Indicator DeM(14)
- Indicator AMA(14,2,30)
Symbol: "EURUSD"
============= End of the parameter list (Main chart window EURUSD H4) =============

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



Что дальше

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

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

К содержанию

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

Прочие классы в библиотеке DoEasy (Часть 67): Класс объекта-чарта
Прочие классы в библиотеке DoEasy (Часть 68): Класс объекта-окна графика и классы объектов-индикаторов в окне графика