English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
Indicadores multiples en un gráfico (Parte 04): Comenzando con el EA

Indicadores multiples en un gráfico (Parte 04): Comenzando con el EA

MetaTrader 5Ejemplos | 12 abril 2022, 14:28
808 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior Múltiples indicadores en un gráfico expliqué el código base para poder utilizar más de un indicador dentro de una subventana, pero lo que se presentó era sólo la base inicial de un sistema mucho mayor. Hay varias cosas que se pueden hacer a partir de ese modelo, pero tenemos que ir poco a poco, porque una de las intenciones de estos artículos es motivarlos a que aprendan a programar y así puedan desarrollar sus propios sistemas, a partir de una idea que tengan. En este artículo a partir de aquí vamos a aumentar las funcionalidades, y estas pueden ser interesantes para aquellos que ya les gustaba lo que podía hacer el sistema, pero les gustaría poder hacer más.


Planificación

Muchas veces, cuando empezamos a implantar un nuevo sistema, no tenemos una idea real de hasta qué punto podemos mejorarlo, por lo que siempre debemos empezar un nuevo proyecto previendo ya que puede experimentar futuras mejoras. Esto es esencial para los que están empezando, siempre planificando las cosas e imaginando futuras ampliaciones y mejoras.

Bueno, el código base no sufrió ningún cambio, que en cierto modo ya era esperado, pero el código de la clase objeto, éste pasó por un cambio radical, pero los cambios se hicieron para implementar las nuevas características y permitir nuevas mejoras de una manera aún más ágil, ya que la reutilización del código se hace aún mayor, y esta es una de las filosofías de la programación orientada a objetos, siempre reutilizar, crear sólo cuando sea necesario. Así que vamos a desmenuzar la nueva clase de objeto, y tengan en cuenta que voy a resaltar los cambios para facilitar la comprensión.

Empecemos con las nuevas definiciones de las variables privadas de la clase.

struct st
{
        string  szObjName,
                szSymbol;
        int     width;
}m_Info[def_MaxTemplates];
int             m_IdSubWin,
                m_Counter,
                m_CPre,
                m_Aggregate;
long            m_Id,
                m_handle;
ENUM_TIMEFRAMES m_Period;

Obsérvese que se ha incrementado mucho el número de variables utilizadas, esto debido a que necesitamos más datos para controlar adecuadamente las nuevas funcionalidades. Vean que ahora tenemos una estructura dentro de nuestro sistema de variables, estas estructuras son muy buenas para agrupar variables relacionadas entre sí, aseguran que cuando manipulemos los datos, tengamos un acceso rápido y fácil a los mismos.

void SetBase(const string szSymbol, int iScale, int iSize)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
        if (m_IdSubWin < 0)
        {
                m_Id = ChartID();
                m_IdSubWin = (int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - 1;
                m_Aggregate = 0;
        }
        m_Info[m_Counter].szObjName = __FILE__ + (string) MathRand() + (string) ObjectsTotal(m_Id, -1, OBJ_CHART);
        ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, 0, 0);
        ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));

// ....

        macro_SetInteger(OBJPROP_PERIOD, m_Period);
        m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
        m_Aggregate += iSize;
        m_Info[m_Counter].width = iSize;
        m_CPre += (iSize > 0 ? 1 : 0);
        m_Counter++;
#undef macro_SetInteger
};

El principal cambio que pronto vemos es que utilizamos la estructura para almacenar el nombre del activo, el nombre del objeto y el ancho del mismo, sí, ahora también tenemos esta posibilidad de indicar el ancho que tendrá el indicador en la ventana, pero también hacemos algunas anotaciones para utilizarlas en otras partes de la clase. Ahora veamos la rutina que más sufrió cambios.

