Português
preview
Del nivel básico al intermedio: Objetos y subventanas (II)

Del nivel básico al intermedio: Objetos y subventanas (II)

MetaTrader 5 — Ejemplos |
37 0
CODE X
CODE X

Introducción

En el artículo anterior, Del nivel básico al intermedio: Objetos y subventanas (I), vimos cómo podíamos dirigir objetos creados en un indicador a la subventana. Sin embargo, si prestaste atención, habrás notado que mencioné la existencia de un fallo en aquel procedimiento. Precisamente por el mecanismo utilizado para identificar el índice de la subventana en el gráfico, existe un problema. Tenemos además otro problema que, a mi entender, merece destacarse y resolverse primero. Este problema no se mencionó en el artículo anterior porque posiblemente no lo habrías entendido antes de probar por ti mismo lo que se mostró allí.

En este artículo abordaremos algunas formas de resolver ciertos errores que pueden aparecer al utilizar lo que hemos visto hasta ahora. Así que me gustaría que dejaras a un lado por un momento las distracciones del día a día y te concentraras en lo que vamos a explicar, ya que el origen de algunos errores puede resultar bastante difícil de comprender.


Objetos y subventanas (II)

Si has estado practicando y estudiando cada uno de estos artículos, ya debes haber comprendido que el objetivo del lenguaje MQL5 es precisamente permitir que los programadores puedan, de alguna manera, controlar el comportamiento y el funcionamiento de MetaTrader 5 para que la plataforma genere algún resultado previsto. Esta es la parte sencilla, aunque pocos llegan realmente a comprenderla.

La parte complicada es entender que no siempre podrás hacer las cosas sin comprender antes ciertos mecanismos y conceptos que utiliza MetaTrader 5. Uno de estos conceptos se explicó anteriormente y se refiere precisamente al hecho de que NO PODEMOS añadir dos instancias del mismo indicador en un mismo gráfico. Esto es un hecho y está garantizado por los mecanismos internos de MetaTrader 5. Sin embargo, si se modifica algún argumento o parámetro de entrada del indicador, podremos añadir una segunda instancia en el mismo gráfico, ya que, en este caso, MetaTrader 5 interpretará que esa segunda o nueva instancia todavía no está presente en el gráfico.

De acuerdo, pero ¿por qué motivo, razón o circunstancia vuelves a mencionarlo? Bien, mi amigo lector, el motivo es que existen algunos problemas en los códigos vistos hasta ahora que pueden complicar bastante las cosas. Y esos problemas aparecen precisamente cuando intentamos perfeccionar algo en el propio código. Así que necesito que entiendas muy bien lo siguiente: aunque los códigos vistos hasta ahora funcionan, como puede comprobarse en cada uno de los artículos mediante las animaciones que representan exactamente lo que hace el código, al modificarlos pueden producir resultados bastante confusos. Esto se debe precisamente a que faltan ciertos detalles en cada uno de los códigos.

Para que esto quede más claro y resulte más fácil de entender, vamos a utilizar uno de los códigos incluidos en el anexo del artículo anterior. Al utilizar uno de esos códigos, obtendremos el resultado que puede verse en la siguiente animación.

Animación 01

Hasta este punto, como podemos ver en la animación 01, todo funciona correctamente, lo que demuestra que el resultado obtenido es exactamente el previsto. Ahora, si abres la lista de indicadores presentes en este mismo gráfico que aparece en la animación 01, verás lo siguiente.

Imagen 01

Ahora viene la cuestión: si intentas añadir de nuevo el indicador que aparece en la animación 01, MetaTrader 5 impedirá que el código vuelva a ejecutarse. Esto evitará que se cree una nueva subventana en el gráfico. Este bloqueo se debe precisamente a que el indicador resaltado en la imagen 01 está presente en el gráfico. Ahora bien, si eliminas ese indicador resaltado en la imagen 01, la situación cambia.

Para que esto resulte más fácil de entender, deberás hacer que el contenido que aparece en la imagen 01 quede como se muestra en la imagen 02, a continuación.

Imagen 02

Observa que ahora MetaTrader 5 ya no impedirá que volvamos a añadir el indicador que aparece en la animación 01. Naturalmente, pensarás que esto se debe a lo visto en el artículo anterior, donde posiblemente la nueva ventana recibirá el objeto presente en el código. Pero, como se explicó antes, esto no ocurrirá precisamente porque el nombre del objeto que se va a utilizar indica que ya está presente en el gráfico. Sin embargo, esta todavía no es la cuestión. La cuestión es: ¿por qué, cuando eliminamos el indicador de la lista, como se muestra en la imagen 02, la subventana creada por él no desaparece también?

Precisamente porque la subventana no se eliminó junto con el indicador, acabamos creando un posible fallo en nuestra aplicación. Esto ocurre simplemente porque los distintos elementos no están completamente vinculados entre sí. De este modo, el resultado puede ser ahora algo parecido a lo que se muestra a continuación.

Animación 02

Observa cómo un simple detalle ha cambiado por completo el resultado y, por así decirlo, ha hecho que todo resulte mucho más confuso, ofreciendo al usuario una pésima experiencia con nuestra aplicación. Entonces, ¿cómo podemos corregir este tipo de fallo y evitar este problema? Bien, la mejor y más sencilla forma de hacerlo será vincular todos los códigos entre sí. Así, si se elimina algún elemento, el código principal recibirá una notificación y podrá actuar en consecuencia, garantizando que nada permanezca en el gráfico ni sea eliminado de forma indebida o incorrecta. Observa que aquí se están mencionando dos tipos de situaciones.

La primera es que algo que no debería eliminarse termine siendo retirado del gráfico. La segunda se refiere a si permanecen o no elementos que deberían eliminarse cuando el código principal se retira del gráfico.

Es muy importante que entiendas que existen estas dos situaciones, ya que cada una exige un tratamiento completamente distinto. Bien, una vez hecha esta distinción, podemos pasar a realizar las correcciones correspondientes. Recuerda que los mecanismos que se muestran aquí pueden y deben adaptarse a cada situación específica. Vamos a comenzar haciendo que, al retirar el código principal del gráfico, este también elimine el indicador presente en la subventana que creó el propio código principal. Aunque, en principio, eliminar el indicador puede parecer algo sencillo, ya que todo lo que necesitamos hacer es indicar dónde se encuentra y cuál es el nombre del indicador que queremos eliminar, en algunos casos esta eliminación puede resultar bastante compleja. Pero empecemos por el caso más sencillo. Para ello, primero tendremos que observar los códigos que vamos a utilizar.

El primer código se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_height          150
08. #property indicator_plots           0
09. //+------------------------------------------------------------------+
10. #define def_ShortName "Test 123."
11. //+------------------------------------------------------------------+
12. class C_MiniChart
13. {
14.     private :
15. //+----------------+
16.         string  m_szNameObject;
17.         int     m_Sub;
18. //+----------------+
19.         void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); }
20. //+----------------+
21.         int WhatSubWin(void)
22.         {
23.             for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++)
24.                 for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--)
25.                     if (ChartIndicatorName(0, c0, c1) == def_ShortName)
26.                         return c0;
27.             return INVALID_HANDLE;
28.         }
29. //+----------------+
30.     public  :
31. //+----------------+
32.         C_MiniChart(string szName)
33.         {
34.             if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return;
35.             ObjectCreate(0, m_szNameObject = szName, OBJ_CHART, m_Sub, 0, 0);
36.             ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol);
37.             SetInteger(OBJPROP_XDISTANCE, 10);
38.             SetInteger(OBJPROP_YDISTANCE, 0);
39.             SetInteger(OBJPROP_XSIZE, 300);
40.             SetInteger(OBJPROP_YSIZE, indicator_height);
41.             SetInteger(OBJPROP_PERIOD, PERIOD_M15);
42.             SetInteger(OBJPROP_DATE_SCALE, false);
43.             SetInteger(OBJPROP_PRICE_SCALE, false);
44.             SetInteger(OBJPROP_SELECTABLE, true); 
45.             SetInteger(OBJPROP_SELECTED, false);
46.         }
47. //+----------------+
48.         ~C_MiniChart()
49.         {
50.             ObjectDelete(0, m_szNameObject);
51.             ChartRedraw();
52.         }
53. //+----------------+
54. }*gl_MiniChart;
55. //+------------------------------------------------------------------+
56. int OnInit(void)
57. {
58.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
59. 
60.     gl_MiniChart = new C_MiniChart("Mini Chart");
61.    
62.     return INIT_SUCCEEDED;
63. };
64. //+------------------------------------------------------------------+
65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
66. {
67.     return rates_total;
68. };
69. //+------------------------------------------------------------------+
70. void OnDeinit(const int reason)
71. {
72.     delete gl_MiniChart;
73. };
74. //+------------------------------------------------------------------+

Código 01

El segundo código se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_plots           0
08. //+------------------------------------------------------------------+
09. #define def_Resource "Code 01.ex5"
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. int OnInit(void)
14. {
15.     ChartIndicatorAdd(0, (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
16.     
17.     return INIT_SUCCEEDED;
18. };
19. //+------------------------------------------------------------------+
20. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
21. {
22.     return rates_total;
23. };
24. //+------------------------------------------------------------------+

Código 02

Ahora presta atención, porque lo que se explicará es muy importante. Para eliminar un indicador del gráfico utilizaremos la función ChartIndicatorDelete, lo cual no supone ninguna complicación, ya que la documentación explica muy bien qué tipo de datos debemos utilizar. Lo importante es que el tercer parámetro que se pasa a la función ChartIndicatorDelete debe ser el nombre del indicador que vamos a eliminar. ¿Pero qué nombre? Bien, esto depende de cada caso concreto. Por eso necesitas prestar mucha atención a lo que implementes en tu código. En nuestro caso específico, el nombre que debemos utilizar se declara en la línea diez del código 01. Sin embargo, la función deberá utilizarse en el código 02. Hum, ¿cómo es eso? Parece no tener sentido.

Sin embargo, como lo que estamos implementando aquí es bastante sencillo, precisamente con fines didácticos, hacerlo resulta muy directo. Bastará con modificar el código 02, como se muestra a continuación.
01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #property indicator_chart_window
07. #property indicator_plots           0
08. //+------------------------------------------------------------------+
09. #define def_Resource "Code 01.ex5"
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. int gl_subWin;
14. //+------------------------------------------------------------------+
15. int OnInit(void)
16. {
17.     ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
18.     
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnDeinit(const int reason)
28. {
29.     ChartIndicatorDelete(0, gl_subWin, "Test 123.");
30. };
31. //+------------------------------------------------------------------+

Código 03

Observa que, en la línea trece de este código 03, estamos declarando una variable. Esta se inicializará en la línea diecisiete, donde obtenemos la subventana en la que se encuentra el indicador que vamos a colocar en el gráfico. Pero observa que, en la línea veintinueve de este código 03, estamos utilizando un dato que, en realidad, aparece en la línea diez del código 01. Esto creará el vínculo que buscamos. Pero, y siempre hay un pero, esta forma de implementar las funcionalidades, aunque funciona, también hace que todo resulte mucho menos práctico, dificultando el mantenimiento a medida que el código crezca. No olvides que lo que estamos viendo aquí es solo una demostración. Así que no te dejes engañar por la aparente sencillez de lo que ves aquí. Antes de profundizar aún más en esto, veamos el resultado que acabamos de obtener al implementar el código de esta nueva forma.

Esto se muestra en la siguiente animación.

Animación 03

Magnífico. Esta sencilla demostración de la animación 03 ya nos proporciona cierta tranquilidad y un mayor control sobre nuestro código. Pero, como se ha dicho, no podemos, o mejor dicho, no debemos aceptar las cosas de esta manera, especialmente en un código que todavía está en desarrollo. Así que la mejor alternativa será hacer algunas modificaciones para que la implementación resulte más adecuada. Al menos, pensando en el desarrollo futuro del código.

Para conseguirlo, comenzaremos a trabajar con archivos de inclusión, también llamados archivos de cabecera. Esta es la parte sencilla. Primero, creamos un archivo en un directorio común para cualquier código que forme parte de la aplicación final. Normalmente, este archivo estaría en el directorio include dentro del directorio raíz. Pero, en algunos casos, resulta más sencillo y práctico mantener este archivo de cabecera junto con los archivos del código fuente de la aplicación. Sin embargo, para mantener cierto orden, colocaremos todo en una carpeta include, dentro de la carpeta del proyecto principal. Esto puede sonar un tanto confuso y complicado. No obstante, en la práctica verás que es mucho más sencillo de lo que parece. Básicamente, lo hacemos así:

1. //+------------------------------------------------------------------+
2. #define def_Resource "Code 03.ex5"
3. //+------------------------------------------------------------------+
4. #define def_ShortName "Test 123."
5. //+------------------------------------------------------------------+

Código 04

Este código 04 será nuestro archivo de cabecera. Ahora vamos a centrar nuestra atención en los códigos de los indicadores y los modificaremos como se muestra a continuación.
01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\defines.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_height          150
10. #property indicator_plots           0
11. //+------------------------------------------------------------------+
12. class C_MiniChart
13. {
14.     private :
15. //+----------------+
16.         string  m_szNameObject;
17.         int     m_Sub;
18. //+----------------+
19.         void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); }
20. //+----------------+
21.         int WhatSubWin(void)
22.         {
23.             for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++)
24.                 for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--)
25.                     if (ChartIndicatorName(0, c0, c1) == def_ShortName)
26.                         return c0;
27.             return INVALID_HANDLE;
28.         }
29. //+----------------+
30.     public  :
31. //+----------------+
32.         C_MiniChart(string szName)
33.         {
34.             if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return;
35.             ObjectCreate(0, m_szNameObject = szName, OBJ_CHART, m_Sub, 0, 0);
36.             ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol);
37.             SetInteger(OBJPROP_XDISTANCE, 10);
38.             SetInteger(OBJPROP_YDISTANCE, 0);
39.             SetInteger(OBJPROP_XSIZE, 300);
40.             SetInteger(OBJPROP_YSIZE, indicator_height);
41.             SetInteger(OBJPROP_PERIOD, PERIOD_M15);
42.             SetInteger(OBJPROP_DATE_SCALE, false);
43.             SetInteger(OBJPROP_PRICE_SCALE, false);
44.             SetInteger(OBJPROP_SELECTABLE, true); 
45.             SetInteger(OBJPROP_SELECTED, false);
46.         }
47. //+----------------+
48.         ~C_MiniChart()
49.         {
50.             ObjectDelete(0, m_szNameObject);
51.             ChartRedraw();
52.         }
53. //+----------------+
54. }*gl_MiniChart;
55. //+------------------------------------------------------------------+
56. int OnInit(void)
57. {
58.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
59. 
60.     gl_MiniChart = new C_MiniChart("Mini Chart");
61.    
62.     return INIT_SUCCEEDED;
63. };
64. //+------------------------------------------------------------------+
65. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
66. {
67.     return rates_total;
68. };
69. //+------------------------------------------------------------------+
70. void OnDeinit(const int reason)
71. {
72.     delete gl_MiniChart;
73. };
74. //+------------------------------------------------------------------+

Código 05

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\defines.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_plots           0
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. #define def_ShortNamePrinc def_ShortName + "_main"
14. //+------------------------------------------------------------------+
15. int gl_subWin;
16. //+------------------------------------------------------------------+
17. int OnInit(void)
18. {
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc);
20.     ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
21.     
22.     return INIT_SUCCEEDED;
23. };
24. //+------------------------------------------------------------------+
25. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
26. {
27.     return rates_total;
28. };
29. //+------------------------------------------------------------------+
30. void OnDeinit(const int reason)
31. {
32.     ChartIndicatorDelete(0, gl_subWin, def_ShortName);
33. };
34. //+------------------------------------------------------------------+

Código 06

Observa que en el código 06 añadí la línea trece para poder asignar un nombre al indicador principal. Esto nos será bastante útil dentro de poco. Pero, por ahora, lo que nos interesa es que podemos ajustar las cosas de una forma mucho más sencilla y práctica, sin correr el riesgo de acabar creando un monstruo incontrolable con el paso del tiempo. Existe otra forma de hacer algo muy parecido, pero sin utilizar un archivo de cabecera. Sin embargo, por ahora utilizaremos este método más sencillo. Con esto, el resultado de ejecutar los códigos anteriores será el mismo que el de la animación 03. Así que podemos pasar al siguiente paso.

Ahora necesitamos tomar algunas decisiones que definirán lo que haremos a continuación. De cualquier forma, necesito que tengas, como mínimo, unos conocimientos básicos sobre cómo se generan y se manejan los eventos. Para ello, existen artículos anteriores de esta misma serie en los que se explicaron precisamente estos fundamentos. En resumen, necesitarás haber comprendido el contenido de dos artículos:

Del nivel básico al intermedio: Eventos (I)

Del nivel básico al intermedio: Eventos (II)

Comprender estos dos artículos es fundamental para seguir lo que haremos a partir de ahora, independientemente de la decisión que tomemos. La idea aquí es iniciar una comunicación entre códigos diferentes. Y, para hacerlo, utilizaremos eventos. De esta forma, los dos indicadores pasarán a comunicarse entre sí y ejecutarán las acciones que hayamos definido para ellos.

No olvides que, llegado este punto, las decisiones que tomemos deberán evaluarse con calma.

Pues bien, la primera decisión se refiere al comportamiento del código cuando eliminemos algún objeto creado por cualquiera de nuestros indicadores. Este tipo de situación, que en principio puede parecer una tontería, puede cambiar por completo la experiencia del usuario o del operador. También define cómo vamos a implementar nuestro código. En principio, seguiremos la siguiente idea: si se elimina algún objeto creado por uno de los indicadores, el propio indicador que creó dicho objeto también deberá eliminarse del gráfico. Sé que esta decisión puede resultar un tanto punitiva para el usuario o el operador. Pero, como aquí el objetivo no es crear algo específico, sino demostrar cómo pueden hacerse determinadas cosas, no veo ningún problema en adoptar este enfoque.

Saber si un objeto ha sido eliminado o no del gráfico no es una de las tareas más complicadas. Sin embargo, hacerlo manualmente es, como mínimo, una pérdida de tiempo, ya que podemos indicarle a MetaTrader 5 que nos informe de qué objeto se ha eliminado del gráfico. Sabiendo esto, resulta mucho más sencillo implementar el resto del procedimiento.

El problema es que, de forma predeterminada, MetaTrader 5 no dispara determinados eventos. Tenemos que indicarle que queremos recibir una notificación cuando se produzcan. Y uno de ellos es precisamente el evento de eliminación de objetos del gráfico. Pero no te preocupes, indicarle a MetaTrader 5 que queremos recibir una notificación cuando se elimine algún objeto es bastante sencillo. Todo lo que tenemos que hacer es enviar un comando a MetaTrader 5 mediante código MQL5. La cuestión no es cómo hacerlo, sino dónde colocar dicho comando.

Me explico: no necesitas añadir necesariamente el comando en todos los indicadores o códigos. Basta con colocarlo en uno de ellos para que, cada vez que se produzca dicho evento, MetaTrader 5 nos informe de ello. Esto se aplica a un gráfico concreto. Personalmente, me gusta utilizar este tipo de comando en un punto clave del código. Normalmente, lo utilizo dentro del archivo principal. En otros casos, lo utilizo en el archivo donde se crean los objetos que es necesario supervisar. La decisión de dónde colocar el comando queda a tu criterio. Como quiero simplificar las cosas al máximo, vamos a usar el comando en lo que puede considerarse el archivo principal del código. Así, al modificar el código 06, obtenemos lo que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\defines.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_plots           0
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. #define def_ShortNamePrinc def_ShortName + "_main"
14. //+------------------------------------------------------------------+
15. int gl_subWin;
16. //+------------------------------------------------------------------+
17. int OnInit(void)
18. {
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc);
20.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
21.     ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
22. 
23.     return INIT_SUCCEEDED;
24. };
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. };
30. //+------------------------------------------------------------------+
31. void OnDeinit(const int reason)
32. {
33.     ChartIndicatorDelete(0, gl_subWin, def_ShortName);
34.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
35. };
36. //+------------------------------------------------------------------+

Código 07

Observa que, en la línea veinte de este código 07, le estamos indicando a MetaTrader 5 que queremos recibir una notificación cuando se elimine un objeto del gráfico. Como buena práctica de programación, también es conveniente indicar que ya no queremos seguir recibiendo esas mismas notificaciones cuando dejemos de necesitarlas. Por esta razón, también incluimos, en la línea treinta y cuatro, una llamada que desactiva dichas notificaciones. Pero esto también puede hacerse en momentos concretos en los que no queramos recibir notificaciones mientras eliminamos algún objeto innecesario del gráfico. Muy bien, esta es la primera parte. Ahora pasemos a la segunda, que consiste precisamente en decidir qué ocurrirá cuando se elimine algún objeto.

Aquí podemos hacer dos cosas. La primera es simplemente volver a crear dicho objeto. La segunda consiste en eliminar el indicador o la aplicación que creó ese objeto. Como este segundo enfoque, en principio, resulta algo más sencillo, tanto desde el punto de vista de la implementación como porque evita que tengamos que preocuparnos por los parámetros internos del propio objeto, lo adoptaremos como mecanismo inicial de gestión.

Aquí es donde las cosas empiezan a ponerse un tanto interesantes, aunque también pueden volverse bastante confusas. Para entenderlo, observa el código 05. En él creamos los objetos que se mostrarán en el gráfico. Pero fíjate en algo. El nombre del objeto se declara en la línea sesenta del código 05. En principio, hacer las cosas de esta forma no debería ocasionarnos problemas. Sin embargo, puede ocurrir que el usuario o el operador haya colocado previamente en el gráfico un objeto con el mismo nombre, antes de añadir el indicador. En ese caso, podríamos generar una inconsistencia. Para evitarlo, necesitamos que el objeto creado por nuestro código tenga un nombre único y fácil de reconocer. Por eso vamos a cambiar la forma en que se genera su nombre.

Para no crear un archivo enorme sin necesidad, vamos a extraer la clase de este código 05 y crear así otro archivo de cabecera. Este se muestra a continuación, ya con la corrección aplicada a la forma de generar el nombre del objeto.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "defines.mqh"
05. //+------------------------------------------------------------------+
06. class C_MiniChart
07. {
08.     private :
09. //+----------------+
10.         string  m_szNameObject;
11.         int     m_Sub;
12. //+----------------+
13.         void SetInteger(ENUM_OBJECT_PROPERTY_INTEGER prop, int value) { ObjectSetInteger(0, m_szNameObject, prop, value); }
14. //+----------------+
15.         int WhatSubWin(void)
16.         {
17.             for (int c0 = 0; c0 < (int)(ChartGetInteger(0, CHART_WINDOWS_TOTAL)); c0++)
18.                 for (int c1 = (ChartIndicatorsTotal(0, c0) - 1); c1 >= 0; c1--)
19.                     if (ChartIndicatorName(0, c0, c1) == def_ShortName)
20.                         return c0;
21.             return INVALID_HANDLE;
22.         }
23. //+----------------+
24.     public  :
25. //+----------------+
26.         C_MiniChart(string szName)
27.         {
28.             if ((m_Sub = WhatSubWin()) == INVALID_HANDLE) return;
29.             ObjectCreate(0, m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1) , OBJ_CHART, m_Sub, 0, 0);
30.             ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol);
31.             SetInteger(OBJPROP_XDISTANCE, 10);
32.             SetInteger(OBJPROP_YDISTANCE, 0);
33.             SetInteger(OBJPROP_XSIZE, 300);
34.             SetInteger(OBJPROP_YSIZE, indicator_height);
35.             SetInteger(OBJPROP_PERIOD, PERIOD_M15);
36.             SetInteger(OBJPROP_DATE_SCALE, false);
37.             SetInteger(OBJPROP_PRICE_SCALE, false);
38.             SetInteger(OBJPROP_SELECTABLE, true); 
39.             SetInteger(OBJPROP_SELECTED, false);
40.         }
41. //+----------------+
42.         ~C_MiniChart()
43.         {
44.             ObjectDelete(0, m_szNameObject);
45.             ChartRedraw();
46.         }
47. //+----------------+
48.         bool operator==(const string szArg) { return (szArg == m_szNameObject); }
49. //+----------------+
50. };
51. //+------------------------------------------------------------------+

Código 08

La corrección para crear un nombre único se realiza en la línea veintinueve de este código 08. Ahora observa que, en la línea cuarenta y ocho, estamos creando un operador sobrecargado para probar la propia clase. Esto es importante para poder verificar si el objeto eliminado es o no uno de los que hemos creado en los indicadores. En esta misma serie explicamos la sobrecarga de operadores. Consulta los artículos para obtener más detalles; el primero es Del nivel básico al intermedio: Sobrecarga de operadores (I). Como he dicho, los conocimientos se van acumulando con el tiempo. Ahora podemos ver cómo ha quedado el antiguo código 05. Se muestra íntegramente a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\C_MiniChart.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_height          150
10. #property indicator_plots           0
11. //+------------------------------------------------------------------+
12. C_MiniChart *gl_MiniChart;
13. //+------------------------------------------------------------------+
14. int OnInit(void)
15. {
16.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
17.     gl_MiniChart = new C_MiniChart("Mini Chart");
18.    
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
28. {
29.     switch (id)
30.     {
31.         case CHARTEVENT_OBJECT_DELETE:
32.             if (gl_MiniChart == sparam)
33.             {
34.                 ChartIndicatorDelete(0, ChartWindowFind(0, def_ShortName), def_ShortName);
35.                 OnDeinit(REASON_INITFAILED);
36.             }
37.             break;      
38.     }
39. };
40. //+------------------------------------------------------------------+
41. void OnDeinit(const int reason)
42. {
43.     delete gl_MiniChart;
44. };
45. //+------------------------------------------------------------------+

Código 09

Aunque este código no es muy elegante, resulta bastante funcional. Sin embargo, hay que prestar atención a algunos detalles para evitar sorpresas en el futuro. Uno de ellos es lo que ocurre en la línea treinta y uno. Observa que aquí estamos capturando el evento CHARTEVENT_OBJECT_DELETE. Sin embargo, este evento no se habilita aquí, en este código 09, sino en el código 07, ya que el código 07 se considera el código principal de la aplicación. El siguiente detalle es que, en la línea treinta y cuatro, estamos eliminando el propio indicador del gráfico y, acto seguido, retirando el código mediante una llamada a OnDeinit.

Pero ¿por qué hacer esto? El motivo es que queremos eliminar por completo cualquier presencia del indicador en el gráfico, para que la siguiente etapa sea algo más sencilla. Podríamos mejorar un poco más las cosas aquí. Pero, por ahora, esto será suficiente. Ahora vamos a modificar el código principal, haciendo que el código 07 quede como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\defines.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_plots           0
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. #define def_ShortNamePrinc def_ShortName + "_main"
14. //+------------------------------------------------------------------+
15. int gl_subWin;
16. //+------------------------------------------------------------------+
17. int OnInit(void)
18. {
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc);
20.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
21.     ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
22. 
23.     return INIT_SUCCEEDED;
24. };
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. };
30. //+------------------------------------------------------------------+
31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
32. {
33.     Print(__FILE__, " -> ", __FUNCTION__, " ?? ", EnumToString((ENUM_CHART_EVENT)id), " >> ", sparam);
34. };
35. //+------------------------------------------------------------------+
36. void OnDeinit(const int reason)
37. {
38.     ChartIndicatorDelete(0, gl_subWin, def_ShortName);
39.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
40. };
41. //+------------------------------------------------------------------+

Código 10

Este código 10 todavía no está terminado. Antes de hacerlo, quiero que veas y entiendas una cosa. Observa que, en la línea treinta y tres, imprimiremos algo en el terminal. Quiero que entiendas precisamente la información que se imprimirá. Así que compilamos los códigos y colocamos en el gráfico la aplicación recién compilada. De esta forma, una vez que los indicadores estén presentes en el gráfico, podremos hacer lo que se muestra en la siguiente animación.

Animación 04

Hum, esta animación 04 me ha parecido un tanto intrigante. De hecho, lo es, pero al mismo tiempo resulta interesante. Ahora quiero que observes algo en particular en esta animación. Para ello, utilizaremos la imagen que se muestra a continuación.

Imagen 03

Presta atención al punto resaltado en la imagen 03. Aunque muchos no entiendan bien qué se está imprimiendo aquí, quiero que observes atentamente esta zona resaltada. Fíjate en que el valor de sparam indica el nombre del objeto que se eliminó del gráfico. Pero, como al definir el nombre utilizamos también una parte del nombre corto del indicador, ahora podemos aprovecharlo.

Para ello, bastará con aislar el nombre del indicador y comprobar si coincide con el que nuestro código principal colocó en el gráfico. Si es así, podremos dirigir la ejecución del código para obtener un determinado resultado. De esta manera, llegamos al punto en el que aquellas decisiones tomadas al principio serán fundamentales para el resultado. Observa que, hasta aquí, hemos hecho todo lo necesario para eliminar el indicador auxiliar del gráfico. Pero esto podría haberse hecho de otras formas.

Como hemos visto, el código 10 es capaz de interceptar los eventos de eliminación del objeto. Por lo tanto, podríamos gestionar esta situación de forma bastante directa, ya que el objetivo principal sería precisamente eliminar los indicadores si se eliminara alguno de los objetos que estos colocaron en el gráfico. Pero, como ya hemos realizado parte de la eliminación directamente en el código del indicador auxiliar, tal como puede verse en el código 09, ahora solo necesitamos eliminar del gráfico el propio indicador principal en cuanto detecte que el indicador auxiliar ha sido eliminado. Para hacerlo correctamente y sin contratiempos, necesitamos modificar el código 10 para que quede como se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\defines.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_plots           0
10. //+------------------------------------------------------------------+
11. #resource def_Resource
12. //+------------------------------------------------------------------+
13. #define def_ShortNamePrinc def_ShortName + "_main"
14. //+------------------------------------------------------------------+
15. int gl_subWin;
16. //+------------------------------------------------------------------+
17. int OnInit(void)
18. {
19.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortNamePrinc);
20.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
21.     ChartIndicatorAdd(0, gl_subWin = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL), iCustom(NULL, NULL, "::" + def_Resource));
22. 
23.     return INIT_SUCCEEDED;
24. };
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. };
30. //+------------------------------------------------------------------+
31. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
32. {
33.     switch (id)
34.     {
35.         case CHARTEVENT_OBJECT_DELETE:
36.             {
37.                 string szRet[];
38.                 StringSplit(sparam, '#', szRet);
39.                 if (szRet[0] == def_ShortName)
40.                     ChartIndicatorDelete(0, 0, def_ShortNamePrinc);
41.             }
42.             break;
43.     }
44. };
45. //+------------------------------------------------------------------+
46. void OnDeinit(const int reason)
47. {
48.     ChartIndicatorDelete(0, gl_subWin, def_ShortName);
49.     ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
50. };
51. //+------------------------------------------------------------------+

Código 11

Ahora observa cómo hemos resuelto el problema de acuerdo con la decisión tomada anteriormente. Lo único que hubo que hacer fue añadir una forma de filtrar el parámetro sparam para obtener el nombre del indicador. Una vez conseguido esto, utilizamos la línea treinta y nueve para comprobar si el nombre coincide con el definido en el código 04. Si esta condición es verdadera, lo siguiente será finalizar el indicador principal, tal como se muestra en la línea cuarenta. Y esto eliminará por completo ambos indicadores del gráfico.

Este enfoque que se muestra aquí es solo una de las opciones que tenemos para mantener un buen control sobre cómo se presenta nuestro código al usuario. Sé que, en principio, este tipo de planteamiento acaba castigando al usuario o al operador que elimina indiscriminadamente objetos del gráfico. Pero, a mi entender, este enfoque es considerablemente más sencillo, precisamente porque no necesitamos preocuparnos por conservar determinados datos sobre el posicionamiento ni sobre las características de cada objeto presente en el gráfico, lo que facilita bastante la comprensión del código.

Sin embargo, al ver lo que hace este código 11, quizá te estés preguntando: ¿no habría una forma más sencilla de hacer todo esto? Al fin y al cabo, antes de empezar, el código principal ya era capaz de eliminar el indicador auxiliar del gráfico. Y sí, mi querido lector. Por esta razón, en el anexo tendrás acceso a dos códigos diferentes: uno mostrará lo que hemos hecho aquí, mientras que el otro permitirá comprobar que al final necesitamos mucho menos código. Para seleccionar uno u otro, bastará con modificar un detalle en el código 04. Así que presta atención: en el anexo, originalmente, el código 04 será el mismo que hemos visto hasta este punto del artículo. Sin embargo, también tendrás otro código para el indicador auxiliar. Este se muestra a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\C_MiniChart.mqh"
07. //+------------------------------------------------------------------+
08. #property indicator_chart_window
09. #property indicator_height          150
10. #property indicator_plots           0
11. //+------------------------------------------------------------------+
12. C_MiniChart *gl_MiniChart;
13. //+------------------------------------------------------------------+
14. int OnInit(void)
15. {
16.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
17.     gl_MiniChart = new C_MiniChart("Mini Chart");
18.    
19.     return INIT_SUCCEEDED;
20. };
21. //+------------------------------------------------------------------+
22. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
23. {
24.     return rates_total;
25. };
26. //+------------------------------------------------------------------+
27. void OnDeinit(const int reason)
28. {
29.     delete gl_MiniChart;
30. };
31. //+------------------------------------------------------------------+

Código 12

Observa que este código 12 es prácticamente idéntico al código 09, salvo por el hecho de que el código 12 no cuenta con el manejador de eventos presente en el código 09 que hemos visto en este artículo. No obstante, a pesar de la numeración utilizada aquí, en el anexo dicho código 09 tendrá el nombre CODE 03.MQ5. Por tanto, el código presente en el archivo de cabecera deberá ser el mismo que aparece en el código 04 de este artículo. Si quieres ver cómo funciona la aplicación con este código 12, deberás cambiar el contenido del archivo DEFINES.MQH por lo que se muestra a continuación.
1. //+------------------------------------------------------------------+
2. #define def_Resource "Code 06.ex5"
3. //+------------------------------------------------------------------+
4. #define def_ShortName "Test 123."
5. //+------------------------------------------------------------------+

Código 13

Con esto, se compilará CODE 06.MQ5 en lugar de CODE 03.MQ5, que es precisamente el código 12 que acabamos de ver. Esto te permitirá estudiar con más calma cada detalle sobre cómo eliminar indicadores de un gráfico cuando sea necesario. Y, como los resultados de la ejecución son exactamente los mismos, no veo ningún motivo para volver a mostrarlos aquí.


Consideraciones finales

Este artículo, así como los códigos incluidos en el anexo, deben estudiarse con calma y mucha atención. Esto se debe a que cada detalle mostrado aquí influye de forma decisiva en la calidad y en el tipo de resultado que podrás obtener en códigos futuros. Es importante destacar que todo el contenido presentado aquí tiene como único objetivo mostrar la mejor manera posible de evitar comprobar innecesariamente si determinados objetos, indicadores o elementos están o no presentes en el gráfico. Podemos hacer que MetaTrader 5 nos ayude cada vez más en este tipo de tareas, de modo que nuestra verdadera labor consista únicamente en procesar, de la mejor forma posible, los datos que MetaTrader 5 envía a nuestra aplicación.

Existe otra forma de realizar este mismo tipo de tarea. Sin embargo, como resulta bastante más laboriosa, porque necesitamos mantener una lista de propiedades y objetos para poder restaurar los datos cuando dichos objetos sean eliminados, posiblemente se explique en otro artículo futuro.

De cualquier manera, ahora ya sabes que no todo necesita programarse como muchos imaginan. Si entendemos cómo funciona MetaTrader 5, podemos crear aplicaciones con mucha más rapidez y con un riesgo mucho menor de que fallen, precisamente porque nuestro código contendrá únicamente aquello que sea necesario.

Archivo MQ5 Descripción
Indicators\Code 01 Demostración básica
Indicators\Code 02 Demostración básica
Indicators\Code 03 Demostración básica
Indicators\Code 04 Demostración básica
Indicators\Code 05 Demostración básica
Indicators\Code 06 Demostración básica

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

Archivos adjuntos |
Anexo.zip (5.51 KB)
Del nivel básico al intermedio: Objetos y subventanas (III) Del nivel básico al intermedio: Objetos y subventanas (III)
Este artículo describe un flujo seguro para manejar la eliminación de objetos gráficos: interceptar eventos, recrear el objeto y recuperar el conjunto mínimo de propiedades. Mostramos cómo evitar errores al trabajar con punteros, impedir recreaciones indebidas durante el cierre y guardar la posición y las dimensiones, actualizándolas mediante DRAG/CHANGE. De este modo, el indicador se mantiene íntegro y el usuario conserva parte de sus personalizaciones.
Red neuronal en la práctica: Perceptrón Red neuronal en la práctica: Perceptrón
Este artículo presenta el perceptrón como base de una red neuronal y detalla su implementación en MQL5. Explicamos las funciones de activación y sus derivadas, la diferencia entre forward propagation y backpropagation, y el uso de la función de costo de mínimos cuadrados y del gradiente. Aprenderás a entrenar pesos y sesgos, validar el resultado mediante scripts de prueba y ajustar la tasa de aprendizaje para obtener una convergencia estable.
Automatización de estrategias de trading en MQL5 (Parte 27): Creando un patrón armónico «Crab» de acción del precio con señales visuales Automatización de estrategias de trading en MQL5 (Parte 27): Creando un patrón armónico «Crab» de acción del precio con señales visuales
En este artículo, desarrollamos un sistema de patrones armónicos «Crab» en MQL5 que identifica patrones armónicos «Crab» alcistas y bajistas utilizando puntos de pivote y niveles de Fibonacci, y que activa operaciones con niveles precisos de entrada, stop loss y take profit. Incorporamos información visual mediante elementos gráficos, como triángulos y líneas de tendencia, para mostrar la estructura del patrón XABCD y los niveles de negociación.
Automatización de estrategias de trading en MQL5 (Parte 26): Desarrollo de un sistema de promediado basado en Pin Bar para la gestión de múltiples posiciones Automatización de estrategias de trading en MQL5 (Parte 26): Desarrollo de un sistema de promediado basado en Pin Bar para la gestión de múltiples posiciones
En este artículo, desarrollamos un sistema de promediación Pin Bar en MQL5 que detecta patrones de barras Pin para iniciar operaciones y emplea una estrategia de promedio para la gestión de múltiples posiciones, mejorada con stops dinámicos y ajustes de punto de equilibrio. Incorporamos parámetros personalizables con un panel de control para el seguimiento en tiempo real de las posiciones y los beneficios.