English Русский 中文 Deutsch 日本語 Português
Gráficos en la biblioteca DoEasy (Parte 95): Elementos de control de los objetos gráficos compuestos

Gráficos en la biblioteca DoEasy (Parte 95): Elementos de control de los objetos gráficos compuestos

MetaTrader 5Ejemplos | 31 marzo 2022, 15:54
610 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

Continuamos con el desarrollo de los objetos gráficos compuestos. Estos son objetos gráficos estándar que constan de varios de ellos y se combinan en un objeto gráfico. Los objetos gráficos incluidos en el objeto compuesto se definen en la biblioteca como objetos gráficos estándar extendidos. Dichos objetos tienen algunas propiedades y funcionalidades adicionales que les permiten incorporar objetos gráficos, y también ser incorporados a otros.
El concepto de objeto gráfico compuesto requiere una funcionalidad que le permita mantener el objeto en el lugar donde está adjunto a otro objeto y ajustar su ubicación cuando el objeto principal cambie o se mueva.
En el último artículo, comenzamos a crear manejadores de eventos para objetos gráficos compuestos; asimismo, procesamos la eliminación de un objeto gráfico compuesto y comenzamos a desarrollar un manejador para moverlo.

Hoy nos desviaremos un poco del tema del desplazamiento de objetos gráficos compuestos y crearemos un manejador de eventos de cambio del gráfico en el que se encuentra el objeto gráfico compuesto; también trabajaremos con los objetos de gestión de objetos gráficos compuestos.
¿Porqué hacemos así? Hemos planificado la creación en tiempo real de objetos gráficos compuestos, vinculando un objeto dependiente al objeto básico al arrastrar el objeto dependiente al objeto básico. El objeto gráfico básico monitoreará la aproximación de otro objeto con el ratón, y a cierta distancia de uno de sus puntos de anclaje al gráfico, se activará el mecanismo para unir el objeto que estamos acercando con el ratón. En este caso, se mostrarán las líneas que conectan el punto de anclaje del objeto adjunto con el punto de anclaje del objeto básico, lo cual mostrará visualmente la preparación del objeto arrastrado para fijarse al objeto básico. Para lograr esto, cada punto de anclaje del objeto gráfico deberá tener un objeto de formulario de cierto tamaño. La entrada en el área de los objetos de formulario activará el mecanismo de anclaje, mientras que las líneas que indican que los objetos están listos para la interacción, se mostrarán en el formulario mismo. Tales formularios estarán presentes de forma invisible en cada punto de pivote del objeto gráfico. El tamaño del área se podrá ver solo en casos concretos (para realizar la depuración), habilitando para ello el dibujado de un rectángulo a lo largo de los bordes del formulario:

Además, en el formulario se mostrarán los puntos de anclaje del objeto gráfico que también aparecerán solo cuando el cursor del ratón entre en el área activa del formulario. Por ello, moveremos y modificaremos el objeto gráfico extendido no seleccionándolo con un clic del ratón, sino colocando el cursor del mismo sobre el área del formulario. Tan pronto como pasemos el cursor sobre el área activa del formulario (delineada por los rectángulos en la figura de arriba), aparecerán unas marcas en el punto de anclaje del objeto gráfico (en la figura de arriba es un punto azul en el centro de el círculo); si tomamos el formulario con el ratón y comenzamos a moverlo, el punto de anclaje correspondiente del objeto gráfico se moverá tras el cursor, lo cual provocará la modificación del objeto en sí y, en consecuencia, del objeto gráfico compuesto.
Si el cursor del ratón entra en el área activa del formulario con el botón del ratón presionado, esto significará (con verificación) que estamos arrojando a este formulario otro objeto gráfico, lo cual activará el mecanismo para vincular un objeto a otro. Así, con la ayuda de estos formularios, resolveremos varios problemas a la vez.

Hoy, no vamos a vincular un objeto a otro; faltan algunas cosas para comenzar a crear esta funcionalidad. No obstante, crearemos los formularios en sí: los adjuntaremos a los puntos de anclaje del objeto gráfico y crearemos un mecanismo para moverlos a las coordenadas de los puntos de pivote del objeto cuando el gráfico cambie, por ejemplo, al moverse y modificarse su escala de visualización. Hay que hacer esto porque el objeto de formulario tiene sus coordenadas en píxeles de la pantalla, mientras que la mayoría de los objetos gráficos tienen valores de tiempo/precio.


Mejorando las clases de la biblioteca

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

//--- CLinkedPivotPoint
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X,                // Not a single pivot point is set for the object along the X axis
   MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y,                // Not a single pivot point is set for the object along the Y axis
   MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE,             // The object is not attached to the basic graphical object
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ,       // Failed to create a data object for the X and Y pivot points
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X,            // Number of base object pivot points for calculating the X coordinate: 
   MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y,            // Number of base object pivot points for calculating the Y coordinate: 

//--- CGStdGraphObjExtToolkit
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA,     // Failed to change the size of the pivot point time data array
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA,    // Failed to change the size of the pivot point price data array
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM,   // Failed to create a form object to manage a pivot point
   
  };
//+------------------------------------------------------------------+

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

