Del básico al intermedio: Objetos (IV)
Introducción
En el artículo anterior, Del básico al intermedio: Objetos (III), mostré cómo podías implementar un indicador con un propósito muy sencillo: colocar de forma bastante simple una línea de tendencia en el gráfico sin tener que abrir el menú de MetaTrader 5.
Sé que a muchos ese contenido, con ese objetivo, les parece totalmente innecesario. Sin embargo, recuerda que la meta aquí no es implementar algo concreto, sino mostrar cómo podemos hacer las cosas. Que algo se implemente o no y posea utilidad práctica es solo un detalle. Lo importante es que tú, querido lector, comprendas que cualquiera puede poner sus ideas en práctica si estudia y practica lo que se viene mostrando en los artículos.
Bien, lo visto y realizado en el artículo anterior es apenas un aperitivo de lo que realmente podemos lograr cuando empezamos a programar nuestras propias aplicaciones con objetivos particulares. En este artículo intentaré mostrarte algo muy interesante: un indicador que está oculto en MetaTrader 5 y del que casi nadie conoce su existencia.
Así que sigamos nuestro ritual habitual, que consiste en apartar todo aquello que pueda distraerte mientras estudias el artículo, y pasemos al siguiente tema para saber qué indicador es.
Un indicador oculto en MetaTrader 5
Muchos dicen que MetaTrader 5 tiene pocos indicadores o pocas herramientas para analizar el mercado. Personalmente, discrepo rotundamente de esa afirmación. No es la plataforma ni su contenido lo que es limitado, sino la visión estrecha de muchos que siempre esperan encontrar las cosas listas y tal como las imaginan.
Existe un indicador en MetaTrader 5 que casi nadie sabe que existe porque está oculto dentro de otro indicador. En realidad, no es exactamente un indicador, sino un objeto de análisis. Lo que necesitamos es abrir ese objeto para que el indicador u objeto oculto salga a la luz. Por defecto sería posible usarlo tal como está implementado en MetaTrader 5, pero, si lo modificamos de la forma correcta, la información que pasará a representar será mucho más clara y fácil de entender, ya que el objetivo será totalmente distinto del propósito original con el que viene por defecto.
Me refiero al objeto Fibonacci. Es cierto que podríamos crear lo que implementaremos dentro de poco usando líneas de tendencia, pero con el objeto Fibonacci resulta mucho más sencillo construir el objeto que buscamos.
Entonces, para empezar, veamos el código del indicador que creamos en el artículo anterior. Se muestra a continuación y será nuestro punto de partida para construir otro tipo de indicador.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_Prefix "Demo" 05. //+------------------------------------------------------------------+ 06. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 07. //+------------------------------------------------------------------+ 08. #include <Tutorial\File 01.mqh> 09. //+------------------------------------------------------------------+ 10. st_Cross gl_Cross; 11. //+------------------------------------------------------------------+ 12. int OnInit() 13. { 14. gl_Cross.Init(); 15. 16. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 17. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 18. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 19. 20. return INIT_SUCCEEDED; 21. }; 22. //+------------------------------------------------------------------+ 23. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 24. { 25. return rates_total; 26. }; 27. //+------------------------------------------------------------------+ 28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 29. { 30. st_TimePrice tp; 31. static string isPaint = ""; 32. 33. switch (id) 34. { 35. case CHARTEVENT_KEYDOWN: 36. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 37. break; 38. case CHARTEVENT_MOUSE_MOVE: 39. if (((uchar)sparam & MOUSE_MIDDLE) != 0) 40. { 41. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 42. if (isPaint == "") 43. { 44. ObjectCreate(0, isPaint = macro_NameObject, OBJ_TREND, 0, tp.Time, tp.Price); 45. ObjectSetInteger(0, isPaint, OBJPROP_SELECTABLE, true); 46. ObjectSetInteger(0, isPaint, OBJPROP_COLOR, clrMagenta); 47. ObjectSetInteger(0, isPaint, OBJPROP_WIDTH, 3); 48. ObjectSetInteger(0, isPaint, OBJPROP_RAY_RIGHT, true); 49. } 50. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 51. gl_Cross.Show(); 52. }else 53. { 54. isPaint = ""; 55. gl_Cross.Hide(); 56. } 57. break; 58. case CHARTEVENT_MOUSE_WHEEL: 59. break; 60. } 61. ChartRedraw(); 62. }; 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 67. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 68. 69. gl_Cross.Hide(); 70. 71. if (reason == REASON_REMOVE) 72. ObjectsDeleteAll(0, def_Prefix); 73. }; 74. //+------------------------------------------------------------------+
Código 01
Ese código, aunque funciona, presenta un pequeño inconveniente desde la perspectiva de la mayoría de los usuarios: crea el objeto que queremos añadir al gráfico usando el botón central del ratón. No es lo habitual, pues normalmente esperamos utilizar el botón izquierdo para este tipo de acción. Sin embargo, este es solo un detalle que por ahora no nos incomoda. Para poder usar el botón izquierdo tendríamos que modificar algunos aspectos del funcionamiento del código, pero eso lo dejaremos para más adelante.
En este momento queremos sustituir el objeto OBJ_TREND por el objeto OBJ_FIBO. Con esta simple sustitución obtenemos lo que ves a continuación.

Animación 01
Observa que pasamos de una línea de tendencia a un objeto Fibonacci. No obstante, no pretendemos usar el objeto tal cual se crea, ya que el objeto que queremos desarrollar se basa en este Fibonacci, pero no es exactamente él. Lo que vamos a construir es una modificación de dicho objeto Fibonacci. Para simplificar ese trabajo, comencemos con el código que aparece a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_Prefix "Demo" 005. //+------------------------------------------------------------------+ 006. #define macro_NameObject def_Prefix + (string)(ObjectsTotal(0) + 1) 007. //+------------------------------------------------------------------+ 008. #include <Tutorial\File 01.mqh> 009. //+------------------------------------------------------------------+ 010. st_Cross gl_Cross; 011. //+------------------------------------------------------------------+ 012. int OnInit() 013. { 014. gl_Cross.Init(); 015. 016. IndicatorSetString(INDICATOR_SHORTNAME, def_Prefix); 017. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 018. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, false); 019. 020. return INIT_SUCCEEDED; 021. }; 022. //+------------------------------------------------------------------+ 023. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 024. { 025. return rates_total; 026. }; 027. //+------------------------------------------------------------------+ 028. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 029. { 030. st_TimePrice tp; 031. static string isPaint = ""; 032. 033. switch (id) 034. { 035. case CHARTEVENT_KEYDOWN: 036. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) gl_Cross.Hide(); 037. break; 038. case CHARTEVENT_MOUSE_MOVE: 039. if (((uchar)sparam & MOUSE_MIDDLE) != 0) 040. { 041. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 042. if (isPaint == "") 043. { 044. ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, tp.Time, tp.Price); 045. Modifier_OBJ_FIBO(isPaint); 046. } 047. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 048. gl_Cross.Show(); 049. }else 050. { 051. isPaint = ""; 052. gl_Cross.Hide(); 053. } 054. break; 055. case CHARTEVENT_MOUSE_WHEEL: 056. break; 057. } 058. ChartRedraw(); 059. }; 060. //+------------------------------------------------------------------+ 061. void OnDeinit(const int reason) 062. { 063. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false); 064. ChartSetInteger(0, CHART_CROSSHAIR_TOOL, true); 065. 066. gl_Cross.Hide(); 067. 068. if (reason == REASON_REMOVE) 069. ObjectsDeleteAll(0, def_Prefix); 070. }; 071. //+------------------------------------------------------------------+ 072. void Modifier_OBJ_FIBO(const string szNameObj) 073. { 074. const double nLevels[] = 075. { 076. 0, 077. 1, 078. 2.5 079. }; 080. const string sLevels[] = 081. { 082. "Stop", 083. "Enter", 084. "Take" 085. }; 086. const color cLevels[] = 087. { 088. clrRed, 089. clrBlue, 090. clrGreen 091. }; 092. 093. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 094. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 095. ObjectSetInteger(0, szNameObj, OBJPROP_WIDTH, 3); 096. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, true); 097. 098. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, nLevels.Size()); 099. for (uint c = 0; c < nLevels.Size(); c++) 100. { 101. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, c, nLevels[c]); 102. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, c, cLevels[c]); 103. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, c, 2); 104. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, c, sLevels[c]); 105. } 106. } 107. //+------------------------------------------------------------------+
Código 02
El código 02 es muy divertido, sobre todo porque casi contiene el objeto que quiero mostrar implementado. ¿Cómo es posible? Querido lector, si te fijas, verás que en la línea 45 hacemos una llamada a un procedimiento que aparece en la línea 72. Este procedimiento está algo feo, por así decirlo, porque está bastante más desordenado de lo que me habría gustado implementar en un principio. Aun así, creo que será más sencillo entender cómo funciona todo.
Antes de explicar el funcionamiento de ese procedimiento, ¿qué tal si vemos el resultado? Se muestra en la animación a continuación.

Animación 02
Como puedes ver, el objetivo que queremos implementar es bastante simple. ¿Cuál sería la utilidad de esta herramienta? Muchas personas emplean ciertos tipos de estrategias operativas en el mercado, y muchas de esas estrategias tienen un objetivo y un stop muy bien definidos. Es importante comprender esto para entender qué se está implementando y, sobre todo, cómo modificar esa misma implementación para objetivos particulares.
Este OBJ_FIBO modificado nos permite precisamente verificar este tipo de condición: antes de entrar en una operación podemos ver dónde estaría la orden de entrada, dónde debería ir el stop y dónde quedaría el objetivo. Con ello evaluamos si una operación es plausible o no; un stop mal colocado generará sin duda una barrida de stops, así como un objetivo mal dimensionado hará que posiblemente no se alcance.
Como varios operadores profesionales del mercado realizan un análisis varios minutos antes de entrar realmente en una operación, esta herramienta resulta muy útil e imprescindible, pues nos ofrece la dimensión adecuada de lo que cabe esperar durante la operación.
Bien, ¿pero cómo fue posible crear esta herramienta? Y, además, ¿cómo podemos mejorarla o adaptarla a nuestro estilo de trabajo? Para entenderlo, centrémonos ahora en el procedimiento Modifier_OBJ_FIBO, que comienza en la línea 72 del código 02.
Allí podemos ver que se crean tres arrays. Cada uno tiene un propósito y están correlacionados entre sí. Antes de examinar qué hace cada array, repasemos otros aspectos del mismo procedimiento. En la línea 93 cambiamos la propiedad del objeto OBJ_FIBO para que el usuario no pueda seleccionarlo al hacer clic. Esto es importante porque no queremos que el objeto se mueva después de haberlo colocado.
Esto, sin embargo, plantea otro problema: no podremos eliminar el objeto haciendo clic sobre él y pulsando la tecla DELETE a continuación. Aun así, puedes borrarlo desde la ventana que muestra la lista de objetos del gráfico o retirando el indicador del gráfico.
La línea 94 evita que la línea discontinua se vea en el gráfico. Esa línea aparece en la animación 01 en magenta, pero al asignarle el color clrNONE deja de ser visible, aunque sigue creándose. Las dos líneas siguientes, 95 y 96, solo ajustan propiedades del OBJ_FIBO, sin nada más que destacar. Ahora llegamos a la parte que realmente nos interesa, que comienza en la línea 98.
Un objeto OBJ_FIBO se compone de niveles. No importa la variante del objeto Fibonacci que utilices: en todos los casos se crea a partir de ellos. Como MetaTrader 5 no sabe cuántos niveles deben crearse, empleamos la línea 98 para indicárselo. En ese momento la plataforma sabrá cuántos niveles deben existir. Puedes especificar más o menos niveles; sin embargo, como aquí buscamos la modificación mostrada en la animación 02, usaremos pocos niveles, concretamente tres.
Ahora presta atención, querido lector. Cada nivel posee su propia propiedad. Como esos niveles pueden representarse mediante líneas de tendencia o incluso curvas con la misma finalidad, podemos indicar cómo debe trazarse cada una de esas líneas en el gráfico y, de este modo, construir el patrón deseado.
¿Y cómo se construye este patrón? Aquí es donde el objeto que estamos modificando se vuelve divertido e interesante. Observa que, dentro del bucle, recorreremos los arrays definidos hace un momento. Cada uno de esos arrays asignará un valor a una de las propiedades de la línea que se va a crear, algo bastante sencillo de entender. Sin embargo, hay un detalle que quizá resulte un poco más complicado: el array nLevels, definido en la línea 74.
La parte complicada de este array son precisamente los valores. ¿Por qué usar estos valores? ¿No podríamos emplear otros? Para entenderlo, primero tienes que conocer otro aspecto del objeto Fibonacci. En un objeto Fibonacci trabajamos con valores que van de cero a uno; todo lo que quede dentro de ese intervalo se traza dentro del Fibonacci que todos conocen. Sin embargo, podemos asignar valores menores que cero y mayores que uno y, al hacerlo, generamos una proyección extendida del propio objeto. Observa lo siguiente: cuando empiezas a dibujar un objeto OBJ_FIBO en el gráfico, el punto donde inicia el trazo corresponde al valor uno y, a medida que arrastras el objeto para crearlo, obtienes la posición del valor cero.
Puede parecer extraño, pero así es como funciona. De este modo, cuando añadimos un valor de 2.5, como se muestra en la línea 78, no estamos indicando que se cree una proyección dos veces y media mayor que la distancia entre los puntos uno y cero, sino una vez y media mayor.
Así calculamos la proyección donde quedaría el objetivo de una posible operación. Si quisieras emplear un objetivo 1:1, deberías colocar en la línea 78 el valor dos; de ese modo la misma distancia que existe entre el punto de entrada y el stop se proyectará como objetivo de la operación.
Lector, quizá pienses que esto es muy loco, y en verdad lo es, porque puedes incluir de manera muy sencilla niveles parciales, por ejemplo. Solo necesitas añadir los valores correspondientes en este array declarado en la línea 74. Recuerda que, al hacerlo, también tendrás que ajustar los otros dos arrays para evitar que la representación en el gráfico difiera de lo previsto cuando uses tu indicador personalizado en MetaTrader 5.
¿Ves que no hace falta demasiado? Todo lo que realmente necesitamos es creatividad y la necesidad de crear algo. Luego, aplicando conocimientos básicos explicados en los demás artículos de esta serie, desarrollamos e implementamos nuestras ideas. Así de simple.
Lo que estamos haciendo aquí es tan interesante y divertido que no nos limitamos a lo visto arriba: podemos ir mucho más allá. Para demostrarlo, hagamos lo siguiente: originalmente, el código mostrado como código 02 genera algo similar a lo que viste en la animación 02; pero, si cambiamos únicamente el contenido del procedimiento Modifier_OBJ_FIBO por otra cosa, como en el fragmento de código que aparece a continuación, ¿qué ocurrirá?
. . . 71. //+------------------------------------------------------------------+ 72. void Modifier_OBJ_FIBO(const string szNameObj) 73. { 74. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) { \ 75. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos); \ 76. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor); \ 77. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width); \ 78. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style); \ 79. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt); \ 80. levels++; \ 81. } 82. 83. int levels = 0; 84. 85. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 86. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 87. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, true); 88. 89. macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID); 90. macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH); 91. macro_Mod_OBJ_FIBO("Partial", 1.5, clrYellowGreen, 1, STYLE_DASHDOTDOT); 92. macro_Mod_OBJ_FIBO("Take", 2, clrGreen, 2, STYLE_SOLID); 93. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels); 94. 95. #undef macro_Mod_OBJ_FIBO 96. } 97. //+------------------------------------------------------------------+
Código 03
Como el resto del código quedará intacto, solo muestro la parte que realmente nos interesa en este fragmento de código 03. Sin embargo, cuando ejecutes el programa con la modificación introducida en el código 03, el resultado será muy diferente, como puedes ver en la siguiente imagen.

Imagen 01
Observa, lector, que lo que estamos haciendo, visible en la imagen 01, es imposible de lograr manipulando un objeto Fibonacci directamente en el gráfico. Por más que lo intentes, no podrás crear algo como lo que se ve en la imagen 01; solo es viable mediante código. En este caso la relación entre el punto de stop y el objetivo es 1:1, con una salida parcial ejecutada al 50 % del recorrido.
Lo complicado es precisamente el patrón de colores y líneas. Si no lo crees, intenta reproducirlo con un objeto Fibonacci insertado desde el menú de MetaTrader 5 y comprobarás que resulta imposible.
En este escenario, mostrado en el fragmento del código 03, el procedimiento Modifier_OBJ_FIBO, a mi entender, resulta mucho más fácil de modificar y ajustar porque empleamos una macro que permite crear los niveles de forma muy sencilla. Cualquier nivel nuevo puede añadirse colocándolo antes de la línea 93 para que MetaTrader 5 sepa cuántos y cuáles niveles debe trazar en el gráfico.
Resulta bastante interesante lo que vienes mostrando, pero me surge una duda. Sé que el objetivo de estos artículos no es enseñar a crear una aplicación completa, sin embargo, si quisiéramos que el usuario interactuara con este último indicador, ¿cómo podríamos permitirlo? Lo pregunto porque, una vez compilado, los valores fijados ya no se pueden modificar. ¿Cómo podríamos, al menos, ajustar la relación entre stop y objetivo? No hay problema en explicarlo; disponemos de tiempo para ver cómo podría hacerse. Para separar los temas, tratemos esto a continuación.
Ajustando la relación stop-objetivo
Existen varias formas de permitir que el usuario indique la relación entre stop y objetivo. Una manera directa es usar un valor de tipo double, tal como se muestra en el código siguiente.
. . . 09. //+------------------------------------------------------------------+ 10. input double user01 = 1.5; //Stop-Target Relationship 11. //+------------------------------------------------------------------+ . . . 73. //+------------------------------------------------------------------+ 74. void Modifier_OBJ_FIBO(const string szNameObj) 75. { 76. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) { \ 77. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos); \ 78. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor); \ 79. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width); \ 80. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style); \ 81. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt); \ 82. levels++; \ 83. } 84. 85. int levels = 0; 86. 87. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 88. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 89. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, true); 90. 91. macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID); 92. macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH); 93. macro_Mod_OBJ_FIBO("Partial", 1 + (user01 / 2), clrYellowGreen, 1, STYLE_DASHDOTDOT); 94. macro_Mod_OBJ_FIBO("Take", 1 + user01, clrGreen, 2, STYLE_SOLID); 95. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels); 96. 97. #undef macro_Mod_OBJ_FIBO 98. } 99. //+------------------------------------------------------------------+
Código 04
En este fragmento del código 04 se presenta el modo más sencillo de establecer una relación directa entre un stop y su objetivo. Solo hubo que añadir la línea 10 al fragmento y ajustar las líneas 93 y 94 para que la relación se generara. Al ejecutar el código 04 obtenemos lo que se muestra en la imagen siguiente.

Imagen 02
Una vez ajustada la relación que se aprecia en la imagen 02, podemos utilizarla directamente en el gráfico, y el resultado es similar al que se ve en la imagen inferior.

Imagen 03
Algo realmente notable, dada la simplicidad adoptada aquí. Quizá te preguntes qué ocurre si el usuario introduce un valor inferior a uno en la relación; es decir, si quiere operar en un contexto donde la relación entre stop y objetivo sea desfavorable. ¿Qué sucederá y qué valor hay que indicar para que el indicador funcione?? No busco fomentar esta situación, pero, para obtener una relación en la que el stop sea mayor que el objetivo final, podemos utilizar algo similar a lo mostrado en la imagen inferior.

Imagen 04
Como el valor es menor que uno, la relación resulta desfavorable. Al aplicar el indicador en el gráfico observarás un resultado parecido al de la imagen correspondiente.

Imagen 05
En este ejemplo utilicé los mismos datos como punto de entrada; sin embargo, la salida es muy distinta, como se aprecia al comparar las imágenes. Disponemos así de una herramienta práctica y funcional: puedes colocar la aplicación en el gráfico, realizar un estudio, modificar el valor (como en la imagen 04) y repetir el análisis, lo que permite mantener varios estudios simultáneamente.
El único inconveniente podría ser que las líneas se crucen, pero se soluciona fácilmente ofreciendo al usuario la opción de trazar o no una línea extendida a la derecha. Para ello basta con modificar el código como se muestra a continuación.
. . . 09. //+------------------------------------------------------------------+ 10. input double user01 = 1.5; //Stop-Target Relationship 11. input bool user02 = true; //Extend lines to the right 12. //+------------------------------------------------------------------+ . . . 73. //+------------------------------------------------------------------+ 74. void Modifier_OBJ_FIBO(const string szNameObj) 75. { 76. #define macro_Mod_OBJ_FIBO(txt, pos, cor, width, style) { \ 77. ObjectSetDouble(0, szNameObj, OBJPROP_LEVELVALUE, levels, pos); \ 78. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELCOLOR, levels, cor); \ 79. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELWIDTH, levels, width); \ 80. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELSTYLE, levels, style); \ 81. ObjectSetString(0, szNameObj, OBJPROP_LEVELTEXT, levels, txt); \ 82. levels++; \ 83. } 84. 85. int levels = 0; 86. 87. ObjectSetInteger(0, szNameObj, OBJPROP_SELECTABLE, false); 88. ObjectSetInteger(0, szNameObj, OBJPROP_COLOR, clrNONE); 89. ObjectSetInteger(0, szNameObj, OBJPROP_RAY_RIGHT, user02); 90. 91. macro_Mod_OBJ_FIBO("Stop", 0, clrRed, 2, STYLE_SOLID); 92. macro_Mod_OBJ_FIBO("Enter", 1, clrBlue, 2, STYLE_DASH); 93. macro_Mod_OBJ_FIBO("Partial", 1 + (user01 / 2), clrYellowGreen, 1, STYLE_DASHDOTDOT); 94. macro_Mod_OBJ_FIBO("Take", 1 + user01, clrGreen, 2, STYLE_SOLID); 95. ObjectSetInteger(0, szNameObj, OBJPROP_LEVELS, levels); 96. 97. #undef macro_Mod_OBJ_FIBO 98. } 99. //+------------------------------------------------------------------+
Código 05
De nuevo, se trata de un cambio sencillo en el fragmento del código 05; en este caso añadimos la línea 11. Su valor se usa en la línea 89 para controlar si la línea se prolongará hacia el extremo derecho. En la imagen siguiente se muestra una configuración en la que la línea no se extiende.

Imagen 06
Como resultado, podemos obtener en el gráfico algo parecido a lo que se ve más abajo.

Imagen 07
Observa lo siguiente: estamos añadiendo un nuevo estudio en la imagen 05 y, aunque cambiamos la configuración del indicador (imagen 06) sin retirarlo del gráfico, ello no elimina ni afecta lo que ya existía. Con muy poco esfuerzo hemos creado algo realmente interesante.
Mientras muchos afirman que ciertas cosas no son posibles en MetaTrader 5, acabas de comprobar que se puede hacer prácticamente de todo con conocimientos mínimos y básicos, sin recurrir a trucos ni métodos complejos.
De acuerdo, todo esto ha sido muy entretenido, pero aún queda una cuestión por resolver, que veremos en el tema siguiente.
Usando el botón izquierdo para hacer el dibujo
Quizá te resulte poco natural usar el botón central del ratón para dibujar en el gráfico. La mayoría de los usuarios está acostumbrada a interactuar con el botón izquierdo, por lo que el botón central se siente extraño. Aunque funciona perfectamente con los códigos que has probado hasta ahora, no es lo habitual, y como programadores debemos corregirlo. Existe, sin embargo, un pequeño matiz.
Al hacer clic con el botón central, todo evento del ratón se encamina a la línea 38, como se ve en el código 02. Mientras el botón permanezca pulsado, la cruz de análisis sigue visible, lo que permite efectuar el dibujo. Cuando se suelta el botón central, la línea 52 del código 02 retira la cruz, indicando que ya no podemos interactuar con el sistema de dibujo.
Si comprendes esto, resulta claro qué debemos hacer: al pulsar el botón central se genera un evento que debe esperar un segundo evento, ya sea la tecla ESCAPE o el clic del botón izquierdo para crear el dibujo y posicionar el objeto como hasta ahora, pero usando el botón izquierdo en lugar del central.
Ese es el objetivo. Hay muchos caminos posibles, algunos más laboriosos que otros; como aquí prima la didáctica, seguiré el camino más simple, aun sabiendo que no es el ideal para todos los casos. Lo verás en el fragmento de código 06.
. . . 029. //+------------------------------------------------------------------+ 030. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 031. { 032. #define macro_CLEAN_EVENT { \ 033. isPaint = ""; \ 034. gl_Cross.Hide(); \ 035. bMouseL = false; \ 036. } 037. 038. st_TimePrice tp; 039. static string isPaint = ""; 040. static bool bMouseL = false; 041. 042. switch (id) 043. { 044. case CHARTEVENT_KEYDOWN: 045. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) macro_CLEAN_EVENT; 046. break; 047. case CHARTEVENT_MOUSE_MOVE: 048. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 049. if (((uchar)sparam & MOUSE_LEFT) != 0) 050. { 051. if (isPaint != "") 052. { 053. bMouseL = true; 054. if (!ObjectGetInteger(0, isPaint, OBJPROP_TIME)) ObjectMove(0, isPaint, 0, tp.Time, tp.Price); 055. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 056. } 057. }else if (bMouseL) macro_CLEAN_EVENT; 058. if ((((uchar)sparam & MOUSE_MIDDLE) != 0) && (isPaint == "")) 059. { 060. ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, 0, 0); 061. Modifier_OBJ_FIBO(isPaint); 062. gl_Cross.Show(); 063. } 064. break; 065. case CHARTEVENT_MOUSE_WHEEL: 066. break; 067. } 068. ChartRedraw(); 069. 070. #undef macro_CLEAN_EVENT 071. }; 072. //+------------------------------------------------------------------+ . . .
Código 06
En él activamos el dibujo con el botón central, pero lo trazamos solo mientras el botón izquierdo esté pulsado. No todo es perfecto: la idea funciona, pero presenta un problema que se aprecia en la animación 03.

Animación 03
En dicha animación se nota la dificultad para dibujar porque el gráfico se desplaza. Ese movimiento a veces es deseable, pero aquí entorpece más de lo que ayuda. Antes de mostrar cómo resolverlo, repasemos los cambios necesarios para usar el botón izquierdo, tal como aparece en la animación 03.
En primer lugar añadí una macro en la línea 32, que permite finalizar el evento de creación del dibujo en el estado en que se encuentre. Luego, en el manejo del evento del ratón, cambié el orden de ejecución para que los eventos se procesen correctamente: primero se pulsa el botón derecho; en ese momento la línea 60 crea el objeto OBJ_FIBO, lo configura según lo requerido y muestra la cruz.
En este momento, si pulsamos el botón izquierdo del ratón, la verificación de la línea 51 se cumple y, en la línea 53, se marca un nuevo punto; simultáneamente, las líneas 54 y 55 colocan el objeto OBJ_FIBO en el gráfico. Esa marca creada en la línea 53 permite, en la línea 57, que la cruz se elimine del gráfico tan pronto como el objeto OBJ_FIBO quede dibujado, es decir, cuando soltemos el botón izquierdo.
Para corregir lo visto en la animación 03, debemos modificar ese fragmento de código 06 añadiendo dos líneas nuevas. Los cambios aparecen en el fragmento de código que sigue.
. . . 029. //+------------------------------------------------------------------+ 030. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 031. { 032. #define macro_CLEAN_EVENT { \ 033. isPaint = ""; \ 034. gl_Cross.Hide(); \ 035. bMouseL = false; \ 036. ChartSetInteger(0, CHART_MOUSE_SCROLL, true); \ 037. } 038. 039. st_TimePrice tp; 040. static string isPaint = ""; 041. static bool bMouseL = false; 042. 043. switch (id) 044. { 045. case CHARTEVENT_KEYDOWN: 046. if (TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) macro_CLEAN_EVENT; 047. break; 048. case CHARTEVENT_MOUSE_MOVE: 049. tp = gl_Cross.Move((ushort)lparam, (ushort)dparam); 050. if (((uchar)sparam & MOUSE_LEFT) != 0) 051. { 052. if (isPaint != "") 053. { 054. bMouseL = true; 055. if (!ObjectGetInteger(0, isPaint, OBJPROP_TIME)) ObjectMove(0, isPaint, 0, tp.Time, tp.Price); 056. ObjectMove(0, isPaint, 1, tp.Time, tp.Price); 057. } 058. }else if (bMouseL) macro_CLEAN_EVENT; 059. if ((((uchar)sparam & MOUSE_MIDDLE) != 0) && (isPaint == "")) 060. { 061. ObjectCreate(0, isPaint = macro_NameObject, OBJ_FIBO, 0, 0, 0); 062. ChartSetInteger(0, CHART_MOUSE_SCROLL, false); 063. Modifier_OBJ_FIBO(isPaint); 064. gl_Cross.Show(); 065. } 066. break; 067. case CHARTEVENT_MOUSE_WHEEL: 068. break; 069. } 070. ChartRedraw(); 071. 072. #undef macro_CLEAN_EVENT 073. }; 074. //+------------------------------------------------------------------+ . . .
Código 07
Cuando utilices el código 07 obtendrás el resultado mostrado en la animación siguiente.

Animación 04
Puede parecer curioso que el simple hecho de emplear las líneas 36 y 62 en el fragmento de código 07 resuelva el problema de la animación 03. Si piensas eso es porque quizá no has probado lo expuesto en los artículos anteriores, donde ya mostré estas mismas funciones, entre otras. Sin embargo, en aquel momento aún no habíamos llegado a activar y desactivar la configuración por defecto de MetaTrader 5 para lograr una interacción como la actual.
Consideraciones finales
Este ha sido tal vez el artículo en el que más nos hemos divertido hasta ahora. Con muy poco de lo visto y explicado hemos conseguido implementar algo que no existe de forma nativa en MetaTrader 5, lo que nos permitió crear un indicador muy llamativo mediante la modificación de un objeto existente en la plataforma para otro fin.
Sé que muchos pensaban que habría que aprender mucho más antes de programar algo tan interesante y, al mismo tiempo, tan entretenido. Sin embargo, el trabajo no termina aquí: el indicador que presentamos todavía contiene un pequeño fallo. Lo he dejado adrede para que tú, lector y alumno, puedas practicar corrigiéndolo. Explicaré cuál es el problema, pero la solución dependerá de ti.
En el fragmento de código 07, la línea 61 crea un objeto OBJ_FIBO antes de que la cruz se genere en la línea 64. El inconveniente es que, si el usuario (o tú mismo) pulsa la tecla ESCAPE, se ejecuta la línea 46 y la cruz se elimina del gráfico, pero el OBJ_FIBO creado en la línea 61 permanece en la lista de objetos aunque no sea visible porque no se ha posicionado.
Tu tarea consiste en resolverlo, de modo que el OBJ_FIBO solo se añada a la lista de objetos si, y solo si, el botón izquierdo se pulsa después de haber pulsado el botón derecho. Recuerda el problema que surgía cuando se creaba un objeto después de haber generado la cruz y la forma en que mostré solucionarlo.
La solución a este problema pasa por algo muy parecido: basta con cambiar el orden en que se ejecutan ciertas operaciones del fragmento de código 07; no necesitas crear ninguna rutina ni añadir funciones o procedimientos nuevos. Si reordenas las operaciones de manera adecuada, podrás eliminar la cruz y evitar que el objeto OBJ_FIBO se cree innecesariamente. Así, el usuario podrá pulsar ESCAPE sin que quede un objeto listado que no aparece en el gráfico.
En el anexo encontrarás los códigos utilizados en este artículo. Practica resolviendo esta cuestión y nos veremos en el próximo artículo.
| Archivo MQ5 | Descripción |
|---|---|
| Code 01 | Demostración de objeto |
| Code 02 | Demostración de objeto |
| Code 03 | Demostración de objeto |
| Code 04 | Demostración de objeto |
| Code 05 | Demostración de objeto |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16026
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.
Simulación de mercado: Iniciando SQL en MQL5 (IV)
Determinación de los tipos de cambio justos en PPA usando los datos del FMI
Del básico al intermedio: Eventos en objetos (I)
Características del Wizard MQL5 que debe conocer (Parte 64): Uso de los patrones de DeMarker y los canales de envolvente con el núcleo de ruido blanco
- 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