
Simulación de mercado (Parte 02): Orden cruzada (II)
Introducción
En el artículo anterior, Simulación de mercado (Parte 01): Orden cruzada (I), mostré una alternativa para un problema bastante común, sobre todo para quienes operan contratos futuros. Aunque no haya mostrado la solución final, ya que todo el artículo se centró únicamente en el indicador Chart Trade, el contenido visto en ese artículo es de suma importancia, pues nos permite hacer que la clase C_Terminal pueda proporcionarnos un nombre adecuado para operar dichos contratos futuros.
El problema, sin embargo, todavía persiste. No por Chart Trade o por el Asesor Experto, sino por el sistema de órdenes pendientes. Aunque aún no hayamos comenzado a hablar sobre él aquí en los artículos, necesitamos prepararnos de algún modo. Esto se debe a que parte de la información que se utiliza en él proviene de Chart Trade, y todo el proceso de comunicación con el servidor se realizará por medio del Asesor Experto. Es decir, tenemos un triángulo, en el que cada uno de los vértices representa una de las aplicaciones que deben desarrollarse. Sin embargo, la arista que se comunica con el servidor parte del vértice del Asesor Experto.
Por esta razón, es perfectamente razonable y aceptable pensar en hacer que el Asesor Experto controle la elección del contrato futuro, para que podamos decidir si operaremos con minicontratos o con el contrato completo en un momento dado. Sin embargo, no podemos colocar la elección en todas las aplicaciones. Es decir, la elección del tipo de contrato que se va a operar debe hacerse en un solo lugar, para evitar ambigüedades en las elecciones o en la información, lo que, por consecuencia, haría que el sistema resultara muy confuso para el usuario, en caso de que la elección del contrato pudiera realizarse en más de un lugar.
Pues bien, al elegir Chart Trade, como se mostró en el artículo anterior, tenemos una forma de controlar las cosas. No obstante, ya que el Asesor Experto es el único que realmente se comunica directamente con el servidor para enviar órdenes, podemos pensar en hacer la elección en él. Sin embargo, esto nos trae otro tipo de problema, y mostrar una de las formas de solucionar este problema será el tema de este artículo. No obstante, antes de que pienses que aquí crearemos una solución definitiva, quiero resaltar que lo que se verá aquí es una propuesta de solución. La solución en sí se verá en otro momento, ya que aún no he decidido cómo se implementará realmente.
Entender los problemas
Como la solución propuesta aquí utilizará el Asesor Experto para seleccionar el tipo de contrato, necesitamos modificar algunas cosas en Chart Trade. Esto se debe a que existe un problema al delegar la elección del tipo de contrato al Asesor Experto. El problema está en la información de Chart Trade. Esta información puede verse en la imagen a continuación.
Observa que estoy destacando el nombre del símbolo o contrato. Uno de los problemas es precisamente este, ya que el nombre que se muestra allí sirve para calcular los valores que deben mostrarse en la parte financiera de Chart Trade. Entonces, el problema se agrava, ya que Chart Trade envía al Asesor Experto los valores ya convertidos en términos de ticks, y el Asesor Experto queda responsable únicamente de enviar las órdenes a precio de mercado.
Pues bien, ahora piensa en el problema: si el Asesor Experto cambia el tipo de contrato a minicontrato o al contrato completo, Chart Trade deberá replicar esa información para que el usuario o operador no cometa un error. Replicar esa información no es ni de lejos la parte complicada. Podemos hacerlo fácilmente, haciendo que Chart Trade se conecte al Asesor Experto. De este modo, a diferencia de lo que se hace actualmente, cuando el usuario debe colocar Chart Trade en el gráfico, esta acción sería realizada por el Asesor Experto. Así, el usuario no necesitaría colocar Chart Trade en el gráfico, sino el Asesor Experto. Y este, posteriormente, lanzaría el indicador Chart Trade en el gráfico, ya ajustando la información del contrato.
Esa sería la solución más fácil y obvia. Sin embargo, esto haría necesario colocar el indicador Chart Trade en todos los Asesores Expertos creados. Esto realmente no es viable. No por el hecho de si se puede o no hacer, sino porque cualquier mejora realizada en Chart Trade nos obligaría a recompilar todos los Asesores Expertos nuevamente. Por esta razón, se decidió que Chart Trade sería algo separado del Asesor Experto.
Muy bien, entonces vamos a dejar las cosas separadas. Pero ¿cómo solucionar el problema en este caso? La solución para este tipo de situación ya viene siendo explicada: el intercambio de mensajes entre el Asesor Experto y el indicador Chart Trade. Sencillo, ¿verdad? Bien, en realidad, no es tan simple así. Por eso decidí explicar cómo hacer esto. Por eso escribí este artículo.
El primer problema está relacionado con el orden en que las aplicaciones se colocan en el gráfico. ¿Pero cómo así? Bien, podríamos forzar al usuario u operador a colocar el Asesor Experto antes que Chart Trade. De esa forma, cuando Chart Trade ingresara al gráfico, preguntaría al Asesor Experto qué contrato debería utilizar. Genial, pero tenemos otro problema en este mismo punto. Podemos forzar al usuario a hacer las cosas, pero no podemos forzar a MetaTrader 5 a hacer eso. Tal vez no hayas pensado en el siguiente problema, que es el segundo: cuando MetaTrader 5 necesita cambiar el marco temporal del gráfico, lo destruye, lo abre nuevamente y, en seguida, vuelve a colocar lo que había en el gráfico. Esto se aplica a indicadores y Asesores Expertos. Pues bien, aquí está el problema: ¿quién entra primero, Chart Trade o el Asesor Experto? Si el Asesor Experto entra primero, perfecto, porque la solución del primer problema resuelve el segundo. Pero ¿y si Chart Trade entra primero?
Entonces, como puedes ver, la situación es un poco más complicada de lo que parece. Además, tenemos un tercer problema: si el usuario u operador cambia el parámetro del contrato en el Asesor Experto o cualquier otro parámetro existente, Chart Trade no será informado adecuadamente, ya que todo el planteamiento cubría solo la solución a los problemas anteriores. Esto se debe a que, en ese caso, el Asesor Experto sería removido del gráfico y, en seguida, colocado nuevamente por MetaTrader 5, sin contar que necesitamos hacer que Chart Trade tenga conciencia de la presencia del Asesor Experto. De lo contrario, tendremos otros problemas.
Afortunadamente, en el caso de la conciencia, podemos dejar ese aspecto de lado. Esto se debe a que todo el sistema ha sido pensado de forma que proporcione información al usuario sobre lo que está ocurriendo. La cuestión de la conciencia será un problema realmente serio en otro momento. Pero, por ahora, y aquí en la relación entre Chart Trade y el Asesor Experto, este no es un problema tan grave. Así que, para que no sea necesario un cambio muy radical en el código, vamos a hacer algunas concesiones. Para separar las cosas, veremos eso en un nuevo tema.
Hacer algunas concesiones
Los cambios que se harán aquí permanecerán tal como se hicieron. Es decir, el código no retrocederá, sino que avanzará. Sin embargo, dichas concesiones abrirán la posibilidad de hacer uso de ciertas cosas que antes no eran posibles. Por lo tanto, utiliza estas nuevas posibilidades con atención y cautela.
El primer cambio puede verse en el archivo C_Terminal.mqh. A continuación, puedes ver el nuevo código en su totalidad.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. protected: 011. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 012. //+------------------------------------------------------------------+ 013. struct st_Terminal 014. { 015. ENUM_SYMBOL_CHART_MODE ChartMode; 016. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 017. long ID; 018. string szSymbol; 019. int Width, 020. Height, 021. nDigits, 022. SubWin, 023. HeightBar; 024. double PointPerTick, 025. ValuePerPoint, 026. VolumeMinimal, 027. AdjustToTrade; 028. }; 029. //+------------------------------------------------------------------+ 030. void CurrentSymbol(bool bUsingFull) 031. { 032. MqlDateTime mdt1; 033. string sz0, sz1; 034. datetime dt = macroGetDate(TimeCurrent(mdt1)); 035. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 036. 037. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 038. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 039. switch (eTS) 040. { 041. case DOL : 042. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 043. case IND : 044. case WIN : sz1 = "GJMQVZ"; break; 045. default : return; 046. } 047. sz0 = EnumToString((eTypeSymbol)(((eTS & 1) == 1) ? (bUsingFull ? eTS : eTS - 1) : (bUsingFull ? eTS + 1: eTS))); 048. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 049. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 050. } 051. //+------------------------------------------------------------------+ 052. private : 053. st_Terminal m_Infos; 054. struct mem 055. { 056. long Show_Descr, 057. Show_Date; 058. bool AccountLock; 059. }m_Mem; 060. //+------------------------------------------------------------------+ 061. inline void ChartChange(void) 062. { 063. int x, y, t; 064. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 065. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 066. ChartTimePriceToXY(m_Infos.ID, 0, 0, 0, x, t); 067. ChartTimePriceToXY(m_Infos.ID, 0, 0, m_Infos.PointPerTick * 100, x, y); 068. m_Infos.HeightBar = (int)((t - y) / 100); 069. } 070. //+------------------------------------------------------------------+ 071. public : 072. //+------------------------------------------------------------------+ 073. C_Terminal(const long id = 0, const uchar sub = 0) 074. { 075. m_Infos.ID = (id == 0 ? ChartID() : id); 076. m_Mem.AccountLock = false; 077. m_Infos.SubWin = (int) sub; 078. CurrentSymbol(false); 079. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 080. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 081. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 082. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 083. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 084. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 085. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 086. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 087. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 088. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 089. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 090. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 091. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 092. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 093. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 094. ChartChange(); 095. } 096. //+------------------------------------------------------------------+ 097. ~C_Terminal() 098. { 099. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 100. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 101. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 102. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 103. } 104. //+------------------------------------------------------------------+ 105. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 106. { 107. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 108. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 109. } 110. //+------------------------------------------------------------------+ 111. inline const st_Terminal GetInfoTerminal(void) const 112. { 113. return m_Infos; 114. } 115. //+------------------------------------------------------------------+ 116. const double AdjustPrice(const double arg) const 117. { 118. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 119. } 120. //+------------------------------------------------------------------+ 121. inline datetime AdjustTime(const datetime arg) 122. { 123. int nSeconds= PeriodSeconds(); 124. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 125. 126. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 127. } 128. //+------------------------------------------------------------------+ 129. inline double FinanceToPoints(const double Finance, const uint Leverage) 130. { 131. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 132. 133. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 134. }; 135. //+------------------------------------------------------------------+ 136. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 137. { 138. static string st_str = ""; 139. 140. switch (id) 141. { 142. case CHARTEVENT_CHART_CHANGE: 143. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 144. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 145. ChartChange(); 146. break; 147. case CHARTEVENT_OBJECT_CLICK: 148. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 149. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 150. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 151. break; 152. case CHARTEVENT_OBJECT_CREATE: 153. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 154. st_str = sparam; 155. break; 156. } 157. } 158. //+------------------------------------------------------------------+ 159. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const 160. { 161. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 162. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 163. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 164. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 165. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 166. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 167. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 168. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 169. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 170. } 171. //+------------------------------------------------------------------+ 172. bool IndicatorCheckPass(const string szShortName) 173. { 174. string szTmp = szShortName + "_TMP"; 175. 176. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 177. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin); 178. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 179. { 180. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 181. Print("Only one instance is allowed..."); 182. SetUserError(C_Terminal::ERR_NoMoreInstance); 183. 184. return false; 185. } 186. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 187. 188. return true; 189. } 190. //+------------------------------------------------------------------+ 191. };
Código fuente del archivo C_Terminal.mqh
Observa que los cambios fueron mínimos. En realidad, respecto al código visto en el artículo anterior, apenas abrimos la posibilidad de llamar al procedimiento CurrentSymbol dentro de otras clases que hereden la clase C_Terminal. Anteriormente, dicho procedimiento era exclusivo de la clase, pero ahora lo pasé al modo protegido. Muchos podrían colocarlo directamente como público, pero no me gustan los cambios tan radicales. Prefiero conceder el mínimo privilegio, hasta que el programa muestre la necesidad de tener más. Como procedimiento protegido, no podrá ser accedido de cualquier manera.
Pues bien, además de ese pequeño cambio, se hizo otro. Ahora, en el constructor, nota que, en la línea 73, eliminé el parámetro añadido en el artículo anterior. Sin embargo, en la línea 78, pasé a forzar el uso inicial del minicontrato. Este tipo de decisión abre precedentes para la creación de nuevas funcionalidades. El motivo de haberlo hecho así es precisamente porque, si se mantuviera la estructura anterior, nos veríamos obligados a cerrar Chart Trade para actualizar el contrato a ser mostrado en el indicador. De esta forma, no necesitamos hacer eso nuevamente. Podemos hacer que un mensaje permita el cambio de contrato. De cualquier forma, como consecuencia de este cambio, habrá otros, conforme se describió en el artículo anterior. Esto está previsto en el código de Chart Trade. Por ahora, sin embargo, no nos preocuparemos con eso. Necesitamos hacer algo primero.
Hablando de mensajes, necesitamos implementar nuevos mensajes para cubrir la solución de los problemas. Pero, antes de ver los nuevos mensajes, reflexionemos un poco. Observa la imagen a continuación:
Aquí tenemos los puntos en los que ocurrirá el intercambio de mensajes. Nota algo: en el caso del Asesor Experto, tenemos dos puntos o dos procedimientos en los que habrá envío de mensajes para Chart Trade. Y, en el caso de Chart Trade, tenemos solo un punto.
Para entender cómo ocurrirá ese intercambio, sin embargo, es necesario comprender que, tras la inicialización, se ejecuta OnChartEvent cuando algo es colocado en el gráfico. ¿Sabes qué evento se dispara para que OnChartEvent sea ejecutado cuando algo es colocado en el gráfico? Bien, si buscas esa información, verás que, cuando algo es colocado en el gráfico, ya sea un indicador o un Asesor Experto, MetaTrader 5 dispara el evento CHARTEVENT_CHART_CHANGE. Es muy importante saber esto, ya que utilizaremos precisamente ese evento para hacer que las cosas funcionen.
Pero, antes de eso, es necesario que consideres el siguiente hecho: ¿por qué necesitamos los botones para enviar órdenes, si el Asesor Experto, que hace eso, podría no estar en el gráfico? Eso no tiene sentido. Entonces, para mostrar que el intercambio de mensajes realmente ocurrió y que la información presente en Chart Trade puede y debe ser usada por el usuario u operador, vamos a modificar un pequeño, pero importante detalle en Chart Trade. Así, ya tenemos las ideas que necesitamos para implementar la solución a nuestro problema.
Implementar la solución
Primero, es necesario añadir tres nuevos eventos a nuestro sistema. Estos pueden verse en el código del archivo Defines.mqh, presentado en su totalidad justo abajo:
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. evHideMouse, //Hide mouse price line 31. evShowMouse, //Show mouse price line 32. evHideBarTime, //Hide bar time 33. evShowBarTime, //Show bar time 34. evHideDailyVar, //Hide daily variation 35. evShowDailyVar, //Show daily variation 36. evHidePriceVar, //Hide instantaneous variation 37. evShowPriceVar, //Show instantaneous variation 38. evCtrlReplayInit, //Initialize replay control 39. evChartTradeBuy, //Market buy event 40. evChartTradeSell, //Market sales event 41. evChartTradeCloseAll, //Event to close positions 42. evChartTrade_At_EA, //Event to communication 43. evEA_At_ChartTrade //Event to communication 44. }; 45. //+------------------------------------------------------------------+
Código fuente del archivo Defines.mqh
Las líneas 42 y 43 fueron añadidas aquí, y son casi autoexplicativas, ya que el secreto está en la dirección de la comunicación. Es decir, la línea 42 se refiere a una comunicación que parte de Chart Trade hacia el Asesor Experto. Atención: no confundas esta comunicación con los eventos comerciales. Este evento estará destinado a otro tipo de comunicación. Sería como si se tratara de un canal especial.
Ya la línea 43 informa el nombre del evento para el cual Chart Trade solicitó una respuesta del Asesor Experto. Por ahora, son solo estas dos nuevas líneas. Pero el resultado de esto será enorme. Así que vamos a hacer lo siguiente: primero, veremos el código del archivo del indicador Chart Trade. Puede verse en su totalidad justo abajo. Nota que está ligeramente diferente de lo que se vio en el artículo anterior. Es decir, ahora ya no tenemos aquel parámetro que el usuario u operador podía ajustar.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Chart Trade Base Indicator." 04. #property description "See the articles for more details." 05. #property version "1.81" 06. #property icon "/Images/Market Replay/Icons/Indicators.ico" 07. #property link "https://www.mql5.com/pt/articles/12537" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Chart Trader\C_ChartFloatingRAD.mqh> 12. //+------------------------------------------------------------------+ 13. #define def_ShortName "Indicator Chart Trade" 14. //+------------------------------------------------------------------+ 15. C_ChartFloatingRAD *chart = NULL; 16. //+------------------------------------------------------------------+ 17. input ushort user01 = 1; //Leverage 18. input double user02 = 100.1; //Finance Take 19. input double user03 = 75.4; //Finance Stop 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. chart = new C_ChartFloatingRAD(def_ShortName, new C_Mouse(0, "Indicator Mouse Study"), user01, user02, user03); 24. 25. if (_LastError >= ERR_USER_ERROR_FIRST) return INIT_FAILED; 26. 27. return INIT_SUCCEEDED; 28. } 29. //+------------------------------------------------------------------+ 30. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 31. { 32. return rates_total; 33. } 34. //+------------------------------------------------------------------+ 35. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 36. { 37. if (_LastError < ERR_USER_ERROR_FIRST) 38. (*chart).DispatchMessage(id, lparam, dparam, sparam); 39. } 40. //+------------------------------------------------------------------+ 41. void OnDeinit(const int reason) 42. { 43. switch (reason) 44. { 45. case REASON_INITFAILED: 46. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 47. break; 48. case REASON_CHARTCHANGE: 49. (*chart).SaveState(); 50. break; 51. } 52. 53. delete chart; 54. } 55. //+------------------------------------------------------------------+
Código fuente del Indicador Chart Trade
Bien, pero ¿dónde están los nuevos eventos? Esperaba verlos en el procedimiento OnChartEvent. Pues están, de hecho, allí. Sin embargo, para simplificar las cosas, todo está siendo tratado en un único lugar. Es decir, en el procedimiento DispatchMessage. Y este procedimiento está en la clase C_ChartFloatingRAD, que puede verse en su totalidad justo abajo.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Mouse.mqh" 005. #include "C_AdjustTemplate.mqh" 006. //+------------------------------------------------------------------+ 007. #define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A) 008. #define macro_CloseIndicator(A) { \ 009. OnDeinit(REASON_INITFAILED); \ 010. SetUserError(A); \ 011. return; \ 012. } 013. //+------------------------------------------------------------------+ 014. class C_ChartFloatingRAD : private C_Terminal 015. { 016. private : 017. enum eObjectsIDE {MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL}; 018. struct st00 019. { 020. short x, y, minx, miny, 021. Leverage; 022. string szObj_Chart, 023. szObj_Editable, 024. szFileNameTemplate; 025. long WinHandle; 026. double FinanceTake, 027. FinanceStop; 028. bool IsMaximized, 029. IsDayTrade, 030. IsSaveState; 031. struct st01 032. { 033. short x, y, w, h; 034. color bgcolor; 035. int FontSize; 036. string FontName; 037. }Regions[MSG_NULL]; 038. }m_Info; 039. struct st01 040. { 041. short y[2]; 042. bool bOk; 043. }m_Init; 044. C_Mouse *m_Mouse; 045. //+------------------------------------------------------------------+ 046. void CreateWindowRAD(int w, int h) 047. { 048. m_Info.szObj_Chart = "Chart Trade IDE"; 049. m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit"; 050. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 051. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x); 052. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y); 053. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 054. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 057. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 058. }; 059. //+------------------------------------------------------------------+ 060. void AdjustEditabled(C_AdjustTemplate &Template, bool bArg) 061. { 062. for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_STOP_VALUE; c0++) 063. if (bArg) 064. { 065. Template.Add(EnumToString(c0), "bgcolor", NULL); 066. Template.Add(EnumToString(c0), "fontsz", NULL); 067. Template.Add(EnumToString(c0), "fontnm", NULL); 068. } 069. else 070. { 071. m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor")); 072. m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz")); 073. m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm"); 074. } 075. } 076. //+------------------------------------------------------------------+ 077. inline void AdjustTemplate(const bool bFirst = false) 078. { 079. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade 080. 081. C_AdjustTemplate *Template; 082. 083. if (bFirst) 084. { 085. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true); 086. for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++) 087. { 088. (*Template).Add(EnumToString(c0), "size_x", NULL); 089. (*Template).Add(EnumToString(c0), "size_y", NULL); 090. (*Template).Add(EnumToString(c0), "pos_x", NULL); 091. (*Template).Add(EnumToString(c0), "pos_y", NULL); 092. } 093. AdjustEditabled(Template, true); 094. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 095. if (_LastError >= ERR_USER_ERROR_FIRST) 096. { 097. delete Template; 098. 099. return; 100. } 101. m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage); 102. m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage)); 103. m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage)); 104. (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 105. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage)); 106. (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2)); 107. (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2)); 108. (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 109. (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 110. if (!(*Template).Execute()) 111. { 112. delete Template; 113. 114. macro_CloseIndicator(C_Terminal::ERR_FileAcess); 115. }; 116. if (bFirst) 117. { 118. for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++) 119. { 120. m_Info.Regions[c0].x = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_x")); 121. m_Info.Regions[c0].y = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_y")); 122. m_Info.Regions[c0].w = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_x")); 123. m_Info.Regions[c0].h = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_y")); 124. } 125. m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x; 126. AdjustEditabled(Template, false); 127. }; 128. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? m_Init.y[m_Init.bOk] : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 129. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx)); 130. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny)); 131. 132. delete Template; 133. 134. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 135. ChartRedraw(m_Info.WinHandle); 136. 137. #undef macro_PointsToFinance 138. } 139. //+------------------------------------------------------------------+ 140. eObjectsIDE CheckMousePosition(const short x, const short y) 141. { 142. int xi, yi, xf, yf; 143. 144. for (eObjectsIDE c0 = MSG_LEVERAGE_VALUE; c0 <= MSG_CLOSE_POSITION; c0++) 145. { 146. xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x; 147. yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y; 148. xf = xi + m_Info.Regions[c0].w; 149. yf = yi + m_Info.Regions[c0].h; 150. if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0; 151. } 152. return MSG_NULL; 153. } 154. //+------------------------------------------------------------------+ 155. inline void DeleteObjectEdit(void) 156. { 157. ChartRedraw(); 158. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Editable); 159. } 160. //+------------------------------------------------------------------+ 161. template <typename T > 162. void CreateObjectEditable(eObjectsIDE arg, T value) 163. { 164. long id = GetInfoTerminal().ID; 165. 166. DeleteObjectEdit(); 167. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0); 168. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 169. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 170. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 171. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 172. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 173. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 174. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 175. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 176. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 177. ChartRedraw(); 178. } 179. //+------------------------------------------------------------------+ 180. bool RestoreState(void) 181. { 182. uCast_Double info; 183. bool bRet; 184. C_AdjustTemplate *Template; 185. 186. if (bRet = GlobalVariableGet(macro_NameGlobalVariable("POST"), info.dValue)) 187. { 188. m_Info.x = (short) info._16b[0]; 189. m_Info.y = (short) info._16b[1]; 190. m_Info.minx = (short) info._16b[2]; 191. m_Info.miny = (short) info._16b[3]; 192. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl"); 193. if (_LastError >= ERR_USER_ERROR_FIRST) bRet = false; else 194. { 195. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", NULL); 196. (*Template).Add("MSG_TAKE_VALUE", "descr", NULL); 197. (*Template).Add("MSG_STOP_VALUE", "descr", NULL); 198. (*Template).Add("MSG_DAY_TRADE", "state", NULL); 199. (*Template).Add("MSG_MAX_MIN", "state", NULL); 200. if (!(*Template).Execute()) bRet = false; else 201. { 202. m_Info.IsDayTrade = (bool) StringToInteger((*Template).Get("MSG_DAY_TRADE", "state")) == 1; 203. m_Info.IsMaximized = (bool) StringToInteger((*Template).Get("MSG_MAX_MIN", "state")) == 1; 204. m_Info.Leverage = (short)StringToInteger((*Template).Get("MSG_LEVERAGE_VALUE", "descr")); 205. m_Info.FinanceTake = (double) StringToDouble((*Template).Get("MSG_TAKE_VALUE", "descr")); 206. m_Info.FinanceStop = (double) StringToDouble((*Template).Get("MSG_STOP_VALUE", "descr")); 207. } 208. }; 209. delete Template; 210. }; 211. 212. GlobalVariablesDeleteAll(macro_NameGlobalVariable("")); 213. 214. return bRet; 215. } 216. //+------------------------------------------------------------------+ 217. public : 218. //+------------------------------------------------------------------+ 219. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop) 220. :C_Terminal(0) 221. { 222. m_Mouse = MousePtr; 223. m_Info.IsSaveState = false; 224. if (!IndicatorCheckPass(szShortName)) return; 225. if (!RestoreState()) 226. { 227. m_Info.Leverage = Leverage; 228. m_Info.IsDayTrade = true; 229. m_Info.FinanceTake = FinanceTake; 230. m_Info.FinanceStop = FinanceStop; 231. m_Info.IsMaximized = true; 232. m_Info.minx = m_Info.x = 115; 233. m_Info.miny = m_Info.y = 64; 234. } 235. m_Init.y[false] = 150; 236. m_Init.y[true] = 210; 237. CreateWindowRAD(170, m_Init.y[m_Init.bOk = false]); 238. AdjustTemplate(true); 239. } 240. //+------------------------------------------------------------------+ 241. ~C_ChartFloatingRAD() 242. { 243. ChartRedraw(); 244. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart); 245. if (!m_Info.IsSaveState) 246. FileDelete(m_Info.szFileNameTemplate); 247. 248. delete m_Mouse; 249. } 250. //+------------------------------------------------------------------+ 251. void SaveState(void) 252. { 253. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B); 254. 255. uCast_Double info; 256. 257. info._16b[0] = m_Info.x; 258. info._16b[1] = m_Info.y; 259. info._16b[2] = m_Info.minx; 260. info._16b[3] = m_Info.miny; 261. macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue); 262. m_Info.IsSaveState = true; 263. 264. #undef macro_GlobalVariable 265. } 266. //+------------------------------------------------------------------+ 267. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 268. { 269. #define macro_AdjustMinX(A, B) { \ 270. B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x; \ 271. mx = x - m_Info.Regions[MSG_TITLE_IDE].w; \ 272. A = (B ? (mx > 0 ? mx : 0) : A); \ 273. } 274. #define macro_AdjustMinY(A, B) { \ 275. B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y; \ 276. my = y - m_Info.Regions[MSG_TITLE_IDE].h; \ 277. A = (B ? (my > 0 ? my : 0) : A); \ 278. } 279. 280. static short sx = -1, sy = -1, sz = -1; 281. static eObjectsIDE obj = MSG_NULL; 282. short x, y, mx, my; 283. double dvalue; 284. bool b1, b2, b3, b4; 285. ushort ev = evChartTradeCloseAll; 286. 287. switch (id) 288. { 289. case CHARTEVENT_CUSTOM + evEA_At_ChartTrade: 290. if (m_Init.bOk = ((lparam >= 0) && (lparam < 2))) 291. CurrentSymbol((bool)lparam); 292. AdjustTemplate(true); 293. break; 294. case CHARTEVENT_CHART_CHANGE: 295. if (!m_Init.bOk) 296. EventChartCustom(GetInfoTerminal().ID, evChartTrade_At_EA, 0, 0, ""); 297. x = (short)ChartGetInteger(GetInfoTerminal().ID, CHART_WIDTH_IN_PIXELS); 298. y = (short)ChartGetInteger(GetInfoTerminal().ID, CHART_HEIGHT_IN_PIXELS); 299. macro_AdjustMinX(m_Info.x, b1); 300. macro_AdjustMinY(m_Info.y, b2); 301. macro_AdjustMinX(m_Info.minx, b3); 302. macro_AdjustMinY(m_Info.miny, b4); 303. if (b1 || b2 || b3 || b4) AdjustTemplate(); 304. break; 305. case CHARTEVENT_MOUSE_MOVE: 306. if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) 307. { 308. switch (CheckMousePosition(x = (short)lparam, y = (short)dparam)) 309. { 310. case MSG_MAX_MIN: 311. if (sz < 0) m_Info.IsMaximized = (m_Info.IsMaximized ? false : true); 312. break; 313. case MSG_DAY_TRADE: 314. if ((m_Info.IsMaximized) && (sz < 0)) m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true); 315. break; 316. case MSG_LEVERAGE_VALUE: 317. if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_LEVERAGE_VALUE, m_Info.Leverage); 318. break; 319. case MSG_TAKE_VALUE: 320. if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_TAKE_VALUE, m_Info.FinanceTake); 321. break; 322. case MSG_STOP_VALUE: 323. if ((m_Info.IsMaximized) && (sz < 0)) CreateObjectEditable(obj = MSG_STOP_VALUE, m_Info.FinanceStop); 324. break; 325. case MSG_TITLE_IDE: 326. if (sx < 0) 327. { 328. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 329. sx = x - (m_Info.IsMaximized ? m_Info.x : m_Info.minx); 330. sy = y - (m_Info.IsMaximized ? m_Info.y : m_Info.miny); 331. } 332. if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, mx); 333. if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, my); 334. if (m_Info.IsMaximized) 335. { 336. m_Info.x = (mx > 0 ? mx : m_Info.x); 337. m_Info.y = (my > 0 ? my : m_Info.y); 338. }else 339. { 340. m_Info.minx = (mx > 0 ? mx : m_Info.minx); 341. m_Info.miny = (my > 0 ? my : m_Info.miny); 342. } 343. break; 344. case MSG_BUY_MARKET: 345. ev = evChartTradeBuy; 346. case MSG_SELL_MARKET: 347. ev = (ev != evChartTradeBuy ? evChartTradeSell : ev); 348. case MSG_CLOSE_POSITION: 349. if ((m_Info.IsMaximized) && (sz < 0) && (m_Init.bOk)) //<< 350. { 351. string szTmp = StringFormat("%d?%s?%s?%c?%d?%.2f?%.2f", ev, _Symbol, GetInfoTerminal().szSymbol, (m_Info.IsDayTrade ? 'D' : 'S'), 352. m_Info.Leverage, FinanceToPoints(m_Info.FinanceTake, m_Info.Leverage), FinanceToPoints(m_Info.FinanceStop, m_Info.Leverage)); 353. PrintFormat("Send %s - Args ( %s )", EnumToString((EnumEvents) ev), szTmp); 354. EventChartCustom(GetInfoTerminal().ID, ev, 0, 0, szTmp); 355. } 356. break; 357. } 358. if (sz < 0) 359. { 360. sz = x; 361. AdjustTemplate(); 362. if (obj == MSG_NULL) DeleteObjectEdit(); 363. } 364. }else 365. { 366. sz = -1; 367. if (sx > 0) 368. { 369. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 370. sx = sy = -1; 371. } 372. } 373. break; 374. case CHARTEVENT_OBJECT_ENDEDIT: 375. switch (obj) 376. { 377. case MSG_LEVERAGE_VALUE: 378. case MSG_TAKE_VALUE: 379. case MSG_STOP_VALUE: 380. dvalue = StringToDouble(ObjectGetString(GetInfoTerminal().ID, m_Info.szObj_Editable, OBJPROP_TEXT)); 381. if (obj == MSG_TAKE_VALUE) 382. m_Info.FinanceTake = (dvalue <= 0 ? m_Info.FinanceTake : dvalue); 383. else if (obj == MSG_STOP_VALUE) 384. m_Info.FinanceStop = (dvalue <= 0 ? m_Info.FinanceStop : dvalue); 385. else 386. m_Info.Leverage = (dvalue <= 0 ? m_Info.Leverage : (short)MathFloor(dvalue)); 387. AdjustTemplate(); 388. obj = MSG_NULL; 389. ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Editable); 390. break; 391. } 392. break; 393. case CHARTEVENT_OBJECT_DELETE: 394. if (sparam == m_Info.szObj_Chart) macro_CloseIndicator(C_Terminal::ERR_Unknown); 395. break; 396. } 397. ChartRedraw(); 398. } 399. //+------------------------------------------------------------------+ 400. }; 401. //+------------------------------------------------------------------+ 402. #undef macro_NameGlobalVariable 403. #undef macro_CloseIndicator 404. //+------------------------------------------------------------------+
Código fuente del archivo C_ChartFloatingRAD.mqh
Aunque el código del archivo de cabecera C_ChartFloatingRAD.mqh parezca enorme para muchos y no sea necesario leerlo en su totalidad, decidí publicarlo en este artículo completo. Esto se debe a los cambios ocurridos en relación con lo que se vio en el artículo anterior. El único y principal cambio ocurrió en el constructor de la clase. Como la numeración de las líneas cambió, y no quería dejar al estimado lector perdido intentando encontrar las líneas correctas, el código fue publicado en su totalidad.
Veamos qué cambió realmente. No en relación con el artículo anterior, sino para dar soporte a lo que podrá verse en el vídeo disponible al final de este artículo. Lo primero que debe notarse es la aparición de una pequeña estructura en la línea 39. Presta mucha atención a lo que explicaré, de lo contrario no entenderás lo que se muestra en el vídeo. Dentro de esta estructura, tenemos la línea 41. Esta contendrá dos valores para el tamaño de la ventana, que, en realidad, es un objeto OBJ_CHART. No te preocupes, pronto lo entenderás. Lo mismo aplica para la variable de la línea 42 que, junto con este array de dos unidades, posibilita lo que puede verse en el vídeo.
Entonces, pasaremos al constructor de la clase. Este se inicia en la línea 219 y, como puedes ver, no está el parámetro extra visto en el artículo anterior, ni la línea 220, donde llamábamos al constructor de la clase C_Terminal. Es decir, en este caso, volvemos al punto anterior al que se vio en el artículo anterior. Ahora, presta atención a lo siguiente: la ventana de Chart Trade es un objeto OBJ_CHART y puede estar minimizada o maximizada. Esto ya está implementado. Sin embargo, cuando el Asesor Experto no esté en el gráfico en el que esté Chart Trade, queremos que los botones sean ocultados.
Para eso, necesitamos alterar la dimensión en la dirección Y del sistema de coordenadas. Tales dimensiones se definen en este momento, en las líneas 235 y 236. Ahora, mucha atención: cuando el valor de la variable m_Init.bOk sea falso, indica que algo no está funcionando correctamente. En ese caso, ocultamos los botones de interacción para evitar que el usuario u operador piense que está enviando órdenes al servidor. Para que ocurra la ocultación, el valor en la coordenada Y deberá ser 150. Cuando el valor de m_Init.bOk sea verdadero, significa que el usuario u operador puede enviar órdenes vía Asesor Experto. Así, el valor de la coordenada Y será de 210, que era exactamente el valor predeterminado anterior, según la llamada en la línea 237.
Observa que la línea 237, responsable por crear el objeto OBJ_CHART que contiene Chart Trade, está diferente. Nota que, en lugar de colocar el valor 210 como dimensión, estamos usando un acceso al valor definido en el array. Al mismo tiempo, definimos el valor de m_Init.bOk como falso. Es decir, en realidad, estamos ordenando que el objeto OBJ_CHART se construya con el valor 150. Pero ¿por qué no usar este valor desde el principio? El motivo es probar y constatar que el código esté realmente funcionando como se espera. Si el valor 150 fuera pasado directamente, diferente de cómo se está haciendo, no tendríamos certeza de que la modelación esté funcionando. De esta manera, sin embargo, podemos garantizar que sí está funcionando.
Ahora, un nuevo detalle: este está en el procedimiento AdjustTemplate, llamado justo después, cuyo código está en la línea 77. En este procedimiento, el único cambio hecho fue en la línea 128, pues, al maximizar o minimizar Chart Trade, cambiamos el valor de la coordenada Y. Entonces, para que Chart Trade se mantenga consistente con lo que estamos viendo, necesitamos hacer que los valores de la coordenada Y sigan las reglas informadas en el constructor de la clase. De esta manera, incluso si maximizas o minimizas Chart Trade, los botones no serán visibles hasta que todo esté de acuerdo para que puedan mostrarse.
Esa parte fue fácil. Nota que los cambios fueron simples y pocos. Ahora, veremos la parte de los mensajes, que también contiene algunas pocas modificaciones. Entonces, vamos a la línea 267, donde se inicia el procedimiento DispatchMessage. Antes de ver los mensajes, vayamos a la línea 349, donde puedes notar una diferencia mínima, pero muy importante. Para no complicar innecesariamente la función de verificación del ratón, a fin de saber dónde ocurrió el clic, añadí un nuevo valor para ser comprobado en esta línea. Si el valor de la variable m_Init.bOk es falso y haces clic en la región donde estarían los botones, la prueba hará que no se dispare ningún evento. Es ese nivel de simplicidad el que hace que la programación sea algo espectacular, ya que muchos intentarían verificar esto en otro punto del código, lo que haría que el procedimiento de verificación fuera muy complicado.
Muy bien, pero volvamos a la cuestión de los mensajes. Comenzaremos con lo siguiente: cuando el indicador es colocado en el gráfico, ya sabes que el primer evento recibido será CHARTEVENT_CHART_CHANGE. Con esto, en la línea 295, verificamos si nuestra variable está en estado falso. Si se confirma, disparamos un evento personalizado en la línea 296. De cualquier forma, sea porque el Asesor Experto capturó el evento disparado o porque acaba de ser colocado en el gráfico, tendremos como resultado otro evento. Este es capturado por Chart Trade en la línea 289.
Ahora viene la parte realmente interesante. Cuando el valor comprobado en la línea 290 sea mayor o igual a cero, se iniciará la ejecución de la línea 291. Presta atención al siguiente hecho: si el valor es cero, será falso; si es diferente de cero, será verdadero. Sin embargo, si el valor es mayor que uno, no deberíamos aceptarlo. Así, el Asesor Experto deberá pasar un valor igual a cero o uno, indicando si estaremos usando un minicontrato o un contrato completo. Por último, en la línea 292, hacemos una solicitud para que Chart Trade sea actualizado. Parece muy complicado, aún más porque podemos tener un valor diferente de cero y uno llegando. Si ese fuera el caso, los botones deberán ser ocultados. Esto ocurre porque el Asesor Experto acaba de ser removido del gráfico o algo extraño acaba de ocurrir.
Ahora debes estar pensando que soy algún tipo de loco. ¿Cómo así? ¿Cómo es que Chart Trade recibió un mensaje diciendo que el Asesor Experto fue removido del gráfico o que algo extraño ocurrió? Bueno, para entender esto, vamos al tema del Asesor Experto. Este se presenta a continuación.
¿Cómo pasó a funcionar el Asesor Experto?
Muy bien, abajo puedes ver el código del Asesor Experto para la demostración.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Virtual Test..." 04. #property description "Demo version between interaction" 05. #property description "of Chart Trade and Expert Advisor" 06. #property version "1.81" 07. #property link "https://www.mql5.com/pt/articles/12537" 08. //+------------------------------------------------------------------+ 09. #include <Market Replay\Defines.mqh> 10. //+------------------------------------------------------------------+ 11. class C_Decode 12. { 13. private : 14. struct stInfoEvent 15. { 16. EnumEvents ev; 17. string szSymbol, 18. szContract; 19. bool IsDayTrade; 20. ushort Leverange; 21. double PointsTake, 22. PointsStop; 23. }info[1]; 24. public : 25. //+------------------------------------------------------------------+ 26. C_Decode() 27. { 28. info[0].szSymbol = _Symbol; 29. } 30. //+------------------------------------------------------------------+ 31. bool Decode(const int id, const string sparam) 32. { 33. string Res[]; 34. 35. if (StringSplit(sparam, '?', Res) != 7) return false; 36. stInfoEvent loc = {(EnumEvents) StringToInteger(Res[0]), Res[1], Res[2], (bool)(Res[3] == "D"), (ushort) StringToInteger(Res[4]), StringToDouble(Res[5]), StringToDouble(Res[6])}; 37. if ((id == loc.ev) && (loc.szSymbol == info[0].szSymbol)) info[0] = loc; 38. 39. ArrayPrint(info, 2); 40. 41. return true; 42. } 43. }*GL_Decode; 44. //+------------------------------------------------------------------+ 45. enum eTypeContract {MINI, FULL}; 46. //+------------------------------------------------------------------+ 47. input eTypeContract user00 = MINI; //Cross order in contract 48. //+------------------------------------------------------------------+ 49. bool bOk; 50. //+------------------------------------------------------------------+ 51. int OnInit() 52. { 53. bOk = false; 54. GL_Decode = new C_Decode; 55. 56. return INIT_SUCCEEDED; 57. } 58. //+------------------------------------------------------------------+ 59. void OnTick() {} 60. //+------------------------------------------------------------------+ 61. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 62. { 63. switch (id) 64. { 65. case CHARTEVENT_CUSTOM + evChartTradeBuy : 66. case CHARTEVENT_CUSTOM + evChartTradeSell : 67. case CHARTEVENT_CUSTOM + evChartTradeCloseAll: 68. GL_Decode.Decode(id - CHARTEVENT_CUSTOM, sparam); 69. break; 70. case CHARTEVENT_CHART_CHANGE: 71. if (bOk) break; 72. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 73. bOk = true; 74. EventChartCustom(ChartID(), evEA_At_ChartTrade, user00, 0, ""); 75. break; 76. } 77. } 78. //+------------------------------------------------------------------+ 79. void OnDeinit(const int reason) 80. { 81. switch (reason) 82. { 83. case REASON_REMOVE: 84. case REASON_INITFAILED: 85. EventChartCustom(ChartID(), evEA_At_ChartTrade, -1, 0, ""); 86. break; 87. } 88. delete GL_Decode; 89. } 90. //+------------------------------------------------------------------+
Código fuente del EA
Ahora viene la pregunta: ¿cómo funciona este código? Bien, antes de responder a eso, observemos un pequeño cambio que ocurrió. En el artículo anterior, pasamos a hacer que Chart Trade indicara cuál era el contrato que estaba visualizando. Si miras el código de Chart Trade, más precisamente el código de la clase C_ChartFloatingRAD, en la línea 351 se añadió una nueva información. Esa información se refiere al nombre del contrato y está siendo decodificada en la línea 36 del código del Asesor Experto. Ese era un pequeño detalle que necesitaba ser mencionado. Ahora, veamos cómo funciona este código del Asesor Experto.
Muy bien, observa que el código que permitía al usuario u operador indicar el tipo de contrato, que antes estaba presente en Chart Trade, ahora está en las líneas 45 y 47 del Asesor Experto. Sin embargo, en ningún lugar accedemos a la clase C_Terminal en este Asesor Experto. ¿Pero por qué? El motivo es que todavía no estamos conectándonos al servidor. El Asesor Experto aún está en fase de demostración. De cualquier modo, observa que tenemos una variable en la línea 49. Esta sirve para evitar que cualquier cambio en el gráfico dispare un evento hacia Chart Trade, con el fin de informarlo. En la línea 53, inicializamos esta variable como falsa. Ya en la línea 73, la declaramos como verdadera. Y, en la línea 71, la comprobamos precisamente para evitar este envío innecesario de mensajes.
Ahora presta atención al siguiente hecho: así como el indicador, el Asesor Experto tiene como primer evento un CHARTEVENT_CHART_CHANGE, lo cual está declarado en la línea 70. Como la variable está definida como false, el evento no se ejecuta. De hecho, vamos a ejecutar lo mismo que se haría si Chart Trade solicitara algo. Es decir, caemos en la línea 72. El resultado de esto es que, en la línea 74, se disparará un evento, y el valor indicado será cero o uno. Por esta razón, la enumeración en la línea 45 debe seguir lo que está siendo mostrado. Si el orden de los términos se altera, el resultado será diferente en Chart Trade. Solo cambia las cosas aquí si sabes lo que estás haciendo. De lo contrario, Chart Trade mostrará el contrato equivocado.
Ahora viene la segunda parte, en la que el Asesor Experto informa a Chart Trade que ya no podrá ejercer su papel. Entonces, Chart Trade debe ocultar los botones de envío de órdenes. Esto ocurrirá cuando se ejecute la rutina en la línea 79. En situaciones normales, MetaTrader 5 dispara un evento DeInit para cerrar algo en el gráfico. Cuando esto ocurre, el valor reason contiene el motivo de la llamada. Aquí en la línea 81, verificamos tales motivos. Si son los que están siendo declarados, se disparará un evento en la línea 85. Nota que el valor tras el tipo de evento es -1. Esto indicará que el Asesor Experto ya no estará disponible para uso en Chart Trade. De esta forma, los controles de envío de órdenes serán ocultados automáticamente.
El resto del código permaneció sin cambios, con excepción de lo que se vio en el artículo en el que expliqué cómo comunicarse con Chart Trade. Considero esta etapa, por lo tanto, concluida.
Consideraciones finales
A pesar de haber mostrado cómo puedes implementar una comunicación para inicializar tanto el Asesor Experto como el indicador Chart Trade, no sé si el código presentado en este artículo será realmente utilizado. Esto se debe a que aún existe la cuestión de las órdenes pendientes. Esa es realmente nuestra piedra en el zapato. Hasta ahora, el usuario u operador podía enviar solicitudes al servidor usando un minicontrato o un contrato completo, simplemente cambiando uno de los parámetros del sistema. Ese tipo de implementación no fue tan difícil de imaginar y elaborar. Todo lo que fue necesario hacer ya había sido expuesto en otros artículos de esta misma serie. Sin embargo, a diferencia de enviar mensajes entre aplicaciones, incorporar información sobre órdenes pendientes hace que la historia sea mucho más complicada.
De cualquier modo, el resultado de tales cambios puede verse en el vídeo. En el apéndice, tendrás acceso a los ejecutables usados en él, para que puedas entender lo que se hizo.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12537





- 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