Simulação de mercado: A união faz a força (III)
Introdução
Olá pessoal, e sejam bem-vindos a mais um artigo da série sobre como construir um sistema de replay/simulação.
No artigo anterior Simulação de mercado: A união faz a força (II), mostrei como iremos modificar a forma das nossas aplicações funcionarem, para que fosse possível simular a interação com um servidor de negociação. Naquele artigo, implementamos uma nova classe a C_InServer. Esta visa justamente permitir uma fácil modelagem das nossas aplicações. De maneira, a deixar completamente invisível quem está respondendo aos nossos requerimentos. Seja o servidor real, seja o servidor simulado. Sei que tudo isto parece ser bem complicado para muitos de vocês. Enquanto para outros, isto será apenas mais uma etapa, do que estamos implementando.
O fato é que, fazendo uso da classe C_InServer, poderemos muito facilmente adaptar as aplicações a fim de conseguir o resultado desejado. Como você muito provavelmente deve estar imaginando. Não mais precisaremos implementar novas coisas, tanto no Expert Advisor, quanto no indicador de posição. Tudo que será preciso fazer. Pelo menos neste primeiro momento. É alterar o código de uma forma bem sutil. Então sem mais delongas, vamos ao que interessa.
Uma paulada, dois coelhos mortos
Antes de realmente ter a implementação do Expert Advisor e do indicador de posição concluídos. Isto para que possamos usá-los no replay/simulador. Já podemos tornar o código de ambos, totalmente funcional e preparado para o replay/simulador. Isto graças a existência da classe C_InServer. Infelizmente me esqueci de mostrar uma função que também será necessária. Isto por conta que o Expert Advisor, poderá estar usando um ativo voltado para contas HEDGING. Este tipo de conta, você já deve estar ciente que podemos ter mais de uma posição em aberto. Então o fragmento quer precisará ser adicionado na classe C_InServer é visto logo abaixo.
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
Não irei repetir todo o código aqui, pois não faz sentido. Então bastará adicionar o fragmento mostrado. E podemos partir para trabalhar no Expert Advisor e no indicador de posição. Para assim ver como o novo código de ambos ficará no final. Começando pelo código do Expert Advisor que pode ser visto logo abaixo.
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
Note que o código permaneceu igual era antes. Não necessitando de novas mudanças para conseguir cumprir o objetivo planejado. Assim como o indicador de posição que é visto logo abaixo.
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 Posição
Com isto, já temos a base do sistema funcionando perfeitamente bem. Sendo que no caso, para o indicador de posição. Já podemos considerar ele totalmente finalizado. Podendo ser usado tanto em no simulador, quanto em conta real ou demo. Agora com relação ao Expert Advisor, não precisaremos mexer em seu código principal. Pelo menos neste momento. Mas precisamos terminar de implementar o código da classe C_Orders. Pois é lá que a mágica estará acontecendo. Nos permitindo de fato executar uma simulação do servidor de negociação.
Pois bem, até o momento, tudo que conseguimos, foi simular parcialmente o que de fato precisamos. Sendo que a única parte finalizada é o que responde ao TRADE_ACTION_SLTP. Não sendo de fato algo realmente testável. Já que precisamos de uma posição aberta, presente no banco de dados, para que este requerimento TRADE_ACTION_SLTP possa ser de fato testado.
Porém aqui temos que pensar um pouco. Não por conta que TRADE_ACTION_DEAL, seja algo complicado de implementar. Mas sim por conta da natureza do que precisamos implementar. O problema de TRADE_ACTION_DEAL, é que a estamos usando tanto para abrir, quanto para fechar uma posição. Mas a parte realmente complicada, é o fato de que ela, deverá ser capaz de entender que tipo de coisa estamos tentando fazer. Isto em dois tipos de contas diferentes.
Ou seja, não importa se venhamos a estar usando uma conta HEDGING ou NETTING. TRADE_ACTION_DEAL deverá ser capaz de entender qual o tipo de conta, e o que deverá ser feito na posição que estiver aberta. Isto por que em uma conta NETTING, TRADE_ACTION_DEAL deverá, caso o usuário não esteja fechando a posição, calcular o preço médio da posição e então atualizar o banco de dados. Já em uma conta HEDGING ela simplesmente deverá criar uma nova posição. Note que para a mesma situação, devemos tratar as coisas de maneira diferente. E é por isto que TRADE_ACTION_DEAL é um pouco mais complicada de ser implementada. Então vamos com calma neste ponto. Vamos implementar TRADE_ACTION_DEAL aos poucos de forma que ela funcione. Para somente depois, gerar algum tipo de otimização no código já produzido. Porém para separar os assuntos vamos ver isto em um novo tópico.
TRADE_ACTION_DEAL a missão
Para não precisar gerar um código extremamente longo e cansativo. Vamos mudar um pouco, que já temos. Assim tornaremos a coisa um tanto quanto mais agradável de ser implementada e compreendida. Então veja como o código original e encontra, vendo o fragmento abaixo.
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
Aqui vamos recortar este código visto no fragmento acima, de forma que ele ficará como mostrado logo abaixo.
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
Observe que agora, o código ficou muito mais interessante. Já que temos um tratamento de erros, praticamente idêntico entre o que é feito quando estamos em contato com o servidor real e quando estamos simulando ele. Para tornar as mensagens um pouco mais claras. Já que podemos ter mensagens vindas tanto do servidor real quanto do simulado. Adicionei nas mensagens o nome do contrato referente aquele requerimento em particular. Assim, mesmo que você esteja utilizando o replay/simulador, ao mesmo tempo que está negociando na conta real. Ao ver uma mensagem no terminal do MetaTrader 5. Você conseguirá saber a origem, com base no ativo que será informado junto da mensagem. Isto torna a coisa toda, bem mais elegante e interessante de ser utilizado. Ok, agora podemos focar em uma parte bem específica no código. Ou seja, Decode_TRADE_ACTION_DEAL que será implementada a partir da linha 68.
Vamos começar pelo mais básico primeiro. Ou seja, abrir uma posição a mercado. Quando isto acontece, Decode_TRADE_ACTION_DEAL, irá receber uma série de informações vindas da função ToMarket. O que precisamos fazer é simplesmente interpretar as informações e criar um novo registro no banco de dados. Nada mais do que isto. Neste momento a simulação não será muito exata, já que não estaremos usando todas as configurações possíveis. Isto será feito quando formos implementar o sistema de ordens pendentes. Por hora vamos apenas usar o que temos em mãos. Assim para que possamos começar a testar as coisas. Vamos implementar o seguinte código visto no fragmento abaixo.
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
O que este fragmento faz é justamente o que queremos. Permitir que o Expert Advisor consiga simular a abertura de uma posição a mercado. Isto usando a mesma interação entre o indicador de mouse e o indicador Chart Trade. Que foi mostrada em artigos anteriores. Isto quando o Expert Advisor estava em contato com o servidor de negociação real. Ao adicionarmos apenas e somente este fragmento, ao código da classe C_Orders. Teremos uma completa possibilidade, não apenas de testar a abertura da posição. Mais também poderemos testar a movimentação dos valores que serão mostrados no indicador de posição. E é esta a parte interessante.
Pois uma vez que tudo estiver no gráfico. O sistema já conseguirá ter alguma funcionalidade desejada por muitos de vocês. Claro, ainda estamos apenas começando a finalizar o desenvolvimento do replay/simulador. Já que teremos apenas o sistema abrindo e movimentando os valores de take profit e stop loss. Além é claro de apresentar o valor de resultado da posição. Assim como seria se você estivesse em contato com o servidor real.
O resultado quando tudo é compilado pode ser visto nas animações abaixo.

Nesta animação acima, temos exatamente a situação onde abrimos a posição. Já na animação abaixo, temos a visualização da atualização do resultado da posição.

Note que praticamente temos a mesma forma de trabalhar, como se estivéssemos em contato com o servidor real. Porém, neste exato momento, apenas estamos abrindo a posição. Não temos de forma alguma o seu fechamento, caso a linha de take profit ou stop loss, venham a ser atingidas. Se quer temos o fechamento da posição caso venhamos a clicar no botão de fechar. Mas todo o restante, está funcionando de forma perfeita e como esperado. Mas este sistema ainda precisa ser melhorado. Lembre-se este foi apenas o primeiro teste. Não estamos ainda fazendo nada de tão grandioso. Mas se você deixar o fragmento como mostrado. Você poderá simular uma conta HEDGING mesmo que o ativo seja para conta NETTING. Então temos algo sendo implementado, mesmo antes de tentarmos implementar isto de fato.
Muito bem. Então já sabemos, que poderemos usar o sistema em ativos voltados para contas HEDGING. Isto por conta de ser possível criar, novas posições mesmo em ativos voltados para conta NETTING. Mas precisamos separar este tipo de coisa. Você pode estar pensando que fazer tal coisa, será uma loucura. Mas não. Isto já está sendo feito de longa data. Tudo que precisamos, é fazer com que a classe C_Orders, consiga obter os dados, a fim de separar um ativo voltado para uma conta NETTING, de um voltado para conta HEDGING. Isto é algo muito simples. Se você olhar o código do replay/simulador irá encontrar a função SetSymbolInfos. Esta está na classe C_FileTicks. Pois bem, nesta função definimos o valor de SYMBOL_TRADE_CALC_MODE. Isto a fim de poder dizer como será o cálculo da posição. Mas diferente de um sistema de conta real. Aqui podemos postergar alguns critérios de cálculo. E usando apenas dois. Isto para conseguir fazer a correta distinção entre os tipos de conta.
Dentro do replay/simulador, já temos implementado a forma de separar os tipos de conta. Mas precisamos fazer isto dentro da classe C_Orders. Bem, você pode fazer este tipo de coisa de maneiras diferentes da que irei apresentar. Mas a forma como farei, na verdade é uma tentativa de criar, ou melhor simular, uma latência entre o pedido do usuário, e a resposta do servidor. Isto é sempre acontece, devido ao próprio fator da distância que existe. E lembre-se: O sinal não viaja instantaneamente. Existe um tempo entre o momento que ele sai de seu equipamento e chega no servidor. Assim para tentar simular um pouco desta latência. Iremos mudar o código da classe C_Orders, como mostrado abaixo.
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
Observe que na linha 95, buscamos o dado que precisamos. Isto será feito a todo momento em que estivermos simulando o servidor. Este mesmo dado é então usado na linha 108. Estou fazendo desta forma para que temos um leve efeito também em outras chamadas. Sei que a linha 95 será executada de forma muito rápida. Mas se você desejar uma latência ainda mais realista, pode adicionar uma chamada Sleep, isto antes da linha 97 ser executada. Mas isto, fica ao seu critério, meu caro leitor. No momento, não quero deixar toda a explicação muito complicada e difícil de ser entendida. Porém observe que na linha 108, não vamos passar um valor numérico, mas sim um valor booleano. Ou simplificando, passaremos verdadeiro quando a conta for NETTING, e falso quando for HEDGING.
Ok. Agora vamos voltar nossa atenção novamente para o procedimento Decode_TRADE_ACTION_DEAL. Apesar deste ter funcionado, ele de fato não é assim tão adequado, da forma como está codificado. Isto por conta que poderemos vir a ter valores duplicados no campo do bilhete. Isto pelo fato, de que na forma como foi feito anteriormente, tínhamos como propósito apenas e tão somente testar se o sistema funcionaria. Uma vez verificados isto, devemos corrigir as coisas. Assim o novo fragmento, já com algumas coisas extras é mostrado logo abaixo.
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
Agora preste atenção. Pois a coisa neste ponto começa a ficar um tanto mais complicada. Mas não é motivo para pânico. Ou correr apavorado para as montanhas tentando se esquivar de uma possível inundação de informação. O que estamos fazendo aqui, é algo à primeira vista um pouco confuso. Mas não é bem assim. Repare que na linha 74, definimos uma variável local. Está nos permitirá acessar o banco de dados. Sem que isto interferirá com algum acesso que o Expert Advisor esteja fazendo.
Na linha 77, fazemos uma checagem, cujo propósito é verificar se estamos em uma conta HEDGING ou NETTING. Isto por que a forma de tratar as coisas é diferente em ambas as contas. Mas também verificamos se temos uma informação, no campo da posição. Isto por que se o pedido estiver sua origem em ToMarket, este campo estará como zero. Diferente o que acontece quando ele vem de ClosePosition. Mas vamos chegar lá ainda. Caso este teste da linha 77 tenha sucesso. Precisamos capturar a informação sobre a posição que está aberta. No caso o bilhete da posição. Isto é feito na linha 81.
Agora preste atenção. Pois é importante. Caso estejamos em uma conta NETTING e não tenha sido encontrado nenhuma posição aberta. O valor em result.retcode será igual a TRADE_RETCODE_ERROR. Isto por que o código em SQL, não irá conseguir encontrar nenhum dado que satisfaça a condição da pesquisa. De qualquer forma, se uma posição estiver aberta teremos o valor da posição em m_Base.TradeRequest.position. E isto é importante para nós. Já que na linha 83, estaremos verificando justamente esta mesma variável.
Então caso estejamos em uma conta HEDGING ou não exista uma posição aberta na conta NETTING. A linha 83 irá ter sucesso e então prosseguiremos para a abertura da posição. Porém como pode haver diversas outras posições já fechadas anteriormente, usamos a linha 85, para buscar a quantidade de registros presentes no banco de dados. Agora não teremos mais aquele problema onde mais de uma posição ou operação terá o mesmo valor de bilhete. O restante funciona como visto anteriormente.
Agora caso tenhamos uma posição sendo indicada. O teste na linha 83 irá falhar, passando o controle para a linha 97. Existem dois casos em que isto pode acontecer. O primeiro é uma chamada originada de ClosePosition. Já o segundo é uma chamada vinda de ToMarket. Mas neste segundo caso, podemos estar fechando uma posição. Invertendo a mão, ou até mesmo aumentando uma posição. Como são três casos distintos. Precisamos tratar eles de forma distinta. Isto por que cada um tem um resultado diferente um do outro.
No fragmento mostrado acima, estamos primeiro tratando do caso em que o usuário está pedindo um aumento da mão. Ou seja, ele estará fazendo preço médio. Aqui temos uma questão importante. Esta é justamente a questão do take profit e stop loss. No caso, estou assumindo que será feito um preço médio destes valores. Mas isto não é de fato o que pode vir a acontecer. O motivo é simples. Algumas plataformas, quando o operador faz preço médio, mantém os valores originais do take profit e do stop loss. Isto da posição original. Assim se manter a modelagem inicial da estratégia. Sendo que estes pontos originais, seriam como parciais a serem feitas no novo volume em aberto.
Como estou presumindo que isto não faz parte da estratégia. Estou forçando o sistema a montar um preço médio destes mesmos valores. Você, meu caro leitor, pode mudar isto se desejar. Mas lembre-se de também modificar o indicador de posição para refletir isto. Caso contrário, poderá ter problemas no uso do replay/simulador.
Entendido esta questão de implementação. Podemos passar para um outro ponto. Note que até o momento, não estamos ainda fechando a posição. Tudo que está sendo feito, é a abertura da posição e sua atualização. Conforme as coisas vão se dando, via interação com o usuário. À parte, onde podemos fechar a posição, isto quando acontece uma chamada vinda de ClosePosition, se parece bastante com uma inversão de mão. Porém a diferença é justamente o volume em ambos os casos. Assim podemos unir ambos os casos em um único. Tomando apenas o cuidado de checar o volume a fim de fechar ou não a posição. Pessoalmente não tenho o hábito de virar a mão em uma operação. Prefiro fechar a posição e aguardar uma nova entrada. Por isto, não tenho total certeza, de que o bilhete da posição é ou não modificado. Porém aqui, por conta de algumas questões iremos fechar a posição e então abrir uma nova. Caso estejamos virando a mão na operação. Assim, o mesmo fragmento mostrado acima deverá ser modificado para o fragmento mostrado logo abaixo.
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 acima implementa tanto a virada de mão, quanto também o pedido de fechamento de uma posição. Isto ao clicar no botão de fechar que se encontra presente na linha de preço de abertura. Porém se você clicar no botão de fechar, que se encontra presente no chart trade. A posição não será finalizada. O motivo é simples. No banco de dados, não estamos criando a condição que reporta o valor do número mágico. Este valor é usado para se conseguir identificar, o Expert Advisor que abriu a posição. Neste ponto temos duas alternativas a serem utilizadas. Você, meu caro leitor, deverá escolher a que melhor lhe atender. Uma é modificar o banco de dados a fim de indicar o número mágico do Expert Advisor. A outra é remover o teste do número mágico, durante o fechamento da posição. Vamos ver como cada uma seria implementada. Lembrando que você deverá escolher uma ou outra. Implementar ambas soluções é pura bobagem.
Removendo o teste do número mágico
Para remover o teste a fim de que, ao clicar no botão de fechar todas posições, que se encontra presente no indicador chart trade. Você simplesmente deverá mudar o seguinte fragmento mostrado abaixo.
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
Tudo que você precisa fazer, é remover a linha 227, que como você pode observar se encontra riscada no fragmento. Porém este tipo de solução, apesar de funcionar muito bem, não nos permite fazer uma outra coisa. Que é checar ou verificar operações efetuadas de forma automatizadas, por um Expert Advisor. Isto por que, você pode no mesmo banco de dados que estará sendo criado pelo replay/simulador. Verificar quando cada um dos Expert Advisores efetuaram algum tipo de operação.
Cruzando estas informações, o que é muito simples de ser feito em SQL. Você consegue verificar pontos em que os Expert Advisores têm o mesmo tipo de visão. Mesmo para tempos gráficos diferentes ou modelos diferentes de operação. Algo que com toda a certeza será bastante apreciado por muitos criadores dos conhecidos setups. Bem, mas para fazer isto será preciso mudar alguns pequenos detalhes. Então vamos ver como fazer isto no próximo tópico.
Adicionando o número mágico
O número mágico sempre aparece nos requerimentos presentes na classe C_Orders. Porém, aqui para podermos acessar ele, precisamos modificar o banco de dados também. Fazer isto é uma tarefa extremamente simples. Basta que mudemos o código, no seguinte 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
Note que primeiro, na linha 13 definimos nova variável. Logo depois na linha 28, dizemos ao SQL para adicionar a nova variável na tabela. Um detalhe, para que esta mudança tenha efeito, o banco de dados não deverá existir. Caso contrário, tão mudança não acontecerá. Porém você pode adicionar uma nova coluna chamada magic usando comandos do SQL. Consulte a documentação sobre a linguagem para mais detalhes. Caso você não saiba como fazer isto.
Para finalizar, na linha 63, retornamos o valor que constar no banco de dados, como sendo o número mágico. Feito esta mudança, devemos também mudar, algumas coisas na classe C_Orders. Mas as mudanças são ainda mais simples, como você pode ver no fragmento abaixo.
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
Note como é algo muitíssimo simples de ser feito. Tanto que nem vou me dar ao trabalho de explicar ou mesmo colocar todo o código. Pois a solução já foi efetuada. Ok, com isto o botão de fechar todas posições, que está presente no indicador chart trade, passa a funcionar. Com isto, você agora pode também usar diversos Expert Advisores dentro do replay/simulador. Podendo assim gerar um banco de dados a fim de analisar com calma cada um de seus Expert Advisores. Cruzando os dados entre eles no mesmo período e no mesmo ativo. Já que cada um terá um número mágico que o irá definir. Assim fica fácil de verificar qual o melhor cenário para usar um ou outro Expert Advisor.
Não estou querendo aqui comparar o replay/simulador, com o testador de estratégia do MetaTrader 5. Se bem que comparações de fato, parecem fazer sentido. Mas não se iluda com isto, meu caro leitor. O replay/simulador, está sendo desenvolvido para um propósito totalmente diferente. Este visa, permitir que operadores e usuários, consigam experimentar diversas estratégias diferentes. Isto fazendo uma simulação do que seria um teste a cega. O famoso FORWARD TESTING que diferente do BACKING TESTING cujo objetivo e procurar um padrão no passado. No FORWARD TESTING, procuramos ver se o nosso modelo também funciona sem que possamos ver as barras futuras. Isto por que, sempre que se tenta fazer um BACKING TESTING adequado, você sempre fica tentado a olhar as futuras barras. E isto invalida completamente qualquer modelo que estiver sendo testado.
Considerações finais
Neste artigo, apresentei o nosso sistema de simulação de operações a mercado. Isto para que fosse possível fazer as primeiras operações, no replay/simulador. Apesar deste sistema está praticamente terminado. Ainda existem algumas coisas a serem feitas e implementadas. Isto antes que a simulação de ordens a mercado de fato fique finalizada e concluída. Além de algumas poucas mudanças que ainda precisam ser feitas. Isto para que todo o sistema fique adequadamente configurado e em perfeito estado de funcionamento.
Mas mesmo com tudo que está já implementado. Confesso que já estou cansado de ficar preso na implementação deste sistema. Apesar de muitos consideraram ele algo complicado de ser implementado e desenvolvido. Confesso que estou decepcionado com o nível de dificuldade. Ele tem se mostrado bem mais simples, fácil de ser desenvolvido do que aparentava ser no começo desta série de artigos. E como já estou entediado de tanto escrever sobre ele. Pretendo dar um tempo.
Mas antes irei mostrar como finalizar pelo menos esta parte sobre a simulação das operações a mercado. Pois ainda falta alguns detalhes a serem implementados. Mesmo com o esforço, para fazer isto neste artigo, ainda ficou faltando pequenos detalhes a serem mostrados. Além de outros, que também serão modificados, por questões de semelhança com o que é visto quando se está em contado com o servidor de negociação real. Apesar de tudo, não irei deixar no anexo as aplicações ainda. Pois não estão no seu estágio de máxima utilização. Mas isto será feito em breve. Então nós vemos no próximo artigo, que possivelmente será o último desta fase de desenvolvimento do replay/simulador.
| Arquivo | Descrição |
|---|---|
| Experts\Expert Advisor.mq5 | Demonstra a interação entre o Chart Trade e o Expert Advisor (É necessário o Mouse Study para interação) |
| Indicators\Chart Trade.mq5 | Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação) |
| Indicators\Market Replay.mq5 | Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação) |
| Indicators\Mouse Study.mq5 | Permite interação entre os controles gráficos e o usuário (Necessário tanto para operar o replay simulador, quanto no mercado real) |
| Indicators\Order Indicator.mq5 | Responsável pela indicação de ordens de mercado, permitindo interação e controle das mesmas |
| Indicators\Position View.mq5 | Responsável pela indicação de posições de mercado, permitindo interação e controle das mesmas |
| Services\Market Replay.mq5 | Cria e mantém o serviço de replay e simulação de mercado (Arquivo principal de todo o sistema) |
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Implementação do mecanismo de breakeven em MQL5 (Parte 1): Classe base e modo de breakeven por pontos fixos
Do básico ao intermediário: Sobrecarga de operadores (IV)
MQL5 Trading Toolkit (Parte 7): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com as Funções da Última Ordem Pendente Cancelada
Algoritmo de otimização caótica — Chaos optimization algorithm (COA): Continuação
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso