Simulación de mercado: Position View (X)
Introducción
Hola a todos, sean bienvenidos a un nuevo 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 (IX), empezamos a explorar cómo debería implementarse el movimiento de las líneas de take profit y stop loss. Como quiero explicarles a todos y dejar muy claro cómo y por qué se producen los cambios en el código, en este artículo intentaremos resolver un problema presentado en el artículo anterior. El problema es la cuestión de ZOrder. Si no sabes la importancia que tiene la propiedad ZOrder en los objetos, no te preocupes, solo procura estudiar los artículos anteriores de esta misma serie, ya que allí explico la importancia de saber definir adecuadamente el valor de esa propiedad.
Más allá de eso, aquí surge una cuestión: ¿cómo podemos usar ZOrder a nuestro favor? La respuesta es simple: no podemos. Parece algo absurdo y completamente sin sentido. Sin embargo, por muy bien que puedas programar, estimado lector, no lograrás superar lo que ya está programado en MetaTrader 5 para manejar ZOrder.
Aun así, necesitamos un medio para manejar los objetos gráficos que se crearán. La propuesta mostrada en el artículo anterior encaja perfectamente en algunos escenarios. Aquí necesitamos algo un poco más elaborado, debido a la naturaleza del problema que estamos tratando. Por lo tanto, no intentaremos sustituir los mecanismos presentes en MetaTrader 5 para manejar ZOrder ni, por supuesto, comprobar qué objeto está en primer plano o queda oculto por otro objeto. Haremos algo completamente diferente. Aquí mostraré qué modificaciones hay que hacer en el código para aprovechar parte de lo que MetaTrader 5 ya hace por nosotros. Es decir, indicar qué objeto debe manipularse o no por haber recibido un clic.
Para que puedas comprender lo que haremos, primero es necesario ver un código bastante simple, aunque extremadamente útil para saber cómo abordar el problema que resolveremos.
El código más simple
Para entender esto, es necesario usar algo que puedas comprender de verdad. Muchos suelen pensar que, para resolver un problema, tenemos que crear una solución rebuscada o extremadamente compleja. Pero no es así como debemos actuar en la práctica. En la práctica, debemos crear algo tan simple como sea posible. Debemos usar la aplicación y entender lo que nos informa. Para demostrar esto, usaremos el código que se ve a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_chart_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. #define debug(A) Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 07. //+------------------------------------------------------------------+ 08. int OnInit() 09. { 10. string sz1 = "Object #01"; 11. 12. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 13. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 100); 14. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 100); 15. 16. sz1 = "Object #02"; 17. ObjectCreate(0, sz1, OBJ_BUTTON, 0, 0, 0); 18. ObjectSetInteger(0, sz1, OBJPROP_XDISTANCE, 200); 19. ObjectSetInteger(0, sz1, OBJPROP_YDISTANCE, 200); 20. 21. ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); 22. 23. return INIT_SUCCEEDED; 24. } 25. //+------------------------------------------------------------------+ 26. int OnCalculate(const int rates_total, 27. const int prev_calculated, 28. const int begin, 29. const double &price[]) 30. { 31. return rates_total; 32. } 33. //+------------------------------------------------------------------+ 34. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 35. { 36. switch(id) 37. { 38. case CHARTEVENT_OBJECT_CLICK: 39. debug(sparam); 40. break; 41. case CHARTEVENT_MOUSE_MOVE: 42. if (sparam != "0") 43. debug(sparam); 44. break; 45. } 46. }; 47. //+------------------------------------------------------------------+ 48. void OnDeinit(const int reason) 49. { 50. ObjectsDeleteAll(0, "Object", -1, OBJ_BUTTON); 51. } 52. //+------------------------------------------------------------------+
Indicador para pruebas
Este código es extremadamente simple. Lo único que hace es crear dos objetos con un ZOrder estándar, es decir, cero, e imprimir un mensaje cuando ocurre un evento. Así de simple. Puede parecer algo tonto e incluso ingenuo. Pero tiene el poder de mostrarnos algo extremadamente importante y útil.
Cuando lo ejecutas en el gráfico, tendrás el resultado que puede verse en la siguiente animación.

En esta animación, el punto de interés son los mensajes que aparecen. Olvida todo lo demás y céntrate en los mensajes. Observa que existe un patrón. Ahora, ¿y si añadimos un objeto que se superponga a los botones? ¿Qué ocurriría? Para responder esta pregunta, basta con mirar la siguiente animación.

