English Русский 中文 Deutsch 日本語 Português
Otras clases en la biblioteca DoEasy (Parte 69): Clases de colección de objetos de gráfico

Otras clases en la biblioteca DoEasy (Parte 69): Clases de colección de objetos de gráfico

MetaTrader 5Ejemplos | 16 junio 2021, 17:07
479 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Bien, hemos preparado la funcionalidad necesaria para trabajar con objetos de gráfico, y también tenemos un objeto de gráfico que contiene las listas de objetos de ventana de gráfico, que, a su vez, contienen las listas de indicadores fijados a ellos. Es hora de combinar todo esto en una base común: una colección de objetos de gráfico desde la que podremos acceder cómodamente a cualquier gráfico abierto en el terminal para trabajar con él y sus propiedades. La colección nos permitirá clasificar, buscar y obtener listas de objetos de gráfico según cualquiera de sus propiedades, y resultará adecuada para trabajar con el gráfico seleccionado o simultáneamente con varios.


Mejorando las clases de la biblioteca

En primer lugar, vamos a añadir a la biblioteca los nuevos mensajes.

En el archivo \MQL5\Include\DoEasy\Data.mqh, escribimos los índices de los nuevos mensajes:

   MSG_CHART_OBJ_CHART_WINDOW,                        // Main chart window
   MSG_CHART_OBJ_CHART_SUBWINDOW,                     // Chart subwindow
   MSG_CHART_OBJ_CHART_SUBWINDOWS_NUM,                // Subwindows
   MSG_CHART_OBJ_INDICATORS_MW_NAME_LIST,             // Indicators in the main chart window
   MSG_CHART_OBJ_INDICATORS_SW_NAME_LIST,             // Indicators in the chart window
   MSG_CHART_OBJ_INDICATOR,                           // Indicator
   MSG_CHART_OBJ_INDICATORS_TOTAL,                    // Indicators
   MSG_CHART_OBJ_WINDOW_N,                            // Window
   MSG_CHART_OBJ_INDICATORS_NONE,                     // No indicators
  
//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Chart collection
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Failed to create a new chart object
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Failed to add a chart object to the collection
  
  };
//+------------------------------------------------------------------+

y los textos de los mensajes que se corresponden con los índices nuevamente añadidos:

   {"Главное окно графика","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"},
   
  };
//+---------------------------------------------------------------------+

Para nosotros, la colección de objetos de gráfico debe monitorear los cambios en el número de gráficos abiertos, así como los cambios en el número de ventanas abiertas en cada gráfico, para poder efectuar los cambios oportunos en nuestras listas de gráficos y las ventanas abiertas en ellos. Algunos de los cambios se pueden monitorear en el manejador OnChartEvent(), pero las pruebas han mostrado que, por lo general, en este manejador se indica que han sucedido ciertos cambios en el gráfico: los eventos de cambio del gráfico (CHARTEVENT_CHART_CHANGE), es decir, no podemos saber nada concreto (si necesitamos conocer el número de gráficos y ventanas). Por eso, vamos a trabajar en el temporizador del programa y a monitorear de forma independiente los cambios en el número de gráficos abiertos y la cantidad de ventanas en ellos. Podemos monitorear los demás cambios que pueden suceder en el gráfico con la ayuda del manejador anteriormente mencionado OnChartEvent(), o bien usando la herencia del objeto de gráfico y el objeto de ventana de gráfico respecto al objeto de la biblioteca CBaseObjExt, que a su vez es heredero del objeto básico de todos los objetos de la biblioteca CBaseObj, y que ofrece una funcionalidad de eventos adicional a sus objetos herederos. Esto en el caso de que más tarde necesitemos esta funcionalidad al trabajar con gráficos.

Como el trabajo con gráficos se realiza principalmente de forma semiautomática, bastará con verificar el número actual de gráficos y ventanas dos veces por segundo para determinar los cambios en el número de gráficos y la cantidad de ventanas abiertas en ellos, y comparar ambos con el número anterior. Si no hay cambios, no deberemos hacer nada. Cuando detectemos cambios en el número de ventanas y gráficos, actualizaremos los datos de nuestra colección.

Para los objetos de gráfico en el temporizador funcionen, necesitaremos un contador de temporizador adicional para la colección de objetos de gráfico. Para cada colección ejecutada en el temporizador, hemos creado un contador de temporizador propio que nos permitirá monitorear la frecuencia de actualización establecida para la colección. Además de los parámetros del contador, necesitaremos añadir el identificador de la nueva colección, puesto que cada colección de objetos tiene su propio identificador que sirve para determinar a qué colección pertenece una lista particular de objetos.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, en el apartado "Macrosustituciones", escribimos los parámetros del contador del temporizador de la colección de objetos de gráfico y el identificador de la lista de colección de objetos de gráfico:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                   ("Russian")                // Country language
#define END_TIME                       (D'31.12.3000 23:59:59')   // End date for account history data requests
#define TIMER_FREQUENCY                (16)                       // Minimal frequency of the library timer in milliseconds
#define TOTAL_TRADE_TRY                (5)                        // Default number of trading attempts
#define IND_COLORS_TOTAL               (64)                       // Maximum possible number of indicator buffer colors
#define IND_BUFFERS_MAX                (512)                      // Maximum possible number of indicator buffers
//--- Standard sounds
#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"
//--- Parameters of the orders and deals collection timer
#define COLLECTION_ORD_PAUSE           (250)                      // Orders and deals collection timer pause in milliseconds
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Increment of the orders and deals collection timer counter
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Orders and deals collection timer counter ID
//--- Parameters of the account collection timer
#define COLLECTION_ACC_PAUSE           (1000)                     // Account collection timer pause in milliseconds
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Account timer counter increment
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Account timer counter ID
//--- Symbol collection timer 1 parameters
#define COLLECTION_SYM_PAUSE1          (100)                      // Pause of the symbol collection timer 1 in milliseconds (for scanning market watch symbols)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Increment of the symbol timer 1 counter
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Symbol timer 1 counter ID
//--- Symbol collection timer 2 parameters
#define COLLECTION_SYM_PAUSE2          (300)                      // Pause of the symbol collection timer 2 in milliseconds (for events of the market watch symbol list)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Increment of the symbol timer 2 counter
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Symbol timer 2 counter ID
//--- Trading class timer parameters
#define COLLECTION_REQ_PAUSE           (300)                      // Trading class timer pause in milliseconds
#define COLLECTION_REQ_COUNTER_STEP    (16)                       // Trading class timer counter increment
#define COLLECTION_REQ_COUNTER_ID      (5)                        // Trading class timer counter ID
//--- Parameters of the timeseries collection timer
#define COLLECTION_TS_PAUSE            (64)                       // Timeseries collection timer pause in milliseconds
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Account timer counter increment
#define COLLECTION_TS_COUNTER_ID       (6)                        // Timeseries timer counter ID
//--- Parameters of the timer of indicator data timeseries collection
#define COLLECTION_IND_TS_PAUSE        (64)                       // Pause of the timer of indicator data timeseries collection in milliseconds
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Increment of indicator data timeseries timer counter
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // ID of indicator data timeseries timer counter
//--- Parameters of the tick series collection timer
#define COLLECTION_TICKS_PAUSE         (64)                       // Tick series collection timer pause in milliseconds
#define COLLECTION_TICKS_COUNTER_STEP  (16)                       // Tick series timer counter increment step
#define COLLECTION_TICKS_COUNTER_ID    (8)                        // Tick series timer counter ID
//--- Parameters of the chart collection timer
#define COLLECTION_CHARTS_PAUSE        (500)                      // Chart collection timer pause in milliseconds
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Chart timer counter increment
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Chart timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
#define COLLECTION_CHARTS_ID           (0x7786)                   // Chart collection list ID
//--- Data parameters for file operations

En la enumeración de propiedades de tipo entero del gráfico, eliminamos la constante CHART_PROP_WINDOW_IS_VISIBLE, dado que no hemos encontrado uso práctico a esta propiedad para el objeto. Por consiguiente, reducimos en 1 valor el número de propiedades de tipo entero (de 67 a 66):

#define CHART_PROP_INTEGER_TOTAL (66)                 // Total number of integer properties

Ajustamos el criterio de clasificación de los objetos de gráfico según sus propiedades, añadiendo las propiedades que hemos omitido antes para la clasificación de objetos:

//+------------------------------------------------------------------+
//| Possible chart sorting criteria                                  |
//+------------------------------------------------------------------+
#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 integer properties
   SORT_BY_CHART_ID = 0,                              // Sort by chart ID
   SORT_BY_CHART_TIMEFRAME,                           // Sort by chart timeframe
   SORT_BY_CHART_SHOW,                                // Sort by the price chart drawing attribute
   SORT_BY_CHART_IS_OBJECT,                           // Sort by chart object (OBJ_CHART) identification attribute

Y eliminamos de la lista de constantes de esta enumeración la constante SORT_BY_CHART_WINDOW_IS_VISIBLE, dado que no vamos a utilizar esta propiedad en el objeto.

Todos los objetos cuyas colecciones tenemos en la biblioteca poseen sus propias listas, que a su vez pueden ser clasificadas según diversas propiedades de los objetos. La lista de colección de objetos de gráfico también tendrá la posibilidad de realizar clasificaciones, para así buscar y seleccionar los objetos con los valores de propiedad requeridos. Para cada uno de estos objetos, crearemos nuestros propios métodos de clasificación, que estarán escritos en el archivo \MQL5\Include\DoEasy\Services\Select.mqh.

Vamos a añadir los nuevos métodos al archivo de clase CSelect para organizar la búsqueda y la clasificación de los objetos del gráfico.

Añadimos al archivo de clase CSelect el archivo de clase del objeto de gráfico:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+

Al final del listado del cuerpo de la clase, declaramos los nuevos métodos:

//+------------------------------------------------------------------+
//| Methods of working with chart data                               |
//+------------------------------------------------------------------+
   //--- Return the list of charts with one of (1) integer, (2) real and (3) string properties meeting a specified criterion
   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);
   //--- Return the chart index with the maximum value of the (1) integer, (2) real and (3) string properties
   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);
   //--- Return the chart index with the minimum value of the (1) integer, (2) real and (3) string properties
   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);
//---
  };
//+------------------------------------------------------------------+

Los implementamos fuera del cuerpo de la clase:

//+------------------------------------------------------------------+
//| Methods of working with chart data                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Return the list of charts with one integer                       |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the list of charts with one real                          |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the list of charts with one string                        |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum integer property value                          |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum real property value                             |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum string property value                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum integer property value                          |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum real property value                             |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum string property value                           |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Ya analizamos el funcionamiento de estos métodos en el tercer artículo de la serie, en el apartado "Organizando la búsqueda".

Al cambiar el número de ventanas adjuntas al gráfico, los índices de ventana también cambian, por lo que puede que necesitemos establecer un nuevo índice de ventana en algunos casos. En el archivo de clase del objeto de ventana del gráfico \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh, en la sección pública de la clase, escribimos el nuevo método para establecer el índice de la ventana:

//+------------------------------------------------------------------+
//| Chart window indicator object class                              |
//+------------------------------------------------------------------+
class CWndInd : public CObject
  {
private:
   long              m_chart_id;                         // Chart ID
   string            m_name;                             // Indicator short name
   int               m_index;                            // Window index on the chart
   int               m_handle;                           // Indicator handle
public:
//--- Return itself
   CWndInd          *GetObject(void)                     { return &this;         }
//--- Return (1) indicator name, (2) window index and (3) indicator handle
   string            Name(void)                    const { return this.m_name;   }
   int               Index(void)                   const { return this.m_index;  }
   int               Handle(void)                  const { return this.m_handle; }
//--- Set the index of the on-chart window
   void              SetIndex(const int index)           { this.m_index=index;   }
   
//--- Display the description of object properties in the journal (dash=true - hyphen before the description, false - description only)
   void              Print(const bool dash=false)        { ::Print((dash ? "- " : "")+this.Header());                      }
//--- Return the object short name
   string            Header(void)                  const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); }
   
//--- Compare CWndInd objects with each other by the specified property
   virtual int       Compare(const CObject *node,const int mode=0) const;
   
//--- Constructors
                     CWndInd(void);
                     CWndInd(const int handle,const string name,const int index) : m_handle(handle),m_name(name),m_index(index) {}
  };
//+------------------------------------------------------------------+

Asimismo, mejoramos la clase del objeto de gráfico ubicado en el archivo \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh.

Eliminamos la declaración del método SetVisible() de la lista de métodos privados para configurar las propiedades del objeto, dado que hemos decidido deshacernos de la propiedad establecida por este método, que no tiene ninguna carga útil:

//--- The methods of setting property values
   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);

Fuera del cuerpo de la clase, encontramos su implementación y también la eliminamos:

//+--------------------------------------------------------------------+
//|  Set the property                                                  |
//| "The total number of chart windows including indicator subwindows" |
//+--------------------------------------------------------------------+
void CChartObj::SetWindowsTotal(void)
  {
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));
  }
//+------------------------------------------------------------------+
//| Set the "Subwindow visibility" property                          |
//+------------------------------------------------------------------+
void CChartObj::SetVisible(void)
  {
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));
  }
//+----------------------------------------------------------------------+
//| Set the property "The number of the first visible bar on the chart"  |
//+----------------------------------------------------------------------+
void CChartObj::SetFirstVisibleBars(void)
  {
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));
  }
//+------------------------------------------------------------------+

El método simplemente establece la propiedad de visibilidad de la ventana principal del gráfico en la propiedad de tipo entero ya eliminada anteriormente. Por lo tanto, no lo usaremos: nos desharemos de todos los métodos asociados con él. En cualquier caso, aún podemos obtener el valor de esta propiedad de la ventana directamente del entorno y no de las propiedades del objeto de ventana del gráfico.

