Indicadores múltiplos em um gráfico (Parte 04): Iniciando pelo EA

Daniel Jose | 3 mayo, 2022

Introducción

En artículos anteriores, expliqué cómo crear un indicador con múltiples subventanas, lo que se vuelve interesante cuando se utiliza un indicador personalizado, la cosa es bastante simple de hacer, pero cuando tratamos de hacer esto en un programa EA, todo comienza a ser un poco más complejo, al no tener los mismos medios que tenemos en un indicador personalizado, en este punto se hace necesaria la programación, y saber crear el código adecuado para tener una subventana es primordial, aunque no es una tarea tan trivial, visto que saber poner una subventana en un EA no implica mucha codificación, sino sólo un cierto conocimiento de cómo funciona MQL5.


Planificación

Ya tenemos nuestro indicador personalizado funcionando, es decir, nuestra clase objeto ya es funcional, y al ser una clase objeto podemos transportarla fácilmente a otros modelos, no obstante el simple hecho de declarar e intentar utilizar la clase en nuestro EA no hará que las cosas funcionen de la misma manera que lo hicimos en nuestro indicador personalizado, y la razón es que no contamos con la posibilidad de una sub ventana en nuestro EA. Pero luego pensamos: "¿qué pasa si utilizo el indicador personalizado que ya está compilado y funcionando y lo llamo desde el EA a través del comando iCustom? ¿Podría funcionar?" Bueno, en realidad podría funcionar, ya que no seria necesaria una subventana, y el comando se vería así:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
input string user01 = "";                //Indicadores que se usarán
input string user02 = "";                //Activos que se acompañarán
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//... Código do EA ...

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), 0, m_handleSub)) return INIT_FAILED;
//... Código del EA ...

        ChartRedraw();
        
        return(INIT_SUCCEEDED);
}
//...Resto del código del EA ...

Este simple fragmento de código sí es capaz de cargar nuestro indicador personalizado, aunque no funcionará correctamente, porque no tenemos la presencia de una subventana, en este caso cuando el código se ejecute en el EA, aplicará nuestro indicador directamente en la ventana principal, lo que significa que nuestro gráfico quedará oculto por los templates cargados por el indicador, y esto definitivamente no es lo que estamos deseando.

Así que nuestro verdadero y principal problema es crear una subventana que pueda ser utilizada para que podamos usar nuestro indicador ya funcional. Pero, ¿por qué crear una subventana para ejecutar nuestro indicador después? Esto no tiene sentido, es mejor añadir las funcionalidades directamente en nuestro EA y así superar cualquier limitación que pueda ocurrir.

Teniendo esto en cuenta, tenemos que realizar algunas tareas:

Tarea Objetivo
1 => Crear un indicador de uso general, es decir, genérico Permitir crear y utilizar el comando iCustom sin contaminar el gráfico en absoluto
2 => Incluir este indicador en el EA de alguna manera  Esto le permitirá transportar el EA con plena funcionalidad sin problemas
3 => Crear una clase de objeto genérico para las subventanas  Permitir añadir subventanas a través de nuestro EA
4 => Hacer que nuestra clase C_TemplateChart se vincule a la clase de ventanas Esto nos permitirá controlar el contenido de las subventanas sin cambiar nada en el código que ya funciona.

Aunque parezca laborioso, las dificultades son bastante sencillas de resolver. Así que vamos a abordar cada uno de los puntos.


Implementación: Creación de un indicador de propósito general

Esta parte se puede resolver creando un código totalmente limpio pero funcional de un indicador personalizado. En este caso, el código sería el siguiente:

#property copyright "Daniel Jose"
#property version   "1.00"
#property description "Este archivo sólo sirve como soporte de indicadores en SubWin"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Solo esto y nada más. Vamos a guardar este archivo como SubSupport.mq5, sólo que no estará junto con otros indicadores, lo transferimos al directorio RESOURCE de nuestro EA, entonces la estructura del archivo se vería como la imagen de abajo:


Esto tiene una gran razón de ser, pero por ahora déjalo así. Ahora pasemos a la siguiente tarea.


Implementación: Inclusión del indicador genérico en la EA

Para conseguirlo tendremos que añadir el siguiente código al principio de nuestro EA.

//+------------------------------------------------------------------+
#define def_Resource "Resources\\SubSupport.ex5"
//+------------------------------------------------------------------+
#resource def_Resource
//+------------------------------------------------------------------+

Esto incorporará el código compilado del indicador genérico en nuestro EA, una vez hecho esto el indicador genérico puede tener el archivo .ex5 eliminado, ya que no es necesario. Ahora hay que tener en cuenta un hecho, si en el momento de compilar el código del EA no se encuentra el ejecutable SubSupport.ex5, el compilador compilará automáticamente el código de nuestro indicador genérico SubSupport.mq5 y añadirá este ejecutable recién compilado a nuestro EA, es decir, si modificamos por cualquier motivo el archivo SubSupport.mq5 y queremos añadir los cambios al EA, borremos el archivo SubSupport.ex5, de lo contrario los cambios no se añadirán.

Es muy importante tener en cuenta este detalle, porque en algunos casos hay que saber cómo añadir los nuevos cambios a un recurso.

Bien, el indicador genérico ya forma parte de nuestro EA, así que pasemos a la siguiente tarea.


Implementación: Creación de una clase objeto de subventanas

Esta parte es igualmente sencilla, aunque debemos decidir algunas cosas antes de codificar, y son las siguientes: ¿Qué funciones necesitaremos realmente en esta clase? Decidí utilizar inicialmente las siguientes:

Función Descripción
Init Permitir añadir subventanas a través de nuestro EA
Close Permitir añadir subventanas a través de nuestro EA

Estas funciones no harán pruebas, así que me imagino que serán llamadas una sola vez durante toda la vida del EA. Pero como nuestro EA está creciendo es bueno pensar en hacerlo aún más práctico para el futuro, entonces vamos a disparar una nueva clase objeto que se llamará C_Terminal, esta clase soportará varias cosas vinculadas al terminal gráfico, pero no te preocupes por esto por ahora, veamos entonces la última tarea, ya que no hay manera de implementar una solución de manera parcial.


Implementación: Herencia de la clase C_TemplateChart

Cuando decidí crear cosas usando POO (Programación Orientada a Objetos) lo hice por ya saber que hay grandes ventajas en usar dicho enfoque, entre ellas están la seguridad y la herencia, aunque también tenemos el polimorfismo, pero esto lo usaremos más adelante cuando creemos un sistema de órdenes cruzadas, mientras que aquí usaremos una de las cosas buenas que trae la POO, la herencia. Pues bien, nuestra clase C_TemplateChart ya es totalmente funcional, y viendo eso, no queremos tener el trabajo de reprogramar todo de nuevo, o correr el riesgo de añadir código a la clase, y que este código impida que la clase sea utilizada en otro lugar. La solución es utilizar la herencia y así añadir nuevos códigos o características sin cambiar de ninguna manera el código original.

El uso de la herencia nos aporta varios beneficios, entre ellos tenemos: El código ya probado se mantiene probado; la complejidad crece sin un crecimiento igual en la cantidad de código; sólo las nuevas características realmente necesitan ser probadas; lo que no cambia simplemente se hereda asegurando la estabilidad, en resumen, la cosa mejora con un esfuerzo mínimo pero con la máxima seguridad, para entenderlo vamos a ver el esquema de abajo.


La clase abuelo es la clase más básica, donde tenemos un nivel de manipulación de datos en menor escala, pero cuando la clase padre hereda cosas de la clase abuelo, todas las cosas declaradas como públicas en la clase abuelo pueden ser vistas y usadas por la clase padre, aunque también podemos hacer adiciones de cosas nuevas a la clase padre, si bien esto no afecta en absoluto a lo heredado y mantenido durante la herencia, pero si la clase padre ya está terminada y funcionando y queremos ampliar las cosas aún más sin cambiar nada en las clases de abajo, entonces creamos una clase hija y ésta ahora tendrá todas las características de las clases anteriores, eso sí, podemos cambiar el funcionamiento, y esto es lo interesante de la herencia, y al hacer estos cambios las otras clases no se verán afectadas. Sin embargo hay una limitación aquí, a diferencia de C++ que permite la herencia múltiple, es decir, el hijo podrá heredar características tanto del lado paterno como del materno, en MQL5 esto no es posible, por lo que la estructura siempre está un poco rezagada, pero aún así se obtiene algún beneficio de la herencia. A continuación se puede ver un ejemplo de herencia múltiple:

Bien, ¿entonces cómo hacemos esto en MQL5? ¿Cómo declaramos la herencia para poder aprovecharla? La forma más clara de entenderlo es leyendo el contenido Programación Orientada a Objetos ( POO ), pero aquí iremos directamente al grano. La herencia se realizará mediante las siguientes líneas:

#include "C_TemplateChart.mqh"
//+------------------------------------------------------------------+
class C_SubWindow : public C_TemplateChart
{
// ... Código da classe
};

Vea que la clase C_SubWindow heredará públicamente la clase C_TemplateChart, por lo que a partir de este momento podremos utilizar la clase C_SubWindow para acceder a la funcionalidad de la clase C_TemplateChart.

En el fragmento de código anterior, he resaltado una cosa, nótese que está entre comillas ( " ) y no como es habitual utilizar ( < > ) y ¿por qué he hecho esto? Al igual que el lenguaje C++, MQL5 tiene algunas cosas muy interesantes, pero que confunden a quien está empezando a aprender el arte de la programación, cuando ponemos un archivo de cabecera entre los signos mayor y menor ( < > ) nos estamos refiriendo a una ruta absoluta, es decir el compilador seguirá exactamente la ruta que le indiquemos, pero cuando usamos comillas como se hizo, el compilador usará una ruta relativa, o para que lo entiendas mejor, primero empezará en el directorio actual donde está el archivo de trabajo. Esto puede sonar extraño, pero hay casos en los que tendremos el mismo nombre para archivos cuyo contenido es diferente, y estarán en diferentes directorios, de todos modos queremos referirnos al directorio actual, así que usamos las comillas para hacerlo.

Las dos funciones que pensábamos utilizar antes, INIT y CLOSE, pueden verse a continuación:

//+------------------------------------------------------------------+
bool Init(void)
{
        if (m_handleSub != INVALID_HANDLE) return true;
        if ((m_handleSub = iCustom(NULL, 0, "::" + def_Resource)) == INVALID_HANDLE) return false;
        m_IdSub = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL);
        if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) return false;
                
        return true;
}
//+------------------------------------------------------------------+
void Close(void)
{
        ClearTemplateChart();
        if (m_handleSub == INVALID_HANDLE) return;
        IndicatorRelease(m_IdSub);
        ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, 0));
        ChartRedraw();
        m_handleSub = INVALID_HANDLE;
}
//+------------------------------------------------------------------+

Vean el código es súper simple y corto, pero hay algo con lo que debemos tener cuidado, observen la parte resaltada. Hay que tener cuidado de no cometer el error al añadir esta parte, porque si no se deja exactamente así, el ejecutable SubSupport.ex5 que pedimos añadir al EA no se verá dentro del EA, sino fuera del EA. Basta con leer sobre Recursos para entender esto, pero básicamente es lo siguiente: Si se utiliza ( :: ) esto indicará que el EA debe utilizar el recurso interno presente en él, pero si sólo nombramos el recurso el EA lo buscará dentro del directorio MQL5, por lo que si el archivo no existe en la ubicación indicada la función fallará, incluso si el archivo fue añadido como un recurso del EA.

Luego, una vez cargado el recurso, comprobamos el número de subventanas presentes y añadimos un indicador en esta subventana.

Lo que este código hace en realidad se puede ver a continuación:

input string user01 = "";               //Indicadores que serán usados
input string user02 = "";               //Activos que serán acompañados
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//...   

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL), m_handleSub)) return INIT_FAILED;

//...

        ChartRedraw();
        
   return(INIT_SUCCEEDED);
}
//...Resto del código del EA ...

Ambos códigos funcionarán de forma idéntica, pero la versión de la clase objeto nos permitirá añadir más cosas con el tiempo, ahora bien, la versión mostrada anteriormente es una versión ya consolidada y no cambiará, pero ambas realizan lo mismo, crean una subventana desde el EA y ponen en esta subventana todos los indicadores personalizados previamente creados. Obsérvese que el código sufrió únicamente una modificación del código visto al principio del artículo y que está resaltado.


Conclusión

Es muy interesante y curioso como decidimos seguir un camino para lograr nuestras metas, varias veces nos enfrentamos e imaginamos que es difícil lograr los objetivos, pero con un poco de paciencia y dedicación podemos superar los obstáculos que inicialmente parecían insuperables. En este artículo demuestro cómo se puede extender la funcionalidad de una clase sin necesidad de modificarla gracias a la herencia, y al mismo tiempo muestro cómo se pueden añadir indicadores a los gráficos, para que funcionen como ya se ha probado. Añadimos un programa ex5 dentro de nuestro EA y lo utilizamos sin tener que transportar el ex5 original, sólo cargando el EA.

El archivo adjunto contiene todas las mejoras desarrolladas hasta ahora, pero pronto tendremos aún más cosas interesantes en este código. 😁👍