
Desenvolvendo um sistema de Replay (Parte 63): Dando play no serviço (IV)
Introdução
No artigo anterior Desenvolvendo um sistema de Replay (Parte 62): Dando play no serviço (III), e irão ser tratados como se fossem ticks reais. Tal excesso não é de fato o nosso principal problema, mas ele pode dificultar o trabalho da aplicação a fim de conseguir fazer uma temporização adequada de modo que a barra de um minuto seja totalmente confeccionada dentro desta janela de tempo de um minuto.
Apesar do avanço que foi dado no artigo anterior, no final do mesmo mencionei que haveria alguns erros, que vieram a surgir e que foram provocados por conta da implementação da simulação dentro dos dados reais. Novamente vou reforçar isto, os erros não existem na simulação pura, mas tais erros, que ocorrerão com certeza, quando a simulação se combinasse com dados reais, não seriam de fato um problema para que fosse possível executar os testes e assim comprovar se o sistema que estava sendo criado era de alguma forma viável ou não de ser mantido e melhorado. Muitas vezes, desenvolvemos algo apenas para testar a sua viabilidade. Se a ideia por trás da implementação não se mostrar viável, a gente simplesmente a descarta, tendo assim gastado muito pouco tempo em correções e outras coisas.
Informei no mesmo artigo, quais seriam estes erros e a origem dos mesmos. Mas como o artigo, já tinha muitas informações das quais você caro leitor deverá compreender a fundo antes que um novo progresso viesse a ser apresentado. Finalizei o artigo em um dado momento. Mas aqui iremos de fato corrigir as tais falhas que foram geradas, não por conta de mudanças no código. Mas por conta de que estamos implementando algo que não seria de fato necessário, se não houvesse uma sobrecarga no sistema de temporização. Esta sobrecarga ainda não pode ser de fato realmente notada. Isto devido ao fato de que estamos em um momento inicial de tudo que precisará ser implementado.
Por conta que as falhas estão de alguma forma correlacionadas, poderemos começar por qualquer uma delas. Mas como sou uma pessoa meticulosa em alguns detalhes. Vamos começar resolvendo o problema do valor mínimo de ticks que deveremos simular quando estivermos fazendo isto com base em dados reais.
Qual o mínimo de ticks a serem de fato criados?
Responder esta pergunta, não é algo tão simples como parece. Mesmo por que você deve sempre se lembrar de uma coisa, enquanto estiver tentando modificar esta aplicação que nos possibilita fazer um replay ou simulação. No caso do replay podemos resolver esta questão forçando a classe de simulação a ter um mínimo de ticks para serem utilizados. Lembre-se: Quando usamos o Replay, isto indica que estamos de fato usando dados reais. Porém ao fazer a simulação dos ticks a fim de controlar a temporização dentro da janela de um minuto, estaremos, neste caso fazendo algo diferente. Isto quando a simulação for feita com os dados de rate obtidos. E é neste ponto que podemos ter problemas, já que no momento não tem como o usuário ajustar o valor de máximos de ticks que poderão ser utilizados. Mas pretendo colocar esta possibilidade no arquivo de configuração, a fim de que possamos controlar este valor. O motivo é que se a estação de trabalho, que o usuário estiver utilizando, conseguir lançar mais ticks do que a aplicação permitiria, o próprio usuário, poderá modificar o valor no arquivo de configuração. Tendo assim um replay mais próximo do que realmente aconteceu. A mesma coisa se dá se a estação do usuário não conseguir trabalhar com uma dada quantidade. Ele simplesmente pode reduzir a quantidade a um valor menor. Tornando assim a operação mais suave e bem menos estressante.
Existem um outro motivo, que também dificulta muito responder esta questão, simplesmente dando um número. Este tem de fato haver, com a questão de que você pode simular as coisas fora da aplicação. Salvar os dados e usá-lo como se fosse uma base real. Este é o pior dos cenários para o sistema de replay. Mas, porém, toda via e, entretanto, este tipo de coisa não irá ser coberta ainda. Vamos primeiro cobrir o problema de um ajuste malfeito por parte do usuário da aplicação. Então vamos voltar ao ponto. Qual é o mínimo de ticks que deveremos ter? Isto depende. Mas para entender, é preciso pensar um pouco. Caso o preço de abertura, fechamento, máxima e mínima sejam iguais, um tick é o bastante. Porém, este é o caso mais simples de todos. Para entender os demais casos é preciso que usemos algumas definições. A primeira é o fato de que a abertura e fechamento, deverão obrigatoriamente estar dentro dos limites definidos pela máxima e mínima. Com isto temos os outros casos:
- Se a abertura for igual a um dos limites e o fechamento for igual ao outro limite, podemos fazer isto com dois ticks;
- Se a abertura ou o fechamento for igual a um dos limites e o limite oposto estiver fora de contato com a abertura ou fechamento, podemos fazer isto com três ticks;
- Agora se todos os 4 valores que representam o OHLC forem diferentes, ou mesmo que a abertura seja igual ao fechamento, mas com a condição de que estes dois valores não estejam nos limites da barra, precisaremos de quatro ticks.
Esta é a base que responde quantos ticks no mínimo precisaremos para fazer a simulação. É verdade, que durante todo este tempo, a simulação se deu sem nenhum tipo de problema. Mas quero permitir que o usuário configure um limite a fim de permitir que a sua estação de trabalho, consiga fazer com que a aplicação responsável pelo replay/simulador funcione de maneira adequada.
Talvez esta explicação, tenha ficado muito complicada em forma de texto. Mas em forma de código a coisa se torna mais simples. Então para não precisar repetir todo o código do arquivo C_Simulation.mqh, irei logo abaixo, colocar apenas o fragmento que de fato precisou ser modificado. Isto para conseguir resolver o problema que existia.
128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume) 130. { 131. int i0, i1, i2, dm = 0; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. dm = (dm == 0 ? ((rate.open == rate.high == rate.low == rate.close) ? 1 : dm) : dm); 136. dm = (dm == 0 ? (((rate.open == rate.high) && (rate.low == rate.close)) || ((rate.open == rate.low) && (rate.close == rate.high)) ? 2 : dm) : dm); 137. if ((dm == 0 ? ((rate.open == rate.close == rate.high) || (rate.open == rate.close == rate.low) ? 3 : 4) : dm) == 0) return -1; 138. m_Marks.iMax = (MaxTickVolume <= dm ? dm : MaxTickVolume); 139. m_Marks.iMax = (((int)rate.tick_volume > m_Marks.iMax) || ((int)rate.tick_volume < dm) ? m_Marks.iMax : (int)rate.tick_volume - 1); 140. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 141. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 142. Simulation_Time(rate, tick); 143. MountPrice(0, rate.open, rate.spread, tick); 144. if (m_Marks.iMax > 10) 145. { 146. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 147. i1 = m_Marks.iMax - i0; 148. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 149. i2 = (i2 == 0 ? 1 : i2); 150. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 151. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 152. 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); 153. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 154. m_Marks.bHigh = m_Marks.bLow = true; 155. 156. }else Random_Price(rate, tick); 157. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 158. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 159. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 160. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 161. CorretTime(tick); 162. 163. return m_Marks.iMax; 164. } 165. //+------------------------------------------------------------------+
Fragmento do arquivo C_Simulation.mqh
A numeração das linhas, vista neste fragmento, mostra exatamente onde você deverá modificar o código que se encontra presente na íntegra no artigo anterior. Observe que na linha 131, adicionei uma nova variável e já estou inicializando-a logo na declaração. Note também o fato de que a linha 134, original deverá ser removida do código e no lugar dela, surgirá três novas linhas. Estas três linhas cumprem exatamente os pontos que mencionei a pouco, de maneira a saber quantos ticks no mínimo precisarão ser criados. Porém tem um detalhe e este é bastante importante que você o consiga notar. Repare que na linha 138, durante o teste do operador ternário, o valor de MaxTickVolume é confrontado com o valor que acabamos de ajustar a fim de saber qual o número mínimo de ticks. Se MaxTickVolume for menor que este valor ajustado, o valor ajustado será utilizado, independente do que qualquer outro dado venha a informar. Tanto que na linha 139, verificamos novamente esta mesma condição. Assim se o valor presente no rate.tick_volume, também se mostrar ser menor do que o valor ajustado, o valor ajustado terá prioridade de uso.
Agora, lembre-se de testar o retorno desta função de simulação. Isto caso venha a utilizá-la para outro propósito. O motivo é que ao tentar ajustar o valor de mínimo de ticks, tivermos uma falha, a função retornará, já na linha 137. Então não tente usar os valores no array retornado sem antes verificar o retorno da função, pois os valores do array podem conter lixo.
Existe uma pequena modificação feita aqui, que existia no código visto no artigo anterior. No entanto, mais para o final deste artigo voltarei a este mesmo fragmento, de maneira que a explicação da mudança feita aqui, tenha algum sentido.
Assim temos a correção do nosso primeiro problema. Então vamos para o próximo tópico resolver o segundo problema.
Resolvendo a falha no ajuste dos ticks
Esta segunda falha é um pouco mais trabalhosa de ser resolvida. Mas o fato de ser mais trabalhosa não indica de fato que ela será mais difícil. Mas apenas irá nos dar um pouco mais de trabalho. Então vamos fazer o seguinte. Vamos rever o fragmento de código que foi visto no artigo anterior, responsável por fazer as movimentações e ajustar os ticks, a fim de que estes pudesse ser usado para plotar o gráfico. O fragmento em questão pode ser visto logo abaixo, para facilitar a nossa vida.
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 arquivo C_FileTicks.mqh
Não se esqueça que este fragmento contém as falhas que precisaremos e resolveremos neste artigo. Pois muito bem, preste bastante atenção agora no fragmento logo abaixo e o compare com este fragmento acima.
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. m_Ticks.bTickReal = true; 21. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 22. { 23. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 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. ArrayResize(TicksLocal, def_MaxSizeArray); 31. C_Simulation *pSimulator = new C_Simulation(nDigits); 32. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 33. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 34. delete pSimulator; 35. ArrayFree(TicksLocal); 36. if (c1 < 0) return 0; 37. } 38. MemShift = nShift; 39. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 40. }; 41. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 42. } 43. if (!ToReplay) 44. { 45. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 46. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 47. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 48. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 49. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 50. m_Ticks.nTicks = MemNTicks; 51. ArrayFree(RatesLocal); 52. }else m_Ticks.nTicks = nShift; 53. 54. return dtRet; 55. }; 56. //+------------------------------------------------------------------+
Fragmento do arquivo C_FileTicks.mqh ( Final )
Conseguem ver as diferenças? Elas não são assim tão escandalosas assim, mas estão presentes. Você pode notar que fiz algumas modificações na ordem de execução de alguns pontos. O principal e mais evidente é o de alocar e liberar a memória dos ticks que foram criados durante a simulação. Pelo fato de que esta função LoadTicks ser uma função primaria, isto é, ela é executada antes do sistema de fato entrar em funcionamento e exigir performance. Podemos nos dar ao luxo de perder um pouco de tempo durante as chamadas para alocar ou liberar memória. Se você achar que tal perda de tempo não é aceitável, sinta-se livre para ajustar a ordem com que as coisas acontecem. Mas de qualquer forma, neste fragmento já corrigido, que se encontra abaixo. Não deveremos deixar de fazer com que o destructor da classe de simulação, seja chamado caso tenhamos uma falha. Você pode comparar os fragmentos e notará que o retorno no fragmento corrigido somente acontece, em caso de falha na linha 36. Mas antes disto, nas linhas 34 e 35, chamamos o destructor e liberamos a memória alocada. Isto nesta ordem.
Se a simulação aconteceu e os dados poderão ser movidos. Fazemos isto na linha 33, onde também usamos o retorno da função de biblioteca para atualizar o novo valor de deslocamento.
Com isto resolvemos o problema que havia, quando a simulação retornava como sendo uma falha. Mas ainda preciso explicar sobre a outra falha, que também existia aqui. Se você observar o fragmento corrigido, notará uma coisa engraçada. Esta talvez e muito provavelmente não faça muito sentido para você, podendo ser vista na linha 23. E por que burros d'água esta linha 23 foi colocada neste fragmento. Qual o sentido de comparar o valor do contador de ticks, com o de deslocamento? Bem, não tem muito sentido. Isto é fato. Mas, porém, toda via e entretanto. Se e somente se, o simulador for executado, o valor de deslocamento ficará diferente do valor do contador. Quando isto acontece, alguns dados de ticks reais ficarão no índex errado. Se você não corrigir isto, no momento em que a linha 52 for executada, diversos ticks reais poderão simplesmente desaparecer. Isto sem contar com o problema de que entre uma barra simulada e uma não simulada, teremos ticks estranhos entre elas, já que o índex destes ticks estarão completamente errados. Agora acredito que você de fato tenha conseguido compreender o verdadeiro problema. Então se fazemos o teste na linha 23 a fim de verificar e movimentar os ticks reais, quando tudo for colocado para executar, não teremos coisas estranhas nos sendo mostradas no gráfico. Uma medida simples, porém, que resolve definitivamente o nosso problema. Muito bem, assim o código final que se encontra no arquivo C_FileTicks.mqh pode ser visto na íntegra, logo abaixo:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_FileBars.mqh" 005. #include "C_Simulation.mqh" 006. //+------------------------------------------------------------------+ 007. #define macroRemoveSec(A) (A - (A % 60)) 008. #define def_MaxSizeArray 16777216 // 16 Mbytes 009. //+------------------------------------------------------------------+ 010. class C_FileTicks 011. { 012. protected: 013. enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX}; 014. struct stInfoTicks 015. { 016. MqlTick Info[]; 017. MqlRates Rate[]; 018. int nTicks, 019. nRate; 020. bool bTickReal; 021. ePlotType ModePlot; 022. }; 023. //+------------------------------------------------------------------+ 024. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew) 025. { 026. double dClose = 0; 027. 028. switch (m_Ticks.ModePlot) 029. { 030. case PRICE_EXCHANGE: 031. if (m_Ticks.Info[iArg].last == 0.0) return false; 032. dClose = m_Ticks.Info[iArg].last; 033. break; 034. case PRICE_FOREX: 035. dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose); 036. if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false; 037. break; 038. } 039. if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time))) 040. { 041. rate.time = macroRemoveSec(m_Ticks.Info[iArg].time); 042. rate.real_volume = 0; 043. rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0); 044. rate.open = rate.low = rate.high = rate.close = dClose; 045. }else 046. { 047. rate.close = dClose; 048. rate.high = (rate.close > rate.high ? rate.close : rate.high); 049. rate.low = (rate.close < rate.low ? rate.close : rate.low); 050. rate.real_volume += (long) m_Ticks.Info[iArg].volume_real; 051. rate.tick_volume += (m_Ticks.bTickReal ? 1 : (int)m_Ticks.Info[iArg].volume); 052. } 053. 054. return true; 055. } 056. //+------------------------------------------------------------------+ 057. private : 058. int m_File; 059. stInfoTicks m_Ticks; 060. //+------------------------------------------------------------------+ 061. inline bool Open(const string szFileNameCSV) 062. { 063. string szInfo = ""; 064. 065. if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) 066. { 067. for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File); 068. if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true; 069. Print("File ", szFileNameCSV, ".csv not a traded tick file."); 070. }else 071. Print("Tick file ", szFileNameCSV,".csv not found..."); 072. 073. return false; 074. } 075. //+------------------------------------------------------------------+ 076. inline bool ReadAllsTicks(void) 077. { 078. string szInfo; 079. 080. Print("Loading replay ticks. Please wait..."); 081. ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 082. m_Ticks.ModePlot = PRICE_FOREX; 083. while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag)) 084. { 085. ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray); 086. szInfo = FileReadString(m_File) + " " + FileReadString(m_File); 087. m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19)); 088. m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3)); 089. m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File)); 090. m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File)); 091. m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File)); 092. m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File)); 093. m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File)); 094. m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot); 095. m_Ticks.nTicks++; 096. } 097. FileClose(m_File); 098. if (m_Ticks.nTicks == (INT_MAX - 2)) 099. { 100. Print("Too much data in tick file.\nIt is not possible to continue..."); 101. return false; 102. } 103. return (!_StopFlag); 104. } 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. } 148. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary); 149. m_Ticks.Rate[++m_Ticks.nRate] = rate[0]; 150. if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume); 151. if (iRet < 0) break; 152. for (int c0 = 0; c0 <= iRet; c0++) 153. { 154. ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray); 155. m_Ticks.Info[m_Ticks.nTicks++] = local[c0]; 156. } 157. } 158. ArrayFree(local); 159. delete pFileBars; 160. delete pSimulator; 161. m_Ticks.bTickReal = false; 162. 163. return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0)); 164. } 165. //+------------------------------------------------------------------+ 166. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 167. { 168. int MemNRates, 169. MemNTicks, 170. nDigits, 171. nShift; 172. datetime dtRet = TimeCurrent(); 173. MqlRates RatesLocal[], 174. rate; 175. MqlTick TicksLocal[]; 176. bool bNew; 177. 178. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 179. nShift = MemNTicks = m_Ticks.nTicks; 180. if (!Open(szFileNameCSV)) return 0; 181. if (!ReadAllsTicks()) return 0; 182. rate.time = 0; 183. nDigits = SetSymbolInfos(); 184. m_Ticks.bTickReal = true; 185. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 186. { 187. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 188. if (!BuildBar1Min(c0, rate, bNew)) continue; 189. if (bNew) 190. { 191. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 192. { 193. nShift = MemShift; 194. ArrayResize(TicksLocal, def_MaxSizeArray); 195. C_Simulation *pSimulator = new C_Simulation(nDigits); 196. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 197. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 198. delete pSimulator; 199. ArrayFree(TicksLocal); 200. if (c1 < 0) return 0; 201. } 202. MemShift = nShift; 203. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 204. }; 205. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 206. } 207. if (!ToReplay) 208. { 209. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 210. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 211. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 212. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 213. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 214. m_Ticks.nTicks = MemNTicks; 215. ArrayFree(RatesLocal); 216. }else m_Ticks.nTicks = nShift; 217. 218. return dtRet; 219. }; 220. //+------------------------------------------------------------------+ 221. inline stInfoTicks GetInfoTicks(void) const 222. { 223. return m_Ticks; 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef def_MaxSizeArray 229. //+------------------------------------------------------------------+
Arquivo de cabeçalho C_FileTicks.mqh
Ok, agora que resolvemos estes problemas, poderemos focar no próximo passo. Este será o de permitir que o usuário defina um valor que será usado como sendo um máximo de ticks presentes na barra de um minuto. Mas para separar as coisas, vamos ver isto em um novo tópico.
Permitindo o usuário ajustar as coisas
Esta parte, com toda a certeza é a mais fácil, simples e divertida de ser feito. Isto por que, o único e verdadeiro trabalho que você como programador terá, será definir qual o nome da chave que será utilizada a fim de definir o valor que precisaremos efetuar o ajuste. Esta chave na verdade, é o valor que o usuário deverá digitar no arquivo de configuração do ativo. Já mostrei no passado, nesta mesma sequência como fazer este tipo de coisa. Porém por se tratar de algo extremamente simples de ser feito, não gerarei uma fragmentação do código para explicar o que estará de fato sendo adicionado. Mas antes de ver o código já concluído, vamos ver primeiro um exemplo de uso onde podemos observar um arquivo de configuração da aplicação de replay/simulador. Este exemplo se encontra logo abaixo.
01. [Config] 02. Path = WDO 03. PointsPerTick = 0.5 04. ValuePerPoints = 5.0 05. VolumeMinimal = 1.0 06. Account = NETTING 07. MaxTicksPerBar = 2800 08. 09. [Bars] 10. WDON22_M1_202206140900_202206141759 11. 12. [ Ticks -> Bars] 13. 14. [ Bars -> Ticks ] 15. 16. [Ticks] 17. WDON22_202206150900_202206151759
Exemplo de arquivo de configuração
Observe que na linha sete, temos uma nova configuração sendo feita. Se você utilizar este arquivo de configuração até na versão anterior da aplicação que faz o replay/simulação no MetaTrader 5. Irá receber uma mensagem de erro, indicando que a linha sete não contem algo que o sistema consiga compreender. No entanto, a partir desta versão que estou mostrando, a aplicação consegue entender o que a linha sete significa. Agora um detalhe: Você ou mesmo o usuário, não é obrigado a informar este dado que está sendo configurado na linha sete. Se você fizer isto, esta configuração irá se sobrepor a que existe internamente na aplicação já compilada. Mas se tal configuração for ignorada e não definida, o serviço que executará o replay/simulação, utilizará o valor interno, definido no momento da compilação da aplicação.
Estou informando isto, antes de mostrar o código, pois quero que você se atente ao fato de que esta configuração em si, é opcional. Porém, ela irá se sobrepor ao valor que foi definido durante o processo de compilação. Uma última coisa que você não deve se esquecer: Cada arquivo de configuração é único, ou seja, você pode definir uma quantidade máxima de ticks diferente para diferentes arquivos. Então sinta-se à vontade de testar o sistema até encontrar uma configuração que não afete o desempenho do MetaTrader 5, para que o gráfico seja plotado de maneira o mais suave possível.
Mas, vamos agora ver o código do arquivo de cabeçalho que é responsável por executar o que este arquivo de configuração está informando. O arquivo em questão é o C_ConfigService.mqh, que tem seu novo código mostrado na íntegra logo a seguir.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Support\C_FileBars.mqh" 005. #include "Support\C_FileTicks.mqh" 006. #include "Support\C_Array.mqh" 007. //+------------------------------------------------------------------+ 008. class C_ConfigService : protected C_FileTicks 009. { 010. protected: 011. //+------------------------------------------------------------------+ 012. private : 013. enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; 014. enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; 015. struct st001 016. { 017. C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; 018. int Line, 019. MaxTickVolume; 020. bool AccountHedging; 021. char ModelLoading; 022. string szPath; 023. }m_GlPrivate; 024. //+------------------------------------------------------------------+ 025. inline void FirstBarNULL(void) 026. { 027. MqlRates rate[1]; 028. int c0 = 0; 029. 030. for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++); 031. rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid); 032. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 033. rate[0].tick_volume = 0; 034. rate[0].real_volume = 0; 035. rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400; 036. CustomRatesUpdate(def_SymbolReplay, rate); 037. } 038. //+------------------------------------------------------------------+ 039. inline eTranscriptionDefine GetDefinition(const string &In, string &Out) 040. { 041. string szInfo; 042. 043. szInfo = In; 044. Out = ""; 045. StringToUpper(szInfo); 046. StringTrimLeft(szInfo); 047. StringTrimRight(szInfo); 048. if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO; 049. if (StringSubstr(szInfo, 0, 1) != "[") 050. { 051. Out = szInfo; 052. return Transcription_INFO; 053. } 054. for (int c0 = 0; c0 < StringLen(szInfo); c0++) 055. if (StringGetCharacter(szInfo, c0) > ' ') 056. StringAdd(Out, StringSubstr(szInfo, c0, 1)); 057. 058. return Transcription_DEFINE; 059. } 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. }; 072. string szRet[]; 073. char cWho; 074. 075. if (StringSplit(szInfo, '=', szRet) == 2) 076. { 077. StringTrimRight(szRet[0]); 078. StringTrimLeft(szRet[1]); 079. for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; 080. switch (cWho) 081. { 082. case 0: 083. m_GlPrivate.szPath = szRet[1]; 084. return true; 085. case 1: 086. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); 087. return true; 088. case 2: 089. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); 090. return true; 091. case 3: 092. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); 093. return true; 094. case 4: 095. m_GlPrivate.ModelLoading = StringInit(szRet[1]); 096. m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading); 097. return true; 098. case 5: 099. if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true; 100. else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false; 101. else 102. { 103. Print("Entered account type is not invalid."); 104. return false; 105. } 106. return true; 107. case 6: 108. m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1])); 109. return true; 110. } 111. Print("Variable >>", szRet[0], "<< not defined."); 112. }else 113. Print("Configuration definition >>", szInfo, "<< invalidates."); 114. 115. return false; 116. } 117. //+------------------------------------------------------------------+ 118. inline bool WhatDefine(const string szArg, char &cStage) 119. { 120. const string szList[] = { 121. "[BARS]", 122. "[TICKS]", 123. "[TICKS->BARS]", 124. "[BARS->TICKS]", 125. "[CONFIG]" 126. }; 127. 128. cStage = 1; 129. for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++) 130. if (szList[c0] == szArg) return true; 131. 132. return false; 133. } 134. //+------------------------------------------------------------------+ 135. inline bool CMD_Array(char &cError, eWhatExec e1) 136. { 137. bool bBarsPrev = false; 138. string szInfo; 139. C_FileBars *pFileBars; 140. C_Array *ptr = NULL; 141. 142. switch (e1) 143. { 144. case eTickReplay : ptr = m_GlPrivate.pTicksToReplay; break; 145. case eTickToBar : ptr = m_GlPrivate.pTicksToBars; break; 146. case eBarToTick : ptr = m_GlPrivate.pBarsToTicks; break; 147. case eBarPrev : ptr = m_GlPrivate.pBarsToPrev; break; 148. } 149. if (ptr != NULL) 150. { 151. for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) 152. { 153. if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break; 154. switch (e1) 155. { 156. case eTickReplay: 157. if (LoadTicks(szInfo, true, m_GlPrivate.MaxTickVolume) == 0) cError = 4; 158. break; 159. case eTickToBar : 160. if (LoadTicks(szInfo, false, m_GlPrivate.MaxTickVolume) == 0) cError = 5; else bBarsPrev = true; 161. break; 162. case eBarToTick : 163. if (!BarsToTicks(szInfo, m_GlPrivate.MaxTickVolume)) cError = 6; 164. break; 165. case eBarPrev : 166. pFileBars = new C_FileBars(szInfo); 167. if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true; 168. delete pFileBars; 169. break; 170. } 171. } 172. delete ptr; 173. } 174. 175. return bBarsPrev; 176. } 177. //+------------------------------------------------------------------+ 178. public : 179. //+------------------------------------------------------------------+ 180. C_ConfigService() 181. :C_FileTicks() 182. { 183. m_GlPrivate.AccountHedging = false; 184. m_GlPrivate.ModelLoading = 1; 185. m_GlPrivate.MaxTickVolume = 2000; 186. } 187. //+------------------------------------------------------------------+ 188. inline const bool TypeAccountIsHedging(void) const 189. { 190. return m_GlPrivate.AccountHedging; 191. } 192. //+------------------------------------------------------------------+ 193. bool SetSymbolReplay(const string szFileConfig) 194. { 195. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo) 196. int file; 197. char cError, 198. cStage; 199. string szInfo; 200. bool bBarsPrev; 201. 202. if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) 203. { 204. Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated..."); 205. return false; 206. } 207. Print("Loading data for playback. Please wait...."); 208. cError = cStage = 0; 209. bBarsPrev = false; 210. m_GlPrivate.Line = 1; 211. m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; 212. while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) 213. { 214. switch (GetDefinition(FileReadString(file), szInfo)) 215. { 216. case Transcription_DEFINE: 217. cError = (WhatDefine(szInfo, cStage) ? 0 : 1); 218. break; 219. case Transcription_INFO: 220. if (szInfo != "") switch (cStage) 221. { 222. case 0: 223. cError = 2; 224. break; 225. case 1: 226. if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); 227. (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); 228. break; 229. case 2: 230. if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); 231. (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); 232. break; 233. case 3: 234. if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); 235. (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); 236. break; 237. case 4: 238. if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); 239. (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); 240. break; 241. case 5: 242. if (!Configs(szInfo)) cError = 7; 243. break; 244. } 245. break; 246. }; 247. m_GlPrivate.Line += (cError > 0 ? 0 : 1); 248. } 249. FileClose(file); 250. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick)); 251. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay)); 252. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); 253. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); 254. switch(cError) 255. { 256. case 0: 257. if (GetInfoTicks().nTicks <= 0) 258. { 259. Print("There are no ticks to use. Service is being terminated..."); 260. cError = -1; 261. }else if (!bBarsPrev) FirstBarNULL(); 262. break; 263. case 1 : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break; 264. case 2 : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line); break; 265. default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line); 266. } 267. 268. return (cError == 0 ? !_StopFlag : false); 269. #undef macroFileName 270. } 271. //+------------------------------------------------------------------+ 272. }; 273. //+------------------------------------------------------------------+
Código fonte do arquivo de cabeçalho C_ConfigService.mqh
Muito bem. O código desta classe C_ConfigService é extremamente agradável de ser trabalhado. Isto por que ele é um código que quase não precisamos mexer e que com muito pouco trabalho conseguimos fazer qualquer tipo de coisa. É raro ter algo do tipo. Mas vamos ao que interessa, pois aqui é que de fato fazemos todas as camadas abaixo trabalharem pesado a fim de nos permitir carregar os dados que serão utilizados para se fazer o replay ou simulação. E tudo isto vai sendo trabalhado conforme irá sendo indicado no conteúdo presente no arquivo de configuração, como o do exemplo mostrado acima.
Pois bem, a primeira coisa que fizemos neste código, foi definir uma nova variável. Esta pode ser vista na linha 19. O nome dela é bastante sugestivo, já nos mostrando o que iremos de fato fazer nos próximos passos. Esta mesma variável é inicializada na linha 185. Isto é dentro do constructor da classe. Por conta desta inicialização, se o arquivo de configuração não for definido um outro valor, o valor a ser utilizado será exatamente este que está sendo visto.
Agora você pode estar se perguntando: Mas espere um pouco. Por que você não está utilizando a definição def_MaxTicksVolume, que existe no arquivo Defines.mqh? O motivo é que aquela definição já não existe mais. Ela foi removida pelo motivo de que não precisamos mais dela. No momento em que começamos a trabalhar no arquivo de configuração, passamos a não depender mais de algumas coisas. Por isto que a definição def_MaxTicksVolume foi removida do arquivo Defines.mqh. Se você desejar a manter lá, ok. Mas não se espante se no futuro quando o arquivo Defines.mqh voltar a ser visualizado em algum artigo, você não notar a presença da definição def_MaxTicksVolume.
Agora vamos entender por que a definição deixou de existir, não sendo mais necessária. Durante a implementação do que estava sendo desenvolvido, que era permitir que o replay, viesse a simular ticks, caso fosse notado um excesso deles na barra de um minuto, eu não havia de fato definido onde a informação começaria a ser dada. Por conta disto e para evitar um verdadeiro emaranhado de valores no código, foi decidido criar uma definição geral. Daí surgiu a def_MaxTicksVolume e esta ficaria no arquivo Defines.mqh. Tomar este tipo de decisão, é uma das coisas mais comuns quando se está projetando algo que você mudará com o tempo. Mas quando chegamos a classe C_ConfigService, tive a ideia de permitir que o usuário pudesse ajustar este mesmo valor, não precisando assim ter que fazer uma nova compilação de todo o código.
De qualquer forma, as classes que irão fazer o trabalho mais pesado, já estavam construídas. Então tudo que precisaremos é repassar o valor de número máximo de ticks para elas. Assim com uma simples modificação, ou melhor dizendo, edição do código, podemos fazer com que as chamadas tenham tal valor que poderá ser facilmente obtido dentro da classe de configuração do serviço. Tais chamadas estão presentes nas linhas 157, 160 e 163. Notem que até agora, não foi feita absolutamente nenhuma adição considerável ao código, apenas uma leve edição a fim de adicionar o suporte necessário para que as chamadas das classes abaixo, pudessem receber de forma controlada um valor que seria definido para toda a aplicação, a fim de poder controlar o número máximo de ticks que seriam simulados, ou deverão existir dentro de uma barra de um minuto.
Agora vem o verdadeiro motivo para que as coisas fossem direcionadas para esta classe. Permitir que o usuário defina um valor máximo de ticks a estarem presentes dentro da barra de um minuto. Se você está caindo de paraquedas neste código daqui, pode estar pensando que fazer isto será uma tarefa grandiosa e extremamente complexa. Mas não. Veja como é simples, fácil e rápido, permitir que o usuário defina um valor para ser utilizado como número máximo de ticks a serem utilizados. Primeiro adicionamos a chave que será utilizada. Isto é feito, adicionado mais uma string a lista de strings que servem de chave para os parâmetros de configuração. Tal matriz se encontra definida na linha 63. Agora preste atenção, pois já expliquei isto antes, mas vou reforçar a explicação. Para adicionar uma nova chave, você deverá colocar a mesma com os valores em maiúsculo. Não importa qual será a chave, a defina em maiúsculo. No caso a nossa nova chave se encontra na linha 70. Agora tem um segundo macete, observe que na linha 75, usamos uma função da biblioteca do MQL5 a fim de separar os valores. O delimitador usado é o sinal de igual. Então o que estiver antes do sinal de igual é considerado a chave para a definição e o que estiver depois do sinal o valor que a definição deverá receber.
O próximo ponto de atenção é na linha 79, onde faremos a pesquisa pela chave em busca da posição que a mesma se encontra na matriz de definições. Por conta disto se você modificar a matriz, deverá modificar também o valor que será o índex da configuração. Mas isto, não é complexo de ser feito, apenas precisa de que você preste atenção ao que está fazendo.
No caso da nossa nova definição, o índex dela está sendo o valor seis. Então na linha 107 definimos como faremos para que a definição seja aplicada corretamente. Já que o valor que esperamos é um valor inteiro, usamos uma outra função da biblioteca do MQL5 para converter o valor para nós. E é desta forma que o usuário consegue definir um valor a ser utilizado como limite máximo de ticks presentes em uma barra.
Observação importante: Aqui na linha 108, onde convertemos o valor que está presente no arquivo de configuração do replay/simulação, para ser usado como número máximo de ticks. Não estamos fazendo nenhum tipo de teste a fim de verificar se o valor se encaixa ou não em alguns tipos de parâmetros esperados. A única coisa que estamos fazendo, é garantindo que o valor será sempre positivo. Mas se ele contiver alguma inconsistência, pode ser que o processo de simulação venha a falhar.
Considerações finais
Antes de terminar este artigo, gostaria de relembrar sobre o fato que se encontra presente no artigo anterior. Trata-se do vídeo que pode ser visto nele. Apesar do foco principal neste artigo ter sido a implementação do sistema de limite de ticks no caso de estarmos usando o replay, gostaria de ressaltar que aquelas falhas que foram vistas no vídeo do artigo anterior, não foram esquecidas. Apesar de elas não serem de fato algo grave que torne todo a aplicação instável, podendo até mesmo causar problemas na plataforma, não foi constatado que isto de fato ocorra. Sendo mais um problema em remover alguns objetos gráficos no momento que o gráfico está sendo fechado. Apesar da falha, acontecer em um tipo de situação bem específica, estamos providenciando a devida correção da mesma. Assim que isto ocorrer, você, caro leitor, que vem acompanhando esta sequência, irá ter um à sua disposição um artigo, explicando os passos que foram feitos para sanar a tal falha.
Mas para encerrar este artigo, quero chamar você a assistir ao vídeo presente neste artigo logo abaixo. O mesmo está demonstrando como o sistema se encontra funcionado. Mas principalmente o comportamento do mesmo quando observamos o uso ou não do controle de número de ticks durante a definição que é feita no arquivo de configuração.
O vídeo é bem curtinho e permite que você veja de forma bastante simplificada, a principal diferença que pode ser notada claramente entre usar dados simulados e reais. Você poderá observar que quando usamos a simulação via Random Walk a fim de preencher os ticks o movimento dos ticks é bem diferente do movimento real que foi produzido pelos negócios executados.
No entanto, quero chamar a atenção para uma outra falha. Está apesar de ser relativamente chata e acontecer a todo momento. Não nos causa um grande transtorno. Apenas é chata mesmo. Você pode notar esta falha no vídeo. Assim se você compilar o código atual e o usar, deve ter em mente, que de tempos em tempos o serviço de replay/simulador irá simplesmente entrar em estado de pause. Sendo necessário que você torne a dar play no serviço. Esta falha é bem chata, porém, diferente da falha vista no artigo anterior, esta precisa ser corrigida o mais breve possível. Então no próximo artigo, providenciaremos as devidas correções a fim de sanar este problema do serviço de replay/simulação, de tempos em tempos simplesmente dar um pause automático. Além é claro de providenciar o retorno de algumas coisas que ainda se encontram desabilitadas no serviço de replay/simulador.
Vídeo de demonstraçã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