//--- CLinkedPivotPoint
   {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"},
   {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"},
   {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"},
   {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"},
   {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "},
   {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "},
   
//--- CGStdGraphObjExtToolkit
   {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"},
   {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"},
   {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"},
   
  };
//+---------------------------------------------------------------------+


En el archivo \MQL5\Include\DoEasy\Defines.mqh, cambiamos la macrosustitución

#define CLR_DEFAULT                    (0xFF000000)               // Default symbol background color in the navigator

por otra más comprensible

#define CLR_MW_DEFAULT                 (0xFF000000)               // Default symbol background color in the Market Watch

y la macrosustitución

#define NULL_COLOR                     (0x00FFFFFF)               // Zero for the canvas with the alpha channel

por otra más comprensible

#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel

y escribimos nuevas macrosustituciones para establecer los valores por defecto de los objetos de formulario que crearemos hoy:

//--- Graphical object parameters
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Maximum value of an ID of a graphical object belonging to a program
#define CTRL_POINT_SIZE                (5)                        // Radius of the control point on the form for managing graphical object pivot points
#define CTRL_FORM_SIZE                 (40)                       // Size of the control point form for managing graphical object pivot points
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+


Como hemos cambiado los nombres de las macrosustituciones, reemplazaremos los nombres obsoletos en todos los archivos.

Simplemente presionaremos la combinación de teclas Ctrl+Shift+H, introduciremos los siguientes valores en los campos y marcaremos las casillas como en la figura:


Después, presionaremos el botón Replace in Files (Reemplazar en los archivos). El editor buscará todas las carpetas en la biblioteca y realizará la sustitución en ellas.

Haremos lo mismo para reemplazar "NULL_COLOR" por "CLR_CANV_NULL"

Estos nombres para las macrosustituciones resultan más descriptivos, y no nos obligarán a recordar en el futuro cuál es la función de cada uno (introdujimos por error CLR_DEFAULT para establecer un fondo transparente para el lienzo y buscamos durante mucho tiempo por qué no era transparente)

Además de estos reemplazos, hemos realizado ajustes menores en todos los archivos de clase de los descendientes del objeto gráfico básico (simplemente hemos añadido una coma al texto del método que registra la descripción breve del objeto):

//+------------------------------------------------------------------+
//| Display a short description of the object in the journal         |
//+------------------------------------------------------------------+
void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false)
  {
   ::Print
     (
      (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0),
      ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
     );
  }
//+------------------------------------------------------------------+

Se trata de una mejora relacionada puramente con el "diseño".
La hemos implementado en todos los archivos de la carpeta \MQL5\Include\DoEasy\Objects\Graph\Standard\; usted mismo podrá verlo en los archivos adjuntos al artículo.


Clase de instrumental del objeto gráfico estándar extendido

Bien, vamos a proceder a crear el instrumental necesario para trabajar con objetos gráficos extendidos. Nos referimos a la clase en la que escribiremos los métodos para crear los objetos de formulario y trabajar con ellos. Cada objeto gráfico extendido contendrá un puntero a un objeto de esta clase. Si es necesario (si hablamos de un objeto básico dentro de un objeto gráfico compuesto), el objeto de clase se creará dinámicamente cuando se cree el objeto extendido, y será eliminado cuando este se elimine.

Los parámetros necesarios del objeto gráfico básico (sus coordenadas, tipo, nombre, etc.) se transferirán al objeto, y dentro del objeto se rastrearán las coordenadas del objeto básico y se corregirán las coordenadas de los objetos de formulario. En consecuencia, los objetos de formulario finalmente nos permitirán controlar el objeto básico.

Pero, vayamos por orden...

En el directorio de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\, creamos una nueva carpeta Extend\, y en ella, un nuevo archivo CGStdGraphObjExtToolkit.mqh de la clase CGStdGraphObjExtToolkit, heredada de la clase básica CObject, para construir la biblioteca estándar MQL5 :

//+------------------------------------------------------------------+
//|                                      CGStdGraphObjExtToolkit.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"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Graph\Form.mqh"
//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
  }

El archivo de clase del objeto de formulario deberá estar conectado al archivo de clase.

En la sección privada de la clase, declararemos las variables en las que almacenaremos todas las propiedades necesarias del objeto básico,
las propiedades para crear formularios, la lista para almacenarlos y los métodos para crear un objeto de formulario y retornar sus coordenadas de pantalla :

//+------------------------------------------------------------------+
//| Extended standard graphical                                      |
//| object toolkit class                                             |
//+------------------------------------------------------------------+
class CGStdGraphObjExtToolkit : public CObject
  {
private:
   long              m_base_chart_id;           // Base graphical object chart ID
   int               m_base_subwindow;          // Base graphical object chart subwindow
   ENUM_OBJECT       m_base_type;               // Base object type
   string            m_base_name;               // Base object name
   int               m_base_pivots;             // Number of base object reference points
   datetime          m_base_time[];             // Time array of base object reference points
   double            m_base_price[];            // Price array of base object reference points
   int               m_base_x;                  // Base object X coordinate
   int               m_base_y;                  // Base object Y coordinate
   int               m_ctrl_form_size;          // Size of forms for managing reference points
   int               m_shift;                   // Shift coordinates for adjusting the form location
   CArrayObj         m_list_forms;              // List of form objects for managing reference points
//--- Create a form object on a base object reference point
   CForm            *CreateNewControlPointForm(const int index);
//--- Return X and Y screen coordinates of the specified reference point of the graphical object
   bool              GetControlPointCoordXY(const int index,int &x,int &y);
public:

Usaremos matrices para almacenar las coordenadas de precio y tiempo, ya que un objeto gráfico puede tener varios puntos de pivote; por ello, las coordenadas de cada punto se almacenarán en las celdas correspondientes de la matriz. La coordenada del primer punto estará en el índice de matriz 0, la coordenada del segundo punto estará en el índice de matriz 1, la coordenada del tercero estará en el índice 2, etc.
Necesitamos el desplazamiento de las coordenadas del formulario para colocar con precisión el formulario en el centro del punto de pivote del objeto; este desplazamiento supondrá la mitad de su tamaño. En este caso, si el tamaño del formulario se establecerá como un múltiplo de dos, por ejemplo, 10 se corregirá en 1 más, es decir, 11. Esto es necesario para que el formulario se pueda colocar exactamente en el centro del punto de pivote del objeto gráfico y no tenga el tamaño de uno de los lados más un píxel del tamaño del lado opuesto.
En la lista de formularios, almacenaremos todos los formularios creados, y desde ella accederemos a estos con la ayuda de un puntero. El método para calcular las coordenadas de la pantalla lo necesitaremos para saber en qué coordenadas de la pantalla colocar el formulario para que quede exactamente en el punto de pivote del objeto gráfico.

En la sección pública de la clase, declaramos todos los métodos necesarios para trabajar con la clase, que analizaremos a continuación:

public:
//--- Set the parameters of the base object of a composite graphical object
   void              SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                const long base_chart_id,const int base_subwindow,
                                const int base_pivots,const int ctrl_form_size,
                                const int base_x,const int base_y,
                                const datetime &base_time[],const double &base_price[]);
//--- Set the base object (1) time, (2) price, (3) time and price coordinates
   void              SetBaseObjTime(const datetime time,const int index);
   void              SetBaseObjPrice(const double price,const int index);
   void              SetBaseObjTimePrice(const datetime time,const double price,const int index);
//--- Set the base object (1) X, (2) Y, (3) X and Y screen coordinates
   void              SetBaseObjCoordX(const int value)                        { this.m_base_x=value;                          }
   void              SetBaseObjCoordY(const int value)                        { this.m_base_y=value;                          }
   void              SetBaseObjCoordXY(const int value_x,const int value_y)   { this.m_base_x=value_x; this.m_base_y=value_y; }
//--- (1) Set and (2) return the size of the form of pivot point management control points
   void              SetControlFormSize(const int size);
   int               GetControlFormSize(void)                          const { return this.m_ctrl_form_size;                }
//--- Return the pointer to the pivot point form by (1) index and (2) name
   CForm            *GetControlPointForm(const int index)                     { return this.m_list_forms.At(index);           }
   CForm            *GetControlPointForm(const string name,int &index);
//--- Return the number of the base object pivot points
   int               GetNumPivotsBaseObj(void)                          const { return this.m_base_pivots;                    }
//--- Create form objects on the base object pivot points
   bool              CreateAllControlPointForm(void);
//--- Remove all form objects from the list
   void              DeleteAllControlPointForm(void);
   
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructor/destructor
                     CGStdGraphObjExtToolkit(const ENUM_OBJECT base_type,const string base_name,
                                             const long base_chart_id,const int base_subwindow,
                                             const int base_pivots,const int ctrl_form_size,
                                             const int base_x,const int base_y,
                                             const datetime &base_time[],const double &base_price[])
                       {
                        this.m_list_forms.Clear();
                        this.SetBaseObj(base_type,base_name,base_chart_id,base_subwindow,base_pivots,ctrl_form_size,base_x,base_y,base_time,base_price);
                        this.CreateAllControlPointForm();
                       }
                     CGStdGraphObjExtToolkit(){;}
                    ~CGStdGraphObjExtToolkit(){;}
  };
//+------------------------------------------------------------------+

En el constructor de clase, limpiamos la lista de objetos de formulario, asignamos a las variables de clase todos los valores del objeto básico (transmitidos ​en los parámetros del constructor) necesarios para el funcionamiento de la clase, y creamos en cada punto de pivote del objeto gráfico básico los objetos de formulario necesarios para controlar e interactuar con el objeto básico.

Método que establece los parámetros del objeto básico de un objeto gráfico compuesto:

//+------------------------------------------------------------------+
//| Set the base object parameters of the                            |
//| composite graphical object                                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObj(const ENUM_OBJECT base_type,const string base_name,
                                         const long base_chart_id,const int base_subwindow,
                                         const int base_pivots,const int ctrl_form_size,
                                         const int base_x,const int base_y,
                                         const datetime &base_time[],const double &base_price[])
  {
   this.m_base_chart_id=base_chart_id;       // Base graphical object chart ID
   this.m_base_subwindow=base_subwindow;     // Base graphical object chart subwindow
   this.m_base_type=base_type;               // Base object type
   this.m_base_name=base_name;               // Base object name
   this.m_base_pivots=base_pivots;           // Number of base object reference points
   this.m_base_x=base_x;                     // Base object X coordinate
   this.m_base_y=base_y;                     // Base object Y coordinate
   this.SetControlFormSize(ctrl_form_size);  // Size of forms for managing reference points
   
   if(this.m_base_type==OBJ_LABEL            || this.m_base_type==OBJ_BUTTON  ||
      this.m_base_type==OBJ_BITMAP_LABEL     || this.m_base_type==OBJ_EDIT    ||
      this.m_base_type==OBJ_RECTANGLE_LABEL  || this.m_base_type==OBJ_CHART)
      return;
   
   if(::ArraySize(base_time)==0)
     {
      CMessage::ToLog(DFUN+"base_time: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArraySize(base_price)==0)
     {
      CMessage::ToLog(DFUN+"base_price: ",MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY);
      return;
     }
   if(::ArrayResize(this.m_base_time,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      return;
     }
   if(::ArrayResize(this.m_base_price,this.m_base_pivots)!=this.m_base_pivots)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      return;
     }
   for(int i=0;i<this.m_base_pivots;i++)
     {
      this.m_base_time[i]=base_time[i];      // Time (i) of the base object pivot point
      this.m_base_price[i]=base_price[i];    // Price (i) of the base object pivot point
     }
  }
//+------------------------------------------------------------------+

Transmitimos al método todos los valores de las propiedades del objeto gráfico básico necesarios para que la clase funcione. A continuación, verificamos el tipo del objeto básico y, si es un objeto que no se ha construido según las coordenadas de precio/tiempo, salimos del método: por el momento, no vamos a trabajar con tales objetos, simplemente no los procesaremos. A continuación, comprobamos los tamaños de las matrices con las coordenadas del objeto básico que se han transmitido al método y, si son de tamaño cero (se ha transmitido una matriz vacía al método), informaremos sobre ello y saldremos del método. Luego cambiamos el tamaño de las matrices internas de coordenadas según las coordenadas transmitidas, y si el cambio de tamaño no ha funcionado, informaremos de ello y saldremos. Finalmente, copiamos las matrices de entrada elemento por elemento en las matrices internas.

Método que asigna a los puntos de pivote el tamaño de los puntos de referencia de control:

//+------------------------------------------------------------------+
//|Set the size of reference points for managing pivot points        |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetControlFormSize(const int size)
  {
   this.m_ctrl_form_size=(size>254 ? 255 : size<5 ? 5 : size%2==0 ? size+1 : size);
   this.m_shift=(int)ceil(m_ctrl_form_size/2)+1;
  }
//+------------------------------------------------------------------+

Transmitimos al método el tamaño de formulario requerido. Si el tamaño es superior a 254, lo estableceremos en 255 (valor impar); si el tamaño transmitido es inferior a 5, lo estableceremos en 5 (este será el valor mínimo del tamaño del formulario). De lo contrario, si el tamaño transmitido es múltiplo de dos, añaderemos uno y lo utilizaremos; si ninguno de los valores verificados es verdadero, usaremos el tamaño transmitido al método.
A continuación, calcularemos el valor del cambio de coordenadas (porque el formulario debe encontrarse de forma que el punto de pivote del objeto gráfico esté exactamente en su centro, y para ello deberemos restar el valor del desplazamiento al valor de la coordenada). Dividimos por dos el tamaño del formulario calculado, tomamos el número entero más próximo por arriba y añadimos uno.

Método que establece la coordenada de tiempo del objeto básico:

//+------------------------------------------------------------------+
//| Set the time coordinate of the base object                       |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTime(const datetime time,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
  }
//+------------------------------------------------------------------+

Transmitimos al método el valor de tiempo del punto de pivote y el índice del punto de pivote del objeto. Si el índice es mayor que el número de puntos de pivote en el objeto, informaremos sobre la solicitud fuera de los límites y saldremos. Como resultado, en la matriz de tiempo, en la celda correspondiente al índice, introduciremos el valor de tiempo transmitido al método.
El método es necesario para señalar el objeto de clase de tiempo del objeto básico cuando cambia.

Método que establece la coordenada de precio del objeto básico:

//+------------------------------------------------------------------+
//| Set the coordinate of the base object price                      |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjPrice(const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

El método es idéntico al analizado anteriormente, salvo que aquí introducimos en la matriz de precios de la clase el precio indicado por el índice del punto de pivote del objeto básico.

Método que establece las coordenadas de tiempo y precio del objeto subordinado:

//+------------------------------------------------------------------+
//| Set the time and price coordinates of the base object            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::SetBaseObjTimePrice(const datetime time,const double price,const int index)
  {
   if(index>this.m_base_pivots-1)
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY);
      return;
     }
   this.m_base_time[index]=time;
   this.m_base_price[index]=price;
  }
//+------------------------------------------------------------------+

El método es idéntico a los dos anteriores, pero establece tanto el precio como el tiempo en las matrices de clase.

Método que retorna las coordenadas X e Y del punto de pivote indicado del objeto gráfico en coordenadas de pantalla:

//+------------------------------------------------------------------+
//| Return the X and Y coordinates of the specified pivot point      |
//| of the graphical object in screen coordinates                    |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y)
  {
   switch(this.m_base_type)
     {
      case OBJ_LABEL             :
      case OBJ_BUTTON            :
      case OBJ_BITMAP_LABEL      :
      case OBJ_EDIT              :
      case OBJ_RECTANGLE_LABEL   :
      case OBJ_CHART             :
        x=this.m_base_x;
        y=this.m_base_y;
        break;
      default:
        if(!::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y))
          {
           x=0;
           y=0;
           return false;
          }
     }
   return true;
  }
//+------------------------------------------------------------------+

Transmitimos al método el índice del punto de pivote necesario del objeto gráfico básico para el que deseamos obtener las coordenadas de pantalla (en píxeles desde la esquina superior izquierda de la pantalla), así como las dos variables por enlace en las que se escribirán las coordinadas de pantalla del formulario. Si el objeto ya está ubicado en las coordenadas de la pantalla, también las retornaremos.
Si el objeto está ubicado en las coordenadas de precio/tiempo, entonces las calcularemos usando la función ChartTimePriceToXY() y, si no ha sido posible convertir las coordenadas a coordenadas de pantalla, estableceremos las coordenadas en cero y retornaremos false.
Como resultado, retornaremos true
.

Método que retorna un puntero al formulario del punto de pivote según el nombre:

//+------------------------------------------------------------------+
//| Return the pointer to the pivot point form by name               |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::GetControlPointForm(const string name,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_forms.Total();i++)
     {
      CForm *form=this.m_list_forms.At(i);
      if(form==NULL)
         continue;
      if(form.Name()==name)
        {
         index=i;
         return form;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

Transmitimos al método el nombre del formulario requerido y una variable por enlace, en la cual escribiremos el índice del formulario encontrado en la lista de objetos de formulario.
En un ciclo a través de la lista de objetos de formulario, obtenemos el siguiente objeto y, si su nombre coincide con el que estamos buscando, escribiremos el índice del ciclo en una variable y retornaremos el puntero al objeto encontrado. Al final del ciclo, retornaremos NULL : no se ha encontrado el formulario, y el índice tendrá el valor -1 escrito antes de que comenzara el ciclo.

Método que crea un objeto de formulario en el punto de pivote del objeto básico:

//+------------------------------------------------------------------+
//| Create a form object on a base object reference point            |
//+------------------------------------------------------------------+
CForm *CGStdGraphObjExtToolkit::CreateNewControlPointForm(const int index)
  {
   string name=this.m_base_name+"_TKPP_"+(index<this.m_base_pivots ? (string)index : "X");
   CForm *form=this.GetControlPointForm(index);
   if(form!=NULL)
      return NULL;
   int x=0, y=0;
   if(!this.GetControlPointCoordXY(index,x,y))
      return NULL;
   return new CForm(this.m_base_chart_id,this.m_base_subwindow,name,x-this.m_shift,y-this.m_shift,this.GetControlFormSize(),this.GetControlFormSize());
  }
//+------------------------------------------------------------------+

Transmitimos al método el índice del punto de pivote en el que debemos crear el objeto de formulario.
A continuación, creamos un nombre para el objeto de formulario que conste del nombre del objeto básico + la abreviatura de "ToolKit Pivot Point" (_TKPP) + el índice del punto de pivote. Al crear una descripción del índice, comprobaremos su valor y, si es menor que el número de puntos de pivote del objeto básico (el conteo comienza desde cero), usaremos la representación de línea del índice transmitido al método. De lo contrario, utilizaremos el icono "X". ¿Para qué necesitamos esto? En el futuro, podremos fijar objetos subordinados al objeto básico no solo en sus puntos de pivote, sino también entre estos. Además, para desplazar el objeto completo, necesitaremos crear un formulario de control en el centro de la línea del objeto básico más allá de la cual moveremos el objeto completo. Por consiguiente, en el nombre del formulario, deberemos prever de inmediato la posibilidad de crear un formulario no solo para los puntos de pivote, sino también para otros puntos.
A continuación, verificamos la presencia de un formulario en la lista según el índice transmitido al método, y si ya hay un objeto de formulario en este índice en la lista (el puntero a él no es igual a NULL), retornaremos NULL.
Luego transformamos las coordenadas del punto de pivote según su índice en coordenadas de pantalla y retornamos el resultado de la creación del objeto de formulario en las coordenadas obtenidas. En este caso, restaremos a ambas coordenadas el valor de su desplazamiento, para así posicionar de forma precisa el centro del formulario en el punto de pivote.
Podríamos haber configurado el punto de anclaje del formulario para que esté centrado, pero no debemos olvidar que habíamos acordado en la biblioteca que el punto de anclaje de todos los formularios sería el mismo, en la parte superior izquierda del formulario. Por ello, donde sea necesario, usaremos el desplazamiento para colocar los objetos de formulario.

Método que crea objetos de formulario en los puntos de pivote del objeto básico:

//+------------------------------------------------------------------+
//| Create form objects on the base object pivot points              |
//+------------------------------------------------------------------+
bool CGStdGraphObjExtToolkit::CreateAllControlPointForm(void)
  {
   bool res=true;
   //--- In the loop by the number of base object pivot points
   for(int i=0;i<this.m_base_pivots;i++)
     {
      //--- Create a new form object on the current pivot point corresponding to the loop index
      CForm *form=this.CreateNewControlPointForm(i);
      //--- If failed to create the form, inform of that and add 'false' to the final result
      if(form==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM);
         res &=false;
        }
      //--- If failed to add the form to the list, inform of that, remove the created form and add 'false' to the final result
      if(!this.m_list_forms.Add(form))
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         delete form;
         res &=false;
        }
      //--- Set all the necessary properties for the created form object
      form.SetBelong(GRAPH_OBJ_BELONG_PROGRAM); // Object is created programmatically
      form.SetActive(true);                     // Form object is active
      form.SetMovable(true);                    // Movable object
      form.SetActiveAreaShift(0,0,0,0);         // Object active area - the entire form
      form.SetFlagSelected(false,false);        // Object is not selected
      form.SetFlagSelectable(false,false);      // Object cannot be selected by mouse
      form.Erase(CLR_CANV_NULL,0);              // Fill in the form with transparent color and set the full transparency
      //form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,clrSilver); // Draw an outlining rectangle for visual display of the form location
      form.DrawCircle((int)floor(form.Width()/2),(int)floor(form.Height()/2),CTRL_POINT_SIZE,clrDodgerBlue);   // Draw a circle in the form center
      form.DrawCircleFill((int)floor(form.Width()/2),(int)floor(form.Height()/2),2,clrDodgerBlue);             // Draw a point in the form center
      form.Done();                              // Save the initial form object state (its appearance)
     }
   //--- Redraw the chart for displaying changes (if successful) and return the final result
   if(res)
      ::ChartRedraw(this.m_base_chart_id);
   return res;
  }
//+------------------------------------------------------------------+

La lógica completa está descrita en los comentarios al código. En resumen: en un ciclo por el número de puntos de pivote del objeto básico, creamos un nuevo objeto de formulario para cada punto de pivote, lo añadimos a la lista de objetos de formulario y asignamos a cada formulario las coordenadas del punto de pivote correspondiente del objeto básico. Cada formulario dibuja un círculo en el centro y un punto para indicar que es un objeto de control del punto de pivote del objeto básico.

Como estaba previsto, dichos objetos serán inicialmente invisibles y aparecerán solo cuando el cursor del ratón entre en el área del formulario. Pero por ahora, los haremos visibles, para poner a prueba su comportamiento cuando cambie el gráfico. Ya en próximos artículos, los haremos según lo previsto: ocultos de inicio y posteriormente visibles cuando el cursor entre en su área activa, que tendrá el tamaño completo del objeto de formulario.

Método que elimina todos los objetos de formulario de la lista:

//+------------------------------------------------------------------+
//| Remove all form objects from the list                            |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::DeleteAllControlPointForm(void)
  {
   this.m_list_forms.Clear();
  }
//+------------------------------------------------------------------+

Simplemente usaremos el método Clear(), que borra totalmente la lista completa.

En el manejador de eventos, procesaremos los eventos de los objetos de formulario según el evento ocurrido:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObjExtToolkit::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      for(int i=0;i<this.m_list_forms.Total();i++)
        {
         CForm *form=this.m_list_forms.At(i);
         if(form==NULL)
            continue;
         int x=0, y=0;
         if(!this.GetControlPointCoordXY(i,x,y))
            continue;
         form.SetCoordX(x-this.m_shift);
         form.SetCoordY(y-this.m_shift);
         form.Update();
        }
      ::ChartRedraw(this.m_base_chart_id);
     }
  }
//+------------------------------------------------------------------+

Por el momento, solo estamos procesando el evento de cambio del gráfico. En un ciclo a través de todos los objetos de formulario, obtenemos el siguiente formulario de la lista, y si no ha sido posible obtener sus coordenadas de pantalla según el punto de pivote en el que se dibuja, pasaremos al siguiente formulario. Establecemos en el formulario las nuevas coordenadas de pantalla y actualizamos el formulario. Al final del ciclo, redibujamos el gráfico para mostrar los cambios.

Como el objeto de instrumental del objeto gráfico estándar extendido se almacenará en el objeto de clase del objeto gráfico estándar, deberemos modificar esta clase en el archivo \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.

En primer lugar, incluiremos en el archivo los archivos de las clases del objeto de formulario y el objeto de instrumental recién creado del objeto gráfico estándar extendido:

//+------------------------------------------------------------------+
//|                                                 GStdGraphObj.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"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\GBaseObj.mqh"
#include "..\..\..\Services\Properties.mqh"
#include "..\..\Graph\Form.mqh"
#include "..\..\Graph\Extend\CGStdGraphObjExtToolkit.mqh"
//+------------------------------------------------------------------+
//| Class of the dependent object pivot point data                   |
//+------------------------------------------------------------------+

En la clase del objeto gráfico estándar abstracto, en su sección privada, declaramos un puntero al objeto de instrumental del objeto gráfico estándar extendido:

//+------------------------------------------------------------------+
//| The class of the abstract standard graphical object              |
//+------------------------------------------------------------------+
class CGStdGraphObj : public CGBaseObj
  {
private:
   CArrayObj         m_list;                                            // List of subordinate graphical objects
   CProperties      *Prop;                                              // Pointer to the property object
   CLinkedPivotPoint m_linked_pivots;                                   // Linked pivot points
   CGStdGraphObjExtToolkit *ExtToolkit;                                 // Pointer to the extended graphical object toolkit
   int               m_pivots;                                          // Number of object reference points
//--- Read and set (1) the time and (2) the price of the specified object pivot point
   void              SetTimePivot(const int index);
   void              SetPricePivot(const int index);
//--- Read and set (1) color, (2) style, (3) width, (4) value, (5) text of the specified object level
   void              SetLevelColor(const int index);
   void              SetLevelStyle(const int index);
   void              SetLevelWidth(const int index);
   void              SetLevelValue(const int index);
   void              SetLevelText(const int index);
//--- Read and set the BMP file name for the "Bitmap Level" object. Index: 0 - ON, 1 - OFF
   void              SetBMPFile(const int index);

public:

En la sección pública, escribimos un método que retorna el puntero al objeto de instrumental:

public:
//--- Set object's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value)     { this.Prop.Curr.SetLong(property,index,value);    }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value)    { this.Prop.Curr.SetDouble(property,index,value);  }
   void              SetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value)    { this.Prop.Curr.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the properties array
   long              GetProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)          const { return this.Prop.Curr.GetLong(property,index);   }
   double            GetProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)           const { return this.Prop.Curr.GetDouble(property,index); }
   string            GetProperty(ENUM_GRAPH_OBJ_PROP_STRING property,int index)           const { return this.Prop.Curr.GetString(property,index); }
//--- Set object's previous (1) integer, (2) real and (3) string properties
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index,long value) { this.Prop.Prev.SetLong(property,index,value);    }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index,double value){ this.Prop.Prev.SetDouble(property,index,value);  }
   void              SetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index,string value){ this.Prop.Prev.SetString(property,index,value);  }
//--- Return object’s (1) integer, (2) real and (3) string property from the previous properties array
   long              GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_INTEGER property,int index)      const { return this.Prop.Prev.GetLong(property,index);   }
   double            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_DOUBLE property,int index)       const { return this.Prop.Prev.GetDouble(property,index); }
   string            GetPropertyPrev(ENUM_GRAPH_OBJ_PROP_STRING property,int index)       const { return this.Prop.Prev.GetString(property,index); }
   
//--- Return (1) itself, (2) properties and (3) the change history
   CGStdGraphObj    *GetObject(void)                                       { return &this;            }
   CProperties      *Properties(void)                                      { return this.Prop;        }
   CChangeHistory   *History(void)                                         { return this.Prop.History;}
   CGStdGraphObjExtToolkit *GetExtToolkit(void)                            { return this.ExtToolkit;  }
//--- Return the flag of the object supporting this property
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property)  { return true;             }
   virtual bool      SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property)  { return true;             }

En la sección pública de la clase, declararemos el manejador de eventos del objeto gráfico; en el constructor predeterminado, estableceremos el puntero al objeto de instrumental como NULL, y en el destructor de la clase, verificaremos la validez del puntero. Después, eliminaremos todos los formularios del objeto de instrumental, y luego el propio objeto:

private:
//--- Set the X coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the Y coordinate (1) from the specified property of the base object to the specified subordinate object, (2) from the base object
   void              SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to);
   void              SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to);
//--- Set the (1) integer, (2) real and (3) string property to the specified subordinate property
   void              SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier);
   void              SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier);
   void              SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier);

public:
//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Default constructor
                     CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; this.ExtToolkit=NULL; }
//--- Destructor
                    ~CGStdGraphObj()
                       {
                        if(this.Prop!=NULL)
                           delete this.Prop;
                        if(this.ExtToolkit!=NULL)
                          {
                           this.ExtToolkit.DeleteAllControlPointForm();
                           delete this.ExtToolkit;
                          }
                       }
protected:
//--- Protected parametric constructor
                     CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                                   const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                                   const ENUM_GRAPH_OBJ_BELONG belong,
                                   const ENUM_GRAPH_OBJ_SPECIES species,
                                   const long chart_id, const int pivots,
                                   const string name);
                     
public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+

En el bloque de métodos para el acceso simplificado y la configuración de las propiedades del objeto gráfico, escribimos el método que retorna el número de puntos de pivote del objeto gráfico:

public:
//+--------------------------------------------------------------------+ 
//|Methods of simplified access and setting graphical object properties|
//+--------------------------------------------------------------------+
//--- Number of object reference points
   int               Pivots(void)                  const { return this.m_pivots;                                                          }
//--- Object index in the list
   int               Number(void)                  const { return (int)this.GetProperty(GRAPH_OBJ_PROP_NUM,0);                            }
   void              SetNumber(const int number)         { this.SetProperty(GRAPH_OBJ_PROP_NUM,0,number);                                 }


En el constructor paramétrico protegido, comprobamos el tipo del elemento gráfico y, si es un objeto gráfico extendido, crearemos un nuevo objeto de instrumental y almacenaremos el puntero a este en la variable ExtToolkit. Al final del listado del constructor, inicializamos el objeto de instrumental:

//+------------------------------------------------------------------+
//| Protected parametric constructor                                 |
//+------------------------------------------------------------------+
CGStdGraphObj::CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type,
                             const ENUM_GRAPH_ELEMENT_TYPE elm_type,
                             const ENUM_GRAPH_OBJ_BELONG belong,
                             const ENUM_GRAPH_OBJ_SPECIES species,
                             const long chart_id,const int pivots,
                             const string name)
  {
//--- Create the property object with the default values
   this.Prop=new CProperties(GRAPH_OBJ_PROP_INTEGER_TOTAL,GRAPH_OBJ_PROP_DOUBLE_TOTAL,GRAPH_OBJ_PROP_STRING_TOTAL);
   this.ExtToolkit=(elm_type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? new CGStdGraphObjExtToolkit() : NULL);
//--- Set the number of pivot points and object levels
   this.m_pivots=pivots;
   int levels=(int)::ObjectGetInteger(chart_id,name,OBJPROP_LEVELS);

//--- Set the property array dimensionalities according to the number of pivot points and levels
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_TIME,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_PRICE,this.m_pivots);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,levels);
   this.Prop.SetSizeRange(GRAPH_OBJ_PROP_BMPFILE,2);
   
//--- Set the object (1) type, type of graphical (2) object, (3) element, (4) subwindow affiliation and (5) index, as well as (6) chart symbol Digits
   this.m_type=obj_type;
   this.SetName(name);
   CGBaseObj::SetChartID(chart_id);
   CGBaseObj::SetTypeGraphObject(CGBaseObj::GraphObjectType(obj_type));
   CGBaseObj::SetTypeElement(elm_type);
   CGBaseObj::SetBelong(belong);
   CGBaseObj::SetSpecies(species);
   CGBaseObj::SetSubwindow(chart_id,name);
   CGBaseObj::SetDigits((int)::SymbolInfoInteger(::ChartSymbol(chart_id),SYMBOL_DIGITS));
   
//--- Save the integer properties inherent in all graphical objects but not present in the current one
   this.SetProperty(GRAPH_OBJ_PROP_CHART_ID,0,CGBaseObj::ChartID());                // Chart ID
   this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,0,CGBaseObj::SubWindow());               // Chart subwindow index
   this.SetProperty(GRAPH_OBJ_PROP_TYPE,0,CGBaseObj::TypeGraphObject());            // Graphical object type (ENUM_OBJECT)
   this.SetProperty(GRAPH_OBJ_PROP_ELEMENT_TYPE,0,CGBaseObj::TypeGraphElement());   // Graphical element type (ENUM_GRAPH_ELEMENT_TYPE)
   this.SetProperty(GRAPH_OBJ_PROP_BELONG,0,CGBaseObj::Belong());                   // Graphical object affiliation
   this.SetProperty(GRAPH_OBJ_PROP_SPECIES,0,CGBaseObj::Species());                 // Graphical object species
   this.SetProperty(GRAPH_OBJ_PROP_GROUP,0,0);                                      // Graphical object group
   this.SetProperty(GRAPH_OBJ_PROP_ID,0,0);                                         // Object ID
   this.SetProperty(GRAPH_OBJ_PROP_BASE_ID,0,0);                                    // Base object ID
   this.SetProperty(GRAPH_OBJ_PROP_NUM,0,0);                                        // Object index in the list
   this.SetProperty(GRAPH_OBJ_PROP_CHANGE_HISTORY,0,false);                         // Flag of storing the change history
   this.SetProperty(GRAPH_OBJ_PROP_BASE_NAME,0,this.Name());                        // Base object name
   
//--- Save the properties inherent in all graphical objects and present in a graphical object
   this.PropertiesRefresh();
   
//--- Save basic properties in the parent object
   this.m_create_time=(datetime)this.GetProperty(GRAPH_OBJ_PROP_CREATETIME,0);
   this.m_back=(bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0);
   this.m_selected=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,0);
   this.m_selectable=(bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,0);
   this.m_hidden=(bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0);

//--- Initialize the extended graphical object toolkit
   if(this.GraphElementType()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      datetime times[];
      double prices[];
      if(::ArrayResize(times,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA);
      if(::ArrayResize(prices,this.Pivots())!=this.Pivots())
         CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA);
      for(int i=0;i<this.Pivots();i++)
        {
         times[i]=this.Time(i);
         prices[i]=this.Price(i);
        }
      this.ExtToolkit.SetBaseObj(this.TypeGraphObject(),this.Name(),this.ChartID(),this.SubWindow(),this.Pivots(),CTRL_FORM_SIZE,this.XDistance(),this.YDistance(),times,prices);
      this.ExtToolkit.CreateAllControlPointForm();
      this.SetFlagSelected(false,false);
      this.SetFlagSelectable(false,false);
     }

//--- Save the current properties to the previous ones
   this.PropertiesCopyToPrevData();
  }
//+-------------------------------------------------------------------+

Al inicializar el objeto de instrumental, primero declaramos las matrices de las propiedades de tiempo y precio; luego cambiamos sus tamaños según el número de puntos de pivote del objeto gráfico y, en un ciclo, escribimos en estas propiedades los valores de precio y tiempo de los puntos de pivote del objeto correspondientes al índice del ciclo.
A continuación, llamamos al método de inicialización del objeto de instrumental y le transmitimos los parámetros necesarios del objeto gráfico y las matrices recién rellenadas de las propiedades de precio y tiempo. Después de la inicialización, llamamos al método para crear los objetos de formulario en los puntos de pivote del objeto gráfico y, al final, establecemos para el objeto gráfico el estado de objeto no seleccionado y prohibimos que se seleccione con el ratón.

En el método que comprueba los cambios en las propiedades del objeto, en el bloque de código que procesa el objeto gráfico estándar extendido, escribimos un bloque de código para desplazar los puntos de pivote (objetos de formulario) a las nuevas coordenadas de pantalla al cambiar la ubicación de los puntos de pivote del objeto gráfico estándar extendido:

//+------------------------------------------------------------------+
//| Check object property changes                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::PropertiesCheckChanged(void)
  {
   CGBaseObj::ClearEventsList();
   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;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   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;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }

   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;
      for(int j=0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME)
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   if(changed)
     {
      for(int i=0;i<this.m_list_events.Total();i++)
        {
         CGBaseEvent *event=this.m_list_events.At(i);
         if(event==NULL)
            continue;
         ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
        }
      if(this.AllowChangeHistory())
        {
         int total=HistoryChangesTotal();
         if(this.CreateNewChangeHistoryObj(total<1))
            ::Print
              (
               DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total),
               ": ",this.HistoryChangedObjTimeChangedToString(total-1)
              );
        }
      //--- If subordinate objects are attached to the base one (in a composite graphical object)
      if(this.m_list.Total()>0)
        {
         //--- In the loop by the number of added graphical objects,
         for(int i=0;i<this.m_list.Total();i++)
           {
            //--- get the next graphical object,
            CGStdGraphObj *dep=m_list.At(i);
            if(dep==NULL)
               continue;
            //--- get the data object of its pivot points,
            CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint();
            if(pp==NULL)
               continue;
            //--- get the number of coordinate points the object is attached to
            int num=pp.GetNumLinkedCoords();
            //--- In the loop by the object coordinate points,
            for(int j=0;j<num;j++)
              {
               //--- get the number of coordinate points of the base object for setting the X coordinate
               int numx=pp.GetBasePivotsNumX(j);
               //--- In the loop by each coordinate point for setting the X coordinate,
               for(int nx=0;nx<numx;nx++)
                 {
                  //--- get the property for setting the X coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyX(j,nx);
                  int modifier_from=pp.GetPropertyModifierX(j,nx);
                  this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx);
                 }
               //--- Get the number of coordinate points of the base object for setting the Y coordinate
               int numy=pp.GetBasePivotsNumY(j);
               //--- In the loop by each coordinate point for setting the Y coordinate,
               for(int ny=0;ny<numy;ny++)
                 {
                  //--- get the property for setting the Y coordinate, its modifier
                  //--- and set it in the object selected as the current one in the main loop
                  int prop_from=pp.GetPropertyY(j,ny);
                  int modifier_from=pp.GetPropertyModifierY(j,ny);
                  this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny);
                 }
              }
            dep.PropertiesCopyToPrevData();
           }
         //--- Move reference control points to new coordinates
         if(ExtToolkit!=NULL)
           {
            for(int i=0;i<this.Pivots();i++)
              {
               ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
              }
            ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
            long   lparam=0;
            double dparam=0;
            string sparam="";
            ExtToolkit.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
         //--- Upon completion of the loop of handling all bound objects, redraw the chart to display all the changes
         ::ChartRedraw(m_chart_id);
        }
      //--- Save the current properties as the previous ones
      this.PropertiesCopyToPrevData();
     }
  }
