Del básico al intermedio: Puntero a función
Introducción
En el artículo anterior, Del básico al intermedio: Objetos (II), comenzamos a trabajar con lo que sería el primer tipo de evento que podemos utilizar para manipular un objeto que esté presente en el gráfico.
No obstante, allí se utilizó un tipo de evento que, de forma predeterminada, MetaTrader 5 siempre disparará cuando el usuario interactúe con el gráfico. En este caso, el evento en cuestión es el de pulsar una tecla. Como se trata de un evento sencillo de capturar mediante el manejador OnChartEvent, quizá estés pensando que no existe otra forma de utilizar el teclado que no sea a través de la captura de ese evento. Pues bien, mi querido lector, las cosas no funcionan exactamente así. Aunque los scripts no utilizan ni permiten el uso del manejador OnChartEvent, sí podemos incorporar mecanismos para controlar ciertas propiedades de un objeto haciendo uso del teclado y de un script, aunque sea algo poco convencional.
Así que, antes de poder ver cómo tratar eventos provenientes del mouse, vamos a ver cómo manejar eventos de teclado cuando estamos utilizando scripts. Aunque MetaTrader 5 y, en consecuencia, MQL5, no están orientados a este tipo de actividad, ya que fueron pensados para trabajar con gráficos de cotizaciones, es importante que tú, mi querido lector, sepas qué se puede hacer y qué no. Porque hay limitaciones que es necesario entender.
¿Script con eventos?
Para empezar, primero necesitas entender que NO. NO PODEMOS TENER UN SCRIPT CON EVENTOS. Pero eso no nos impide crear un script que pueda manejar eventos provenientes del teclado. Sin embargo, y aquí es donde realmente empieza lo complicado, es necesario que tú, mi querido lector, entiendas que MQL5 NO ESTÁ PENSADO para determinados tipos de implementación. Por esta razón, surgen diversas limitaciones y dificultades cuando deseas programar algo utilizando única y exclusivamente MQL5.
Pero ¿cómo podemos capturar y manejar eventos de teclado dentro de un script? La verdad es que NO PODEMOS. Lo que sí podemos hacer es capturar determinadas teclas y, haciendo uso de algún tipo de filtrado, generar algo similar a un manejador de eventos de teclado. Aunque en realidad estaríamos utilizando el manejador OnChartEvent.
Para que puedas entenderlo, vamos a utilizar un código que vimos en el artículo anterior. Así, será mucho más sencillo que comprendas el tipo de problema en el que podemos meternos. El código en cuestión se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 15. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 16. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch(id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. switch ((int)lparam) 35. { 36. case def_KEY_DOWN: 37. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 38. break; 39. case def_KEY_UP: 40. p = (p > 0 ? p - 1 : p); 41. break; 42. default: 43. return; 44. } 45. Comment(StringFormat("Current bar analyzed: %d", p)); 46. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 47. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 48. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 49. break; 50. } 51. ChartRedraw(); 52. }; 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. Comment(""); 57. for (uint c = 0; c < gl_Objs.Size(); c++) 58. ObjectDelete(0, gl_Objs[c]); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
Código 01
Este código 01, cuando se ejecute, producirá como resultado lo que puede verse en la animación siguiente.

