Creación de indicadores personalizados usando la clase CCanvas
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:
- Fondo del indicador en arco. En forma de arco coloreado.
- Indicador en arco. En forma de sector coloreado de la elipse.
- Fondo de representación de la información textual. En forma de arco coloreado.
- 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 |
6 | 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 |
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/3236
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso