English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 87): Colección de objetos gráficos - control de la modificación de propiedades en todos los gráficos abiertos

Gráficos en la biblioteca DoEasy (Parte 87): Colección de objetos gráficos - control de la modificación de propiedades en todos los gráficos abiertos

MetaTrader 5Ejemplos | 6 diciembre 2021, 10:26
424 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Ya podemos controlar el cambio en las propiedades de los objetos gráficos estándar construidos en la ventana del gráfico en la que se ejecuta el programa creado sobre la base de esta biblioteca. Para monitorear algunos eventos, en el último artículo, decidimos usar un modelo de eventos para procesar los eventos de los objetos gráficos en el manejador OnChartEvent(). Esto simplificó enormemente el código (aunque ahora tenemos la gestión de eventos ubicada en dos bloques diferentes del código de la biblioteca) y resolvió el problema del rellenado incompleto de las propiedades del objeto de clase justo al crear un objeto gráfico en el gráfico. Ya discutimos esto en en el último artículo.

Todo parece ir bien, pero ahora tenemos un gran "pero": no podemos obtener directamente los eventos de los objetos gráficos de otros gráficos; todos los eventos ocurridos en un gráfico llegan al manejador OnChartEvent() del programa que funciona en este gráfico en particular. Y esto significa que, para determinar qué evento ha sucedido en un gráfico que no tiene nuestro programa, necesitaremos enviar de alguna forma un evento desde este gráfico al gráfico donde se está ejecutando nuestro programa.

Podemos enviar nuestro evento: se trata de un evento personalizado enviado al gráfico de nuestro programa usando la función EventChartCustom().
Esta función puede generar un evento personalizado para el gráfico indicado en él. En este caso, además, al enviar el identificador del evento al gráfico indicado, la función añadirá automáticamente a su valor la magnitud de la constante CHARTEVENT_CUSTOM. Tras recibir un evento de este tipo en la biblioteca, solo tendremos que restarle este valor añadido, y así averiguaremos qué tipo de evento ha sucedido en algún otro gráfico. Para entender en qué gráfico ha sucedido el evento, podemos indicar su identificador (gráfico) en el parámetro long del evento lparam. A continuación, tras ver que el parámetro lparam tiene un valor (por defecto, para los eventos de objetos gráficos lparam y dparam no tienen valores, son iguales a cero), ya entendemos que este evento ha venido de otro gráfico, así que restamos del parámetro id (también transmitido en el evento) el valor CHARTEVENT_CUSTOM, y obtenemos el ID del evento. Pero solo sabremos con qué objeto ha sucedido este evento a partir del parámetro sparam: en este se transmite el nombre del objeto.

Por consiguiente, ya hemos aclarado que, a pesar de todo, podemos enviar eventos de otros gráficos al gráfico de nuestro programa. También podemos determinar el tipo de evento según el identificador de evento (id), determinar el gráfico según el parámetro lparam, y el nombre del objeto según el parámetro sparam. Pero ahora necesitamos averiguar cómo controlaremos estos eventos en otros gráficos; después de todo, el programa se está ejecutando en un gráfico, y debemos recibir los eventos y enviarlos a la biblioteca y al gráfico del programa desde otros gráficos. Y en estos otros gráficos, debería funcionar un programa que nuestra biblioteca conozca y pueda ejecutar.

No olvidemos que tenemos una pequeña clase para controlar los eventos en diferentes gráficos (CChartObjectsControl), y en la clase de colección de objetos gráficos creamos listas de todos los gráficos abiertos del terminal de cliente registrados solo en los parámetros de la clase mencionada, que además se ocupa de monitorear el cambio en el número de objetos gráficos en el gráfico controlados por un objeto de esta clase. En consecuencia, dentro de esta clase, podemos crear un programa para el gráfico que está controlado por este objeto, y colocarlo en él. Así, tendremos como dicho programa un indicador: el lenguaje MQL5 nos permite crear un indicador personalizado directamente en los recursos del programa (para que el indicador sea una parte integral de él después de la compilación), crear un identificador de indicador para cada uno de los gráficos abiertos en el terminal y, lo que es más agradable, colocar en este gráfico el indicador creado utilizando para ello la programación.

El indicador no tendrá búferes de dibujado: todo lo que hará es monitorear dos eventos de los objetos gráficos, CHARTEVENT_OBJECT_CHANGE y CHARTEVENT_OBJECT_DRAG, en el manejador OnChartEvent() y enviarlos al gráfico del programa como un evento personalizado que necesitaremos definir y procesar en la biblioteca.

Antes de comenzar a implementar nuestro plan, querríamos señalar que en muchos archivos de la biblioteca hemos realizado cambios en los nombres de las variables locales diseñadas para indicar el inicio de los ciclos sobre las propiedades de los objetos de la biblioteca. Simplemente, el nombre de la variable "beg" (la llamamos así como abreviatura de "begin") no parece muy correcto para los usuarios de habla inglesa... Por lo tanto, decidimos reemplazarlo en todos los archivos por el nombre completo "begin".

Archivos de la biblioteca en los que hemos cambiado el nombre de la variable:

DataTick.mqh, Symbol.mqh, Bar.mqh, PendRequest.mqh, Order.mqh, MQLSignal.mqh, IndicatorDE.mqh, DataInd.mqh, Buffer.mqh, GCnvElement.mqh, Event.mqh, ChartWnd.mqh, ChartObj.mqh, MarketBookOrd.mqh, Account.mqh and GStdGraphObj.mqh.


Mejorando las clases de la biblioteca

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

   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_UPPER,              // Anchor point at the upper left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT,                    // Anchor point at the left center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LEFT_LOWER,              // Anchor point at the lower left corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_LOWER,                   // Anchor point at the bottom center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_LOWER,             // Anchor point at the lower right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT,                   // Anchor point at the right center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_RIGHT_UPPER,             // Anchor point at the upper right corner
   MSG_GRAPH_OBJ_TEXT_ANCHOR_UPPER,                   // Anchor point at the upper center
   MSG_GRAPH_OBJ_TEXT_ANCHOR_CENTER,                  // Anchor point at the very center of the object

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Failed to get the list of newly added objects
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Failed to remove a graphical object from the list
   
   MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR,           // Indicator for controlling and sending events created
   MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR,    // Failed to create the indicator for controlling and sending events
   MSG_GRAPH_OBJ_CLOSED_CHARTS,                       // Chart window closed:
   MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS,            // Objects removed together with charts:
   
  };
//+------------------------------------------------------------------+

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

   {"Точка привязки в левом верхнем углу","Anchor point at the upper left corner"},
   {"Точка привязки слева по центру","Anchor point to the left in the center"},
   {"Точка привязки в левом нижнем углу","Anchor point at the lower left corner"},
   {"Точка привязки снизу по центру","Anchor point below in the center"},
   {"Точка привязки в правом нижнем углу","Anchor point at the lower right corner"},
   {"Точка привязки справа по центру","Anchor point to the right in the center"},
   {"Точка привязки в правом верхнем углу","Anchor point at the upper right corner"},
   {"Точка привязки сверху по центру","Anchor point above in the center"},
   {"Точка привязки строго по центру объекта","Anchor point strictly in the center of the object"},

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},
   
   {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"},
   {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"},
   {"Закрыто окон графиков: ","Closed chart windows: "},
   {"С ними удалено объектов: ","Objects removed with them: "},
   
  };
//+---------------------------------------------------------------------+


El método Symbol() en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh en la clase de objeto gráfico abstracto estándar retorna el símbolo de objeto gráfico "Gráfico":

//--- Symbol for the Chart object 
   string            Symbol(void)                  const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }

Sin embargo, como todos los métodos que trabajan con el objeto gráfico "Gráfico" tienen el prefijo "ChartObj" en sus nombres, para mantener la coherencia con los otros métodos, cambiaremos también el nombre de este:

//--- Symbol for the Chart object 
   string            ChartObjSymbol(void)          const { return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL);                      }
   void              SetChartObjSymbol(const string symbol)
                       {
                        if(::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
                           this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,symbol);
                       }
//--- Return the flags indicating object visibility on timeframes

Aquí, podemos ver ya las variables renombradas sobre las que hablamos al principio:

//+------------------------------------------------------------------+
//| Compare CGStdGraphObj objects by all properties                  |
//+------------------------------------------------------------------+
bool CGStdGraphObj::IsEqual(CGStdGraphObj *compared_obj) const
  {
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Display object properties in the journal                         |
//+------------------------------------------------------------------+
void CGStdGraphObj::Print(const bool full_prop=false,const bool dash=false)
  {
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") =============");
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n");
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   bool changed=false;
   int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }

   begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL;
   for(int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i;
      if(!this.SupportProperty(prop)) continue;
      if(this.GetProperty(prop)!=this.GetPropertyPrev(prop))
        {
         changed=true;
         ::Print(DFUN,this.Name(),": ",TextByLanguage(" Изменённое свойство: "," Modified property: "),GetPropertyDescription(prop));
        }
     }
   if(changed)
      PropertiesCopyToPrevData();
  }
//+------------------------------------------------------------------+


Indicador para el envío de mensajes sobre el cambio de propiedades de los objetos en todos los gráficos

Vamos a aclararnos con los parámetros del indicador que monitorearán los eventos de los objetos gráficos del gráfico al que se fijará.

Nuestro indicador debe conocer:

  1. el identificador del gráfico en el que se ha iniciado (llamémoslo SourseID) y
  2. el identificador del gráfico al que debe enviar los eventos personalizados (DestinationID).
Mientras que todo ha quedado claro con DestinationID (deberemos especificarlo en los parámetros de entrada del indicador), con el parámetro SourseID hay algunos matices.

Si simplemente ejecutamos el indicador en el gráfico manualmente, es decir, lo buscamos en el navegador y lo arrastramos al gráfico del símbolo. Luego la función ChartID(), que retorna el identificador del gráfico actual para el programa, devolverá el identificador del gráfico en el que el indicador está funcionando. Todo parece estar tal y como lo necesitamos. Pero... Si el indicador se encuentra en los recursos del programa, es decir, si al compilarse, está incorporado en el código del programa y se inicia desde el recurso incorporado, entonces la función ChartID() retornará el identificador del gráfico en el que se está ejecutando el programa, y ​​no una instancia de indicador, es decir, al iniciar programáticamente el indicador en diferentes gráficos, siempre que el indicador no se inicie desde la carpeta Indicators\, sino desde el recurso integrado, no reconoceremos el identificador del gráfico en el que se inicia este indicador. Por consiguiente, aquí tenemos la salida de transferir también el identificador del gráfico actual en la configuración del indicador, ya que disponemos de listas con los identificadores de todos los gráficos abiertos en el terminal de cliente.

Entonces, en el navegador del editor, en la carpeta \MQL5\Indicators\, podemos crear una nueva carpeta DoEasy\


y en ella, el nuevo indicador personalizado EventControl.mq5.



Al crearla, indicaremos dos parámetros de entrada del tipo long con un valor inicial igual a 0:


En el siguiente paso del funcionamiento del wizard, indicaremos la necesidad de incluir el manejador OnChartEvent() en el código del indicador, marcando la casilla correspondiente:


En el siguiente paso, dejaremos todos los campos y casillas de verificación como están y pulsaremos "Listo":


Eso es todo, ya hemos creado nuestro indicador.

Si lo compilamos ahora, se nos advertirá de que el indicador no tiene un solo búfer de dibujado:


Para evitar esta advertencia, deberemos indicar explícitamente en el código del indicador que no necesitamos búferes de dibujado:

//+------------------------------------------------------------------+
//|                                                 EventControl.mq5 |
//|                                  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"
#property indicator_chart_window
#property indicator_plots 0
//--- input parameters
input long     InpChartSRC = 0;
input long     InpChartDST = 0;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

En la biblioteca, al desinicializar sus clases, necesitaremos eliminar de todos los gráficos abiertos este indicador iniciado en ellos. Como podemos encontrar un indicador en el gráfico según su nombre breve, necesitaremos indicar explícitamente este nombre en el manejador OnInit() del indicador, de forma que luego podamos encontrar con la ayuda de este nombre el indicador para eliminarlo del gráfico:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator shortname
   IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(string)InpChartSRC+"_To#"+(string)InpChartDST);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

El nombre breve contendrá:

  • el nombre del indicador: "EventSend",
  • el identificador del gráfico desde el que se envían los mensajes: "_From#"+(string)InpChartSRC и
  • el identificador del gráfico al que se envían los mensajes: "_To#"+(string)InpChartDST.

En el manejador OnCalculate(), no haremos nada, simplemente retornaremos el valor del número de barras del gráfico:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   return rates_total;
  }
//+------------------------------------------------------------------+

En el manejador OnChartEvent() del indicador, monitorearemos dos eventos de objetos gráficos (CHARTEVENT_OBJECT_CHANGE y CHARTEVENT_OBJECT_DRAG) y, si se han registrado, enviaremos un evento personalizado al gráfico del programa de control:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG)
     {
      EventChartCustom(InpChartDST,(ushort)id,InpChartSRC,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

En el propio mensaje, indicaremos el gráfico al que estamos enviando el evento, el identificador del evento (la función EventChartCustom() añadirá automáticamente el valor CHARTEVENT_CUSTOM al valor del evento), el identificador del gráfico desde el que se ha enviado el evento y otros dos valores con las magnitudes por defecto: dparam tendrá un valor cero, y en sparam se escribirá el nombre del objeto gráfico en el que han sucedido los cambios.

Compilamos el indicador y lo dejamos en nuestra carpeta; más tarde, recurriremos a él desde la biblioteca. Lo necesitaremos solo al compilar la biblioteca, que lo colocará en sus recursos y luego hará referencia a la instancia del indicador guardada en los mismos.
Al distribuir un programa compilado, no será necesario transmitir ni el código fuente ni el archivo compilado del indicador, ya que, durante la compilación del programa, el código del indicador estará incrustado en el código de dicho programa, y ​​se referirá específicamente a él.

Podemos encontrar el archivo del indicador en los archivos adjuntos a la biblioteca, al final del artículo.

Ahora necesitamos crear un recurso que guarde el código ejecutable del indicador.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, definimos una macrosustitución para especificar la ruta al archivo ejecutable del indicador:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        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"
//+------------------------------------------------------------------+
//| Include files                                                 |
//+------------------------------------------------------------------+
#include "DataSND.mqh"
#include "DataIMG.mqh"
#include "Data.mqh"
#ifdef __MQL4__
#include "ToMQL4.mqh"
#endif 
//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#define PATH_TO_EVENT_CTRL_IND         "Indicators\\DoEasy\\EventControl.ex5"
//+------------------------------------------------------------------+
//| Macro substitutions                                                 |
//+------------------------------------------------------------------+

En esta macrosustitución obtendremos en lo sucesivo la ruta al archivo compilado del indicador en los recursos de la biblioteca.

Vamos a añadir al mismo archivo la lista de posibles eventos de los objetos gráficos:

//+------------------------------------------------------------------+
//| Data for handling graphical elements                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| List of possible graphical object events                         |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_OBJ_EVENT
  {
   GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,// No event
   GRAPH_OBJ_EVENT_CREATE,                            // "Creating a new graphical object" event
   GRAPH_OBJ_EVENT_CHANGE,                            // "Changing graphical object properties" event
   GRAPH_OBJ_EVENT_MOVE,                              // "Moving graphical object" event
   GRAPH_OBJ_EVENT_RENAME,                            // "Renaming graphical object" event
   GRAPH_OBJ_EVENT_DELETE,                            // "Removing graphical object" event
  };
#define GRAPH_OBJ_EVENTS_NEXT_CODE  (GRAPH_OBJ_EVENT_DELETE+1)  // The code of the next event after the last graphical object event code
//+------------------------------------------------------------------+
//| List of anchoring methods                                         |
//| (horizontal and vertical text alignment)                 |
//+------------------------------------------------------------------+

Este listado contiene una lista preliminar de eventos que enviaremos al programa al crear una funcionalidad unificada para controlar los eventos de los objetos gráficos estándar, que analizaremos en un futuro próximo.


Procesamiento de las señales del indicador sobre los eventos de cambio de las propiedades de los objetos

Al abrir nuevas ventanas de gráficos, la biblioteca crea automáticamente instancias de objetos de la clase de control de los objetos de gráfico CChartObjectsControl y los guarda en la lista de objetos de gestión de gráficos en la clase de colección de objetos gráficos.

El objeto de control de objetos gráficos guarda en sus propiedades el identificador del gráfico que controla. Por consiguiente, en el propio objeto, al crear este para la ventana del gráfico, podemos crear un indicador que se encargue de controlar los eventos de los objetos gráficos. Por ello, podemos colocar en cada gráfico recién abierto (o en el primer inicio, en los ya existentes) nuestro nuevo indicador para monitorear los eventos de los objetos gráficos y enviarlos al gráfico del programa de control.

En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, en la sección privada de la clase CChartObjectsControl, declaramos tres nuevas variables:

//+------------------------------------------------------------------+
//| Chart object management class                                    |
//+------------------------------------------------------------------+
class CChartObjectsControl : public CObject
  {
private:
   CArrayObj         m_list_new_graph_obj;      // List of added graphical objects
   ENUM_TIMEFRAMES   m_chart_timeframe;         // Chart timeframe
   long              m_chart_id;                // Chart ID
   long              m_chart_id_main;           // Идентификатор графика управляющей программы
   string            m_chart_symbol;            // Chart symbol
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_last_objects;            // Number of graphical objects during the previous check
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   int               m_handle_ind;              // Хендл индикатора-контроллера событий
   string            m_name_ind;                // Короткое имя индикатора-контроллера событий
   
//--- Return the name of the last graphical object added to the chart
   string            LastAddedGraphObjName(void);
//--- Set the permission to track mouse events and graphical objects
   void              SetMouseEvent(void);
   
public:

Las funciones de las variables quedan claras a partir de sus descripciones.

En la sección pública de la clase, declaramos dos nuevos métodos para crear un indicador y añadirlo al gráfico:

public:
//--- Return the variable values
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_chart_timeframe;    }
   long              ChartID(void)                             const { return this.m_chart_id;           }
   string            Symbol(void)                              const { return this.m_chart_symbol;       }
   bool              IsEvent(void)                             const { return this.m_is_graph_obj_event; }
   int               TotalObjects(void)                        const { return this.m_total_objects;      }
   int               Delta(void)                               const { return this.m_delta_graph_obj;    }
//--- Create a new standard graphical object
   CGStdGraphObj    *CreateNewGraphObj(const ENUM_OBJECT obj_type,const long chart_id, const string name);
//--- Return the list of newly added objects
   CArrayObj        *GetListNewAddedObj(void)                        { return &this.m_list_new_graph_obj;}
//--- Create the event control indicator
   bool              CreateEventControlInd(const long chart_id_main);
//--- Add the event control indicator to the chart
   bool              AddEventControlInd(void);
//--- Check the chart objects
   void              Refresh(void);
//--- Constructors

En los constructores de la clase, establecemos las nuevas variables en sus valores predeterminados y añadimos el destructor de clase en el que se elimina el indicador en el gráfico y se elimina el identificador del indicador, liberando la parte computacional del indicador:

//--- Check the chart objects
   void              Refresh(void);
//--- Constructors
                     CChartObjectsControl(void)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=::ChartID();
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        this.SetMouseEvent();
                       }
                     CChartObjectsControl(const long chart_id)
                       { 
                        this.m_list_new_graph_obj.Clear();
                        this.m_list_new_graph_obj.Sort();
                        this.m_chart_id=chart_id;
                        this.m_chart_timeframe=(ENUM_TIMEFRAMES)::ChartPeriod(this.m_chart_id);
                        this.m_chart_symbol=::ChartSymbol(this.m_chart_id);
                        this.m_chart_id_main=::ChartID();
                        this.m_is_graph_obj_event=false;
                        this.m_total_objects=0;
                        this.m_last_objects=0;
                        this.m_delta_graph_obj=0;
                        this.m_name_ind="";
                        this.m_handle_ind=INVALID_HANDLE;
                        this.SetMouseEvent();
                       }
//--- Destructor
                     ~CChartObjectsControl()
                       {
                        ::ChartIndicatorDelete(this.ChartID(),0,this.m_name_ind);
                        ::IndicatorRelease(this.m_handle_ind);
                       }
                     
//--- Compare CChartObjectsControl objects by a chart ID (for sorting the list by an object property)
   virtual int       Compare(const CObject *node,const int mode=0) const
                       {
                        const CChartObjectsControl *obj_compared=node;
                        return(this.ChartID()>obj_compared.ChartID() ? 1 : this.ChartID()<obj_compared.ChartID() ? -1 : 0);
                       }

//--- Event handler
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

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


Implementamos fuera del cuerpo de la clase los métodos declarados:

Método que crea un indicador de control de eventos:

//+------------------------------------------------------------------+
//| CChartObjectsControl: Create the event control indicator         |
//+------------------------------------------------------------------+
bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main)
  {
   this.m_chart_id_main=chart_id_main;
   string name="::"+PATH_TO_EVENT_CTRL_IND;
   ::ResetLastError();
   this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main);
   if(this.m_handle_ind==INVALID_HANDLE)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR);
      CMessage::ToLog(DFUN,::GetLastError(),true);
      return false;
     }
   this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main;
   Print
     (
      DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ",
      CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\""
     );
   return true;
  }
//+------------------------------------------------------------------+

Aquí, establecemos el identificador del programa de control transmitido en los parámetros del método, establecemos la ruta en los recursos a nuestro indicador y creamos el manejador del indicador construido usando como base el símbolo y el marco temporal del gráfico controlado por esta clase.
En los parámetros de entrada del indicador, transmitimos el identificador del gráfico controlado por el objeto de clase, así como el identificador del programa de control
.
Si no hemos podido crear el indicador, notificaremos sobre ello en el registro del terminal, informando del número y la descripción del error y retornaremos false
.
Si se ha creado el manejador del indicador, especificaremos el nombre breve según el cual el indicador es eliminado del gráfico en el destructor de clases; luego mostraremos un mensaje en el diario sobre la creación del indicador en el gráfico y retornaremos true.

Tenga en cuenta que al especificar la ruta al indicador en los recursos de la biblioteca, indicaremos el signo de resolución de contexto "::" antes de la línea con la ruta,
mientras que al crear el recurso, indicaremos el signo "\\".

Método que añade a un gráfico el indicador de gestión de eventos:

//+------------------------------------------------------------------+
//|CChartObjectsControl: Add the event control indicator to the chart|
//+------------------------------------------------------------------+
bool CChartObjectsControl::AddEventControlInd(void)
  {
   if(this.m_handle_ind==INVALID_HANDLE)
      return false;
   return ::ChartIndicatorAdd(this.ChartID(),0,this.m_handle_ind);
  }
//+------------------------------------------------------------------+

Aquí verificamos el manejador del indicador y, si no es válido, retornamos false. De lo contrario, retornamos el resultado de la función de adición de un indicador al gráfico.

Antes de definir la clase de colección de objetos gráficos, indicamos la ruta al recurso donde se guarda el indicador:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
class CGraphElementsCollection : public CBaseObj
  {

Esta línea crea un recurso en la biblioteca donde se coloca el archivo ejecutable compilado del indicador para controlar los eventos del gráfico.

En la sección privada de la clase, declaramos cuatro nuevos métodos:

class CGraphElementsCollection : public CBaseObj
  {
private:
   CArrayObj         m_list_charts_control;     // List of chart management objects
   CListObj          m_list_all_canv_elm_obj;   // List of all graphical elements on canvas
   CListObj          m_list_all_graph_obj;      // List of all graphical objects
   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   
//--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements
   bool              IsPresentGraphElmInList(const int id,const ENUM_GRAPH_ELEMENT_TYPE type_obj);
//--- Return the flag indicating the presence of the graphical object class in the graphical object collection list
   bool              IsPresentGraphObjInList(const long chart_id,const string name);
//--- Return the flag indicating the presence of a graphical object on a chart by name
   bool              IsPresentGraphObjOnChart(const long chart_id,const string name);
//--- Return the pointer to the object of managing objects of the specified chart
   CChartObjectsControl *GetChartObjectCtrlObj(const long chart_id);
//--- Create a new object of managing graphical objects of a specified chart and add it to the list
   CChartObjectsControl *CreateChartObjectCtrlObj(const long chart_id);
//--- Update the list of graphical objects by chart ID
   CChartObjectsControl *RefreshByChartID(const long chart_id);
//--- Check if the chart window is present
   bool              IsPresentChartWindow(const long chart_id);
//--- Handle removing the chart window
   void              RefreshForExtraObjects(void);
//--- Return the first free ID of the graphical (1) object and (2) element on canvas
   long              GetFreeGraphObjID(void);
   long              GetFreeCanvElmID(void);
//--- Add a graphical object to the collection
   bool              AddGraphObjToCollection(const string source,CChartObjectsControl *obj_control);
//--- Find an object present in the collection but not on a chart
   CGStdGraphObj    *FindMissingObj(const long chart_id);
//--- Find the graphical object present on a chart but not in the collection
   string            FindExtraObj(const long chart_id);
//--- Remove the graphical object from the graphical object collection list: (1) specified object, (2) by chart ID
   bool              DeleteGraphObjFromList(CGStdGraphObj *obj);
   void              DeleteGraphObjectsFromList(const long chart_id);
//--- Remove the object of managing charts from the list
   bool              DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj);
  
public:

Para eliminar los objetos innecesarios de gestión de gráficos de la lista de clases y los objetos que describen objetos gráficos remotos de la lista de colección, necesitaremos saber que tal o cual gráfico ha sido eliminado. El método IsPresentChartWindow() servirá para esto. En el método RefreshForExtraObjects() se procesará la presencia de objetos extra en las listas de la clase de colección, mientras que los métodos DeleteGraphObjectsFromList() y DeleteGraphObjCtrlObjFromList() eliminarán directamente los objetos indicados de las listas de la clase de colección de objetos gráficos.

En el método que crea un nuevo objeto para gestionar los objetos gráficos del gráfico especificado, añadimos el código para crear el indicador y lo añadimos al gráfico:

//+------------------------------------------------------------------+
//| Create a new graphical object management object                  |
//| for a specified and add it to the list                           |
//+------------------------------------------------------------------+
CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const long chart_id)
  {
//--- Create a new object for managing chart objects by ID
   CChartObjectsControl *obj=new CChartObjectsControl(chart_id);
//--- If the object is not created, inform of the error and return NULL
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(string)chart_id);
      return NULL;
     }
//--- If failed to add the object to the list, inform of the error, remove the object and return NULL
   if(!this.m_list_charts_control.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete obj;
      return NULL;
     }
   if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID()))
      obj.AddEventControlInd();
//--- Return the pointer to the object that was created and added to the list
   return obj;
  }
//+------------------------------------------------------------------+

Aquí, comprobamos si el objeto de gestión ha sido creado para el gráfico actual en el que funciona el programa, y si el indicador se ha creado con éxito para el gráfico controlado por el objeto de gestión del gráfico; de ser así, añadiremos el indicador creado al gráfico.

En el método que actualiza la lista de todos los objetos gráficos, en primer lugar, procesamos las situaciones de cierre de gráficos en el terminal, para así eliminar los objetos de gestión de gráficos correspondientes a los gráficos remotos y los objetos de las clases que describen los objetos gráficos eliminados junto con los gráficos que se volvieron sobrantes en la lista de colección después de cerrar el gráfico:

//+------------------------------------------------------------------+
//| Update the list of all graphical objects                         |
//+------------------------------------------------------------------+
void CGraphElementsCollection::Refresh(void)
  {
   this.RefreshForExtraObjects();
//--- Declare variables to search for charts
   long chart_id=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<0)
         break;
      //--- Get the pointer to the object for managing graphical objects
      //--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      //--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         continue;
      //--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         //--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>0)
           {
            //--- Get the list of added graphical objects and move them to the collection list
            //--- (if failed to move the object to the collection, move on to the next object)
            if(!AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               continue;
           }
         //--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<0)
           {
            // Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);
            if(obj!=NULL)
              {
               //--- Display a short description of a detected object deleted from a chart in the journal
               obj.PrintShort();
               //--- Remove the class object of a removed graphical object from the collection list
               if(!this.DeleteGraphObjFromList(obj))
                  CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
              }
           }
        }
      //--- Increase the loop index
      i++;
     }
  }
//+------------------------------------------------------------------+


Método que comprueba la presencia de la ventana del gráfico:

//+------------------------------------------------------------------+
//| Check if the chart window is present                             |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::IsPresentChartWindow(const long chart_id)
  {
   long chart=0;
   int i=0;
//--- In the loop by all open charts in the terminal (no more than 100)
   while(i<CHARTS_MAX)
     {
      //--- Get the chart ID
      chart=::ChartNext(chart);
      if(chart<0)
         break;
      if(chart==chart_id)
         return true;
      //--- Increase the loop index
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo por todos los gráficos abiertos en el terminal, obtenemos el identificador del siguiente gráfico y lo comparamos con el que se ha transmitido al método.
Si los identificadores coinciden, significará que el gráfico existe, y retornaremos true
.
Tras finalizar el ciclo, retornamos false, no existe el gráfico con el identificador indicado.

Método que procesa la eliminación de la ventana del gráfico:

//+------------------------------------------------------------------+
//| Handle removing the chart window                                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::RefreshForExtraObjects(void)
  {
   for(int i=this.m_list_charts_control.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
      if(obj_ctrl==NULL)
         continue;
      if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
        {
         long chart_id=obj_ctrl.ChartID();
         int total_ctrl=m_list_charts_control.Total();
         this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
         int total_obj=m_list_all_graph_obj.Total();
         this.DeleteGraphObjectsFromList(chart_id);
         int del_ctrl=total_ctrl-m_list_charts_control.Total();
         int del_obj=total_obj-m_list_all_graph_obj.Total();
         Print
           (
            DFUN,CMessage::Text(MSG_GRAPH_OBJ_CLOSED_CHARTS),(string)del_ctrl,". ",
            CMessage::Text(MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS),(string)del_obj
           );
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, en un ciclo por la lista de objetos de gestión del gráfico, obtenemos el siguiente objeto y si no hay un gráfico correspondiente al objeto en el terminal, significará que el gráfico se ha cerrado.
Por consiguiente, obtenemos el identificador del gráfico cerrado y el número total de gráficos abiertos previamente y
eliminamos de la lista el objeto de gestión correspondiente al gráfico ya cerrado en el terminal
.
Obtener el número total de objetos gráficos presentes en el terminal antes de eliminar el gráfico y eliminar objetos de clases de objetos gráficos de la colección de listas que estaban en el gráfico ahora cerrado.
A continuación, calculamos el número de gráficos cerrados y el número de objetos gráficos eliminados junto con los gráficos y mostramos un mensaje en el diario sobre el número de gráficos cerrados y la cantidad de objetos gráficos en ellos.
Posteriormente, en lugar de este mensaje, crearemos un evento y lo enviaremos al gráfico del programa de control.

Método que elimina de la lista de colección de objetos gráficos un objeto gráfico según el identificador del gráfico:

//+------------------------------------------------------------------+
//| Remove a graphical object by a chart ID                          |
//| from the graphical object collection list                        |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteGraphObjectsFromList(const long chart_id)
  {
   CArrayObj *list=CSelect::ByGraphicStdObjectProperty(GetListGraphObj(),GRAPH_OBJ_PROP_CHART_ID,chart_id,EQUAL);
   if(list==NULL)
      return;
   for(int i=list.Total();i>WRONG_VALUE;i--)
     {
      CGStdGraphObj *obj=list.At(i);
      if(obj==NULL)
         continue;
      this.DeleteGraphObjFromList(obj);
     }
  }
//+------------------------------------------------------------------+

Aquí, obtenemos la lista de objetos gráficos con el identificador de gráfico especificado. En un ciclo por la lista resultante, obtenemos el siguiente objeto y lo eliminamos de la lista de colección.

Método que elimina el objeto de control de gráficos de la lista:

//+------------------------------------------------------------------+
//| Remove the object of managing charts from the list               |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::DeleteGraphObjCtrlObjFromList(CChartObjectsControl *obj)
  {
   this.m_list_charts_control.Sort();
   int index=this.m_list_charts_control.Search(obj);
   return(index==WRONG_VALUE ? false : m_list_charts_control.Delete(index));
  }
//+------------------------------------------------------------------+

Aquí, asignamos la bandera de lista clasificada a la lista de objetos de control de gráficos, buscamos con la ayuda del método Search() el índice del objeto especificado en la lista y, si el objeto no está en la lista, retornamos false; de lo contrario, retornamos el resultado del método Delete().

En el manejador de eventos de la clase de colección de objetos gráficos, necesitamos sustituir el trabajo con el gráfico actual por el trabajo con un gráfico según su identificador. Para hacer esto, controlaremos el valor del parámetro lparam en el evento. Este será igual a cero cuando se obtenga un evento del gráfico actual y será igual al identificador del gráfico desde el que se ha recibido el evento cuando se obtenga un evento personalizado del indicador.

Para obtener de un evento personalizado el identificador de evento de un objeto gráfico, necesitaremos restar el valor CHARTEVENT_CUSTOM al valor del identificador recibido y, junto con la comprobación del identificador, realizar la verificación del valor calculado del identificador de evento en la variable idx.

Además, si lparam no es igual a cero, el evento no se obtendrá del gráfico actual; de lo contrario, se obtendrá del actual.
Solo queda sustituir todas las entradas ::ChartID() en el código por el chart_id obtenido:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   CGStdGraphObj *obj=NULL;
   ushort idx=ushort(id-CHARTEVENT_CUSTOM);
   if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG)
     {
      //--- Get the chart ID. If lparam is zero,
      //--- the event is from the current chart,
      //--- otherwise, this is a custom event from an indicator
      long chart_id=(lparam==0 ? ::ChartID() : lparam);
      //--- If the object, whose properties were changed or which was relocated,
      //--- is successfully received from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      if(obj!=NULL)
        {
         //--- Update the properties of the obtained object
         //--- and check their change
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      else
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         if(obj==NULL)
            return;
         //--- Get the name of the renamed graphical object on the chart, which is not in the collection list
         string name_new=this.FindExtraObj(chart_id);
         //--- Set a new name for the collection list object, which does not correspond to any graphical object on the chart,
         //--- update the chart properties and check their change
         obj.SetName(name_new);
         obj.PropertiesRefresh();
         obj.PropertiesCheckChanged();
        }
     }
  }
//+------------------------------------------------------------------+

Estas son todas las mejoras que necesitábamos por hoy. Vamos a poner a prueba el resultado obtenido.


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\Part87\ con el nuevo nombre TestDoEasyPart87.mq5.

No haremos ningún cambio en el asesor experto, simplemente lo compilaremos y lo ejecutaremos en el gráfico, abriendo previamente dos gráficos más en el terminal. Al crear y modificar objetos en los gráficos adicionales, todos los eventos producidos con los objetos gráficos serán registrados por la biblioteca, después de lo cual, los mensajes correspondientes se mostrarán en el diario. Al abrir otro gráfico, también se creará un objeto de control del gráfico, así como un indicador que registrará los eventos de cambio de objeto y los enviará a la biblioteca. Al eliminar gráficos adicionales, se mostrará información sobre ello en el diario:



¿Qué es lo próximo?

En el próximo artículo, comenzaremos a juntar todo el procesamiento creado para los eventos de los objetos gráficos de tal forma que envíe al gráfico del programa de control todos los eventos sucedidos con los objetos gráficos en los gráficos abiertos (ahora, nuestros eventos registrados son simplemente mostrados en el registro, pero el programa que se ejecuta bajo el control de la biblioteca no los conoce).

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:

Gráficos en la biblioteca DoEasy (Parte 83): Clase de objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 84): Clases herederas del objeto gráfico abstracto estándar
Gráficos en la biblioteca DoEasy (Parte 85): Colección de objetos gráficos - añadiendo los objetos nuevamente creados
Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades

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

Archivos adjuntos |
MQL5.zip (4155.98 KB)
Combinatoria y teoría de la probabilidad en el trading (Parte IV): Lógica de Bernoulli Combinatoria y teoría de la probabilidad en el trading (Parte IV): Lógica de Bernoulli
En el presente artículo, hemos decidido hablar del conocido esquema de Bernoulli, y también mostrar cómo podemos utilizarlo al describir conjuntos de datos relacionados con el trading, para su posterior uso en la futura creación de un sistema comercial autoadaptable. Asimismo, buscaremos un algoritmo más general (la fórmula de Bernoulli constituye un caso especial dentro de este tipo), y encontraremos una aplicación para él.
Use los canales y chats grupales de MQL5.community Use los canales y chats grupales de MQL5.community
En el sitio web MQL5.com podrá encontrar tráders de todo el mundo: estos publican artículos, códigos gratuitos y productos en el Mercado, ejecutan trabajos en freelance y codifican señales comerciales. Podrá relacionarse con ellos en el foro, los chats de tráders y los canales de MetaTrader.
Stoploss de PriceAction Fijo o RSI fijo (Smart StopLoss) Stoploss de PriceAction Fijo o RSI fijo (Smart StopLoss)
Los Stop Loss son una herramienta importante en cuanto a la gestión de dinero en el trading. El uso efectivo de stop-loss, take profit y el tamaño de lote puede hacer que un tráder sea más consistente en el comercio y, sobre todo, que logre mayor rentabilidad. Aunque el stop-loss es una gran herramienta, existen desafíos derivados de su uso. El principal es la caza de stop-loss. Este artículo analiza cómo reducir la caza de stop-loss en el trading y la compara con el uso clásico de stop-loss para determinar su rentabilidad.
Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades Gráficos en la biblioteca DoEasy (Parte 86): Colección de objetos gráficos - controlando la modificación de propiedades
En el presente artículo, analizaremos la modificación de los valores de las propiedades, así como la eliminación y el cambio de nombre de los objetos gráficos en la biblioteca.