De nuevo, presta atención a los mensajes que se muestran. Ahora compáralos y dime: ¿cuál es el patrón aquí? Si no lo notaste, procura mirar los mensajes y el código. Y piensa: ¿cuál es el patrón que existe aquí?
Si no lo entendiste o no lo percibiste, el patrón es el siguiente: cuando hacemos clic en un objeto del gráfico, MetaTrader 5 dispara primero un evento CHARTEVENT_MOUSE_MOVE y después un evento CHARTEVENT_OBJECT_CLICK. Saber esto es importante. ¿Por qué? Porque, al saberlo, podemos usar este mismo patrón a nuestro favor para resolver el problema cuando tenemos muchos objetos en el gráfico. Entonces, al contrario de lo que muchos podrían estar esperando, no intentaremos sustituir el mecanismo existente en MetaTrader 5. Lo usaremos a nuestro favor. Para hacerlo, este patrón, que puedes probar en tu plataforma, será manipulado por nuestras aplicaciones. De esta forma, cuando ejecutemos un estudio con el indicador de mouse, podremos ignorar los clics que se den en objetos que tienen eventos en nuestras aplicaciones.
Quizá estés pensando: ¿pero cómo haremos esto? Ya estamos bastante avanzados en el desarrollo del código. ¿Tendremos que volver al comienzo y abandonar por completo todo lo que ya se creó? No, estimado lector. Como vengo diciendo, el propósito de esta serie de artículos no es mostrar cómo desarrollar realmente un sistema de repetición/simulación, sino mostrar cómo un programador debe abordar los problemas que surgen. Mucha gente imagina que crear una aplicación es algo directo y sin problemas. Pero quienes realmente entran en este mundo saben que las cosas son un tanto tortuosas. Muchas veces necesitamos abordar problemas más o menos complejos.
Este problema de ZOrder puede, en muchos casos, ser algo bastante complicado. Tú, estimado lector, podrías simplemente imaginar que, en ningún momento del desarrollo, yo, como programador, tuve problemas o dificultades. Esto se debe a que, si simplemente corrigiera el código y lo publicara ya sin fallas, podrías tener la ilusión de que todo fue de maravilla y que, si tú, mi estimado lector y entusiasta, tuvieras dificultades para programar algo, sería porque eres un completo débil mental. Pero no es así. Todos pasamos por dificultades. La forma en que las superas es lo que te hace aprender y convertirte en un profesional de mejor o peor calidad.
Entonces, pensemos lo siguiente, y quiero que tú, estimado lector, intentes seguir el razonamiento. ¿Cómo podemos usar a nuestro favor esta información que nos dio este simple programita, de modo que tengamos que modificar lo mínimo posible el código ya diseñado e implementado? Esta es, realmente, una cuestión que haría que muchos terminaran desistiendo de intentar solucionarla, ya que parece una tarea extremadamente complicada. Pero, como dije: quiero mostrarte, estimado lector, que, si te detienes a pensar unos instantes, acabarás comprendiendo lo que debe hacerse.
Actualización del código
Antes de continuar leyendo este artículo, espero que realmente hayas intentado pensar en una solución para el problema. Porque, por increíble que parezca, es más simple de lo que muchos podrían imaginar.
Lo primero que haremos será eliminar los niveles de ZOrder. En realidad, no los eliminaremos, sino que los redefiniremos de manera que podamos contar con una asistencia completa de MetaTrader 5. De esta forma, el propio MetaTrader 5 podrá decirnos fácilmente quién recibió realmente un evento de clic. Con esto, vamos a modificar el archivo que puede verse a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss //Event to communication 53. }; 54. //+------------------------------------------------------------------+ 55. enum EnumPriority { //Priority list on objects 56. ePriorityNull = -1, 57. ePriorityDefault = 0 58. }; 59. //+------------------------------------------------------------------+
Defines.mqh
Observa que ahora tenemos menos tipos de enumeración en EnumPriority. Con esto, ahora tenemos el tipo estándar, o default, que es cero. Y tenemos otro, que es ePriorityNull. Este deberá usarse en todos los objetos que no deberán recibir ninguna interacción de ninguna manera. Pues bien, hecho este cambio, tendremos que hacer una nueva compilación de todo nuestro código. Esto sirve para garantizar que permanezca completamente estable. Al intentar compilar el indicador Chart Trade, se generan algunos errores provenientes de la clase C_ChartFloatingRAD. Pueden verse en la siguiente imagen.

En este punto, muchos ya empiezan a tirarse de los pelos, pensando: vamos, ¿qué hicimos? Las cosas ya no van a funcionar. ¿Pero será así realmente? Al ir al punto en el que se informa el primer error, es decir, a la línea 169 de la clase C_ChartFloatingRAD, veremos el motivo del error. Entonces, para corregirlo, bastará con cambiar el código original por el código que se ve en el fragmento siguiente.
164. //+------------------------------------------------------------------+ 165. template <typename T > 166. void CreateObjectEditable(eObjectsIDE arg, T value) 167. { 168. DeleteObjectEdit(); 169. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, ePriorityDefault); 170. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 171. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 172. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 173. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 174. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 175. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 176. ObjectSetInteger(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 177. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 178. ObjectSetString(m_Init.id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 179. ChartRedraw(); 180. } 181. //+------------------------------------------------------------------+
Fragmento de C_ChartFloatingRAD.mqh
Una vez realizado el cambio, intentamos compilar nuevamente el indicador Chart Trade. Y, con esto, el resultado se ve a continuación.

Es decir, éxito. Podemos pasar al siguiente. En el caso del indicador de mouse, no habrá ningún cambio, ya que no sufrió ninguna alteración en su ZOrder. Entonces, el siguiente paso sería compilar el indicador de posición. Pero, como el código de la clase C_ElementsTrade es donde habrá problemas con ZOrder y ese mismo código pasará por cambios adicionales, todavía no mostraré el código modificado. Vamos al paso para utilizar aquella información que obtuvimos en el tema anterior. Es decir, vamos a implementar la asistencia que MetaTrader 5 nos proporcionará. Así, al hacer clic en algo, podremos saber exactamente en qué hicimos clic. Sin embargo, cuando hagamos un estudio, el clic deberá ignorarse.
Para hacerlo, será necesario un cambio profundo y radical. Algo extremadamente complejo y difícil incluso de imaginar. Algo que solo un gran gurú podría imaginar y hacer. Entonces, observa lo difícil y complicada que es la solución. La puedes ver en el código siguiente.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. public : 011. //+------------------------------------------------------------------+ 012. struct st_Mouse 013. { 014. struct st00 015. { 016. short X_Adjusted, 017. Y_Adjusted, 018. X_Graphics, 019. Y_Graphics; 020. double Price; 021. datetime dt; 022. }Position; 023. uchar ButtonStatus; 024. bool ExecStudy; 025. string szObjNameClick; 026. }; 027. //+------------------------------------------------------------------+ 028. protected: 029. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 030. //+------------------------------------------------------------------+ 031. struct st_Terminal 032. { 033. ENUM_SYMBOL_CHART_MODE ChartMode; 034. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 035. long ID; 036. string szSymbol; 037. int Width, 038. Height, 039. nDigits, 040. SubWin, 041. HeightBar; 042. double PointPerTick, 043. ValuePerPoint, 044. VolumeMinimal, 045. AdjustToTrade; 046. }; 047. //+------------------------------------------------------------------+ 048. void CurrentSymbol(bool bUsingFull) 049. { 050. MqlDateTime mdt1; 051. string sz0, sz1; 052. datetime dt = macroGetDate(TimeCurrent(mdt1)); 053. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 054. 055. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 056. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 057. switch (eTS) 058. { 059. case DOL : 060. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 061. case IND : 062. case WIN : sz1 = "GJMQVZ"; break; 063. default : return; 064. } 065. sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS))); 066. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 067. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 068. } 069. //+------------------------------------------------------------------+ 070. inline void DecodeMousePosition(int xi, int yi) 071. { 072. int w = 0; 073. 074. xi = (xi > 0 ? xi : 0); 075. yi = (yi > 0 ? yi : 0); 076. ChartXYToTimePrice(m_Infos.ID, m_Mouse.Position.X_Graphics = (short)xi, m_Mouse.Position.Y_Graphics = (short)yi, w, m_Mouse.Position.dt, m_Mouse.Position.Price); 077. m_Mouse.Position.dt = AdjustTime(m_Mouse.Position.dt); 078. m_Mouse.Position.Price = AdjustPrice(m_Mouse.Position.Price); 079. ChartTimePriceToXY(m_Infos.ID, w, m_Mouse.Position.dt, m_Mouse.Position.Price, xi, yi); 080. yi -= (int)ChartGetInteger(m_Infos.ID, CHART_WINDOW_YDISTANCE, m_Infos.SubWin); 081. m_Mouse.Position.X_Adjusted = (short) xi; 082. m_Mouse.Position.Y_Adjusted = (short) yi; 083. } 084. //+------------------------------------------------------------------+ 085. private : 086. st_Terminal m_Infos; 087. st_Mouse m_Mouse; 088. struct mem 089. { 090. long Show_Descr, 091. Show_Date; 092. bool AccountLock; 093. }m_Mem; 094. //+------------------------------------------------------------------+ 095. inline void ChartChange(void) 096. { 097. int x, y, t; 098. 099. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 100. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 101. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 102. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 103. m_Infos.HeightBar = (int)(t - y) / 100; 104. } 105. //+------------------------------------------------------------------+ 106. public : 107. //+------------------------------------------------------------------+ 108. C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false) 109. { 110. m_Infos.ID = (id == 0 ? ChartID() : id); 111. m_Mem.AccountLock = false; 112. m_Infos.SubWin = (int) sub; 113. CurrentSymbol(bFull); 114. ZeroMemory(m_Mouse); 115. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 116. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 117. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 118. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 119. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 120. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 121. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 122. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 123. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 124. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 125. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 126. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 127. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 128. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 129. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 130. ChartChange(); 131. } 132. //+------------------------------------------------------------------+ 133. ~C_Terminal() 134. { 135. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 136. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 137. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 138. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 139. } 140. //+------------------------------------------------------------------+ 141. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 142. { 143. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 144. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 145. } 146. //+------------------------------------------------------------------+ 147. inline const st_Terminal GetInfoTerminal(void) const 148. { 149. return m_Infos; 150. } 151. //+------------------------------------------------------------------+ 152. inline const st_Mouse GetPositionsMouse(void) const 153. { 154. return m_Mouse; 155. } 156. //+------------------------------------------------------------------+ 157. const double AdjustPrice(const double arg) const 158. { 159. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 160. } 161. //+------------------------------------------------------------------+ 162. inline datetime AdjustTime(const datetime arg) 163. { 164. int nSeconds= PeriodSeconds(); 165. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 166. 167. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 168. } 169. //+------------------------------------------------------------------+ 170. inline double FinanceToPoints(const double Finance, const uint Leverage) 171. { 172. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 173. 174. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 175. }; 176. //+------------------------------------------------------------------+ 177. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 178. { 179. static string st_str = ""; 180. 181. switch (id) 182. { 183. case CHARTEVENT_CHART_CHANGE: 184. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 185. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 186. ChartChange(); 187. break; 188. case CHARTEVENT_MOUSE_MOVE: 189. DecodeMousePosition((int)lparam, (int)dparam); 190. break; 191. case CHARTEVENT_OBJECT_CLICK: 192. m_Mouse.szObjNameClick = sparam; 193. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 194. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 195. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 196. break; 197. case CHARTEVENT_OBJECT_CREATE: 198. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 199. st_str = sparam; 200. break; 201. } 202. } 203. //+------------------------------------------------------------------+ 204. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const EnumPriority zOrder = ePriorityNull) const 205. { 206. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 207. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 208. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 209. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 210. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 211. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 212. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 213. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 214. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 215. } 216. //+------------------------------------------------------------------+ 217. bool IndicatorCheckPass(const string szShortName) 218. { 219. string szTmp = szShortName + "_TMP"; 220. 221. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 222. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szShortName)) < 0 ? 0 : m_Infos.SubWin); 223. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 224. { 225. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 226. Print("Only one instance is allowed..."); 227. SetUserError(C_Terminal::ERR_NoMoreInstance); 228. 229. return false; 230. } 231. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 232. 233. return true; 234. } 235. //+------------------------------------------------------------------+ 236. };
C_Terminal.mqh
La solución está implementada en este código anterior. ¿Puedes percibir dónde? Si no estás siguiendo esta secuencia o no estás estudiando este material que estoy poniendo a disposición, con toda seguridad no lograrás ver la solución. De hecho, coloqué, deliberadamente, el código completo de la clase C_Terminal para que tú, estimado lector, quedaras completamente perdido y sin entender realmente dónde se implementó la solución.
Bromas aparte. En realidad, la solución consiste en usar MetaTrader 5 a nuestro favor. Pero, para ello, es necesario que usemos algún tipo de artificio. Este artificio podría implementarse aquí, en la clase C_Terminal, o en el lugar donde el código implementado aquí realmente se usaría. Quizá estés pensando: vamos, ¿la implementación no debería ocurrir en la clase C_Mouse, ya que ella es la responsable de informar eventos de clic a nuestras aplicaciones? Sí, en efecto. Pero, si intentamos hacerlo, tendremos un pequeño problema. Observa nuevamente las animaciones. Nota que el evento CHARTEVENT_MOUSE_MOVE viene antes del evento CHARTEVENT_OBJECT_CLICK. Y, cuando analizamos si hubo un clic en un objeto durante un estudio, tendríamos cierta pérdida en la sincronización de los eventos o, mejor dicho, cuando hicieras clic en un objeto, la clase C_Mouse aún seguiría apuntando al objeto que recibió el clic anterior. Tal vez esto parezca extraño. Pero, cuando veamos la clase C_ElementsTrade, quedará más claro.
Entonces, nuevamente, la solución fue añadir una nueva variable. Está en la línea 25 y se utiliza solo en la línea 192. Como dije, podríamos colocar la solución en el mismo código que la usaría. Pero, como quiero dejar todo muy bien organizado, la coloco en este lugar. Es decir, en la clase C_Terminal. Con esto, ahora podemos ver el código del indicador de posición.
Actualización del indicador de posición
Una vez realizados los cambios en los demás códigos mostrados anteriormente, podemos ver qué fue necesario cambiar en el indicador de posición. Para empezar, se decidió que el operador o usuario ya no podrá modificar los colores usados en las líneas. Quedarán estandarizados dentro del indicador. Así, el código del archivo principal cambió y puede verse a continuación.
//+------------------------------------------------------------------+ 01. #property copyright "Daniel Jose" 02. #property icon "/Images/Market Replay/Icons/Positions.ico" 03. #property description "Indicator for tracking an open position on the server." 04. #property description "This should preferably be used together with an Expert Advisor." 05. #property description "For more details see the same article." 06. #property version "1.122" 07. #property link "https://www.mql5.com/pt/articles/13274" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #define def_ShortName "Position View" 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 14. #include <Market Replay\Defines.mqh> 15. //+------------------------------------------------------------------+ 16. input ulong user00 = 0; //For Expert Advisor use 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition *Positions = NULL; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 23. Positions = new C_IndicatorPosition(); 24. if (!Positions.CheckCatch(user00)) 25. { 26. ChartIndicatorDelete(0, 0, def_ShortName); 27. return INIT_FAILED; 28. } 29. 30. return INIT_SUCCEEDED; 31. } 32. //+------------------------------------------------------------------+ 33. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 34. { 35. return rates_total; 36. } 37. //+------------------------------------------------------------------+ 38. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 39. { 40. (*Positions).DispatchMessage(id, lparam, dparam, sparam); 41. }; 42. //+------------------------------------------------------------------+ 43. void OnDeinit(const int reason) 44. { 45. delete Positions; 46. } 47. //+------------------------------------------------------------------+
Indicador de posición:
Debido a esto, el código de la clase C_IndicatorPosition cambió ligeramente. Entonces, el nuevo código puede verse íntegramente a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_ElementsTrade.mqh" 05. //+------------------------------------------------------------------+ 06. class C_IndicatorPosition 07. { 08. private : 09. struct st00 10. { 11. ulong ticket; 12. string szShortName; 13. }m_Infos; 14. C_ElementsTrade *Open, *Stop, *Take; 15. //+------------------------------------------------------------------+ 16. public : 17. //+------------------------------------------------------------------+ 18. C_IndicatorPosition() 19. { 20. ZeroMemory(m_Infos); 21. Open = Take = Stop = NULL; 22. } 23. //+------------------------------------------------------------------+ 24. ~C_IndicatorPosition() 25. { 26. delete Open; 27. delete Take; 28. delete Stop; 29. } 30. //+------------------------------------------------------------------+ 31. bool CheckCatch(ulong ticket) 32. { 33. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 34. if (!PositionSelectByTicket(m_Infos.ticket)) return false; 35. if (ObjectFind(0, m_Infos.szShortName) >= 0) 36. { 37. m_Infos.ticket = 0; 38. return false; 39. } 40. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 41. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 42. 43. return true; 44. } 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. //+------------------------------------------------------------------+ 81. }; 82. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
Con todo esto, llega el momento de ver finalmente el nuevo código de la clase C_ElementsTrade. Y este puede verse a continuación. Observa que el código es bastante diferente del que vimos en el artículo anterior. Sin embargo, contiene los mismos elementos y funciona de la misma manera.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #define def_NameHLine m_Info.szPrefixName + "#HLINE" 005. #define def_NameBtnClose m_Info.szPrefixName + "#CLOSE" 006. //+------------------------------------------------------------------+ 007. #define def_PathBtns "Images\\Market Replay\\Orders\\" 008. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 009. #resource "\\" + def_Btn_Close; 010. //+------------------------------------------------------------------+ 011. #include "..\Auxiliar\C_Mouse.mqh" 012. //+------------------------------------------------------------------+ 013. class C_ElementsTrade : private C_Mouse 014. { 015. private : 016. //+------------------------------------------------------------------+ 017. struct st00 018. { 019. ulong ticket; 020. string szPrefixName; 021. EnumEvents ev; 022. double price; 023. bool bClick; 024. }m_Info; 025. //+------------------------------------------------------------------+ 026. void UpdateViewPort(void) 027. { 028. int x, y; 029. 030. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 031. ObjectSetDouble(0, def_NameHLine, OBJPROP_PRICE, m_Info.price); 032. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, 130); 033. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 034. } 035. //+------------------------------------------------------------------+ 036. public : 037. //+------------------------------------------------------------------+ 038. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 039. :C_Mouse(0, "") 040. { 041. string szObj; 042. 043. ZeroMemory(m_Info); 044. m_Info.szPrefixName = StringFormat("%I64u@%d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 045. CreateObjectGraphics(szObj = def_NameHLine, OBJ_HLINE, _color, ePrio); 046. ObjectSetInteger(0, szObj, OBJPROP_WIDTH, 2); 047. ObjectSetString(0, szObj, OBJPROP_TEXT, szDescr); 048. ObjectSetString(0, szObj, OBJPROP_TOOLTIP, szDescr); 049. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 050. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 051. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 052. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 053. } 054. //+------------------------------------------------------------------+ 055. ~C_ElementsTrade() 056. { 057. if (m_Info.szPrefixName != "") 058. ObjectsDeleteAll(0, m_Info.szPrefixName); 059. } 060. //+------------------------------------------------------------------+ 061. inline void UpdatePrice(const double price) 062. { 063. m_Info.price = price; 064. UpdateViewPort(); 065. } 066. //+------------------------------------------------------------------+ 067. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 068. { 069. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 070. switch (id) 071. { 072. case CHARTEVENT_OBJECT_CLICK: 073. if ((m_Info.bClick) && (GetPositionsMouse().szObjNameClick == def_NameBtnClose)) switch (m_Info.ev) 074. { 075. case evMsgClosePositionEA: 076. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 077. break; 078. case evMsgCloseTakeProfit: 079. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 080. break; 081. case evMsgCloseStopLoss: 082. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 083. break; 084. } 085. m_Info.bClick = false; 086. break; 087. case CHARTEVENT_MOUSE_MOVE: 088. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 089. break; 090. case CHARTEVENT_CHART_CHANGE: 091. UpdateViewPort(); 092. break; 093. } 094. } 095. //+------------------------------------------------------------------+ 096. }; 097. //+------------------------------------------------------------------+ 098. #undef def_Btn_Close 099. #undef def_PathBtns 100. //+------------------------------------------------------------------+ 101. #undef def_NameBtnClose 102. #undef def_NameHLine 103. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Varios de los elementos que se habían colocado fueron eliminados, como puedes notar claramente al comparar ambos códigos. Sin embargo, observa que, en la línea 23, surgió una nueva variable. Se usa en el procedimiento DispatchMessage. Aparte de eso, nada más cambió. Pero veamos cómo funcionará ahora el procedimiento DispatchMessage. Este funcionamiento se debe a que, a partir de este momento, ese procedimiento contará con la ayuda de MetaTrader 5.
Observa, en la línea 67, donde se inicia el procedimiento DispatchMessage, que no tuvimos cambios drásticos. Solo adaptamos el código que existía anteriormente. Nota que, en el evento CHARTEVENT_MOUSE_MOVE, en la línea 87, lo único que hacemos es verificar, junto al indicador de mouse, si un clic es válido o no. Lo hacemos de una manera bastante simple y directa. Así, aquella variable vista en la línea 23 recibirá un valor verdadero cuando tengamos un clic válido, o falso, si el indicador de mouse realiza algún estudio. Esta es la parte que quedará a cargo de nuestra aplicación. La parte en la que MetaTrader 5 nos ayudará puede verse en el evento CHARTEVENT_OBJECT_CLICK, presente en la línea 72.
Observa una cosa en este código, en el evento CHARTEVENT_OBJECT_CLICK. Es exactamente el mismo que se veía en el artículo anterior. Solo que estaba en el evento CHARTEVENT_MOUSE_MOVE. ¿Pero tendremos problemas aquí? En realidad, no. Porque, antes del evento de clic, tendremos un evento de movimiento. Y, cuando ocurra el clic, habremos verificado si es válido o no. Solo habrá una ejecución, como se veía en el artículo anterior, si, y solo si, tenemos un clic válido y el nombre del objeto corresponde al nombre que tratamos en este momento. Es decir, el objeto que representa el botón de cerrar. Todo lo demás ya fue explicado en detalle, por lo que no necesita explicaciones adicionales.
Muy bien, pero, antes de finalizar este artículo, haremos un pequeño cambio en el sistema. Esto se debe a que no tiene mucho sentido tener una línea horizontal que vaya de extremo a extremo del gráfico y tenga, en algún punto, un botón de cerrar. Podemos mejorar esto de forma que tengamos una línea que termine en el botón de cerrar, lo que tiene algo más de sentido. Sin embargo, aquí prepararemos el terreno para otra modificación que se hará en el futuro. No obstante, para dejar las cosas un poco más cerca de lo que necesitamos, tendremos que cambiar la clase. Como no tengo plena certeza de cómo se harán las cosas después, no te apegues demasiado a lo que verás. Pero procura entender cómo funciona todo. Pues quizá esto pueda llegar a serte útil en algún momento futuro.
Preparación para los siguientes pasos
El siguiente paso que implementaremos puede parecer un poco tonto e incluso innecesario. Sin embargo, es importante, dado lo que tendremos que hacer a continuación. Muy bien, ¿cuál es la idea en este momento? La idea es crear un medio para verificar visualmente y de forma bastante clara qué línea estamos manipulando. ¿Y por qué esto es importante? El motivo es que podemos usar un sistema en el que se haga clic una vez para tomarla y otra vez para soltarla. Y, sin saber qué línea estamos manipulando, resulta muy confuso hacerlo correctamente. Así, necesitamos un medio simple y eficiente para proporcionar dicha indicación.
Muchos pensarían en hacer lo siguiente: ya que tenemos una posición abierta, podemos simplemente crear un sistema de conmutación interno. Y, una vez más, estoy de acuerdo contigo, estimado lector. Implementar un sistema de conmutación interno es lo más prudente. Sin embargo, vamos a generalizar un poco más las cosas. Piensa en lo siguiente: si estás en una cuenta de tipo HEDGING, un sistema de conmutación no será suficientemente adecuado, ya que podremos tener dos o más indicadores de posición en el mismo gráfico. Esto hace bastante complicado implementar dicha conmutación, si pensamos en la posibilidad de que hagas clic en la línea de una de las posiciones y después hagas clic en la línea de otra posición. Es decir, tendremos un impasse si esto ocurre.
Además, tenemos otro problema que, aunque por ahora no nos afecta, nos causará grandes problemas en el futuro, si no implementamos las cosas de una manera más genérica. Estoy hablando de las órdenes pendientes. Estas usarán un sistema muy similar al que vamos a desarrollar aquí y ahora. Entonces, una conmutación interna para hacer los cambios correspondientes no será suficiente. Sin embargo, y afortunadamente, tenemos una solución igualmente simple que cubrirá por completo todos los escenarios posibles. Esta implica el envío de mensajes entre los indicadores. Pero puedes imaginar que esto es algo extremadamente complicado y difícil de implementar. ¿Pero será así realmente? Pues bien, antes de ver los cambios necesarios, veamos qué cambió en el archivo de encabezado Defines.mqh. Este puede verse a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaskTimeService 0xFED00000 16. #define def_IndicatorTimeFrame (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96)))) 17. #define def_IndexTimeFrame 4 18. //+------------------------------------------------------------------+ 19. union uCast_Double 20. { 21. double dValue; 22. long _long; // 1 Information 23. datetime _datetime; // 1 Information 24. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 25. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 26. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 27. }; 28. //+------------------------------------------------------------------+ 29. enum EnumEvents { 30. evTicTac, //Event of tic-tac 31. evHideMouse, //Hide mouse price line 32. evShowMouse, //Show mouse price line 33. evHideBarTime, //Hide bar time 34. evShowBarTime, //Show bar time 35. evHideDailyVar, //Hide daily variation 36. evShowDailyVar, //Show daily variation 37. evHidePriceVar, //Hide instantaneous variation 38. evShowPriceVar, //Show instantaneous variation 39. evCtrlReplayInit, //Initialize replay control 40. evChartTradeBuy, //Market buy event 41. evChartTradeSell, //Market sales event 42. evChartTradeCloseAll, //Event to close positions 43. evChartTrade_At_EA, //Event to communication 44. evEA_At_ChartTrade, //Event to communication 45. evChatWriteSocket, //Event to Mini Chat 46. evChatReadSocket, //Event To Mini Chat 47. evUpdate_Position, //Event to communication 48. evMsgClosePositionEA, //Event to communication 49. evMsgCloseTakeProfit, //Event to communication 50. evMsgCloseStopLoss, //Event to communication 51. evMsgNewTakeProfit, //Event to communication 52. evMsgNewStopLoss, //Event to communication 53. evMsgSetFocus //Event to communication 54. }; 55. //+------------------------------------------------------------------+ 56. enum EnumPriority { //Priority list on objects 57. ePriorityNull = -1, 58. ePriorityDefault = 0 59. }; 60. //+------------------------------------------------------------------+
Defines.mqh
Observa que, en la línea 53, tenemos un nuevo evento definido. Este es el evento que usaremos para permitir realizar la tarea que queremos implementar. Hecho esto, podemos pasar al código de la clase C_ElementsTrade para ver las modificaciones que fueron necesarias para generar el resultado esperado. Todo el código de la clase puede verse 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. //+------------------------------------------------------------------+ 007. #define macro_LineInFocus(A) ObjectSetInteger(0, def_NameHLine, OBJPROP_YSIZE, m_Info.weight = (A ? 7 : 3)); 008. //+------------------------------------------------------------------+ 009. #define def_PathBtns "Images\\Market Replay\\Orders\\" 010. #define def_Btn_Close def_PathBtns + "Btn_Close.bmp" 011. #resource "\\" + def_Btn_Close; 012. //+------------------------------------------------------------------+ 013. #include "..\Auxiliar\C_Mouse.mqh" 014. //+------------------------------------------------------------------+ 015. class C_ElementsTrade : private C_Mouse 016. { 017. private : 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szPrefixName; 023. EnumEvents ev; 024. double price; 025. bool bClick; 026. char weight; 027. }m_Info; 028. //+------------------------------------------------------------------+ 029. void UpdateViewPort(void) 030. { 031. int x, y; 032. 033. ChartTimePriceToXY(0, 0, 0, m_Info.price, x, y); 034. x = (m_Info.ev == evMsgClosePositionEA ? 150 : (m_Info.ev == evMsgCloseTakeProfit ? 200 : 250)); 035. ObjectSetInteger(0, def_NameHLine, OBJPROP_XDISTANCE, x); 036. ObjectSetInteger(0, def_NameHLine, OBJPROP_YDISTANCE, y - (m_Info.weight / 2)); 037. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_XDISTANCE, x); 038. ObjectSetInteger(0, def_NameBtnClose, OBJPROP_YDISTANCE, y); 039. } 040. //+------------------------------------------------------------------+ 041. public : 042. //+------------------------------------------------------------------+ 043. C_ElementsTrade(const ulong ticket, const EnumEvents ev, color _color, EnumPriority ePrio, string szDescr = "\n") 044. :C_Mouse(0, "") 045. { 046. string szObj; 047. 048. ZeroMemory(m_Info); 049. m_Info.szPrefixName = StringFormat("%I64u@%03d", m_Info.ticket = ticket, (int)(m_Info.ev = ev)); 050. CreateObjectGraphics(szObj = def_NameHLine, OBJ_RECTANGLE_LABEL, _color, (EnumPriority)(ePriorityDefault)); 051. macro_LineInFocus(false); 052. ObjectSetInteger(0, szObj, OBJPROP_BGCOLOR, _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, szDescr); 057. ObjectSetInteger(0, szObj, OBJPROP_SELECTABLE, ePrio != ePriorityNull); 058. CreateObjectGraphics(szObj = def_NameBtnClose, OBJ_BITMAP_LABEL, clrNONE, (EnumPriority)(ePriorityDefault)); 059. ObjectSetString(0, szObj, OBJPROP_BMPFILE, 0, "::" + def_Btn_Close); 060. ObjectSetInteger(0, szObj, OBJPROP_ANCHOR, ANCHOR_CENTER); 061. } 062. //+------------------------------------------------------------------+ 063. ~C_ElementsTrade() 064. { 065. ObjectsDeleteAll(0, m_Info.szPrefixName); 066. } 067. //+------------------------------------------------------------------+ 068. inline void UpdatePrice(const double price) 069. { 070. m_Info.price = price; 071. UpdateViewPort(); 072. } 073. //+------------------------------------------------------------------+ 074. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 075. { 076. string sz0; 077. 078. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 079. switch (id) 080. { 081. case CHARTEVENT_CUSTOM + evMsgSetFocus: 082. macro_LineInFocus((m_Info.ticket == (ulong)(lparam)) && ((EnumEvents)(dparam) == m_Info.ev)); 083. break; 084. case CHARTEVENT_OBJECT_CLICK: 085. sz0 = GetPositionsMouse().szObjNameClick; 086. if (m_Info.bClick) switch (m_Info.ev) 087. { 088. case evMsgClosePositionEA: 089. if (sz0 == def_NameBtnClose) 090. EventChartCustom(0, evMsgClosePositionEA, m_Info.ticket, 0, ""); 091. else if (sz0 == def_NameHLine) 092. EventChartCustom(0, evMsgSetFocus, 0, 0, ""); 093. break; 094. case evMsgCloseTakeProfit: 095. if (sz0 == def_NameBtnClose) 096. EventChartCustom(0, evMsgCloseTakeProfit, m_Info.ticket, PositionGetDouble(POSITION_SL), PositionGetString(POSITION_SYMBOL)); 097. else if (sz0 == def_NameHLine) 098. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseTakeProfit, ""); 099. break; 100. case evMsgCloseStopLoss: 101. if (sz0 == def_NameBtnClose) 102. EventChartCustom(0, evMsgCloseStopLoss, m_Info.ticket, PositionGetDouble(POSITION_TP), PositionGetString(POSITION_SYMBOL)); 103. else if (sz0 == def_NameHLine) 104. EventChartCustom(0, evMsgSetFocus, m_Info.ticket, evMsgCloseStopLoss, ""); 105. break; 106. } 107. m_Info.bClick = false; 108. break; 109. case CHARTEVENT_MOUSE_MOVE: 110. m_Info.bClick = (CheckClick(C_Mouse::eClickLeft) ? true : m_Info.bClick); 111. break; 112. case CHARTEVENT_CHART_CHANGE: 113. UpdateViewPort(); 114. break; 115. } 116. } 117. //+------------------------------------------------------------------+ 118. }; 119. //+------------------------------------------------------------------+ 120. #undef macro_LineInFocus 121. //+------------------------------------------------------------------+ 122. #undef def_Btn_Close 123. #undef def_PathBtns 124. //+------------------------------------------------------------------+ 125. #undef def_NameBtnClose 126. #undef def_NameHLine 127. //+------------------------------------------------------------------+
C_ElementsTrade.mqh
Y el resultado esperado se ve en la siguiente animación.

Muestro este resultado antes de explicar el código para que tú, estimado lector, prestes atención a lo que se explicará. Pues este mecanismo podría sufrir algunos cambios después. Sin embargo, si en algún momento necesitas hacer algo similar, debes saber que es posible hacer diversas cosas. Pero es necesario que entiendas por qué funcionan. Dicho esto, creo que ya tienes curiosidad por entender cómo pudo ocurrir la animación anterior sin crear un mecanismo de conmutación convencional. Entonces, entendamos cómo y por qué ocurre esto.
En primer lugar, observa que, en la línea 7, definí una macro para destacar, de forma bastante evidente, la línea que reciba el foco. En la línea 26, definí una nueva variable que almacenará el valor del grosor que tendrá la línea definida en esta clase. En la línea 36, posicionaremos la línea creada aquí en una posición en la que quede centrada. Para que esto ocurra correctamente, el grosor de la línea deberá definirse con un valor impar. Si defines un valor par, como dos o cuatro, la línea quedará levemente desplazada de la posición ideal. Observa otra cosa: en la línea 34, definimos dónde quedará cada uno de los botones. Esto evita que queden demasiado cerca o lleguen a superponerse. Esto dificultaría el acceso correcto a ellos. Estos valores no son definitivos, pero ya cumplen su propósito.
Hasta aquí, todo bien. Ahora, observa un cambio en el constructor de la clase. Antes, se usaba un objeto HLINE para indicar la línea de precio. Sin embargo, ahora usaremos un objeto OBJ_RECTANGLE_LABEL. Este cambio nos permite establecer un límite máximo para el tamaño de las líneas horizontales. Así, no irán de extremo a extremo en el gráfico. Y el motivo de usar un OBJ_RECTANGLE_LABEL y no un OBJ_TREND es justamente que las líneas de tendencia necesitan un anclaje en el tiempo. Algo que, en algunos momentos, resulta complicado ajustar correctamente. En cambio, un OBJ_RECTANGLE_LABEL usa coordenadas cartesianas. Esto resulta bastante práctico para nuestro propósito.
Prácticamente todo el código se mantuvo, a pesar de la diferencia en el objeto que será nuestra línea horizontal. Ahora bien, el cambio real ocurrió en el procedimiento de la línea 74, es decir, en DispatchMessage. En este punto, las cosas pueden parecer un poco confusas al principio. Pero no hay motivo para entrar en pánico. Solo presta atención y todo te quedará claro.
Observa que, en la línea 81, tenemos el tratamiento del mensaje que creamos en el archivo Defines.mqh. Sin embargo, este mensaje solo existe aquí porque estamos generalizando las cosas al máximo. Observa que el tratamiento del mensaje es muy simple. Si una clase C_ElementsTrade recibe este mensaje, evMsgSetFocus, verificará si el valor lparam es el mismo que el ticket observado por la clase. Además, dparam debe ser uno de los valores de evento. Si esto no ocurre, la línea se reconfigurará con un grosor más fino. Si la condición se cumple, la línea se configurará con un grosor mayor. Así de simple.
Pero espera un poco. ¿Cómo sabrá la clase cuándo la línea será más gruesa o más fina? ¿Esto no tendría que definirse en algún lugar? Sí, estimado lector. Quienes realmente realizan la tarea de indicarlo son los eventos presentes en las líneas 92, 98 y 104. Cada uno de ellos solo se dispara cuando hay un clic válido en el indicador de mouse. Observa que la condición que permite disparar estos eventos es justamente el nombre del objeto en el que se hizo clic. Hum. Pero ¿por qué hacerlo así? ¿No sería más rápido, simple y práctico usar la macro en lugar de disparar estos eventos? Nuevamente, tienes razón, estimado lector.
Sin embargo, esto nos traería justamente el problema que aparece cuando hay más de un indicador de posición en el gráfico. O cuando tengamos los indicadores de órdenes pendientes. En este caso, un indicador no podría informar a otro que perdió el foco y que ya no recibirá atención. Pero, al usar justamente este mecanismo de paso de mensajes, permitimos que todos los indicadores funcionen en perfecta armonía y sepan exactamente qué debe hacerse y qué no. Aunque este mecanismo no sea tan eficiente en términos de ejecución, resulta bastante práctico y seguro. Además, es considerablemente más simple que un mecanismo más eficiente en términos de ejecución.
Consideraciones finales
Puedes verificar personalmente cómo funciona este mecanismo usando una cuenta demo. Te sugiero usar la cuenta demo de MetaQuotes, para que puedas ver el mecanismo funcionando cuando haya más de una posición abierta. Créeme, será algo bastante interesante de visualizar y estudiar.
Si llegas a hacer esto, seguramente notarás que existe cierta dificultad para seleccionar las líneas, incluso aunque tengan un grosor superior a 1 píxel. Seleccionarlas puede ser un poco complicado en ciertos momentos. Para facilitar aún más las cosas, en el próximo artículo las mejoraremos un poco más. Además, por supuesto, empezaremos a trabajar directamente en el gráfico, sin necesitar el sistema de terminal, para crear las líneas de stop loss y take profit.
En el archivo adjunto encontrarás los ejecutables para poder experimentar con lo visto hasta ahora. Esto es para quienes no sepan cómo compilar todo el sistema.
| 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 | Responsable de la indicación de órdenes de mercado, permitiendo su interacción y control |
| Indicators\Position View.mq5 | Responsable de la indicación de posiciones de mercado, permitiendo su interacción y control |
| 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/13274
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 (III)
De novato a experto: Supervisión y registro del backend de un EA con MQL5
Particularidades del trabajo con números del tipo double en MQL4
Del básico al intermedio: Colas, listas y árboles (II)
- 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