Creación de indicadores personalizados usando la clase CCanvas

Alexander Fedosov | 12 julio, 2017


Contenido

Introducción

Los indicadores personalizados se han convertido en una parte inseparable del trading moderno en el terminal MetaTrader 5. Se usan tanto en los sistemas comerciales automáticos (como parte de los algoritmos), como en el comercio manual. Hasta ahora, al crear un indicador era posible indicar los estilos de dibujado y usar 18 tipos de construcciones gráficas. Pero las capacidades gráficas del terminal no se quedan ahí. Para crear sus propios indicadores no estandarizados con posibilidades ilimitadas de visualización, se ha creado la biblioteca de Gráficos personalizados CCanvas. El objetivo de este artículo es familiarizar a los usuarios con las posibilidades de esta biblioteca y proporcionar ejemplos de su uso, así como desarrollar una biblioteca estándar de indicadores personalizados de diversos tipos.


Construcción de la clase básica de los gráficos de usario

Como clase básica será necesario escribir un conjunto de métodos que creen la base de cualquier objeto de gráficos personalizados, y que al mismo tiempo incluya un conjunto general de propiedades. Para ello, crearemos en el <catálogo de datos>\MQL5\Include la carpeta CustomGUI, y en ella, el archivo CanvasBase.mqh. Este archivo contendrá la clase básica CCanvasBase para todos los futuros tipos de gráficos personalizados.

//+------------------------------------------------------------------+
//|                                                  CCanvasBase.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Clase básica para la creación de gráficos personalizados         |
//+------------------------------------------------------------------+
class CCanvasBase
  {
private:
   //--- Nombre del lienzo
   string            m_canvas_name;
   //--- Coordenadas del lienzo
   int               m_x;
   int               m_y;
   //--- Tamaño del lienzo
   int               m_x_size;
   int               m_y_size;
protected:
   CCanvas           m_canvas;
   //--- Crea un recurso gráfico para el objeto
   bool              CreateCanvas(void);
   //--- Elimina un recurso gráfico
   bool              DeleteCanvas(void);
public:
                     CCanvasBase(void);
                    ~CCanvasBase(void);
   //--- Establece y retorna las coordenadas
   void              X(const int x)                         { m_x=x;                      }
   void              Y(const int y)                         { m_y=y;                      }
   int               X(void)                                { return(m_x);                }
   int               Y(void)                                { return(m_y);                }
   //--- Establece y retorna el tamaño
   void              XSize(const int x_size)                { m_x_size=x_size;            }
   void              YSize(const int y_size)                { m_y_size=y_size;            }
   int               XSize(void)                            { return(m_x_size);           }
   int               YSize(void)                            { return(m_y_size);           }
   //--- Establece el nombre del indicador al crearlo
   void              Name(const string canvas_name) { m_canvas_name=canvas_name;  }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(void) : m_x(0),
                                 m_y(0),
                                 m_x_size(200),
                                 m_y_size(200)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
  }