En la sección privada de la clase, declaramos el método necesario para crear una lista de ventanas adjuntas al gráfico:

//--- The methods of setting property values
   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);
   
//--- Create the list of chart windows
   void              CreateWindowsList(void);

public:

En la sección pública de la clase, declaramos el método para actualizar las propiedades del objeto de gráfico y sus subventanas:

//--- Update the chart object and its list of indicator windows
   void              Refresh(void);
   
//--- Constructors
                     CChartObj(){;}
                     CChartObj(const long chart_id);


Al organizar el trabajo con el método que retorna el número total de ventanas del gráfico, encontramos que después de realizar una solicitud de estos datos, siempre tenemos que configurarlos de inmediato en las propiedades del objeto. Por consiguiente, hemos decidido combinar en el método la solicitud de estos datos y su registro en la propiedad del objeto.

En el método WindowsTotal(), en primer lugar, añadimos la solicitud y establecemos el número de ventanas de gráficos en el método SetWindowsTotal(). A continuación, implementamos el retorno del valor recién obtenido y almacenado en las propiedades del objeto. Por último, eliminamos la implementación del método Visible():

//--- Return the number of bars on a chart that are available for display
   int               VisibleBars(void)                               const { return (int)this.GetProperty(CHART_PROP_VISIBLE_BARS);       }

//--- Return the total number of chart windows including indicator subwindows
   int               WindowsTotal(void) 
                       { 
                        this.SetWindowsTotal();
                        return (int)this.GetProperty(CHART_PROP_WINDOWS_TOTAL);
                       }

//--- Return the window visibility
   bool              Visible(void)                                   const { return (bool)this.GetProperty(CHART_PROP_WINDOW_IS_VISIBLE); }

Vamos a añadir otro método que retorna una bandera que indica que este objeto de gráfico es el principal, es decir, el objeto de gráfico al que se ha vinculado el programa creado sobre la base de la biblioteca:

//--- Emulate a tick (chart updates - similar to the terminal Refresh command)
   void              EmulateTick(void)                                     { ::ChartSetSymbolPeriod(this.ID(),this.Symbol(),this.Timeframe());}

//--- Return the flag indicating that the chart object belongs to the program chart
   bool              IsMainChart(void)                               const { return(this.m_chart_id==CBaseObj::GetMainChartID());            }
//--- Return the chart window specified by index
   CChartWnd        *GetWindowByIndex(const int index)               const { return this.m_list_wnd.At(index);                               }

El objeto básico de todos los objetos de la biblioteca CBaseObj contiene la variable m_chart_id_main, que almacena el identificador del gráfico en el que se ejecuta el programa. En su constructor, establecemos con esta variable el valor retornado por la función ChartID(). El valor del identificador del gráfico actual es retornado por el método GetMainChartID() de la clase CBaseObj, que devuelve el valor escrito en la variable m_chart_id_main. Por consiguiente, retornamos la bandera que indica que el identificador del gráfico actual coincide con el identificador de la ventana principal del programa. Si los identificadores coinciden, el método retornará true; de lo contrario, false.

Del constructor paramétrico, eliminamos la línea que establece el valor de visibilidad de la ventana del gráfico actual:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Set chart ID to the base object
   CBaseObj::SetChartID(chart_id);
//--- Set integer properties
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Chart ID
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Chart timeframe
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Price chart drawing attribute
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Chart object identification attribute
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Show chart above all others
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Access to the context menu using the right click
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Access the Crosshair tool by pressing the middle mouse button
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Scroll the chart horizontally using the left mouse button
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Send messages about mouse wheel events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Send messages about mouse button click and movement events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Send messages about the graphical object creation event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Send messages about the graphical object destruction event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Type of the chart (candlesticks, bars or line)
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Price chart in the foreground
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Mode of shift of the price chart from the right border
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // The mode of automatic shift to the right border of the chart
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Allow managing the chart using a keyboard
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Scale
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Fixed scale mode
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // 1:1 scale mode
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Mode for specifying the scale in points per bar
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Display a symbol ticker in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Display OHLC values in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Display Bid value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Display Ask value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Display Last value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Display vertical separators between adjacent periods
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Display the chart grid
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Display volumes on the chart
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Display text descriptions of the objects
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Number of bars on a chart that are available for display
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // The total number of chart windows including indicator subwindows
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));// Window visibility
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Chart window handle
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Number of the first visible bar on the chart
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Chart width in bars
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Chart width in pixels
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Chart height in pixels
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Chart background color
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Color of axes, scale and OHLC line
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Grid color
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Color of volumes and position opening levels
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Color for the up bar, shadows and body borders of bullish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Color for the down bar, shadows and body borders of bearish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Color of the chart line and the Doji candlesticks
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Color of the bullish candle body
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Color of the bearish candle body
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Bid price line color
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Ask price line color
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Color of the last performed deal's price line (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Color of stop order levels (Stop Loss and Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Enable the ability to drag trading levels on a chart using mouse
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Display the time scale on the chart
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Display the price scale on the chart
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Display the quick trading panel on the chart
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Chart window maximized
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Chart window minimized
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Chart window docked
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Left coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Upper coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Right coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Bottom coordinate of the undocked chart window relative to the virtual screen
//--- Set real properties
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Shift size of the zero bar from the right border in %
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Chart fixed position from the left border in %
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Fixed chart maximum
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Fixed chart minimum
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Scale in points per bar
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Chart minimum
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Chart maximum
//--- Set string properties
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Comment text on the chart
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // name of an EA launched on the chart
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // name of a script launched on the chart
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Chart symbol
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);

En lugar del ciclo en el que se crean los objetos de ventana que pertenecen a este gráfico

   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;
     }

añadimos la llamada del metodo en el que se crea la lista de ventanas que pertenecen a este gráfico:

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

Como resultado, el constructor paramétrico tendrá ahora este aspecto:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Set chart ID to the base object
   CBaseObj::SetChartID(chart_id);
//--- Set integer properties
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Chart ID
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Chart timeframe
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Price chart drawing attribute
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Chart object identification attribute
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Show chart above all others
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Access to the context menu using the right click
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Access the Crosshair tool by pressing the middle mouse button
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Scroll the chart horizontally using the left mouse button
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Send messages about mouse wheel events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Send messages about mouse button click and movement events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Send messages about the graphical object creation event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Send messages about the graphical object destruction event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Type of the chart (candlesticks, bars or line)
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Price chart in the foreground
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Mode of shift of the price chart from the right border
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // The mode of automatic shift to the right border of the chart
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Allow managing the chart using a keyboard
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Scale
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Fixed scale mode
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // 1:1 scale mode
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Mode for specifying the scale in points per bar
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Display a symbol ticker in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Display OHLC values in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Display Bid value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Display Ask value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Display Last value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Display vertical separators between adjacent periods
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Display the chart grid
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Display volumes on the chart
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Display text descriptions of the objects
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Number of bars on a chart that are available for display
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // The total number of chart windows including indicator subwindows
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Chart window handle
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Number of the first visible bar on the chart
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Chart width in bars
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Chart width in pixels
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Chart height in pixels
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Chart background color
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Color of axes, scale and OHLC line
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Grid color
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Color of volumes and position opening levels
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Color for the up bar, shadows and body borders of bullish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Color for the down bar, shadows and body borders of bearish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Color of the chart line and the Doji candlesticks
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Color of the bullish candle body
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Color of the bearish candle body
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Bid price line color
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Ask price line color
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Color of the last performed deal's price line (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Color of stop order levels (Stop Loss and Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Enable the ability to drag trading levels on a chart using mouse
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Display the time scale on the chart
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Display the price scale on the chart
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Display the quick trading panel on the chart
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Chart window maximized
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Chart window minimized
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Chart window docked
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Left coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Upper coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Right coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Bottom coordinate of the undocked chart window relative to the virtual screen
//--- Set real properties
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Shift size of the zero bar from the right border in %
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Chart fixed position from the left border in %
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Fixed chart maximum
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Fixed chart minimum
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Scale in points per bar
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Chart minimum
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Chart maximum
//--- Set string properties
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Comment text on the chart
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // name of an EA launched on the chart
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // name of a script launched on the chart
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Chart symbol
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Del método GetPropertyDescription(), que retorna la descripción de una propiedadad de tipo de entero de un objeto, eliminamos el bloque de código en el que se crea la descripción del parámetro de visibilidad de la ventana, pues ya hemos eliminado esta propiedad del objeto:

      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)
         )  :

En los métodos que muestran en el diario los datos de todos los indicadores de todas las ventanas del gráfico y las propiedades de todas las ventanas del gráfico, sustituimos el tamaño del ciclo por el número de objetos de ventana del gráfico en las listas:

//+-------------------------------------------------------------------+
//| Display data of all indicators of all chart windows in the journal|
//+-------------------------------------------------------------------+
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);
     }
  }
//+------------------------------------------------------------------+
//| Display the properties of all chart windows in the journal       |
//+------------------------------------------------------------------+
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);
     }
  }
//+------------------------------------------------------------------+

Anteriormente, los ciclos se contaban hasta el valor retornado por el método WindowsTotal(), lo cual no resultaba del todo cierto: este método retorna el valor real obtenido del gráfico, mientras que las listas pueden contener más o menos objetos de los que hay realmente. Esto sucede cuando su número cambia en el gráfico en el terminal. Por consiguiente, un ciclo por el número incorrecto de objetos en la lista se verá plagado de errores, que es lo que hemos corregido aquí.

Fuera del cuerpo de la clase, implementamos el método que actualiza un objeto de gráfico y la lista de sus ventanas:

//+------------------------------------------------------------------+
//| Update the chart object and its window list                      |
//+------------------------------------------------------------------+
void CChartObj::Refresh(void)
  {
   int change=this.WindowsTotal()-this.m_list_wnd.Total();
   if(change==0)
      return;
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Aquí, calculamos la diferencia entre el número real de ventanas del gráfico y el número de objetos de ventana en la lista. Si la diferencia es igual a cero, significará que no hay cambios: salimos del método. De lo contrario, creamos de nuevo la lista completa de todos los objetos de ventana del gráfico pertenecientes a este objeto de gráfico. Crear de nuevo la lista resulta bastante más simple que buscar la diferencia entre los objetos, o buscar los objetos de la ventana del gráfico que no están o faltan y eliminar los objetos innecesarios o añadir otros nuevos a la lista.

Vamos a implementar el método que crea la lista de objetos de ventana del gráfico que pertenecen al mismo:

//+------------------------------------------------------------------+
//| Create the list of chart windows                                 |
//+------------------------------------------------------------------+
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;
     }
  }
//+------------------------------------------------------------------+

Aquí, limpiamos la lista de objetos de ventana, obtenemos el número total de ventanas del gráfico de sus parámetros en el terminal y, en un ciclo por el número de ventanas obtenido, creamos un nuevo objeto de ventana y lo añadimos a la lista. Si no logramos añadir el objeto a la lista, lo eliminamos, para evitar así pérdidas de memoria.

Ya hemos terminado la mejora de las clases de la biblioteca.

Vamos a proceder a crear la clase de colección de objetos de gráfico.


Clase de colección de objetos de gráfico

De la misma manera que para el objeto gráfico, donde verificamos el cambio en el número de ventanas del gráfico para activar la actualización de su número en el objeto, en la colección de objetos de gráfico, comprobaremos el cambio en el número de gráficos abiertos para poder iniciar el proceso de reconstrucción de la colección de listas de objetos.

Primero, comprobamos el número de ventanas para los objetos de gráfico ya existentes; a continuación, verificaremos el cambio en el número de gráficos abiertos y, en caso de que el resultado de la verificación sea distinto a cero, comenzaremos a reconstruir la colección de objetos de gráfico.

En la carpeta de la biblioteca \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh, creamos el nuevo archivo de la clase CChartObjCollection. La clase debe ser herada de la clase del objeto básico de todos los objetos de la biblioteca CBaseObj, mientras que el archivo del objeto de gráfico debe ser incluido en el archivo de la clase de colección de objetos de gráfico:

//+------------------------------------------------------------------+
//|                                           ChartObjCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
  }

En la sección privada de la clase, declaramos la lista CListObj, que almacenará los objetos de gráfico, una variable para almacenar el valor pasado del número de gráficos abiertos en el terminal y los métodos auxiliares para organizar el trabajo de la clase:

//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // List of chart objects
   int                     m_charts_total_prev;                      // Previous number of charts in the terminal
   //--- Return the number of charts in the terminal
   int                     ChartsTotal(void) const;
   //--- Return the flag indicating the existence of (1) a chart object and (2) a chart
   bool                    IsPresentChartObj(const long chart_id);
   bool                    IsPresentChart(const long chart_id);
   //--- Create a new chart object and add it to the list
   bool                    CreateNewChartObj(const long chart_id,const string source);
   //--- Find the missing chart object, create it and add it to the collection list
   bool                    FindAndCreateMissingChartObj(void);
   //--- Find a chart object not present in the terminal and remove it from the list
   void                    FindAndDeleteExcessChartObj(void);
public:
  }

En la sección pública de la clase, ubicamos los métodos estándar para los objetos de la biblioteca:

public:
//--- Return (1) itself and (2) chart object collection list
   CChartObjCollection    *GetObject(void)                                 { return &this;                  }
   CArrayObj              *GetList(void)                                   { return &this.m_list;           }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   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);  }
//--- Return the number of chart objects in the list
   int                     DataTotal(void)                           const { return this.m_list.Total();    }
//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(void);
//--- Constructor
                           CChartObjCollection();

y añadimos los métodos auxiliares:

