Simulación de mercado (Parte 08): Sockets (II)
Introducción
En el artículo anterior, «Simulación de mercado (Parte 07): Sockets (I)», mostré los primeros pasos para que pudieras comenzar a estudiar sockets. Sin embargo, quizá la aplicación que mostré no resultara tan emocionante. En realidad, se parecía más a un programa "HELLO WORLD", que es el primer programa que intentamos hacer cuando empezamos a estudiar algo relacionado con la programación. Es un primer paso en un largo camino de aprendizaje.
Bien, dicho esto, en este artículo mostraré cómo hacer algo un poco más interesante. Para ser sincero, no resultará particularmente útil, pero podrás experimentar y divertirte estudiando los conceptos implicados. Al igual que en el artículo anterior, también necesitaremos algo de programación externa. El motivo de usar programación externa es el mismo que en el artículo anterior: no quiero añadir DLL a MetaTrader 5, al menos por ahora.
Por lo tanto, no entraré en muchos detalles sobre el funcionamiento del código externo, ya que podrás encontrar distintos códigos que realicen la misma tarea. Incluso podrás crear tu propio código estudiando las referencias que indiqué en el artículo anterior. Pero, ¿qué haremos en este artículo? Bien, en este artículo, para demostrar lo interesantes que pueden ser los sockets —y dado que realmente quiero hacer uso de ellos en el sistema de repetición/simulador—, crearemos un minichat de la manera más sencilla posible. Así es, te mostraré cómo crear un minichat en MetaTrader 5.
Planificación
El concepto de un minichat es bastante simple y, para ser sincero, incluso obvio. Tienes un área para escribir mensajes, un botón para enviarlos y otra área para ver lo que han enviado otras personas. Es decir, algo muy sencillo de realizar. Por lo tanto, necesitaremos colocar un objeto de edición, un botón y un objeto adicional para leer los mensajes publicados. Todo esto ya lo ofrece MetaTrader 5.
Muy bien. Normalmente, este tipo de programas de chat utilizan un sistema cliente-servidor incorporado. Pero, para usar MQL5 puro, el servidor será un programa externo y los clientes se desarrollarán en MQL5. Esa es la parte interesante del uso de sockets. No necesitas limitarte a hacer las cosas de una sola forma, puedes realizarlas de muchas maneras diferentes. Pero quizá estés pensando: «¿Cómo controlaremos las conexiones hasta el punto de poder tener cualquier cantidad de participantes en nuestro minichat?» Seguramente pensarás que es muy complicado de crear y desarrollar. ¿Estás de acuerdo? Bueno, todo depende de lo que quieras hacer y de cómo quieras hacerlo.
El programa que voy a mostrar es tan simple que podrías instalarlo en un Raspberry y usar este dispositivo como un pequeño servidor. De esta forma, se podría permitir un número muy grande de participantes sin necesidad de recompilar el código, ya que el servidor será dinámico. Decir que el servidor es dinámico significa que el límite de conexiones lo determinará la configuración del sistema operativo o las capacidades del hardware utilizado. El límite no estará definido en el código del servidor. Pero antes de preocuparnos por el código del servidor, veamos cómo debe ser el código del cliente. Este sí se desarrollará dentro de MQL5.
Implementación básica
Esta parte de la implementación será bastante interesante, especialmente en lo referente a MQL5, ya que utilizaremos una forma peculiar de hacer las cosas. En primer lugar, necesitamos crear una ventana para interactuar con el minichat. La forma más sencilla de hacerlo en MQL5 para que se pueda usar en MetaTrader 5 es mediante un indicador. Pero, hay un detalle: los indicadores no tienen permiso para usar sockets, ya que, dependiendo de cómo se programe el socket, podría bloquear el flujo de cálculo de los indicadores. Esto ocurre porque todos los indicadores forman parte del mismo espacio. Entonces, ¿cómo podemos crear dicha ventana? El código más sencillo es el siguiente:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property indicator_separate_window 04. #property indicator_plots 0 05. //+------------------------------------------------------------------+ 06. int OnInit() 07. { 08. return INIT_SUCCEEDED; 09. } 10. //+------------------------------------------------------------------+ 11. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 12. { 13. return rates_total; 14. } 15. //+------------------------------------------------------------------+
Código inicial del indicador
Este sencillo código crea una ventana para nosotros gracias a la línea tres, que indica que, al colocar este código en el gráfico, debe crearse una nueva ventana. Pero tenemos un pequeño problema. El problema es que, si lo dejamos así, podríamos colocar más de un chat en el gráfico. Además, existe otro problema: un indicador no puede utilizar sockets.
Por tanto, necesitamos modificar este código para que pueda ser utilizado por algún código en el que sea posible hacer uso de sockets. Para simplificar al máximo, lo pondremos todo dentro de un asesor experto. Recuerda que esto es solo para demostración. También se puede hacer lo mismo usando un script, por ejemplo. Sin embargo, dado que el script se elimina del gráfico cuando cambiamos el marco temporal, voy a mostrar cómo hacerlo en el asesor experto. Lo primero que hay que hacer es crear el siguiente código:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. //+------------------------------------------------------------------+ 05. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 06. #resource "\\" + def_IndicatorMiniChat 07. //+------------------------------------------------------------------+ 08. long gl_id; 09. int subWin; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. gl_id = ChartID(); 14. subWin = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL); 15. 16. ChartIndicatorAdd(gl_id, subWin, iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 17. 18. return INIT_SUCCEEDED; 19. } 20. //+------------------------------------------------------------------+ 21. void OnDeinit(const int reason) 22. { 23. ChartIndicatorDelete(gl_id, subWin, ChartIndicatorName(gl_id, subWin, 0)); 24. } 25. //+------------------------------------------------------------------+ 26. void OnTick() 27. { 28. } 29. //+------------------------------------------------------------------+ 30. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 31. { 32. } 33. //+------------------------------------------------------------------+
Código de inicio del Asesor Experto
Este es el esqueleto básico de nuestro asesor experto para que se abra una ventana. Sin embargo, observa que en la línea cinco defino que en la línea seis usaremos el indicador como un recurso interno del asesor experto. ¿Por qué hago esto? El motivo es que quiero que el indicador cree una ventana para nosotros. El asesor experto no puede hacerlo. Por eso usamos el indicador.
Ahora, presta atención: cuando el asesor experto se coloque en el gráfico, añadirá nuestro indicador en la línea 16 con el objetivo de crear la ventana que necesitamos. Pero el código del indicador mostrado arriba no nos sirve para esto. El motivo es que el indicador mostrado arriba se puede añadir al gráfico. Y no queremos eso. Lo que queremos es que el asesor experto añada el indicador al gráfico por nosotros y que el usuario no pueda hacerlo.
Entonces, ¿cómo podemos resolver este problema? Es sencillo. Modificaremos el indicador para que el usuario no pueda colocarlo en el gráfico. Aunque, una vez compilado el asesor experto, se puede borrar el ejecutable del indicador. Esto se debe a que el indicador estará incrustado en el asesor experto. De todos modos, veamos cómo resolver el problema en caso de que el usuario pueda acceder al ejecutable del indicador y colocarlo en el gráfico. La solución puede verse en el código de abajo:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property indicator_chart_window 07. #property indicator_plots 0 08. //+------------------------------------------------------------------+ 09. #define def_ShortName "Mini Chat" 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. long id = ChartID(); 14. string sz0 = def_ShortName + "_TMP"; 15. int i0; 16. 17. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 18. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 19. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 20. { 21. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 22. return INIT_FAILED; 23. } 24. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 25. i0 = ChartWindowFind(id, def_ShortName); 26. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 27. { 28. ChartIndicatorDelete(id, i0, def_ShortName); 29. return INIT_FAILED; 30. } 31. 32. return INIT_SUCCEEDED; 33. } 34. //+------------------------------------------------------------------+ 35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 36. { 37. return rates_total; 38. } 39. //+------------------------------------------------------------------+
Primera modificación del código del indicador
Este sencillo código impide que el usuario coloque el indicador en el gráfico. De este modo, se garantiza que el indicador solo podrá colocarlo el asesor experto, ya que estará incorporado en él. Veamos por qué este código impide que el usuario coloque el indicador en el gráfico. Observa que en la línea seis indico que el indicador no creará una nueva ventana.
En la línea nueve, definimos un nombre para el indicador. Este nombre será importante para nosotros durante las pruebas. Ahora, presta atención: para poder eliminar el indicador de la lista, necesitamos que tenga un nombre. Este nombre NO DEBE SER EL DEFINITIVO, sino uno temporal. Esto se hace en la línea 14 y, a continuación, establecemos este nombre temporal como el nombre del indicador. Esto se realiza en la línea 17.
Ahora viene la parte interesante. En el bucle de la línea 18, buscaremos el nombre del indicador en la lista de indicadores del gráfico. Sin embargo, el nombre que buscaremos es el que aparece en la línea nueve. La prueba que realiza esto se encuentra en la línea 19. Cuando se encuentre el indicador, se ejecutará la línea 21. Esta línea no eliminará el indicador que ya está en el gráfico, sino el que estamos intentando colocar. Inmediatamente después, en la línea 22, indicaremos que la inicialización ha fallado.
Sin embargo, si el indicador se puede colocar, la línea 24 hará que se utilice el nombre indicado en la línea nueve. De esta manera, garantizamos que solo se utilice el indicador incorporado en el asesor experto.
Pero aún falta un pequeño detalle. Se resuelve en las líneas siguientes, ya que los fragmentos explicados anteriormente evitan que el usuario coloque el indicador después de que ya esté en el gráfico. No obstante, si el indicador no está presente, el usuario aún podría colocarlo en el gráfico. Para evitarlo, realizamos una nueva verificación.
Pero primero necesitamos saber dónde se está colocando el indicador. Esto se obtiene en la línea 25. Ahora, presta atención a esto: si el indicador se coloca en la ventana principal, el valor de la línea 25 será cero. De lo contrario, indicará en qué subventana se está colocando el indicador. Así que, en la verificación de la línea 26, analizamos este valor. Si el valor es cero, tendremos que eliminar el indicador. También lo haremos si la ventana indicada tiene más de un indicador.
Ahora quizá estés algo confundido. Si el asesor experto va a colocar el indicador en el gráfico, ¿no lo hará usando la ventana principal? ¿No es eso lo que indica la línea seis, cuando dice que el indicador se colocará en la ventana principal? En realidad, cuando el asesor experto coloque el indicador en el gráfico, lo hará creando una nueva ventana en este. Por eso, si el valor devuelto en la línea 25 es cero, podemos eliminar el indicador sin problemas. Pero, también podemos eliminarlo si en la misma ventana devuelta existe algún otro elemento, ya que esto indicará que es el usuario quien está intentando colocar el indicador.
De esta forma, limitamos las opciones e impedimos que el usuario coloque el indicador en el gráfico. Muy bien, una vez que hayamos conseguido esto, podremos pasar a la siguiente etapa.
Implementar los objetos de interacción
Por algunos factores y para no complicar innecesariamente nuestra aplicación en MQL5, haremos lo siguiente: los objetos de interacción se colocarán en el indicador y la parte de la conexión, en el asesor experto. Pero, haremos las cosas de manera que, si lo deseas, puedas mejorar aún más este minichat. Para ello, dividiremos todo el trabajo en archivos de cabecera. Como todo lo que estamos haciendo está relacionado con el proyecto de repetición/simulador, haremos que este minichat también pueda funcionar dentro de este mismo modelo. Por lo tanto, lo primero que debemos hacer es añadir dos nuevos eventos a nuestra enumeración de eventos. El código 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. }; 48. //+------------------------------------------------------------------+
Código fuente del archivo Defines.mqh
Observa que el archivo Defines.mqh antiguo ha recibido dos nuevas líneas: la 45 y la 46. Estas líneas permiten la comunicación mediante sockets. Estos eventos solo se han añadido porque separaremos el minichat del código interno del asesor experto. Es decir, necesitamos hacer que el indicador pueda mostrar los mensajes recibidos vía socket. Como el indicador no puede utilizar sockets, haremos que el asesor experto vigile el socket y envíe los mensajes disponibles al minichat. Así podremos tener acceso a ellos.
Bien. Ahora veamos el archivo de cabecera responsable de crear los objetos en el gráfico. Podemos verlo justo abajo:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ShortName "Mini Chat" 007. #define def_MaxRows 256 008. #define def_FontName "Lucida Console" 009. #define def_FontSize 12 010. #define def_SizeControls (m_txtHeight + 6) 011. #define macroColorRGBA(A) ((uint)((0xFF << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16))) 012. //+------------------------------------------------------------------+ 013. class C_Chat 014. { 015. private : 016. long m_id; 017. int m_sub; 018. int m_txtHeight; 019. bool m_full; 020. ushort m_Width, 021. m_Height, 022. m_index; 023. uint m_Pixel[]; 024. string m_ObjEdit, 025. m_ObjBtn, 026. m_ObjPanel; 027. struct st0 028. { 029. string info; 030. bool loc; 031. }m_Msgs[def_MaxRows + 1]; 032. //+------------------------------------------------------------------+ 033. void Add(string szMsg, bool isloc = false) 034. { 035. m_Msgs[m_index].info = szMsg; 036. m_Msgs[m_index].loc = isloc; 037. if ((++m_index) > def_MaxRows) 038. { 039. m_full = true; 040. m_index = 0; 041. } 042. Paint(); 043. }; 044. //+------------------------------------------------------------------+ 045. void Paint(void) 046. { 047. int max, count, p0, p1; 048. 049. ArrayInitialize(m_Pixel, macroColorRGBA(clrBlack)); 050. if ((p0 = m_Height - def_SizeControls) < 0) return; 051. max = (int)(floor(p0 / (m_txtHeight * 1.0))); 052. p1 = m_index - max; 053. if (m_full) 054. count = (max > def_MaxRows ? m_index + 1 : (p1 > 0 ? p1 : (def_MaxRows + p1 + 1))); 055. else 056. count = (p1 > 0 ? p1 : 0); 057. for (ushort row = 0; row < p0; count++) 058. { 059. count = (count > def_MaxRows ? 0 : count); 060. if (count == m_index) break; 061. TextOut(m_Msgs[count].info, 2, row, 0, m_Pixel, m_Width, m_Height, macroColorRGBA(m_Msgs[count].loc ? clrSkyBlue : clrLime), COLOR_FORMAT_ARGB_NORMALIZE); 062. row += (ushort) m_txtHeight; 063. } 064. ResourceCreate("::" + m_ObjPanel, m_Pixel, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE); 065. ChartRedraw(); 066. } 067. //+------------------------------------------------------------------+ 068. void CreateObjEdit(const string szArg) 069. { 070. ObjectCreate(m_id, szArg, OBJ_EDIT, m_sub, 0, 0); 071. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 072. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 073. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 074. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 075. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 076. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrDarkGray); 077. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 078. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrNavy); 079. } 080. //+------------------------------------------------------------------+ 081. void CreateObjButton(const string szArg, const string szTxt) 082. { 083. ObjectCreate(m_id, szArg, OBJ_BUTTON, m_sub, 0, 0); 084. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, 0); 085. ObjectSetInteger(m_id, szArg, OBJPROP_XSIZE, 70); 086. ObjectSetInteger(m_id, szArg, OBJPROP_YSIZE, def_SizeControls); 087. ObjectSetString(m_id, szArg, OBJPROP_FONT, def_FontName); 088. ObjectSetInteger(m_id, szArg, OBJPROP_FONTSIZE, def_FontSize); 089. ObjectSetInteger(m_id, szArg, OBJPROP_BGCOLOR, clrSkyBlue); 090. ObjectSetInteger(m_id, szArg, OBJPROP_COLOR, clrBlack); 091. ObjectSetInteger(m_id, szArg, OBJPROP_BORDER_COLOR, clrBlack); 092. ObjectSetString(m_id, szArg, OBJPROP_TEXT, szTxt); 093. } 094. //+------------------------------------------------------------------+ 095. void CreateObjPanel(const string szArg) 096. { 097. ObjectCreate(m_id, szArg, OBJ_BITMAP_LABEL, m_sub, 0, 0); 098. ObjectSetInteger(m_id, szArg, OBJPROP_XDISTANCE, 2); 099. ObjectSetInteger(m_id, szArg, OBJPROP_YDISTANCE, m_txtHeight + 8); 100. ObjectSetString(m_id, szArg, OBJPROP_BMPFILE, "::" + m_ObjPanel); 101. } 102. //+------------------------------------------------------------------+ 103. public : 104. //+------------------------------------------------------------------+ 105. C_Chat() 106. :m_index(0), 107. m_full(false), 108. m_Width(0), 109. m_Height(0) 110. { 111. int tmp; 112. 113. m_sub = ChartWindowFind(m_id = ChartID(), def_ShortName); 114. TextSetFont(def_FontName, -10 * def_FontSize, 0, 0); 115. TextGetSize("M", tmp, m_txtHeight); 116. CreateObjEdit(m_ObjEdit = def_ShortName + " Edit" + (string)ObjectsTotal(m_id)); 117. CreateObjButton(m_ObjBtn = def_ShortName + " Button" + (string)ObjectsTotal(m_id), "Send"); 118. CreateObjPanel(m_ObjPanel = def_ShortName + " Panel" + (string)ObjectsTotal(m_id)); 119. } 120. //+------------------------------------------------------------------+ 121. ~C_Chat() 122. { 123. ObjectsDeleteAll(m_id, def_ShortName); 124. }; 125. //+------------------------------------------------------------------+ 126. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 127. { 128. switch(id) 129. { 130. case CHARTEVENT_CHART_CHANGE: 131. m_Width = (ushort)ChartGetInteger(m_id, CHART_WIDTH_IN_PIXELS, m_sub); 132. m_Height = (ushort)ChartGetInteger(m_id, CHART_HEIGHT_IN_PIXELS, m_sub); 133. ObjectSetInteger(m_id, m_ObjEdit, OBJPROP_XSIZE, m_Width - 75); 134. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_XDISTANCE, m_Width - 72); 135. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_XSIZE, m_Width - 4); 136. ObjectSetInteger(m_id, m_ObjPanel, OBJPROP_YSIZE, m_Height - 4); 137. ArrayResize(m_Pixel, m_Width * m_Height); 138. Paint(); 139. break; 140. case CHARTEVENT_OBJECT_CLICK: 141. if (sparam == m_ObjBtn) 142. { 143. string sz0 = ObjectGetString(m_id, m_ObjEdit, OBJPROP_TEXT); 144. if (sz0 != "") 145. { 146. EventChartCustom(m_id, evChatWriteSocket, 0, 0, sz0); 147. Add(sz0, true); 148. ObjectSetString(m_id, m_ObjEdit, OBJPROP_TEXT, ""); 149. ObjectSetInteger(m_id, m_ObjBtn, OBJPROP_STATE, 0); 150. } 151. } 152. break; 153. case CHARTEVENT_CUSTOM + evChatReadSocket: 154. Add(sparam); 155. break; 156. } 157. } 158. //+------------------------------------------------------------------+ 159. }; 160. //+------------------------------------------------------------------+ 161. #undef macroColorRGBA 162. #undef def_MaxRows 163. //+------------------------------------------------------------------+
Código del archivo C_Chat.mqh
El código anterior genera los objetos con los que vamos a interactuar. Sin embargo, como sé que hay muchos entusiastas leyendo estos artículos, me gustaría pedir calma a quienes ya tienen un buen dominio de la programación, pues voy a explicar este código con todo detalle. Así, quienes decidan utilizarlo podrán mejorarlo. Además, por supuesto, de motivar a más personas a desarrollar sus propias soluciones.
Muy bien, pasemos a las explicaciones. En la línea cuatro, añadimos el archivo de cabecera que contiene algunas de nuestras definiciones. Aun así, las definiciones que realmente usaremos son las que acabamos de añadir. Así, en la línea seis, definimos un nombre para nuestro indicador. En la línea siete, definimos el número máximo de líneas que nuestro minichat mostrará. Más adelante entenderás mejor este valor. En las líneas ocho y nueve definimos el nombre y el tamaño de la fuente que se utilizará. Esto nos permitirá ajustar más fácilmente estos parámetros, ya que, una vez ajustadas las dimensiones aquí, todo el minichat se adaptará a ellas. La línea diez nos indica la altura de los controles para evitar problemas en su presentación. La línea once es solo una macro para colocar el texto en el gráfico. Después entenderás cómo se hace.
Muy bien, una vez realizadas estas definiciones, podemos comenzar con nuestra clase. En la línea 15, declaramos la cláusula privada, de modo que todas las variables y funciones que aparezcan después de esta cláusula serán exclusivas de la clase C_Chat. Las variables definidas entre las líneas 16 y 26 son las mínimas necesarias. Ahora bien, si deseas añadir más funcionalidades al minichat, será necesario considerar la posibilidad de aumentar el número de objetos. Por lo tanto, hazlo en este punto. La estructura declarada en la línea 27 sirve como memoria auxiliar, lo que nos permite acceder rápidamente a los mensajes publicados por otros usuarios.
Un detalle: el servidor no almacenará los mensajes de ninguna manera. Por tanto, los mensajes enviados antes de que te conectes se perderán. Hay otro detalle importante: cuando se modifica el marco temporal del gráfico, todo lo que esté en él se eliminará. De esta forma, también se perderán los mensajes presentes en esta estructura. Si quieres recuperarlos, deberás pensar en una forma de guardarlos y restaurarlos desde un archivo para no perder los mensajes anteriores. Todo lo que necesitarás hacer será almacenar la estructura definida en la línea 27 y, cuando leas el archivo para restaurarla, deberás llamar al procedimiento que explicaré más adelante. Así de simple.
Ahora observa la línea 31. En ella hacemos uso de la declaración realizada en la línea siete. Pero fíjate que estamos sumando uno. Esto se debe a que, al igual que en C/C++, en MQL5 comenzamos a contar desde cero. Por lo tanto, al sumar uno, indicamos que realmente queremos conservar las últimas 256 líneas, tal y como se declaró en la línea siete. Puedes establecer más o menos líneas; eso queda a tu criterio.
Ahora comienza la parte interesante de esta clase: los procedimientos y funciones que permiten que el minichat funcione. El primer procedimiento está en la línea 33. Su objetivo es añadir nuevos mensajes a la estructura declarada en la línea 27. Aquí simplemente asignamos los valores a sus correspondientes. Esto se hace en las líneas 35 y 36.
Ahora, presta atención. En la línea 37, verificamos si nuestro contador alcanzó el límite de mensajes. Cuando esto ocurra, debemos hacer que el contador vuelva a la posición cero. Esto se realiza en la línea 40. Para saber cuándo se alcanza este límite, en la línea 39 asignamos el valor verdadero a la variable que indica que la lista está llena de mensajes.
Pero, ¿por qué lo hacemos? El motivo es que, cuando se alcance el número máximo de mensajes, empezaremos a sobrescribir los mensajes más antiguos. De hecho, funciona como una lista circular: cuando la lista esté llena, comenzaremos a grabar los nuevos mensajes en las posiciones antiguas, pero mantendremos siempre la lista llena. Puedes modificar esto si lo deseas. Esto también queda a tu criterio.
El gran detalle aquí radica en que, una vez añadido el mensaje, necesitamos colocarlo en el gráfico. Esto se hace en el siguiente procedimiento, que puede verse en la línea 45.
Pero antes de ver este procedimiento, revisemos algunas otras cosas para entender correctamente el procedimiento de la línea 45. Con esto, saltamos a la línea 95. En ella, creamos un panel que contendrá el texto de la lista circular. Lo hacemos de la manera más simple posible. Es decir, en la línea 97 creamos el objeto OBJ_BITMAP_LABEL. En las líneas 98 y 99 indicamos la ubicación de la esquina superior del objeto. Pero, lo realmente importante es la línea 100, ya que en ella indicamos cuál será el bitmap que se usará.
Pero espera un momento. ¿Cómo que bitmap? ¿Nuestro minichat solo mostrará mensajes de texto? ¿Por qué usar un bitmap? No tiene sentido. De hecho, mostraremos mensajes de texto. Sin embargo, MQL5 no cuenta con una forma sencilla de colocar texto directamente en el gráfico como lo necesitamos. La función de biblioteca Comment no es suficiente ni adecuada para nosotros. Realmente necesitamos controlar dónde y cómo se mostrará el texto. Por tanto, la forma correcta de hacerlo es usar un bitmap. Es decir, MetaTrader 5 dibujará las letras en un mapa de bits y este se mostrará para su lectura en el gráfico.
Esto puede sonar complicado a primera vista, pero no lo es. En realidad, es bastante sencillo. Todo lo que necesitas entender es que en la línea 100 definimos el nombre del bitmap que se usará. Ahora podemos volver a la línea 45, porque lo que se hará allí empezará a tener sentido.
Para empezar, en la línea 49 limpiamos la matriz de píxeles. El color que se utilizará se puede modificar por cualquier otro; en este caso, el fondo será de color clrBlack, es decir, el fondo del panel de texto será negro. Aun así, puedes cambiarlo si lo deseas. Justo después, realizamos un pequeño cálculo para verificar si la ventana del minichat necesita contener el panel de texto. Si este fuera el caso, terminamos aquí. Si no es así, dibujaremos el texto.
Entonces, en la línea 51, calculamos el número máximo de líneas posibles dentro de la región permitida del panel de texto. Después, en la línea 52, buscamos el punto inicial de la lista circular para poder mostrar todas las líneas posibles. Ahora bien, hay un detalle: cuando la lista está llena, el valor de la variable m_index puede ser menor, igual o mayor que el número de líneas posibles en el panel. Por lo tanto, debemos reajustar ese valor.
Así, realizamos los ajustes entre las líneas 53 y 56. Ahora sí, la variable que se usará para el recuento realmente apunta a la línea más antigua que debe mostrarse. A continuación, entramos en el bucle de la línea 57. Este bucle puede parecerte extraño, pero es muy sencillo. Todo lo que hace es recorrer la lista circular posición por posición y, en la línea 61, utilizar una llamada a la biblioteca MQL5 para dibujar el texto en el bitmap.
Cuando este bucle termine, el bitmap contendrá el texto dibujado en él. A continuación, debemos indicar a MetaTrader 5 que el bitmap ya puede utilizarse. Esto se hace en la línea 64. Presta mucha atención a esta secuencia de eventos: primero definimos el nombre del bitmap, luego lo dibujamos y, por último, le indicamos a MetaTrader 5 que puede mostrarse en el gráfico. Esto ocurre en la línea 64. Es sencillo, ¿verdad?
Los procedimientos comprendidos entre las líneas 68 y 93 son prácticamente comunes en la programación en MQL5, por lo que no requieren mayores comentarios. Sin embargo, la explicación aún no ha terminado. Pasemos ahora al constructor de la clase, que se encuentra después de la declaración de una cláusula pública en la línea 103; todo lo que esté después de la línea 103 será visible fuera de la clase. En este constructor, presente en la línea 105, inicializaremos nuestro minichat. Esto se hace en pasos bastante simples.
En primer lugar, necesitamos capturar el índice de la ventana donde se mostrará el minichat. Esto se realiza en la línea 113 y, a continuación, en la línea 114, indicamos a MetaTrader 5 cómo se definirá la fuente que se usará para crear el texto en formato bitmap. En la línea 115, capturamos la altura de las letras, ya que, dependiendo de la configuración del sistema operativo, pueden tener diferentes tamaños. De este modo, para evitar una presentación desproporcionada, se captura la altura del texto.
A continuación, entre las líneas 116 y 118, creamos los objetos necesarios. Si necesitas más objetos, agrégalos en este punto, pero asegúrate de seguir las reglas de creación que se muestran en los objetos del código.
El destructor, presente en la línea 121, solo tiene la tarea de eliminar los objetos creados del gráfico. Por eso, solo contiene la línea 123.
Finalmente, llegamos a la línea 126, donde tratamos los mensajes que MetaTrader 5 enviará a nuestro indicador. Recuerda que aún estamos dentro del código del indicador. Para mantenerlo lo más simple posible, aquí solo tratamos tres tipos de eventos o mensajes. Uno de ellos es CHARTEVENT_CHART_CHANGE, que definirá la posición y el tamaño correctos de los objetos. Este es el primer mensaje que genera MetaTrader 5 tan pronto como nuestro código se coloca en el gráfico.
También procesamos el mensaje CHARTEVENT_OBJECT_CLICK, que se genera cuando se hace clic en el gráfico. En este caso, en la línea 141 verificamos si el clic se ha realizado en el botón de envío de mensajes. Si es así, en la línea 143 capturamos el texto del área de edición para enviarlo inmediatamente. Sin embargo, en la línea 144 comprobamos si el texto no está vacío. Si no lo está, en la línea 146 disparamos un evento personalizado para enviar este mismo texto al asesor experto.
Un detalle importante: aunque el asesor experto podría acceder directamente al texto del objeto de edición, no debemos correr el riesgo de permitirlo, ya que dicho objeto podría haber sido destruido por el indicador debido a la ejecución de la línea 148. De cualquier manera, el mismo texto que enviamos mediante el evento personalizado se mostrará también en el panel, en la línea 147.
Por último, en la línea 153 tenemos un evento personalizado. Su objetivo es capturar el texto que el asesor experto recibió del socket y colocarlo en el panel de texto de nuestro minichat. Tal vez en este momento te resulte un poco confuso, estimado lector. Sobre todo si has llegado aquí de forma inesperada, directamente a este artículo. En artículos anteriores de esta misma serie expliqué con todo detalle cómo trabajar con eventos personalizados. Si tienes curiosidad, te recomiendo leer los artículos anteriores, ya que te ayudarán a comprender lo que está ocurriendo aquí.
Muy bien, entonces, como último fragmento de código que veremos en este artículo, observemos cómo quedó el código final del indicador MiniChat. Este puede verse en su totalidad a continuación:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Base indicator for Mini Chat." 04. #property description "It cannot be used without outside assistance." 05. #property version "1.00" 06. #property link "https://www.mql5.com/pt/articles/12672" 07. #property indicator_chart_window 08. #property indicator_plots 0 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Chat.mqh> 11. //+------------------------------------------------------------------+ 12. C_Chat *Chat; 13. //+------------------------------------------------------------------+ 14. int OnInit() 15. { 16. long id = ChartID(); 17. string sz0 = def_ShortName + "_TMP"; 18. int i0; 19. 20. IndicatorSetString(INDICATOR_SHORTNAME, sz0); 21. for (int c0 = (int)ChartGetInteger(id, CHART_WINDOWS_TOTAL) - 1; c0 >= 0; c0--) 22. if (ChartIndicatorName(id, c0, 0) == def_ShortName) 23. { 24. ChartIndicatorDelete(id, ChartWindowFind(id, sz0), sz0); 25. return INIT_FAILED; 26. } 27. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 28. i0 = ChartWindowFind(id, def_ShortName); 29. if ((i0 == 0) || (ChartIndicatorsTotal(id, i0) > 1)) 30. { 31. ChartIndicatorDelete(id, i0, def_ShortName); 32. return INIT_FAILED; 33. } 34. 35. Chat = new C_Chat(); 36. 37. return INIT_SUCCEEDED; 38. } 39. //+------------------------------------------------------------------+ 40. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 41. { 42. return rates_total; 43. } 44. //+------------------------------------------------------------------+ 45. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 46. { 47. (*Chat).DispatchMessage(id, lparam, dparam, sparam); 48. } 49. //+------------------------------------------------------------------+ 50. void OnDeinit(const int reason) 51. { 52. delete Chat; 53. } 54. //+------------------------------------------------------------------+
Código fuente del indicador
Consideraciones finales
En este artículo, presenté la primera parte del código del minichat. Aún queda por tratar el código del asesor experto y, principalmente, el del servidor. Pero como este es un tema bastante extenso, dejaré que disfrutes del código visto hasta ahora, estimado lector. Nos veremos en el próximo artículo, donde terminaremos de construir el minichat con el servidor y lo veremos funcionando.
| Archivo | Descripción |
|---|---|
| Experts\Expert Advisor.mq5 | Demuestra la interacción entre Chart Trade y el Asesor Experto (Es necesario el Mouse Study para la interacción) |
| Indicators\Chart Trade.mq5 | Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción) |
| Indicators\Market Replay.mq5 | Crea los controles para la interacción con el servicio de reproducción/simulador (es necesario el 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 como en el mercado real) |
| Servicios\Market Replay.mq5 | Crea y mantiene el servicio de reproducción y simulación de mercado (archivo principal de todo el sistema) |
| Código VS C++\Server.cpp | Crea y mantiene un socket servidor desarrollado en C++ (versión MiniChat). |
| Code in Python\Server.py | Crea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e o Excel |
| Scripts\CheckSocket.mq5 | Permite realizar una prueba de conexión con un socket externo. |
| Indicators\Mini Chat.mq5 | Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar). |
| Experts\Mini Chat.mq5 | Permite implementar un mini chat mediante un asesor experto (requiere el uso de un servidor para funcionar) |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12672
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.
Redes neuronales en el trading: Modelos bidimensionales del espacio de enlaces (Final)
Cómo funciones centenarias pueden actualizar nuestras estrategias comerciales
Simulación de mercado (Parte 07): Sockets (I)
- 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