Del nivel básico al intermedio: Indicadores técnicos (II)
Introducción
En el artículo anterior Del nivel básico al intermedio: Indicadores técnicos (I), vimos cómo podíamos implementar un código cuyo objetivo era mostrar cómo utilizar un indicador técnico. Los indicadores cuyo uso se explicó en aquel artículo son los que MetaTrader 5 incorpora y mantiene disponibles. Se puede acceder a ellos desde MQL5 de una manera muy sencilla y directa.
Sin embargo, aquel mismo contenido también se aplica a otro tipo de indicador. Un indicador que tú, mi querido lector, implementarás para comprender mejor un determinado movimiento del mercado. Pero, antes de hablar de esto, necesitamos ver otras cuestiones que, desde mi punto de vista, ayudarán bastante a entender cómo podemos utilizar indicadores propios en las situaciones más diversas.
Además, por supuesto, esto nos permitirá explicar mejor cómo podemos manipular MetaTrader 5 para que nos muestre aquello que queremos ver o incluso hacer.
Para alcanzar este nivel de comprensión, será necesario explicar una serie de conceptos que muchos no entienden o ni siquiera saben que existen y que pueden utilizarse en la programación MQL5. Cuando estos conceptos estén debidamente explicados y los hayas comprendido correctamente, se te abrirán muchas puertas, mi querido lector, para convertirte en un buen programador MQL5.
De acuerdo, ha llegado el momento de dejar a un lado las cosas que puedan distraerte y centrarte en lo que se explicará en este artículo.
Indicadores técnicos (II)
En el artículo anterior terminamos viendo el código que 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_applied_price PRICE_CLOSE 08. #property indicator_buffers 1 09. #property indicator_plots 1 10. #property indicator_type1 DRAW_LINE 11. #property indicator_color1 clrRed 12. #property indicator_style1 STYLE_SOLID 13. #property indicator_width1 1 14. #property indicator_label1 "EMA" 15. //+------------------------------------------------------------------+ 16. input uint def_nPeriods = 9; //Period: 17. input ENUM_MA_METHOD def_MA_Method = MODE_EMA; //Method: 18. //+------------------------------------------------------------------+ 19. double Buff_iMA[]; 20. int Handle; 21. //+------------------------------------------------------------------+ 22. int OnInit(void) 23. { 24. SetIndexBuffer(0, Buff_iMA, INDICATOR_DATA); 25. if ((Handle = iMA(NULL, 0, def_nPeriods, 0, def_MA_Method, _AppliedTo)) == INVALID_HANDLE) 26. { 27. Print("Could not start the indicator..."); 28. return INIT_FAILED; 29. }; 30. 31. return INIT_SUCCEEDED; 32. }; 33. //+------------------------------------------------------------------+ 34. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 35. { 36. ArrayInitialize(Buff_iMA, EMPTY_VALUE); 37. CopyBuffer(Handle, 0, 0, def_nPeriods, Buff_iMA); 38. 39. return rates_total; 40. }; 41. //+------------------------------------------------------------------+ 42. void OnDeinit(const int reason) 43. { 44. if (Handle != INVALID_HANDLE) 45. IndicatorRelease(Handle); 46. }; 47. //+------------------------------------------------------------------+
Código 01
Claro que aquí no voy a repetir la explicación que se dio en aquel artículo sobre el resultado que se obtendrá al ejecutar este código 01 en MetaTrader 5. Pero creo que muchos de ustedes habrán sentido cierta curiosidad por una cuestión concreta. Es la siguiente: supongamos que quieres añadir varias medias móviles en un mismo indicador. Esto evitaría tener que configurar cada una por separado cada vez que quieras añadirlas a un símbolo cualquiera. Lo digo porque algunos operadores tienen una configuración previamente definida de medias e indicadores que permanecerán visibles en el gráfico. Sin embargo, no quieren o no les gusta utilizar plantillas para crear ese patrón gráfico. Veremos en breve cómo se hace esto. Pero, por ahora, centrémonos en el artículo actual.
Así, muchos de estos operadores intentan crear algún indicador que sea sencillo de implementar y, al mismo tiempo, funcional. Así, muchos de estos operadores intentan crear algún indicador que sea sencillo de implementar y, al mismo tiempo, funcional, que permita colocar varias medias móviles a la vez y eliminarlas retirando un solo indicador.
Tal vez esto parezca una de esas tareas casi imposibles de realizar, ¿no es cierto, mi querido lector? Sin embargo, con el nivel de conocimientos mostrado hasta ahora en esta serie de artículos, hacer algo así es relativamente sencillo. Aun así, existen algunos trucos que quizá muchos de ustedes ni siquiera sepan que pueden usar para acelerar considerablemente la implementación de este indicador.
Por lo tanto, el objetivo inicial de este artículo es precisamente entender y mostrar estos trucos. No es algo complicado, pero marcará una enorme diferencia en tu forma de programar, haciéndote mucho más productivo y, al mismo tiempo, reduciendo la posibilidad de introducir errores en el código.
Entonces, empecemos haciendo lo siguiente: añadamos una segunda media móvil al código del indicador anterior. Sin embargo, como ahora el objetivo es crear un indicador con una configuración previamente establecida, el código ya no será como el del código 01. Ahora organizaremos el código de una forma un poco diferente. Esto puede verse a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #property indicator_chart_window 07. #property indicator_applied_price PRICE_CLOSE 08. //+----------------+ 09. #property indicator_buffers 2 10. #property indicator_plots 2 11. //+----------------+ 12. #property indicator_type1 DRAW_LINE 13. #property indicator_color1 clrRed 14. #property indicator_style1 STYLE_SOLID 15. #property indicator_width1 2 16. //+----------------+ 17. #property indicator_type2 DRAW_LINE 18. #property indicator_color2 clrBlue 19. #property indicator_style2 STYLE_SOLID 20. #property indicator_width2 2 21. //+------------------------------------------------------------------+ 22. double Buff_iMA_01[], 23. Buff_iMA_02[]; 24. int Handle_01, 25. Handle_02; 26. //+------------------------------------------------------------------+ 27. int OnInit(void) 28. { 29. SetIndexBuffer(0, Buff_iMA_01, INDICATOR_DATA); 30. SetIndexBuffer(1, Buff_iMA_02, INDICATOR_DATA); 31. 32. if ((Handle_01 = iMA(NULL, NULL, 9, 0, MODE_EMA, _AppliedTo)) == INVALID_HANDLE) 33. { 34. Print("Could not start the indicator..."); 35. return INIT_FAILED; 36. }; 37. if ((Handle_02 = iMA(NULL, NULL, 20, 0, MODE_SMA, _AppliedTo)) == INVALID_HANDLE) 38. { 39. Print("Could not start the indicator..."); 40. return INIT_FAILED; 41. }; 42. 43. return INIT_SUCCEEDED; 44. }; 45. //+------------------------------------------------------------------+ 46. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 47. { 48. ArrayInitialize(Buff_iMA_01, EMPTY_VALUE); 49. ArrayInitialize(Buff_iMA_02, EMPTY_VALUE); 50. 51. CopyBuffer(Handle_01, 0, 0, 9, Buff_iMA_01); 52. CopyBuffer(Handle_02, 0, 0, 20, Buff_iMA_02); 53. 54. return rates_total; 55. }; 56. //+------------------------------------------------------------------+ 57. void OnDeinit(const int reason) 58. { 59. if (Handle_01 != INVALID_HANDLE) 60. IndicatorRelease(Handle_01); 61. if (Handle_02 != INVALID_HANDLE) 62. IndicatorRelease(Handle_02); 63. }; 64. //+------------------------------------------------------------------+
Código 02
Cuando ejecutamos este código 02 y lo aplicamos a un gráfico, obtenemos un resultado parecido al que se muestra a continuación.

Imagen 01
Observa atentamente las dos medias móviles que aparecen en esta imagen 01. Tendrán un comportamiento muy similar al que vimos en el artículo anterior. Sin embargo, aunque este código 02 funciona, como demuestra la imagen 01, todavía es demasiado complicado. Al menos, desde mi punto de vista. El motivo es que varias partes del código se repiten continuamente. Y, peor aún, esta repetición hace que el código resulte demasiado tedioso de mantener o mejorar, especialmente si queremos añadir nuevas medias móviles. Normalmente, algunos operadores suelen trabajar con cuatro o más medias, además de otros indicadores. Por lo tanto, este código 02 nos exigirá mucho más esfuerzo que si utilizáramos algunos trucos para hacerlo bastante más sencillo y fácil de modificar.
El primero de los problemas que tú, mi estimado lector, puedes observar aquí es que, entre las líneas 12 y 15, tenemos algunas configuraciones. Prácticamente las mismas configuraciones se repiten entre las líneas 17 y 20. Y eso porque estamos definiendo solo dos medias móviles. Piensa en lo que ocurriría si hubiera más medias móviles. La siguiente cuestión que, desde mi punto de vista, complica innecesariamente el código está en las declaraciones comprendidas entre las líneas 22 y 25. Observa que también dependen de la cantidad de medias que vayamos a añadir al gráfico.
En consecuencia, todas las llamadas, tanto para inicializar como para acceder al buffer de cada una de las medias, así como para liberar el propio identificador, tuvieron que duplicarse. Insisto en que, si hubiera más medias, tendríamos que implementar en el código todas estas llamadas. Es decir, menudo esfuerzo.
Pero lo peor de todo está en las líneas nueve y diez. Incluso puedes idear alguna forma, como veremos en breve, de resolver buena parte de los problemas mencionados anteriormente. Sin embargo, resolver el problema que aparece en las líneas nueve y diez es bastante más complicado. Esto se debe a que, si aumentas o reduces el número de medias móviles que se van a trazar en el gráfico, tendrás que modificar estas dos líneas y ajustarlas correctamente. Si no lo haces de la forma adecuada, podrías tener problemas durante el trazado en el gráfico.
Así, el objetivo será mostrarte una alternativa para reducir todo este esfuerzo, haciendo que el compilador de MQL5 resuelva por nosotros buena parte de estos problemas, si no todos. La única tarea que realmente tendremos que realizar será indicar qué medias deben utilizarse. El resto será historia, porque ya no tendremos que preocuparnos por absolutamente nada más.
De acuerdo, ahora sí tengo verdadera curiosidad por saber cómo vas a resolver todos estos puntos. Algunos incluso puedo imaginar cómo solucionarlos. Sin embargo, en otros casos, como el problema de las líneas nueve y diez, no tengo la menor idea de cómo hacerlo. Entonces, acompáñame para aprender cómo podemos resolverlo. Lo que veremos aquí es solo uno de los muchos métodos posibles para hacer lo mismo. Recuerda: lo que importa es el resultado. La forma de llegar a él importa poco.
Así, lo primero que haremos será intentar reducir el número de llamadas a procedimientos o funciones que estén duplicadas en el código. Hacerlo es relativamente sencillo y directo, como puede verse al observar el código que 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_applied_price PRICE_CLOSE 08. //+----------------+ 09. #property indicator_buffers 2 10. #property indicator_plots 2 11. //+----------------+ 12. #property indicator_type1 DRAW_LINE 13. #property indicator_color1 clrRed 14. #property indicator_style1 STYLE_SOLID 15. #property indicator_width1 2 16. //+----------------+ 17. #property indicator_type2 DRAW_LINE 18. #property indicator_color2 clrBlue 19. #property indicator_style2 STYLE_SOLID 20. #property indicator_width2 2 21. //+------------------------------------------------------------------+ 22. struct stInfos 23. { 24. double Buff_iMA[]; 25. int Handle; 26. uint nPeriods; 27. ENUM_MA_METHOD Method; 28. }Infos[2]; 29. //+------------------------------------------------------------------+ 30. int OnInit(void) 31. { 32. Infos[0].nPeriods = 9; 33. Infos[0].Method = MODE_EMA; 34. Infos[1].nPeriods = 20; 35. Infos[1].Method = MODE_SMA; 36. for (uint c = 0; c < Infos.Size(); c++) 37. { 38. SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA); 39. if ((Infos[c].Handle = iMA(NULL, NULL, Infos[c].nPeriods, 0, Infos[c].Method, _AppliedTo)) == INVALID_HANDLE) 40. { 41. Print("Could not start the indicator..."); 42. return INIT_FAILED; 43. }; 44. } 45. 46. return INIT_SUCCEEDED; 47. }; 48. //+------------------------------------------------------------------+ 49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 50. { 51. for (uint c = 0; c < Infos.Size(); c++) 52. { 53. ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE); 54. CopyBuffer(Infos[c].Handle, 0, 0, Infos[c].nPeriods, Infos[c].Buff_iMA); 55. } 56. 57. return rates_total; 58. }; 59. //+------------------------------------------------------------------+ 60. void OnDeinit(const int reason) 61. { 62. for (uint c = 0; c < Infos.Size(); c++) 63. IndicatorRelease(Infos[c].Handle); 64. }; 65. //+------------------------------------------------------------------+
Código 03
El resultado de este código 03, al aplicarlo a cualquier gráfico, será el mismo que se muestra en la imagen 01. Sin embargo, observa cómo el código ha quedado mucho más sencillo y manejable. Esto se debe a que eliminamos todas aquellas llamadas duplicadas a funciones y procedimientos. Y esto empieza a hacer que el código resulte mucho más claro y manejable. Si queremos añadir nuevas medias móviles, tendremos que modificar muchas menos cosas. Básicamente, solo tendremos que modificar lo que se encuentre entre las líneas nueve y treinta y cinco. Esto se debe a que todo lo demás se adaptará a lo que hagamos dentro de esas líneas. Mmm, parece que el esfuerzo necesario para ajustar y modificar el código para futuras actualizaciones ha mejorado bastante. Es cierto, mi querido lector, pero todavía podemos mejorarlo mucho más.
La siguiente mejora que implementaremos es un poco más complicada en este primer momento. Pero solo resulta complicada ahora. A medida que practiques lo que veremos aquí, todo esto pasará a ser muy sencillo de entender.
Entonces, veamos cuál será la siguiente mejora que realizaremos aquí. Para entender lo que haremos, necesito que vuelvas al código 03 y observes el contenido comprendido entre las líneas 32 y 35. Lo que aparece en esas líneas no tiene por qué estar ahí. Además, las variables declaradas en las líneas 26 y 27 no necesitan definirse de esta manera.
¿Por qué? Pues bien, el motivo es que, si quieres crear un indicador que contenga un conjunto determinado de reglas, esas reglas no cambiarán con el tiempo. ¿Cierto? Por lo tanto, no tiene mucho sentido que esos valores sean variables. Podemos convertirlos en constantes.
Pero espera un momento. ¿Cómo es posible? Si convertimos estos valores en constantes, ¿cómo evitaremos que el código sea todavía más grande? Sinceramente, no sé cómo hacer algo así. Pues bien, para eso estamos aquí, mi querido lector. El objetivo es precisamente mostrarte cómo puedes modificar el código para hacerlo mucho más sencillo y práctico. Nada de pasarte el tiempo escribiendo un montón de código si puedes hacerlo todo de una forma mucho más rápida y sin complicaciones.
De esta manera, para alcanzar nuestro objetivo, modificaremos este código 03 para que quede 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_applied_price PRICE_CLOSE 08. #property indicator_buffers 2 09. #property indicator_plots 2 10. //+----------------+ 11. #property indicator_type1 DRAW_LINE 12. #property indicator_color1 clrRed 13. #property indicator_style1 STYLE_SOLID 14. #property indicator_width1 2 15. //+----------------+ 16. #property indicator_type2 DRAW_LINE 17. #property indicator_color2 clrBlue 18. #property indicator_style2 STYLE_SOLID 19. #property indicator_width2 2 20. //+------------------------------------------------------------------+ 21. const struct stAverange 22. { 23. uint nPeriods; 24. ENUM_MA_METHOD Method; 25. }Averange[] = 26. { 27. {9, MODE_EMA}, 28. {20, MODE_SMA} 29. }; 30. //+----------------+ 31. struct stInfos 32. { 33. double Buff_iMA[]; 34. int Handle; 35. }Infos[Averange.Size()]; 36. //+------------------------------------------------------------------+ 37. int OnInit(void) 38. { 39. for (uint c = 0; c < Averange.Size(); c++) 40. { 41. SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA); 42. if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE) 43. { 44. Print("Could not start the indicator..."); 45. return INIT_FAILED; 46. }; 47. } 48. 49. return INIT_SUCCEEDED; 50. }; 51. //+------------------------------------------------------------------+ 52. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 53. { 54. for (uint c = 0; c < Averange.Size(); c++) 55. { 56. ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE); 57. CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA); 58. } 59. 60. return rates_total; 61. }; 62. //+------------------------------------------------------------------+ 63. void OnDeinit(const int reason) 64. { 65. for (uint c = 0; c < Averange.Size(); c++) 66. IndicatorRelease(Infos[c].Handle); 67. }; 68. //+------------------------------------------------------------------+
Código 04
Una vez más, el resultado de ejecutar este código 04 sigue siendo el que se muestra en la imagen 01. Sin embargo, observa lo que hemos hecho aquí. Ahora tenemos, en la línea 21, la declaración de una estructura constante. Pero, a diferencia de lo que quizá estés imaginando, aquí NO ES LA ESTRUCTURA LA QUE ES CONSTANTE, sino la declaración realizada en la línea 25. Alto ahí. Ahora quiero que me expliques bien todo esto. Porque veo claramente que la declaración se realiza en la línea 21 y no en la línea 25. Entonces, ¿cómo puede ser constante la línea 25 y no la línea 21? De acuerdo, esto merece una explicación. El problema es que aquí estoy presentando el código en un formato más compacto. Para entenderlo mejor, vamos a utilizar un pequeño script. Se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. struct stInfos 07. { 08. uint u32; 09. double pf64; 10. }; 11. 12. const stInfos m1[] = 13. { 14. {10, 1.3}, 15. {15, 3.14} 16. }; 17. 18. for (uint c = 0; c < m1.Size(); c++) 19. Print("[", c, "]: ", m1[c].u32, " ", m1[c].pf64); 20. } 21. //+------------------------------------------------------------------+
Código 05
Cuando ejecutemos este código 05, obtendremos como resultado lo que se muestra en la siguiente imagen.

Imagen 02
Ahora presta atención: en la línea seis estamos definiendo una estructura. Esta puede utilizarse con cualquier variable que declaremos posteriormente. Esto ya deberías saber hacerlo. Pero supongamos que queremos disponer de una serie de valores que serán constantes. En ese caso, en la línea doce, cuando declaremos esos valores, podemos hacer que lo que inicialmente sería una variable pase a ser una constante. Esto no cambia el hecho de que el tipo de estructura declarado en la línea seis pueda utilizarse tanto para declarar variables como constantes.
Esto se aprecia claramente cuando ves las declaraciones por separado. Pero ¿qué ocurriría si las uniéramos, como se muestra en el código 04? ¿Qué dirías al respecto? Obviamente, podrías pensar que la estructura pasaría a considerarse constante. ¿No es así? Sin embargo, esta idea es completamente equivocada. Esto se debe a que NO ESTÁS VIENDO EL CÓDIGO, sino únicamente lo que aparece ahí. Para que esto quede realmente claro en tu mente, mi querido lector, vamos a modificar el código 05 y crear una pequeña prueba para demostrarlo. Este nuevo código se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const struct stInfos 07. { 08. uint u32; 09. double pf64; 10. } m1[] = 11. { 12. {10, 1.3}, 13. {15, 3.14} 14. }; 15. 16. stInfos var; 17. 18. var.u32 = 5; 19. var.pf64 = 2.59; 20. 21. for (uint c = 0; c < m1.Size(); c++) 22. Print("[", c, "]: ", m1[c].u32, " ", m1[c].pf64); 23. Print("[ variable ]: ", var.u32, " ", var.pf64); 24. } 25. //+------------------------------------------------------------------+
Código 06
Y, para sorpresa de muchos, cuando ejecutes este código 06, obtendrás el resultado que se muestra en la imagen siguiente.

Imagen 03
Pero ¿cómo es eso? Esto no tiene el menor sentido para mí. Si todavía no lo has entendido, estudia con calma este código 06 y experimenta con él hasta ver qué ocurre. En la práctica, en el fondo, la palabra clave const que aparece en la línea seis no está asociada a la declaración de la estructura, sino a la variable declarada en la línea diez. Sin embargo, como la palabra clave const aparece en la línea seis antes de la declaración de la variable de la línea diez, el compilador reconoce inmediatamente que en la línea diez tenemos la declaración de una constante. Por eso, en la línea 16 podemos hacer la declaración como una simple variable, cuyo contenido deberá ajustarse a la estructura definida en la línea seis.
De acuerdo, creo que ahora lo he entendido. Aun así, esto me sigue pareciendo un tanto confuso. Bien, ¿queda alguna otra duda sobre esta cuestión, mi querido lector? Mmm, estoy observando algo y hay un detalle de estos códigos que no termino de entender. Además de esta cuestión de la constante, que después practicaré para comprenderla mejor, ahora mi duda está relacionada con esta declaración o, mejor dicho, con la forma en que se inicializa este array. Veo que colocas los valores de una determinada manera. ¿Por qué? Pues bien, esta es una duda bastante pertinente. Y, como hasta ahora no se ha explicado, creo que merece una explicación adecuada.
Normalmente, cuando vamos a inicializar un array constante, utilizamos algo que ya se explicó en el artículo Del nivel básico al intermedio: Array (II). Sin embargo, lo que vimos allí sirve únicamente para valores discretos. En el caso de las estructuras, debemos tener en cuenta algunas particularidades adicionales.
En este caso, vamos a centrarnos en el código 06. Cada elemento, por así decirlo, es un bloque que debe contener la estructura correspondiente. Ahora presta atención, porque esto es importante. Normalmente, para que el código sea lo más legible posible, definimos en cada línea un bloque de la estructura. Y, dentro de ese bloque, el orden de los elementos determina el resultado que obtendremos.
Pero ¿cómo es eso? Pues bien, mi querido lector, en algunos lenguajes, como Python, por ejemplo, al realizar una declaración puedes indicar a qué campo corresponde cada valor concreto. Sería algo parecido a lo que aparece en las líneas 18 y 19 del código 06. Sin embargo, en muchos lenguajes, entre ellos MQL5, el orden en que están declarados los campos de una estructura determina el orden en que deberán aparecer los valores dentro del bloque de inicialización.
Es decir, como dentro de la estructura declarada en la línea seis del código 06, el primer tipo declarado en la línea ocho es un tipo entero, el primer dato dentro del bloque de la línea 12 TAMBIÉN deberá ser un valor entero. Y no solo eso: el primer elemento dentro del bloque se referirá, efectivamente, al primer elemento de la propia estructura. Puede parecer una tontería. Pero este tipo de concepto debe seguirse al pie de la letra. Muchos códigos devuelven valores incorrectos precisamente porque el programador introdujo el valor de un elemento en una posición equivocada dentro de un bloque. Y, al hacerlo, podemos alterar por completo el comportamiento de nuestro código.
Para que tengas una idea de lo perjudicial que puede ser esto, vamos a crear un ejemplo muy sencillo. En un caso, el bloque se crea de la forma correcta y, en otro, de forma incorrecta. Este ejemplo se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. const struct stAverange 07. { 08. uint nPeriods, 09. Method; 10. }Averange[] = 11. { 12. {9, MODE_EMA}, 13. {MODE_SMA, 20} 14. }; 15. 16. for (uint c = 0; c < Averange.Size(); c++) 17. Print("[", c, "]: Periods=> ", Averange[c].nPeriods, " ", EnumToString((ENUM_MA_METHOD)Averange[c].Method)); 18. } 19. //+------------------------------------------------------------------+
Código 07
Cuando se ejecute, este código 07 producirá el resultado que se muestra a continuación.

Imagen 04
Bien, quizá estés pensando: ¿pero quién sería lo bastante descuidado como para no darse cuenta de que hay algo incorrecto en este código 07? Y yo respondo: todos podemos cometer este tipo de error, mi querido lector. Y, por extraño que parezca, es mucho más común de lo que podrías imaginar. Aquí, por supuesto, estoy llevando la situación al extremo, ya que, en un caso, utilizamos un valor numérico entero y, en el otro, una enumeración.
Sin embargo, no olvides que una enumeración no es más que un valor entero representado mediante una constante simbólica. El resultado que se muestra en la imagen 04 deja claro que hay algo incorrecto. Sin embargo, en una amplia variedad de casos, esto no será tan fácil de detectar. Por eso debes tener cuidado al declarar un array constante cuyos elementos siguen una estructura, ya que el orden de los elementos modificará inevitablemente y en gran medida el resultado obtenido. Veremos esto en otro momento, cuando se explique el tema de matrices y vectores.
Muy bien, ahora que ya tienes algunos conceptos que te permiten comprender cómo funciona el código 04, podemos pasar al siguiente paso y buscar una implementación mucho más adecuada. En este paso eliminaremos buena parte de lo que todavía sigue duplicado. Pero lo haremos de una manera concreta. No porque sea la mejor ni tampoco la más adecuada, sino porque, desde mi punto de vista, es la que mejor se ajusta al objetivo didáctico. Así, como mejora del código 04, tenemos lo que 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_applied_price PRICE_CLOSE 08. #property indicator_buffers 2; 09. #property indicator_plots 2; 10. //+------------------------------------------------------------------+ 11. const struct stAverange 12. { 13. uint nPeriods; 14. ENUM_MA_METHOD Method; 15. color cor; 16. string szLabel; 17. }Averange[] = 18. { 19. {9, MODE_EMA, clrRed, "exponential of %d"}, 20. {20, MODE_SMA, clrGreen, "arithmetica of %d"} 21. }; 22. //+----------------+ 23. struct stInfos 24. { 25. double Buff_iMA[]; 26. int Handle; 27. }Infos[Averange.Size()]; 28. //+------------------------------------------------------------------+ 29. int OnInit(void) 30. { 31. for (uint c = 0; c < Averange.Size(); c++) 32. { 33. SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA); 34. PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE); 35. PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID); 36. PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 2); 37. PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor); 38. PlotIndexSetString(c, PLOT_LABEL, StringFormat(Averange[c].szLabel, Averange[c].nPeriods)); 39. if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE) 40. { 41. Print("Could not start the indicator..."); 42. return INIT_FAILED; 43. }; 44. } 45. 46. return INIT_SUCCEEDED; 47. }; 48. //+------------------------------------------------------------------+ 49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 50. { 51. for (uint c = 0; c < Averange.Size(); c++) 52. { 53. ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE); 54. CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA); 55. } 56. 57. return rates_total; 58. }; 59. //+------------------------------------------------------------------+ 60. void OnDeinit(const int reason) 61. { 62. for (uint c = 0; c < Averange.Size(); c++) 63. IndicatorRelease(Infos[c].Handle); 64. }; 65. //+------------------------------------------------------------------+
Código 08
Ahora presta atención a una cosa, mi querido lector, porque lo que estamos haciendo aquí es bastante peculiar y todavía puede mejorarse. Observa que, en comparación con el código 04, este código 08 ya no contiene aquella enorme cantidad de declaraciones relacionadas con las propiedades de cada una de las medias que vamos a trazar en el gráfico. Aquí, todo aquello se ha sustituido por una serie de funciones que se ajustarán según un patrón previamente definido.
Observa el contenido comprendido entre las líneas 34 y 38. Estas líneas hacen de forma dinámica lo que antes se hacía de forma estática mediante las declaraciones del código 04. Es cierto que esta línea 38 puede parecerte bastante extraña. Pero, para el compilador, tiene todo el sentido. Por eso estoy avanzando con ella poco a poco, para que realmente puedas comprender el concepto. Observa que, en la línea 16, estamos declarando un nuevo elemento que formará parte de la estructura. Este elemento recibe valores diferentes definidos en las líneas 19 y 20. Sin embargo, presta atención a estas dos líneas. ¿Te resultan familiares?
Si es así, felicidades, porque has estado estudiando los artículos y practicando para aprender cómo hacer las cosas. Para quienes no hayan conseguido entenderlo, estas dos líneas son, en realidad, una copia de una de esas cadenas que podemos utilizar en funciones como PrintFormat o StringFormat. Esto sirve para dar formato a una cadena y generar algún tipo de información con un formato muy específico.
Como lo hacemos así en las líneas 19 y 20, en la línea 38 podemos utilizar estas constantes como primer argumento de la función StringFormat para dar formato al código que mostraremos después.
De una forma u otra, el resultado final será exactamente el que podemos ver en la imagen 01. Sin embargo, aquí existe una diferencia. Cuando apliques el indicador por primera vez al gráfico, verás lo que se muestra en la siguiente imagen.

Imagen 05
Observa que no tenemos ninguna información que nos indique qué podemos ajustar ni cómo hacerlo. Sin embargo, una vez cargado el indicador, podremos volver a abrirlo y, en este caso, el resultado será el que se muestra a continuación.

Imagen 06
Observa que ahora, debido a que el código comprendido entre las líneas 34 y 38 se ejecutó la primera vez, tenemos información que sí tiene sentido. Y esto es algo muy interesante precisamente por lo siguiente. Fíjate en que, en las líneas 35, 36 y 37, estamos definiendo valores constantes. Sin embargo, y esta es la parte interesante, esos mismos valores serán sobrescritos por los que se definan en la ventana mostrada en la imagen 06. Es decir, aunque los definas de una determinada manera durante la implementación del código, MetaTrader 5 considerará prioritarios los valores definidos por el usuario. Esto nos proporciona mucha más flexibilidad de la que muchos podrían imaginar con solo observar el código.
Teniendo todo esto en cuenta y analizándolo con calma, se ve claramente que podemos mejorar aún más este código con cambios muy sencillos. Tanto es así que los mostraré en el siguiente fragmento.
. . . 10. //+------------------------------------------------------------------+ 11. const struct stAverange 12. { 13. uint nPeriods; 14. ENUM_MA_METHOD Method; 15. color cor; 16. }Averange[] = 17. { 18. {9, MODE_EMA, clrRed}, 19. {20, MODE_SMA, clrGreen} 20. }; 21. //+----------------+ . . . 37. PlotIndexSetString(c, PLOT_LABEL, StringFormat("%s of %d", (Averange[c].Method == MODE_EMA ? "Exponential" : "Arithmetica"), Averange[c].nPeriods)); . . .
Fragmento 01
Observa que ha quedado mucho más sencillo y que podría haberse construido así desde el principio. Pero, como tuvimos la oportunidad de mostrar algo diferente, aproveché para que pudieras aprender algo nuevo. Algo que muchos quizá no entiendan si llegan a verlo en alguno de mis otros códigos.
De acuerdo, todo parece muy bonito y maravilloso. Pero, aun así, seguimos teniendo una limitación en nuestro código. Incluso este código 08, que ya parece bastante adecuado, todavía puede mejorarse hasta llegar a la solución que quiero mostrar en este artículo. Esto se debe a que todavía tenemos el problema de las líneas ocho y nueve de este código 08. Puedes observar que aún necesitamos cambiar estos números manualmente. De acuerdo, pero ¿no existe algún otro método que nos permita agilizar todavía más las cosas? Sí, mi querido lector, existe otro método. Pero, antes de mostrarlo, necesito enseñarte otra cosa. Observa que, en ambas líneas, tanto en la ocho como en la nueve, tenemos definido el mismo valor. ¿No podríamos sustituir esto por una definición? Pues bien, veámoslo. Para ello, modificamos el código como se muestra en el siguiente fragmento.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. #define nAveranges 2 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_applied_price PRICE_CLOSE 10. #property indicator_buffers nAveranges; 11. #property indicator_plots nAveranges; 12. //+------------------------------------------------------------------+ 13. const struct stAverange 14. { 15. uint nPeriods; 16. ENUM_MA_METHOD Method; 17. color cor; 18. }Averange[] = . . .
Fragmento 02
Mmm, qué interesante. No sabía que podíamos hacer esto. Me gusta, porque, si necesito cambiar algo, ahora basta con modificar el valor que aparece en la línea seis y todo quedará resuelto. Pero, pensándolo bien, tampoco me parece tan sorprendente. Ahora recuerdo haber visto en el artículo Del nivel básico al intermedio: Definiciones (II) que podíamos utilizar definiciones para muchas cosas. Es cierto, mi querido lector. Pero la idea de utilizar una definición es solo un pequeño detalle para ayudarte a comprender otra cosa. Ahora vamos a modificar este fragmento 02 para que quede como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. const int nAveranges = 2; 07. //+------------------------------------------------------------------+ 08. #property indicator_chart_window 09. #property indicator_applied_price PRICE_CLOSE 10. #property indicator_buffers nAveranges; 11. #property indicator_plots nAveranges; 12. //+------------------------------------------------------------------+ 13. const struct stAverange 14. { 15. uint nPeriods; 16. ENUM_MA_METHOD Method; 17. color cor; 18. }Averange[] = . . .
Fragmento 03
Ahora presta atención, porque lo que vamos a hacer es algo bastante peculiar. Observa que lo único que hemos cambiado entre el fragmento 02 y este fragmento 03 es la línea seis. Solo se ha modificado eso. Sin embargo, si intentas compilar el código del indicador, recibirás el siguiente mensaje del compilador.