void Decode(string &szArg, int &iScale, int &iSize)
{
#define def_ScaleDefault 4
#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
                                                                
        string sz1;
        int i0, i1, c1 = StringLen(szArg);
        bool b0 = true;
        StringToUpper(szArg);
        iScale = def_ScaleDefault;
        m_Period = _Period;
        for (int c0 = 0, max = StringLen(szArg); c0 < max; c0++) switch (szArg[c0])
        {
                case ':':
                        b0 = false;
                        for (; (c0 < max) && ((szArg[c0] < '0') || (szArg[c0] > '9')); c0++);
                        iScale = (int)(szArg[c0] - '0');
                        iScale = ((iScale > 5) || (iScale < 0) ? def_ScaleDefault : iScale);
                        break;
                case ' ':
                        break;
                case '<':
                        macro_GetData('>');
                        if (sz1 == "1M") m_Period = PERIOD_M1; else

//....

                        if (sz1 == "1MES") m_Period = PERIOD_MN1;
                        break;
                case '[':
                        macro_GetData(']');
                        iSize = (int) StringToInteger(sz1);
                        break;
                default:
                        c1 = (b0 ? c0 : c1);
                        break;
        }
        szArg = StringSubstr(szArg, 0, c1 + 1);
#undef macro_GetData
#undef def_ScaleDefault
}

Las líneas verdes son adiciones en el código, en cambio la línea amarilla ya existía en el código original, pero fue movida por razones prácticas. Pero vamos a entender lo que toda esta adición hace en el código y, lo más importante, lo que mejora en términos de funcionalidad el sistema original. Bueno, básicamente creamos medios para que el usuario configure algunas cosas específicas, y tratamos de hacerlo añadiendo nuevas reglas a la sintaxis existente, véase la tabla siguiente:

Delimitador Funcionalidad Ejemplo  Resultado 
< > Indica el período gráfico que se utilizará  < 15m > Bloquea el periodo del indicador en 15 min, el gráfico de origen puede utilizar un periodo diferente, pero el indicador sólo se mostrará con datos de 15 min.
 [ ] Indica la anchura del indicador  [ 350 ] Bloquea el ancho del indicador en 350 píxeles 

El delimitador que bloquea el periodo del gráfico lo hará sólo en el indicador en el que está presente, y no afectará de ninguna manera a los cambios que pueda hacer el usuario, todos los demás indicadores y el gráfico principal se actualizarán al nuevo periodo del gráfico deseado por el usuario, pero el indicador bloqueado no seguirá el nuevo periodo del gráfico que ahora se utilizará. Esto puede ser interesante en algunos casos, véase la imagen de abajo para un caso específico.

         

Esto facilita mucho en varios tipos de configuraciones, donde se deben mantener gráficos del mismo activo pero en diferentes periodos visibles en la pantalla de tráding, ahora el delimitador que bloquea el ancho del gráfico, lo facilitará en casos aún más específicos, pero tiene otra utilidad muy grande que presentaré en otro artículo, pero de momento se puede utilizar para poder controlar que indicador debe tener un mayor ancho, por una u otra razón.

Se puede usar la combinación de todos los delimitadores, o sólo usar el que realmente se necesita en un indicador, no hay reglas sobre su prioridad, la única regla es que el nombre del indicador debe ir antes que cualquier otra cosa. Volviendo a la explicación del código, observen las siguientes líneas:

#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
           
//....                                                     
                case '<':
                        macro_GetData('>');

Fíjense que definimos algo que para muchos puede ser extraño, pero el nombre es muy sugerente: macro_GetData(A), esto creará un trozo de código que es una macro, cuando el compilador encuentre esta definición en el código, él, compilador, sustituirá la declaración por el código de la macro, esto es muy útil cuando vamos a repetir un determinado trozo de código en varios puntos, pero con una variación mínima entre una declaración y otra, en el ejemplo anterior la línea verde sería sustituida y el código generado por el compilador sería como se ve a continuación:

case '<':
	b0 = false;
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != '>'); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
//....

Este tipo de cosas es la representación más fiel de la filosofía, reutilizar todo lo posible, escribir lo menos posible. Ahora vamos a ver algo que se puede cambiar si se desea para que la sintaxis sea más clara, es un pequeño detalle, pero que puede resultar mucho más agradable al adaptarlo a nuestro estilo personal. Observen la siguiente línea:

//.....

if (sz1 == "1M") m_Period = PERIOD_M1; else

//.....

La información resaltada es el gran detalle, por la plantilla que utilicé, el usuario cuando desea utilizar un periodo de 1 minuto debe informarlo utilizando la siguiente sintaxis: < 1M >, pero esto se puede cambiar si se desea dejar la cosa con un estilo personal, pero las letras tienen que estar en mayúsculas y sin espacio, se puede por ejemplo sustituir el fragmento resaltado por "1MIN", "MIN_1", "1_MINUTO" o algo aún más obvio como: "LOCK_IN_1_MIN" o en español "BLOQUEAR_EM_1_MIN", esto se puede hacer y funcionará, siempre y cuando no haya espacios entre las palabras, esta limitación en cuanto al espacio es algo que se puede quitar, pero en mi opinión quizás no haya necesidad real de hacerlo, vean que bonito es el hecho de saber programar, puedes dejar las cosas con un estilo personalizado. El siguiente código que cambió fue el destructor por defecto.

~C_TemplateChart() 
{
        for (char c0 = 0; c0 < m_Counter; c0++)
        {
                ObjectDelete(m_Id, m_Info[c0].szObjName);
                SymbolSelect(m_Info[c0].szSymbol, false);
        }
}

La línea resaltada se agregó para que si el activo no está abierto en una ventana separada, ya no aparezca en la Market Watch, esto evita que los activos que no están en uso permanezcan allí ocupando un espacio innecesario y contaminando la ventana. Ahora veamos algo de lo que prometí hablar en el artículo anterior, pero que no estaba en el código original, pero que será parte del código a partir de este momento.

void AddTemplate(const eTypeChart type, const string szTemplate, int scale, int iSize)
{
        if (m_Counter >= def_MaxTemplates) return;
        if (type == SYMBOL) SymbolSelect(szTemplate, true);
        SetBase((type == INDICATOR ? _Symbol : szTemplate), scale, iSize);
        if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl");
        ChartRedraw(m_handle);
}

La línea resaltada hace algo que dije que se podía hacer, usar un archivo de configuración por defecto si todos los activos a observar usasen la misma configuración. No es bueno hacer algo sin entender realmente cómo se debe proceder si las cosas no son como se espera, pero si la configuración es EXACTAMENTE la misma ¿por qué no usar la misma configuración? Bien, observe que cuando no se encuentra el archivo de configuración del activo especificado, se considerará que quiere utilizar las configuraciones por defecto de MetaTrader 5, y esto se define como el archivo llamado DEFAULT.TPL que se encuentra en el directorio Profiles\Templates pero entendamos una cosa aquí y esto es importante. ¿Por qué no he informado de ningún directorio de búsqueda en la función ChartApplyTemplate? La razón es que la búsqueda la realiza MetaTrader 5 siguiendo una determinada lógica, y conocer cómo funciona esta lógica puede ayudarte a afrontar las cosas de una forma muy interesante y con menos estrés.

Supongamos el siguiente escenario, en el que sustituiríamos la línea resaltada por esta otra:

if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""MyTemplates\\Default.tpl");

MetaTrader 5 buscaría primero el fichero de configuración dentro del subdirectorio MYTEMPLATES del directorio donde se encuentra el ejecutable del indicador personalizado, es decir, si en la misma carpeta donde se encuentra el ejecutable que estamos utilizando para producir múltiples indicadores hay una carpeta llamada MYTEMPLATES, MetaTrader 5 buscará allí el fichero que queremos. Sin embargo si no se encuentra nada ahí, buscará el mismo archivo pero ahora en la carpeta MQL5\Profiles\Templates\MyTemplates, por eso no lo mostré antes, pero no es solo eso, hay un detalle extra en este mismo código, supongamos que es como se muestra a continuación:

if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""\\MyTemplates\\Default.tpl");

Un pequeño detalle que lo cambia todo, ahora MetaTrader 5 buscará primero el archivo que queremos en el directorio MQL5\MyTemplates, y si no encuentra el archivo requerido ejecutará los pasos informados anteriormente. Esto lo puedes ver viendo la documentación de ChartApplyTemplate, es decir, no quería confundirlos a ustedes que tal vez no saben cómo funciona MetaTrader 5 dándoles una falsa idea de cómo funciona, pero ahora que entienden cómo se busca el archivo de configuración, pueden crear variaciones y saber dónde deben poner los archivos.

La siguiente función de nuestra clase que ha sufrido cambios importantes se puede ver a continuación:

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
        }
        ChartRedraw();
#undef macro_SetInteger
}

Los puntos resaltados son los cálculos más importantes de esta rutina, ajustarán las cosas para que las ventanas se ajusten según el deseo del usuario, pero fíjense que las ventanas que se pusieron con tamaño fijo crearán un área libre donde las otras tendrán sus dimensiones ajustadas, pero si la ventana principal tiene su ancho reducido, el cálculo en azul no hará que las ventanas de tamaño fijo tengan sus anchos reducidos, esto puede ser un problema, pero lo dejaremos para el futuro, ya que las ventanas de ancho fijo tienen otra utilidad que será mejor explorada en el futuro. Y la última rutina de nuestra clase se puede ver a continuación:

void AddThese(const eTypeChart type, string szArg)
{
        string szLoc;
        int i0, iSize;
//....
        Decode(szLoc, i0, iSize);
        AddTemplate(type, szLoc, i0, iSize);
//....
}

Obsérvese que lo único que ha sufrido modificaciones está resaltado, es decir, prácticamente nada.


Conclusión

Espero que este artículo junto con otros muestren lo interesante que es una programación estructurada, con pequeñas modificaciones y un poco de calma, podemos añadir mucha funcionalidad a un sistema y la reutilización del código hace que el trabajo de mantenimiento sea mucho menor ya que cuanto más se utiliza un código, menos posibilidades tiene de contener errores. En el próximo artículo transportaremos este sistema a otros lugares, donde puede ser más interesante para muchos y así entender más cosas sobre la programación. Así que hasta...



Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/10239

Plantilla para proyectar el MVC y posibilidades de uso (Parte 2): Esquema de interacción entre los tres componentes Plantilla para proyectar el MVC y posibilidades de uso (Parte 2): Esquema de interacción entre los tres componentes
Este artículo continúa y completa el tema planteado en el último artículo: la plantilla MVC en los programas MQL. En este artículo, veremos un posible esquema de interacción entre estos tres componentes.
Cómo hacer el gráfico más interesante: Adicionando un fondo de pantalla Cómo hacer el gráfico más interesante: Adicionando un fondo de pantalla
Muchos terminales de trabajo contienen alguna imagen representativa que muestra algo sobre el usuario, estas imágenes hacen que el escritorio sea más bonito y alegre. Descubra cómo hacer el gráfico más interesante poniendo un fondo de pantalla.
Usando la clase CCanvas en las aplicaciones MQL Usando la clase CCanvas en las aplicaciones MQL
En este artículo, hablaremos sobre el uso de la clase CCanvas en las aplicaciones MQL, ofreciendo un análisis detallado y con ejemplos del tema. Asimismo, mostraremos a los usuarios los fundamentos necesarios para trabajar con esta herramienta.
Gráficos en la biblioteca DoEasy (Parte 96): Trabajando con los eventos del ratón y los gráficos en los objetos de formulario Gráficos en la biblioteca DoEasy (Parte 96): Trabajando con los eventos del ratón y los gráficos en los objetos de formulario
En este artículo, comenzaremos a desarrollar las funciones necesarias para trabajar con los eventos del ratón en los objetos de formulario y añadiremos nuevas propiedades y la monitorización de las mismas al objeto de símbolo. Además, hoy finalizaremos la clase de objeto símbolo, ya que, desde el momento en que la escribimos, los símbolos gráficos han adquirido nuevas propiedades que debemos considerar, y cuyos cambios tenemos que monitorear.