Animación 01
Ahora la pregunta es: ¿cómo podríamos crear algo parecido a este código 01, con un resultado idéntico al que puede verse en la animación 01, pero utilizando para ello un script? Bien, esta es la cuestión a la que vamos a responder.
Para conseguirlo, necesitaremos utilizar diversas llamadas de la biblioteca de MQL5. Sin embargo, como quiero centrarme en MQL5 puro, no adoptaremos aquí ninguna otra metodología. Aunque el uso de una metodología en la que hiciéramos uso de un código escrito en C o C++ podría simplificar mucho las cosas, no haremos eso aquí, en estos artículos dirigidos a contenidos que van del básico al intermedio.
Tal vez, en el futuro, si decido mostrar cómo desarrollar códigos más avanzados, en los que sea necesario usar código escrito y compilado en otros lenguajes de programación, veamos cómo abordar este mismo problema, que se verá aquí, de otra manera que, a mi juicio, es mucho más simple. Sin embargo, también es mucho más compleja, debido a que necesitas dominar bien MQL5, así como también otro lenguaje de programación, para lograr que ambos lenguajes se comuniquen y así resolver un problema determinado.
Bien, entonces volvamos a nuestro problema central. Para empezar, aquí no utilizaremos un código estructural. Esto se debe a que la idea no es generar una aplicación, sino mostrar cómo resolver un problema. Por esta razón, necesitamos elaborar un plan de acción. Y, para hacerlo, el primer paso es definir cómo sería el script inicial.
Como puede verse, tanto en el código como en la animación, tenemos dos objetos: uno de ellos es una línea horizontal y el otro, una línea vertical. Ambos estarán vinculados al momento en que se creó la barra, y también al precio de cierre. Y, cuando el script finalice, y lo haga correctamente, tendremos que eliminar ambos objetos creados por el script.
Muy bien, ya tenemos nuestro plan inicial de acción. Podemos, entonces, comenzar a implementar el código inicial. Este se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. int p = 0; 14. MqlRates rate[1]; 15. 16. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 17. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 18. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 19. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 20. 21. Comment(StringFormat("Current bar analyzed: %d", p)); 22. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 23. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 24. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 25. 26. Comment(""); 27. for (uint c = 0; c < gl_Objs.Size(); c++) 28. ObjectDelete(0, gl_Objs[c]); 29. ChartRedraw(); 30. } 31. //+------------------------------------------------------------------+
Código 02
Este código 02 da inicio a lo que es nuestro código de ataque. Sin embargo, si intentas ejecutarlo, en realidad no lograrás ver ningún resultado. La razón es que los objetos se crean, se posicionan y se eliminan antes de que podamos percibir que, en efecto, estaban allí.
Pero aquí, en este código 02, quiero llamar tu atención sobre algo, mi querido lector. Observa atentamente cada línea de este código 02 y fíjate en que esas mismas líneas también están presentes en el código 01. Sin embargo, aquí no podemos ver nada. Y la razón es que el fragmento comprendido entre las líneas 21 y 24, que forma parte del manejador de eventos OnChartEvent, que se muestra en el código 01, aquí se ejecuta con demasiada rapidez, sin darnos oportunidad de interacción. Y es precisamente en este fragmento donde necesitamos intervenir para implementar el tratamiento de eventos del teclado.
Perfecto. Ya sabemos cómo sería el código inicial. Es hora de separar las cosas en bloques más pequeños. Porque crear códigos monolíticos es muy molesto, además de resultar muy cansador. Así, el código 02 se modifica para convertirse en el código 03.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. KeyEvent(def_KEY_DOWN); 15. Deinit(); 16. } 17. //+------------------------------------------------------------------+ 18. void Init(void) 19. { 20. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 21. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 22. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 23. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 24. } 25. //+------------------------------------------------------------------+ 26. void KeyEvent(int lparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch (lparam) 32. { 33. case def_KEY_DOWN: 34. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 35. break; 36. case def_KEY_UP: 37. p = (p > 0 ? p - 1 : p); 38. break; 39. default: 40. return; 41. } 42. Comment(StringFormat("Current bar analyzed: %d", p)); 43. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 44. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 45. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 46. } 47. //+------------------------------------------------------------------+ 48. void Deinit(void) 49. { 50. Comment(""); 51. for (uint c = 0; c < gl_Objs.Size(); c++) 52. ObjectDelete(0, gl_Objs[c]); 53. ChartRedraw(); 54. } 55. //+------------------------------------------------------------------+
Código 03
Ahora sí, tenemos un código mucho más agradable. Una auténtica delicia. Esto se debe a que, al observar este código 03, puedes notar lo que realmente necesitamos hacer. Necesitamos que la línea 14 ejecute algún tipo de bucle, para que podamos interactuar y controlar los objetos, tal como puede verse en la animación 01. Pregunta: ¿Cómo podemos hacer esto? Bien, mi querido lector, esta es realmente la parte divertida. Pero, antes de hacerlo, quiero recordarte que los bucles son peligrosos. Ya expliqué esto y también la forma de salir de los bucles de manera segura.
Y, como todo script se elimina permanentemente de un gráfico en cuanto MetaTrader 5 necesita reconstruir el gráfico desde cero, por ejemplo, cuando el usuario le pide a MetaTrader 5 que cambie el período del gráfico, el script será eliminado en ese momento. Sin embargo, y es importante que entiendas esto, no basta con que el script sea eliminado. También necesita llevarse los objetos que creó. De lo contrario, dichos objetos volverán a colocarse en el gráfico en cuanto MetaTrader 5 comience a reconstruirlo.
Sé que esto puede parecer bastante desalentador. Pero te lo digo para que prestes atención al utilizar objetos dentro de un script. Si no quieres que los objetos creados por el script permanezcan en el gráfico cuando el script termine, debes eliminarlos. De lo contrario, basta con ignorar esta fase de eliminación, y asunto resuelto.
Bien, entonces ahora vamos a abordar el tema del bucle. El problema aquí no es exactamente crear el bucle, sino evitar que sea demasiado agresivo y consuma así muchos recursos de CPU. Como los eventos de teclado, por muy loco que pueda ser un usuario, no ocurren a cada instante, podemos añadir algunos elementos al cuerpo del bucle. Así, tendremos un script mucho menos agresivo en términos de consumo de CPU. Entonces, después de analizar estas posibilidades, obtenemos lo que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. while (!IsStopped()) 15. { 16. if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) 17. KeyEvent(def_KEY_UP); 18. if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN)) 19. KeyEvent(def_KEY_DOWN); 20. Sleep(100); 21. } 22. Deinit(); 23. } 24. //+------------------------------------------------------------------+ 25. void Init(void) 26. { 27. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 28. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 29. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 30. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 31. } 32. //+------------------------------------------------------------------+ 33. void KeyEvent(int lparam) 34. { 35. static int p = 0; 36. MqlRates rate[1]; 37. 38. switch (lparam) 39. { 40. case def_KEY_DOWN: 41. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 42. break; 43. case def_KEY_UP: 44. p = (p > 0 ? p - 1 : p); 45. break; 46. default: 47. return; 48. } 49. Comment(StringFormat("Current bar analyzed: %d", p)); 50. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 51. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 52. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 53. } 54. //+------------------------------------------------------------------+ 55. void Deinit(void) 56. { 57. Comment(""); 58. for (uint c = 0; c < gl_Objs.Size(); c++) 59. ObjectDelete(0, gl_Objs[c]); 60. ChartRedraw(); 61. } 62. //+------------------------------------------------------------------+
Código 04
Ahora observa el bucle en la línea 14. Con esto, ahora tenemos el comportamiento que puede verse en la animación que aparece a continuación.

Animación 02
Interesante, ¿no? Pero eso no es todo. Aún hay algo más que debe mostrarse. Puede verse en la animación siguiente.

Animación 03
Observa lo siguiente, mi querido lector: cuando pedimos modificar el período del gráfico, MetaTrader 5 destruirá el gráfico y lo volverá a crear justo después. Como los scripts no se recargan de forma predeterminada en MetaTrader 5, el script se cerrará, y los objetos que había creado se eliminarán del gráfico, que es exactamente lo que queríamos lograr a partir del código 04, que se muestra anteriormente.
Pero quizá te estés preguntando: ¿por qué utilizaste las funciones de las líneas 16 y 18? Bien, mi querido lector, la razón es no usar absolutamente nada que no forme parte de la biblioteca estándar de MQL5. Como MQL5 no proporciona, porque no lo necesita, ninguna función ni procedimiento para que podamos leer directamente las teclas presionadas, no podemos hacer esa lectura de forma más genérica. Esto, para lograr un mecanismo equivalente al que tendríamos si estuviéramos interceptando el evento ChartEvent y capturando CHARTEVENT_KEYDOWN, como puede verse en el código 01.
Sin embargo, esto no significa que no pudiéramos hacer este tipo de lectura del teclado. Solo sería necesario utilizar recursos que no forman parte de MQL5 y, por consiguiente, no los abordaré en estos artículos centrados en un nivel que va del básico al intermedio.
Bien, pero puedes notar que, en este código 04, estamos utilizando recursos, o una enumeración, que hace mucho más sencillo entender qué tipo de tecla se espera. Pero, al mismo tiempo, podemos ver que no necesitamos plantear las cosas necesariamente como se muestra en el código 04. Podemos hacer algo un poco mejor. Y, en este caso, como no quiero mostrar una modificación demasiado simple y sin interés, voy a aprovechar para explicar otro recurso disponible en MQL5. Algo que tiene un objetivo muy específico, y son pocas las oportunidades que tenemos de mostrar cómo funciona ese recurso, siendo esta una de ellas. Así que, para separar adecuadamente los temas, pasemos a un nuevo tema.
Puntero a función
Esta es una de esas raras oportunidades que tenemos para explicar algún recurso con un objetivo muy específico en MQL5. Como, en el tema anterior, implementamos una versión alternativa, por así decirlo, de lo que se había implementado anteriormente como indicador, ahora tenemos la oportunidad de explicar los punteros a funciones. Un recurso muy divertido, pero que genera una enorme confusión en la cabeza de todo principiante en programación, sobre todo cuando la persona no tiene ciertos conceptos bien fundamentados y claros en su mente.
Para empezar, hace ya bastante tiempo que no menciono la necesidad de ningún requisito previo para entender un tema. Esto se debe a que los temas siempre estaban vinculados a algún artículo cercano. Sin embargo, este es uno de aquellos casos en los que los conceptos que vamos a utilizar se encuentran en artículos relativamente distantes. En este caso, se trata de Del básico al intermedio: Variables (III). Ese artículo cierra el tema de los conceptos relacionados con variables y constantes. Entender con claridad el concepto explicado allí será de suma importancia para comprender lo que vamos a hacer aquí.
Para comenzar de la manera correcta, veamos un ejemplo muy simple y, en apariencia, tonto. Sin embargo, nos ayudará a entender cómo trabajar con este recurso que vamos a explorar. El código inicial puede verse a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. string Msg_01(const int value) 05. { 06. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 07. } 08. //+------------------------------------------------------------------+ 09. string Msg_02(const int value) 10. { 11. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 12. } 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. Print(Msg_01(171)); 17. Print(Msg_02(-375)); 18. } 19. //+------------------------------------------------------------------+
Código 05
Bien, este código 05 es muy simple, así que no lo explicaré. Pero el resultado de su ejecución puede verse a continuación.

