Desenvolvendo um sistema de Replay (Parte 62): Dando play no serviço (III)
Introdução
No artigo anterior Desenvolvendo um sistema de Replay (Parte 61): Dando play no serviço (II), expliquei sobre um problema que estamos tendo com o sistema no momento que vamos usar o modo simulação. Tal problema não decorre necessariamente de alguma falha catastrófica na aplicação que estamos desenvolvendo. Mas sim por conta da velocidade na resposta, que todo o sistema vem tendo. Tal tempo de resposta, não se mostrou adequado, para que a aplicação consiga dar cabo de todos os dados que estão sendo enviados a ela. Sendo assim necessário, que adaptemos de alguma forma as coisas. Mesmo que nosso serviço venha a se apresentar um pouco fora de uma realidade prefeita, sabemos que tal realidade não se apresenta na prática.
A melhor solução que consegui imaginar, foi ajustar os limites máximo que podem estar presentes na simulação. Mas durante o artigo mostrarei com mais calma as implicações de tais mudanças, e por que escolhi este caminho específico. Além deste fato, existe um outro. Mas este segundo está de fato relacionado aos dados reais, ou simulados fora da aplicação que está sendo desenvolvida. Por mais estranho que possa parecer, em alguns casos, especialmente relacionado a contratos futuros, pode acontecer de termos uma quantidade muito alta de ticks, ou negociações em uma barra de um minuto. Quando isto acontece, mesmo quando estamos ligados ao servidor de negociação, temos problemas relacionados a velocidade com que a plataforma MetaTrader 5, consegue responder e nos mostrar a movimentação que está acontecendo no preço. Se por um acaso você nunca se deparou com este tipo de coisa, talvez imagine que possa ser algum tipo de problema correlacionado ao equipamento que esteja sendo usado para rodar o MetaTrader 5, ou alguma falha no sistema operacional. Mas lamento dizer que isto é pura besteira, espalhada por quem de fato não entende absolutamente nada de computação.
Assim, se mesmo ligado em um servidor de negociação REAL, temos tais problemas, onde a plataforma não consegue digerir a imensa quantidade de informações que estão chegando. O que dirá quando formos fazer um replay de tais dados. Será um desastre total. Já que a qualidade da temporização irá pro espaço. Assim iremos também promover um valor de limite para os dados reais, ou simulados de fora da plataforma. Justamente para evitar que tais coisas, como uma dificuldade da plataforma em digerir os dados, não venha a de fato ser notada, ou mesmo venha a surgir. Então vamos começar a ver como o novo código ficará.
Um novo conceito. Um novo sistema
Talvez o título deste tópico, não tenha ficado assim tão representativo e ao mesmo tempo explique o que de fato faremos. Mas começaremos vendo o que mudou na classe responsável por gerar e modelar qualquer simulação para o nosso sistema. Mas antes de ver o código da classe de simulação, precisamos ver o arquivo de definição. Visto que foi adicionada uma linha ao mesmo, e esta será utilizada em alguns pontos do código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaxTicksVolume 2000 16. //+------------------------------------------------------------------+ 17. union uCast_Double 18. { 19. double dValue; 20. long _long; // 1 Information 21. datetime _datetime; // 1 Information 22. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 23. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 24. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 25. }; 26. //+------------------------------------------------------------------+ 27. enum EnumEvents { 28. evHideMouse, //Hide mouse price line 29. evShowMouse, //Show mouse price line 30. evHideBarTime, //Hide bar time 31. evShowBarTime, //Show bar time 32. evHideDailyVar, //Hide daily variation 33. evShowDailyVar, //Show daily variation 34. evHidePriceVar, //Hide instantaneous variation 35. evShowPriceVar, //Show instantaneous variation 36. evSetServerTime, //Replay/simulation system timer 37. evCtrlReplayInit, //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Código fonte do arquivo Defines.mqh
A linha 15 é que foi adicionada ao código. Mas não vou explicar aqui, o motivo deste valor, isto será visto no momento em que formos usar ele no código de simulação. Pois este será o primeiro local onde este valor será utilizado. Agora que já vimos esta mudança, podemos passar para o código fonte da classe responsável por simular os ticks. Esta pode ser vista logo abaixo e já se encontra modificada.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Simulation 007. { 008. private : 009. //+------------------------------------------------------------------+ 010. int m_NDigits; 011. bool m_IsPriceBID; 012. double m_TickSize; 013. struct st00 014. { 015. bool bHigh, bLow; 016. int iMax; 017. }m_Marks; 018. //+------------------------------------------------------------------+ 019. template < typename T > 020. inline T RandomLimit(const T Limit01, const T Limit02) 021. { 022. T a = (Limit01 > Limit02 ? Limit01 - Limit02 : Limit02 - Limit01); 023. return (Limit01 >= Limit02 ? Limit02 : Limit01) + ((T)(((rand() & 32767) / 32737.0) * a)); 024. } 025. //+------------------------------------------------------------------+ 026. inline void Simulation_Time(const MqlRates &rate, MqlTick &tick[]) 027. { 028. for (int c0 = 0, iPos, v0 = (int)(60000 / m_Marks.iMax), v1 = 0, v2 = v0; c0 <= m_Marks.iMax; c0++, v1 = v2, v2 += v0) 029. { 030. iPos = RandomLimit(v1, v2); 031. tick[c0].time = rate.time + (iPos / 1000); 032. tick[c0].time_msc = iPos % 1000; 033. } 034. } 035. //+------------------------------------------------------------------+ 036. inline void CorretTime(MqlTick &tick[]) 037. { 038. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 039. tick[c0].time_msc += (tick[c0].time * 1000); 040. } 041. //+------------------------------------------------------------------+ 042. inline int Unique(const double price, const MqlTick &tick[]) 043. { 044. int iPos = 1; 045. 046. do 047. { 048. iPos = (m_Marks.iMax > 20 ? RandomLimit(1, m_Marks.iMax - 1) : iPos + 1); 049. }while ((m_IsPriceBID ? tick[iPos].bid : tick[iPos].last) == price); 050. 051. return iPos; 052. } 053. //+------------------------------------------------------------------+ 054. inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[]) 055. { 056. if (m_IsPriceBID) 057. { 058. tick[iPos].bid = NormalizeDouble(price, m_NDigits); 059. tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits); 060. }else 061. tick[iPos].last = NormalizeDouble(price, m_NDigits); 062. } 063. //+------------------------------------------------------------------+ 064. inline void Random_Price(const MqlRates &rate, MqlTick &tick[]) 065. { 066. for (int c0 = 1; c0 < m_Marks.iMax; c0++) 067. { 068. MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); 069. m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh; 070. m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow; 071. } 072. } 073. //+------------------------------------------------------------------+ 074. inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[]) 075. { 076. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 077. { 078. tick[c0].volume_real = 1.0; 079. tick[c0].volume = 1; 080. } 081. if ((m_Marks.iMax + 1) < rate.tick_volume) for (int c0 = (int)(rate.tick_volume - m_Marks.iMax); c0 > 0; c0--) 082. tick[RandomLimit(0, m_Marks.iMax - 1)].volume += 1; 083. for (int c0 = (int)(rate.real_volume - m_Marks.iMax); c0 > 0; c0--) 084. tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0; 085. } 086. //+------------------------------------------------------------------+ 087. inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode, int iDesloc) 088. { 089. double vStep, vNext, price, vH = High, vL = Low; 090. char i0 = 0; 091. 092. vNext = vStep = (Out - In) / ((High - Low) / m_TickSize); 093. for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++) 094. { 095. price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc)); 096. price = (price > vH ? vH : (price < vL ? vL : price)); 097. MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick); 098. switch (iMode) 099. { 100. case 1: 101. i0 |= (price == High ? 0x01 : 0); 102. i0 |= (price == Low ? 0x02 : 0); 103. vH = (i0 == 3 ? High : vH); 104. vL = (i0 ==3 ? Low : vL); 105. break; 106. case 0: 107. if (price == Close) return c0; 108. default: 109. break; 110. } 111. if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue; 112. vNext += vStep; 113. vL = (iMode != 2 ? (Close > vL ? (i0 == 3 ? vL : vL + m_TickSize) : vL) : (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize))); 114. vH = (iMode != 2 ? (Close > vL ? vH : (i0 == 3 ? vH : vH - m_TickSize)) : (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH))); 115. } 116. 117. return Out; 118. } 119. //+------------------------------------------------------------------+ 120. public : 121. //+------------------------------------------------------------------+ 122. C_Simulation(const int nDigits) 123. { 124. m_NDigits = nDigits; 125. m_IsPriceBID = (SymbolInfoInteger(def_SymbolReplay, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID); 126. m_TickSize = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 127. } 128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume = def_MaxTicksVolume) 130. { 131. int i0, i1, i2; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. m_Marks.iMax = ((int)rate.tick_volume > m_Marks.iMax ? m_Marks.iMax : (int)rate.tick_volume - 1); 136. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 137. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 138. Simulation_Time(rate, tick); 139. MountPrice(0, rate.open, rate.spread, tick); 140. if (m_Marks.iMax > 10) 141. { 142. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 143. i1 = m_Marks.iMax - i0; 144. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 145. i2 = (i2 == 0 ? 1 : i2); 146. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 147. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 148. RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1, i2); 149. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 150. m_Marks.bHigh = m_Marks.bLow = true; 151. 152. }else Random_Price(rate, tick); 153. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 154. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 155. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 156. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 157. CorretTime(tick); 158. 159. return m_Marks.iMax; 160. } 161. //+------------------------------------------------------------------+ 162. }; 163. //+------------------------------------------------------------------+
Código fonte do arquivo C_Simulation.mqh
Aqui, talvez e muito provavelmente você não conseguirá ver nenhuma mudança. Ainda mais por que faz um bom tempo que não mexemos nesta classe. A última vez que mexemos aqui, foi quando o artigo sobre o Random Walk foi publicado. Mas devido ao fato de que precisamos fazer com que o sistema tenha alguma consistência em referência ao tempo, mesmo com uma certa degradação no número de ticks que serão gerados. Foi preciso modificar levemente este código.
Mas para que você não fique apenas olhando o mesmo, sem de fato entender o que mudou. Vou mostrar um dos pontos, apesar de existirem outras mudanças pequenas em alguns pontos, mas que não merecem realmente algum tipo de destaque, já que se tais mudanças aconteceram, foram para que o processo viesse a se adequar, de alguma forma a uma nova metodologia. De qualquer forma, o principal ponto e neste caso foi uma adição que ocorreu, se dá na linha 16. Esta variável não existia originalmente no código da classe. Mas antes de ver o que esta variável implicou, em termos de mudanças em todo o código da classe. Vamos ver onde ela é inicializada. E você muito provavelmente errou o ponto de inicialização, já que muito provavelmente você suspeitou que a inicialização se daria no constructor da classe. Mas não, ela irá se dar em um outro ponto. Sendo mais preciso entre as linhas 134 e 135. Então muita atenção o que irá está acontecendo nestas duas linhas. Pois isto será muito importante, caso você queira modificar o sistema. Na linha 129, temos a declaração da função responsável por efetivamente criar a simulação dos ticks. No entanto, agora temos um parâmetro extra, que informa justamente o máximo de ticks que serão simulados aqui. Pois bem, lembra da linha que foi adicionada no arquivo Defines.mqh? Um dos lugares que aquela definição é utilizada é justamente este daqui. Mas vamos entender o que está acontecendo. Isto para que se você desejar modificar as coisas, saiba o que tais mudanças poderão fazer a respeito de como o código irá de fato se comportar. Então, ao fazer a chamada para efetuar a simulação dos ticks, você deverá também informar um valor, que é a quantidade máxima de ticks que deverão ser simulados. Tal valor não é obrigatório, como você pode ver, ele está recebendo um valor default. No entanto, caso o valor repassado seja igual ou menor que zero, assumiremos que o máximo de ticks será de um. Este valor não é arbitrário. Ele tem seu motivo de ser. Isto por que, no caso do Forex, o menor valor que poderá ser visto como volume de ticks é justamente este, o valor um. Porém, caso você informe um valor acima daquele que é informado na definição, a classe ignorará o valor que você esteja informando e usará o valor máximo que se encontra definido dentro do sistema. No caso este valor está sendo definido no arquivo de cabeçalho Defines.mqh. E este será o valor que indica o número máximo de ticks que irá estar sendo simulados internamente. No entanto, qualquer valor entre estes dois extremos, será usado como valor máximo de ticks simulados. Assim você poderá ajustá-lo dentro deste range.
Agora um detalhe: Este valor máximo definido, não foi escolhido por um acaso. Quando pegamos e dividimos a barra de um minuto por este valor, que no caso é de dois mil, teremos cada tick sendo lançado em aproximadamente 30 milissegundos, o que de certa forma é um valor bastante adequado para que o movimento seja suave e ao mesmo tempo constante durante todo o processo de plotagem.
Você pode até usar valores maiores, mas esteja ciente que este não será de fato o valor real a ser simulado, e sim o valor máximo que poderá ser simulado. Esta explicação se aplica a linha 134. Mas, o valor real, é de fato definido na linha 135. Então quando a linha 135 for ajustar o número máximo real de ticks a serem simulados, testaremos o valor que foi montado na linha 134, com o valor presente na informação da barra. E caso o valor da linha 134, venha a ser menor que o valor costado na barra, ele será utilizado. Mas se o valor da barra for menor, o valor que você estiver informando para ser simulado será ignorado e o número máximo será o que estiver na barra.
Como informei no começo, este tipo de modificação, fez com que todos os testes relacionados ao número de ticks máximos a serem criados, fossem revisados. Então as funções e procedimentos dentro desta classe, passaram por ligeiras mudanças. E como as mesmas são simples de serem compreendidas, não entrarei em detalhes sobre como cada uma das funções e procedimentos funcionam. Caso você esteja na dúvida, bastará consultar o artigo sobre o Random Walk. Este pode ser visto em Desenvolvendo um sistema de Replay (Parte 15): Nascimento do SIMULADOR (V) RANDOM WALK
Muito bem. Com isto sendo feito, já temos a primeira questão resolvida. Ou seja, agora sempre que formos fazer a simulação, já poderemos contar com uma forma de ajustar o número máximo de ticks que estarão presentes a fim de gerar a barra. No entanto, apenas fazer isto não é de fato suficiente para o que queremos e pretendemos fazer. Lembre-se: Estamos apenas controlando a parte referente a simulação. E mesmo assim não estamos dando ao usuário a possibilidade de ajustar as coisas a fim que ele, usuário, possa colocar uma quantidade menor de ticks sendo simulados. Então temos dois problemas ainda para serem solucionados. Cada qual demanda algum tipo de modificação a ser criada no sistema.
Vamos então dividir para conquistar. Assim a próxima coisa que resolveremos é a questão referente ao limite máximo que de ticks reais, ou simulados externamente poderão ter. Para fazer isto vamos a um novo tópico.
Ajustando as coisas para dados reais
Apesar do título, sugerir que apenas dados reais de mercado poderão ser utilizados, isto não é de todo verdadeiro. Você pode simular os movimentos de mercado externamente. Salvar os dados dentro de um arquivo que depois será utilizado como arquivo contendo ticks e não barras. Este tipo de coisa é perfeitamente admissível e plausível de ser feito. Mas a questão aqui, é a mesma que foi discutida no tópico anterior. Ou seja, iremos de alguma forma limitar o número máximo de ticks que estarão presentes, mesmo quando estamos usando dados reais de mercado.
Esta questão, diferente do que foi visto no tópico anterior é bem mais complexa de ser resolvida do que possa parecer. Isto devido a própria natureza do que iremos estar tratando. Se o serviço de replay / simulação, fosse voltado a ser utilizado apenas para um dado tipo der mercado, a questão seria mais facilmente resolvida. Pois seria possível fazer uma análise durante o carregamento dos ticks e assim verificar se a quantidade dentro de uma janela de tempo, ultrapassou um valor pré-definido. Se isto de fato viesse a ocorrer, iriamos forçar o sistema a recusar os ticks dentro daquela janela, que seria o equivalente a uma barra de um minuto. Logo em seguida, irei forçar o simulador a criar o movimento baseado no RANDOM WALK. Desta maneira, a limitação do número de ticks dentro da janela seria facilmente conseguida. No entanto, devido ao fato de que durante a carga dos ticks, não sabemos se iremos estar lidando com valores que farão uso da plotagem BID ou da plotagem LAST, temos um sério e espinhoso abacaxi nas mãos, para poder descascar. Note que todo o problema se deve justamente por conta de não sabermos se o sistema de plotagem é BID ou LAST.
Agora vem uma das primeiras questões a serem pensadas: Qual seria a melhor solução para este problema? Permitir que o usuário informe o tipo de plotagem, correndo o risco de que ele o faça de maneira equivocada. Ou modificar o código a fim de que ele consiga lidar com esta situação? Apesar de que a solução de permitir que o usuário indique o tipo de plotagem, ser consideravelmente mais simples de ser implementada. A mesma conta justamente com uma falha em potencial. Esta falha se dá no fato de que o usuário poderá informar de forma errada o tipo de plotagem que deverá está sendo feita. Cá entre nós. Quantos usuários você conhece que sabe que existe dois tipos de plotagem, e que um tipo de destina a um determinado modelo de mercado e outro a um modelo totalmente diferente? A maior parte dos usuários se quer sabem que existem três tipos de servidores, no qual o MetaTrader 5 pode se comunicar. O que dirá ter que configurar o tipo de plotagem que o ativo de fato utiliza.
Então por conta deste problema em potencial, teremos um pouco mais de trabalho na implementação do que precisará ser feito. Mas de qualquer forma, temos um ponto de partida. A função LoadTicks presente na classe C_FileTicks. Muito bem, então vamos ver a função original e pensar um pouco a respeito do que temos que fazer de fato. A função original pode ser vista no fragmento logo abaixo:
01. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true) 02. { 03. int MemNRates, 04. MemNTicks; 05. datetime dtRet = TimeCurrent(); 06. MqlRates RatesLocal[], 07. rate; 08. bool bNew; 09. 10. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 11. MemNTicks = m_Ticks.nTicks; 12. if (!Open(szFileNameCSV)) return 0; 13. if (!ReadAllsTicks()) return 0; 14. rate.time = 0; 15. for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++) 16. { 17. if (!BuildBar1Min(c0, rate, bNew)) continue; 18. if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 19. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 20. } 21. if (!ToReplay) 22. { 23. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 24. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 25. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 26. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 27. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 28. m_Ticks.nTicks = MemNTicks; 29. ArrayFree(RatesLocal); 30. }else SetSymbolInfos(); 31. m_Ticks.bTickReal = true; 32. 33. return dtRet; 34. };
Fragmento do código fonte C_FilesTicks.mqh
Não se preocupe com a numeração nas linhas. Aqui ela servirá apenas como referência para a explicação, não sendo de fato as linhas reais dentro do arquivo C_FileTicks.mqh, já que o mesmo já sofreu algumas modificações que serão vistas depois. Mas vamos entender o que está acontecendo aqui, durante a leitura dos ticks reais.
Quando a função inicia, temos nas linhas 10 e na linha 11, pois pontos onde armazenamos temporariamente os valores atuais das posições de ticks carregados e das barras que representam estes mesmos ticks. Muito bem, se o chamador, disser quer os ticks não serão usados como base do replay, mas sim como barras previas, estes mesmos valores armazenados nas linhas 10 e 11, serão repostos nas linhas 27 e 28 respectivamente. Assim o sistema irá se manter intacto, esperando os ticks de replay.
De qualquer forma, na linha 12, tentaremos abrir o arquivo que contém os dados e na linha 13 iremos ler todos e absolutamente todos, os ticks presentes no arquivo. Se a leitura for bem-sucedida, teremos todos os ticks carregados. Mas pode ser quem em alguns momentos, venhamos a ter mais ticks por unidade de tempo, do que irá está sendo definido como máximo pelo sistema. Até neste ponto, não podemos fazer absolutamente nada com relação a isto. O motivo é justamente por não sabermos, qual é o tipo de plotagem que irá está sendo utilizado. Mas uma vez que os ticks tenham sido totalmente lidos, saberemos o tipo de plotagem e podemos começar a pensar em uma solução.
Agora vem a parte interessante. A janela de tempo que iremos está analisando é a de um minuto. Ok. Na linha 15 entramos em um laço que tem como finalidade montar as barras de um minuto para que o sistema tenha acesso a elas quando for preciso. Neste ponto é que atuaremos. Isto por que na linha 17, fazemos a chamada que construirá as barras. Tal função, contabilizará o volume de ticks que foram utilizados para se criar o movimento dentro da barra. Agora preste bastante atenção: Quando o teste da linha 18 indicar uma condição verdadeira, teremos os dados da barra sendo expresso da mesma forma que se a barra tivesse sido lida de um arquivo de barras. E é justamente este o dado que precisamos passar para a classe de simulação. Você pode observar isto, voltando ao tópico anterior e observando a linha 129.
Captaram a ideia que precisamos implementar? Isto quando for verificado que o número de ticks, é maior do que o valor definido internamente no programa, isto neste primeiro momento da implementação. Pois mudaremos isto depois.
Esta é a parte fácil. Testar e caso necessário pedir para a classe de simulação, fazer um RANDOM WALK a fim de que tenhamos um movimento com o número adequado de ticks. Mas agora vem a parte complicada. A classe de simulação de ticks, gerará os ticks, mas precisamos de alguma forma que seja a mais simples possível a fim de modificar os ticks que foram carregados. No entanto, só removeremos os ticks que fazem parte da janela em que o número de ticks é maior do que o indicado internamente, ou definido na aplicação. Além deste problema, temos um outro. Porém este é mais simples de ser solucionado. A simulação só precisa de fato acontecer quando os dados forem, ou tiverem como destino o uso no replay. Mas como falei, este problema é simples de se resolver, já que o chamador nos informa se os dados serão ou não usados no replay. Então tudo que precisamos fazer é testar o valor em ToReplay. Mamão com açúcar. Mas vamos então tentar fazer a parte complicada da coisa, que é sobrepor de alguma forma os ticks desnecessários. Para isto teremos que mudar esta função vista no fragmento para uma outra.
A primeira tentativa de fazer as coisas funcionarem, e sim o desenvolvimento de novas coisas sempre se dá na base da tentativa e erro, é mostrada no fragmento que se encontra logo abaixo:
01. //+------------------------------------------------------------------+ 02. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 03. { 04. int MemNRates, 05. MemNTicks, 06. nDigits, 07. nShift; 08. datetime dtRet = TimeCurrent(); 09. MqlRates RatesLocal[], 10. rate; 11. MqlTick TicksLocal[]; 12. bool bNew; 13. 14. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 15. nShift = MemNTicks = m_Ticks.nTicks; 16. if (!Open(szFileNameCSV)) return 0; 17. if (!ReadAllsTicks()) return 0; 18. rate.time = 0; 19. nDigits = SetSymbolInfos(); 20. ArrayResize(TicksLocal, def_MaxSizeArray); 21. m_Ticks.bTickReal = true; 22. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 23. { 24. if (!BuildBar1Min(c0, rate, bNew)) continue; 25. if (bNew) 26. { 27. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 28. { 29. nShift = MemShift; 30. C_Simulation *pSimulator = new C_Simulation(nDigits); 31. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) < 0) return 0; 32. ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 33. nShift += c1; 34. delete pSimulator; 35. } 36. MemShift = nShift; 37. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 38. }; 39. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 40. } 41. ArrayFree(TicksLocal); 42. if (!ToReplay) 43. { 44. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 45. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 46. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 47. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 48. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 49. m_Ticks.nTicks = MemNTicks; 50. ArrayFree(RatesLocal); 51. }else m_Ticks.nTicks = nShift; 52. 53. return dtRet; 54. }; 55. //+------------------------------------------------------------------+
Fragmento do código fonte da classe C_FileTicks.mqh
Você pode notar que aqui foram adicionadas diversas coisas. Além disto, foram feitas algumas modificações na sequência de execução de alguns dos comandos. Mas mesmo que este fragmento pareça ser a solução, ele ainda contém uma pequena falha. Mas antes de chegarmos lá, vamos ver como este fragmento do código presente na classe C_FileTicks, de fato consegue lidar com a questão de não permitir que tenhamos um número maior de ticks reais, do que o definido internamente pelo sistema.
Para conseguir fazer isto, foi preciso adicionar as linhas 6, 7 e 11. Tais linhas são novas variáveis que iremos de fato precisar. Na linha 15 iniciamos a primeira das nossas novas variáveis, justamente de forma a saber onde se encontra o atual contador de ticks carregados. Mas foi preciso adicionar as linhas 18 e 19, mas estas contêm apenas uma inicialização de alguns valores. Porém também foi preciso adicionar a linha 20, onde alocamos um espaço em memória para poder colocar os ticks que serão simulados. Tal região de memória será desalocada apenas na linha 41. Porém aqui temos uma pequena falha, que corrigiremos no código final, e tal falha se dá por conta da linha 31. Mas calma chegaremos lá.
Agora na linha 21 temos algo bastante importante. Neste fragmento não é possível ver o motivo. Mas se o conteúdo desta linha 21 estiver no ponto original do código, o mesmo não conseguirá fazer o que precisa ser feito. Você pode olhar no fragmento original, visto pouco acima, onde esta linha 21 deste fragmento daqui se encontra. Você irá ver que ele estará no fragmento original, na linha 31. Porém, quando a linha 24 executar, a função BuildBar1Min, não conseguirá estabelecer os critérios que precisamos. Por isto que as coisas precisam ser feitas nesta ordem mostrada neste novo fragmento.
De qualquer maneira, o que foi explicado como necessário a ser feito, está implementado entre as linhas 27 e 35. Que é justamente a simulação dos ticks a serem utilizados quando a quantidade de ticks na barra de um minuto superar um determinado valor. Este valor é o que está sendo definido internamente dentro da aplicação.
Agora preste atenção. Na linha 27 temos dois testes. O primeiro irá verificar se os dados serão usados no replay ou serão usados como barras previas. Caso eles venham a ser utilizados como barras previas, não será necessário fazer a simulação. Já no segundo teste é que iremos verificar se o número de ticks é maior do que o definido. Mas como neste segundo teste podemos estar nos referindo a um índex negativo, fazemos no primeiro teste, uma verificação a fim de evitar tão transtorno. Desta forma ao se fazer o segundo teste, estaremos nos referindo ao índex atual que acabará de ter o seu volume de ticks contabilizado.
Mas espere um pouco. O teste feito na linha 25, somente permitirá que estes testes que acabaram de ser descritos, ocorram apenas quando uma nova barra for detectada. Como é que estamos olhando o valor contabilizado da barra, sendo que estaremos em uma nova barra? Se você pensou assim, devo lhe parabenizar, pois isto indica que você de fato esteja compreendendo o código. Mas você se esqueceu de um pequeno e mísero detalhe. Até que a linha 39 ser executada, iremos está olhando a barra que acabou de ser fechada. Quem de fato faz com que apontemos para uma nova barra é justamente a linha 39. Entenderam por que as coisas devem ser feitas em uma determinada sequência?
Muito bem, mas vamos voltar a questão da parte sobre a simulação, pois com exceção da linha 51, todas as demais continuaram funcionando como foi descrito em artigos passados desta sequência. A linha 51 é dependente do que acontecerá durante a fase de simulação dos ticks. Então preste bastante atenção, pois existe uma falha aqui, mas que para os testes desta função, não é assim tão prejudicial.
Na linha 29, fazemos com que o ponto de deslocamento, aponte para o início das posições onde os ticks da barra que serão substituídos se inicia. Logo depois iniciamos o sistema de simulação, isto na linha 30. Já na linha 31 executamos a simulação propriamente dita. Observe que se ela falhar a linha 41 não será executada, liberando assim a memória alocada. Este pequeno empecilho será corrigido depois, mas para os testes isto não afeta em nada a plataforma. Tendo então sucesso na simulação dos ticks, fazemos uso de uma chamada na linha 32. Agora atenção ao seguinte: Esta linha 32 poderia ser substituída por um laço for, mas por conta que muito provavelmente a rotina de biblioteca do MQL5 estará de fato otimizada para mover os dados rapidamente, é preferível fazer uso dela, do que usar um laço aqui para isto. Na linha 33, atualizamos o valor de deslocamento para apontar para uma nova posição, imediatamente após a transferência dos dados. E finalmente na linha 34 destruímos o simulador.
Para que a memória de posição seja atualizada de modo que na linha 29 apontemos para o local correto, temos a linha 36, que atualizará esta memória. Porém todo este procedimento tem algumas falhas bem pequenas, que serão corrigidas no próximo artigo, já que precisaremos corrigir um outro problema também, que permanece quase que oculto, mas está presente na fase de simulação.
Conclusão
Apesar de termos feito grandes avanços relacionados a limitar o número de ticks em uma barra de um minuto. Tais avanços nos trouxeram alguns problemas e revelaram outros. No entanto, por conta que o conteúdo deste artigo, já se encontra ao meu ver, bastante denso, precisando que você o estude com atenção para entender o que está de fato acontecendo. Não quero elevar ainda mais a quantidade de coisas que você precisará compreender a fim de conseguir acompanhar o meu raciocínio. Mas caso você queira tentar entender quais são as falhas que ainda existem e que apareceram durante a implementação deste código. Deixo aqui uma dica: Uma das falhas tem a ver com o número mínimo de ticks que devemos simular, esta é uma falha bastante interessante de ser corrigida. Apesar de que esta falha somente apareceu pelo fato de que agora queremos simular ticks, quando a quantidade dos mesmos exceder um dado valor. Pense um pouco e você conseguirá entender como ela acontece. Já a outra falha, é quando copiamos os valores simulados para dentro do array de ticks. Quando esta cópia acontece, o sistema de confecção das barras durante o replay ficará comprometido. Podendo em diversos casos formar padrões estranhos e totalmente sem nenhum tipo de lógica. Mas além disto, em alguns momentos os ticks simplesmente somem, impedindo que o sistema de replay possa de fato fazer seu trabalho com precisão e de forma correta.
Se você deseja tentar resolver tais falhas, antes de ler o próximo artigo. Ótimo. Pois isto será um grande treinamento para que você entenda como de fato corrigir, criar e pensar para conseguir criar suas próprias soluções. Mas de qualquer forma no próximo artigo mostrarei como corrigir estas falhas. Isto será algo bastante interessante de ser feito. Então nos vemos no próximo artigo.
Vídeo que demonstra uma falha no sistema
Em breve, irei mostrar como corrigir esta falha, mas não será de forma tão imediata, já que a falha não é critica. Sendo apenas um problema no despejo ou descarregamento dos objetos criados. Aqui está uma ótima oportunidade. Caso você queira realmente mostrar que já tem um bom domínio em programação. Pode tentar corrigir a falha antes que eu mostre como fazer. Não precisa me mostrar, mas tente fazer antes que eu mostre como corrigir. Pois assim você saberá em que estágio de aprendizagem você se encontra.
- 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