Del nivel básico al intermedio: Subventanas (II)
Introducción
En el artículo anterior, Del básico al intermedio: Subventanas (I), empezamos a hablar de las subventanas. Aunque fue una breve introducción, lo que vimos allí es fundamental para comprender lo que viene a continuación. Por sencilla que parezca esta parte introductoria, no creas que ya sabes trabajar con subventanas solo porque puedes hacer algunas cosas simples con ellas. Las subventanas ofrecen posibilidades muy interesantes y, si planificas bien cómo utilizarlas en tu código, podrás implementar soluciones que de otro modo serían imposibles o quizá no tan sencillas de implementar.
Bien, no quiero extenderme demasiado antes de entrar en el tema principal, así que sigamos nuestro ritual. Es hora de que dejes a un lado las distracciones y te concentres en lo que voy a explicar. Dicho esto, vayamos al grano.
Subventanas (II)
Una de las primeras cosas que intentan hacer muchos programadores principiantes es crear un Asesor Experto para operar en el mercado. Al hacerlo, suelen añadir numerosos controles para manejarlo. El resultado es un gráfico recargado de objetos que podrían estar en otro lugar: una subventana. Así, el gráfico principal quedaría despejado y podríamos analizar con claridad lo que sucede en cada una de sus barras.
Bien, colocar los controles en una subventana es una buena idea. Sin embargo, hay una condición:LAS SUBVENTANAS SOLO SE PUEDEN CREAR MEDIANTE UN INDICADOR.
Aquí empieza a complicarse todo, sobre todo si aún no tienes experiencia para hacer funcionar este tipo de implementación. Más adelante explicaré con claridad y sencillez cómo hacerlo. Por ahora, te pido paciencia, querido lector, porque apenas estamos empezando a estudiar las subventanas. Quería darte este ejemplo para animarte a estudiar estos artículos con atención. Quizá a algunos les parezcan una pérdida de tiempo, pero cada punto que veremos será importante para comprender los artículos que vendrán después.
Empecemos por profundizar en una cuestión que abordamos en el artículo anterior: cómo calcula tu indicador la curva que se trazará en una subventana. ¿Otra vez con este tema? Creo que ya entendí cómo hacer que mi indicador calcule correctamente. ¿Ah, sí, querido lector? ¿De verdad sabes hacerlo?
Creo que pocos saben hacerlo. No porque me considere un genio, sino por lo que he visto hacer a otros programadores. Para explicarlo de la forma más sencilla posible, utilizaremos uno de los códigos del artículo anterior. Esta vez reduciremos el ejemplo a un cálculo aún más simple: cuantos más elementos haya en el código, más difícil será explicarlo y comprenderlo. El código que utilizaremos aparece a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. const struct stAverange 07. { 08. int nPeriods; 09. ENUM_MA_METHOD Method; 10. color cor; 11. }Averange[] = 12. { 13. {20, MODE_SMA, clrBlack} 14. }; 15. //+----------------+ 16. #property indicator_chart_window 17. #property indicator_applied_price PRICE_CLOSE 18. #property indicator_buffers Averange.Size(); 19. #property indicator_plots Averange.Size(); 20. //+------------------------------------------------------------------+ 21. struct stInfos 22. { 23. double Buff[]; 24. }Infos[Averange.Size()]; 25. //+------------------------------------------------------------------+ 26. int OnInit(void) 27. { 28. for (uint c = 0; c < Averange.Size(); c++) 29. { 30. SetIndexBuffer(c, Infos[c].Buff, INDICATOR_DATA); 31. PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE); 32. PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID); 33. PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 2); 34. PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor); 35. PlotIndexSetString(c, PLOT_LABEL, StringFormat("Arithmetic of %d", Averange[c].nPeriods)); 36. } 37. 38. return INIT_SUCCEEDED; 39. }; 40. //+------------------------------------------------------------------+ 41. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 42. { 43. 44. for (uint c = 0; c < Averange.Size(); c++) 45. { 46. if (prev_calculated == 0) 47. { 48. double loc = 0; 49. for (int c1 = 0; c1 < Averange[c].nPeriods; c1++) loc += price[Averange[c].nPeriods - c1]; 50. Infos[c].Buff[Averange[c].nPeriods] = loc / Averange[c].nPeriods; 51. } 52. switch (Averange[c].Method) 53. { 54. case MODE_SMA: 55. for (int i = (prev_calculated > 0 ? prev_calculated - 1 : Averange[c].nPeriods + 1); i < rates_total; i++) 56. Infos[c].Buff[i] = ((Infos[c].Buff[i - 1] * Averange[c].nPeriods) - price[i - Averange[c].nPeriods] + price[i]) / Averange[c].nPeriods; 57. break; 58. } 59. } 60. 61. return rates_total; 62. }; 63. //+------------------------------------------------------------------+
Código 01
Gracias a la línea 13, el código 01 calcula correctamente la media aritmética que queremos trazar. Pero ¿sabes en qué dirección se realiza el cálculo? La siguiente imagen muestra la media obtenida y la dirección del cálculo.

Imagen 01
La flecha de la imagen 01 indica la dirección del cálculo; la línea negra representa la media calculada. ¿Por qué es importante saberlo? Quizá pienses que, en la práctica, no supone ninguna diferencia. Y, querido lector, la mayoría de las veces da igual calcular en una dirección o en la otra. Sin embargo, en programación no podemos guiarnos solo por el sentido común. Los programadores implementan lo que se les pide, al margen de lo que dicte el sentido común. Su código debe producir el resultado esperado; cómo lo consigan es secundario.
Ahora bien, en programación, 2 + 3 puede ser distinto de 3 + 2. ¿Cómo es posible? En los artículos dedicados a la sobrecarga de operadores mostré que las operaciones no siempre funcionan como muchos imaginan. Por eso, según lo que intentes hacer, el cálculo puede funcionar en una dirección, mientras que hacerlo en la otra puede provocar que el código falle. El código 01 es un buen ejemplo de ello.
Para empezar a examinarlo, añadamos un indicador de volumen, como se muestra a continuación.

Imagen 02
Ahora añadiremos el indicador del código 01 al indicador de volumen. Así podremos observar la media del volumen de los últimos 20 períodos. Lo haremos como se muestra en la siguiente animación.

Animación 01
Como era de esperar, funcionó. Sin embargo, eso no significa que el código 01 sirva para cualquier situación. Ahora veremos un caso en el que no producirá el resultado que buscamos. Para ello, quitaremos el indicador de volumen y añadiremos otro tipo de indicador. Pero antes, querido lector, quiero mostrarte algo. La siguiente imagen muestra estas opciones.

Imagen 03
Observa que, en la imagen 03, hay dos indicadores que utilizan el código 01. Tenlo presente, porque este detalle nos resultará muy útil más adelante. Ahora añadiremos otro indicador, como se muestra en la siguiente animación.

Animación 02
Aquí no importa por qué he elegido este indicador: lo escogí al azar entre los disponibles. Si quieres, después puedes probar con otro. Lo que buscamos ahora es calcular una segunda media a partir de los valores del indicador de la subventana para suavizar la media verde que este traza. Para ello utilizaremos el indicador del código 01. Tendrás que añadirlo de la forma que ya conocemos. Cuando se trace, aparecerá una segunda media: la media aritmética de los últimos veinte valores calculados previamente por el indicador de la subventana.
Hagamos lo que se muestra en la siguiente animación.

Animación 03
Pero ¿qué pasó? ¿Dónde está el indicador? ¿Habrá fallado de forma catastrófica y se habrá quitado del gráfico? Mmm, no entiendo. Bien, querido lector, ahora empiezas a darte cuenta de que las cosas no son tan sencillas como creías, ¿verdad? Veamos qué sucedió. Primero, comprobemos si hay algún mensaje en el terminal que indique un posible fallo.

Imagen 04
Como muestra la imagen 04, no se informó de ningún fallo. Probemos otra forma de averiguar qué pasó: consultemos la lista de indicadores. Allí veremos lo que aparece en la siguiente imagen.

Imagen 05
Vaya, qué extraño. En la imagen 05 vemos que el indicador figura en la lista donde corresponde, aunque no se muestra en el gráfico. De acuerdo, me rindo. Creo que voy a dedicarme a otra cosa. Definitivamente, la programación no es para mí. Tranquilo, querido lector. Recuerda que estos artículos buscan enseñarte a alcanzar tus objetivos. Para ello necesitas entender cómo detectar los fallos y errores que revela el comportamiento de tu código. Si observas con atención la animación 03, notarás algo en el gráfico. Para verlo mejor, detendremos la animación en un fotograma y examinaremos con calma ese detalle.

Imagen 06
Observa el punto que he destacado en la imagen. El valor resaltado debería ser otro. ¿Significa eso que no podemos calcular una media de valores con un indicador como el que aparece en la imagen 06? Hagamos otra prueba para responder a esta pregunta. Recuerda que el código 01 calcula una media aritmética de veinte períodos. Utilizaremos ahora otro indicador incluido de forma predeterminada en MetaTrader 5 para comprobar si el fallo está en nuestro indicador o si hay otro motivo por el que no se traza en el gráfico. La siguiente animación muestra la prueba.

Animación 04
¡Qué curioso! Resulta que sí funciona. Entonces, el error está en nuestro código. Así es, querido lector. Bien, ahora no sé cómo corregirlo: a diferencia de lo que vimos en el artículo anterior, donde pedíamos a MetaTrader 5 que realizara el cálculo, esta vez lo hacemos nosotros mismos y, aun así, el indicador falla. ¿Por qué?
La causa está en la dirección del cálculo, como vimos en la imagen 01. El código 01 calcula desde los datos más antiguos hacia los más recientes. Es posible que, en algún punto, no procese correctamente esos datos, de modo que el indicador falle y la media no llegue a trazarse. Esto ocurre porque el indicador contiene datos no válidos. Por eso aparece el valor que vimos en la imagen 06.
¿Ves cómo empieza a encajar todo y cómo estos elementos están relacionados entre sí? Si me limitara a mostrarte un código que funciona perfectamente, no tendrías ocasión de descubrir dónde estaba el error, porque a primera vista no resulta evidente.
¿Cómo podemos corregir el código 01 para que la media se trace en la subventana? Debemos invertir la dirección del cálculo dentro del procedimiento OnCalculate. Es decir, el cálculo debe empezar por los valores más recientes y avanzar hacia los más antiguos. Para ello modificaremos el código 01 como se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property description "DEMO Indicator" 05. //+------------------------------------------------------------------+ 06. const struct stAverange 07. { 08. int nPeriods; 09. ENUM_MA_METHOD Method; 10. color cor; 11. }Averange[] = 12. { 13. {20, MODE_SMA, clrYellow} 14. }; 15. //+----------------+ 16. #property indicator_chart_window 17. #property indicator_applied_price PRICE_CLOSE 18. #property indicator_buffers Averange.Size(); 19. #property indicator_plots Averange.Size(); 20. //+------------------------------------------------------------------+ 21. struct stInfos 22. { 23. double Buff_iMA[], 24. f_Expo; 25. }Infos[Averange.Size()]; 26. //+------------------------------------------------------------------+ 27. int OnInit(void) 28. { 29. for (uint c = 0; c < Averange.Size(); c++) 30. { 31. SetIndexBuffer(c, Infos[c].Buff_iMA, INDICATOR_DATA); 32. Infos[c].f_Expo = (double) (2.0 / (1.0 + Averange[c].nPeriods)); 33. PlotIndexSetInteger(c, PLOT_DRAW_TYPE, DRAW_LINE); 34. PlotIndexSetInteger(c, PLOT_LINE_STYLE, STYLE_SOLID); 35. PlotIndexSetInteger(c, PLOT_LINE_WIDTH, 1); 36. PlotIndexSetInteger(c, PLOT_LINE_COLOR, Averange[c].cor); 37. PlotIndexSetString(c, PLOT_LABEL, StringFormat("%s of %d", (Averange[c].Method == MODE_EMA ? "Exponential" : "Arithmetica"), Averange[c].nPeriods)); 38. } 39. 40. return INIT_SUCCEEDED; 41. }; 42. //+------------------------------------------------------------------+ 43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 44. { 45. int c0 = 0; 46. double value; 47. 48. for (uint c = 0; c < Averange.Size(); c++) switch (Averange[c].Method) 49. { 50. case MODE_SMA: 51. for (int i = rates_total - 1; i > prev_calculated; i--) 52. { 53. value = 0; 54. for (int j = 0; (i - j) > 0 && j < Averange[c].nPeriods; j++) 55. value += price[i - j]; 56. Infos[c].Buff_iMA[i] = value / Averange[c].nPeriods; 57. } 58. break; 59. } 60. 61. return rates_total; 62. }; 63. //+------------------------------------------------------------------+
Código 02
Ahora presta atención, querido lector. El código 02 invierte la dirección del cálculo. Según cómo realicemos esa inversión, tendremos que adaptar el código de una u otra manera. Como el objetivo aquí es didáctico, utilizo la media aritmética, que nos permite trabajar con un código considerablemente más sencillo. Todo lo visto hasta aquí se aplica también al código 02. Hay, sin embargo, una diferencia: cuando aplicamos el indicador del código 02 al indicador añadido al gráfico en la animación 02, obtenemos un resultado muy distinto del que vimos en la animación 03. Puedes observarlo en la siguiente animación.

Animación 05
Observa que cambié el color y el grosor de la línea del indicador directamente en el código 02. Así, al añadirlo, podemos ver claramente que la curva que traza se ajusta a la del indicador colocado en la animación 04. Esto demuestra que ya funciona correctamente para su uso básico. ¿No es maravilloso verlo y aprenderlo, querido lector? Sin lo que acabas de aprender, no habrías podido identificar el error: casi nadie menciona que hay que tener en cuenta la dirección del cálculo al programar.
Quizá se deba a que muchos no hayan intentado hacer lo que te estoy mostrando. Por eso desconocen estos detalles y no pueden explicar la causa de fallos como este. Cuando nadie te da una explicación, puedes terminar por rendirte y atribuir el problema a la causa equivocada. Muchos culpan a la plataforma o incluso al lenguaje MQL5 cuando, en realidad, desconocen lo que acabamos de ver.
Bien, esto ha sido interesante y bastante entretenido. Ahora utilizaremos el código 02 para explicar algo que apenas esbozamos en el artículo anterior: cómo y por qué elegir entre FIRST INDICATOR'S DATA y PREVIOUS INDICATOR'S DATA. La siguiente imagen muestra estas opciones.

Imagen 07
Para entenderlo con facilidad, necesitamos otra información, que aparece en la siguiente imagen.

Imagen 08
Cuando añadas un indicador a una subventana y quieras que sus cálculos utilicen valores ya calculados y presentes en ella, tendrás que fijarte en lo que muestra la imagen 08.
La flecha de la imagen 08 indica el orden en que se añadieron los indicadores. Hay casos especiales en los que ese orden no se refleja exactamente así, pero se cumple en casi el 100 % de las ocasiones. En cualquier caso, el indicador que aparece más arriba en la lista se considera el primero; los demás vienen uno detrás de otro. Para entender FIRST INDICATOR'S DATA y PREVIOUS INDICATOR'S DATA, imagina que los indicadores de la imagen 08 están organizados como una cola de valores de la que puedes elegir los datos del primero o del último.
Si no sabes qué es una cola de valores, consulta el artículo Del básico al intermedio: Colas, listas y árboles (II) para hacerte una idea de lo que hablamos. ¿Ves cómo se acumulan los conocimientos con el tiempo? Por eso puede parecer que no avanzamos cuando, en realidad, vamos volando.
Una vez que entiendes que estamos tratando con una cola, resulta fácil saber qué opción elegir. Con FIRST INDICATOR'S DATA le indicamos a MetaTrader 5 que utilice los datos del primer indicador de la cola que muestra la imagen 08. Con PREVIOUS INDICATOR'S DATA le indicamos que utilice los datos del último indicador de esa cola.
Mmm, esto sí que está bien pensado. Pero ¿podemos saltarnos este sistema y elegir qué indicador queremos utilizar? Sí, querido lector, hay formas de hacerlo. Por ahora no te preocupes por eso: apenas estamos empezando a explorar lo que podemos hacer con las subventanas. Recuerda que también podemos establecer este tipo de relación entre indicadores en el gráfico principal, o ventana principal. En ese caso, sin embargo, estaremos limitados a PREVIOUS INDICATOR'S DATA. Aun así, los resultados pueden ser muy interesantes, como muestra la siguiente animación.

Animación 06
En efecto, esto permite realizar estudios con distintas finalidades. Pero volvamos a las subventanas. Ya vimos qué ocurre al añadir el indicador del código 02 a una subventana en la animación 05. Veamos ahora qué pasa si elegimos PREVIOUS INDICATOR'S DATA para entender cómo se forma la cola con cada nuevo indicador. Recuerda que la cola inicial es la de la imagen 08. Al añadir el primer indicador nuevo, obtenemos lo que muestra la siguiente animación.

Animación 07
La cola resultante aparece en la siguiente imagen.

Imagen 09
Observa que Code 02 es ahora el último elemento de la cola. Si añadimos otro indicador, obtendremos el resultado que muestra la siguiente animación.

Animación 08
Creo que ya puedes imaginar adónde nos lleva esto. Cada vez que añadimos un indicador que utiliza como base de cálculo los datos del indicador inmediatamente anterior, suavizamos o intensificamos la señal, según cómo realice los cálculos el indicador. Con este modelo sencillo podemos hacer muchas cosas. No necesitamos cálculos rebuscados ni una media móvil mágica o un indicador mágico que solo un genio de las matemáticas podría idear. Podemos obtener resultados muy interesantes con un cálculo simple, rápido y directo.
Bien, ¿qué ocurre si quitamos un indicador del que dependen otros? En la animación 08, por ejemplo, tenemos dos indicadores Code 02 encadenados, uno dependiente del otro. Si quitamos el que está más arriba, ¿qué pasará con el de abajo? ¿Ocupará el lugar del que acabamos de quitar? Es una pregunta interesante, querido lector, porque la respuesta depende de cómo gestione MetaTrader 5 esta situación. No podemos indicarle cómo debe actuar o, al menos, yo no conozco una forma de hacerlo. Por tanto, necesitamos ver qué sucede en este caso concreto. La siguiente animación nos lo mostrará.

Animación 09
Observa que MetaTrader 5, de forma predeterminada, elimina los indicadores que dependen del que estamos quitando. Esto es mucho más práctico y sencillo que idear algún mecanismo complicado para mantener los demás indicadores en el gráfico. Además, es una solución limpia y elegante: no deja lugar a dudas sobre lo que calcula cada indicador.
Veamos ahora un último detalle para cerrar el artículo. Modificaremos una sola línea del código 02, que aparece en el siguiente fragmento.
. . . 11. }Averange[] = 12. { 13. {20, MODE_SMA, clrDodgerBlue} 14. }; 15. //+----------------+ 16. #property indicator_separate_window 17. #property indicator_applied_price PRICE_CLOSE 18. #property indicator_buffers Averange.Size(); . . .
Fragmento 01
El cambio está en la línea 16. No te preocupes, querido lector: en el anexo encontrarás todos los códigos completos para estudiarlos con calma. Esta modificación produce un comportamiento muy interesante en la práctica, aunque, en teoría, cabría esperar algo muy distinto de lo que vas a observar.
Cuando defines en un indicador la propiedad indicada en la línea 16, esperas que SIEMPRE se coloque en una subventana nueva, ¿verdad, querido lector? Sin embargo, no siempre ocurre así. Esta propiedad solo hará que se cree una subventana SI Y SOLO SI añadimos el indicador con la configuración predeterminada de la fuente de datos. Es decir, sin elegir FIRST INDICATOR'S DATA ni PREVIOUS INDICATOR'S DATA como fuente de datos. Si seleccionamos alguna de estas opciones, MetaTrader 5 ignorará la propiedad definida en la línea 16 y colocará el indicador en la subventana a la que lo estamos añadiendo.
Pero espera un momento. ¿Significa esto que podemos colocar el indicador en el gráfico principal a pesar de esa propiedad? NO, querido lector, en este caso no podemos hacerlo. MetaTrader 5 impide seleccionar PREVIOUS INDICATOR'S DATA en esa situación concreta. En cambio, si añades el indicador a una subventana que ya contiene otro, podrás elegir entre FIRST INDICATOR'S DATA y PREVIOUS INDICATOR'S DATA. En ese caso, no se creará una subventana nueva.
Para que quede más claro, veamos algunas animaciones que muestran este comportamiento.

Animación 10
En la animación 10 vemos qué sucede cuando intentamos añadir al gráfico principal un indicador configurado para colocarse en una subventana nueva. Observa que aquí no podemos modificar gran cosa.

Animación 11
En la animación 11 comprobamos qué ocurre al añadir un indicador a una subventana cuando este está configurado para colocarse en una subventana nueva. Observa que, a diferencia de lo que sucedía en la animación 10, ahora podemos elegir opciones que antes no estaban disponibles.
Lo que más nos interesa es que, en la animación 11, se traza exactamente la misma curva que en la animación 10. Aunque la propiedad del indicador indique que debe ocupar su propia subventana, MetaTrader 5 detecta que ya lo estamos colocando en una. Por eso no necesita crear otra.

Animación 12
En la animación 12 hacemos que el mismo indicador de las animaciones 10 y 11 utilice datos ya calculados y presentes en la subventana. El resultado se parece mucho a lo que vimos al principio del artículo. Esto muestra que un indicador puede comportarse de forma algo distinta de la que indican sus propiedades si con ello MetaTrader 5 puede gestionar correctamente los indicadores de la subventana.

Animación 13
Para terminar la demostración, veamos la animación 13. Al igual que en la animación 12, MetaTrader 5 determina que lo mejor es mantener el indicador en la subventana a la que lo arrastramos. Esta decisión facilita la gestión de todos los indicadores del gráfico. Así, MetaTrader 5 sabe cómo quitarlos después según las dependencias que existan entre ellos.
Consideraciones finales
En este artículo, que por cierto ha sido bastante entretenido y didáctico, mostré varias características del uso de indicadores en subventanas. Recuerda que apenas estamos empezando a estudiar y a demostrar cómo funciona todo esto en la práctica.
También vimos que debemos prestar atención al escribir nuestro código. Según cómo lo implementemos, puede llegar a comportarse de una forma distinta de la esperada y producir resultados completamente erróneos o incluso no producir ninguno. En programación, aprender de verdad exige experimentar y estudiar los detalles de cada implementación. Así es como puedes llegar a ser un profesional cualificado.
En el anexo encontrarás los tres códigos principales del artículo para que los pruebes y observes qué sucede en cada situación. Entender dónde falla un código es mucho más importante y enseña mucho más que limitarse a verlo funcionar perfectamente en pantalla. Así que, querido lector, aprovecha esta oportunidad y estudia con calma lo que hemos visto. Puede resultarte muy útil en el futuro.
| 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 |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/17113
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
Probador de estrategias para Python-MetaTrader 5 (Parte 01): Simulador de operaciones
Particularidades del trabajo con números del tipo double en MQL4
Red neuronal en la práctica: Cuándo usar una neurona artificial y entender su función en MQL5
- 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