Imagen 01
Aunque este código 05 es muy simple, contiene elementos que nos interesan antes de pasar a algo más elaborado. La gran cuestión aquí es: ¿qué tipo de mecanismo podríamos crear para unir la función definida en la línea cuatro con la función definida en la línea nueve? Esto, para poder seleccionar qué función se utilizará, pero sin usar directamente su nombre.
Bien, quizá estés pensando en la sobrecarga, que se explicó en el artículo Del básico al intermedio: Sobrecarga. Sin embargo, la sobrecarga no se aplica aquí, al menos no de la forma en que queremos utilizarlo. En realidad, el mecanismo más cercano al ideal serían los arrays. En los artículos Del básico al intermedio: Array (IV), expliqué de forma sencilla cómo trabajar con arrays. Allí, el enfoque estaba en almacenar valores discretos, como enteros y valores de punto flotante. No obstante, podemos almacenar otro tipo de información dentro de los arrays. En este caso, funciones o procedimientos. Un momento. ¿Cómo es eso? ¿Estás loco? ¿Cómo podemos almacenar funciones y procedimientos dentro de un array? Eso no tiene el menor sentido.
Por esta razón, mencioné que era necesario que tuvieras el concepto de variable bien claro en tu mente, mi querido lector. Existe un tipo especial de variable, llamado puntero. Como, por motivos de seguridad y simplicidad, MQL5 no implementa, o mejor dicho, no permite al programador implementar un código que utilice punteros, difícilmente llegarás a tener contacto con ellos. Sin embargo, hay algunas situaciones en las que, incluso aquí, en MQL5, se crean y utilizan punteros. Y, como en la práctica los punteros en MQL5 no son iguales a los punteros en C y C++, prácticamente no se oye hablar de ellos aquí.
Así, antes de que entiendas lo que vamos a hacer, necesito que entiendas que cualquier función o procedimiento está en la memoria en una determinada dirección. Esta dirección se almacena en un tipo especial de variable conocida como puntero. Los punteros son, con diferencia, lo más poderoso que existe en programación. Pero, al mismo tiempo, también son lo más confuso y complicado de dominar, teniendo en cuenta que, cuando manipulamos punteros, estamos manipulando el contenido de la memoria del ordenador, independientemente del tipo de dato al que estemos accediendo.
Bien, no vamos a profundizar demasiado en esta cuestión, precisamente porque no necesitamos hacerlo. Solo necesito que entiendas lo siguiente:
Un puntero es una variable. Esta variable apunta a una región de la memoria. Si esta región es código ejecutable, podemos hacer que el programa cambie su flujo de ejecución hacia esa región.
Entender esto ya es suficiente para lo que necesitamos y vamos a hacer. Perfecto, ahora ya sabemos qué es un puntero. Pero entonces, ¿cómo podemos crear, o mejor dicho, definir un puntero? Esto en MQL5. Bien, esta es la parte en la que necesitaré que hayas entendido cómo funcionan las plantillas. En los artículos Del básico al intermedio: Plantilla y Typename (V), expliqué cómo funcionan las plantillas y cómo trabajar con ellas para crear funciones y procedimientos sobrecargados. Pero, y aquí es donde la cosa se vuelve confusa para los principiantes, también podemos definir plantillas de tipos de datos. Un puntero sería precisamente eso: una plantilla de un tipo de dato. Solo que, en este caso, un tipo de dato que representa una plantilla de función o procedimiento. Vaya, ahora sí se complicó, porque, para mí, esto no tiene el menor sentido.
Calma, mi querido lector. Vamos con calma. Sé que esto es, en efecto, complicado al principio. Por eso, solo ahora menciono este recurso y voy a explicarlo. Es necesario que tengas conocimientos sobre varios conceptos distintos para poder entender realmente cómo trabajar con este recurso, conocido como puntero.
En el código 05, las funciones de las líneas cuatro y nueve fueron declaradas de esa manera a propósito. Esto es para facilitar la comprensión del concepto de plantilla que se usará en la definición del puntero. Bien, si te fijas, verás que, básicamente, solo cambia el nombre de la función. El contenido, o la lógica que implementa la función, no importa; lo que importa es la declaración. Como la declaración allí es muy parecida, tanto en términos del tipo de retorno como en términos del número y del tipo de elementos que se pasarán a las funciones, tenemos las condiciones adecuadas para definir un puntero. Y, para hacerlo, añadiremos la siguiente línea que se muestra en el código de abajo. Iré mostrando poco a poco cómo va cambiando el código 05, para que realmente puedas comprender lo que estará ocurriendo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(Msg_01(171)); 19. Print(Msg_02(-375)); 20. } 21. //+------------------------------------------------------------------+
Código 06
Ahora, deja todo lo que estés haciendo y que pueda distraerte, y presta mucha atención a lo que voy a explicar. Esto se debe a que, si no entiendes este comienzo, te quedarás como perro que se cayó del camión de mudanza. Observa que, en este código 06, añadimos la línea cuatro, que no existía en el código 05. Esta línea cuatro es precisamente la que crea la definición del puntero que vamos a usar.
Observa que la declaración se parece mucho a lo que aparece en las líneas seis y once. Solo que se ha omitido el nombre de la variable, y el nombre de la función fue sustituido por (FnPtr). Esta parte, ese elemento, (FnPtr), puede ser cualquier cosa que quieras usar. Sin embargo, ten cuidado al hacerlo, ya que es precisamente esta parte la que necesitaremos utilizar dentro de poco.
Bien, primera parte explicada. Ahora vamos a declarar el array con el tipo definido en la línea cuatro del código 06. Recuerda que lo que se definió allí fue un puntero. Esta segunda parte se muestra en el código de abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. Print(Msg_01(171)); 21. Print(Msg_02(-375)); 22. } 23. //+------------------------------------------------------------------+
Código 07
Ahora, en la línea 18, que se ve en este código 07, tenemos la definición de un array para usar punteros del tipo definido en la línea cuatro. Para simplificar las cosas, estamos definiendo un array estático con dos elementos. Si tienes dudas sobre lo que ocurre aquí, revisa los artículos anteriores. Porque no voy a entrar en más detalles sobre cosas ya explicadas anteriormente. Perfecto, creo que hasta aquí lo estás siguiendo bien. El siguiente paso se muestra en el código de abajo.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. } 26. //+------------------------------------------------------------------+
Código 08
Vaya, ¿qué locura es esta? ¿Estás loco o has perdido completamente el juicio? Este código 08 no va a compilar.
Pero claro que este código 08 va a compilar, mi querido lector. ¿Cómo es eso? ¿No entendiste lo que se hizo aquí? Aquí estamos definiendo elementos del array. Si esto te pareció completamente descabellado, vuelve al inicio de este tema e intenta entender lo que se dijo. En la línea cuatro, estamos definiendo un tipo de dato. En la línea 18, estamos definiendo una variable que va a utilizar ese mismo tipo de dato definido en la línea cuatro. Y, en las líneas 20 y 21, estamos asignando un valor a los elementos de la variable declarada en la línea 18. Nada más que eso. Todo muy simple y fácil de entender. La parte complicada es precisamente la que se muestra en el siguiente paso. Mira esto en el código que aparece a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. 26. for (uchar c = 0, i = 15; c < FnMsg.Size(); c++, i += 30) 27. Print(FnMsg[c](i)); 28. } 29. //+------------------------------------------------------------------+
Código 09
Cuando ejecutes este código 09, verás los siguientes mensajes en el terminal.

