Simulación de mercado: La unión hace la fuerza (III)
Introducción
Hola a todos y bienvenidos a un nuevo artículo de la serie dedicada a la construcción de un sistema de repetición/simulación.
En el artículo anterior, Simulación de mercado: La unión hace la fuerza (II), mostré cómo modificaríamos el funcionamiento de nuestras aplicaciones para simular la interacción con un servidor de trading. En aquel artículo implementamos una nueva clase: C_InServer. Su finalidad es precisamente permitir modelar fácilmente nuestras aplicaciones, de modo que resulte completamente invisible quién responde a nuestras solicitudes: el servidor real o el servidor simulado. Sé que todo esto puede parecer bastante complicado para muchos de ustedes, mientras que, para otros, no será más que una etapa más de lo que estamos implementando.
Lo cierto es que, mediante la clase C_InServer, podremos adaptar las aplicaciones con mucha facilidad para obtener el resultado deseado. Como probablemente ya imaginas, no tendremos que implementar nuevas cosas ni en el Asesor Experto ni en el indicador de posición. Al menos en esta primera etapa, solo será necesario modificar el código de una forma muy sutil. Así que, sin más preámbulos, vayamos a lo que nos interesa.
Matar dos pájaros de un tiro
Incluso antes de completar la implementación del Asesor Experto y del indicador de posición para utilizarlos en el sistema de repetición/simulador, ya podemos hacer que el código de ambos quede completamente funcional y preparado para el sistema de repetición/simulador. Esto es posible gracias a la clase C_InServer. Por desgracia, olvidé mostrar una función que también necesitaremos, ya que el Asesor Experto puede utilizar un símbolo asociado a una cuenta HEDGING. Como ya sabes, en este tipo de cuenta podemos mantener más de una posición abierta. Por tanto, debemos añadir a la clase C_InServer el fragmento que aparece a continuación.
127. //+------------------------------------------------------------------+ 128. inline const ulong _PositionGetTicket(int arg) 129. { 130. #define macro_ERROR { Print("Error in accessing the database..."); return 0; } 131. 132. bool bRet; 133. 134. if (!m_IsReplay) 135. return PositionGetTicket(arg); 136. 137. ZeroMemory(m_Info); 138. if (!ExecRequestOfData("SELECT * FROM tb_Replay AS tb WHERE tb.history = 0;")) macro_ERROR 139. for (int c = 0; (bRet = GetRegisterOfRequest(m_Info)) && (c < arg); c++); 140. 141. return (bRet ? m_Info.ticket : 0); 142. 143. #undef macro_ERROR 144. } 145. //+------------------------------------------------------------------+
Fragmento de C_InServer
No repetiré aquí todo el código, porque no tendría sentido. Bastará con añadir el fragmento mostrado y podremos comenzar a trabajar en el Asesor Experto y en el indicador de posición para comprobar cómo quedará finalmente el código de ambos. Comenzaremos por el código del Asesor Experto, que aparece a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Replay - EA.ico" 004. #property description "Demo version between interaction" 005. #property description "of Chart Trade and Expert Advisor" 006. #property version "1.135" 007. #property link "https://www.mql5.com/pt/articles/13555" 008. //+------------------------------------------------------------------+ 009. #include <Market Replay\Order System\C_Orders.mqh> 010. #include <Market Replay\Auxiliar\C_Terminal.mqh> 011. //+------------------------------------------------------------------+ 012. enum eTypeContract {MINI, FULL}; 013. //+------------------------------------------------------------------+ 014. input eTypeContract user00 = MINI; //Cross order in contract 015. //+------------------------------------------------------------------+ 016. C_Orders *Orders = NULL; 017. C_Terminal *Terminal = NULL; 018. //+------------------------------------------------------------------+ 019. int OnInit() 020. { 021. Orders = new C_Orders(0xC0DEDAFE78514269); 022. 023. return INIT_SUCCEEDED; 024. } 025. //+------------------------------------------------------------------+ 026. void OnTick() {} 027. //+------------------------------------------------------------------+ 028. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 029. { 030. int handle; 031. ulong ul; 032. 033. (*Orders).DispatchMessage(id, lparam, dparam, sparam); 034. switch (id) 035. { 036. case CHARTEVENT_CHART_CHANGE: 037. if (Terminal != NULL) break; 038. else 039. { 040. Terminal = new C_Terminal(0, 0, user00); 041. for (int count = (*Orders)._PositionsTotal() - 1; count >= 0; count--) 042. { 043. ul = (*Orders)._PositionGetTicket(count); 044. if ((*Orders)._PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) 045. { 046. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 047. continue; 048. } 049. handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul); 050. ChartIndicatorAdd(0, 0, handle); 051. IndicatorRelease(handle); 052. } 053. } 054. case CHARTEVENT_CUSTOM + evChartTrade_At_EA: 055. EventChartCustom(0, evEA_At_ChartTrade, user00, 0, ""); 056. break; 057. } 058. } 059. //+------------------------------------------------------------------+ 060. void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) 061. { 062. if (Terminal == NULL) return; 063. static ulong ticket = 0; 064. switch (trans.type) 065. { 066. case TRADE_TRANSACTION_HISTORY_ADD: 067. EventChartCustom(0, evUpdate_Position, trans.position, 0, ""); 068. ticket = (trans.order != trans.position ? trans.position : 0); 069. break; 070. case TRADE_TRANSACTION_REQUEST: 071. if ((request.symbol == (*Terminal).GetInfoTerminal().szSymbol) && (result.retcode == TRADE_RETCODE_DONE)) switch (request.action) 072. { 073. case TRADE_ACTION_DEAL: 074. if (ticket > 0) EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 075. else 076. { 077. int handle = iCustom(NULL, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", result.order); 078. ChartIndicatorAdd(0, 0, handle); 079. IndicatorRelease(handle); 080. } 081. ticket = 0; 082. break; 083. case TRADE_ACTION_SLTP: 084. EventChartCustom(0, evUpdate_Position, request.position, 0, ""); 085. break; 086. } 087. break; 088. }; 089. } 090. //+------------------------------------------------------------------+ 091. void OnDeinit(const int reason) 092. { 093. ulong ul; 094. 095. switch (reason) 096. { 097. case REASON_REMOVE: 098. case REASON_INITFAILED: 099. EventChartCustom(0, evEA_At_ChartTrade, -1, 0, ""); 100. break; 101. } 102. if (Terminal != NULL) 103. { 104. for (int count = (*Orders)._PositionsTotal() - 1; count >= 0; count--) 105. { 106. ul = (*Orders)._PositionGetTicket(count); 107. if ((*Orders)._PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue; 108. ChartIndicatorDelete(0, 0, IntegerToString(ul)); 109. } 110. } 111. delete Orders; 112. delete Terminal; 113. } 114. //+------------------------------------------------------------------+
Expert Advisor
Observa que el código permanece igual que antes y no requiere nuevos cambios para cumplir el objetivo previsto. Lo mismo ocurre con el indicador de posición, cuyo código se muestra a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property icon "/Images/Market Replay/Icons/Positions.ico" 004. #property description "Indicator for tracking an open position on the server." 005. #property description "This should preferably be used together with an Expert Advisor." 006. #property description "For more details see the same article." 007. #property version "1.135" 008. #property link "https://www.mql5.com/pt/articles/13555" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. //+------------------------------------------------------------------+ 012. #define def_ShortName "Position View" 013. //+------------------------------------------------------------------+ 014. #include <Market Replay\Order System\C_ElementsTrade.mqh> 015. #include <Market Replay\Order System\C_InServer.mqh> 016. //+------------------------------------------------------------------+ 017. input ulong user00 = 0; //For Expert Advisor use 018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen, 025. var, 026. tickSize; 027. char digits; 028. bool bIsBuy; 029. }m_Infos; 030. //+------------------------------------------------------------------+ 031. C_ElementsTrade *Open = NULL, *Stop = NULL, *Take = NULL; 032. C_InServer *Order = NULL; 033. //+------------------------------------------------------------------+ 034. bool CheckCatch(ulong ticket) 035. { 036. double vv; 037. 038. ZeroMemory(m_Infos); 039. m_Infos.szShortName = StringFormat("%I64u", m_Infos.ticket = ticket); 040. if (!(*Order)._PositionSelectByTicket(m_Infos.ticket)) return false; 041. if (ChartWindowFind(0, m_Infos.szShortName) >= 0) 042. { 043. m_Infos.ticket = 0; 044. return false; 045. } 046. m_Infos.szSymbol = (*Order)._PositionGetString(POSITION_SYMBOL); 047. m_Infos.digits = (char)SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 048. m_Infos.tickSize = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 049. vv = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 050. m_Infos.var = m_Infos.tickSize / vv; 051. IndicatorSetString(INDICATOR_SHORTNAME, m_Infos.szShortName); 052. EventChartCustom(0, evUpdate_Position, ticket, 0, ""); 053. 054. return true; 055. } 056. //+------------------------------------------------------------------+ 057. inline void ProfitNow(void) 058. { 059. double ask, bid, value; 060. 061. ask = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_ASK); 062. bid = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_BID); 063. if (Open != NULL) 064. { 065. (*Open).ViewValue(value = (m_Infos.bIsBuy ? bid - m_Infos.priceOpen : m_Infos.priceOpen - ask)); 066. (*Take).ViewValue(value, false); 067. (*Stop).ViewValue(value, false); 068. } 069. } 070. //+------------------------------------------------------------------+ 071. int OnInit() 072. { 073. Order = new C_InServer(); 074. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 075. if (!CheckCatch(user00)) 076. { 077. ChartIndicatorDelete(0, 0, def_ShortName); 078. return INIT_FAILED; 079. } 080. 081. return INIT_SUCCEEDED; 082. } 083. //+------------------------------------------------------------------+ 084. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 085. { 086. ProfitNow(); 087. 088. return rates_total; 089. } 090. //+------------------------------------------------------------------+ 091. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 092. { 093. double volume; 094. 095. if (Open != NULL) (*Open).DispatchMessage(id, lparam, dparam, sparam); 096. if (Take != NULL) (*Take).DispatchMessage(id, lparam, dparam, sparam); 097. if (Stop != NULL) (*Stop).DispatchMessage(id, lparam, dparam, sparam); 098. switch (id) 099. { 100. case CHARTEVENT_CUSTOM + evUpdate_Position: 101. if (lparam != m_Infos.ticket) break; 102. if (!(*Order)._PositionSelectByTicket(m_Infos.ticket)) 103. { 104. ChartIndicatorDelete(0, 0, m_Infos.szShortName); 105. return; 106. }; 107. if (Open == NULL) Open = new C_ElementsTrade( 108. m_Infos.ticket, 109. m_Infos.szSymbol, 110. evMsgClosePositionEA, 111. clrRoyalBlue, 112. m_Infos.digits, 113. m_Infos.tickSize, 114. StringFormat("%I64u : Position opening price.", m_Infos.ticket), 115. m_Infos.bIsBuy = ((*Order)._PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) 116. ); 117. if (Take == NULL) Take = new C_ElementsTrade( 118. m_Infos.ticket, 119. m_Infos.szSymbol, 120. evMsgCloseTakeProfit, 121. clrForestGreen, 122. m_Infos.digits, 123. m_Infos.tickSize, 124. StringFormat("%I64u : Take Profit price.", m_Infos.ticket), 125. m_Infos.bIsBuy 126. ); 127. if (Stop == NULL) Stop = new C_ElementsTrade( 128. m_Infos.ticket, 129. m_Infos.szSymbol, 130. evMsgCloseStopLoss, 131. clrFireBrick, 132. m_Infos.digits, 133. m_Infos.tickSize, 134. StringFormat("%I64u : Stop Loss price.", m_Infos.ticket), 135. m_Infos.bIsBuy 136. ); 137. volume = (*Order)._PositionGetDouble(POSITION_VOLUME); 138. (*Open).UpdatePrice(0, m_Infos.priceOpen = (*Order)._PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 139. (*Take).UpdatePrice(m_Infos.priceOpen, (*Order)._PositionGetDouble(POSITION_TP), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_SL)); 140. (*Stop).UpdatePrice(m_Infos.priceOpen, (*Order)._PositionGetDouble(POSITION_SL), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_TP)); 141. ProfitNow(); 142. break; 143. } 144. ChartRedraw(); 145. }; 146. //+------------------------------------------------------------------+ 147. void OnDeinit(const int reason) 148. { 149. delete Order; 150. delete Open; 151. delete Take; 152. delete Stop; 153. } 154. //+------------------------------------------------------------------+
Indicador de posición
Con esto ya tenemos la base del sistema funcionando correctamente. En el caso del indicador de posición, podemos considerarlo completamente terminado: ya puede utilizarse tanto en el simulador como en una cuenta real o de demostración. En cuanto al Asesor Experto, por ahora no necesitaremos modificar su código principal. Sin embargo, debemos terminar de implementar la clase C_Orders, porque es allí donde ocurrirá la magia que nos permitirá simular realmente el servidor de trading. Hasta ahora solo hemos conseguido simular parcialmente lo que necesitamos.
La única parte terminada es la que responde a TRADE_ACTION_SLTP, pero todavía no podemos probarla de forma efectiva, ya que necesitamos una posición abierta almacenada en la base de datos para comprobar este tipo de solicitud.
En este punto debemos detenernos a pensar un poco. No porque TRADE_ACTION_DEAL sea especialmente difícil de implementar, sino por la naturaleza de lo que debemos implementar. Utilizamos TRADE_ACTION_DEAL tanto para abrir como para cerrar una posición. Sin embargo, la verdadera dificultad consiste en que debe identificar qué estamos intentando hacer en dos tipos de cuenta diferentes.
Es decir, tanto si utilizamos una cuenta HEDGING como una cuenta NETTING, TRADE_ACTION_DEAL deberá reconocer el tipo de cuenta y determinar qué debe hacerse con la posición abierta. En una cuenta NETTING, si el usuario no está cerrando la posición, TRADE_ACTION_DEAL deberá recalcular el precio medio de la posición abierta y actualizar después la base de datos. En una cuenta HEDGING, en cambio, simplemente deberá crear una nueva posición. Observa que una misma solicitud obliga a actualizar una posición en NETTING y a crear otra en HEDGING. Por eso TRADE_ACTION_DEAL resulta algo más compleja de implementar. Avancemos con calma. Implementaremos TRADE_ACTION_DEAL de manera gradual hasta conseguir que funcione correctamente. Solo después optimizaremos el código ya creado. Para separar ambos asuntos, pasemos a un nuevo apartado.
TRADE_ACTION_DEAL: la misión
Para evitar un código excesivamente largo y tedioso, modificaremos ligeramente lo que ya tenemos. Así, será algo más agradable de implementar y comprender. Observa el estado actual del código original en el siguiente fragmento.
067. //+------------------------------------------------------------------+ 068. ulong SimulateServer(void) 069. { 070. MqlTradeResult TradeResult; 071. MqlTradeTransaction TradeTrans; 072. bool bResult = false; 073. 074. ZeroMemory(TradeResult); 075. ZeroMemory(TradeTrans); 076. 077. switch (m_Base.TradeRequest.action) 078. { 079. case TRADE_ACTION_SLTP: 080. bResult = ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 081. m_Base.TradeRequest.sl, 082. m_Base.TradeRequest.tp, 083. m_Base.TradeRequest.position)); 084. TradeTrans.type = TRADE_TRANSACTION_REQUEST; 085. TradeResult.retcode = TRADE_RETCODE_DONE; 086. if (bResult) 087. { 088. OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult); 089. return m_Base.TradeRequest.position; 090. } 091. break; 092. case TRADE_ACTION_DEAL: 093. break; 094. } 095. TradeResult.retcode = TRADE_RETCODE_INVALID; 096. OnTradeTransaction(TradeTrans, m_Base.TradeRequest, TradeResult); 097. return 0; 098. } 099. //+------------------------------------------------------------------+ 100. ulong SendToPhysicalServer(void) 101. { 102. MqlTradeCheckResult TradeCheck; 103. MqlTradeResult TradeResult; 104. 105. ZeroMemory(TradeCheck); 106. ZeroMemory(TradeResult); 107. if (_Symbol == def_SymbolReplay) 108. return SimulateServer(); 109. if (!OrderCheck(m_Base.TradeRequest, TradeCheck)) 110. { 111. PrintFormat("Order System - Check Error: %d", GetLastError()); 112. return 0; 113. } 114. m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult); 115. if (TradeResult.retcode != TRADE_RETCODE_DONE) 116. { 117. PrintFormat("Order System - Send Error: %d", TradeResult.retcode); 118. return 0; 119. }; 120. 121. return TradeResult.order; 122. } 123. //+------------------------------------------------------------------+
Fragmento de C_Orders
Ahora extraeremos parte del código mostrado anteriormente para que quede como en el fragmento que aparece a continuación.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result) 069. { 070. } 071. //+------------------------------------------------------------------+ 072. void SimulateServer(MqlTradeTransaction &trans, MqlTradeResult &result) 073. { 074. switch (m_Base.TradeRequest.action) 075. { 076. case TRADE_ACTION_SLTP: 077. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 078. m_Base.TradeRequest.sl, 079. m_Base.TradeRequest.tp, 080. m_Base.TradeRequest.position)) ? TRADE_RETCODE_DONE : TRADE_RETCODE_INVALID); 081. trans.type = TRADE_TRANSACTION_REQUEST; 082. result.order = m_Base.TradeRequest.position; 083. break; 084. case TRADE_ACTION_DEAL: 085. Decode_TRADE_ACTION_DEAL(trans, result); 086. break; 087. } 088. OnTradeTransaction(trans, m_Base.TradeRequest, result); 089. } 090. //+------------------------------------------------------------------+ 091. ulong SendToPhysicalServer(void) 092. { 093. MqlTradeCheckResult TradeCheck; 094. MqlTradeResult TradeResult; 095. MqlTradeTransaction TradeTrans; 096. 097. ZeroMemory(TradeCheck); 098. ZeroMemory(TradeResult); 099. ZeroMemory(TradeTrans); 100. if (_Symbol == def_SymbolReplay) SimulateServer(TradeTrans, TradeResult); else 101. { 102. if (!OrderCheck(m_Base.TradeRequest, TradeCheck)) 103. { 104. PrintFormat("Order System [%s] - Check Error: %d", m_Base.TradeRequest.symbol, GetLastError()); 105. return 0; 106. } 107. m_Base.bTrash = OrderSend(m_Base.TradeRequest, TradeResult); 108. } 109. if (TradeResult.retcode != TRADE_RETCODE_DONE) 110. { 111. PrintFormat("Order System [%s] - Send Error: %d", m_Base.TradeRequest.symbol, TradeResult.retcode); 112. return 0; 113. }; 114. 115. return TradeResult.order; 116. } 117. //+------------------------------------------------------------------+
Fragmento de C_Orders
El resultado es mucho más interesante, ya que el código gestiona los errores casi de la misma forma cuando estamos conectados al servidor real y cuando lo simulamos. Para que los mensajes sean más claros, dado que pueden proceder del servidor real o del simulado, he añadido a cada uno el nombre del contrato relacionado con la solicitud correspondiente. De este modo, aunque utilices el sistema de repetición/simulador al mismo tiempo que operas en una cuenta real, podrás identificar el origen de cada mensaje mostrado en el terminal de MetaTrader 5 mediante el símbolo indicado junto a él. Esto hace que todo resulte mucho más elegante e interesante de utilizar. Ahora podemos centrarnos en una parte muy concreta del código: Decode_TRADE_ACTION_DEAL, cuya implementación comenzará en la línea 68.
Comencemos por lo más básico: abrir una posición a mercado. Cuando esto ocurra, Decode_TRADE_ACTION_DEAL recibirá una serie de datos procedentes de la función ToMarket. Solo tendremos que interpretarlos y crear un nuevo registro en la base de datos. Nada más. Por ahora, la simulación no será completamente precisa, ya que no utilizaremos todas las configuraciones posibles. Esto se hará cuando implementemos el sistema de órdenes pendientes. De momento, trabajaremos únicamente con lo que ya tenemos. Para comenzar a realizar las pruebas, implementaremos el código que aparece en el siguiente fragmento.
67. //+------------------------------------------------------------------+ 68. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result) 69. { 70. trans.type = TRADE_TRANSACTION_REQUEST; 71. if (m_Base.TradeRequest.position == 0) 72. { 73. struct stLocal 74. { 75. ulong value; 76. }Info; 77. 78. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay AS tb WHERE tb.history = 0;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; 79. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 80. result.order = Info.value + 10; 81. if ((result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", 82. result.order, 83. m_Base.TradeRequest.type, 84. m_Base.TradeRequest.volume, 85. m_Base.TradeRequest.price, 86. m_Base.TradeRequest.sl, 87. m_Base.TradeRequest.tp, 88. 0 89. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 90. } 91. } 92. //+------------------------------------------------------------------+
Fragmento de C_Orders
Este fragmento hace precisamente lo que necesitamos: permite que el Asesor Experto simule la apertura de una posición a mercado mediante la misma interacción entre el indicador de mouse y el indicador Chart Trade que mostramos en artículos anteriores, cuando el Asesor Experto estaba conectado al servidor de trading real. Con solo añadir este fragmento al código de la clase C_Orders, podremos probar no solo la apertura de una posición, sino también el movimiento de los valores que mostrará el indicador de posición. Esta es la parte interesante.
Una vez que todo aparezca en el gráfico, el sistema ya podrá hacer lo que muchos de ustedes esperaban: abrir posiciones, mover los valores de take profit y stop loss y mostrar el resultado de la posición. Naturalmente, apenas estamos comenzando a completar el desarrollo del sistema de repetición/simulador, ya que, por ahora, solo podremos realizar esas operaciones, tal como ocurriría si estuviéramos conectados al servidor real.
El resultado obtenido después de compilar todo puede verse en las siguientes animaciones.