//+------------------------------------------------------------------+
//| Crea un recurso gráfico para el objeto                           |
//+------------------------------------------------------------------+
bool CCanvasBase::CreateCanvas(void)
  {
   ObjectDelete(0,m_canvas_name);
   if(!m_canvas.CreateBitmapLabel(m_canvas_name,m_x,m_y,m_x_size,m_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
      return(false);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_ANCHOR,ANCHOR_CENTER);
   ObjectSetInteger(0,m_canvas_name,OBJPROP_BACK,false);
   return(true);
  }
//+------------------------------------------------------------------+
//| Elimina el recurso gráfico                                       |
//+------------------------------------------------------------------+
bool CCanvasBase::DeleteCanvas()
  {
   return(ObjectDelete(0,m_canvas_name)?true:false);
  }
//+------------------------------------------------------------------+

Como se puede ver, aparte de las coordenadas iniciales, el nombre y las dimensiones, entre los métodos creados hay métodos para la creación y eliminación del recurso gráfico: una especie de base o lienzo en el que se dibujarán los elementos del gráfico de usuario. En lo sucesivo, al realizarse la construcción inicial de cualquier objeto gráfico se usarán obligatoriamente los métodos CreateCanvas() y DeleteCanvas(). Por eso, las variables usadas en estos métodos deberán ser inicializadas como se ha hecho en el constructor.


Clase del indicador circular simple CCircleSimple

Bien, para comprender los principios de construcción de los gráficos personalizados, partiremos de lo sencillo hacia lo complejo. Crearemos un indicador circular simple con un marco, un valor numérico y una descripción. En la fig.1 se presenta la estructura de los elementos básicos de los cuales constará.

  • Marco. Es un especie de borde dibujado.
  • Fondo. Es el espacio en el que se encontrarán los elementos de texto.
  • Valor. Es el elemento de texto que representa el valor numérico del indicador.
  • Descripción. Elemento de texto para describir el indicador (su nombre, periodo u otra información distintiva).


Fig.1 Estructura básica del indicador circular simple

En la carpeta CustomGUI anteriormente creada, creamos la carpeta Indicator. En ella se encontrarán todas las clases de los futuros indicadores. Crearemos la primera de ellas con el nombre CircleSimple.mqh. Al principio conectaremos el archivo CanvaseBase.mqh anteriormente creado con la clase básica para todos los tipos de objetos gráficos, y haremos la clase CCanvaseBase básica para la actual, de forma que todos los métodos de esta clase estén disponibles para el actual CCircleSimple.

//+------------------------------------------------------------------+
//|                                                 CircleSimple.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Indicador circular con valor numérico y descripción              |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase

Para que al usar los objetos gráficos en los archivos de los expertos e indicadores no haya que escribirlos manualmente cada vez, crearemos en la carpeta CustomGUI el archivo CustomGUI.mqh, en el que se reunirán todas las inclusiones de las clases de la carpeta Indicators. De esta forma, necesitaremos incluir solo este archivo para obtener acceso a todas las clases de la biblioteca. Ahora incluimos el actual:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"

Al implementar una clase de indicador circular sencilla, era necesario tener en cuenta el conjunto de propiedades y métodos que podrían permitir configurar al máximo incluso un modelo en apariencia tan sencillo de indicador gráfico. En la lista se muestran sus conjuntos:

//+------------------------------------------------------------------+
//| Indicador circular con valor numérico y descripción              |
//+------------------------------------------------------------------+
class CCircleSimple : public CCanvasBase
  {
private:
   //--- Color del fondo 
   color             m_bg_color;
   //--- Color del marco
   color             m_border_color;
   //--- Color del texto del valor
   color             m_font_color;
   //--- Color del texto del rótulo
   color             m_label_color;
   //--- Transparencia
   uchar             m_transparency;
   //--- Grosor del marco
   int               m_border;
   //--- Tamaño del indicador
   int               m_radius;
   //--- Tamaño de la fuente del valor
   int               m_font_size;
   //--- Tamaño de la fuente del rótulo
   int               m_label_font_size;
   //---
   int               m_digits;
   //--- Rótulo
   string            m_label;
public:
                     CCircleSimple(void);
                    ~CCircleSimple(void);
   //--- Establece y retorna el color del fondo
   color             Color(void)                      { return(m_bg_color);            }
   void              Color(const color clr)           { m_bg_color=clr;                }
   //--- Establece y retorna el tamaño
   int               Radius(void)                     { return(m_radius);              }
   void              Radius(const int r)              { m_radius=r;                    }
   //--- Establece y retorna el tamaño de la fuente del valor
   int               FontSize(void)                   { return(m_font_size);           }
   void              FontSize(const int fontsize)     { m_font_size=fontsize;          }
   //--- Establece y retorna el tamaño de la fuente del rótulo
   int               LabelSize(void)                  { return(m_label_font_size);    }
   void              LabelSize(const int fontsize)    { m_label_font_size=fontsize;   }
   //--- Establece y retorna el color de la fuente del valor
   color             FontColor(void)                  { return(m_font_color);          }
   void              FontColor(const color fontcolor) { m_font_color=fontcolor;        }
   //--- Establece y retorna el color de la fuente del rótulo
   color             LabelColor(void)                 { return(m_label_color);         }
   void              LabelColor(const color fontcolor){ m_label_color=fontcolor;       }
   //--- Establece el color y el grosor del marco
   void              BorderColor(const color clr)     { m_border_color=clr;            }
   void              BorderSize(const int border)     { m_border=border;               }
   //--- Establece y retorna la transparencia
   uchar             Alpha(void)                      { return(m_transparency);        }
   void              Alpha(const uchar alpha)         { m_transparency=alpha;          }
   //--- Establece y retorna el valor del rótulo
   string            Label(void)                      { return(m_label);               }
   void              Label(const string label)        { m_label=label;                 }
   //--- Crea un indicador
   void              Create(string name,int x,int y);
   //--- Elimina un indicador
   void              Delete(string name);
   //--- Establece y actualiza el valor del indicador
   void              NewValue(int value);
   void              NewValue(double value);
  };

Por la descripción podemos ver la designación de las variables y los métodos en los que se usan. Nos detendremos en aquellos métodos que implementan la propia construcción del indicador en la forma que se representa en la fig.1. El primer método que analizaremos es CreateCanvas(). Como podemos ver por la lista, solo tiene tres argumentos. Hemos considerado dichos argumentos como los más importantes. Añadir argumentos adicionales es excesivo, esto complicaría la implementación del método. Por eso, todas las demás propiedades se han introducido en métodos aparte. En relación con esto, todas las variables han sido inicializadas en el constructor de la clase:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCircleSimple::CCircleSimple(void) : m_bg_color(clrAliceBlue),
                                     m_border_color(clrRoyalBlue),
                                     m_font_color(clrBlack),
                                     m_label_color(clrBlack),
                                     m_transparency(255),
                                     m_border(5),
                                     m_radius(40),
                                     m_font_size(17),
                                     m_label_font_size(20),
                                     m_digits(2),
                                     m_label("label")
  {
  }

Esto resulta cómodo porque al crear un indicador de tal tipo, a usted le bastará con crear un ejemplar de su clase y usar solo el método CreateCanvas(). Está claro que antes de la creación, solo podrá indicar las propiedades que usted querría cambiar. Al crear objetos gráficos complejos con la biblioteca CCanvas conviene entender que los métodos que implementan las primitivas, se dibujan de forma consecutiva y por capas. En esencia, como sucede en cualquier lienzo, el pintor pinta en primer lugar el fondo y después dibuja sobre él los objetos, y sobre estos, los detalles, etcétera. 

//+------------------------------------------------------------------+
//| Crea un indicador                                                |
//+------------------------------------------------------------------+
void CCircleSimple::Create(string name,int x,int y)
  {
   int r=m_radius;
//--- Corrección de la posición del indicador con respecto al radio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
   if(m_border>0)
      m_canvas.FillCircle(r,r,r,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

No vamos a entrar en detalles sobre la implementación del método. Vamos a detenernos solo en los métodos principales:

  • En primer lugar, corregimos la posición del recurso gráfico con respecto al tamaño factual del indicador, para que no resulte que parte del mismo quede fuere de los límites del gráfico principal.
  • Después usaremos los ajustes y el trabajo con los métodos del nombre, los tamaños y las coordinadas. Se crea usando como base la clase básica CCanvasBase.
  • Al implementar el marco, se ha utilizado el principio de superposición descrito arriba. Concretamente se han creado dos círculos: el primero (coloreado) y el segundo, cuyo radio es menor al radio del primero en un valor igual al grosor del marco.
  • Por encima de estos objetos se han creado elementos de valor numérico y descripción.

A continuación, analizaremos el método NewValue(), que implementa la representación de la actualización del valor numérico del indicador en tiempo real:

//+------------------------------------------------------------------+
//| Establece y actualiza el valor del indicador                    |
//+------------------------------------------------------------------+
void CCircleSimple::NewValue(int value)
  {
   int r=m_radius;
   m_canvas.FillCircle(r,r,r-m_border,ColorToARGB(m_bg_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_font_size);
   m_canvas.TextOut(r,r,IntegerToString(value),ColorToARGB(m_font_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Para que el valor numérico del indicador se actulice correctamente, hay que redibujar de nuevo tres objetos (el fondo y los elementos de texto) en el mismo orden que se crearon. Por eso, en el método NewValue() se redibuja el fondo, y después los elementos textuales y las descripciones.

Así, ha llegado el momento de comprobar la implementación del indicador circular en el terminal MetaTrader 5. Para ello, crearemos un indicador vacío, conectaremos al mismo el archivo CustomGUI.mqh y crearemos dos ejemplares de la clase CCircleSimple:

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/es/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSimple ind1,ind2;

A continuación, en la inicialización tomaremos los indicadores estándar, cuyos valores se utilizarán posteriormente en los indicadores circulares:

//---
int InpInd_Handle,InpInd_Handle1;
double adx[],rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//---- obteniendo el manejador del indicador
   InpInd_Handle=iADX(Symbol(),PERIOD_CURRENT,10);
   InpInd_Handle1=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
   ArraySetAsSeries(adx,true);
   ArraySetAsSeries(rsi,true);

Vamos a definir ciertas propiedades de los indicadores circulares para realizar una demostración y crear las mismas:

//---
   ind1.Radius(60);
   ind1.Label("ADX");
   ind1.Color(clrWhiteSmoke);
   ind1.LabelColor(clrBlueViolet);
   ind1.Create("adx",100,100);
//---
   ind2.Radius(55);
   ind2.BorderSize(8);
   ind2.FontSize(22);
   ind2.LabelColor(clrCrimson);
   ind2.BorderColor(clrFireBrick);
   ind2.Label("RSI");
   ind2.Create("rsi",250,100);

En la parte del cálculo del indicador solo queda sustituir los actuales valores numéricos de los indicadores estándar por los creados:

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[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,2,adx)<=0 || 
      CopyBuffer(InpInd_Handle1,0,0,2,rsi)<=0
      )
      return(0);
   ind1.NewValue(adx[0]);
   ind2.NewValue(rsi[0]);
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Para que funcione correctamente, queda escribir los métodos de eliminación de los recursos gráficos en la desinicialización, para que al eliminar un indicador, los objetos gráficos se eliminen correctamente junto con él.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind1.Delete();
   ind2.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

El resultado del trabajo se muestra en la fig.2. Como se puede ver, los indicadores se distinguen notoriamente uno de otro en muchos parámetros.

Fig.2. Ejemplo de funcionamiento de los indicadores circulares.

Clase del indicador circular con indicación en arco CCircleArc

Hemos analizado la clase que constituye la forma más sencilla de implementar un indicador circular. Vamos a complicar un poco la tarea y crear una clase con un elemento adicional de representación aparte del numérico: se trata del arco. La estructura de los elementos básicos del indicador con indicación en arco se muestra en la fig.3.


Fig.3 Estructura básica del indicador circular con indicación en arco.

Como se puede ver en la figura, este tipo se distingue por la adición del elemento del indicador en Arco. Para implementar la indicación en arco nos vendrá muy bien el método que dibuja un sector coloreado de la elipse y se llama Pie(). De todas las sobrecargas representadas de este método, hemos decidido usar la siguiente:

void  Pie( 
   int         x,       // coordenada X del centro de la elipse 
   int         y,       // coordenada Y del centro de la elipse 
   int         rx,      // radio de la elipse en la coordenada X 
   int         ry,      // radio de la elipse en la coordenada Y 
   int         fi3,     // ángulo del rayo desde el centro de la elipse, que establece el primer límite del arco 
   int         fi4,     // ángulo del rayo desde el centro de la elipse, que establece el segundo límite del arco 
   const uint  clr,     // color de la línea 
   const uint  fill_clr // color del relleno 
   );

Aquí el valor fi3, que establece el primer límite del arco, servirá de comienzo de nuestra escala de arco, y el valor fi4 cambiará de forma dinámica, dependiendo del valor numérico transmitido a nuestro indicador. Creamos en la carpeta Indicators el archivo CircleArc.mqh y directamente en el archivo CustomGUI.mqh añadimos la siguiente línea:

#include "Indicators\CircleArc.mqh"

De esa misma forma, añadimos la futura clase a la lista general de inclusiones de todos los indicadores. Definimos el conjunto de propiedades y métodos generales. En esencia, no se diferencian de los métodos de la clase anterior, pero merece la pena fijarse con mayor atención en la enumeración ENUM_ORIENTATION. Tiene dos valores — VERTICAL y HORIZONTAL — y define la posición del primer límite del arco de la escala de arco. Con un valor vertical, el arco comienza desde 90 grados, y con uno horizontal, desde 0. La indicación se calcula en el sentido opuesto a las agujas del reloj.

//+------------------------------------------------------------------+
//|                                                    CircleArc.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Indicador circular con valor numeríco e indicación en arco     |
//+------------------------------------------------------------------+
class CCircleArc : public CCanvasBase
  {
private:
   //--- Color del fondo del indicador
   color             m_bg_color;
   //--- Color del arco del indicador
   color             m_arc_color;
   //--- Color de la zona de la indicación 
   color             m_area_color;
   //--- Color de la descripción del indicador 
   color             m_label_color;
   //--- Color del valor del indicador
   color             m_value_color;
   //--- Transparencia del indicador
   uchar             m_transparency;
   //--- Tamaño del indicador
   int               m_radius;
   //--- Grosor de la escala del indicador 
   int               m_scale_width;
   //--- Tamaño de la fuente de la descripción
   int               m_label_font_size;
   //--- Tamaño de la fuente del valor
   int               m_value_font_size;
   //--- Descripción del indicador
   string            m_label_value;
   //--- Tipo de orientación de la escala del indicador
   ENUM_ORIENTATION  m_orientation;
public:
                     CCircleArc(void);
                    ~CCircleArc(void);
   //--- Establece y retorna el color del fondo del indicador
   color             BgColor(void)                                   { return(m_bg_color);            }
   void              BgColor(const color clr)                        { m_bg_color=clr;                }
   //--- Establece y retorna el color del arco del indicador
   color             ArcColor(void)                                  { return(m_arc_color);           }
   void              ArcColor(const color clr)                       { m_arc_color=clr;               }
   //--- Establece y retorna el color de la zona de la indicación
   color             AreaColor(void)                                 { return(m_area_color);          }
   void              AreaColor(const color clr)                      { m_area_color=clr;              }
   //--- Establece y retorna el color de la descripción del indicador 
   color             LabelColor(void)                                { return(m_label_color);         }
   void              LabelColor(const color clr)                     { m_label_color=clr;             }
   //--- Establece y retorna el color del valor del indicador
   color             ValueColor(void)                                { return(m_value_color);         }
   void              ValueColor(const color clr)                     { m_value_color=clr;             }
   //--- Establece y retorna la transparencia del indicador 
   uchar             Alpha(void)                                     { return(m_transparency);        }
   void              Alpha(const uchar trn)                          { m_transparency=trn;            }
   //--- Establece y retorna el tamaño del indicador
   int               Radius(void)                                    { return(m_radius);              }
   void              Radius(const int r)                             { m_radius=r;                    }
   //--- Establece y retorna el grosor de la escala del indicador
   int               Width(void)                                     { return(m_scale_width);         }
   void              Width(const int w)                              { m_scale_width=w;               }
   //--- Establece y retorna el tamaño de la fuente de la descripción
   int               LabelSize(void)                                 { return(m_label_font_size);     }
   void              LabelSize(const int sz)                         { m_label_font_size=sz;          }
   //--- Establece y retorna el tamaño de la fuente del valor
   int               ValueSize(void)                                 { return(m_value_font_size);     }
   void              ValueSize(const int sz)                         { m_value_font_size=sz;          }
   //--- Establece y retorna la descripción del indicador
   string            LabelValue(void)                                { return(m_label_value);         }
   void              LabelValue(const string str)                    { m_label_value=str;             }
   //--- Establece y retorna el tipo de orientación de la escala
   void              Orientation(const ENUM_ORIENTATION orietation)  { m_orientation=orietation;      }
   ENUM_ORIENTATION  Orientation(void)                               { return(m_orientation);         }
   //--- Crea el indicador
   void              Create(string name,int x,int y);
   //--- Elimina el indicador
   void              Delete(void);
   //--- Establece y actualiza el valor del indicador
   void              NewValue(double value,double maxvalue,int digits);
  };

Nos detendremos con más detalle en los métodos cuya implementación no se ha presentado: Create() y NewValue(). Recordemos que al crear objetos, estos se colocan unos sobre otros por capas, por eso su orden de creación será el siguiente:

  1. Fondo del indicador en arco. En forma de arco coloreado.
  2. Indicador en arco. En forma de sector coloreado de la elipse.
  3. Fondo de representación de la información textual. En forma de arco coloreado.
  4. Valor numérico del indicador y su descripción.

Más abajo mostramos la implementación en el orden establecido:

//+------------------------------------------------------------------+
//| Crea el indicador                                                |
//+------------------------------------------------------------------+
void CCircleArc::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Estableciendo la posición inicial del indicador
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
   b+=90*M_PI/180;
//--- Corrección de la posición del indicador con respecto al radio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Aquí deberemos prestar atención a que, al establecer los valores iniciales del primer y el segundo límite del arco (es decir, el inicio del indicador y su valor actual), deberemos tener en cuenta su orientación inicial y el hecho de que los ángulos para el método Pie() deberán indicarse en radianes. Como ejemplo, por defecto, el valor actual que corresponde a la variable b se establece en 90, además, se pasa a radianes , de lo contrario, la representación será incorrecta. Según estos mismos principios se implementa el método NewValue():

//+------------------------------------------------------------------+
//| Establece y actualiza el valor del indicador                     |
//+------------------------------------------------------------------+
void CCircleArc::NewValue(double value,double maxvalue,int digits=2)
  {
   int r=m_radius;
   double a,b,result;
//--- Comprobando la salida de los límites del diapasón
   value=(value>maxvalue)?maxvalue:value;
   value=(value<0)?0:value;
//--- Estableciendo la posición inicial del indicador
   a=(m_orientation==VERTICAL)?M_PI_2:0;
   b=(m_orientation==VERTICAL)?M_PI_2:0;
//---
   result=value*(360.0/maxvalue);
   b+=result*M_PI/180;
   if(b>=2*M_PI)
      b=2*M_PI-0.02;
//---
   m_canvas.FillCircle(r,r,r,ColorToARGB(m_bg_color,m_transparency));
   m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_arc_color,m_transparency),ColorToARGB(m_arc_color,m_transparency));
   m_canvas.FillCircle(r,r,r-m_scale_width,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_label_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

Después de comprobar y establecer las posiciones iniciales, tiene lugar el recálculo del ángulo en radianes del segundo límite del arco, y después todos los elementos del indicador se redibujan con nuevos valores. Como ejemplo del uso de este indicador, se ha implementado un sencillo indicador de spread que al darse el valor umbral cambia el color de la escala de arco de la indicación.

//+------------------------------------------------------------------+
//|                                                                  |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//+------------------------------------------------------------------+
//|  Parámetros de entrada del indicador                             |
//+------------------------------------------------------------------+
input double         maxspr=12;                 // Valor maximo
input int            stepval=8;                 // Valor umbral
input color          stepcolor=clrCrimson;      // Color al darse el valor umbral
//---
CCircleArc arc;
double spr;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   arc.Orientation(HORIZONTAL);
   arc.LabelValue("Spread");
   arc.Create("spread_ind",100,100);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
   spr=double(SymbolInfoInteger(Symbol(),SYMBOL_SPREAD));
   if(spr>=stepval)
      arc.ArcColor(stepcolor);
   else
      arc.ArcColor(clrForestGreen);
//---
   arc.NewValue(spr,maxspr,0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   arc.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Como se puede ver por la lista, la implementación con la ayuda de la clase CCircleArc es suficientemente sencilla de crear y configurar. Debemos señalar lo siguiente: cualquier propiedad debe ser modificada o ajustada rigurosamente antes de la creación del indicador (método Create()), o bien antes de la utilización del método NewValue(), ya que precisamente en ellos tiene lugar el redibujado de los elementos del indicador, y por consiguiente, el uso de las propiedades modificadas. En la fig.4 se muestra un ejemplo del funcionamiento del indicador de spread.

Fig.4. Ejemplo de funcionamiento del indicador circular con indicación en arco.


Clase del indicador circular con indicación de sección en arco CCircleSection

A diferencia de la indicación en arco simple, la de sección visualmente parece como si tuviese marcas que separasen intervalos iguales de los valores. Al construir la maqueta de este tipo de indicador hemos decidido hacer diez secciones, además, vamos a añadir un nuevo elemento: el marco interior. La estructura básica con indicación en arco con secciones se muestra en la fig.5.


Fig.5 Estructura básica del indicador circular con indicación en arco con secciones.

El método de representación en arco con secciones se diferencia radicalmente de la clase anterior. La diferencia reside en que en la clase anterior se usó una sola vez el método Pie() de la biblioteca CCanvas, en el que al cambiar un valor cambiaba el valor y la posición del segundo arco del sector de la elipse. Aquí, este método se aplica diez veces, y las posiciones son estáticas. Dicho de forma más sencilla, en este indicador hay 10 sectores coloreados en la elipse, ubicados en círculo uno tras otro. El cambio de color de determinadas secciones servirá de indicación visual.

Vamos a crear en la carpeta Indicators el archivo CircleSection.mqh, y de inmediato, como hemos hecho con todos los anteriores, vamos a incluirlo en el archivo CustomUI.mqh. Ahora la lista tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//|                                                    CustomGUI.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"

Ya que la presentación de las clases de los indicadores va en orden de complejidad en su construcción y funcionalidad, no voy a mostrar las propiedades y métodos generales para ellas. Centraremos nuestra atención en aquellas que añaden una funcionalida adicional o tienen otra implementación. Por eso, en la clase actual la mayor parte de los métodos es idéntica a la anterior, pero se han añadido los siguientes:

//+------------------------------------------------------------------+
//|                                                CircleRounded.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+--------------------------------------------------------------------------+
//| Indicador circular con valor numérico e indicación circular de secciones |
//+--------------------------------------------------------------------------+
class CCircleSection : public CCanvasBase
  {
private:
   //--- Color de la sección activa/inactiva de la escala
   color             m_scale_color_on;
   color             m_scale_color_off;
public:
   //--- Establece el color de la sección activa/inactiva de la escala
   void              ScaleColorOn(const color clr)                   { m_scale_color_on=clr;          }
   void              ScaleColorOff(const color clr)                  { m_scale_color_off=clr;         }
   //--- Crea el indicador
   void              Create(string name,int x,int y);
   //--- Establece y actualiza el valor del indicador
   void              NewValue(double value,double maxvalue,int digits);
  };

Los métodos Create() y NewValue() se han dejado debido a la diferencia de sus implementaciones en comparación con los anteriores. Como se puede ver por la lista de más abajo, después de la corrección de la posición con respecto al radio, va un bloque de creación de diez secciones con la ayuda de un ciclo. Además, se tiene en cuenta el inicio del cálculo: el horizontal, desde los cero grados, el vertical, desde los 90.

//+------------------------------------------------------------------+
//| Crea el indicador                                                |
//+------------------------------------------------------------------+
void CCircleSection::Create(string name,int x,int y)
  {
   int r=m_radius;
   double a,b;
//--- Corrección de la posición del indicador con respecto al radio
   x=(x<r)?r:x;
   y=(y<r)?r:y;
   Name(name);
   X(x);
   Y(y);
   XSize(2*r+1);
   YSize(2*r+1);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Secciones del indicador
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(m_scale_color_off,m_transparency));
     }
//--- Marco y fondo
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//--- Descripción
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//--- Valor numérico
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,"0",ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }

Como ya hemos dicho, aparte del cambio del valor numérico, la indicación visual se presenta en forma de cambio de color de las secciones. Además, el cambio deberá ser consecutivo y partir desde el valor actual y el valor máximo indicado. Así, por ejemplo, con valor numérico de 20 y un máximo de 100, cambiarán de color dos secciones, pero con un máximo igual a 200, solo una. Veamos de forma más detallada cómo se ha hecho esto en el método NewValue():

//+------------------------------------------------------------------+
//| Establece y actualiza el valor del indicador                    |
//+------------------------------------------------------------------+
void CCircleSection::NewValue(double value,double maxvalue=100,int digits=2)
  {
//---
   int r=m_radius;
   double a,b;
   color clr;
//---
   for(int i=0;i<10;i++)
     {
      if(m_orientation==HORIZONTAL)
        {
         a=36*i*M_PI/180;
         b=36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      else
        {
         a=M_PI_2+36*i*M_PI/180;
         b=M_PI_2+36*(i+1)*M_PI/180;
         if(a>2*M_PI)
            a-=2*M_PI;
         if(b>2*M_PI)
            b-=2*M_PI;
        }
      clr=(maxvalue/10*(i+1)<=value)?m_scale_color_on:m_scale_color_off;
      m_canvas.Pie(r,r,XSize()/2,YSize()/2,a,b,ColorToARGB(m_bg_color,m_transparency),ColorToARGB(clr,m_transparency));
     }
//---
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillCircle(r,r,XSize()/2-m_scale_width-m_border_size,ColorToARGB(m_area_color,m_transparency));
//---
   m_canvas.FontSizeSet(m_label_font_size);
   m_canvas.TextOut(r,r+m_value_font_size,m_label_value,ColorToARGB(m_label_color,m_transparency),TA_CENTER|TA_VCENTER);
//---
   m_canvas.FontSizeSet(m_value_font_size);
   m_canvas.TextOut(r,r,DoubleToString(value,digits),ColorToARGB(m_value_color,m_transparency),TA_CENTER|TA_VCENTER);
   m_canvas.Update();
  }
//+------------------------------------------------------------------+

Como se puede ver en la lista más arriba, en su implementación tiene un aspecto bastante sencillo. Se comprueba el valor actual con respecto al máximo. Si es mayor o igual al valor máximo, dividido por el número de secciones y multiplicado por la iteración de ciclo actual, el color de la sección cambiará de inactivo a activo.

Como ejemplo de uso de esta clase, hemos implementado un indicador de reducción que representa la proporción de equidad con respecto al balance actual en la cuenta. De esta forma, es posible controlar la reducción en la cuenta visualmente, sin calcularla en el terminal con cifras. La lista de implementación de este indicador se muestra abajo.

//+------------------------------------------------------------------+
//|                                              CustomIndicator.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CCircleSection ind;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   ind.Radius(70);
   ind.LabelValue("Drawdown");
   ind.Create("drawdown",150,150);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   ind.NewValue(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE));
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

No tenemos que olvidar tampoco usar en la desinicialización el método Delete() para eliminar correctamente el indicador del gráfico.


Clase del gráfico lineal CLineGraph

Para crear un gráfico lineal mediante gráficos personalizados, primero hay que decidir qué elementos habrá en el propio gráfico, y más concretamente:

  • El fondo trasero del gráfico. Tendrá dos propiedades: el tamaño, que será igualmente el tamaño del propio objeto, y el color.
  • Marco del gráfico. Solo tiene la propiedad del color. Consta de dos rectángulos coloreados, además, las coordenadas del último (que también cumple la función de fondo del gráfico) están desplazadas en una unidad, lo que le da el efecto de marco.
  • Fondo del gráfico. Solo tiene la propiedad de color. 
  • Ejes del gráfico. Solo tiene la propiedad de color. Se implementa de la misma forma que el marco del gráfico, mediante la superposición de dos rectángulos, donde el superior tiene un desplazamiento de coordenadas de una unidad.
  • División del eje. Solo tiene la propiedad de color. Conjunto de líneas implementado con la ayuda de los métodos LineHorizontal() para el eje Y y LineVertical() para el eje Х.
  • Valor del eje. Tiene la propiedad del color y el tamaño de la fuente. Conjuntos de objetos de texto con la ayuda del método TextOut().
  • Cuadrícula. Solo tiene la propiedad de color. Para implementar la cuadrícula se ha elegido el método LineAA(), puesto que en este se puede establecer el estilo de la línea.
  • Gráfico. Solo tiene la propiedad de color. Consta de los elementos línea y círculo coloreado: el elemento FillCircle()

En la descripción de los elementos se indican solo las propiedades configurables. En la fig.6 se presenta la estructura de los elementos del gráfico. 


Fig.6. Estructura básica del gráfico lineal.

Para comenzar, crearemos en la carpeta Indicators el archivo LineGraph.mqh y lo incluiremos en el archivo CustomGUI.mqh:

//+------------------------------------------------------------------+
#include "Indicators\CircleSimple.mqh"
#include "Indicators\CircleArc.mqh"
#include "Indicators\CircleSection.mqh"
#include "Indicators\LineGraph.mqh"
//+------------------------------------------------------------------+

A continuación, en el archivo LineGraph.mqh crearemos la clase CLineGraph y haremos, como en las anteriores, la clase básica para ella CCanvasBase. Definiremos todas las propiedades y métodos descritos más arriba en la estructura básica del gráfico lineal. 

//+------------------------------------------------------------------+
//|                                                    LineGraph.mqh |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#include "..\CanvasBase.mqh"
//+------------------------------------------------------------------+
//| Gráfico lineal                                                  |
//+------------------------------------------------------------------+
class CLineGraph : public CCanvasBase
  {
private:
   //--- Color del fondo trasero del gráfico
   color             m_bg_color;
   //--- Color del fondo del gráfico
   color             m_bg_graph_color;
   //--- Color del marco del gráfico
   color             m_border_color;
   //--- Color de los valores de los ejes del gráfico
   color             m_axis_color;
   //--- Color de la cuadrícula del gráfico
   color             m_grid_color;
   //--- Color de las secciones en los ejes del gráfico
   color             m_scale_color;
   //--- Color de las líneas del gráfico
   color             m_graph_color;
   //--- Tamaño del gráfico
   int               m_x_size;
   int               m_y_size;
   //--- Margen del gráfico con respecto al límite del fondo trasero
   int               m_gap;
   //--- Tamaño de la fuente de los valores en los ejes del gráfico
   int               m_font_size;
   //--- Matriz de los valores del eje de ordenadas(y)
   int               m_x[];
   //--- Valor mínimo y máximo del eje de ordenadas
   double            m_y_min;
   double            m_y_max;
   //--- Número de secciones del eje de ordenadas
   int               m_num_grid;
   //--- Transparencia del gráfico
   uchar             m_transparency;
public:
                     CLineGraph(void);
                    ~CLineGraph(void);
   //--- Establece y retorna el color del fondo trasero del gráfico
   color             BgColor(void)                                   { return(m_bg_color);                  }
   void              BgColor(const color clr)                        { m_bg_color=clr;                      }
   //--- Establece y retorna el color del fondo del gráfico
   color             BgGraphColor(void)                              { return(m_bg_graph_color);            }
   void              BgGraphColor(const color clr)                   { m_bg_graph_color=clr;                }
   //--- Establece y retorna el color del marco del gráfico
   color             BorderColor(void)                               { return(m_border_color);              }
   void              BorderColor(const color clr)                    { m_border_color=clr;                  }
   //--- Establece y retorna el color de los valores de los ejes del gráfico
   color             AxisColor(void)                                 { return(m_axis_color);                }
   void              AxisColor(const color clr)                      { m_axis_color=clr;                    }
   //--- Establece y retorna el color de la cuadrícula del gráfico
   color             GridColor(void)                                 { return(m_grid_color);                }
   void              GridColor(const color clr)                      { m_grid_color=clr;                    }
   //--- Establece y retorna el color de las secciones en los ejes del gráfico
   color             ScaleColor(void)                                { return(m_scale_color);               }
   void              ScaleColor(const color clr)                     { m_scale_color=clr;                   }
   //--- Establece y retorna el color de las líneas del gráfico
   color             GraphColor(void)                                { return(m_graph_color);               }
   void              GraphColor(const color clr)                     { m_graph_color=clr;                   }
   //--- Establece y retorna el tamaño del gráfico
   int               XGraphSize(void)                                { return(m_x_size);                    }
   void              XGraphSize(const int x_size)                    { m_x_size=x_size;                     }
   int               YGraphSize(void)                                { return(m_y_size);                    }
   void              YGraphSize(const int y_size)                    { m_y_size=y_size;                     }
   //--- Establece y retorna el tamaño de la fuente de los valores en los ejes del gráfico
   int               FontSize(void)                                  { return(m_font_size);                 }
   void              FontSize(const int fontzise)                    { m_font_size=fontzise;                }
   //--- Establece y retorna el margen del gráfico con respecto al límite del fondo trasero
   int               Gap(void)                                       { return(m_gap);                       }
   void              Gap(const int g)                                { m_gap=g;                             }
   //--- Establece y retorna el valor mínimo y máximo del eje de ordenadas
   double            YMin(void)                                      { return(m_y_min);                     }
   void              YMin(const double ymin)                         { m_y_min=ymin;                        }
   double            YMax(void)                                      { return(m_y_max);                     }
   void              YMax(const double ymax)                         { m_y_max=ymax;                        }
   //--- Establece y retorna el número de secciones del eje de ordenadas
   int               NumGrid(void)                                   { return(m_num_grid);                  }
   void              NumGrid(const int num)                          { m_num_grid=num;                      }
   //--- Crea un gráfico
   void              Create(string name,int x,int y);
   //--- Elimina el gráfico 
   void              Delete(void);
   //--- Establece la matriz de datos de entrada
   void              SetArrayValue(double &data[]);
private:
   //---
   void              VerticalScale(double min,double max,int num_grid);
   void              HorizontalScale(int num_grid);
  };

Vamos a deternernos con mayor detalle en la implementación de los métodos no indicados en la lista de arriba. En el método Create() se crea un recurso gráfico y se coloca en el gráfico del instrumento. Aquí mismo se usan dos métodos privados para configurar la escala vertical y horizontal: bien partiendo de los ajustes inicializados en el constructor de la clase, o bien con la ayuda de los métodos de clase antes de la creación.

//+------------------------------------------------------------------+
//| Crea el gráfico                                                   |
//+------------------------------------------------------------------+
void CLineGraph::Create(string name,int x,int y)
  {
//--- Corrección de la posición del indicador relativa a
   x=(x<m_x_size/2)?m_x_size/2:x;
   y=(y<m_y_size/2)?m_y_size/2:y;
   Name(name);
   X(x);
   Y(y);
   XSize(m_x_size);
   YSize(m_y_size);
   if(!CreateCanvas())
      Print("Error. Can not create Canvas.");
//--- Creación del marco del gráfico y el fondo trasero
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Creando los ejes y el fondo del gráfico
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Creando las secciones del eje y sus valores
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(5);
   m_canvas.Update();
  }

Para representar un gráfico lineal la decisión óptimia será tomar como base una matriz de datos, puesto que en MetaTrader se usan con frecuencia matrices para copiar los valores de los búferes de los indicadores. Por eso, al implementar el método SetArrayValue() de representación de gráficos, se toma como base una matriz de datos (constituye el argumento del método), donde en el eje Х se apartan los valores correspondientes al tamaño de la matriz, y en el eje Y, los valores. 

//+------------------------------------------------------------------+
//| Establece la matriz de datos de entrada                              |
//+------------------------------------------------------------------+
void CLineGraph::SetArrayValue(double &data[])
  {
   int y0,y1;
//--- Creando el marco del gráfico y del fondo trasero
   m_canvas.FillRectangle(0,0,XSize(),YSize(),ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(1,1,XSize()-2,YSize()-2,ColorToARGB(m_bg_color,m_transparency));
//--- Creando los ejes y el fondo del gráfico
   m_canvas.FillRectangle(m_gap-1,m_gap-1,XSize()-m_gap+1,YSize()-m_gap+1,ColorToARGB(m_border_color,m_transparency));
   m_canvas.FillRectangle(m_gap,m_gap,XSize()-m_gap,YSize()-m_gap,ColorToARGB(m_bg_graph_color,m_transparency));
//--- Si el valor máximo en la matriz de datos supera el límite superior del eje Y, entonces escalamos el eje.
   if(data[ArrayMaximum(data)]>m_y_max)
      m_y_max=data[ArrayMaximum(data)];
//--- Creando las secciones del eje y sus valores
   VerticalScale(m_y_min,m_y_max,m_num_grid);
   HorizontalScale(ArraySize(data));
//--- Construyendo el gráfico según la matriz de datos
   for(int i=0;i<ArraySize(data)-1;i++)
     {
      y0=int((YSize()-2*m_gap)*(1-data[i]/m_y_max));
      y0=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i]);
      y0=(y0<m_gap)?m_gap:y0;
      y1=int((YSize()-m_gap)-(YSize()-2*m_gap)/m_y_max*data[i+1]);
      y1=(y1<m_gap)?m_gap:y1;
      m_canvas.LineAA(m_x[i+1],y0,m_x[i+2],y1,ColorToARGB(m_graph_color,m_transparency),STYLE_SOLID);
      m_canvas.FillCircle(m_x[i+1],y0,2,ColorToARGB(m_graph_color,m_transparency));
      m_canvas.FillCircle(m_x[i+2],y1,2,ColorToARGB(m_graph_color,m_transparency));
     }
   m_canvas.Update();
  }

Como ejemplo de uso de esta clase, hemos decidido construir el gráfico según los valores del oscilador elegido y compararlo con la representación original. Para ello se ha tomado un RSI normal. La implementación de la construcción según sus valores de búfer con la ayuda de la clase ClineGraph se puede ver en la lista de abajo.

//+------------------------------------------------------------------+
//|                                                            4.mq5 |
//|                                Copyright 2017, Alexander Fedosov |
//|                           https://www.mql5.com/ru/users/alex2356 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Alexander Fedosov"
#property link      "https://www.mql5.com/ru/users/alex2356"
#property version   "1.00"
#property indicator_plots 0
#property indicator_chart_window

#include <CustomGUI\CustomGUI.mqh>
//---
CLineGraph ind;
int InpInd_Handle;
double rsi[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//---- obteniendo el manejador del indicador
   InpInd_Handle=iRSI(Symbol(),PERIOD_CURRENT,10,PRICE_CLOSE);
   if(InpInd_Handle==INVALID_HANDLE)
     {
      Print("Failed to get indicator handle");
      return(INIT_FAILED);
     }
//---
   ind.NumGrid(10);
   ind.YMax(100);
   ind.Create("rsi",350,250);

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---
   if(CopyBuffer(InpInd_Handle,0,0,10,rsi)<=0)
      return(0);
   ind.SetArrayValue(rsi);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ind.Delete();
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Ahora establecemos el indicador original con los mismos parámetros y comparamos los resultados obtenidos en la fig.7:

Fig.7. Comparando la implementación de la construcción con la ayuda de la clase CLineGraph y el RSI original.

Preste atención a que la matriz en la que se registran los datos mediante el método CopyBuffer tenga el aspecto original. Es decir, no es necesario cambiar la indexación, como en las series temporales.


Conclusión

En el artículo se han analizado ejemplos de creación por etapas de indicadores personalizados de forma aleatoria con la ayuda de la biblioteca de gráficos personalizados CСanvas. Se han descrito ejemplos de diferente dificultad: desde un indicador sencillo que consta de cuatro elementos, hasta uno circular con indicación con secciones. Además, se ha estudiado la posibilidad de implementar de una nueva forma tipos de indicadores ya conocidos. En este caso, las posibilidades no se limitan a un conjunto de propiedades rigurosamente establecido. Estos ejemplos de implementación en el artículo han sido estructurados en forma de clases, es decir, en su esencia son complementos o ampliaciones de la clase CCanvas ya preparada. 

Al final del artículo se adjuntan todos los archivos enumerados, clasificados por carpetas. Por eso, para que todo funcione correctamente, basta con colocar la carpeta MQL5 en la carpeta raíz del terminal. 

Programas usados en el artículo:

#
 Nombre
Tipo
Descripción
1
CanvasBase.mqh Biblioteca  Clase básica de gráficos personalizados
2
CustomGUI.mqh Biblioteca  Archivo con la lista de inclusión de todas las clases de la biblioteca 
3 CircleArc.mqh Biblioteca  Contine la clase del indicador circular con indicación en arco CCircleArc
4 CircleSection.mqh Biblioteca  Contiene la clase del indicador circular con indicación con secciones en arco CCircleSection
5 CircleSimple.mqh Biblioteca  Contiene la clase del indicador circular simple CCircleSimle
LineGraph.mqh Biblioteca  Contiene la clase del gráfico lineal CLineGraph
7 1.mq5 Indicador  Ejemplo de implementación de la clase CCirleSimple
8 2.mq5 Indicador  Ejemplo de implementación de la clase CCirleArc
9 3.mq5 Indicador  Ejemplo de implementación de la clase CCirleSection
 10 4.mq5 Indicador  Ejemplo de implementación de la clase CLineGraph