Imagen 07
De acuerdo, el compilador informa de un error. Este error indica que el compilador no ha podido entender qué debe hacer en las líneas diez y once. Sin embargo, aquí está la parte curiosa de este sistema que te estoy enseñando a crear, mi querido lector. El compilador no ha conseguido interpretar lo que hicimos en el fragmento 03. Pero, si esta vez modificamos el código hasta obtener lo que se muestra a continuación, ¿qué ocurrirá?
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. const struct stAverange 07. { 08. uint nPeriods; 09. ENUM_MA_METHOD Method; 10. color cor; 11. }Averange[] = 12. { 13. {9, MODE_EMA, clrRed}, 14. {20, MODE_SMA, clrGreen}, 15. {50, MODE_SMA, clrBlue}, 16. {200, MODE_SMA, clrBlack} 17. }; 18. //+----------------+ 19. #property indicator_chart_window 20. #property indicator_applied_price PRICE_CLOSE 21. #property indicator_buffers Averange.Size(); 22. #property indicator_plots Averange.Size(); 23. //+------------------------------------------------------------------+ 24. struct stInfos 25. { 26. double Buff_iMA[]; 27. int Handle; 28. }Infos[Averange.Size()]; 29. //+------------------------------------------------------------------+ 30. int OnInit(void) 31. { 32. for (uint c = 0; c < Averange.Size(); c++) 33. { 34. SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA); 35. PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE); 36. PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID); 37. PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 2); 38. PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor); 39. PlotIndexSetString(c, PLOT_LABEL, StringFormat("%s of %d", (Averange[c].Method == MODE_EMA ? "Exponential" : "Arithmetica"), Averange[c].nPeriods)); 40. if ((Infos[c].Handle = iMA(NULL, NULL, Averange[c].nPeriods, 0, Averange[c].Method, _AppliedTo)) == INVALID_HANDLE) 41. { 42. Print("Could not start the indicator..."); 43. return INIT_FAILED; 44. }; 45. } 46. 47. return INIT_SUCCEEDED; 48. }; 49. //+------------------------------------------------------------------+ 50. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 51. { 52. for (uint c = 0; c < Averange.Size(); c++) 53. { 54. ArrayInitialize(Infos[c].Buff_iMA, EMPTY_VALUE); 55. CopyBuffer(Infos[c].Handle, 0, 0, Averange[c].nPeriods, Infos[c].Buff_iMA); 56. } 57. 58. return rates_total; 59. }; 60. //+------------------------------------------------------------------+ 61. void OnDeinit(const int reason) 62. { 63. for (uint c = 0; c < Averange.Size(); c++) 64. IndicatorRelease(Infos[c].Handle); 65. }; 66. //+------------------------------------------------------------------+
Código 09
Lo que ocurrirá es que obtendrás el resultado que se muestra en la siguiente imagen.

Imagen 08
Pero espera un momento. No te vas a ir sin explicarme antes qué ha sido esto. ¿Cómo es posible? ¿Qué clase de código tan extraño y disparatado has creado? Vamos, hasta hace un momento parecía que no iba a funcionar. ¿Y ahora vienes y haces esto? Pues bien, mi querido lector, este era precisamente el punto al que quería llegar. Este código 09 es mucho más práctico, porque todo lo que necesitas hacer para trazar un mayor o menor número de medias móviles en el gráfico es añadirlas a la declaración de la constante que aparece en la línea seis. En este caso estoy declarando cuatro medias diferentes, pero, si fueran necesarias más o quizá menos, bastaría con modificar este punto del código. Al hacerlo, el compilador hará que el código se adapte a lo que hayas declarado allí, sin que tengas que modificar ningún otro punto del código.
Este tipo de conocimiento no se adquiere de un momento para otro. Se debe a que sé cómo interpreta un compilador determinadas partes del código, algo que llevo años practicando y estudiando. Observa que, aunque el fragmento 03 no funcione, eso no significa que, si intentamos hacer algo de una determinada manera, el compilador no vaya a entenderlo. El hecho de que el compilador no haya interpretado el fragmento 03 como queríamos no le impidió entender este código 09. Y, al final, terminamos generando algo que antes muchos habrían dudado que fuera posible hacer.
Consideraciones finales
Este ha sido un artículo bastante divertido, aunque en muchos momentos no me haya sentido realmente entusiasmado. Esto se debe a que lo que hicimos aquí es algo tan común para mí que resulta tedioso y que ya debería ser conocido por muchos de ustedes. Sin embargo, al observar el código de muchas otras personas, terminé dándome cuenta de que buena parte de ellas ni siquiera sabía que esto podía hacerse. Y, si el código que vimos al final se hubiera mostrado simplemente, sin ofrecer antes una explicación adecuada de cómo llegar hasta él, muchos, con toda seguridad, no habrían entendido absolutamente nada del código y, sobre todo, por qué funciona el código 09.
Así que aquí queda el consejo para todos. Intenta estudiar y practicar buscando hacer las cosas de una manera poco convencional. Acabarás descubriendo que existen soluciones mucho más sencillas para problemas que parece que todo el mundo intenta resolver de la misma forma. Así que diviértete con los códigos incluidos en el anexo y nos vemos en el próximo artículo.
| 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 |
| Scripts\Code 01 | Demostración básica |
| Scripts\Code 02 | Demostración básica |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/17070
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.
Red neuronal en la práctica: Descenso de gradiente estocástico
Red neuronal en la práctica: Descenso de gradiente
Particularidades del trabajo con números del tipo double en MQL4
Redes neuronales en el trading: una visión unificada del espacio y el tiempo (Final)
- 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