En la animación anterior observamos el momento exacto en el que se abre la posición. En la siguiente podemos ver cómo se actualiza el resultado de la posición.

Como puedes observar, el funcionamiento es prácticamente idéntico al que tendríamos al estar conectados al servidor real. Sin embargo, en este momento solo podemos abrir la posición. Todavía no se cerrará si se alcanza la línea de take profit o de stop loss. Tampoco podemos cerrarla haciendo clic en el botón de cierre situado en la línea del precio de apertura. Todo lo demás funciona perfectamente y de la forma esperada. Aun así, el sistema necesita algunas mejoras. Recuerda que esta ha sido únicamente la primera prueba; todavía no estamos implementando nada especialmente ambicioso. Sin embargo, si mantienes el fragmento tal como se muestra, podrás simular una cuenta HEDGING aunque el símbolo corresponda a una cuenta NETTING. Por tanto, ya estamos implementando esto incluso antes de intentar hacerlo realmente.
Muy bien. Ya sabemos que podremos utilizar el sistema con símbolos destinados a cuentas HEDGING, puesto que es posible crear nuevas posiciones incluso en símbolos configurados para cuentas NETTING. Sin embargo, debemos distinguir correctamente ambos casos. Quizá pienses que hacerlo será una locura, pero no es así. Esto se viene haciendo desde hace mucho tiempo. Solo necesitamos que la clase C_Orders obtenga los datos necesarios para diferenciar un símbolo destinado a una cuenta NETTING de otro destinado a una cuenta HEDGING. Esto es muy sencillo. Si examinas el código del sistema de repetición/simulador, encontrarás la función SetSymbolInfos, situada en la clase C_FileTicks. En esa función definimos el valor de SYMBOL_TRADE_CALC_MODE para indicar cómo se calculará la posición. Sin embargo, a diferencia de lo que ocurre en un sistema conectado a una cuenta real, aquí podemos posponer algunos criterios de cálculo y utilizar únicamente dos valores para distinguir correctamente los tipos de cuenta.
El sistema de repetición/simulador ya tiene implementada la forma de distinguir ambos tipos de cuenta, pero ahora debemos hacerlo dentro de la clase C_Orders. Puedes hacerlo de una forma distinta de la que presentaré. No obstante, la forma en que lo haré pretende también crear o, mejor dicho, simular cierta latencia entre la solicitud del usuario y la respuesta del servidor. Esta latencia siempre existe debido, entre otros factores, a la distancia. Recuerda que la señal no se desplaza de forma instantánea: transcurre cierto tiempo desde que sale de tu equipo hasta que llega al servidor. Para simular parcialmente esa latencia, modificaremos el código de la clase C_Orders como se muestra a continuación.
092. //+------------------------------------------------------------------+ 093. void SimulateServer(MqlTradeTransaction &trans, MqlTradeResult &result) 094. { 095. ENUM_SYMBOL_CALC_MODE Account = (ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE); 096. 097. switch (m_Base.TradeRequest.action) 098. { 099. case TRADE_ACTION_SLTP: 100. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET sl = %f, tp = %f WHERE ticket = %d;", 101. m_Base.TradeRequest.sl, 102. m_Base.TradeRequest.tp, 103. m_Base.TradeRequest.position)) ? TRADE_RETCODE_DONE : TRADE_RETCODE_INVALID); 104. trans.type = TRADE_TRANSACTION_REQUEST; 105. result.order = m_Base.TradeRequest.position; 106. break; 107. case TRADE_ACTION_DEAL: 108. Decode_TRADE_ACTION_DEAL(trans, result, Account == SYMBOL_CALC_MODE_EXCH_STOCKS); 109. break; 110. } 111. OnTradeTransaction(trans, m_Base.TradeRequest, result); 112. } 113. //+------------------------------------------------------------------+
Fragmento de C_Orders
Observa que en la línea 95 obtenemos el dato que necesitamos. Esta consulta se realizará cada vez que simulemos el servidor. Después utilizaremos ese mismo dato en la línea 108. Lo hago de este modo para añadir una ligera latencia a las demás llamadas. Sé que la línea 95 se ejecutará con mucha rapidez, pero, si deseas una latencia más realista, puedes añadir una llamada a Sleep antes de que se ejecute la línea 97. La decisión queda a tu criterio, estimado lector. Por ahora no quiero complicar demasiado la explicación ni dificultar su comprensión. Observa también que en la línea 108 no pasaremos un valor numérico, sino uno booleano. En términos sencillos, enviaremos true cuando la cuenta sea NETTING y false cuando sea HEDGING.
Muy bien. Volvamos ahora al método Decode_TRADE_ACTION_DEAL. Aunque este método ha funcionado, su implementación actual no es del todo adecuada, porque podríamos generar valores duplicados en el campo del ticket. Esto se debe a que la versión anterior solo tenía como objetivo comprobar si el sistema funcionaba. Una vez confirmado, debemos corregir el código. El nuevo fragmento, que ya incluye algunas instrucciones adicionales, aparece a continuación.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 069. { 070. struct stLocal 071. { 072. ulong value; 073. }Info; 074. C_InServer *UseLocal; 075. 076. trans.type = TRADE_TRANSACTION_REQUEST; 077. if ((IsNetting) && (m_Base.TradeRequest.position == 0)) 078. { 079. result.retcode = (ExecRequestOfData("SELECT tb.ticket FROM tb_Replay AS tb WHERE tb.history = 0;") ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 080. result.retcode = (result.retcode != TRADE_RETCODE_DONE ? result.retcode : (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)); 081. m_Base.TradeRequest.position = (result.retcode == TRADE_RETCODE_DONE ? Info.value : 0); 082. } 083. if (m_Base.TradeRequest.position == 0) 084. { 085. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; 086. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; 087. result.order = Info.value + 10; 088. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", 089. result.order, 090. m_Base.TradeRequest.type, 091. m_Base.TradeRequest.volume, 092. m_Base.TradeRequest.price, 093. m_Base.TradeRequest.sl, 094. m_Base.TradeRequest.tp, 095. 0 096. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 097. } else 098. { 099. UseLocal = new C_InServer(); 100. 101. (*UseLocal)._PositionSelectByTicket(m_Base.TradeRequest.position); 102. if (UseLocal._PositionGetInteger(POSITION_TYPE) == m_Base.TradeRequest.type) 103. { 104. m_Base.TradeRequest.price = NormalizeDouble(((*UseLocal)._PositionGetDouble(POSITION_PRICE_OPEN) * (*UseLocal)._PositionGetDouble(POSITION_VOLUME) + 105. m_Base.TradeRequest.price * m_Base.TradeRequest.volume) / 106. (UseLocal._PositionGetDouble(POSITION_VOLUME) + m_Base.TradeRequest.volume), (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 107. m_Base.TradeRequest.sl = NormalizeDouble((m_Base.TradeRequest.sl + (*UseLocal)._PositionGetDouble(POSITION_SL)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 108. m_Base.TradeRequest.tp = NormalizeDouble((m_Base.TradeRequest.tp + (*UseLocal)._PositionGetDouble(POSITION_TP)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 109. m_Base.TradeRequest.volume += (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 110. 111. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET volume = %f, price = %f, sl = %f, tp = %f;", 112. m_Base.TradeRequest.volume, 113. m_Base.TradeRequest.price, 114. m_Base.TradeRequest.sl, 115. m_Base.TradeRequest.tp 116. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 117. if (result.retcode == TRADE_RETCODE_DONE) 118. { 119. trans.type = TRADE_TRANSACTION_HISTORY_ADD; 120. trans.order = m_Base.TradeRequest.position; 121. trans.position = m_Base.TradeRequest.position; 122. OnTradeTransaction(trans, m_Base.TradeRequest, result); 123. } 124. } 125. 126. delete UseLocal; 127. } 128. } 129. //+------------------------------------------------------------------+
Fragmento de C_Orders
Ahora presta atención, porque en este punto todo comienza a complicarse un poco. Sin embargo, no hay motivo para alarmarse ni para salir corriendo hacia las montañas intentando escapar de una posible avalancha de detalles. A primera vista, lo que hacemos aquí puede parecer algo confuso, pero en realidad no lo es tanto. Observa que en la línea 74 declaramos una variable local. Esta nos permitirá acceder a la base de datos sin interferir con cualquier otro acceso que esté realizando el Asesor Experto.
En la línea 77 efectuamos una comprobación para determinar si estamos trabajando con una cuenta HEDGING o NETTING, ya que la clase debe ejecutar operaciones distintas en cada tipo de cuenta. También comprobamos si el campo position contiene un ticket. Si la solicitud procede de ToMarket, este campo será cero, a diferencia de lo que ocurre cuando la llamada procede de ClosePosition. Más adelante analizaremos este segundo caso. Si la condición de la línea 77 se cumple, debemos obtener el ticket de la posición abierta. Esto se realiza en la línea 81.
Ahora presta mucha atención, porque este punto es importante. Si estamos en una cuenta NETTING y la consulta no devuelve ninguna posición abierta, result.retcode recibirá el valor TRADE_RETCODE_ERROR. En cambio, si devuelve una posición abierta, su ticket se almacenará en m_Base.TradeRequest.position. Este dato es fundamental, porque en la línea 83 evaluaremos precisamente esa variable.
Por tanto, si estamos en una cuenta HEDGING o la consulta no devuelve ninguna posición abierta en la cuenta NETTING, la condición de la línea 83 se cumplirá y continuaremos con la apertura de la posición. Como puede haber varias posiciones cerradas anteriormente, en la línea 85 contamos los registros de la tabla para generar un ticket nuevo. De este modo evitamos que dos posiciones u operaciones reciban el mismo ticket. El resto funciona como vimos anteriormente.
En cambio, si se ha indicado una posición, la condición de la línea 83 no se cumplirá y el control pasará a la línea 97. Esto puede ocurrir con dos tipos de llamada: una procedente de ClosePosition y otra procedente de ToMarket. En este último caso, podemos estar cerrando la posición, revirtiéndola —es decir, cerrándola y abriendo otra en sentido contrario— o aumentando su volumen en el mismo sentido para recalcular su precio medio. Como se trata de tres operaciones distintas, debemos procesarlas por separado, ya que cada una produce un resultado diferente.
En el fragmento anterior comenzamos por el caso en el que el usuario solicita aumentar el volumen de la posición en el mismo sentido, es decir, recalcular su precio medio de apertura. Aquí debemos decidir qué hacer con los precios de take profit y stop loss. En esta implementación estoy suponiendo que esos precios también se recalcularán mediante un promedio. Sin embargo, este no es necesariamente el comportamiento que aplican todas las plataformas. La razón es sencilla. Cuando el operador aumenta una posición para recalcular su precio medio, algunas plataformas conservan los precios originales de take profit y stop loss de la posición inicial. De este modo mantienen el planteamiento original de la estrategia y esos precios pueden actuar como puntos de cierre parcial sobre el nuevo volumen abierto.
Como estoy suponiendo que conservar los precios originales de take profit y stop loss no forma parte de la estrategia, haré que el sistema también recalcule el promedio de esos precios. Estimado lector, puedes cambiar este comportamiento si lo deseas. Sin embargo, recuerda modificar también el indicador de posición para que muestre los precios resultantes. De lo contrario, podrías tener problemas al utilizar el sistema de repetición/simulador.
Una vez aclarado cómo recalcularemos esos precios, podemos pasar al siguiente punto. Observa que, hasta ahora, todavía no estamos cerrando la posición. El sistema se limita a abrirla y actualizarla a medida que se producen las interacciones con el usuario. El cierre de una posición mediante una llamada procedente de ClosePosition se parece bastante a su reversión. La diferencia entre ambas operaciones está en el volumen solicitado. Por tanto, podemos gestionarlas mediante un mismo bloque de código, siempre que comprobemos ese volumen para decidir si la solicitud debe limitarse a cerrar la posición abierta o si, además de cerrarla, debe abrirse una nueva posición en sentido contrario. Personalmente, no suelo revertir una posición durante una operación. Prefiero cerrarla y esperar una nueva entrada. Por este motivo, no puedo afirmar con total certeza si el ticket de la posición se modifica al revertirla. No obstante, en esta implementación cerraremos la posición actual y abriremos una nueva en sentido contrario cuando la operación implique una reversión. Para ello, debemos sustituir el fragmento anterior por el que aparece a continuación.
067. //+------------------------------------------------------------------+ 068. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 069. { 070. #define macro_NewPosition { \ 071. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; \ 072. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; \ 073. result.order = Info.value + 10; \ 074. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (ticket, type, volume, price, sl, tp, history) values (%d, %d, %f, %f, %f, %f, %d);", \ 075. result.order, \ 076. m_Base.TradeRequest.type, \ 077. m_Base.TradeRequest.volume, \ 078. m_Base.TradeRequest.price, \ 079. m_Base.TradeRequest.sl, \ 080. m_Base.TradeRequest.tp, \ 081. 0 \ 082. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); \ 083. } 084. 085. #define macro_History if (result.retcode == TRADE_RETCODE_DONE) { \ 086. trans.type = TRADE_TRANSACTION_HISTORY_ADD; \ 087. trans.order = m_Base.TradeRequest.position; \ 088. trans.position = m_Base.TradeRequest.position; \ 089. OnTradeTransaction(trans, m_Base.TradeRequest, result); } 090. 091. struct stLocal 092. { 093. ulong value; 094. }Info; 095. C_InServer *UseLocal; 096. 097. trans.type = TRADE_TRANSACTION_REQUEST; 098. if ((IsNetting) && (m_Base.TradeRequest.position == 0)) 099. { 100. result.retcode = (ExecRequestOfData("SELECT tb.ticket FROM tb_Replay AS tb WHERE tb.history = 0;") ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 101. result.retcode = (result.retcode != TRADE_RETCODE_DONE ? result.retcode : (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)); 102. m_Base.TradeRequest.position = (result.retcode == TRADE_RETCODE_DONE ? Info.value : 0); 103. } 104. if (m_Base.TradeRequest.position == 0) macro_NewPosition else 105. { 106. UseLocal = new C_InServer(); 107. 108. (*UseLocal)._PositionSelectByTicket(m_Base.TradeRequest.position); 109. if (UseLocal._PositionGetInteger(POSITION_TYPE) == m_Base.TradeRequest.type) 110. { 111. m_Base.TradeRequest.price = NormalizeDouble(((*UseLocal)._PositionGetDouble(POSITION_PRICE_OPEN) * (*UseLocal)._PositionGetDouble(POSITION_VOLUME) + 112. m_Base.TradeRequest.price * m_Base.TradeRequest.volume) / 113. (UseLocal._PositionGetDouble(POSITION_VOLUME) + m_Base.TradeRequest.volume), (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 114. m_Base.TradeRequest.sl = NormalizeDouble((m_Base.TradeRequest.sl + (*UseLocal)._PositionGetDouble(POSITION_SL)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 115. m_Base.TradeRequest.tp = NormalizeDouble((m_Base.TradeRequest.tp + (*UseLocal)._PositionGetDouble(POSITION_TP)) / 2, (int)SymbolInfoInteger(def_SymbolReplay, SYMBOL_DIGITS)); 116. m_Base.TradeRequest.volume += (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 117. 118. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET volume = %f, price = %f, sl = %f, tp = %f;", 119. m_Base.TradeRequest.volume, 120. m_Base.TradeRequest.price, 121. m_Base.TradeRequest.sl, 122. m_Base.TradeRequest.tp 123. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 124. macro_History; 125. } else 126. { 127. m_Base.TradeRequest.volume = m_Base.TradeRequest.volume - (*UseLocal)._PositionGetDouble(POSITION_VOLUME); 128. 129. result.retcode = (ExecCommandSQL(StringFormat("UPDATE tb_Replay SET history = 1 WHERE ticket = %d;", 130. m_Base.TradeRequest.position 131. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); 132. macro_History; 133. trans.type = TRADE_TRANSACTION_REQUEST; 134. macro_NewPosition; 135. } 136. 137. delete UseLocal; 138. } 139. #undef macro_History 140. #undef macro_NewPosition 141. } 142. //+------------------------------------------------------------------+
Fragmento de C_Orders
Este fragmento implementa tanto la reversión de la posición —cierra la posición actual y abre otra en sentido contrario— como el cierre de una posición concreta al hacer clic en el botón situado en la línea de su precio de apertura. Sin embargo, el botón de Chart Trade destinado a cerrar todas las posiciones todavía no conseguirá cerrarlas. La razón es sencilla: no estamos registrando en la base de datos el número mágico recibido en la solicitud. Ese valor permite identificar qué Asesor Experto abrió cada posición y se utiliza al buscar las posiciones que deben cerrarse. En este punto disponemos de dos alternativas. Estimado lector, deberás elegir la que mejor se adapte a tus necesidades. La primera consiste en modificar la base de datos para registrar el número mágico del Asesor Experto en cada posición. La segunda consiste en eliminar la comprobación del número mágico al seleccionar las posiciones que se cerrarán. Veamos cómo se implementaría cada opción. Recuerda que debes elegir una u otra; implementar ambas soluciones no tendría ningún sentido.
Eliminar la comprobación del número mágico
Para eliminar esta comprobación y hacer que, al pulsar el botón de Chart Trade, se cierren todas las posiciones, solo tendrás que modificar el fragmento que aparece a continuación.
221. //+------------------------------------------------------------------+ 222. void CloseAllsPosition(void) 223. { 224. for (int count = _PositionsTotal() - 1; count >= 0; count--) 225. { 226. if (_PositionGetSymbol(count) != m_ChartTrade.Data.szContract) continue; 227. if (_PositionGetInteger(POSITION_MAGIC) != m_Base.MagicNumber) continue; 228. ClosePosition(_PositionGetInteger(POSITION_TICKET)); 229. } 230. }; 231. //+------------------------------------------------------------------+
Fragmento de C_Orders
Lo único que debes hacer es eliminar la línea 227, que, como puedes observar, aparece tachada en el fragmento. Sin embargo, aunque esta solución funciona muy bien, nos impide registrar e identificar las operaciones ejecutadas automáticamente por cada Asesor Experto. En la misma base de datos que creará el sistema de repetición/simulador, puedes consultar cuándo realizó alguna operación cada uno de los Asesores Expertos.
Al cruzar estos datos, algo muy sencillo de hacer mediante SQL, podrás identificar los puntos en los que varios Asesores Expertos coinciden en su interpretación del mercado, incluso cuando utilizan distintos marcos temporales o modelos operativos. Sin duda, este cruce de datos resultará muy útil para muchos desarrolladores de los conocidos setups. Sin embargo, para realizarlo tendremos que modificar algunos detalles. Veamos cómo hacerlo en el siguiente apartado.
Añadir el número mágico
El número mágico siempre está presente en las solicitudes que recibe la clase C_Orders. Para utilizarlo al seleccionar y cerrar las posiciones, debemos guardarlo en cada registro de la tabla. Basta con modificar el código del siguiente fragmento.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\Defines.mqh" 05. #include "..\SQL\C_ReplayDataBase.mqh" 06. //+------------------------------------------------------------------+ 07. class C_InServer : public C_ReplayDataBase 08. { 09. private : 10. bool m_IsReplay; 11. struct stLocal 12. { 13. ulong numberMagic, 14. ticket; 15. int type; 16. double volume, 17. price, 18. sl, 19. tp; 20. }m_Info; 21. public : 22. //+------------------------------------------------------------------+ 23. C_InServer() 24. :C_ReplayDataBase(), 25. m_IsReplay(_Symbol == def_SymbolReplay) 26. { 27. ZeroMemory(m_Info); 28. ExecCommandSQL("CREATE TABLE IF NOT EXISTS tb_Replay ( magic, ticket, type, volume, price, sl, tp, history );"); 29. } 30. //+------------------------------------------------------------------+ . . . 49. //+------------------------------------------------------------------+ 50. inline const long _PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER arg) 51. { 52. if (!m_IsReplay) 53. return PositionGetInteger(arg); 54. 55. switch (arg) 56. { 57. case POSITION_TICKET : return (long) m_Info.ticket; 58. case POSITION_TIME : break; 59. case POSITION_TIME_MSC : break; 60. case POSITION_TIME_UPDATE : break; 61. case POSITION_TIME_UPDATE_MSC : break; 62. case POSITION_TYPE : return (long) m_Info.type; 63. case POSITION_MAGIC : return (long) m_Info.numberMagic; 64. case POSITION_IDENTIFIER : break; 65. } 66. 67. return 0; 68. } 69. //+------------------------------------------------------------------+
Fragmento de C_InServer
Observa que primero, en la línea 13, declaramos el miembro que almacenará el número mágico. A continuación, en la línea 28, añadimos la columna magic a la sentencia CREATE TABLE. Ten en cuenta un detalle: para que esta nueva definición se aplique, la base de datos no debe existir previamente. De lo contrario, la tabla conservará su estructura anterior y la columna no se creará. También puedes añadir la columna magic a una tabla existente mediante ALTER TABLE. Consulta la documentación de SQL si no sabes cómo hacerlo.
Por último, en la línea 63 leemos la columna magic del registro consultado y devolvemos ese valor como número mágico de la posición. Una vez realizado este cambio, también debemos modificar la clase C_Orders para insertar ese número al crear el registro correspondiente y actualizarlo cuando proceda. Estas modificaciones son todavía más sencillas, como muestra el fragmento siguiente.
67. //+------------------------------------------------------------------+ 68. void Decode_TRADE_ACTION_DEAL(MqlTradeTransaction &trans, MqlTradeResult &result, const bool IsNetting) 69. { 70. #define macro_NewPosition { \ 71. if ((result.retcode = (ExecRequestOfData("SELECT COUNT(*) FROM tb_Replay;")) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR) == TRADE_RETCODE_ERROR) return; \ 72. if ((result.retcode = (GetRegisterOfRequest(Info) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR)) == TRADE_RETCODE_ERROR) return; \ 73. result.order = Info.value + 10; \ 74. result.retcode = (ExecCommandSQL(StringFormat("INSERT INTO tb_Replay (magic, ticket, type, volume, price, sl, tp, history)" \ 75. "values (%I64u, %d, %d, %f, %f, %f, %f, %d);", \ 76. m_Base.TradeRequest.magic, \ 77. result.order, \ 78. m_Base.TradeRequest.type, \ 79. m_Base.TradeRequest.volume, \ 80. m_Base.TradeRequest.price, \ 81. m_Base.TradeRequest.sl, \ 82. m_Base.TradeRequest.tp, \ 83. 0 \ 84. )) ? TRADE_RETCODE_DONE : TRADE_RETCODE_ERROR); \ 85. } 86. 87. . 88. . 89. .
Fragmento de C_Orders
Observa lo fácil que resulta implementarlas. Ni siquiera será necesario explicarlas ni volver a mostrar todo el código, porque la solución ya está aplicada. Con esto, al pulsar el botón de Chart Trade para cerrar todas las posiciones, la clase podrá seleccionar los registros cuyo número mágico corresponda al Asesor Experto y cerrar esas posiciones. Además, ahora podrás utilizar varios Asesores Expertos dentro del sistema de repetición/simulador y generar una base de datos con la que analizar detenidamente cada uno de ellos. Cada registro conservará el número mágico del Asesor Experto que realizó la operación, por lo que podrás cruzar sus datos durante un mismo periodo y sobre un mismo símbolo. De este modo, será fácil determinar cuál es el mejor escenario para utilizar uno u otro.
No pretendo comparar aquí el sistema de repetición/simulador con el simulador de estrategias de MetaTrader 5, aunque es comprensible que surjan ciertas comparaciones. Sin embargo, no te confundas, estimado lector. El sistema de repetición/simulador se está desarrollando con una finalidad completamente distinta. Su objetivo es permitir que los operadores y usuarios prueben diferentes estrategias mediante una simulación equivalente a una prueba a ciegas: el conocido FORWARD TESTING. A diferencia del BACKTESTING, cuyo objetivo consiste en buscar patrones en datos históricos, en el FORWARD TESTING comprobamos si nuestro modelo también funciona sin poder ver las barras futuras. Esto es importante porque, al intentar realizar un BACKTESTING adecuado, siempre existe la tentación de consultar las barras posteriores. Y hacerlo invalida por completo cualquier modelo que se esté evaluando.
Consideraciones finales
En este artículo he presentado nuestro sistema para simular operaciones a mercado, con el fin de realizar las primeras operaciones en el sistema de repetición/simulador. Aunque el sistema está prácticamente terminado, todavía quedan algunas cosas por desarrollar e implementar antes de dar por finalizada la simulación de órdenes a mercado. También será necesario realizar unos pocos cambios para configurar bien todo el sistema y dejarlo funcionando correctamente.
Sin embargo, a pesar de todo lo que ya hemos implementado, confieso que estoy cansado de seguir atrapado en el desarrollo de este sistema. Aunque muchos consideran que es complicado de implementar y desarrollar, debo admitir que esperaba que fuera más difícil. Ha resultado mucho más sencillo de desarrollar de lo que parecía al comienzo de esta serie de artículos. Como ya me aburre escribir tanto sobre este sistema, pretendo tomarme un descanso.
Antes de hacerlo, mostraré cómo finalizar, al menos, esta parte dedicada a la simulación de operaciones a mercado, porque todavía quedan algunos detalles por implementar. A pesar del esfuerzo por incluirlos en este artículo, aún faltan algunos detalles por explicar y otros que modificaré para que las aplicaciones se comporten de forma más parecida a cuando están conectadas al servidor de trading real. Por ahora no las incluiré entre los archivos adjuntos, porque todavía no están listas para usarse. Sin embargo, lo haré próximamente. Nos veremos en el siguiente artículo, que posiblemente será el último de esta fase de desarrollo del sistema de repetición/simulador.
| Archivo | Descripción |
|---|---|
| Experts\Expert Advisor.mq5 | Muestra la interacción entre Chart Trade y el Asesor Experto. Es necesario Mouse Study para la interacción. |
| Indicators\Chart Trade.mq5 | Crea la ventana en la que se configura la orden que se enviará. Es necesario Mouse Study para la interacción. |
| Indicators\Market Replay.mq5 | Crea los controles necesarios para interactuar con el servicio de repetición/simulador. Es necesario Mouse Study para la interacción. |
| Indicators\Mouse Study.mq5 | Permite la interacción entre los controles gráficos y el usuario. Es necesario tanto para utilizar el sistema de repetición/simulador como para operar en el mercado real. |
| Indicators\Order Indicator.mq5 | Se encarga de mostrar las órdenes de mercado y permite interactuar con ellas y controlarlas. |
| Indicators\Position View.mq5 | Se encarga de mostrar las posiciones de mercado y permite interactuar con ellas y controlarlas. |
| Services\Market Replay.mq5 | Crea y mantiene el servicio de repetición y simulación de mercado. Es el archivo principal de todo el sistema. |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13555
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.
Motor de decision Multi-IA para MQL5 (Parte 5): backtest del motor en el simulador de estrategias y comparacion contra reglas simples.
Del básico al intermedio: Sobrecarga de operadores (IV)
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias (2)
- 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