Replay e Simulação de mercado: Gran Finale
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 (III), nos praticamente finalizamos esta etapa do desenvolvimento do replay/simulador. Porém ainda falta mostrar como efetuar um pequeno detalhe. E também mostrar algumas mudanças a serem feitas para que o sistema de replay/simulação, de fato possa ser usado com uma boa metodologia de treinamento. Neste artigo então iremos finalizar estes detalhes. Já que no próximo, quero começar a abordar um outro tema. Se bem que você, meu caro leitor, possa achar que ainda não implementamos todos os detalhes para usar o replay/simulador. Você verá que, sim, todos os detalhes já foram construídos ou estão praticamente prontos. Então como quero encerrar esta fase, neste artigo. Vamos ao que interessa.
Ajustando o número de dígitos
Durante bastante tempo, o sistema foi desenvolvido pensando e ser finalizado assim que o sistema de plotagem fosse terminado. Porém, surgiu por parte das pessoas mais próximas, a ideia de estender o replay/simulador para que fosse possível usar a simulação de operações. Visto que isto seria um desafio, já que muitos não saberiam como fazer tal coisa. De fato, no começo notei que se tratava de um desafio, já que não haveria suporte direto do MetaTrader 5, para se cumprir o objetivo.
Assim, foi necessário implementar um indicador para tal propósito. Este indicador, que é o de posição, foi implementado e testado completamente usando o servidor real de negociação. Uma vez que ele, já se encontrava em um ponto adequado. O mesmo foi transferido para dentro do replay/simulador. No entanto, ao fazer isto, uma falha surgiu. Esta falha se deve a uma propriedade, que para o sistema de plotagem, era necessário. Mas que acabou atrapalhando o indicador de posição. Fazendo com que as informações não fossem coerentes com as vistas quando estivéssemos em contato com o servidor real.
Resolver esta falha é muito simples. Porém, será necessário um pouco de edição. Para começar veja o fragmento abaixo.
105. //+------------------------------------------------------------------+ 106. int SetSymbolInfos(void) 107. { 108. int iRet; 109. 110. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5)); 111. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX); 112. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID); 113. 114. return iRet; 115. } 116. //+------------------------------------------------------------------+ 117. public : 118. //+------------------------------------------------------------------+ 119. C_FileTicks() 120. { 121. ArrayResize(m_Ticks.Rate, def_BarsDiary); 122. m_Ticks.nRate = -1; 123. m_Ticks.nTicks = 0; 124. m_Ticks.Rate[0].time = 0; 125. } 126. //+------------------------------------------------------------------+ 127. bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume) 128. { 129. C_FileBars *pFileBars; 130. C_Simulation *pSimulator = NULL; 131. int iMem = m_Ticks.nTicks, 132. iRet = -1; 133. MqlRates rate[1]; 134. MqlTick local[]; 135. bool bInit = false; 136. 137. pFileBars = new C_FileBars(szFileNameCSV); 138. ArrayResize(local, def_MaxSizeArray); 139. Print("Converting bars to ticks. Please wait..."); 140. while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) 141. { 142. if (!bInit) 143. { 144. m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX); 145. pSimulator = new C_Simulation(SetSymbolInfos()); 146. bInit = true; 147. } . . . 165. } 166. //+------------------------------------------------------------------+ 167. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 168. { 169. int MemNRates, 170. MemNTicks, 171. nDigits, 172. nShift; 173. datetime dtRet = TimeCurrent(); 174. MqlRates RatesLocal[], 175. rate; 176. MqlTick TicksLocal[]; 177. bool bNew; 178. 179. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 180. nShift = MemNTicks = m_Ticks.nTicks; 181. if (!Open(szFileNameCSV)) return 0; 182. if (!ReadAllsTicks()) return 0; 183. rate.time = 0; 184. nDigits = SetSymbolInfos(); 185. m_Ticks.bTickReal = true; 186. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 187. { 188. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 189. if (!BuildBar1Min(c0, rate, bNew)) continue; 190. if (bNew) 191. { 192. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 193. { 194. nShift = MemShift; 195. ArrayResize(TicksLocal, def_MaxSizeArray); 196. C_Simulation *pSimulator = new C_Simulation(nDigits); 197. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 198. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 199. delete pSimulator; 200.
Fragmento de C_FileTicks
Este fragmento é da classe C_FileTicks. Agora observe na linha 110, que estamos definindo, um valor para a propriedade SYMBOL_DIGITS. Porém ao fazer isto, o indicador de posição irá apresentar os valores de uma forma estranha. No entanto, este mesmo sistema, é usado na linha 184, a fim de na linha 196, dizer ao simulador como normalizar os valores que serão criados. A mesma coisa acontece na linha 145. Muito bem, isto será modificado como mostrado no fragmento abaixo.
105. //+------------------------------------------------------------------+ 106. int SetSymbolInfos(void) 107. { 108. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX); 109. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID); 110. 111. return (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5); 112. } 113. //+------------------------------------------------------------------+ 114. public : 115. //+------------------------------------------------------------------+ 116. C_FileTicks() 117. { 118. ArrayResize(m_Ticks.Rate, def_BarsDiary); 119. m_Ticks.nRate = -1; 120. m_Ticks.nTicks = 0; 121. m_Ticks.Rate[0].time = 0; 122. } 123. //+------------------------------------------------------------------+ 124. bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume) 125. { 126. C_FileBars *pFileBars; 127. C_Simulation *pSimulator = NULL; 128. int iMem = m_Ticks.nTicks, 129. iRet = -1; 130. MqlRates rate[1]; 131. MqlTick local[]; 132. bool bInit = false; 133. 134. pFileBars = new C_FileBars(szFileNameCSV); 135. ArrayResize(local, def_MaxSizeArray); 136. Print("Converting bars to ticks. Please wait..."); 137. while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) 138. { 139. if (!bInit) 140. { 141. m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX); 142. pSimulator = new C_Simulation(SetSymbolInfos()); 143. bInit = true; 144. } . . . 162. } 163. //+------------------------------------------------------------------+ 164. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 165. { 166. int MemNRates, 167. MemNTicks, 168. nDigits, 169. nShift; 170. datetime dtRet = TimeCurrent(); 171. MqlRates RatesLocal[], 172. rate; 173. MqlTick TicksLocal[]; 174. bool bNew; 175. 176. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 177. nShift = MemNTicks = m_Ticks.nTicks; 178. if (!Open(szFileNameCSV)) return 0; 179. if (!ReadAllsTicks()) return 0; 180. rate.time = 0; 181. nDigits = SetSymbolInfos(); 182. m_Ticks.bTickReal = true; 183. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 184. { 185. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 186. if (!BuildBar1Min(c0, rate, bNew)) continue; 187. if (bNew) 188. { 189. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 190. { 191. nShift = MemShift; 192. ArrayResize(TicksLocal, def_MaxSizeArray); 193. C_Simulation *pSimulator = new C_Simulation(nDigits); 194. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 195. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 196. delete pSimulator; 197.
Fragmento de C_FileTicks
Veja que a mudança é bem sutil. Porém quero ainda permitir que o número de dígitos seja configurado. Para isto, vamos a classe C_ConfigService, e modificar ela de forma que o usuário indique o número de dígitos. Isto é feito como mostrado no fragmento abaixo.
060. //+------------------------------------------------------------------+ 061. inline bool Configs(const string szInfo) 062. { 063. const string szList[] = { 064. "PATH", 065. "POINTSPERTICK", 066. "VALUEPERPOINTS", 067. "VOLUMEMINIMAL", 068. "LOADMODEL", 069. "ACCOUNT", 070. "MAXTICKSPERBAR", 071. "DIGITS" 072. }; 073. string szRet[]; 074. char cWho; 075. 076. if (StringSplit(szInfo, '=', szRet) == 2) 077. { 078. StringTrimRight(szRet[0]); 079. StringTrimLeft(szRet[1]); 080. for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; 081. switch (cWho) 082. { 083. case 0: 084. m_GlPrivate.szPath = szRet[1]; 085. return true; 086. case 1: 087. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); 088. return true; 089. case 2: 090. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); 091. return true; 092. case 3: 093. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); 094. return true; 095. case 4: 096. m_GlPrivate.ModelLoading = StringInit(szRet[1]); 097. m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading); 098. return true; 099. case 5: 100. if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true; 101. else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false; 102. else 103. { 104. Print("Entered account type is not invalid."); 105. return false; 106. } 107. return true; 108. case 6: 109. m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1])); 110. return true; 111. case 7: 112. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, (int) MathAbs(StringToInteger(szRet[1]))); 113. return true; 114. 115. } 116. Print("Variable >>", szRet[0], "<< not defined."); 117. }else 118. Print("Configuration definition >>", szInfo, "<< invalidates."); 119. 120. return false; 121. } 122. //+------------------------------------------------------------------+
Fragmento de C_ConfigService
Note como é simples de fazer as coisas. Na linha 71, colocamos em caixa alta a string, que o usuário deverá usar. Depois é só adicionar o tratador para aquela string. Isto foi feito adicionado a linha 111. Logo depois na linha 112, dizemos onde aquele valor será usado. E como ele foi adequadamente utilizado, na linha 113 retornamos true, para evitar que o serviço de replay/simulador não seja inicializado.
No código abaixo, vemos como o usuário poderia definir usar isto no arquivo de configuração.
01. [Config] 02. Path = WDO 03. PointsPerTick = 0.5 04. ValuePerPoints = 5.0 05. VolumeMinimal = 1.0 06. Account = NETTING 07. Digits = 3 08. 09. [Bars] 10. WDON22_M1_202206140900_202206141759 11. 12. [ Ticks -> Bars] 13. 14. [ Bars -> Ticks ] 15. WDON22_M1_202206150900_202206151759 16. 17. [Ticks] 18.
Arquivo de configuração
Note como é super simples. Na linha sete, o usuário informa quantos dígitos serão usados no ativo. Isto permite que o indicador de posição possa mostrar os valores da maneira como seria visto se estivéssemos ligados ao servidor real. Para que você entenda melhor o que estou mostrando. Vamos ver um caso prático. O contrato de dólar tem uma variação de 0.5, ou meio ponto, como se fala ao negociar tal contrato. Se no arquivo de configuração você fizer o que é mostrado no código acima. O resultado será o que é visto abaixo.

Ou seja, está errado, pois ao usar o indicador de posição em uma conta DEMO ou REAL, o valor apresentado terá um digito decimal e não três. Assim para resolver o problema, basta mudar a linha sete onde definimos o número de dígitos para a vista abaixo.
Digits = 1
Assim, o resultado será o visto abaixo, que de fato seria o correto.

Simples não é mesmo? A próxima questão que quero mostrar como resolver, pode ser causada, quando você não toma os devidos cuidado com o SQL. Isto por que, caso você venha a mudar o código, tentando fazer certas coisas, pode acontecer de você quebrar o banco de dados. Mas para explicar isto vamos a um novo tópico.
Evitando quebrar o banco de dados
Muita gente evita, ou tem grandes dificuldade em usar SQL, justamente por não entender que SQL, não é um programa, mas sim uma linguagem. Se bem utilizado o SQL evita que você tenha diversos problemas e aborrecimentos. Mas se mal utilizado, ele se torna um pesadelo impossível de ser resolvido. Uma das coisas que definitivamente quebra qualquer banco de dados, é a presença de registros duplicados, ou de registros com colunas com valores estranhos. Isto definitivamente destrói o banco de dados. Muitos imaginam ser necessário fazer uso de uma programação externa, ou extremamente cuidadosa para evitar tais problemas. Porém resolver isto é extremamente simples. Pois o próprio SQL faz isto para nós. Como? Bem, é algo tão simples, que basta dizer ao SQL, e ele irá tomar conta para nós. Evitando tais problemas. Assim como também podemos dizer que não queremos que os registros sejam modificados ou removidos. Mas isto eu deixo como tema de pesquisa. Uma dica: Use TRIGGERS que é o caminho mais simples.
No constructor da classe C_InServer, dizemos ao SQL, para criar uma tabela. Como nosso código permite integrar, ou até mesmo executar scripts SQL, fora do executável principal. Vamos mudar o código para podermos editar o script SQL em um arquivo. Porém vamos tornar este script um recurso do executável. Já mostrei como fazer isto antes. Dê uma olhada nos artigos sobre SQL nesta sequência. Mas o código a ser modificado é visto logo abaixo. Já com a sua nova aparência.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\Defines.mqh" 05. #include "..\SQL\C_ReplayDataBase.mqh" 06. //+------------------------------------------------------------------+ 07. #resource "Script.sql" as string SQL_01 08. //+------------------------------------------------------------------+ 09. class C_InServer : public C_ReplayDataBase 10. { 11. private : 12. bool m_IsReplay; 13. struct stLocal 14. { 15. ulong numberMagic, 16. ticket; 17. int type; 18. double volume, 19. price, 20. sl, 21. tp; 22. }m_Info; 23. public : 24. //+------------------------------------------------------------------+ 25. C_InServer() 26. :C_ReplayDataBase(), 27. m_IsReplay(_Symbol == def_SymbolReplay) 28. { 29. ZeroMemory(m_Info); 30. ExecResourceSQL(SQL_01); 31. } 32. //+------------------------------------------------------------------+
Fragmento de C_InServer
Já o script é mostrado abaixo.
CREATE TABLE IF NOT EXISTS tb_Replay ( magic NOT NULL DEFAULT 0, -- ID Expert Advisor ticket PRIMARY KEY NOT NULL, -- Position Ticket type NOT NULL, -- Position type volume NOT NULL, -- Position volume price NOT NULL, -- Opening point sl NOT NULL DEFAULT 0, -- Stop loss point tp NOT NULL DEFAULT 0, -- Take profit point history NOT NULL DEFAULT 0 -- If false, the position is open );
Script em SQL
Basicamente o que estamos fazendo aqui, é dizer ao SQL como os registros deverão ser criados. Algo bastante simples. Porém caso você não saiba onde colocar este arquivo de script. Você o deverá colocar no local indicado na imagem abaixo

E no momento em que o código for compilado, você deverá ver a mensagem que está em destaque na imagem a seguir.

Com isto, eliminamos muitos dos problemas que poderiam acontecer no banco de dados. Algo simples, porém bastante funcional. Com tudo isto, praticamente, não temos mais o que mexer, a fim de ajustar as coisas. Mas ainda falta um detalhe a ser implementado. Este detalhe é fazer com que as linhas de take profit e stop loss, se tornem funcionais. Mas você verá que é algo bastante simples. Porém para separar os assuntos, vamos a um novo tópico.
Take profit e Stop Loss
Aqui existe uma questão, que muitos operadores iniciantes, tem dúvidas. E muitos operadores já com algum tempo de mercado, pensam sobre o tema, de forma no mínimo equivocada. As linhas de take profit e stop loss, não são ordens do tipo pendente. Elas são ordens, porém de um tipo especial. Algumas plataformas e mesmo operadores, fazem com que as coisas fiquem bem mais confusas do que realmente são. Digo isto, pois, aqui no BRASIL, analistas e operadores profissionais, por usar outras plataformas, tem uma grande dificuldade em entender o que irei explicar. Não existe aquela de dizer para ajustar o tal OFFSET, a fim de que a linha de stop loss, não feche a posição. Isto é a maior das falácias ditas por operadores e analistas de mercado aqui no BRASIL. O tal OFFSET que muitos tanto falam, somente existe em um tipo de ordem chamada BUY STOP LIMIT ou SELL STOP LIMIT como você pode ver em destaque na imagem abaixo.

Este valor indicado na imagem abaixo, somente é configurado para ordens pendentes. Antes de elas se tornarem uma posição. Não existe esta de que é preciso ajustar o offset para evitar da posição ficar aberta, por conta que o stop loss ou take profit foram pulados. A propósito, é curioso o fato de que apenas o stop loss poder ser pulado. NUNCA o take profit. Por que SERÁ? Bem, mas não estou aqui para falar sobre isto. Quero mostrar como você pode fazer com que as linhas de take profit e stop loss, passem a ser funcionais. Isto por que até o momento, você apenas pode abrir a posição e fechar a mesma. Porém isto deve ser feito manualmente. Então aquelas linhas de take profit e stop loss, em se tratando do replay/simulador, ainda não funcionam. Apesar de você as poder manipular.
Para torná-las funcionais, é preciso simplesmente fazer um teste. Somente isto, nada mais. Caso o teste seja positivo, enviamos um pedido ao sistema para que a posição seja fechada. A coisa é bastante simples de ser feita. Porém, apesar de ser algo simples é preciso tomar um certo cuidado. Isto por que, se você fizer as coisas sem o devido cuidado, irá criar uma falsa impressão, o que poderá lhe trazer alguns problemas. Mas é justamente esta programação do teste, que se feita de forma descuidada, permitirá que você consiga fazer uma outra coisa. Que irei explicar em breve. Mas primeiro vamos ver como a coisa deverá ser feita. Para que o stop loss e take profit passem a funcionar.
Bom, existem diversos pontos dentro do sistema que podemos utilizar para fazer com que stop loss e take profit, sejam funcionais. Alguns pontos são mais interessantes, e outros nem tanto. Isto por que você precisará transferir informações entre aplicações. Então, o pior dos locais a colocar o tal teste é justamente no Expert Advisor. Isto por que, cada Expert Advisor, que você vier a criar, terá que ter o mesmo código usado para testar, quando no replay/simulador, o take profit e o stop loss. Definitivamente é o pior local. Poderíamos fazer o teste no indicador de controle? Sim poderíamos. Mas tem locais melhores. Que tal no indicador Chart Trade? Também seria viável, e plausível. Mas ainda temos locais melhores para fazer isto.
Ok, então que tal fazer no indicador de posição. Seria perfeito, não seria? De fato seria um local bastante interessante. Isto por que não precisaríamos buscar a informação no banco de dados. O indicador de posição, já tem as informações necessárias. Assim como também seria um bom local, colocar o teste dentro do serviço de replay. Neste caso precisaríamos fazer uma pesquisa, de tempos em tempos dentro do banco de dados. Isto tornaria o sistema bem interessante para simular determinadas situações específicas. Mas não quero ficar ali lendo coisas o tempo todo no banco de dados. Sendo assim, faremos o teste no indicador de posição. Mas considere tentar fazer isto dentro do serviço de replay. Você notará que é algo bem interessante de ser feito.
Muito bem, tomada a decisão vamos ver o que deverá ser feito. Isto para que o código do indicador de posição, possa efetivamente "SIMULAR" o que seria feito pelo servidor de negociação real. Você pode ver isto observando o fragmento abaixo.
018. //+------------------------------------------------------------------+ 019. struct st00 020. { 021. ulong ticket; 022. string szShortName, 023. szSymbol; 024. double priceOpen, 025. var, 026. sl, tp, 027. tickSize; 028. char digits; 029. bool bIsBuy; 030. }m_Infos; 031. //+------------------------------------------------------------------+ . . . 071. //+------------------------------------------------------------------+ 072. int OnInit() 073. { 074. ZeroMemory(m_Infos); 075. Order = new C_InServer(); 076. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 077. if (!CheckCatch(user00)) 078. { 079. ChartIndicatorDelete(0, 0, def_ShortName); 080. return INIT_FAILED; 081. } 082. 083. return INIT_SUCCEEDED; 084. } 085. //+------------------------------------------------------------------+ 086. int OnCalculate (const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], 087. const double &high[], const double &low[], const double &close[], const long &tick_volume[], 088. const long &volume[], const int &spread[]) 089. { 090. ProfitNow(); 091. 092. if ((close[rates_total - 1] == m_Infos.sl) || (close[rates_total - 1] == m_Infos.tp)) 093. EventChartCustom(0, evMsgClosePositionEA, m_Infos.ticket, 0, m_Infos.szSymbol); 094. 095. return rates_total; 096. } 097. //+------------------------------------------------------------------+ 098. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 099. { . . . 144. volume = (*Order)._PositionGetDouble(POSITION_VOLUME); 145. (*Open).UpdatePrice(0, m_Infos.priceOpen = (*Order)._PositionGetDouble(POSITION_PRICE_OPEN), volume, m_Infos.var); 146. (*Take).UpdatePrice(m_Infos.priceOpen, m_Infos.tp = (*Order)._PositionGetDouble(POSITION_TP), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_SL)); 147. (*Stop).UpdatePrice(m_Infos.priceOpen, m_Infos.sl = (*Order)._PositionGetDouble(POSITION_SL), volume, m_Infos.var, (*Order)._PositionGetDouble(POSITION_TP)); 148. ProfitNow();
Fragmento do indicador de posição
Muito bem, vamos então entender o que está sendo feito neste fragmento. Na linha 26, defino os novos valores que serão usados aqui no indicador. Na linha 74, inicializo a estrutura com valores zero. Isto para garantir que não teremos LIXO nas variáveis. A próxima coisa a ser feita é na linha 146 e 147, onde capturamos os valores de take profit e stop loss. Até este ponto, não fizemos nada de grandioso. Mas agora preste bastante atenção no que estará acontecendo na função OnCalculate, que se encontra presente na linha 86.
Observe que mudei ela, frente ao que era visto anteriormente. Isto para garantir que estaremos testando o valor presente no array de preço de fechamento. Se bem que poderíamos usar o modelo anterior. Mas ali tínhamos um problema, que era o fato de que o usuário ou operador poderia vir a mudar o valor no array price. E isto tornaria toda a ideia usada no indicador, completamente inválida.
O detalhe no qual quero que você, meu caro e estimado leitor preste atenção está na linha 92. Mas antes que eu me esqueça. Se o teste efetuado nesta linha 92 tiver sucesso, iremos disparar um evento na linha 93. Este tem como finalidade, forçar uma mensagem customizada para o Expert Advisor. Esta mensagem tem como objetivo, dizer ao Expert Advisor, para que ele envie um requerimento o servidor, de forma a fechar a posição. A posição em questão é justamente a posição do indicador.
Agora vamos voltar a linha 92. Pois é nela que mora todo o macete. Aqui temos uma questão, da qual se você, meu caro leitor conseguir compreender. Conseguirá criar o indicador de ordens pendentes. E não só isto, conseguirá fazer, caso você venha a implementar o tal indicador de ordens pendentes, funcionar perfeitamente. Então para separar o assunto vamos a um novo tópico. Mas ele irá realmente tratar do que está sendo feito na linha 92. Então caso você não saiba como realmente as coisas funcionam. Preste muita atenção a este novo tópico.
Tipos de ordens pendentes e como fazer elas funcionarem
Apesar do título deste tópico, sugerir que irei tratar de ordens pendentes. Não será bem isto que faremos. O que faremos é entender o que acontece na linha 92 no último fragmento do tópico anterior. Lá você pode notar que estou comparando a preço de fechamento com outros dois pontos. Um que é o take profit e o outro que é o stop loss. Pois bem. Quando qualquer um destes testes, vier a ser verdadeiro, teremos um disparo que fará com que durante o replay/simulação a posição seja fechada. Mas e se em vez de testar uma igualdade estivéssemos testando uma outra coisa. O que aconteceria? Por exemplo, se este indicador de posição, viesse a ser modificado, para no lugar de mostrar uma posição aberta, mostrasse uma ordem pendente. Será que conseguiríamos simular, a abertura de uma posição?
Uau, se você de fato pensou isto, meu caro leitor, gostaria de realmente fazer uma entrevista com você, e talvez lhe contratar. Pois você acabou de pensar como implementar o sistema de ordens pendentes. Para entender isto, caso você não tenha pensado nesta hipótese. Vamos ver algumas figuras e entender como elas poderão disparar eventos a fim de simular a abertura de uma posição. Isto sem se quer mexer em nenhuma parte do código já existente. Porém todas as mensagens ou eventos customizados a serem criados, podem ser vistos na classe C_ChartFloatingRAD. Isto entre as linhas 345 e 356. Bastando assim que você crie a string, com os valores que estiverem presentes no indicador de ordem pendente. Então será apenas necessário que você faça apenas algumas modificações no indicador de posição, para que ele se tornasse um indicador de ordens pendentes. E tudo irá de fato funcionar. A primeira das figuras, pode ser vista na imagem abaixo.

Este é o primeiro tipo de ordem pendente. Ou seja, uma ordem do tipo BUY LIMIT. O que esta ordem faz é o seguinte: Quando o preço de fechamento, entrar na zona VERDE, ou seja, quando ele for menor ou igual ao preço onde a ordem está, será feita uma compra a mercado. Isto pode ser simulado, fazendo o seguinte teste em OnCalculate:
092. if (close[rates_total - 1] <= m_Infos.open) 093. EventChartCustom(...See the article for details...);
Note que no exato momento em que o teste obtiver sucesso, um evento será disparado. Tudo que você precisa fazer é configurar este evento, de forma que o Expert Advisor, imagine que o evento foi originado no Chart Trade. Apesar de ele ter vindo do indicador de ordem pendente. Lembre-se de finalizar o indicador de ordem pendente. Isto para que ele não fique disparando eventos um atrás do outro.
Da mesma forma que foi feito paraBUY LIMIT, temos uma nova figura, vista abaixo.

Neste caso estamos fazendo uma venda, caso o preço de fechamento caia abaixo do ponto indicado. Neste caso o código de disparo, será o mesmo visto anteriormente. A única diferença será que no lugar de uma compra a mercado, você fará uma venda a mercado. Assim podemos ver a próxima figura, esta é vista em seguida.

Neste caso temos um tipo diferente de percepção. Já que não iremos vender caso o preço caia, mas sim se ele vier a subir acima de um dado valor. Este valor estará sendo indicado no preço onde a ordem pendente estará. No caso, será preciso substituir o teste por este visto abaixo.
092. if (close[rates_total - 1] >= m_Infos.open) 093. EventChartCustom(...See the article for details...);
Veja que a mudança no código é muito sutil. Mas mesmo assim funcionará, fazendo apenas isto. O mesmo código funcionará para a figura logo abaixo.

O detalhe aqui, é que agora, o evento a ser enviado, não é de uma venda a mercado, e sim de uma compra a mercado. Notaram como é muito simples fazer a simulação de uma ordem pendente. Por isto, estou finalizando as coisas neste artigo. Não vejo motivos para criar novos artigos apenas para mostrar isto. Se você está acompanhando esta sequência desde o seu início, já conseguirá facilmente fazer os devidos ajustes a fim de que o código consiga executar as simulações de ordens pendentes.
Agora existe uma outra questão, que para não confundir você, meu caro leitor, vamos ver isto em um novo tópico.
BUY STOP LIMIT & SELL STOP LIMIT
Muito bem, se você conseguiu entender como simular os tipos anteriores. Este daqui agora será ainda mais simples de ser feito. Parece complicado, mas não é assim tão complicado. É tudo uma questão de entender o que se precisa fazer, e fazer o que foi planejado.
Estes tipos,BUY STOP LIMIT e SELL STOP LIMIT, na verdade não são ordens diretas, elas são ordens indiretas. Ou seja, elas de fato não são disparadas a qualquer preço, ou executadas assim que o preço atinge um dado valor. Como elas são ordens voltadas para evitar grandes picos de volatilidade do mercado. Acredito que não faz muito sentido você as implementar. Mas se ainda assim, desejar fazer isto. É preciso entender como elas de fato funcionam. Par isto veja as imagens logo abaixo.


Ambas imagem representam como é estruturada uma ordem BUY STOP LIMIT e SELL STOP LIMIT, isto pelo ponto de vista do servidor. Tanto que se você colocar este tipo de ordem no gráfico do MetaTrader 5, verá que aparecerá não três, mas quatro linhas. Mas por que? O motivo é que três das quatro linhas de fato representa uma das ordens vista no tópico anterior. A quarta linha que aparece no gráfico, neste tipo de ordem, representa o gatilho. Este fará com que um dos quatro tipos vistos no tópico anterior venham a surgir, podendo ou não vir a ser executadas imediatamente. E é aqui que mora o tal do OFFSET.
Você pode ver nas figuras que existe uma distância entre as ordens. Esta distância é o tal offset. Se a volatilidade for muito forte, pode acontecer do gatilho ser disparado, porém uma das quatro ordens, vista no tópico anterior, que vier a surgir, não seja de fato disparada. Caso isto aconteça você verá uma ordem pendente no gráfico. Ao contrário do que seria esperado. Já que muitos esperariam ver uma posição aberta.
Como eu disse, este tipo de ordem, é bem específica. Para criar ela, temos que fazer um primeiro teste, baseado no que foi visto no tópico anterior. Assim que este teste passar, não voltaremos mais a fazer ele, e passamos a fazer um dos testes visto anteriormente. Simples assim. No momento em que este segundo teste vier a dar verdadeiro, iremos disparar e evento que fará a simulação da abertura da posição.
Desta maneira teremos com praticamente nenhum esforço, criado toda a simulação de ordens pendentes.
Mostrando o histórico
Como último ponto a ser dito nesta série de artigos, temos a visualização do histórico de operações feitas no replay/simulador. Este, pode ser feito de diversas maneiras. Tudo irá depender de como você deseja visualizar os dados. Já que eles estarão disponíveis em um banco de dados SQL, não será problema para ninguém conseguir analisar o que aconteceu. Mas talvez você queira ver isto diretamente no gráfico, isto para poder visualizar algum tipo de melhoria na estratégia de negociação. Bem, cria tão indicador, é ainda mais simples. Se bem, que você precisará adicionar os dados sobre o tempo em que as operações aconteceram. Isto dentro da tabela do banco de dados. Mas como é algo muito simples de ser feito, acredito que qualquer entusiasta conseguirá tal coisa. Uma vez feito isto, bastará você varrer o banco de dados, buscando linhas no registro onde a coluna de histórico, esteja com um valor igual a um.
Pois valores iguais a zero, indicam que se trata de uma posição em aberto. Uma vez com os dados da linha, bastará que você adicione alguns objetos no gráfico do tipo OBJ_ARROW_BUY e OBJ_ARROW_SELL, junto com um objeto OBJ_TREND. Isto criará exatamente a indicação de histórico de operações diretamente no gráfico. Mas você também pode desejar fazer isto, assim que a posição for fechada. De qualquer maneira, a forma de fazer vária pouco. Tudo depende do que você deseja de fato criar, e precisa criar.
Considerações finais
Bem, finalmente chegamos a um sistema de replay/simulador que você, meu caro e paciente leitor, pode finalmente usufruir. Sei que muitos poderiam imaginar que seria feito mais artigos, explicando mais pontos do sistema. Mas considero desnecessário, visto que tudo que necessita ser feito, foi explicado neste artigo. Considere implementar as partes faltantes, não como uma sacanagem da minha parte, mas sim como um desafio para você, que esteja, começando a estudar programação. Eu mesmo comecei assim. Então se você de fato, deseja se tornar um programador de qualidade, comece por estes desafios que deixei em aberto. Leia os artigos com atenção, observe como fui criando o sistema aos poucos, e com cada vez menos modificações no mesmo. Isto sempre tentando reutilizar o que já existia, ou já havia sido implementado. Não tente criar algo totalmente do zero, sem antes tentar modificar as coisas como expliquei ao logo desta série.
E um último detalhe. Se no indicador de posição você usar um dos testes mostrados na explicação de como simular as ordens pendentes. Irá conseguir construir um sistema que não lhe deixará na mão. Isto por que, mesmo que o stop loss ou take profit venham a ser pulados. O próprio indicador de posição perceberá isto, e enviará um requerimento para o Expert Advisor, fechar a posição para você.
Como eu disse no decorrer da sequência. No momento em que você ler qualquer um destes artigos. O código neles já estará completamente obsoleto. Lá na sequência sobre como construir um Expert Advisor automático. Eu não mostrei o que estou dizendo neste artigo. Ou nesta sequência. Então, não espere que eu lhe mostre tudo de uma só vez. Continue acompanhando minhas publicações e estudando. E sempre estudando.
E para quem não sabe como compilar o código completo do replay/simulador. No anexo, vou deixar todas as aplicações e arquivos necessários para que você possa ter pelo menos, um exemplo. Assim poderá quem sabe, se interessar em estudar e saber como programar as partes que faltantes. Mas que foram explicadas neste último artigo. No mais, desejo a todos uma boa sorte e nos vemos no próximo artigo. Onde começarei a tratar de um outro assunto. E quem sabe, futuramente eu venha a atualizar este sistema de replay/simulador. Mas isto só DEUS sabe se eu realmente farei.😂😁👍. Um forte abraço a todos e nos vemos em um outro artigo.
| 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.
Do básico ao intermediário: Sobrecarga de operadores (V)
Redes neurais em trading: Previsão de séries temporais com o auxílio da decomposição modal adaptativa (Conclusão)
Redes Adversariais Generativas (GANs) para Dados Sintéticos em Modelagem Financeira (Parte 2): Criação de Símbolo Sintético para Testes
Análise quantitativa de tendências: coletando estatísticas em Python
- 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