//--- Return the pointer to the chart object (1) by ID and (2) by an index in the list
   CChartObj              *GetChart(const long id);
   CChartObj              *GetChart(const int index)                       { return this.m_list.At(index);  }
//--- Return the list of chart objects by (1) symbol and (2) timeframe
   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);   }
//--- Return the chart ID with the program
   long                    GetMainChartID(void)                      const { return CBaseObj::GetMainChartID();   }
//--- Create the collection list of chart objects
   bool                    CreateCollection(void);
//--- Update (1) the chart object collection list and (2) the specified chart object
   void                    Refresh(void);
   void                    Refresh(const long chart_id);

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

Vamos a analizar la implementación de algunos métodos de la clase.

En el constructor de la clase, limpiamos la lista de objetos de la colección, establecemos para la lista la bandera de lista clasificada, asignamos a la lista el identificador de la colección de objetos de gráfico y guardamos el valor de la cantidad de gráficos actuales abiertos en el terminal:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CChartObjCollection::CChartObjCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_CHARTS_ID);
   this.m_charts_total_prev=this.ChartsTotal();
  }
//+------------------------------------------------------------------+

En MQL no existe una función que retorne el número de gráficos abiertos; tampoco existe la posibilidad de iterar en un ciclo por una matriz preparada de gráficos abiertos y obtener cada nuevo gráfico según el índice del ciclo. Pero existe una función que retorna el identificador del primer gráfico abierto, ChartFirst(), y una función que retorna el identificador del siguiente gráfico después del indicado, ChartNext(). Por cpnsiguiente, podemos crear un ciclo para todos los gráficos abiertos, uno por uno, obteniendo cada gráfico siguiente según el identificador del anterior. En la guía de ayuda para la función ChartNext(), tenemos un ejemplo sobre cómo organizar un ciclo de este tipo:

//--- variables for chart identifiers
   long currChart,prevChart=ChartFirst();
   int i=0,limit=100;
   Print("ChartFirst = ",ChartSymbol(prevChart)," ID = ",prevChart);
   while(i<limit)// we probably have no more than 100 open charts
     {
      currChart=ChartNext(prevChart); // get new chart on the basis of the previous one
      if(currChart<0) break;          // the end of chart list is reached
      Print(i,ChartSymbol(currChart)," ID =",currChart);
      prevChart=currChart;// memorize identifier of the current chart for ChartNext()
      i++;// do not forget to increase the counter
     }

Usando dicho ciclo como base, crearemos nuestros métodos para trabajar con los gráficos abiertos del terminal de cliente.

Método que crea una lista de colección de objetos de gráfico:

//+------------------------------------------------------------------+
//| Create the collection list of chart objects                      |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateCollection(void)
  {
   //--- Clear the list and set the flag of sorting by the chart ID
   m_list.Clear();
   m_list.Sort(SORT_BY_CHART_ID);
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- Create the first chart object and add it to the list
   if(!this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- Create the chart object based on the current chart ID in the loop and add it to the list
      if(!this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   //--- Filled in the list successfully
   return true;
  }
//+------------------------------------------------------------------+

La lógica completa del método se encuentra en los comentarios al código. En pocas palabras: primero eliminamos de la lista de colección los objetos añadidos previamente; luego, en el ciclo anterior, obtenemos cada gráfico subsiguiente abierto según su identificador, creamos un nuevo objeto de gráfico y lo añadimos a la lista de colección.

Método que actualiza la lista de colección de objetos de gráfico:

//+------------------------------------------------------------------+
//| Update the collection list of chart objects                      |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(void)
  {
   //--- Get the number of open charts in the terminal and
   int charts_total=this.ChartsTotal();
   //--- calculate the difference between the number of open charts in the terminal
   //--- and chart objects in the collection list. These values are displayed in the chart comment
   int change=charts_total-this.m_list.Total();
   Comment(DFUN,", list total=",DataTotal(),", charts total=",charts_total,", change=",change);
   //--- If there are no changes, leave
   if(change==0)
      return;
   //--- If a chart is added in the terminal
   if(change>0)
     {
      //--- Find the missing chart object, create and add it to the collection list
      this.FindAndCreateMissingChartObj();
      //--- Get the current chart and return to it since 
      //--- adding a new chart switches the focus to it
      CChartObj *chart=this.GetChart(GetMainChartID());
      if(chart!=NULL)
         chart.SetBringToTopON(true);
     }
   //--- If a chart is removed in the terminal
   else if(change<0)
    {
     //--- Find an extra chart object in the collection list and remove it from the list
     this.FindAndDeleteExcessChartObj();
    }
   //--- In the loop by the number of chart objects in the list,
   for(int i=0;i<this.m_list.Total();i++)
     {
      //--- get the next chart object and
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      //--- update it
      chart.Refresh();
     }
  }
//+------------------------------------------------------------------+

La lógica completa del método se encuentra en los comentarios al código. Resumiendo: primero comprobamos el cambio en el número de gráficos abiertos en el terminal de cliente. Si el gráfico ha sido añadido, llamamos al método FindAndCreateMissingChartObj(), en el que se busca el objeto de gráfico que falta; a continuación, este se crea y añade a la lista de la colección, después de lo cual, el foco cambia al gráfico actual con el programa (dado que la adición de un nuevo gráfico al terminal cambia el foco a este). Si el gráfico ha sido eliminado del terminal de cliente, llamamos al método en el que se busca y se elimina de la lista un objeto de gráfico adicional. Finalmente, todos los objetos del gráfico son actualizados: el método Refresh() del objeto de gráfico comprueba el cambio en el número de ventanas adjuntas al gráfico y, si se encuentran cambios, modifica la cantidad de estas en su lista de ventanas.

Método que actualiza el objeto de gráfico especificado según el identificador del gráfico:

//+------------------------------------------------------------------+
//| Update the specified chart object                                |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(const long chart_id)
  {
   CChartObj *chart=this.GetChart(chart_id);
   if(chart==NULL)
      return;
   chart.Refresh();
  }
//+------------------------------------------------------------------+

Aquí, obtenemos de la lista de colección el objeto de gráfico según el identificador y lo actualizamos.

Método que muestra en el diario la descripción completa de la colección:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CChartObjCollection::Print(void)
  {
   //--- Display the header in the journal and print all chart objects in full
   ::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();
     }
  }
//+------------------------------------------------------------------+

Aquí, imprimimos el encabezado en primer lugar; después, en un ciclo por el número total de objetos en la lista de colección, obtenemos el siguiente objeto de gráfico, y finalmente mostramos su descripción completa.

Método que muestra en el diario la descripción breve de la colección:

//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CChartObjCollection::PrintShort(void)
  {
   //--- Display the header in the journal and print short descriptions of chart objects
   ::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);
     }
  }
//+------------------------------------------------------------------+

La lógica del método es idéntica a la anteriormente analizada, salvo que en el diario se muestran las descripciones breves de los objetos de gráfico.

Método que retorna el número de gráficos en el terminal:

//+------------------------------------------------------------------+
//| Return the number of charts in the terminal                      |
//+------------------------------------------------------------------+
int CChartObjCollection::ChartsTotal(void) const
  {
   //--- Declare the variables and get the first chart ID
   long currChart,prevChart=::ChartFirst(); 
   int res=1; // We definitely have one chart - the current one
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(res<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      currChart=::ChartNext(prevChart);
      //--- When reaching the end of the chart list, complete the loop
      if(currChart<0) break;
      prevChart=currChart;
      res++;
     }
   //--- Return the obtained loop counter
   return res;
  }
//+------------------------------------------------------------------+

La lógica del método se describe en los comentarios al código. En resumen: seguro que tenemos un gráfico abierto, nuestro programa se está ejecutando en él. Por ello, el contador de ciclos comienza desde uno. Más adelante en el ciclo, obtenemos cada gráfico posterior basado en el anterior, mientras aumentamos el contador. Como resultado, una vez finalizado el ciclo, retornamos el valor del contador obtenido en el ciclo.

Método que crea un nuevo objeto de gráfico y lo añade a la lista de colección:

//+------------------------------------------------------------------+
//| Create a new chart object and add it to the list                 |
//+------------------------------------------------------------------+
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;
  }
//+------------------------------------------------------------------+

Aquí, transmitimos al método el identificador del gráfico (chart_id), el objeto de gráfico a partir del cual debe crearse, y el nombre (source) del método desde el cual llamamos a este método. Creamos un nuevo objeto de gráfico, y si no hemos podido crearlo, mostraremos un mensaje al respecto y retornaremos false. Si el objeto se ha creado con éxito, establecemos el indicador de lista clasificada según los identificadores de los gráficos de la lista de colección e intentamos añadir el objeto a la lista clasificada. Si no ha sido posible añadir un objeto a la lista, mostramos un mensaje sobre ello, borramos el objeto recién creado y retornamos false. Si todo ha salido bien, retornamos true.

Método que retorna el puntero a un objeto de gráfico usando el identificador del gráfico:

//+------------------------------------------------------------------+
//| Return the pointer to the chart object by ID                     |
//+------------------------------------------------------------------+
CChartObj *CChartObjCollection::GetChart(const long id)
  {
   CArrayObj *list=CSelect::ByChartProperty(GetList(),CHART_PROP_ID,id,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Aquí, obtenemos una lista con los objetos de gráfico cuya propiedad "identificador del gráfico" es igual al valor transmitido al método (solo puede haber un objeto de gráfico en la lista, ya que los identificadores de gráfico son únicos). Si el objeto se ha recibido en una nueva lista de un elemento, lo retornamos. En todos los demás casos, retornaremos NULL: ​​el objeto no ha sido recibido.

Método que retorna la bandera sobre la existencia de un objeto de gráfico:

//+------------------------------------------------------------------+
//| Return the flag indicating the existence of a chart object       |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChartObj(const long chart_id)
  {
   return(this.GetChart(chart_id)!=NULL);
  }
//+------------------------------------------------------------------+

Aquí, si podemos obtener el objeto con el identificador indicado de la lista de colección (el resultado de la solicitud no es igual a NULL), retornamos true. De lo contrario, retornaremos false.

Método que retorna la bandera de presencia de un gráfico en el terminal de cliente:

//+------------------------------------------------------------------+
//| Return the flag indicating the existence of a chart              |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChart(const long chart_id)
  {
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst();
   //--- If the IDs match, return 'true'
   if(prev_chart==chart_id)
      return true;
   int i=0; 
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- If the IDs match, return 'true'
      if(curr_chart==chart_id)
         return true;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo a través de los gráficos del terminal obtenidos según el identificador del gráfico anterior, comprobamos si el identificador del gráfico actualmente seleccionado en el ciclo coincide con el valor transmitido al método. Si los valores coinciden, significa que hay un gráfico con este identificador, por lo que retronamos true.
Al finalizar el ciclo, retornamos
false: no se ha encontrado el gráfico con el identificador solicitado.

Método que busca el objeto de gráfico que falta, lo crea y lo añade a la lista de colección:

//+------------------------------------------------------------------+
//| Find a missing chart object,                                     |
//| create it and add to the collection list                         |
//+------------------------------------------------------------------+
bool CChartObjCollection::FindAndCreateMissingChartObj(void)
  {
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- If the first chart object is not in the list, attempt to create and add it to the list
   if(!this.IsPresentChartObj(prev_chart) && !this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- In the loop by the total number of terminal charts (not more than 100), look for the rest of the charts
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- If the object is not in the list, attempt to create and add it to the list
      if(!this.IsPresentChartObj(curr_chart) && !this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   return true;
  }
//+------------------------------------------------------------------+

Aquí también utilizamos un ciclo por los gráficos usando como base el identificador del gráfico anterior. Primero, determinamos la presencia/ausencia del objeto gráfico con el programa en la lista de colección, y luego, utilizando todos los demás gráficos en el ciclo, buscamos también los objetos faltantes que existan en el terminal de gráficos y los añadimos a la lista de colecciones.

Método que busca y elimina de la lista un objeto de gráfico ausente en el terminal, pero presente en la lista de colecciones:

//+-----------------------------------------------------------------------------+
//|Find a chart object not present in the terminal and remove it from the list  |
//+-----------------------------------------------------------------------------+
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);
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo inverso por todos los objetos de gráfico en la lista de colección, obtenemos el siguiente objeto de gráfico y, si en el terminal de cliente no hay ningún gráfico con el identificador como el del objeto de gráfico, eliminamos este objeto de la lista de colección.

Con esto, damos por concluida la creación de la primera versión de la colección de clases de objetos de gráfico.

Ahora necesitamos incluir el archivo de la colección de clases de objetos de gráfico creada en la clase principal de la biblioteca CEngine, así como crear en ella los métodos necesarios para acceder a los métodos de la colección de clases de objetos de gráfico, para que podamos trabajar con ellos desde nuestros programas.

En el archivo \MQL5\Include\DoEasy\Engine.mqh de la clase CEngine, incluimos el archivo de la clase de colección de objetos de gráfico recién creada:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+

En la sección privada de la clase, declaramos el objeto de clase de colección de gráficos:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_time_series;                  // Timeseries collection
   CBuffersCollection   m_buffers;                       // Collection of indicator buffers
   CIndicatorsCollection m_indicators;                   // Indicator collection
   CTickSeriesCollection m_tick_series;                  // Collection of tick series
   CMBookSeriesCollection m_book_series;                 // Collection of DOM series
   CMQLSignalsCollection m_signals_mql5;                 // Collection of MQL5.com Signals service signals
   CChartObjCollection  m_charts;                        // Chart collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CPause               m_pause;                         // Pause object
   CArrayObj            m_list_counters;                 // List of timer counters

En la sección pública de la clase, ubicamos los métodos para acceder a la clase de colección de objetos de gráfico:

//--- Display (1) the complete, (2) short collection description in the journal and (3) parameters of the signal copying settings
   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();}
   
//--- Current the chart collection
   bool                 ChartCreateCollection(void)                                    { return this.m_charts.CreateCollection();            }
//--- Return (1) the chart collection and (2) the list of charts from the chart collection
   CChartObjCollection *GetChartObjCollection(void)                                    { return &this.m_charts;                              }
   CArrayObj           *GetListCharts(void)                                            { return this.m_charts.GetList();                     }

//--- Return (1) the specified chart object and (2) the chart object with the program
   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());}

//--- Update (1) the chart specified by ID and (2) all charts
   void                 ChartRefresh(const long chart_id)                              { this.m_charts.Refresh(chart_id);                    }
   void                 ChartsRefreshAll(void)                                         { this.m_charts.Refresh();                            }

//--- Return the list of chart objects by (1) symbol and (2) timeframe
   CArrayObj           *ChartGetChartsList(const string symbol)                        { return this.m_charts.GetChartsList(symbol);         }
   CArrayObj           *ChartGetChartsList(const ENUM_TIMEFRAMES timeframe)            { return this.m_charts.GetChartsList(timeframe);      }
   
//--- Return (1) the buffer collection and (2) the buffer list from the collection 

Todos los métodos que acabamos de añadir simplemente retornan el resultado de la llamada a los métodos correspondientes de la colección de objetos de gráfico que acabamos de analizar antes. Todos estos métodos estarán visibles y disponibles para su uso en nuestros programas basados ​​en la biblioteca.

En el constructor de la clase, añadimos la creación de un nuevo contador para la colección de clases de objetos de gráfico:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
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 
   //---
  }
