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

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

MetaTrader 5 — Ejemplos |
29 0
CODE X
CODE X

Introducción

En el artículo anterior, Del nivel básico al intermedio: Objetos y subventanas (II), vimos cómo podíamos manejar, de la forma más sencilla y práctica posible, el caso de que se eliminara algún objeto que nuestro código hubiera colocado en el gráfico, ya fuera de forma intencionada o por un error del operador o del usuario. Sin embargo, este enfoque puede acabar causando cierto descontento entre algunos usuarios. Para ellos, eliminar un objeto del gráfico no debería provocar que también se eliminara toda la aplicación.

Como existe una segunda forma de manejar la eliminación accidental de objetos, suponiendo que el usuario haya eliminado algo sin darse cuenta de la verdadera importancia de ese objeto, reservé este tema para este artículo. Aquí veremos cómo reponer un objeto eliminado accidentalmente del gráfico. El objetivo será mostrar cómo puede hacerse. Además, los conceptos que veremos pueden aplicarse a otras situaciones similares. Así que vayamos a lo que nos interesa.


Objetos y subventanas (III)

Aunque, en teoría, el objetivo aquí sea mantener la integridad de una subventana y de los objetos colocados en ella, lo que veremos va mucho más allá de este tema. Puede utilizarse incluso cuando no estés usando subventanas y solo quieras que uno u otro objeto permanezca en el gráfico mientras la aplicación siga presente. Por este motivo, comenzaremos con algo más sencillo y después ampliaremos el concepto a un caso en el que necesitemos subventanas.

Muy bien, hasta el artículo anterior habíamos desarrollado un concepto pensado para ayudarte a crear aplicaciones bastante complejas con un esfuerzo mínimo. Esto se debe a que, en MetaTrader 5, los indicadores son la base de toda una serie de elementos que, al final, culminan en lo que muchos conocen como Asesores Expertos. Todavía no hemos empezado a hablar de los Asesores Expertos precisamente porque, sin los indicadores, esos códigos no sirven de mucho. Sin embargo, si contamos con una base sólida y bien construida, y entendemos qué podemos y qué no podemos hacer dentro de las posibilidades que ofrecen los indicadores, construir y diseñar un Asesor Experto se convierte en una de las tareas más sencillas de realizar.

Sin embargo, si no hemos construido antes esta base, todo lo relacionado con los Asesores Expertos se volverá confuso y tendrá muy poco sentido. Aunque puedas pensar que todo esto es una pérdida de tiempo, entender cómo manejar distintas situaciones es más importante que crear código sin una planificación clara. Al final, acabarás en una situación que no podrás resolver, precisamente por no haber tenido la paciencia o la comprensión necesarias para planificar lo que pretendes implementar.

Bien, a partir de lo que ya hemos visto, tenemos la siguiente situación: hemos conseguido crear un indicador que puede utilizarse o no en una subventana por el simple hecho de integrarlo con un segundo indicador. De este modo, el primero, que antes era el indicador principal, pasa a ser un indicador auxiliar. Por lo tanto, no necesitamos realizar todo el trabajo de una sola vez. Podemos dividirlo en pequeñas etapas, más sencillas y fáciles de manejar, y solo después unirlo todo en un bloque mayor y más complejo.

Partiendo de este principio, podemos suponer lo siguiente: el código que se muestra a continuación puede analizarse, implementarse según nuestras necesidades y, después, integrarse en un sistema mucho mayor.

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.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
18.    gl_MiniChart = new C_MiniChart("Mini Chart");
19. 
20.    return INIT_SUCCEEDED;
21.   };
22. //+------------------------------------------------------------------+
23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
24.   {
25.    return rates_total;
26.   };
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29.   {
30.    switch(id)
31.      {
32.       case CHARTEVENT_OBJECT_DELETE:
33.          if(gl_MiniChart == sparam)
34.            {
35.             ChartIndicatorDelete(0, ChartWindowFind(0, def_ShortName), def_ShortName);
36.             OnDeinit(REASON_INITFAILED);
37.            }
38.          break;
39.      }
40.   };
41. //+------------------------------------------------------------------+
42. void OnDeinit(const int reason)
43.   {
44.    delete gl_MiniChart;
45.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
46.   };
47. //+------------------------------------------------------------------+
48. //+------------------------------------------------------------------+
49. 

Código 01

Si ejecutas este código 01 y eliminas el objeto que ha creado, observarás que el propio código también se eliminará del gráfico. Esto ya se vio y explicó en el artículo anterior. Por lo tanto, no veo necesario entrar en más detalles sobre el funcionamiento de este código 01. Sin embargo, aquí vamos a manejar esta misma situación de una forma diferente. Es decir, en lugar de limitarnos a eliminar del gráfico el propio indicador o cualquier otro código que se encuentre en él, haremos que el objeto eliminado vuelva a mostrarse, cambiando así por completo la forma de abordar el problema.

Para empezar, quiero que entiendas lo siguiente: cuanto más quieras que las cosas vuelvan a estar como estaban antes de desmontarlo todo, mayor será el esfuerzo necesario para garantizar que todos los datos requeridos estén realmente disponibles cuando sea preciso recrear algo que ha sido destruido. En algunos casos, podremos utilizar simples estructuras de datos, mientras que en otros será necesario recurrir a una pequeña base de datos. Afortunadamente, para ambas situaciones, MQL5 ofrece todos los recursos y mecanismos que necesitamos para realizar los análisis y las correcciones pertinentes. La única pregunta es: ¿cuánto esfuerzo estás dispuesto a dedicar a resolver el problema?

Bien, planteándolo de esta manera, tendremos que empezar por algún punto. Y empezaremos por el punto que requiera el menor esfuerzo. Cuando digo menor esfuerzo, me refiero a que cualquier modificación o personalización que haya realizado un usuario u operador se perderá por completo. Esto obligará al usuario a volver a realizar todas esas modificaciones o personalizaciones.

Esta decisión, aunque pueda parecer algo drástica, sigue siendo mejor que la alternativa que vimos en el artículo anterior o que aparece en este código 01. Así que, para conseguir que este código 01 no se elimine del gráfico cuando se elimine el objeto que ha creado, tendremos que hacerle una modificación muy pequeña, aunque esa modificación cambiará por completo el comportamiento del código. Esta modificación 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. void Initilize(void)
15.   {
16.    gl_MiniChart = new C_MiniChart("Mini Chart");
17.    ChartRedraw();
18.   }
19. //+------------------------------------------------------------------+
20. int OnInit(void)
21.   {
22.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
23.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
24.    Initilize();
25. 
26.    return INIT_SUCCEEDED;
27.   };
28. //+------------------------------------------------------------------+
29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
30.   {
31.    return rates_total;
32.   };
33. //+------------------------------------------------------------------+
34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
35.   {
36.    switch(id)
37.      {
38.       case CHARTEVENT_OBJECT_DELETE:
39.          if(gl_MiniChart == sparam)
40.             Initilize();
41.          break;
42.      }
43.   };
44. //+------------------------------------------------------------------+
45. void OnDeinit(const int reason)
46.   {
47.    delete gl_MiniChart;
48.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
49.   };
50. //+------------------------------------------------------------------+
51. //+------------------------------------------------------------------+
52. 

Código 02

Este código 02 corrige parte de nuestro problema. Suponiendo que hayas seguido los artículos de esta serie, no creo que tengas dificultades para entender lo que estamos haciendo aquí. Basta con observar y analizar este código 02 para comprender enseguida cuál es el objetivo. Sin embargo, aunque este código 02 funciona, contiene algunos pequeños fallos. Para entenderlo, primero necesito que veas cómo se realiza gran parte de las pruebas. La mayoría de los programadores principiantes simplemente hacen lo que se muestra en la siguiente animación.

Animación 01

Esto no está mal. Sin embargo, se considera una prueba incompleta y puede llevar a conclusiones equivocadas. El objetivo era precisamente comprobar si el objeto se recreaba o no. Y, como pudiste ver, sí, se recreaba. Por lo tanto, consideras que el resultado es satisfactorio y sigues adelante. Sin embargo, al observar con más atención todo lo que está ocurriendo, te encuentras con lo que podemos ver en la siguiente animación.


Animación 02

Fíjate en lo siguiente: lo que se muestra aquí, en la animación 02, solo ocurre porque el terminal está abierto. Muchas veces, gran parte de los principiantes no mantienen abierto el terminal. De este modo, diversos avisos o incluso alguna alerta pueden pasar inadvertidos durante mucho tiempo. Y, cuando intentas resolver el problema, puede que ya no seas capaz de hacerlo, precisamente porque el código puede haberse vuelto demasiado complejo como para encontrar todos los fallos. Pero, como la animación 02 transcurre algo rápido, veamos la parte que nos interesa, resaltada en la siguiente imagen.


Imagen 01

Pero ¿cómo es posible? ¿De dónde ha salido este fallo? No veo nada incorrecto en el código 02, ya que, básicamente, solo hicimos una pequeña modificación en lo que era el código 01. No entiendo por qué ha aparecido este fallo. Bien, mi querido lector, este fallo demuestra una de las razones por las que MQL5 NO CONTIENE OPERACIONES CON PUNTEROS. Los punteros son una herramienta maravillosa, pero, cuando no se entienden bien, se convierten en una piedra en el zapato. En los códigos anteriores tenemos un puntero declarado en la línea doce. Sin embargo, solo se inicializa cuando utilizamos el operador new. Ahora presta atención: si, y solo si, la inicialización se produjera una única vez, es decir, en el momento en que se ejecutara el manejador OnInit, el mensaje resaltado en la imagen 01 NO APARECERÍA.

Sin embargo, en el código 02 el puntero puede inicializarse más de una vez. ¡Ah! ¿Cómo? Siempre había pensado que una variable no podía estar asociada a dos posiciones distintas de memoria. Y, de hecho, no puede estarlo. El problema es que, cuando se ejecuta la línea veinticuatro de este código 02, se produce la primera inicialización del puntero. Pero, si el usuario elimina el objeto del gráfico, tendremos una segunda inicialización del mismo puntero. Esto ocurre debido a la línea cuarenta. Precisamente por esta segunda inicialización, algún contenido asociado no se liberará realmente de la memoria cuando se ejecute la línea cuarenta y siete. Por esta razón aparece el mensaje resaltado en la imagen 01, indicando que aquí hay algo que no está bien.

Pero ¿cómo podemos evitar este tipo de problema? Básicamente, existen dos soluciones. La primera sería no utilizar punteros en tus códigos. Sin embargo, esto limita bastante las posibilidades e incluso puede hacer que tu código resulte menos claro a medida que vaya creciendo. La segunda solución consiste en garantizar que, antes de inicializar un puntero, este quede libre de cualquier referencia anterior. A mi juicio, esta sería la mejor solución. Así que la adoptaremos para corregir el problema indicado por el mensaje resaltado en la imagen 01. Sin embargo, existe un segundo problema en este código 02. Este segundo problema es, en cierto modo, menos perjudicial para el sistema. Pero, dependiendo de lo que se esté implementando, puede llegar a causarnos dolores de cabeza. Y ese problema se encuentra dentro del procedimiento OnDeinit. Ahora detente y piensa un momento. En la línea cuarenta y siete de este código 02 estamos eliminando el objeto del gráfico. ¿Correcto? Hasta ahí no hay nada de malo.

Sin embargo, fíjate en que, al realizar esta eliminación, seguimos pidiendo a MetaTrader 5 que nos informe cuando se elimine algún objeto del gráfico. Por esta razón, MetaTrader 5 añadirá a la cola de eventos un evento que indique que se ha eliminado un objeto del gráfico. Por lo tanto, si el código tarda un poco más en finalizar, se ejecutará la línea cuarenta, haciendo que vuelva a crearse el objeto que se destruyó porque el código estaba finalizando. Esto puede no ocurrir en muchas situaciones, pero en otras sí puede suceder. Así que tenemos que corregirlo antes de que el comportamiento empiece a quedar fuera de nuestro control. Una vez hecho este análisis, podemos modificar el código 02 y obtener la versión que 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. void Initilize(void)
15.   {
16.    delete gl_MiniChart;
17.    gl_MiniChart = new C_MiniChart("Mini Chart");
18.    ChartRedraw();
19.   }
20. //+------------------------------------------------------------------+
21. int OnInit(void)
22.   {
23.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
24.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
25.    Initilize();
26. 
27.    return INIT_SUCCEEDED;
28.   };
29. //+------------------------------------------------------------------+
30. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
31.   {
32.    return rates_total;
33.   };
34. //+------------------------------------------------------------------+
35. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
36.   {
37.    switch(id)
38.      {
39.       case CHARTEVENT_OBJECT_DELETE:
40.          if(gl_MiniChart == sparam)
41.             Initilize();
42.          break;
43.      }
44.   };
45. //+------------------------------------------------------------------+
46. void OnDeinit(const int reason)
47.   {
48.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
49.    delete gl_MiniChart;
50.   };
51. //+------------------------------------------------------------------+

Código 03

Estos sencillos cambios realizados en el código 03 garantizarán que no tengamos los problemas mencionados anteriormente. Ahora veamos cómo responderá el código ante la eliminación del objeto del gráfico. Esto puede verse en la siguiente animación.


Animación 03

Simplemente perfecto. Ha funcionado de maravilla, sin ningún fallo aparente. Ahora fíjate en que, incluso después de mover el objeto por la pantalla, este volvió a colocarse en su posición inicial. Este es el coste de optar por la solución que requiere el mínimo esfuerzo, como mencioné al principio del artículo. Al hacerlo, decidimos ignorar cualquier configuración o personalización que pudiera haberse realizado, obligando así al usuario a rehacerlo todo desde el principio. Bien, este es un coste bajo, ya que el indicador continúa activo en el gráfico, a diferencia de lo que hacíamos antes, cuando simplemente eliminábamos el indicador del gráfico.

De todos modos, podemos mejorar esto un poco y hacer que la experiencia del usuario u operador resulte algo menos desagradable. Sin embargo, al decidir hacerlo, debemos tener en cuenta algo importante: cuanto más fielmente queramos recrear el estado del objeto para que el usuario no perciba diferencias entre antes y después de eliminarlo del gráfico, mayor será el esfuerzo necesario. Así que aumentaremos solo un poco ese esfuerzo, ya que el objetivo aquí es puramente didáctico. Bien, ahora llega el momento de tomar algunas decisiones.

Como quiero mantener las cosas bastante sencilla y, al mismo tiempo, darte una idea de cómo realizar una recreación más completa, haremos lo siguiente: almacenaremos la posición y las dimensiones del objeto gráfico. Y, aunque solo vayamos a hacer esto, notarás que el esfuerzo necesario para implementar correctamente este comportamiento será mucho mayor de lo que hemos visto hasta ahora. Para empezar, tendremos que modificar el código original de la clase. El original se muestra a continuación.

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)
29.          return;
30.       ObjectCreate(0, m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1), OBJ_CHART, m_Sub, 0, 0);
31.       ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol);
32.       SetInteger(OBJPROP_XDISTANCE, 10);
33.       SetInteger(OBJPROP_YDISTANCE, 0);
34.       SetInteger(OBJPROP_XSIZE, 300);
35.       SetInteger(OBJPROP_YSIZE, indicator_height);
36.       SetInteger(OBJPROP_PERIOD, PERIOD_M15);
37.       SetInteger(OBJPROP_DATE_SCALE, false);
38.       SetInteger(OBJPROP_PRICE_SCALE, false);
39.       SetInteger(OBJPROP_SELECTABLE, true);
40.       SetInteger(OBJPROP_SELECTED, false);
41.      }
42.    //+----------------+
43.                     ~C_MiniChart()
44.      {
45.       ObjectDelete(0, m_szNameObject);
46.       ChartRedraw();
47.      }
48.    //+----------------+
49.    bool              operator==(const string szArg) { return (szArg == m_szNameObject); }
50.    //+----------------+
51.   };
52. //+------------------------------------------------------------------+

Código 04

Muy bien, como quiero que puedas experimentar con esto sin necesidad de teclear estos códigos, en el archivo adjunto la versión modificada tendrá otro nombre. Esto es únicamente para facilitar su identificación y permitirte también realizar las pruebas en tu entorno local. Bien, empecemos creando un mecanismo para almacenar la posición del objeto. Más adelante entenderás por qué este mecanismo debe tratarse por separado del resto del código. De este modo, al modificar el código de la clase anterior, obtenemos lo que se muestra a continuación.

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.    struct  st_Memory
14.      {
15.       short          x, y, w, h;
16.       bool           selectable;
17.      } m_Memory;
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 < (short)(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)
35.          return;
36.       m_Memory.x = 10;
37.       m_Memory.y = 0;
38.       m_Memory.w = 300;
39.       m_Memory.h = indicator_height;
40.       m_Memory.selectable = true;
41.       m_szNameObject = def_ShortName + "#" + szName + "#" + (string)ObjectsTotal(0, -1, -1);
42.       RestoreMemory();
43.       ObjectSetString(0, m_szNameObject, OBJPROP_SYMBOL, _Symbol);
44.       SetInteger(OBJPROP_PERIOD, PERIOD_M15);
45.       SetInteger(OBJPROP_DATE_SCALE, false);
46.       SetInteger(OBJPROP_PRICE_SCALE, false);
47.       SetInteger(OBJPROP_SELECTED, false);
48.      }
49.    //+----------------+
50.                     ~C_MiniChart()
51.      {
52.       ObjectDelete(0, m_szNameObject);
53.       ChartRedraw();
54.      }
55.    //+----------------+
56.    void              RestoreMemory(void)
57.      {
58.       if(ObjectFind(0, m_szNameObject) < 0)
59.          ObjectCreate(0, m_szNameObject, OBJ_CHART, m_Sub, 0, 0);
60.       SetInteger(OBJPROP_XDISTANCE, m_Memory.x);
61.       SetInteger(OBJPROP_YDISTANCE, m_Memory.y);
62.       SetInteger(OBJPROP_XSIZE, m_Memory.w);
63.       SetInteger(OBJPROP_YSIZE, m_Memory.h);
64.       SetInteger(OBJPROP_SELECTABLE, m_Memory.selectable);
65.      }
66.    //+----------------+
67.    void              StoreMemory(void)
68.      {
69.       m_Memory.x = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_XDISTANCE);
70.       m_Memory.y = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_YDISTANCE);
71.       m_Memory.w = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_XSIZE);
72.       m_Memory.h = (short) ObjectGetInteger(0, m_szNameObject, OBJPROP_YSIZE);
73.       m_Memory.selectable = (bool) ObjectGetInteger(0, m_szNameObject, OBJPROP_SELECTABLE);
74.      }
75.    //+----------------+
76.    bool              operator==(const string szArg) { return (szArg == m_szNameObject); }
77.    //+----------------+
78.   };
79. //+------------------------------------------------------------------+

Código 05

Bien, ahora tenemos entre manos un código realmente peculiar. Esto se debe a que este código 05 funciona igual que el código 04. Sin embargo, incorpora un pequeño bloque de memoria interna destinado a uso local. Y esto permite que el código 05 pueda restablecer las configuraciones que hayan podido perderse. Esto ocurrirá si el usuario elimina el objeto del gráfico mientras el indicador continúa activo. Si se da esta condición concreta, el indicador podrá utilizar los datos almacenados en la memoria del objeto para recrearlo con las propiedades que se hayan almacenado previamente.

Así que, si te ha interesado lo que acabo de decir, presta atención a cómo funciona este mecanismo. Porque, aunque es muy sencillo, exige cierto cuidado cuando vayas a utilizarlo en algún código.

Como he mencionado, necesitamos una zona de memoria separada para estos datos. Esta se crea en la línea trece de este código 05. Pero quizá te estés preguntando: bien, esta memoria no está realmente separada. Se encuentra dentro de la clase que crea el objeto. ¿No provocará esto algún problema más adelante? No, siempre que entiendas cómo funcionan realmente las cosas cuando se ejecuta el código. Si simplemente crees que ya entiendes lo que estás haciendo, es muy probable que cometas algún error. Pero, si entiendes realmente lo que se explicará a continuación, todo funcionará perfectamente.

Una vez creada esta estructura de memoria, tendremos que inicializarla entre las líneas treinta y cinco y treinta y nueve. Observa que estas líneas se encuentran dentro del constructor de la clase. Hacerlo en este momento es muy importante. Del mismo modo, también es importante que el nombre del objeto se defina en este constructor. Ahora fíjate en que NO ESTAMOS CREANDO EL OBJETO EN EL CONSTRUCTOR. Esto se debe a que, si lo creáramos aquí, tendríamos dificultades para saber cuándo y dónde se creó realmente el objeto.

De este modo, una vez inicializada la estructura de memoria, podemos llamar al procedimiento que creará el objeto o, mejor dicho, lo volverá a colocar en el gráfico. De ello se encarga el procedimiento implementado en la línea cincuenta y cinco. Este procedimiento hace exactamente lo mismo que hacía el constructor del código 04, con una única excepción: la línea cincuenta y siete, donde comprobamos si el objeto que queremos volver a colocar en el gráfico se encuentra o no en la lista de objetos. Pero ¿por qué realizar esta comprobación? Bien, la razón es que, dependiendo de las circunstancias, puede que haya ocurrido algún cambio y que otro objeto haya ocupado el lugar del objeto eliminado. Por ahora, no te preocupes por ello, ya que será tema de otro artículo. Así que sigamos viendo qué está ocurriendo.

Una vez restaurados los datos durante la ejecución del constructor de la clase, el objeto ya estará operativo y contará con todas las configuraciones definidas anteriormente. Pero, antes de pasar al código principal, veamos un último procedimiento añadido a la clase. Este aparece en la línea sesenta y cinco. El objetivo de este procedimiento es muy sencillo. Capturará los datos que queremos restaurar y los almacenará en la estructura de memoria del objeto para conservar, aunque sea temporalmente, la configuración del objeto. Este es el código más básico y sencillo que podemos implementar para restaurar un objeto eliminado por el usuario. Ahora veamos cómo hubo que modificar el código principal, ya que no es exactamente igual que el código 03. Sin embargo, se parece bastante, como puedes observar a continuación, donde se muestra el código completo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #property description "DEMO Indicator"
05. //+------------------------------------------------------------------+
06. #include "include\C_MiniChart_2.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. void Initilize(void)
15.   {
16.    if(gl_MiniChart == NULL)
17.       gl_MiniChart = new C_MiniChart("Mini Chart");
18.    else(*gl_MiniChart).RestoreMemory();
19.    ChartRedraw();
20.   }
21. //+------------------------------------------------------------------+
22. int OnInit(void)
23.   {
24.    IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
25.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, true);
26.    gl_MiniChart = NULL;
27.    Initilize();
28. 
29.    return INIT_SUCCEEDED;
30.   };
31. //+------------------------------------------------------------------+
32. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
33.   {
34.    return rates_total;
35.   };
36. //+------------------------------------------------------------------+
37. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
38.   {
39.    switch(id)
40.      {
41.       case CHARTEVENT_OBJECT_DELETE:
42.          if(gl_MiniChart == sparam)
43.             Initilize();
44.          break;
45.       case CHARTEVENT_OBJECT_DRAG:
46.          if(gl_MiniChart == sparam)
47.             (*gl_MiniChart).StoreMemory();
48.          break;
49.      }
50.   };
51. //+------------------------------------------------------------------+
52. void OnDeinit(const int reason)
53.   {
54.    ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, 0, false);
55.    delete gl_MiniChart;
56.   };
57. //+------------------------------------------------------------------+

Código 06

Este código 06 es realmente muy elegante, aunque le falta un detalle. Sin embargo, antes de corregir ese detalle, quiero que observes y admires este código, porque es capaz de traer algo de vuelta del mundo de los muertos. En realidad, el objeto vuelve un tanto deformado, pero esto se debe a que no estamos guardando todas las propiedades del objeto, sino solo algunas. Por eso, para entender este código 06, primero debes comprender los códigos anteriores, incluido lo explicado en otros artículos de esta misma serie.

Especialmente el artículo Del nivel básico al intermedio: Eventos en objetos (IV), ya que en él explico cómo trabajar con eventos sencillos en objetos también simples. Claro que quizá necesites consultar también los artículos anteriores. Pero esa decisión la dejo a tu criterio.

Bien, ahora presta muchísima atención, porque si te distraes, te perderás en mitad de la explicación. ¿Recuerdas que, al principio del artículo, mencioné que utilizábamos punteros para trabajar de una determinada manera en MQL5? ¿Y que, aunque en principio los punteros pueden resultar bastante complicados, pueden ser muy útiles en distintas situaciones? Pues bien, esta es una de ellas. Para entender lo que ocurre en el código 06, quiero que lo compares con el código 03. Al igual que en el código 03, en este código 06 también declaramos el puntero en la línea doce. Sin embargo, antes de llamar al procedimiento de inicialización, utilizamos la línea veinticinco para inicializar el puntero con un valor conocido.

Detalle importante: los punteros NO PUEDEN recibir directamente valores arbitrarios como si fueran direcciones de memoria. Siempre deben apuntar a alguna ubicación de memoria.

Sin embargo, a pesar de este detalle, el valor NULL sí puede asignarse a un puntero, lo que nos permite identificarlo como un puntero nulo. Esta situación es sumamente importante para nosotros. Esto se debe a que, a diferencia de lo que hacíamos antes en el procedimiento Initilize, ahora NO VAMOS A destruir el puntero si el procedimiento recibe una segunda llamada, algo que ocurrirá siempre que el objeto sea eliminado del gráfico. Una vez más, es necesario entender los códigos anteriores para comprender cómo funciona este código 06.

Así, en la línea dieciséis realizamos una comprobación cuyo objetivo es precisamente determinar si el puntero es válido o no. Como en la primera llamada se declara como inválido, la condición se cumplirá y se reservará memoria mediante el operador new y el puntero pasará a hacer referencia a esa ubicación. Ahora viene la parte que resulta confusa para muchos. ¿Qué ocurre cuando la línea cuarenta y uno vuelve a llamar al procedimiento de la línea catorce? Bien, en este caso el objeto habrá sido eliminado del gráfico. Sin embargo, el objeto o bloque de memoria al que apunta el puntero todavía no habrá sido liberado. Ese bloque de memoria solo se liberará cuando se ejecute el operador delete, algo que únicamente sucede en la línea cincuenta y dos.

Ahora fíjate en que, como la línea cincuenta y uno desactiva la notificación del evento correspondiente a la eliminación de un objeto del gráfico, MetaTrader 5 dejará de disparar un evento que provoque una nueva ejecución de la línea cuarenta y uno. Es muy importante realizar estas operaciones exactamente en este orden. De lo contrario, cuando volviera a ejecutarse la comprobación de la línea dieciséis, tendríamos un puntero inválido y se crearía uno nuevo. Sin embargo, como esto no ocurrirá durante la ejecución normal del código, cuando se ejecute la línea cuarenta y uno, la comprobación de la línea dieciséis hará que el flujo continúe por la línea diecisiete. Y esta línea hará que se restauren los datos del objeto. No obstante, como el objeto ya no se encuentra en el gráfico, la clase forzará la creación de un nuevo objeto con el mismo nombre, como puede verse en la línea cincuenta y siete del código 05.

Hmm, esto parece bastante interesante. Pero ¿qué ocurre con la memoria? Por lo que he podido observar, los datos no se están actualizando. Al menos no de una forma que yo pueda entender. ¿Cómo sucede esto? Bien, mi amigo lector, por eso es importante que estudies los artículos anteriores. Allí tratamos el evento CHARTEVENT_OBJECT_DRAG. Este evento procede de MetaTrader 5 y sirve para indicarnos que se ha modificado alguna propiedad del objeto, principalmente su posición. Sin embargo, si consultas el artículo que mencioné hace un momento, verás que no solo puede modificarse la posición, sino también las dimensiones del objeto. Pero no voy a repetir lo que se explicó anteriormente. Por lo tanto, en este caso, solo se modificará la posición. Esto da lugar al comportamiento que puede observarse en las siguientes animaciones.


Animación 04

En esta animación 04 podemos ver cómo se aplica el principio básico. Observa que, en el momento en que eliminamos el objeto del gráfico, se crea uno nuevo. Sin embargo, se restauran las propiedades guardadas. Las demás se sustituyen por los valores predeterminados del objeto.

Bien, ¿y qué ocurre si modificamos los valores en el cuadro de propiedades del objeto? En este caso, la respuesta más adecuada sería: depende. Pero ¿de qué depende? Si estás utilizando la implementación del código 06, la respuesta es la siguiente: cuando se modifican las propiedades y el objeto NO SE MUEVE, de modo que no se dispara el evento CHARTEVENT_OBJECT_DRAG que nuestro código puede capturar, las propiedades modificadas se perderán si se elimina el objeto. Esto puede verse en la siguiente animación.


Animación 05

Sin embargo, y esto es importante, si después de modificar las propiedades mueves el objeto, los cambios quedarán guardados, como se muestra en la animación siguiente.


Animación 06

Hmm, interesante. Pero ¿no existe alguna forma de modificar las propiedades y conservar los nuevos valores sin necesidad de mover el objeto por el gráfico? Sí, amigo mío, existe una forma de hacerlo. Pero, antes de mostrarla, veamos una última animación, la que muestra qué ocurre al eliminar la aplicación que mantiene el objeto en el gráfico. Esta animación se muestra a continuación.


Animación 07

Observa que en ninguna de las animaciones hubo indicios de problemas en el código. Esto es una excelente señal. Así que veamos cómo resolver el problema observado en la animación 05, donde, después de modificar las propiedades, estas se perdieron precisamente porque no se habían guardado en memoria. Para resolver este problema, solo necesitamos añadir una línea de código. Su objetivo es capturar precisamente el evento que genera MetaTrader 5 para notificarnos que se ha modificado alguna propiedad del objeto. Como el código permanece prácticamente igual, podemos centrarnos únicamente en el fragmento que realmente necesita modificarse. Este se muestra a continuación.

                                   .
                                   .
                                   .

35. //+------------------------------------------------------------------+
36. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
37.   {
38.    switch(id)
39.      {
40.       case CHARTEVENT_OBJECT_DELETE:
41.          if(gl_MiniChart == sparam)
42.             Initilize();
43.          break;
44.       case CHARTEVENT_OBJECT_CHANGE:
45.       case CHARTEVENT_OBJECT_DRAG:
46.          if(gl_MiniChart == sparam)
47.             (*gl_MiniChart).StoreMemory();
48.          break;
49.      }
50.   };
51. //+------------------------------------------------------------------+ 
                                   .
                                   .
                                   .

Fragmento 01

Y eso es todo, mi querido lector. Lo único que necesitamos hacer para corregir lo que se muestra en la animación 05 es añadir la línea cuarenta y tres del fragmento 01. Con esto, cuando MetaTrader 5 genere una notificación indicando que se ha modificado alguna propiedad del objeto, nuestro código podrá interceptar esta notificación y garantizar que se actualicen los datos almacenados en memoria. Así, aunque después de modificar las propiedades se elimine el objeto sin haberlo movido, nuestra aplicación podrá recrearlo con las propiedades que se hayan guardado. Recuerda que, para restablecer un objeto idéntico al que existía antes, tendremos que almacenar un número mucho mayor de propiedades. Pero, como aquí solo quiero demostrar cómo podríamos hacerlo, me doy por satisfecho con el resultado obtenido.


Consideraciones finales

Aunque no hayamos llevado el indicador mostrado en este artículo a una subventana mediante su integración con algún otro indicador, como hicimos en el artículo anterior, lo que hemos visto y explicado aquí puede considerarse un punto clave para entender cómo resolveremos la segunda parte de otro problema: garantizar que un indicador presente en una subventana e integrado con otro indicador no se elimine simplemente porque se haya eliminado del gráfico alguno de los objetos que crea y mantiene.

Aunque relacionar lo que hemos visto aquí con lo explicado en el artículo anterior parezca algo sencillo, existe un pequeño truco, por así decirlo, para evitar un comportamiento distinto del esperado. Una cosa es eliminar y volver a colocar un objeto en el gráfico. Y otra, completamente distinta, es reponer un indicador completo que, por cualquier motivo, haya sido eliminado del gráfico. Ambas situaciones requieren enfoques ligeramente diferentes.

Así, en el próximo artículo veremos cómo podemos resolver este tipo de situaciones. Aunque parezcan diferentes, tienen muchas cosas en común. Sin embargo, sin los conocimientos adecuados, no podrás manejarlas.

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




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

Archivos adjuntos |
Anexo.zip (5.99 KB)
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.
Del nivel básico al intermedio: Objetos y subventanas (II) Del nivel básico al intermedio: Objetos y subventanas (II)
Este artículo explica cómo capturar y gestionar la eliminación de objetos del gráfico en MQL5 utilizando eventos de MetaTrader 5. Al detectar la eliminación de un objeto creado por el indicador, el código elimina la instancia correspondiente para evitar inconsistencias y subventanas residuales. El enfoque utiliza nombres únicos y un archivo de cabecera, priorizando la simplicidad y la previsibilidad del comportamiento.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
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.