Simulación de mercado: Position View (XI)
Introducción
Hola a todos, bienvenidos a otro artículo de la serie sobre cómo construir un sistema de repetición/simulación.
En el artículo anterior, Simulación de mercado: Position View (X), expliqué cómo podríamos seleccionar los objetos que estamos creando con la ayuda de MetaTrader 5. Hasta ese momento, la principal preocupación era encontrar una forma satisfactoria de seleccionar los objetos en el gráfico. Sin la ayuda de MetaTrader 5 y recurriendo a la propiedad ZOrder, esta tarea sería bastante complicada y difícil de resolver. Pero, como quedó demostrado en el artículo anterior, encontramos una forma bastante práctica y eficaz de usar MetaTrader 5 a nuestro favor.
Del mismo modo que conseguimos obtener ese resultado, también quedó claro que tenemos otro problema que resolver. Ese problema es, precisamente, la selección de las líneas de precio. Aunque sean relativamente más gruesas, sigue sin ser una tarea sencilla. Sobre todo, cuando necesitamos hacerlo con bastante rapidez y con las líneas muy cerca unas de otras.
Seleccionar correctamente la línea deseada y hacerlo lo más rápido posible es algo extremadamente necesario, pero también debemos pensarlo con cuidado. No solo vamos a seleccionar la línea, sino que también queremos permitir la creación de la línea de precio, ya sea de take profit o stop loss, directamente en el gráfico. Esta interacción deberá ser lo más simple posible para el operador. No desde el punto de vista del código, sino desde el punto de vista del operador.
Hace bastante tiempo, presenté en estos artículos un modelo que, en mi opinión, es bastante adecuado para nuestras necesidades. Sin embargo, aquel modelo estaba completamente vinculado al Asesor Experto, por lo que resultaba complicado extraerlo de su estructura interna. Pero, como es interesante para lo que necesitamos, explicaré cómo implementarlo. No obstante, ahora lo haremos en un indicador. Entonces, comencemos.
Creación de un botón para mover los precios
La idea de aquel modelo bastante antiguo era usar un botón específico para seleccionar la línea deseada. Además de permitirnos seleccionar la línea, ese botón también nos permitía crearla si no existía en el gráfico, entre otras pequeñas funciones que también desarrollaremos en este artículo. Así, lo primero que hay que hacer se muestra en el siguiente código.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 007. //+------------------------------------------------------------------+ 008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 009. //+------------------------------------------------------------------+ 010. #define def_PathBtns "Images\\Market Replay\\Orders\\" 011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 012. #resource "\\" + def_Btn_Close; 013. //+------------------------------------------------------------------+ 014. #include "..\Auxiliar\C_Mouse.mqh" 015. //+------------------------------------------------------------------+ 016. class C_ElementsTrade : private C_Mouse 017. { 018. private : 019. //+------------------------------------------------------------------+ 020. struct st00 021. { 022. ulong ticket; 023. string szPrefixName; 024. EnumEvents ev; 025. double price; 026. bool bClick; 027. char weight; 028. }m_Info; 029. //+------------------------------------------------------------------+ 030. void UpdateViewPort(void) 031. { 032. int x, y; 033. 034. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 035. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 036. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 037. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 038. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 039. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 040. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 041. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 042. } 043. //+------------------------------------------------------------------+ 044. inline void CreateLinePrice(const color _color, const string szDescr) 045. { 046. string szObj; 047. 048. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault)); 049. macro_LineInFocus(false); 050. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _color); 051. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 052. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 053. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 054. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 055. } 056. //+------------------------------------------------------------------+ 057. inline void CreateBoxMove(const color _color) 058. { 059. string szObj; 060. 061. CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, _color, (EnumPriority)(ePriorityDefault)); 062. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 063. ObjectSetString(0, szObj, OBJPROP_TEXT, "u"); 064. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17); 065. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 066. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 067. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 068. } 069. //+------------------------------------------------------------------+ 070. inline void CreateButtonClose(void) 071. { 072. string szObj; 073. 074. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 075. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 076. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 077. } 078. //+------------------------------------------------------------------+ 079. public : 080. //+------------------------------------------------------------------+ 081. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 082. :C_Mouse(0, "") 083. { 084. ZeroMemory(m_Info); 085. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 086. CreateLinePrice(_color, szDescr); 087. if (ev != evMsgClosePositionEA) CreateBoxMove(_color); 088. CreateButtonClose(); 089. } 090. //+------------------------------------------------------------------+ 091. ~C_ElementsTrade() 092. { 093. ObjectsDeleteAll(0, m_Info.szPrefixName); 094. } 095. //+------------------------------------------------------------------+ 096. inline void UpdatePrice(const double price) 097. { 098. m_Info.price = price; 099. UpdateViewPort(); 100. } 101. //+------------------------------------------------------------------+ 102. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 103. { 104. string sz0; 105. 106. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 107. switch (id) 108. { 109. case CHARTEVENT_CUSTOM + evMsgSetFocus: 110. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 111. m_Info.bClick = false; 112. case CHARTEVENT_CHART_CHANGE: 113. UpdateViewPort(); 114. break; 115. case CHARTEVENT_OBJECT_CLICK: 116. sz0 = GetPositionsMouse().szObjNameClick; 117. if (m_Info.bClick) switch (m_Info.ev) 118. { 119. case evMsgClosePositionEA: 120. if (sz0 == def_NameBtnClose) 121. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 122. break; 123. case evMsgCloseTakeProfit: 124. if (sz0 == def_NameBtnClose) 125. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 126. else if (sz0 == def_NameBtnMove) 127. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 128. break; 129. case evMsgCloseStopLoss: 130. if (sz0 == def_NameBtnClose) 131. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 132. else if (sz0 == def_NameBtnMove) 133. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 134. break; 135. } 136. m_Info.bClick = false; 137. break; 138. case CHARTEVENT_MOUSE_MOVE: 139. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 140. if (m_Info.bClick && (m_Info.weight > 1)) EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 141. break; 142. } 143. } 144. //+------------------------------------------------------------------+ 145. }; 146. //+------------------------------------------------------------------+ 147. #undef macro_LineInFocus 148. //+------------------------------------------------------------------+ 149. #undef def_Btn_Close 150. #undef def_PathBtns 151. //+------------------------------------------------------------------+ 152. #undef def_NameBtnMove 153. #undef def_NameBtnClose 154. #undef def_NameHLine 155. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Aquí empezamos a mejorar aún más la gestión de los objetos. Observa que en la línea 6 creamos una nueva definición. Esta definición le dará nombre al objeto que representará nuestro icono de desplazamiento. Mientras no esté presente, no podremos mover la línea. Cuando exista, la línea podrá moverse. Sin embargo, en este momento no nos preocuparemos por el desplazamiento. Primero veremos cómo gestionar la selección.
Observa que el código ha cambiado muy poco. Esto te permitirá entender realmente el proceso paso a paso, querido lector. Así, si lo deseas, podrás modificar fácilmente lo que te muestro.
En la línea 40, indicamos la ubicación de nuestro nuevo objeto en el eje X. Fíjate en que he dejado una pequeña separación entre este objeto y el botón de cierre. Ahora, presta atención. En la línea 35, modifiqué el desplazamiento de los objetos para que no se superpongan. De este modo, queda un pequeño espacio entre ellos, de modo que no tengamos problemas cuando este indicador esté en el gráfico.
Para facilitar aún más los cambios futuros, saqué del constructor los procedimientos que crean los objetos. Así se crearon los procedimientos CreateLinePrice y CreateButtonClose. El contenido de estos procedimientos estaba antes en el constructor. Por tanto, no entraré en más detalles sobre ellos. Sin embargo, el procedimiento CreateBoxMove es nuevo, por lo que merece una explicación básica. En este procedimiento, solo creamos un objeto del tipo OBJ_LABEL y le asignamos un texto. En realidad, un solo carácter. Este será el elemento que indicará si podemos seleccionar y, en consecuencia, mover la línea de precio. Como el código es muy simple, no creo que sea necesario explicarlo detalladamente.
Ahora, observa el siguiente detalle en el constructor. Para ser más exactos, mira la línea 87. En esa línea, comprobamos si el control creado es una línea de cierre de posición. Si no lo es, podemos crear el objeto de desplazamiento de la línea de precio. Muy bien, ahora podemos pasar al procedimiento DispatchMessage. En este procedimiento se produjeron cambios algo más importantes. Sin embargo, estos cambios buscan evitar la duplicación de código, ya que algunas cosas pueden manejarse en cascada.
Puedes verlo al observar el evento de la línea 109: en este evento, en la línea 111, asignaremos el valor false al estado del clic del mouse. Pero este evento de la línea 109 no finalizará donde muchos esperarían. En realidad, pasará al siguiente, CHARTEVENT_CHART_CHANGE, que aparece en la línea 112. Pero ¿por qué hacer o permitir esto? El motivo es sencillo. Cuando el grosor de la línea cambie, necesitamos repintarla en el gráfico. Si no dejáramos que el evento personalizado continuara hacia el evento de redibujado, tendríamos que añadir una llamada específica para redibujar la línea en el evento personalizado, algo que podemos descartar, ya que simplemente estamos encadenando el tratamiento de eventos en cascada.
Ahora, observa que en el evento CHARTEVENT_OBJECT_CLICK no comprobamos si se hizo clic en la línea de precio. Ahora verificamos si se hizo en el objeto de desplazamiento. Así que no pierdas el tiempo intentando seleccionar la línea de precio. Haz clic directamente en el pequeño diamante que aparece en ella. Así, la línea se seleccionará junto con el diamante.
Otra novedad se encuentra precisamente en el evento CHARTEVENT_MOUSE_MOVE. Observa que ahora, en la línea 140, se lleva a cabo una nueva comprobación. Esta comprobación sirve para verificar si hay alguna otra línea seleccionada en el gráfico. Si esto se cumple, se disparará un evento para quitar la selección. Este tipo de implementación es bastante curioso, ya que si hay alguna línea seleccionada y haces clic en cualquier parte del gráfico, la línea seleccionada se desmarcará. Y no tendremos que preocuparnos por cuál es la línea seleccionada. Todas las líneas se desmarcarán.
Muy bien, pero aunque lo que acabo de mostrar sirva para seleccionar o marcar una línea de precio, no permite hacer nada más útil. Ahora pensemos un poco. Te garantizo que será algo bastante interesante.
Al observar el código anterior y leer la explicación de su funcionamiento, ¿se te ocurre, querido lector, una manera simple, rápida y directa de mover estas líneas de precio? No importa si el servidor de trading lo acepta o no. Solo quiero que me digas si es posible mover las líneas con lo que acabamos de implementar. Recuerda que necesitamos añadir código al sistema para que esto sea realmente posible. La cuestión es si podemos hacerlo con la mínima cantidad de código posible. No quiero tener que escribir mucho código. Entonces, ¿es posible hacerlo o no?
Si respondiste que NO, es posible que no estés viendo realmente cómo funciona todo. Seguramente pensarás que no es posible hacerlo sin pensarlo con calma. Pero podemos hacerlo. Siempre que añadamos, claro está, un poco más de código a nuestro sistema principal. Recuerda que en los artículos anteriores ya añadimos dos mensajes nuevos al sistema. Sin embargo, esos mensajes no se implementaron porque no era prudente hacerlo en aquel momento. Ahora, sin embargo, tenemos una forma sencilla y rápida de seleccionar las líneas de precio. Así que podemos usar ese sistema de mensajes e implementarlo. Antes de hacerlo, quiero explicar qué vamos a hacer. Así podrás entender o intentar imaginar, antes de ver la solución, cómo vamos a implementarla.
Si sustituyes el archivo de cabecera C_ElementsTrade.mqh por el código que vimos al principio de este tema, verás que, al seleccionar una línea de precio y hacer clic en algún punto, la línea se desmarca. Esa es precisamente la idea. Cuando pidamos al indicador de posición que desmarque una línea de precio, este deberá enviar un mensaje al Asesor Experto para indicar que la línea marcada debe desplazarse a una nueva posición. Esta nueva posición será exactamente el punto donde se haga clic con el mouse. ¿Lo has entendido ahora?
Aun así, la idea no está del todo pulida. Pero no nos preocuparemos por eso ahora. Solo haremos lo que acabo de decir: cuando una línea de precio esté seleccionada y hagas clic en otro punto, esa línea deberá desplazarse hasta él. Esto se puede conseguir modificando el código del procedimiento DispatchMessage para que quede como se muestra a continuación.
138. case CHARTEVENT_MOUSE_MOVE: 139. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 140. if (m_Info.bClick && (m_Info.weight > 1)) 141. { 142. switch (m_Info.ev) 143. { 144. case evMsgCloseTakeProfit: 145. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 146. break; 147. case evMsgCloseStopLoss: 148. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 149. break; 150. } 151. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 152. } 153. break;
Fragmento de C_ElementsTrade.mqh
Observa lo que ocurrió aquí. Cuando añadas este fragmento al código de la clase C_ElementsTrade, podrás mover las líneas de take profit y stop loss con el indicador de mouse. Sin embargo, fíjate en el siguiente detalle. No estamos moviendo las líneas ni los objetos vinculados a ellas. Estamos haciendo exactamente lo que se había previsto. Es decir, marca una línea usando el objeto de desplazamiento. Lleva el indicador de mouse hasta una nueva posición y haz clic nuevamente. Cuando esto ocurra, la comprobación de la línea 140 deberá cumplirse en algún punto.
Cuando esto suceda, se realizará una selección en la línea 142 para disparar el evento más adecuado. En este caso, se podrá disparar el evento de la línea 145 si se había seleccionado la línea de take profit o el de la línea 148 si se había seleccionado la línea de stop loss. Observa que el mensaje que se enviará a MetaTrader 5 es prácticamente igual. Sin embargo, difiere en su segundo parámetro.
Este mensaje será reenviado por MetaTrader 5 al gráfico donde esté cargado el indicador y capturado por el Asesor Experto. Dentro del código de la clase C_Orders, incluida en el Asesor Experto, este mensaje se interpretará como una solicitud para cambiar la posición de la línea de precio, ya sea la de stop loss o la de take profit. En cuanto el servidor devuelva el resultado de nuestra solicitud, el Asesor Experto volverá a enviar un mensaje que será capturado por el indicador de posición, lo que forzará a este a mostrar el nuevo punto donde se encuentra la línea de precio. De esta manera, siempre que miremos el gráfico, sabremos exactamente dónde están las líneas en el servidor. Esto se debe a que siempre reflejamos exactamente lo que hay en el servidor. Así es como podemos mover fácilmente las líneas con la mínima cantidad de código posible.
Este sistema que acabamos de implementar funciona tan bien que incluso puedes usar el trailing stop, disponible en MetaTrader 5, para que la línea de stop loss se mueva y este indicador de posición la siga y se mantenga fiel a lo que exista en el servidor de trading.
Aunque este sistema funcione maravillosamente bien, todavía no hemos resuelto otro problema. El problema es el siguiente: ¿cómo podemos crear las líneas de take profit o stop loss si se eliminaron del servidor y, en consecuencia, del gráfico? Bien, esto parece mucho más complicado. Pero acompáñanos en la explicación y verás que es tan simple como lo que acabamos de hacer. Para separar los temas y evitar confusiones, pasaremos a un tema nuevo.
Sin líneas de stop loss y take profit. ¿Cómo volver a colocarlas?
Ahora tenemos un problema un poco diferente al del tema anterior. Este problema es el siguiente: Supongamos que eliminas los valores de stop loss y take profit del servidor. Pero, después de un tiempo, decides volver a colocarlos en su posición. Existen varias maneras de hacerlo. Sin embargo, aquí quiero mostrar cómo modificar el indicador de posición para volver a colocar el stop loss o el take profit directamente desde el gráfico.
Para ello, tendremos que modificar un poco más el indicador. Esto se debe a que, al eliminar el stop loss o el take profit de la posición, el indicador también elimina los elementos correspondientes a la línea de stop loss o de take profit. Ese es el comportamiento que tendremos que cambiar. Así podremos usar el mismo mecanismo que vimos en el tema anterior. Es decir, haz clic en uno de los puntos, ya sea stop loss o take profit. Lleva el indicador de mouse hasta el punto deseado y haz clic nuevamente. Así se cambia el valor límite para el cierre de la posición. Esta es la idea básica.
Pero, ¿cómo podemos hacerlo fácilmente? Quizá estés pensando en alguna solución bastante disparatada, mi querido lector. No sé si sería más sencilla o más complicada que la solución que se presentará. Pero, como en el tema anterior, la idea es modificar el código lo mínimo posible.
Muy bien, detengámonos nuevamente y pensemos un poco: ¿cuál sería la solución más simple a nuestro problema? Bien, considerando que queremos utilizar el mismo modelo que vimos en el tema anterior, la solución más sencilla sería mantener el objeto de desplazamiento de la línea de precio en el gráfico. Si has pensado eso, querido lector, has pensado lo mismo que yo. Pero, ¿dónde podemos colocar el objeto cuando no existe la línea de precio, ya sea la de take profit o la de stop loss? Bien, en este caso, pienso que la mejor solución sería colocar el objeto de desplazamiento justo en la línea donde se abrió la posición. No sé si piensas lo mismo, querido lector. Eres libre de pensar como quieras, siempre que, claro está, sepas lo que estás haciendo.
Entonces, hagámoslo de esta manera. Cuando se elimine la línea de stop loss o take profit de la posición, el indicador de desplazamiento se colocará junto a la línea de precio de apertura. Pero ahora tenemos un problema. Cuando se elimina esta misma línea, ya sea de stop loss o take profit, el indicador de posición también elimina los objetos asociados a ella. No queremos complicar aún más nuestro código de forma inútil, así que vamos a ver en qué parte del código el indicador elimina esos objetos. Esto se muestra en el siguiente fragmento.
45. //+------------------------------------------------------------------+ 46. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 47. { 48. double value; 49. 50. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 51. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 52. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 53. switch (id) 54. { 55. case CHARTEVENT_CUSTOM + evUpdate_Position: 56. if (lparam != m_Infos.ticket) return; 57. if (!PositionSelectByTicket(m_Infos.ticket)) 58. { 59. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 60. return; 61. }; 62. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket)); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket)); 65. (*Open).UpdatePrice(PositionGetDouble(POSITION_PRICE_OPEN)); 66. if ((value = PositionGetDouble(POSITION_TP)) > 0) (*Take).UpdatePrice(value); else 67. { 68. delete Take; 69. Take = NULL; 70. } 71. if ((value = PositionGetDouble(POSITION_SL)) > 0) (*Stop).UpdatePrice(value); else 72. { 73. delete Stop; 74. Stop = NULL; 75. } 76. break; 77. } 78. ChartRedraw(); 79. } 80. //+------------------------------------------------------------------+
Fragmento de C_IndicatorPosition.mqh
Este fragmento forma parte de la clase C_IndicatorPosition. Es el código original de dicha clase. Bien, ahora observa los siguientes puntos. En las líneas 66 y 71 tenemos dos comprobaciones: una para el take profit y otra para el stop loss. Ahora, presta atención para entender lo que tenemos que hacer. Estas comprobaciones le comunican al indicador de posición que debemos eliminar los punteros a la clase C_ElementsTrade. Esto ocurre cuando el valor de la posición es igual o menor que cero. Creo que puedes entenderlo claramente, querido lector y entusiasta.
Sin embargo, aquí hay un punto en el que tendremos que modificar el código. Piensa un poco. Si, en lugar de eliminar el puntero a la clase C_ElementsTrade, pasamos el valor de las líneas de take profit y de stop loss a dicha clase, podremos crear algún mecanismo que nos permita hacer lo que deseamos. Entonces, este mismo fragmento deberá modificarse para implementar dicho mecanismo. Esta modificación se muestra a continuación.
45. //+------------------------------------------------------------------+ 46. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 47. { 48. double value; 49. 50. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 51. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 52. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 53. switch (id) 54. { 55. case CHARTEVENT_CUSTOM + evUpdate_Position: 56. if (lparam != m_Infos.ticket) return; 57. if (!PositionSelectByTicket(m_Infos.ticket)) 58. { 59. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 60. return; 61. }; 62. if (Open == NULL) Open = new C_ElementsTrade(m_Infos.ticket, evMsgClosePositionEA, clrRoyalBlue, ePriorityNull, StringFormat("%I64u : Position opening price.", m_Infos.ticket)); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, ePriorityDefault, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, ePriorityDefault, StringFormat("%I64u : Stop Loss price.", m_Infos.ticket)); 65. (*Open).UpdatePrice(0, value = PositionGetDouble(POSITION_PRICE_OPEN)); 66. (*Take).UpdatePrice(value, PositionGetDouble(POSITION_TP)); 67. (*Stop).UpdatePrice(value, PositionGetDouble(POSITION_SL)); 68. break; 69. } 70. ChartRedraw(); 71. } 72. //+------------------------------------------------------------------+
Fragmento de C_IndicatorPosition.mqh
Observa que el código apenas ha cambiado. Sin embargo, lo que hicimos fue eliminar las comprobaciones que se estaban realizando. Ahora, independientemente del valor del stop loss o del take profit, este se pasará a la clase C_ElementsTrade. De este modo, podremos manejar adecuadamente los objetos de la línea de precio.
Perfecto, ahora veamos cómo está el código en la clase C_ElementsTrade. El código original se muestra en el siguiente fragmento.
095. //+------------------------------------------------------------------+ 096. inline void UpdatePrice(const double price) 097. { 098. m_Info.price = price; 099. UpdateViewPort(); 100. } 101. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade.mqh
Pues bien, ahora centrémonos en el fragmento anterior. Aquí es donde ocurrirá la magia. Sin embargo, observa que originalmente esperábamos recibir un solo parámetro. No obstante, en la modificación realizada en la clase C_IndicatorPosition, se pueden ver dos parámetros que se están pasando a este procedimiento. Esta será la primera modificación. Pero antes de ver cómo deberá quedar el código, reunamos algunos detalles más. Bien, el primer argumento recibido indicará dónde se encuentra la línea de precio de apertura de la posición. El segundo es el precio de la línea que se va a crear.
Ahora, piensa un poco. Solo necesitamos eliminar los objetos innecesarios, que en este momento son el objeto que representa la línea de precio y el botón de cierre, y mantener el botón de desplazamiento. Sin embargo, este objeto que representa el desplazamiento se reubicará en una nueva posición. Esa nueva posición es el precio de apertura de la posición. Así, la manera más simple de implementar este ajuste es modificar el fragmento anterior para que quede como se muestra a continuación.
095. //+------------------------------------------------------------------+ 096. inline void UpdatePrice(const double open, const double price) 097. { 098. m_Info.price = (price > 0 ? price : open); 099. if (price == 0) 100. { 101. ObjectDelete(0, def_NameBtnClose); 102. ObjectDelete(0, def_NameHLine); 103. } 104. UpdateViewPort(); 105. } 106. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade.mqh
Este fragmento permitiría que nuestro indicador de posición funcionara como se esperaba, pero hay un pequeño inconveniente. Observa que, cuando el operador elimina la línea de stop loss o take profit de la posición abierta, el indicador de posición también elimina la línea horizontal y el botón de cierre. Esto se debe a las líneas 101 y 102 del fragmento anterior. No obstante, cuando el operador solicite volver a colocar la línea de stop loss o de take profit en la posición abierta, solo se moverá correctamente el botón de desplazamiento. Los demás objetos se habrán eliminado de la lista de objetos. Y no aparecerán en el gráfico en ningún caso. El resultado se muestra en la siguiente imagen:

Esto no es un gran problema. Es solo un inconveniente que debemos corregir. Corregirlo es tan sencillo como lo que hemos hecho hasta ahora. Sin embargo, tendremos que realizar un cambio algo mayor en el código de la clase. A continuación se muestra el código final, ya corregido.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. #define def_NameBtnMove m_Info.szPrefixName + "#MOVE" 007. //+------------------------------------------------------------------+ 008. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 009. //+------------------------------------------------------------------+ 010. #define def_PathBtns "Images\\Market Replay\\Orders\\" 011. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 012. #resource "\\" + def_Btn_Close; 013. //+------------------------------------------------------------------+ 014. #include "..\Auxiliar\C_Mouse.mqh" 015. //+------------------------------------------------------------------+ 016. class C_ElementsTrade : private C_Mouse 017. { 018. private : 019. //+------------------------------------------------------------------+ 020. struct st00 021. { 022. ulong ticket; 023. string szPrefixName, 024. szDescr; 025. EnumEvents ev; 026. double price; 027. bool bClick; 028. char weight; 029. color _color; 030. }m_Info; 031. //+------------------------------------------------------------------+ 032. void UpdateViewPort(void) 033. { 034. int x, y; 035. 036. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 037. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 038. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 039. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 040. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 041. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 042. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 043. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 044. } 045. //+------------------------------------------------------------------+ 046. inline void CreateLinePrice(void) 047. { 048. string szObj; 049. 050. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 051. macro_LineInFocus(false); 052. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 053. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 054. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 055. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 056. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 057. } 058. //+------------------------------------------------------------------+ 059. inline void CreateBoxMove(void) 060. { 061. string szObj; 062. 063. CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 064. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 065. ObjectSetString(0, szObj, OBJPROP_TEXT, "u"); 066. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17); 067. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 068. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 069. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 070. } 071. //+------------------------------------------------------------------+ 072. inline void CreateButtonClose(void) 073. { 074. string szObj; 075. 076. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 077. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 078. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 079. } 080. //+------------------------------------------------------------------+ 081. public : 082. //+------------------------------------------------------------------+ 083. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 084. :C_Mouse(0, "") 085. { 086. ZeroMemory(m_Info); 087. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 088. m_Info._color = _color; 089. m_Info.szDescr = szDescr; 090. } 091. //+------------------------------------------------------------------+ 092. ~C_ElementsTrade() 093. { 094. ObjectsDeleteAll(0, m_Info.szPrefixName); 095. } 096. //+------------------------------------------------------------------+ 097. inline void UpdatePrice(const double open, const double price) 098. { 099. m_Info.price = (price > 0 ? price : open); 100. if (price > 0) 101. { 102. CreateLinePrice(); 103. CreateButtonClose(); 104. }else 105. ObjectsDeleteAll(0, m_Info.szPrefixName); 106. if (m_Info.ev != evMsgClosePositionEA) CreateBoxMove(); 107. UpdateViewPort(); 108. } 109. //+------------------------------------------------------------------+ 110. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 111. { 112. string sz0; 113. 114. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 115. switch (id) 116. { 117. case CHARTEVENT_CUSTOM + evMsgSetFocus: 118. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 119. m_Info.bClick = false; 120. case CHARTEVENT_CHART_CHANGE: 121. UpdateViewPort(); 122. break; 123. case CHARTEVENT_OBJECT_CLICK: 124. sz0 = GetPositionsMouse().szObjNameClick; 125. if (m_Info.bClick) switch (m_Info.ev) 126. { 127. case evMsgClosePositionEA: 128. if (sz0 == def_NameBtnClose) 129. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 130. break; 131. case evMsgCloseTakeProfit: 132. if (sz0 == def_NameBtnClose) 133. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 134. else if (sz0 == def_NameBtnMove) 135. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 136. break; 137. case evMsgCloseStopLoss: 138. if (sz0 == def_NameBtnClose) 139. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 140. else if (sz0 == def_NameBtnMove) 141. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 142. break; 143. } 144. m_Info.bClick = false; 145. break; 146. case CHARTEVENT_MOUSE_MOVE: 147. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 148. if (m_Info.bClick && (m_Info.weight > 1)) 149. { 150. switch (m_Info.ev) 151. { 152. case evMsgCloseTakeProfit: 153. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 154. break; 155. case evMsgCloseStopLoss: 156. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 157. break; 158. } 159. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 160. } 161. break; 162. } 163. } 164. //+------------------------------------------------------------------+ 165. }; 166. //+------------------------------------------------------------------+ 167. #undef macro_LineInFocus 168. //+------------------------------------------------------------------+ 169. #undef def_Btn_Close 170. #undef def_PathBtns 171. //+------------------------------------------------------------------+ 172. #undef def_NameBtnMove 173. #undef def_NameBtnClose 174. #undef def_NameHLine 175. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
El resultado de ejecutar este código se muestra en la siguiente animación.

Observa que ahora el sistema funciona perfectamente. Tal y como se esperaba. Como el código ha sufrido algunos cambios, muchos de ellos muy simples, no es necesario destacarlos especialmente. Sin embargo, quiero que prestes atención a dos puntos de este código en especial, mi querido lector. El primero es el constructor. Observa que es un poco diferente de lo que se veía en la versión anterior. El segundo es el procedimiento UpdatePrice. Observa que los procedimientos que creaban los objetos, que antes estaban en el constructor, ahora están en este procedimiento. Como el código es bastante simple, no es necesario entrar en detalles. De hecho, lo único que se hizo fue quitar lo que se hacía en el constructor y pasarlo a este procedimiento. Nada especialmente espectacular.
Sin embargo, mi querido lector, quiero que dirijas tu atención a otra cosa. En concreto, quiero que prestes atención a la animación anterior. Y respóndeme sinceramente. Si, como operador, intentaras usar este sistema que acabamos de implementar, ¿dudarías si estás ajustando el take profit o el stop loss? ¿Sabrías si tendrías que llevar la línea del indicador de mouse hacia un precio mayor o menor para volver a colocar la línea de precio en el servidor? Quizá esta última pregunta no haya quedado lo suficientemente clara. Intentemos aclararla un poco para que puedas comprender el problema que aún tenemos que resolver.
Al observar solo la animación, se puede apreciar claramente que estoy en una posición vendida. Esto se debe a que la línea de take profit está por debajo del precio de apertura de la posición. Bien, pero supongamos que, como operador, hubieras eliminado ambas líneas del gráfico. O, mejor dicho, que las hubieras eliminado de la posición registrada en el servidor. Entonces, al mirar esta misma animación y observar solo el indicador de posición, no sabrías si estamos vendidos o comprados. Este es el primer punto.
El segundo punto es el siguiente: en caso de que hayamos vendido y los valores de take profit y stop loss no estén definidos en la posición, es decir, que ambos objetos que permiten mover la línea de precio se encuentren en el precio de apertura, ¿cómo podrías saber, solo mirando el gráfico, si debes colocar la línea de stop loss por encima o por debajo del precio actual? Es difícil responder a esta pregunta, ¿no? Sin embargo, tenemos una solución bastante sencilla para este problema. Como puedes observar, en esta clase C_ElementsTrade no accedemos en ningún momento a datos de la posición. Los datos proceden de la clase C_IndicatorPosition. Y no quiero contaminar la clase C_ElementsTrade con datos que, en el futuro, tendrían que eliminarse de ella. Esto es porque también la usaremos cuando trabajemos con órdenes pendientes. Por eso será necesario esforzarnos un poco más. Lo veremos en el próximo artículo.
Consideraciones finales
En este artículo, te he mostrado, querido y estimado lector, cómo puedes modificar el indicador de posición para que sea capaz de hacer muchas más cosas de las que originalmente podía hacer, sin mucho esfuerzo. Vimos cómo incorporar la capacidad de mover los precios y crear líneas de precio directamente en el gráfico. Algo que muchos considerarían extremadamente complicado y difícil de resolver. Sin embargo, habrás notado que lo hicimos todo con mucha facilidad y con un mínimo de esfuerzo. Lo único que hizo falta fue detenerse y pensar un poco.
Ese es precisamente el propósito de esta serie de artículos. No estoy aquí para enseñarte a crear una aplicación. Sin embargo, aprender programación es mucho más motivador e interesante cuando vemos que estamos creando algo. Espero que, querido lector, que estás iniciándote en este fantástico mundo de la programación, puedas entender el objetivo de estos artículos y que te sirvan de buena base teórica y motivacional para seguir aprendiendo cada vez más. Porque no existen problemas imposibles. Lo que sí existen son personas que no logran ver una solución sencilla para grandes problemas.
| Archivo | Descripción |
|---|---|
| Experts\Expert Advisor.mq5 | Muestra la interacción entre Chart Trade y el Asesor Experto. (Es necesario usar Mouse Study para la interacción) |
| Indicators\Chart Trade.mq5 | Crea la ventana para configurar la orden que se va a enviar. (Es necesario usar Mouse Study para la interacción) |
| Indicators\Market Replay.mq5 | Crea los controles para interactuar con el servicio de repetición y simulación de mercado. (Es necesario usar Mouse Study para la interacción) |
| Indicators\Mouse Study.mq5 | Permite interactuar con los controles gráficos y el usuario. (Necesario tanto para operar el sistema de repetición / simulador como en el mercado real) |
| Indicators\Order Indicator.mq5 | Responsable de mostrar las órdenes de mercado, permitiendo interactuar con ellas y controlarlas. |
| Indicators\Position View.mq5 | Responsable de mostrar las posiciones de mercado, permitiendo interactuar con ellas y controlarlas. |
| Services\Market Replay.mq5 | Crea y mantiene el servicio de replay y simulación de mercado. (Archivo principal de todo el sistema) |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13295
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.
Del básico al intermedio: Colas, listas y árboles (IV)
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 27): Herramienta de barrido de liquidez con filtro de media móvil
Particularidades del trabajo con números del tipo double en MQL4
De novato a experto: Desmitificando los niveles ocultos de retroceso de Fibonacci
- 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