//+------------------------------------------------------------------+

En el temporizador de la clase, añadimos el bloque de código para trabajar con la colección de objetos de gráfico:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- If this is not a tester, work with collection events by timer
   if(!this.IsTester())
     {
   //--- Timer of the collections of historical orders and deals, as well as of market orders and positions
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- If unpaused, work with the order, deal and position collections events
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Account collection timer
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- If unpaused, work with the account collection events
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Timer 1 of the symbol collection (updating symbol quote data in the collection)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- If the pause is over, update quote data of all symbols in the collection
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- If the pause is over
         if(cnt4.IsTimeDone())
           {
            //--- update data and work with events of all symbols in the collection
            this.SymbolEventsControl();
            //--- When working with the market watch list, check the market watch window events
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Trading class timer
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- If unpaused, work with the list of pending requests
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Timeseries collection timer
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- If the pause is over, work with the timeseries list (update all except the current one)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Timer of timeseries collection of indicator buffer data
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Tick series collection timer
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- If the pause is over, work with the tick series list (update all except the current one)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
   //--- Chart collection timer
      index=this.CounterIndex(COLLECTION_CHARTS_COUNTER_ID);
      CTimerCounter* cnt9=this.m_list_counters.At(index);
      if(cnt9!=NULL)
        {
         //--- If unpaused, work with the chart list
         if(cnt9.IsTimeDone())
            this.ChartsRefreshAll();
        }
     }
//--- If this is a tester, work with collection events by tick
   else
     {
      //--- work with events of collections of orders, deals and positions by tick
      this.TradeEventsControl();
      //--- work with events of collections of accounts by tick
      this.AccountEventsControl();
      //--- update quote data of all collection symbols by tick
      this.m_symbols.RefreshRates();
      //--- work with events of all symbols in the collection by tick
      this.SymbolEventsControl();
      //--- work with the list of pending orders by tick
      this.m_trading.OnTimer();
      //--- work with the timeseries list by tick
      this.SeriesRefresh(data_calculate);
      //--- work with the timeseries list of indicator buffers by tick
      this.IndicatorSeriesRefreshAll();
      //--- work with the list of tick series by tick
      this.TickSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

En cuanto el contador del temporizador cuente medio segundo, se llamará al método de actualización de todos los objetos de gráfico en sus colecciones. De esta forma, habremos automatizado el seguimiento de los cambios en el número de gráficos en el terminal y de las ventanas fijadas a dicho gráfico abierto.

Vamos a poner a prueba el funcionamiento de la clase que hemos creado hoy.


Simulación

Para la simulación, vamos a tomar el asesor del artículo anterior y a guardarlo en la nueva carpeta \MQL5\Experts\TestDoEasy\Part69\ con el nuevo nombre TestDoEasyPart69.mq5.

En esencia, no hemos cambiado mucho la lógica del asesor anterior: mostraremos de la misma forma las descripciones breves de todos los objetos de gráfico cuyos gráficos estén abiertos en el terminal, y mostraremos su descripción completa para el gráfico principal. Además, en la colección de clases de objetos de gráfico, en el método de actualización de la lista de colección, hemos añadido la muestra de un comentario al gráfico que nos indica cuántos objetos de gráfico tenemos en la colección, cuántos gráficos correspondientes a ellos están abiertos en el terminal, y cuál es la diferencia al comparar su número. Eliminaremos este comentario de depuración más tarde. Entre tanto, hoy vamos a comporbar la exactitud del funcionamiento de la clase de colección de objetos de gráfico que lo usa.

En primer lugar, eliminamos del código del asesor la inclusión del archivo de clase de objeto de gráfico:

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

Ahora tenemos una colección de clases de objetos de gráfico conectada al objeto principal de la biblioteca y todas las clases de gráficos y sus colecciones están disponibles en él.

En el bloque de entrada, añadimos la variable que especifica la bandera que indica la necesidad de usar la clase de colección de objetos de gráfico en el programa:

//--- 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

Asimismo, eliminamos del manejador OnTick() el bloque de código en el que hemos creado la lista de objetos de gráfico y mostrado sus descripciones en el diario:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Handle the NewTick event in the library
   engine.OnTick(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      engine.EventsHandling();      // Working with events
     }

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
   
//--- If it is the first launch
   static bool done=false;
   if(!done)
     {
      //--- Create the list object for storing chart objects
      CArrayObj *list=new CArrayObj();
      if(list==NULL)
         return;
      //--- Declare the variables and get the first chart ID
      long currChart,prevChart=ChartFirst(); 
      int i=0; 
      //--- Create the chart object and add it to the list
      CChartObj *chart_first=new CChartObj(prevChart);
      list.Add(chart_first);
      //--- In the loop by the total number of terminal charts (not more than 100)
      while(i<CHARTS_MAX)
        { 
         //--- based on the previous one, get the new chart
         currChart=ChartNext(prevChart);
         //--- When reaching the end of the chart list, complete the loop
         if(currChart<0) break;
         //--- Create the chart object based on the current chart ID in the loop and add it to the list
         CChartObj *chart=new CChartObj(currChart);
         list.Add(chart);
         //--- remember the current chart ID for ChartNext() and increase the loop counter
         prevChart=currChart;
         i++;
        }
      Print("");
      //--- From the filled list in the loop, receive the next chart object and display its short description
      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("");
      //--- Display the full description of the current chart: in the loop by all objects of the created list
      for(int j=0;j<total;j++)
        {
         //--- get the next chart object and
         CChartObj *chart_obj=list.At(j);
         //--- if its symbol matches the current chart symbol, display its full description in the journal
         if(chart_obj!=NULL && chart_obj.Symbol()==Symbol())
            chart_obj.Print();
        }
      //--- Destroy the list of chart objects
      delete list;
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Ahora, todo lo mismo se ejecutará con estas líneas de código:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Handle the NewTick event in the library
   engine.OnTick(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      engine.EventsHandling();      // Working with events
     }

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
   
//--- If it is the first launch
   static bool done=false;
   if(!done)
     {
      //--- Get the chart with the EA from the collection and display its full description
      CChartObj *chart_main=engine.ChartGetMainChart();
      Print("");
      chart_main.Print();
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Aquí se muestra solo la descripción completa del gráfico en el que ha sido iniciado el asesor. Las descripciones breves de todos los objetos de gráfico se mostrarán en la función OnInitDoEasy(), en este bloque de código:

//--- If working with signals is not enabled or failed to create the signal collection, 
//--- disable copying deals by subscription
   else
      engine.SignalsMQL5CurrentSetSubscriptionEnableOFF();

//--- Create the chart collection
//--- If working with charts and the chart collection is created
   if(InpUseCharts && engine.ChartCreateCollection())
     {
      //--- Check created chart objects - display the short collection description in the journal
      engine.GetChartObjCollection().PrintShort();
     }

//--- Create resource text files

En el manejador OnBookEvent() del asesor, comentamos la muestra de la información de depuración en el gráfico de la clase de colección de profundidades de mercado, ya que ahora no necesitamos ver esta información (mientras está presente debido al comportamiento a veces extraño del terminal cuando se trabaja con la profundidad de mercado; el asesor a veces no responde durante bastante tiempo, y esta información, que con frecuencia cambia sus valores, nos permite ver visualmente el trabajo del asesor), y el resultado de este comentario evitará que veamos los cambios registrados por la clase de colección de objetos de gráfico.

//+------------------------------------------------------------------+
//| OnBookEvent function                                             |
//+------------------------------------------------------------------+
void OnBookEvent(const string& symbol)
  {
   static bool first=true;
   //--- Leave if failed to update the symbol snapshot series
   if(!engine.OnBookEvent(symbol))
      return;
   //--- Work by the current symbol
   if(symbol==Symbol())
     {
      //--- Get the DOM snapshot series of the current symbol
      CMBookSeries *book_series=engine.GetMBookSeries(symbol);
      if(book_series==NULL)
         return;
      //--- Get the last DOM snapshot object from the DOM snapshot series object
      CMBookSnapshot *book=book_series.GetLastMBook();
      if(book==NULL)
         return;
      //--- Get the very first and last DOM order objects from the DOM snapshot object
      CMarketBookOrd *ord_0=book.GetMBookByListIndex(0);
      CMarketBookOrd *ord_N=book.GetMBookByListIndex(book.DataTotal()-1);
      if(ord_0==NULL || ord_N==NULL) return;
      //--- Display the time of the current DOM snapshot in the chart comment,
      //--- the maximum number of displayed orders in DOM for a symbol,
      //--- the obtained number of orders in the current DOM snapshot,
      //--- the total number of DOM snapshots set in the series list and
      //--- the highest and lowest orders of the current DOM snapshot
      //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()
      //  );
      //--- Display the first DOM snapshot in the journal
      if(first)
        {
         //--- series description
         book_series.Print();
         //--- snapshot description
         book.Print();
         first=false;
        }
     }
  }
//+------------------------------------------------------------------+

Vamos a compilar el asesor. Abrimos cuatro gráficos al azar en el terminal (en el primero, iniciaremos el asesor) y creamos el mismo entorno que construimos durante las pruebas en el artículo anterior:

En la ventana principal del gráfico con el asesor, añadimos un indicador de fractales + la ventana de un indicador, por ejemplo, DeMarker, en el que ubicaremos otro más, por ejemplo, AMA, calculado con los datos de DeMarker.
En el segundo gráfico, colocamos la ventana del estocástico...

Después de iniciar el asesor, en el diario se mostrarán los datos sobre los objetos de la colección de objetos de gráfico y sobre el gráfico con el programa:

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) =============

A continuación, primero añadimos varios gráficos, y luego eliminamos los añadidos. El comentario sobre el gráfico mostrará los cambios en curso en el número de objetos de gráfico en la colección, el número de gráficos abiertos en el terminal y el valor de la diferencia al comparar el número de gráficos y objetos:



¿Qué es lo próximo?

En el próximo artículo, continuaremos trabajando con la colección de objetos de gráfico para ampliar su funcionalidad.

Más abajo se adjuntan todos los archivos de la versión actual de la biblioteca y el archivo del asesor de prueba para MQL5. Puede descargarlo todo y ponerlo a prueba por sí mismo.
Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

Otras clases en la biblioteca DoEasy (Parte 67): Clase de objeto de gráfico
Otras clases en la biblioteca DoEasy (Parte 68): Clase de objeto de ventana de gráfico y clases de objetos de indicador en la ventana del gráfico

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/9260

Archivos adjuntos |
MQL5.zip (3950.16 KB)
Consejos de un programador profesional (parte I): guardado, depuración y compilación de códigos. Trabajando con proyectos y logs Consejos de un programador profesional (parte I): guardado, depuración y compilación de códigos. Trabajando con proyectos y logs
Consejos de un programador profesional sobre métodos, técnicas y herramientas auxiliares para facilitar la programación.
Scalping combinatorio: analizando transacciones del pasado para aumentar el rendimiento de las transacciones futuras Scalping combinatorio: analizando transacciones del pasado para aumentar el rendimiento de las transacciones futuras
Ofrecemos al lector la descripción de una tecnología para aumentar la eficacia de cualquier sistema de comercio automático. El artículo expone brevemente la idea, los fundamentos básicos, las posibilidades y las desventajas del método.
Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas Swaps (parte I) : Bloqueo de posiciones y posiciones sintéticas
En este artículo intentaremos expandir el concepto clásico de los métodos de swap en el comercio, y también hablaremos sobre por qué hemos llegado a la conclusión de que este concepto merece una atención especial y es absolutamente recomendable para el análisis y el estudio.
Otras clases en la biblioteca DoEasy (Parte 68): Clase de objeto de ventana de gráfico y clases de objetos de indicador en la ventana del gráfico Otras clases en la biblioteca DoEasy (Parte 68): Clase de objeto de ventana de gráfico y clases de objetos de indicador en la ventana del gráfico
En este artículo, seguiremos desarrollando la clase de objeto de gráfico. Para ello, le añadiremos una lista de objetos de ventana de gráfico, en la que, a su vez, estarán disponibles las listas de indicadores colocados en ellos.