Imagen 02
Virgen santa María. ¿Qué locura es esta? Bien, mi querido lector, en este punto sí hemos llegado a la parte realmente complicada. De cierta manera, fui un poco retorcido con este código. Ya que, si te saltas las etapas anteriores e intentas entender, de entrada, este código 09, con toda seguridad quedarás más perdido que nunca en la vida. Esto se debe a que lograrías entender las dos primeras líneas que aparecen en esta imagen 02. Pero ¿de dónde salen las otras dos que estoy destacando? Y ese es precisamente el punto. Y eso que, aquí en MQL5, los punteros son elementos realmente muy simples de explicar y de entender. Pero, aun así, pueden resultar muy confusos para quien observa un código que los utiliza sin entender realmente qué se está implementando allí.
Observa que las dos líneas destacadas en la imagen 02 aparecen precisamente definidas en la línea 27 del código 09. Pero ¿cómo? Esta es la parte confusa. Como la declaración de la línea 18 está utilizando un tipo que es un puntero, al ver la línea 27 esperabas obtener cierto tipo de información. Sin embargo, el compilador entendió que allí lo que tenemos es una llamada a una función. Por eso, la declaración está hecha de esa manera. Y por eso el resultado es el que puede verse en la imagen 02. Observa que no importa el nombre de la función. La línea 27 podrá saber qué función llamar precisamente porque, en las líneas 20 y 21, asignamos la función al elemento del array.
Puede que estés pensando: ¿Pero para qué crear este tipo de complicación? ¿La vida ya no era lo bastante complicada? ¿De verdad necesitábamos algo más de este tipo, siendo además posible hacerlo? Por DIOS, los programadores no son personas normales. Están todos locos de remate.
Tal vez una gran parte de los programadores, en algún momento, pierda realmente la cordura y se convierta en gente muy extraña. (RISAS). Pero, en general, todos somos muy buena gente, solo un tanto excéntricos. Pero, en este punto, ya podemos pensar en algo que antes no era posible hacer. Esto se debe a que tú, mi querido lector, todavía no habías visto lo que se mostró en este tema. Pero ahora vamos a actualizar el código 04, que vimos en el tema anterior, para poder utilizar lo que vimos en este tema. Con ello, tendremos un código que se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. typedef void (*ProcPtr)(void); 12. typedef void (*KeyEvent)(int &); 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. ProcPtr proc[3]; 17. 18. proc[0] = Init; 19. proc[1] = Loop; 20. proc[2] = Deinit; 21. 22. for (uint c = 0; c < proc.Size(); c++) 23. proc[c](); 24. } 25. //+------------------------------------------------------------------+ 26. void Init(void) 27. { 28. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 29. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 30. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 31. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 32. } 33. //+------------------------------------------------------------------+ 34. void Loop(void) 35. { 36. KeyEvent key[3]; 37. int pos = 0; 38. 39. key[0] = Bar_NONE; 40. key[1] = Bar_Next; 41. key[2] = Bar_Prev; 42. 43. while (!IsStopped()) 44. { 45. key[TerminalInfoInteger(TERMINAL_KEYSTATE_UP) ? 1 : (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) ? 2 : 0)](pos); 46. Sleep(100); 47. } 48. } 49. //+------------------------------------------------------------------+ 50. void Event(const int arg) 51. { 52. MqlRates rate[1]; 53. 54. Comment(StringFormat("Current bar analyzed: %d", arg)); 55. CopyRates(_Symbol, _Period, arg, rate.Size(), rate); 56. for (uint c = 0; c < gl_Objs.Size(); c++) 57. ObjectMove(0, gl_Objs[c], 0, rate[0].time, rate[0].close); 58. } 59. //+------------------------------------------------------------------+ 60. void Deinit(void) 61. { 62. Comment(""); 63. for (uint c = 0; c < gl_Objs.Size(); c++) 64. ObjectDelete(0, gl_Objs[c]); 65. ChartRedraw(); 66. } 67. //+------------------------------------------------------------------+ 68. void Bar_Prev(int &arg) 69. { 70. arg = (arg < Bars(_Symbol, _Period) ? arg + 1 : arg); 71. Event(arg); 72. } 73. //+------------------------------------------------------------------+ 74. void Bar_Next(int &arg) 75. { 76. arg = (arg > 0 ? arg - 1 : arg); 77. Event(arg); 78. } 79. //+------------------------------------------------------------------+ 80. void Bar_NONE(int &arg) 81. {} 82. //+------------------------------------------------------------------+
Código 10
El resultado de la ejecución de este código 10 será exactamente igual al que puede verse en la animación 01. Por lo tanto, no voy a repetirla aquí. Sin embargo, en este código 10 me estoy tomando una pequeña licencia. Lo hago para que tú, mi querido lector, puedas entender que no siempre necesitamos hacer las cosas de una única manera. Hay infinidad de formas de obtener un mismo resultado. Algunas son más simples, pero hacen que ampliar el código sea una tarea un poco más lenta, mientras que otras, algo más complejas, permiten ampliarlo con mucha rapidez.
De todos modos, a mi juicio, este código 10 no contiene elementos tan complicados de entender. Sobre todo porque, en este momento, el material todavía está bastante fresco en tu mente. No obstante, en este código 10 hay algunos puntos que, tal como están planteados, puede que no parezcan tener mucho sentido. Así que déjame darte una explicación rápida de esos puntos.
El primero se encuentra dentro del procedimiento OnStart. Aquí podemos ver que, en la línea 16, se declara un array de procedimientos. Justo después, en las líneas 18, 19 y 20, definimos los procedimientos que estarán en cada elemento del array. A continuación, entramos en un bucle para ejecutar cada uno de esos procedimientos en un orden determinado. Sé que, a primera vista, esto puede no parecer tener mucho sentido aquí. Sin embargo, necesitas entender que el propósito de estos artículos es explicar y hacerte pensar como un programador.
Ahora piensa en una serie de pequeños pasos, muy repetitivos, que necesitan ejecutarse en un cierto orden. Si tuvieras que escribir, línea por línea, cada uno de los procedimientos que habría que ejecutar, eso, además de llevar mucho tiempo, convertiría el cambio y la corrección del orden de ejecución en algo tedioso. Pero, si puedes utilizar una expresión matemática para calcular índices en un array, perfectamente podrías definir los procedimientos como elementos de ese mismo array. Al hacerlo, usarías un bucle para ejecutar los elementos del array en un orden determinado, algo que, de otro modo, sería imposible.
En cuanto al procedimiento Loop, que aparece en la línea 34, tenemos algo con un propósito muy parecido. Sin embargo, allí el mecanismo funciona de una manera un poco diferente. En este caso, estamos definiendo, entre las líneas 39 y 41, algunos procedimientos que se utilizarán según la tecla que se presione. Y, en la línea 45, usando un operador ternario, hacemos la llamada al procedimiento adecuado.
Este tipo de enfoque, o esta forma de pensar al abordar un determinado problema, puede, a veces, convertir una tarea que sería muy cansadora en algo mucho más simple y rápido de hacer. Ya que, si fuera necesario cambiar algo, tendríamos que modificar el menor número posible de líneas, haciendo así que cualquier solución pueda implementarse muy rápido.
Consideraciones finales
Aunque podemos hacer muchas más cosas que las que se mostraron aquí, estoy seguro de que, para muchos, lo que vimos en este artículo es algo completamente nuevo y, por lo tanto, debe estudiarse debidamente. Hasta el punto de que llegues a comprender cada punto mostrado aquí en este artículo.
En el anexo, tendrás acceso a los principales códigos presentados en este artículo. Así que aprovecha para estudiar y practicar lo que se mostró aquí. Además, intenta reflexionar sobre lo explicado en los artículos anteriores y sobre cómo esos mismos hechos y conceptos pueden utilizarse junto con lo que vimos aquí. Intenta implementar algo que haga uso de todo el conocimiento adquirido hasta este punto. Y, en el próximo artículo, volveremos a hablar de eventos con objetos. Pero esta vez el tema principal será el uso del mouse, ya que la parte relacionada con el teclado ya se exploró debidamente.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15995
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Red neuronal en la práctica La práctica lleva a la perfección
Particularidades del trabajo con números del tipo double en MQL4
Características del Wizard MQL5 que debe conocer (Parte 61): Uso de patrones del ADX y el CCI con aprendizaje supervisado
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso