Simulación de mercado: Position View (XII)
Introducción
Hola a todos y 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 (XI), expliqué cómo mover las líneas de stop loss y take profit de una forma muy sencilla y práctica, reflejando en el gráfico exactamente el estado registrado en el servidor de trading real. Como seguramente habrás notado, existe una dificultad evidente que afecta al indicador de posición cuando eliminamos tanto la línea de stop loss como la línea de take profit. El problema se debe a que no es posible comprobar directamente en el gráfico si la posición es larga o corta. Para verificarlo, es necesario abrir el terminal. Sin embargo, podemos resolverlo de una forma muy sencilla y práctica. Esto es lo primero que veremos en este artículo. Pasemos al primer tema.
Representación gráfica de la dirección de la posición
Tal vez el aspecto más interesante de este artículo no sean los indicadores ni el propio sistema de repetición/simulación, sino la forma de modificar el código para incorporar nuevas funcionalidades o adaptarlo a una necesidad concreta.
En el artículo anterior, mostré cómo mover las líneas de stop loss y take profit con un esfuerzo mínimo. Muchos consideran que esta es una tarea extremadamente compleja y difícil de ejecutar, dado el número de acciones necesarias. Sin las debidas precauciones, el gráfico podría mostrar un nivel de precio distinto del registrado en el servidor. Esto puede ocurrir si mueves la línea hasta un nuevo nivel, pero el servidor no registra el cambio. Como consecuencia, podrías acumular muchas pérdidas y generar pocos beneficios en operaciones reales. Siempre debemos procurar modificar el código lo menos posible. Cuando realicemos cualquier modificación, debemos asegurarnos de que se ajuste a lo esperado y al comportamiento real del sistema.
Siguiendo esta misma filosofía de introducir el menor número posible de cambios sin alterar el comportamiento del sistema, añadiremos al indicador de posición una señal visual que muestre directamente en el gráfico si la posición es larga o corta.
Muy bien, lo primero que debemos hacer es modificar la clase C_ElementsTrade para incluir un objeto gráfico que indique si la posición es larga o corta. Es una tarea sencilla y directa. El siguiente código muestra la nueva versión de la clase C_ElementsTrade con esta señal gráfica.
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. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 008. //+------------------------------------------------------------------+ 009. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 010. //+------------------------------------------------------------------+ 011. #define def_PathBtns "Images\\Market Replay\\Orders\\" 012. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 013. #resource "\\" + def_Btn_Close; 014. //+------------------------------------------------------------------+ 015. #include "..\Auxiliar\C_Mouse.mqh" 016. //+------------------------------------------------------------------+ 017. class C_ElementsTrade : private C_Mouse 018. { 019. private : 020. //+------------------------------------------------------------------+ 021. struct st00 022. { 023. ulong ticket; 024. string szPrefixName, 025. szDescr; 026. EnumEvents ev; 027. double price; 028. bool bClick, 029. bIsBuy; 030. char weight; 031. color _color; 032. }m_Info; 033. //+------------------------------------------------------------------+ 034. void UpdateViewPort(void) 035. { 036. int x, y; 037. 038. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 039. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 040. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 041. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 042. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 044. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 045. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 046. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 047. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateLinePrice(void) 051. { 052. string szObj; 053. 054. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 055. macro_LineInFocus(false); 056. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 057. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 058. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 059. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 060. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 061. } 062. //+------------------------------------------------------------------+ 063. inline void CreateBoxMove(void) 064. { 065. string szObj; 066. 067. CreateObjectGraphics(szObj = def_NameBtnMove, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 068. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 069. ObjectSetString(0, szObj, OBJPROP_TEXT, "u"); 070. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 17); 071. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 072. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 073. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 074. } 075. //+------------------------------------------------------------------+ 076. inline void CreateButtonClose(void) 077. { 078. string szObj; 079. 080. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 081. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 082. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 083. } 084. //+------------------------------------------------------------------+ 085. inline void CreateInfoDirect(void) 086. { 087. string szObj; 088. const char c[] = {(char)(m_Info.bIsBuy ? 236 : 238), 0}; 089. 090. CreateObjectGraphics(szObj = def_NameInfoDirect, OBJ_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 091. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 092. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 093. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, 15); 094. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 095. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, 21); 096. ObjectSetInteger(0, szObj, OBJPROP_YSIZE, 23); 097. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (m_Info.bIsBuy ? clrForestGreen : clrFireBrick)); 098. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 099. } 100. //+------------------------------------------------------------------+ 101. public : 102. //+------------------------------------------------------------------+ 103. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, string szDescr = "\n", const bool IsBuy = true) 104. :C_Mouse(0, "") 105. { 106. ZeroMemory(m_Info); 107. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 108. m_Info._color = _color; 109. m_Info.szDescr = szDescr; 110. m_Info.bIsBuy = IsBuy; 111. } 112. //+------------------------------------------------------------------+ 113. ~C_ElementsTrade() 114. { 115. ObjectsDeleteAll(0, m_Info.szPrefixName); 116. } 117. //+------------------------------------------------------------------+ 118. inline void UpdatePrice(const double open, const double price) 119. { 120. m_Info.price = (price > 0 ? price : open); 121. if (price > 0) 122. { 123. CreateLinePrice(); 124. CreateButtonClose(); 125. }else 126. ObjectsDeleteAll(0, m_Info.szPrefixName); 127. if (m_Info.ev != evMsgClosePositionEA) CreateBoxMove(); 128. else CreateInfoDirect(); 129. UpdateViewPort(); 130. } 131. //+------------------------------------------------------------------+ 132. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 133. { 134. string sz0; 135. 136. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 137. switch (id) 138. { 139. case CHARTEVENT_CUSTOM + evMsgSetFocus: 140. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 141. m_Info.bClick = false; 142. case CHARTEVENT_CHART_CHANGE: 143. UpdateViewPort(); 144. break; 145. case CHARTEVENT_OBJECT_CLICK: 146. sz0 = GetPositionsMouse().szObjNameClick; 147. if (m_Info.bClick) switch (m_Info.ev) 148. { 149. case evMsgClosePositionEA: 150. if (sz0 == def_NameBtnClose) 151. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 152. break; 153. case evMsgCloseTakeProfit: 154. if (sz0 == def_NameBtnClose) 155. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 156. else if (sz0 == def_NameBtnMove) 157. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 158. break; 159. case evMsgCloseStopLoss: 160. if (sz0 == def_NameBtnClose) 161. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 162. else if (sz0 == def_NameBtnMove) 163. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 164. break; 165. } 166. m_Info.bClick = false; 167. break; 168. case CHARTEVENT_MOUSE_MOVE: 169. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 170. if (m_Info.bClick && (m_Info.weight > 1)) 171. { 172. switch (m_Info.ev) 173. { 174. case evMsgCloseTakeProfit: 175. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 176. break; 177. case evMsgCloseStopLoss: 178. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 179. break; 180. } 181. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 182. } 183. break; 184. } 185. } 186. //+------------------------------------------------------------------+ 187. }; 188. //+------------------------------------------------------------------+ 189. #undef macro_LineInFocus 190. //+------------------------------------------------------------------+ 191. #undef def_Btn_Close 192. #undef def_PathBtns 193. //+------------------------------------------------------------------+ 194. #undef def_NameInfoDirect 195. #undef def_NameBtnMove 196. #undef def_NameBtnClose 197. #undef def_NameHLine 198. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Si no logras identificar los cambios, destacaré algunas modificaciones concretas para hacerlas más evidentes. Sin embargo, lo ideal es que tú, mi querido lector, intentes comprender el código sin necesidad de leer las explicaciones que voy a proporcionar. Así te acostumbrarás a comprender el código y a identificar por tu cuenta las partes que puedan interesarte. Muy bien, en la línea siete tenemos la definición del nombre que recibirá el objeto que se va a crear. En la línea 29, definimos una nueva variable. Por ahora se utilizará muy poco, por lo que puede parecer innecesario haberla declarado. Más adelante comprenderás mejor el motivo de su declaración. Ahora, al pasar al procedimiento de la línea 34, encontramos dos líneas nuevas, las líneas 44 y 45. Estas posicionan el objeto en el gráfico.
Ahora pasemos a la línea 85, donde el procedimiento CreateInfoDirect crea el objeto gráfico. Hay un detalle que debemos comprender. Si decides utilizar una imagen para representar una posición larga o corta, este procedimiento, a partir de la línea 85, deberá modificarse ligeramente. No es nada demasiado complicado. Utiliza el código de CreateButtonClose como base para cargar la imagen. Sin embargo, aquí utilizaremos una solución similar a la empleada en el procedimiento CreateBoxMove. Es decir, colocaremos un carácter que represente si la posición es larga o corta. Un detalle: por ahora, gran parte del código de CreateBoxMove también aparecerá en CreateInfoDirect. Así se muestra cómo crear estos elementos. Más adelante, esta duplicación de código desaparecerá.
Bien, analicemos este fragmento, ya que algunas instrucciones pueden resultar difíciles de comprender. Por ejemplo, la expresión de la línea 88 puede generar dudas. Ahora responde con sinceridad: al observar esta línea 88, ¿entiendes qué estoy haciendo? Si es así, perfecto. Esto significa que ya sabes lo que voy a explicar. Si no, presta atención. La línea 92 asigna una cadena de caracteres al objeto gráfico. Para generarla, la función CharArrayToString convierte en un string el array de tipo ushort definido en la línea 88.
¿Por qué estoy definiendo el valor como una constante? Porque quiero garantizar que no modificaré accidentalmente el valor de este array. Quiero asegurarme de que se mantenga igual durante toda la rutina. Aunque esta rutina sea sencilla, puede que en el futuro se vuelva un poco más compleja. Y no quiero correr el riesgo de cambiar accidentalmente el valor del array c. Ahora viene la magia. Un string en MQL5 sigue el mismo principio que los strings en C/C++, es decir, termina con un carácter NULO, o \0. Este es el valor cero que ves en la línea 88. El array c es un array dinámico unidimensional cuyo tamaño inicial es de dos elementos: el código del carácter que se mostrará y el cero terminal. Su carácter dinámico se debe a que la primera dimensión se declara sin un tamaño fijo; el tamaño inicial, en cambio, queda determinado por los dos valores incluidos en la inicialización. Si no se utilizara esta sintaxis, el código fallaría durante la compilación.
Es decir, en la declaración definimos c como un array dinámico unidimensional cuyos elementos son de tipo ushort. Después del signo de igualdad comienza la inicialización, delimitada por llaves. Dentro de esas llaves, la expresión (ushort) no declara una variable ni modifica la declaración del array: convierte explícitamente al tipo ushort el resultado de la expresión evaluada mediante el operador ternario. Esta conversión evita que el compilador genere advertencias durante la compilación. Aquí utilizamos el valor de la variable definida en la línea 29. Si es verdadero, el primer elemento del array almacena el código numérico 236; si es falso, almacena el código numérico 238. Estos números no son texto por sí mismos: identifican caracteres. La función CharArrayToString convierte el array en un string que contiene el carácter seleccionado. La línea 91 asigna Wingdings como fuente del objeto gráfico, por lo que la fuente representa ese carácter mediante el glifo asociado al código 236 o 238. Para conocer la forma que aparecerá en el gráfico, consulta esos códigos en el mapa de caracteres de Wingdings. El resto del procedimiento utiliza las operaciones habituales de MQL5 para crear y configurar objetos gráficos.
Bien, probablemente te estés preguntando: ¿cómo especificaremos a la clase cuál de los dos glifos debe mostrar para representar una posición larga o corta? La respuesta es sencilla: transmitiremos la dirección de la posición mediante el constructor. Por esta razón, observa en la línea 103 el código del constructor. Fíjate en que se ha modificado ligeramente, aunque no es nada tan importante como para requerir una explicación. Como este constructor se ha modificado, también necesitamos modificar el código que lo utiliza. Antes de verlo, echemos un último vistazo a los cambios de esta clase. Me refiero al código de la línea 118. La línea 128 diferencia la línea del precio de apertura de las líneas de take profit y stop loss. La condición de la línea
127 determina si la instancia representa la línea del precio de apertura o una de las líneas de take profit y stop loss. Cuando representa el precio de apertura, el código no crea el objeto gráfico para mover la línea. En su lugar, crea el objeto que muestra si la posición es larga o corta. Con esto, podemos finalizar el código de la clase C_ElementsTrade. Como mencioné hace poco, se produjo un cambio en el código que llama al constructor. Por lo tanto, necesitamos modificar el código de la clase C_IndicatorPosition. Como gran parte del código se ha mantenido intacto, nos centraremos únicamente en lo que ha cambiado. Así, la parte modificada 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, StringFormat("%I64u : Position opening price.", m_Infos.ticket), PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY); 63. if (Take == NULL) Take = new C_ElementsTrade(m_Infos.ticket, evMsgCloseTakeProfit, clrForestGreen, StringFormat("%I64u : Take Profit price.", m_Infos.ticket)); 64. if (Stop == NULL) Stop = new C_ElementsTrade(m_Infos.ticket, evMsgCloseStopLoss, clrFireBrick, 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 lo extremadamente sutil que es el cambio. Este se produce precisamente en las líneas 62 a 64. Esto es lo único que se ha modificado con respecto a lo que existía anteriormente. Cuando compiles el indicador de posición y lo coloques en el gráfico, aparecerá una señal gráfica similar a la de la siguiente imagen.

La zona destacada de esta imagen muestra la diferencia. Ahora el gráfico representa claramente si la posición es larga o corta. Genial, ¿verdad? Ha sido bastante sencillo. Antes de pasar a la siguiente modificación, veamos cómo quedó el código final de la clase C_ElementsTrade después de eliminar las partes duplicadas. Esto se muestra a continuación.
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. #define def_NameInfoDirect m_Info.szPrefixName + "#DIRECT" 008. //+------------------------------------------------------------------+ 009. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 3 : 1)); 010. //+------------------------------------------------------------------+ 011. #define def_PathBtns "Images\\Market Replay\\Orders\\" 012. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 013. #resource "\\" + def_Btn_Close; 014. //+------------------------------------------------------------------+ 015. #include "..\Auxiliar\C_Mouse.mqh" 016. //+------------------------------------------------------------------+ 017. class C_ElementsTrade : private C_Mouse 018. { 019. private : 020. //+------------------------------------------------------------------+ 021. struct st00 022. { 023. ulong ticket; 024. string szPrefixName, 025. szDescr; 026. EnumEvents ev; 027. double price; 028. bool bClick, 029. bIsBuy; 030. char weight; 031. color _color; 032. }m_Info; 033. //+------------------------------------------------------------------+ 034. void UpdateViewPort(void) 035. { 036. int x, y; 037. 038. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 039. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 040. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 041. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 042. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 044. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 045. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 046. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 047. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateLinePrice(void) 051. { 052. string szObj; 053. 054. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, m_Info._color, (EnumPriority)(ePriorityDefault)); 055. macro_LineInFocus(false); 056. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, m_Info._color); 057. ObjectSetInteger(0, szObj, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); 058. ObjectSetInteger(0, szObj, OBJPROP_BORDER_TYPE, BORDER_FLAT); 059. ObjectSetInteger(0, szObj, OBJPROP_CORNER, CORNER_LEFT_UPPER); 060. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, m_Info.szDescr); 061. } 062. //+------------------------------------------------------------------+ 063. inline void CreateBoxInfo(const bool bMove) 064. { 065. string szObj; 066. const char c[] = {(char)(bMove ? 'u' : (m_Info.bIsBuy ? 236 : 238)), 0}; 067. 068. CreateObjectGraphics(szObj = (bMove ? def_NameBtnMove : def_NameInfoDirect), OBJ_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 069. ObjectSetString(0, szObj, OBJPROP_FONT, "Wingdings"); 070. ObjectSetString(0, szObj, OBJPROP_TEXT, CharArrayToString(c)); 071. ObjectSetInteger(0, szObj, OBJPROP_COLOR, (bMove ? m_Info._color : (m_Info.bIsBuy ? clrForestGreen : clrFireBrick))); 072. ObjectSetInteger(0, szObj, OBJPROP_FONTSIZE, (bMove ? 17 : 15)); 073. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 074. } 075. //+------------------------------------------------------------------+ 076. inline void CreateButtonClose(void) 077. { 078. string szObj; 079. 080. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 081. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 082. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 083. } 084. //+------------------------------------------------------------------+ 085. public : 086. //+------------------------------------------------------------------+ 087. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, string szDescr = "\n", const bool IsBuy = true) 088. :C_Mouse(0, "") 089. { 090. ZeroMemory(m_Info); 091. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 092. m_Info._color = _color; 093. m_Info.szDescr = szDescr; 094. m_Info.bIsBuy = IsBuy; 095. } 096. //+------------------------------------------------------------------+ 097. ~C_ElementsTrade() 098. { 099. ObjectsDeleteAll(0, m_Info.szPrefixName); 100. } 101. //+------------------------------------------------------------------+ 102. inline void UpdatePrice(const double open, const double price) 103. { 104. m_Info.price = (price > 0 ? price : open); 105. if (price > 0) 106. { 107. CreateLinePrice(); 108. CreateButtonClose(); 109. }else 110. ObjectsDeleteAll(0, m_Info.szPrefixName); 111. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 112. UpdateViewPort(); 113. } 114. //+------------------------------------------------------------------+ 115. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. string sz0; 118. 119. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 120. switch (id) 121. { 122. case CHARTEVENT_CUSTOM + evMsgSetFocus: 123. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 124. m_Info.bClick = false; 125. case CHARTEVENT_CHART_CHANGE: 126. UpdateViewPort(); 127. break; 128. case CHARTEVENT_OBJECT_CLICK: 129. sz0 = GetPositionsMouse().szObjNameClick; 130. if (m_Info.bClick) switch (m_Info.ev) 131. { 132. case evMsgClosePositionEA: 133. if (sz0 == def_NameBtnClose) 134. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 135. break; 136. case evMsgCloseTakeProfit: 137. if (sz0 == def_NameBtnClose) 138. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 139. else if (sz0 == def_NameBtnMove) 140. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 141. break; 142. case evMsgCloseStopLoss: 143. if (sz0 == def_NameBtnClose) 144. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 145. else if (sz0 == def_NameBtnMove) 146. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 147. break; 148. } 149. m_Info.bClick = false; 150. break; 151. case CHARTEVENT_MOUSE_MOVE: 152. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 153. if (m_Info.bClick && (m_Info.weight > 1)) 154. { 155. switch (m_Info.ev) 156. { 157. case evMsgCloseTakeProfit: 158. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 159. break; 160. case evMsgCloseStopLoss: 161. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 162. break; 163. } 164. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 165. } 166. break; 167. } 168. } 169. //+------------------------------------------------------------------+ 170. }; 171. //+------------------------------------------------------------------+ 172. #undef macro_LineInFocus 173. //+------------------------------------------------------------------+ 174. #undef def_Btn_Close 175. #undef def_PathBtns 176. //+------------------------------------------------------------------+ 177. #undef def_NameInfoDirect 178. #undef def_NameBtnMove 179. #undef def_NameBtnClose 180. #undef def_NameHLine 181. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Para animarte a estudiar, no explicaré qué se modificó ni por qué. Quiero que estudies e intentes comprender las modificaciones realizadas en el código anterior para eliminar las partes duplicadas. Tu tarea será entender por qué estos cambios no afectaron a lo que se muestra en el gráfico.
Antes de terminar este artículo, vamos a incorporar una actualización más al sistema para que todo resulte aún más interesante. Esta actualización nos permitirá saber si estamos ajustando la línea de take profit o la línea de stop loss. Veámoslo en un nuevo tema.
¿Qué línea estoy moviendo: la de take profit o la de stop loss?
No es nada raro que, después de hacer clic en el objeto gráfico, dudes sobre cuál de las dos líneas estás moviendo: la de take profit o la de stop loss. De hecho, la duda puede surgir incluso después de hacer clic en el gráfico, ya que, hasta el momento, no existe ninguna forma de saber si estamos moviendo una de las líneas de precio o si simplemente estamos desplazando el cursor del mouse por el gráfico. Además, si estamos moviendo una línea de precio y olvidamos hacer clic de nuevo, podríamos llevarnos una sorpresa desagradable al hacer clic en una determinada posición del gráfico. Para resolver este problema, vamos a mejorar aún más el sistema. Eso sí, por ahora la mejora será muy sencilla y solo mostrará cuál de las dos líneas de precio estamos moviendo: la de take profit o la de stop loss.
Muy bien, mi querido lector, en este momento quizá estés pensando lo siguiente: ahora sí tenemos un problema cuya solución requerirá bastante trabajo. Sin embargo, en realidad no requiere tanto trabajo. Si has estado siguiendo esta serie de artículos sobre el sistema de repetición/simulación, comprenderás rápidamente la estrategia que aplicaremos. Antes de continuar, necesitas comprender cómo intercambian eventos el indicador de mouse, el Asesor Experto y el indicador de posición.
Haré un breve resumen, pero te aconsejo que leas los artículos anteriores si aún no lo has hecho, para comprenderlo todo. De lo contrario, no entenderás los cambios que aplicaré a continuación.
Las interacciones se distribuyen entre varios componentes del gráfico que intercambian y procesan eventos de forma coordinada. Es decir, tenemos el indicador de mouse, el Asesor Experto y el indicador de posición. Además, por supuesto, está el indicador Chart Trade. Sin embargo, este último no participará en la modificación que realizaremos a continuación. Cuando hacemos clic en el control que finaliza el trazado, en una línea de precio o en la representación gráfica de la posición, el Asesor Experto procesa el evento y el indicador de posición actualiza el gráfico. Al hacer clic en el objeto gráfico que permite mover la línea de take profit o de stop loss, la línea horizontal del indicador de mouse sigue el cursor.
El segundo clic envía al Asesor Experto el nivel de precio definitivo de esa línea. El indicador de mouse transmite el nivel de precio seleccionado al indicador de posición, y este genera el evento que recibe el Asesor Experto. A continuación, el Asesor Experto solicita al servidor de trading que actualice el nivel de precio de la línea de take profit o de stop loss. La línea conserva su nivel anterior hasta que el servidor confirme el cambio. Cuando llega la confirmación, el Asesor Experto envía el nivel validado al indicador de posición, que actualiza la línea en el gráfico. El resultado visible depende, por tanto, de este flujo de eventos y de la respuesta del servidor. Entonces, lo primero que debemos hacer es enviar a cada componente el mensaje correspondiente para que actualice su estado,
sin alterar el flujo actual de eventos. Para que comprendas con claridad el flujo de interacción, aplicaremos los cambios paso a paso. Primero haremos lo siguiente: cuando hagas clic en uno de los objetos gráficos para mover la línea de precio, ocultaremos la línea horizontal del indicador de mouse. En cuanto selecciones el nuevo nivel de precio de la línea de take profit o de stop loss, volveremos a mostrar la línea horizontal del indicador de mouse. Así de sencillo.
Si no estás siguiendo esta serie de artículos, quizá sientas la tentación de modificar los objetos del gráfico. Aunque esto no sea incorrecto en sí mismo, se considerará un error, porque no debemos manipular dichos objetos, sino notificar a las aplicaciones que deben cambiar su comportamiento. Este es el camino correcto. Por lo tanto, para implementar este comportamiento, será necesario modificar ligeramente el código de la clase C_ElementsTrade. El cambio es tan sutil que, si no prestas atención, no lo notarás. Se muestra en el siguiente fragmento.
114. //+------------------------------------------------------------------+ 115. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 116. { 117. string sz0; 118. 119. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 120. switch (id) 121. { 122. case CHARTEVENT_CUSTOM + evMsgSetFocus: 123. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 124. EventChartCustom(0, (ushort)(dparam ? evHideMouse : evShowMouse), 0, 0, ""); 125. m_Info.bClick = false; 126. case CHARTEVENT_CHART_CHANGE: 127. UpdateViewPort(); 128. break; 129. case CHARTEVENT_OBJECT_CLICK: 130. sz0 = GetPositionsMouse().szObjNameClick; 131. if (m_Info.bClick) switch (m_Info.ev) 132. {
Fragmento de C_ElementsTrade
El fragmento anterior muestra exactamente el cambio que debemos realizar. Así es: estamos modificando el procedimiento DispatchMessage. Si no prestas mucha atención, difícilmente notarás dónde se encuentra la modificación. Está precisamente en la línea 124. Esta línea envía al indicador de mouse el valor de la enumeración que determina si debe mostrar u ocultar su línea horizontal. Así de sencillo. Observa que la expresión (ushort) no es una declaración, sino una conversión explícita de tipo aplicada al valor de la enumeración, que normalmente es de tipo int. Necesitamos convertir ese valor a ushort; de lo contrario, el compilador advertirá de una posible pérdida de datos.
Con esto concluimos la primera modificación. Cuando estemos en el modo de mover la línea de precio, se ocultará la línea horizontal del indicador de mouse. En caso contrario, se mostrará en el gráfico. Pasemos ahora a la segunda modificación. Lo haremos de la siguiente manera. Sustituiremos la línea horizontal del indicador de mouse por una línea de precio auxiliar que representará la línea que estamos moviendo. Para ello, tendremos que realizar algunos cambios más en el código. Aun así, seguiremos trabajando en la clase C_ElementsTrade. Muy bien, pensemos un poco.
¿Cómo queremos representar esta línea de precio auxiliar? En mi opinión, la mejor forma es utilizar los colores originales de las líneas. Así tendremos la clara sensación de que estamos moviendo la línea correcta. Hay otro detalle. Mover únicamente la línea me parece demasiado sencillo e incluso un tanto aburrido. ¿Qué te parece si lo complicamos un poco y desplazamos también los controles gráficos vinculados a esa línea, como el botón de cierre y el control utilizado para moverla? Hum, ahora parece un poco más desafiante. ¿Realmente lo será? Bien, veámoslo. Si la idea es mover todos los objetos junto con la línea de precio, bastará con pasar el nuevo nivel de precio al procedimiento UpdateViewPort. Hum, con el código tal como está, esto no será posible. Sin embargo, ¿qué ocurriría si modificáramos el procedimiento UpdateViewPort para que reciba el precio? El nuevo código se muestra en el siguiente fragmento.
33. //+------------------------------------------------------------------+ 34. void UpdateViewPort(const double price) 35. { 36. int x, y; 37. 38. ChartTimePriceToXY(0, 0, 0, price, x, y); 39. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 220 : 290)); 40. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 41. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight > 1 ? (int)(m_Info.weight / 2) : 0)); 42. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 43. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 44. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_XDISTANCE, x + 18); 45. ObjectSetInteger(0, def_NameInfoDirect, OBJPROP_YDISTANCE, y); 46. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_XDISTANCE, x + 30); 47. ObjectSetInteger(0, def_NameBtnMove, OBJPROP_YDISTANCE, y); 48. } 49. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Bien. Con esto, necesitamos modificar el procedimiento encargado de actualizar el precio. Este se muestra en el siguiente fragmento.
101. //+------------------------------------------------------------------+ 102. inline void UpdatePrice(const double open, const double price) 103. { 104. if (price > 0) 105. { 106. CreateLinePrice(); 107. CreateButtonClose(); 108. }else 109. ObjectsDeleteAll(0, m_Info.szPrefixName); 110. CreateBoxInfo(m_Info.ev != evMsgClosePositionEA); 111. UpdateViewPort(m_Info.price = (price > 0 ? price : open)); 112. } 113. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Bien, ¿y qué conseguimos con esto? Hacerlo no resuelve realmente el problema. No podemos mover la línea de precio como esperábamos. Sin embargo, si ahora modificamos el procedimiento DispatchMessage como se muestra a continuación, la situación cambia.
113. //+------------------------------------------------------------------+ 114. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 115. { 116. string sz0; 117. 118. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 119. switch (id) 120. { 121. case CHARTEVENT_CUSTOM + evMsgSetFocus: 122. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 123. EventChartCustom(0, (ushort)(dparam ? evHideMouse : evShowMouse), 0, 0, ""); 124. m_Info.bClick = false; 125. case CHARTEVENT_CHART_CHANGE: 126. UpdateViewPort(m_Info.price); 127. break; 128. case CHARTEVENT_OBJECT_CLICK: 129. sz0 = GetPositionsMouse().szObjNameClick; 130. if (m_Info.bClick) switch (m_Info.ev) 131. { 132. case evMsgClosePositionEA: 133. if (sz0 == def_NameBtnClose) 134. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 135. break; 136. case evMsgCloseTakeProfit: 137. if (sz0 == def_NameBtnClose) 138. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 139. else if (sz0 == def_NameBtnMove) 140. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 141. break; 142. case evMsgCloseStopLoss: 143. if (sz0 == def_NameBtnClose) 144. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 145. else if (sz0 == def_NameBtnMove) 146. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 147. break; 148. } 149. m_Info.bClick = false; 150. break; 151. case CHARTEVENT_MOUSE_MOVE: 152. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 153. if (m_Info.weight > 1) 154. { 155. UpdateViewPort(GetPositionsMouse().Position.Price); 156. if (m_Info.bClick) 157. { 158. switch (m_Info.ev) 159. { 160. case evMsgCloseTakeProfit: 161. EventChartCustom(0, evMsgNewTakeProfit, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 162. break; 163. case evMsgCloseStopLoss: 164. EventChartCustom(0, evMsgNewStopLoss, m_Info.ticket, GetPositionsMouse().Position.Price, PositionGetString(POSITION_SYMBOL)); 165. break; 166. } 167. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 168. } 169. } 170. break; 171. } 172. } 173. //+------------------------------------------------------------------+
Fragmento de C_ElementsTrade
Ahora presta atención a lo siguiente. Cuando una línea esté seleccionada, la variable weight tendrá un valor mayor que uno. Por lo tanto, la expresión booleana de la línea 153 devolverá true y el programa ejecutará el bloque correspondiente. Dentro de este bloque, la línea 155 llama al procedimiento UpdateViewPort y le transmite el nuevo nivel de precio provisional de la línea. La vista se actualiza en tiempo real, pero el cambio solo se confirma cuando haces clic. Cuando el servidor confirme el nivel de precio, el sistema reanudará su funcionamiento habitual.
Es decir, hemos modificado el código sin alterar el comportamiento observable del sistema. Ya has visto que no ha sido nada complicado. Solo tuvimos que ajustar unas pocas instrucciones para que UpdateViewPort actualizara la línea y los controles gráficos vinculados a ella. Nada desafiante. A decir verdad, incluso ha resultado un poco monótono. Ahora sí podemos abordar una situación más compleja. ¿O quizá no? Bien, pensemos en lo siguiente. ¿Qué ocurriría si, mientras mueves la línea de precio, decidieras realizar un estudio? Esto debería cancelar el movimiento en curso y obligar a los objetos a volver a su posición original. Quizá pienses que esto es muy complicado, ya que los valores cambiaron durante los primeros desplazamientos.
Veamos cómo resolver este problema. Muy bien, cuando comenzamos a mover una de las líneas de precio, ya sea la de take profit o la de stop loss, no podemos iniciar un estudio. Para iniciar un estudio con el indicador de mouse, consulta los artículos anteriores para saber cómo hacerlo. Para continuar, deberás cancelar el movimiento y decidir cómo hacerlo. Tras hablar con algunas personas, me sugirieron utilizar algo bastante práctico y seguro: pulsar la tecla ESC. Podrías pulsar accidentalmente el botón del mouse o algún otro control, pero no pulsarás la tecla ESC sin darte cuenta. Por lo tanto, modificaremos nuevamente el procedimiento DispatchMessage para que la tecla ESC cancele el movimiento. Después podrás realizar cualquier otra acción.
113. //+------------------------------------------------------------------+ 114. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 115. { 116. string sz0; 117. long _lparam = lparam; 118. double _dparam = dparam; 119. 120. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 121. switch (id) 122. { 123. case (CHARTEVENT_KEYDOWN): 124. if (!TerminalInfoInteger(TERMINAL_KEYSTATE_ESCAPE)) break; 125. _lparam = (long) m_Info.ticket; 126. _dparam = 0; 127. case CHARTEVENT_CUSTOM + evMsgSetFocus: 128. macro_LineInFocus((m_Info.ticket == (ulong)(_lparam)) && ((EnumEvents)(_dparam) == m_Info.ev)); 129. EventChartCustom(0, (ushort)(_dparam ? evHideMouse : evShowMouse), 0, 0, ""); 130. m_Info.bClick = false; 131. case CHARTEVENT_CHART_CHANGE: 132. UpdateViewPort(m_Info.price); 133. break; 134. case CHARTEVENT_OBJECT_CLICK: 135. sz0 = GetPositionsMouse().szObjNameClick; 136. if (m_Info.bClick) switch (m_Info.ev)
Fragmento de C_ElementsTrade
El fragmento anterior muestra la solución. No incluí todo el código del procedimiento DispatchMessage porque, en mi opinión, es totalmente innecesario, ya que la solución puede verse fácilmente en este fragmento. Observa que fue necesario añadir dos variables locales para conservar intactos los valores de los parámetros del evento recibidos desde MetaTrader 5. Estas variables se encuentran en las líneas 117 y 118. Ahora surge una pregunta bastante válida: ¿por qué no eliminas el calificador const de los argumentos de la llamada de la línea 114? Así no sería necesario utilizar dichas variables. En muchos casos, esta sería una solución válida. Sin embargo, por experiencia sé que no es una buena idea hacerlo.
El motivo es que a menudo sentirás la tentación de modificar el valor. En algún momento futuro, el procedimiento podría dejar de interpretar correctamente los parámetros del evento enviados por MetaTrader 5 o por el sistema operativo. En Windows, este mismo patrón de parámetros se utiliza para intercambiar mensajes entre aplicaciones o procesos. Esto suele verse con mayor frecuencia al programar DLLs. Por esta razón, no te aconsejo realizar ese cambio. Es preferible modificar siempre el valor de forma local, como se muestra aquí. Esto te ahorrará muchos dolores de cabeza y tiempo perdido intentando entender por qué el código no funciona correctamente.
Bien, una vez hecho esto, observa que la línea 123 identifica los eventos de teclado recibidos por el procedimiento. Existen varias formas de gestionar y filtrar estos eventos. En MetaTrader 5, la línea 124 compara el código de la tecla recibida con el código de ESC. Si la tecla no es ESC, el comando break finaliza en esa línea el tratamiento del evento. Por ejemplo, si has seleccionado un objeto para moverlo y presionas F2, no se ejecutará el bloque de cancelación. Si la tecla recibida es ESC, el programa ejecutará las líneas 125 a 133. En ese caso, la ejecución continúa deliberadamente por los dos bloques de tratamiento siguientes, lo que permite reutilizar su lógica sin duplicar código.
Consideraciones finales
Bien, por ahora creo que ya hemos avanzado bastante. A estas alturas, tú, mi querido y estimado lector, ya tienes mucho que aprender y probar. Como quizá no sepas cómo compilar estos archivos MQL5, en el anexo se incluyen las versiones ya compiladas. Así, si estás dando tus primeros pasos, podrás comprobar el comportamiento del sistema descrito hasta aquí.
Recuerda que todo lo mostrado aquí tiene fines didácticos. Por lo tanto, debes actuar con extrema precaución al utilizar las aplicaciones adjuntas en una cuenta real. Mi sugerencia es que las utilices en cuentas demo. Aprende cómo funciona el sistema y modifícalo según tus necesidades particulares.
Nos vemos en el próximo artículo, donde seguiremos avanzando hacia un sistema más completo.
| 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/simulador (Es necesario usar Mouse Study para la interacción) |
| Indicators\Mouse Study.mq5 | Permite la interacción entre 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 | Se encarga de mostrar las órdenes de mercado y permite interactuar con ellas y controlarlas |
| Indicators\Position View.mq5 | Se encarga de mostrar las posiciones de mercado y permite interactuar con ellas y controlarlas |
| Services\Market Replay.mq5 | Crea y mantiene el servicio de repetición 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/13317
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 (V)
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 28): Herramienta de ruptura del rango de apertura
Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Final)
Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Componentes principales)
- 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