//+------------------------------------------------------------------+

Si movemos uno de los puntos de pivote de un objeto gráfico, o el objeto completo, las coordenadas de pantalla de sus puntos de pivote cambiarán. En consecuencia, necesitaremos desplazar también a las nuevas coordenadas de pantalla los objetos de formulario de la clase de instrumental para que encajen en su lugar. Por lo tanto, aquí primero transmitimos las nuevas coordenadas del objeto gráfico al objeto de instrumental (en un ciclo por el número de puntos de pivote para las coordenadas de precio/tiempo, y las coordenadas aparte en píxeles), y luego llamamos al manejador de eventos del objeto de instrumental, transmitiéndole el identificador del evento de cambio del gráfico. Esto hará que el manejador de eventos del objeto de instrumental recalcule las coordenadas de pantalla de todos los formularios y los desplace a la nueva ubicación según las nuevas coordenadas de precio y tiempo del objeto gráfico.

En el método que añade un objeto gráfico estándar subordinado a la lista, corregimos un error: el objeto gráfico subordinado añadido cambia sus propiedades, por lo tanto, las nuevas propiedades deben corregirse inmediatamente como las anteriores, para que no se generen nuevos eventos de cambio de estos objetos gráficos al clicar en él:

//+------------------------------------------------------------------+
//| Add a subordinate standard graphical object to the list          |
//+------------------------------------------------------------------+
bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj)
  {
   //--- If the current object is not an extended one, inform of that and return 'false'
   if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
     {
      CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ);
      return false;
     }
   //--- If failed to add the pointer to the passed object into the list, inform of that and return 'false'
   if(!this.m_list.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST);
      return false;
     }
   //--- Object added to the list - set its number in the list,
   //--- name and ID of the current object as the base one,
   //--- set the flags of object availability and selection
   //--- and the graphical element type - standard extended graphical object
   obj.SetNumber(this.m_list.Total()-1);
   obj.SetBaseName(this.Name());
   obj.SetBaseObjectID(this.ObjectID());
   obj.SetFlagSelected(false,false);
   obj.SetFlagSelectable(false,false);
   obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED);
   obj.PropertiesCopyToPrevData();
   return true;
  }
//+------------------------------------------------------------------+


Manejador de eventos del objeto gráfico estándar abstracto:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CGStdGraphObj::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(GraphElementType()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)
      return;
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      if(ExtToolkit==NULL)
         return;
      for(int i=0;i<this.Pivots();i++)
        {
         ExtToolkit.SetBaseObjTimePrice(this.Time(i),this.Price(i),i);
        }
      ExtToolkit.SetBaseObjCoordXY(this.XDistance(),this.YDistance());
      ExtToolkit.OnChartEvent(id,lparam,dparam,sparam);
     }
  }
//+------------------------------------------------------------------+

Hasta ahora, el manejador procesa solo el evento de cambio del gráfico.

Si el objeto no es extendido, salimos del manejador. Si se detecta un evento de cambio del gráfico, comprobamos la validez del puntero al objeto de instrumental del objeto gráfico estándar extendido. Si no se ha creado el instrumental, salimos. A continuación, en un ciclo por el número de puntos de pivote del objeto gráfico, establecemos las nuevas coordenadas de precio/tiempo del objeto gráfico en el objeto de instrumental. A continuación, establecemos sus nuevas coordenadas de pantalla y llamamos al manejador de eventos del objeto de instrumental, en el cual, al ocurrir un evento de cambio del gráfico, todos los formularios se establecen en las nuevas coordenadas de pantalla calculadas a partir de las nuevas coordenadas de percio/tiempo que se acaban de transmitir al objeto de instrumental.


Al eliminar un objeto gráfico estándar extendido de un gráfico, deberemos eliminar los objetos de formulario de su objeto de instrumental del gráfico, si dicho objeto se ha creado para el objeto gráfico. La eliminación de objetos gráficos de un gráfico la gestionaremos en la clase de colección de elementos gráficos, en el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

En el método que procesa la eliminación de objetos gráficos extendidos, escribimos un bloque de código para eliminar todos los objetos de formulario del objeto de instrumental:

//+------------------------------------------------------------------+
//| Handle the removal of extended graphical objects                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj)
  {
   if(obj==NULL)
      return;
   //--- Save the ID of the graphical object chart and the number of subordinate objects in its list
   long chart_id=obj.ChartID();
   int total=obj.GetNumDependentObj();
   //--- If the list of subordinate objects is not empty (this is the base object)
   if(total>0)
     {
      CGStdGraphObjExtToolkit *toolkit=obj.GetExtToolkit();
      if(toolkit!=NULL)
        {
         toolkit.DeleteAllControlPointForm();
        }
      //--- In the loop, move along all dependent objects and remove them
      for(int n=total-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=obj.GetDependentObj(n);
         if(dep==NULL)
            continue;
         //--- If failed to remove it from the chart, display the appropriate message in the journal
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
        }
      //--- Upon the loop completion, update the chart to display the changes and exit the method
      ::ChartRedraw(chart_id);
      return;
     }
   //--- If this is a subordinate object
   else if(obj.BaseObjectID()>0)
     {
      //--- Get the base object name and its ID
      string base_name=obj.BaseName();
      long base_id=obj.BaseObjectID();
      //--- Get the base object from the graphical object collection list
      CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id);
      if(base==NULL)
         return;
      //--- get the number of dependent objects in its list
      int count=base.GetNumDependentObj();
      //--- In the loop, move along all its dependent objects and remove them
      for(int n=count-1;n>WRONG_VALUE;n--)
        {
         //--- Get the next graphical object
         CGStdGraphObj *dep=base.GetDependentObj(n);
         //--- If failed to get the pointer or the object has already been removed from the chart, move on to the next one
         if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name()))
            continue;
         //--- If failed to delete the graphical object from the chart,
         //--- display the appropriate message in the journal and move on to the next one
         if(!::ObjectDelete(dep.ChartID(),dep.Name()))
           {
            CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
            continue;
           }
        }
      //--- Remove the base object from the chart and from the list
      if(!::ObjectDelete(base.ChartID(),base.Name()))
         CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART);
     }
   //--- Update the chart for displaying the changes
   ::ChartRedraw(chart_id);
  }
//+------------------------------------------------------------------+

Aquí, todo es sencillo: obtenemos del objeto el puntero a su objeto de instrumental del objeto gráfico extendido y, si el puntero es válido, llamamos al método para eliminar todos los formularios creados del objeto de instrumental del objeto gráfico estándar extendido que hemos analizado anteriormente.

En el manejador de eventos de la clase de colección de elementos gráficos, añadimos el procesamiento del cambio del gráfico para los objetos gráficos estándar extendidos:

//+------------------------------------------------------------------+
//| 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    || id==CHARTEVENT_OBJECT_CLICK   ||
      idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG   || idx==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Calculate the chart ID
      //--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
      //--- If the event ID corresponds to a user event, the chart ID is received from lparam
      //--- Otherwise, the chart ID is assigned to -1
      long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
      long chart_id=(param==WRONG_VALUE ? (lparam==0 ? ::ChartID() : lparam) : param);
      //--- Get the object, whose properties were changed or which was relocated,
      //--- from the collection list by its name set in sparam
      obj=this.GetStdGraphObject(sparam,chart_id);
      //--- If failed to get the object by its name, it is not on the list,
      //--- which means its name has been changed
      if(obj==NULL)
        {
         //--- Let's search the list for the object that is not on the chart
         obj=this.FindMissingObj(chart_id);
         //--- If failed to find the object here as well, exit
         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,
         //--- and send an event with the new name of the object to the control program chart
         if(obj.SetNamePrev(obj.Name()) && obj.SetName(name_new))
            ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),obj.TimeCreate(),obj.Name());
        }
      //--- Update the properties of the obtained object
      //--- and check their change
      obj.PropertiesRefresh();
      obj.PropertiesCheckChanged();
     }
//--- Handle chart changes for extended standard objects
   if(id==CHARTEVENT_CHART_CHANGE || idx==CHARTEVENT_CHART_CHANGE)
     {
      CArrayObj *list=this.GetListStdGraphObjectExt();
      if(list!=NULL)
        {
         for(int i=0;i<list.Total();i++)
           {
            obj=list.At(i);
            if(obj==NULL)
               continue;
            obj.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, si se ha registrado un evento de cambio del gráfico, obtenemos una lista con todos los objetos gráficos estándar extendidos, y en un ciclo por su número, obtenemos el siguiente objeto y llamamos a su manejador de eventos, transmitiendo a este el valor de evento "Gráfico cambiado".


Simulación

Para la prueba, tomaremos el asesor del artículo anteriory lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\ Part95\ con el nuevo nombre TestDoEasyPart95.mq5.

Lo único que ha cambiado son los nombres ligeramente distintos de los objetos de etiqueta de precio fijados al objeto de línea de tendencia básico. Simplemente, en el manejador de eventos del asesor experto, en el bloque de creación de objetos gráficos compuestos, hemos añadido el texto "Ext" a los nombres de los objetos subordinados para que los nombres se correspondan con el tipo de objetos gráficos extendidos:

   if(id==CHARTEVENT_CLICK)
     {
      if(!IsCtrlKeyPressed())
         return;
      //--- Get the chart click coordinates
      datetime time=0;
      double price=0;
      int sw=0;
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price))
        {
         //--- Get the right point coordinates for a trend line
         datetime time2=iTime(Symbol(),PERIOD_CURRENT,1);
         double price2=iOpen(Symbol(),PERIOD_CURRENT,1);
         
         //--- Create the "Trend line" object
         string name_base="TrendLineExt";
         engine.CreateLineTrend(name_base,0,true,time,price,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and pass its properties to the journal
         CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID());
         
         //--- Create the "Left price label" object
         string name_dep="PriceLeftExt";
         engine.CreatePriceLabelLeft(name_dep,0,false,time,price);
         //--- Get the object from the list of graphical objects by chart name and ID and
         CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line left point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0);
         
         //--- Create the "Right price label" object
         name_dep="PriceRightExt";
         engine.CreatePriceLabelRight(name_dep,0,false,time2,price2);
         //--- Get the object from the list of graphical objects by chart name and ID and
         dep=engine.GraphGetStdGraphObject(name_dep,ChartID());
         //--- add it to the list of graphical objects bound to the "Trend line" object
         obj.AddDependentObj(dep);
         //--- Set its pivot point by X and Y axis to the trend line right point
         dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1);
        }
     }
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);

¿Qué pondremos a prueba? Vamos a crear un objeto gráfico compuesto. Al crearlo, los objetos de formulario se establecerán en sus puntos de pivote.
Estos objetos de formulario tienen coordenadas en píxeles desde la esquina superior izquierda de la pantalla. En consecuencia, si desplazamos el gráfico, estas coordenadas de pantalla deberán recalcularse para que los objetos se coloquen en los puntos de pivote correspondientes del objeto gráfico, cosa que vamos a comprobar a continuación.

Compilamos el asesor y lo ejecutamos en el gráfico:


Bien, a ver qué tenemos. Vemos lo siguiente: los objetos se colocan en su lugar al cambiar el gráfico, pero se retrasan mucho.
Al eliminar un objeto gráfico, también se eliminan los objetos de formulario que le pertenecen.

¿Qué podemos hacer con semejante retraso? En principio, nunca vamos a necesitar ver su movimiento en vivo: estos formularios siempre estarán ocultos al desplazarse el gráfico (ahora se muestran para controlar la reacción al evento). Además, la propia línea del objeto gráfico se moverá cuando estos objetos de formulario sean desplazados con el ratón. Y cualquier interacción con los formularios se realizará sobre un gráfico fijo. Por lo tanto, este resultado puede ser totalmente aceptable, sobre todo teniendo en cuenta que la actualización del gráfico no se realiza en cada iteración del ciclo, sino solo al final del mismo. No obstante, para aliviar la carga, podemos controlar la finalización del cambio del gráfico, y solo entonces representar los cambios y mostrar el objeto (y solo si el cursor se encuentra en el área activa del objeto de formulario, cuando debería ser visible).

¿Qué es lo próximo?

En el próximo artículo, continuaremos trabajando con los eventos de los objetos gráficos compuestos.

Más abajo, se adjuntan todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de gráficos 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 93): Preparando la funcionalidad para crear objetos gráficos compuestos
Gráficos en la biblioteca DoEasy (Parte 94): Objetos gráficos compuestos, desplazamiento y eliminación

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

Archivos adjuntos |
MQL5.zip (4207.98 KB)
Múltiples indicadores en un gráfico (Parte 01): Entendiendo los conceptos Múltiples indicadores en un gráfico (Parte 01): Entendiendo los conceptos
Entienda cómo se puede agregar varios indicadores al mismo tiempo sin ocupar un área diferente de su gráfico. A mucha gente le gusta y se siente más segura operando cuando observa varios indicadores al mismo tiempo, por ejemplo, RSI, ESTOCÁSTICO, MACD, ADX, entre otros, y en algunos casos incluso diferentes activos que componen un índice determinado.
Matemáticas en el trading: Ratios de Sharpe y Sortino Matemáticas en el trading: Ratios de Sharpe y Sortino
El rendimiento es la métrica más obvia usada por los inversores y los tráders principiantes a la hora de analizar la efectividad del comercio. Los tráders profesionales utilizan herramientas más fiables para el análisis de estrategias, como los ratios de Sharpe y Sortino.
Múltiples indicadores en un gráfico (Parte 02): primeros experimentos Múltiples indicadores en un gráfico (Parte 02): primeros experimentos
En el artículo anterior, múltiples indicadores en un gráfico, presenté los conceptos y fundamentos para que podamos utilizar múltiples indicadores en un gráfico. Aquí presentaré y desglosaré el código fuente.
Cómo desarrollar sistemas basados ​​en medias móviles Cómo desarrollar sistemas basados ​​en medias móviles
En este artículo, aprenderemos cómo desarrollar varios sistemas basados ​​en estrategias que usan medias móviles.