preview
Desenvolvendo um sistema de Replay (Parte 65): Dando play no serviço (VI)

Desenvolvendo um sistema de Replay (Parte 65): Dando play no serviço (VI)

MetaTrader 5Exemplos | 16 setembro 2024, 09:23
333 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 64): Dando play no serviço (V), resolvemos duas falhas que existiam na aplicação de replay/simulador. Mas ali não resolvemos tudo. Não de forma que podermos partir para novas conquistas a partir deste artigo. Ainda temos que resolver alguns pequenos problemas que ainda nos assola. Tais problemas já não existiam quando era feito o uso das variáveis globais de terminal. Mas como abandonamos as mesmas e partimos para fazer uso de outras técnicas e meios para que a aplicação de replay/simulação trabalha-se, temos que adaptar e construir uma nova implementação. No entanto, como você caro leitor, deve ter notado, não começamos do zero. Estamos de fato adaptando e ajustando o código, de forma a fazer todo o trabalho executado durante o uso das variáveis globais de terminal não seja de fato perdido.

Com isto já estamos quase no mesmo ponto da funcionalidade que existia. Porém para chegar no mesmo ponto, temos que resolver mais alguns detalhes. Vou tentar fazer isto dentro deste artigo, já que tais coisas são relativamente bem simples. Isto diferente do que foi corrigir a falha de despejo da memória. Onde de fato, o problema era bastante tenso, sendo necessário entrar nos detalhes. Por isto precisei explicar por que a falha estava acontecendo, mesmo quando o código parecia absolutamente e totalmente correto. Mas se em vez de explicar, eu simplesmente mostra-se a linha que precisou ser adicionada, acredito que muitos ficariam a ver navios. Ou em outra hipótese, simplesmente corrigir o código e não explicar nada. Isto sim seria algo bem chato, já que você nunca viria a ter contato com tais problemas e ficaria com uma falsa segurança de que eles não existem. E quando acontecesse você iria se frustrar, por não ter a quem perguntar, ou ficaria se achando o pior profissional que existe. Não quero que ninguém pense assim. Pois mesmo os profissionais com muita experiência, ainda sim cometem erros. Mesmo que você não venha a ver tais erros, eles sempre acontecerão. Mas o profissional, logo percebe e consegue corrigir a falha o quanto antes. Assim sendo quero e torço por todos aspirantes, venham de fato a ser tornar verdadeiros profissionais. E não simples profissionais, mas grandes e excelentes profissionais na sua área de atuação. Assim sendo, vamos começar resolvendo o primeiro dos problemas ainda pendentes.


Adicionando o recurso de avanço rápido ( Modelo básico )

Este recurso, já existia no passado, tendo sido implementado durante a fase de uso das variáveis globais de terminal. Mas por conta que não estamos mais fazendo uso de tais variáveis, precisaremos adaptar o código, para assim fazer com que o avanço rápido possa de fato acontecer. Manterei a mesma filosofia de avanço rápido que foi adotada no passado. Desta forma, você poderá compreender muito mais facilmente como a adaptação do antigo código será feita, para que a nova implementação consiga fazer uso do recurso de avanço rápido.

Para começar, precisamos fazer uma pequena modificação, no código visto no artigo passado. Esta modificação, visa assegurar que o indicador de controle de fato funcionará da maneira correta. Você pode ver a mesma no fragmento abaixo:

35. //+------------------------------------------------------------------+
36. inline void UpdateIndicatorControl(void)
37.          {
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
44.                   m_IndControl.Memory.dValue = Buff[0];
45.                if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
46.                   if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
47.                      m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
48.             }else if (m_IndControl.Mode == C_Controls::ePause)
49.             {
50.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
51.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
52.                m_IndControl.Memory._8b[7] = 'D';
53.                m_IndControl.Memory._8b[6] = 'M';
54.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
55.             }
56.          }
57. //+------------------------------------------------------------------+

Fragmento de código do arquivo C_Replay.mqh

A mudança, ou melhor dizendo, adição foi feita justamente na linha 48. E por que esta mudança teve de ser feita? O motivo é o código presente na função LoopEventOnTime. Bom, mas agora ficou confuso. Por que fazer a mudança no procedimento UpdateIndicatorControl, se o motivo para tal mudança se deve ao que acontece na função LoopEventOnTime? Isto não faz sentido. De fato, não faria sentido, se não fosse a questão de que a função LoopEventOnTime, fica lendo e escrevendo mensagens para acessar e modificar o indicador de controle. No entanto, se o teste que é visto na linha 48 não existir, algo estranho acontecerá quando você experimentar avançar no tempo e logo depois dar play. Isto antes de que implementemos o procedimento de avanço.

Se você avançar no tempo e dar play, ficará impedido de enviar um comando de pause para o serviço. O que? Mas que loucura é esta? Bastaria pressionar o botão de pause e o serviço receberá a atualização. Sim, ele irá de fato ele receber a atualização, dizendo para pausar o sistema. No entanto, isto não terá efeito imediato. E o motivo é a linha 41. O fato é que o buffer do indicador de controle estará em uma posição e a memória do buffer estará em uma outra posição. Assim se você iniciar o serviço, logo depois pressionar o play, em tentar avançar no tempo, nada de errado acontecerá. Mas se você pausar o serviço e tentar avançar o tempo e isto sem que o teste presente na linha 48 exista. A barra de trava no indicador de controle acompanhará o deslocamento que você está fazendo. Lhe tornará impossível o ajuste da posição pelo usuário.

Agora se você, iniciar o serviço de replay/simulação, avançar uma única posição e logo em seguida acionar o play. Não conseguirá pressionando o botão de pause, fazer o serviço parar. Ele somente irá de fato parar quando o teste na linha 41 for verdadeiro e o buffer do indicador estiver dizendo que o modo pause está selecionado. Isto poderá demorar um bocado de tempo. Talvez a explicação tenha ficando um pouco confusa, já que é preciso entender que existem três tipos de situações diferentes a se lidar. Cada uma delas tem seus problemas devido ao fato de que a função LoopEventOnTime fica lendo e enviando mensagens para o indicador de controle, durante o tempo normal de execução da aplicação de replay/simulação.

Porém, adicionando apenas e somente um simples teste e este sendo implementado na linha 48, como você pode ver no fragmento. Tudo se resolve e deixamos de ter problemas com relação ao que será feito pela função LoopEventOnTime. Podendo assim focarmos a nossa atenção em de fato fazer com que o avanço rápido aconteça.

Fazer, ou implementar o avanço rápido, não é de fato uma tarefa complexa. Na verdade, podemos fazer o avanço rápido, simplesmente adicionando os seguintes trechos, vistos abaixo, no arquivo C_Replay.mqh.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl "Market Replay Control"
013. //+------------------------------------------------------------------+
014. class C_Replay : public C_ConfigService
015. {
016.    private   :

              ...

035. //+------------------------------------------------------------------+
036. inline void UpdateIndicatorControl(void)
037.          {
038.             double Buff[];
039.                                  
040.             if (m_IndControl.Handle == INVALID_HANDLE) return;
041.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
042.             {
043.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
044.                   m_IndControl.Memory.dValue = Buff[0];
045.                if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
046.                   if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                      m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else if (m_IndControl.Mode == C_Controls::ePause)
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+

              ...

068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
092.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
093.             }
094.             m_Infos.CountReplay++;
095.          }
096. //+------------------------------------------------------------------+

              ...

123. //+------------------------------------------------------------------+
124.       void AdjustPositionToReplay(void)
125.          {
126.             int nPos;
127.             
128.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxPosSlider * 1.0) / m_MemoryData.nTicks)) return;
129.             nPos = (int)(m_MemoryData.nTicks * ((m_IndControl.Position * 1.0) / (def_MaxPosSlider + 1)));
130.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
131.                CreateBarInReplay(false);
132.          }
133. //+------------------------------------------------------------------+
134.    public   :
135. //+------------------------------------------------------------------+

              ...

200. //+------------------------------------------------------------------+
201.       bool LoopEventOnTime(void)
202.          {         
203.             int iPos;
204. 
205.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
206.             {
207.                UpdateIndicatorControl();
208.                Sleep(200);
209.             }
210.             m_MemoryData = GetInfoTicks();
211.             AdjustPositionToReplay();
212.             iPos = 0;
213.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
214.             {
215.                if (m_IndControl.Mode == C_Controls::ePause) return true;
216.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
217.                CreateBarInReplay(true);
218.                while ((iPos > 200) && (def_CheckLoopService))
219.                {
220.                   Sleep(195);
221.                   iPos -= 200;
222.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / m_MemoryData.nTicks);
223.                   UpdateIndicatorControl();
224.                }
225.             }
226. 
227.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
228.          }
229. //+------------------------------------------------------------------+
230. };
231. //+------------------------------------------------------------------+
232. #undef macroRemoveSec
233. #undef def_SymbolReplay
234. #undef def_CheckLoopService
235. //+------------------------------------------------------------------+

Trechos do código da classe C_Replay.mqh

Observe que no código acima, onde temos fragmentos e trechos do arquivo C_Replay.mqh. Podemos ver tudo que será preciso fazer a fim de conseguir o avanço rápido básico. Estou dizendo básico, pois existem alguns pequenos problemas que precisarei explicar. Mas é preciso que você entenda este modo mais básico, pois ele é a base para a implementação que iremos de fato fazer em breve. Note que aqui podemos ver aquilo que foi explicado acima, sobre a linha 48. Observe a linha 207. Esta linha é justamente a que nos causa problemas no indicador de controle, caso o teste presente na linha 48 não existisse. Você pode experimentar os problemas se desabilitar o teste na linha 48 e também desabilitando a linha 211. Mas como aqui as coisas já estão funcionando no indicador de controle, vamos as manter assim. Então vamos entender como o avanço rápido está sendo conseguido nesta versão mais básica.

Quando o código do serviço chamar a função LoopEventOnTime, nós estaremos no modo pause. Assim sendo, o loop que se inicia na linha 205 e vai até a linha 209, ficará executando de forma infinita, permitindo que o usuário ajuste a posição no indicador de controle. Ok. Assim que o usuário pressionar o botão dando play no serviço de replay/simulador, capturaremos os dados do ativo na linha 210, a fim de podermos acessá-los mais rapidamente. Logo depois na linha 211 efetuamos uma chamada ao procedimento presente na linha 124. Este procedimento é que fará o avanço rápido da posição atual até a posição requerida pelo usuário.

Agora na linha 128 verificamos se a posição informada pelo usuário é a que estamos atualmente utilizando. Se este for o caso, o procedimento de avanço termina e o loop presente entre as linhas 213 e 225 será executado. Mas se a posição requerida pelo usuário se encontra mais a frente, precisamos saber onde e para isto fazemos um pequeno cálculo na linha 129. Até aí nada muito mágico ou extraordinário. Mas na linha 130 a mágica acontece, pois entramos em um loop que fará com que a linha 131 execute o procedimento de criação das barras. Este procedimento, será executado da forma o mais rápida possível a fim de que o contador de posição venha a ser igual ao indicador de deslocamento calculado. Porém como você, poderá notar de forma clara e fácil, o procedimento chamado na linha 131 executará o código entre as linhas 69 e 95. Mas repare no fato de que estamos passando um parâmetro false para o procedimento de criação das barras. Por conta disto o teste presente na linha 91 evitará que a chamada CustomTicksAdd seja executada. Isto impedirá o lançamento de ticks para o ativo. Porém, a linha 92 lançará as barras no gráfico, conforme elas forem sendo construídas. Uma a uma.

Todo o processo funciona maravilhosamente bem, com exceção de dois pontos. Estes causam um retardo considerável na execução do avanço rápido. Então caso o usuário, venha a requerer um avanço grande o bastante, ele poderá visualizar a criação das barras sendo feitas no gráfico. Os responsáveis por tal retardo, são as linhas 75 e 92, além é claro a chamada do procedimento, mas está chamada não nos custa tanto assim, então podemos ignorar este valor. Mas estas outras duas linhas causam algum retardo, principalmente a linha 75.

Muito bem, esta é a forma básica de fazer isto. Porém temos uma forma consideravelmente mais rápida de fazer a mesma coisa. Porém, se você desejar visualizar as barras sendo construídas o caminho mais simples é este que acabamos de implementar. Se você modificar o código do arquivo C_Replay.mqh adicionando o que acabamos de ver, o avanço rápido já será possível. Então fica ao seu critério usar este mais básico, ou usar um mais elaborado, porém consideravelmente mais ágil no que rege executar o avanço rápido. Muito bem, então para separar as coisas vamos para um novo tópico.


Adicionando o recurso de avanço rápido ( Modelo ágil )

Se você observar com atenção o código notará que na classe C_FileTicks, já temos durante o carregamento dos ticks a confecção das barras de um minuto sendo feitas. Então por que devemos perder tempo refazendo isto? Vamos simplesmente usar as barras já criadas para que possamos nos aproximar ao máximo do ponto ideal e depois se for de nosso desejo, avançar até que o ponto perfeito que estará de fato sendo calculado seja de fato alcançado. Isto fará com que o procedimento de avanço rápido aconteça muito mais rapidamente. Nos parecendo ser algo instantâneo.

Porém nem tudo é magnífico e perfeito logo de cara. Para de fato termos agilidade no que queremos e precisaremos fazer, será necessário adicionar um tipo de índex, ou link que pode ser usado para efetuar uma pesquisa rápida. Mas para isto, vamos usar uma parte da estrutura de dados, que para nós aqui no replay/simulador não tem grande utilidade. Esta estrutura é o Spread que se encontra na estrutura MqlRates. Então para entender as mudanças que terão de ser feitas, veja o fragmento abaixo:

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.                rate[0].spread = m_Ticks.nTicks;
153.                for (int c0 = 0; c0 <= iRet; c0++)
154.                {
155.                   ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
156.                   m_Ticks.Info[m_Ticks.nTicks++] = local[c0];
157.                }
158.                m_Ticks.Rate[++m_Ticks.nRate] = rate[0];
159.             }
160.             ArrayFree(local);
161.             delete pFileBars;
162.             delete pSimulator;
163.             m_Ticks.bTickReal = false;
164.             
165.             return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0));
166.          }
167. //+------------------------------------------------------------------+
168.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
169.          {
170.             int      MemNRates,
171.                      MemNTicks,
172.                      nDigits,
173.                      nShift;
174.             datetime dtRet = TimeCurrent();
175.             MqlRates RatesLocal[],
176.                      rate;
177.             MqlTick  TicksLocal[];
178.             bool     bNew;
179.             
180.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
181.             nShift = MemNTicks = m_Ticks.nTicks;
182.             if (!Open(szFileNameCSV)) return 0;
183.             if (!ReadAllsTicks()) return 0;         
184.             rate.time = 0;
185.             nDigits = SetSymbolInfos(); 
186.             m_Ticks.bTickReal = true;
187.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
188.             {
189.                if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0];
190.                if (!BuildBar1Min(c0, rate, bNew)) continue;
191.                if (bNew)
192.                {
193.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
194.                   {
195.                      nShift = MemShift;
196.                      ArrayResize(TicksLocal, def_MaxSizeArray);
197.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
198.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0)
199.                         nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
200.                      delete pSimulator;
201.                      ArrayFree(TicksLocal);
202.                      if (c1 < 0) return 0;
203.                   }
204.                   rate.spread = MemShift;
205.                   MemShift = nShift;
206.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
207.                };
208.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
209.             }
210.             if (!ToReplay)
211.             {
212.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
213.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
214.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
215.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
216.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
217.                m_Ticks.nTicks = MemNTicks;
218.                ArrayFree(RatesLocal);
219.             }else   m_Ticks.nTicks = nShift;
220.                            
221.             return dtRet;
222.          };
223. //+------------------------------------------------------------------+

Fragmento de código do arquivo C_FileTicks.mqh

Este fragmento mostra exatamente as linhas que deverão ser modificadas no código do arquivo C_FileTicks.mqh. Observe que a linha 149 deverá ser removida, ou melhor dizendo, ela será movida para uma nova posição. Esta nova posição é a linha 158. Mas por que fazer isto? Calma, meu caro leitor. Tudo será explicado no seu devido tempo. Agora note que foi adicionada uma linha nova. Ela é a linha 152. Agora atenção ao seguinte fato: A função aqui, está transformando barras em ticks por meio da simulação. Então na linha 152, temos o valor de índex onde uma nova barra irá começar. Este valor é armazenado no spread da barra.

Agora vamos a próxima função, onde faremos algo muito parecido. Observe na foi adicionada a linha 204 e somente ela nesta função onde estaremos lendo os ticks. Mas você deve se lembrar do seguinte fato: Apesar de estarmos lendo os ticks de um arquivo, pode ser que seja necessário que os descartemos e no lugar dos mesmos, tenhamos que simular os ticks que serão utilizados. Vimos isto nos artigos anteriores, onde expliquei os motivos de se fazer tal coisa. Por conta deste fato, o valor que realmente nos interessa é justamente o valor de memória, ou seja, MemShift, que nos diz onde se inicia a nova barra. Da mesma forma como foi feito na função anterior, aqui armazenamos este valor no spread da estrutura de rates.

Muito bem, mas por que estamos fazendo isto? Qual a real utilidade de se fazer isto? Pois bem, vamos entender uma coisa agora. Durante a simulação das barras em ticks, que é feita na função anterior. Sabemos exatamente quando e onde cada barra começará, isto por que temos o índex que aponta exatamente para esta posição de início da barra antes que a mesma seja simulada. Da mesma forma aqui na função que carrega os ticks de um arquivo. Neste caso, na linha 190, transformamos os ticks em uma barra, assim como seria feito lá na classe C_Replay. Por conta disto, a cada nova barra criada aqui, sabemos onde exatamente ela começará. Então não precisamos deixar isto, para que a classe C_Replay, tenha o trabalho de procurar o ponto de início da barra. E já que o valor de spread não tem nenhuma serventia neste momento para a aplicação de replay/simulação, usamos este valor aparentemente inútil para armazenar algo valioso para nós. O ponto onde cada uma das barras estará de fato começando.

Se você observar, no tópico anterior o procedimento que executa o avanço rápido, notará que na linha 129, estamos fazendo um cálculo, este cálculo nos diz qual é exatamente o índex para onde deveremos pular a fim de avançar rapidamente o replay ou simulação. Começaram a entender, a importância deste valor, que está sendo gerado, durante o carregamento dos ticks? Este valor é muito importante e irá nos ajudar muito, a fim de que venhamos a conseguir, fazer com que o avanço rápido seja inda mais rápido. Isto por que, não precisaremos reconstruir barra a barra, podemos ir direto para o ponto correto e depois pedir para que o MetaTrader 5, mostre e atualize as barras entre os dois pontos. Ou seja, agora temos um novo modo de fazer as coisas.

Para que possamos fazer uso destes dados de uma forma útil, precisaremos mudar algumas coisas no código da classe C_Replay, mas será algo bem pouco, frente ao que foi colocado no tópico anterior. Então as você precisará adicionar os seguintes pontos no arquivo C_Replay.mqh que são vistos no fragmento imediatamente abaixo.

013. #define def_MaxSlider             (def_MaxPosSlider + 1)

              ...

124. //+------------------------------------------------------------------+
125.       void AdjustPositionToReplay(void)
126.          {
127.             int nPos, nCount;
128.             
129.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
130.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
131.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
132.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
133.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
134.                CreateBarInReplay(false);
135.          }
136. //+------------------------------------------------------------------+

Fragmento de código do arquivo C_Replay.mqh

Como você pode ver, foi necessário adicionar uma nova linha no arquivo de cabeçalho C_Replay.mqh. Esta é a linha 13, onde fazemos uma pequena correção no deslocamento geral a ser calculado. E novamente, tudo tem seu motivo de ser e de existir. Se você não fizer este pequeno ajuste, irá ter um outro problema, ou terá que fazer as coisas de uma outra forma. Mas para evitar ter que refazer grande parte da modelagem dos dados, prefiro simplesmente ajustar as coisas aqui. E por que este ajuste daqui, feito na linha 13 é importante para nós? O motivo é que sem ele, você ficará à mercê de um deslocamento onde a última posição no controle deslizante, presente no indicador de controle, simplesmente encerrará a simulação ou replay que estiver sendo feito. O simples fato de adicionar uma posição aqui, faz com que tenhamos alguns poucos ticks que poderão ser aplicados ao gráfico. Isto de certa forma é muito bom e garante um encerramento tranquilo para o simulador ou replay que estiver sendo usado.

Mas a questão que realmente nos importa se encontra nas linhas 131 e 132. Observe que apenas adicionando estas duas linhas temos um avanço consideravelmente mais rápido do que era feito anteriormente. Mas mesmo assim inda poderá haver alguns ticks que não estarão na posição, devendo assim serem adicionado da maneira como era feito antes. Tais ticks usarão o laço que se encontra presente na linha 133. Mas como serão bem poucos na grande maioria das vezes, o processo será bastante rápido.

De qualquer forma o que estamos de fato fazendo aqui é o seguinte: Na linha 131, procuramos qual é o índex da barra onde estará o valor imediatamente baixo do que iremos nos posicionar. Isto é feito inteiramente dentro do laço for. Apesar desta construção parecer bastante incomum, para grande maioria e vocês. Ela é perfeitamente funcional. O fato de ela parecer incomum, se deve a questão de que estou colocando dentro da declaração do laço, a atribuição do valor a ser colocado no contador de replay, o tal CountReplay. Mas nada impede de você colocar esta atribuição do lado de fora.

Mas na linha 132, realmente precisamos testar o valor de nCount. Isto por que não quero correr o risco da chamada de biblioteca do MQL5 CustomRatesUpdate não conseguir entender a quantidade de dados ou barras a serem utilizadas. O restante da função já foi explicada no tópico anterior. A parte curiosa destas mudanças, é que por conta delas, o código final no arquivo C_Replay.mqh precisou ser novamente modificado. Já que as modificações são simples e não precisam de fato serem explicadas, irei apenas mostrar o código final. Pelo menos até este momento. Assim sendo o código pode ser visto na íntegra logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl    "Market Replay Control"
013. #define def_MaxSlider              (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.          uCast_Double               Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void UpdateIndicatorControl(void)
038.          {
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
045.                   m_IndControl.Memory.dValue = Buff[0];
046.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
047.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
048.             }else
049.             {
050.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
051.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
052.                m_IndControl.Memory._8b[7] = 'D';
053.                m_IndControl.Memory._8b[6] = 'M';
054.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
055.             }
056.          }
057. //+------------------------------------------------------------------+
058.       void SweepAndCloseChart(void)
059.          {
060.             long id;
061.             
062.             if ((id = ChartFirst()) > 0) do
063.             {
064.                if (ChartSymbol(id) == def_SymbolReplay)
065.                   ChartClose(id);
066.             }while ((id = ChartNext(id)) > 0);
067.          }
068. //+------------------------------------------------------------------+
069. inline void CreateBarInReplay(bool bViewTick)
070.          {
071.             bool    bNew;
072.             double dSpread;
073.             int    iRand = rand();
074. 
075.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
076.             {
077.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
078.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
079.                {                  
080.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
081.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
082.                   {
083.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
084.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
085.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
086.                   {
087.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
088.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
089.                   }
090.                }
091.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
092.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
093.             }
094.             m_Infos.CountReplay++;
095.          }
096. //+------------------------------------------------------------------+
097.       void AdjustViewDetails(void)
098.          {
099.             MqlRates rate[1];
100. 
101.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
102.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
103.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
104.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
105.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
106.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
107.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
108.             if (rate[0].close > 0)
109.             {
110.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
111.                   m_Infos.tick[0].last = rate[0].close;
112.                else
113.                {
114.                   m_Infos.tick[0].bid = rate[0].close;
115.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
116.                }               
117.                m_Infos.tick[0].time = rate[0].time;
118.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
119.             }else
120.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
121.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
122.          }
123. //+------------------------------------------------------------------+
124.       void AdjustPositionToReplay(void)
125.          {
126.             int nPos, nCount;
127.             
128.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
129.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
130.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
131.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
132.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
133.                CreateBarInReplay(false);
134.          }
135. //+------------------------------------------------------------------+
136.    public   :
137. //+------------------------------------------------------------------+
138.       C_Replay()
139.          :C_ConfigService()
140.          {
141.             Print("************** Market Replay Service **************");
142.             srand(GetTickCount());
143.             SymbolSelect(def_SymbolReplay, false);
144.             CustomSymbolDelete(def_SymbolReplay);
145.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
146.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
147.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
148.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
149.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
150.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
151.             SymbolSelect(def_SymbolReplay, true);
152.             m_Infos.CountReplay = 0;
153.             m_IndControl.Handle = INVALID_HANDLE;
154.             m_IndControl.Mode = C_Controls::ePause;
155.             m_IndControl.Position = 0;
156.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
157.          }
158. //+------------------------------------------------------------------+
159.       ~C_Replay()
160.          {
161.             SweepAndCloseChart();
162.             IndicatorRelease(m_IndControl.Handle);
163.             SymbolSelect(def_SymbolReplay, false);
164.             CustomSymbolDelete(def_SymbolReplay);
165.             Print("Finished replay service...");
166.          }
167. //+------------------------------------------------------------------+
168.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
169.          {
170.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
171.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
172.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
173.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
174.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
175.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
176.             SweepAndCloseChart();
177.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
178.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
179.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
180.             else
181.                Print("Apply template: ", szNameTemplate, ".tpl");
182. 
183.             return true;
184.          }
185. //+------------------------------------------------------------------+
186.       bool InitBaseControl(const ushort wait = 1000)
187.          {
188.             Print("Waiting for Mouse Indicator...");
189.             Sleep(wait);
190.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
191.             if (def_CheckLoopService)
192.             {
193.                AdjustViewDetails();
194.                Print("Waiting for Control Indicator...");
195.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
196.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
197.                UpdateIndicatorControl();
198.             }
199.             
200.             return def_CheckLoopService;
201.          }
202. //+------------------------------------------------------------------+
203.       bool LoopEventOnTime(void)
204.          {         
205.             int iPos;
206. 
207.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
208.             {
209.                UpdateIndicatorControl();
210.                Sleep(200);
211.             }
212.             m_MemoryData = GetInfoTicks();
213.             AdjustPositionToReplay();
214.             iPos = 0;
215.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
216.             {
217.                if (m_IndControl.Mode == C_Controls::ePause) return true;
218.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
219.                CreateBarInReplay(true);
220.                while ((iPos > 200) && (def_CheckLoopService))
221.                {
222.                   Sleep(195);
223.                   iPos -= 200;
224.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
225.                   UpdateIndicatorControl();
226.                }
227.             }
228. 
229.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
230.          }
231. };
232. //+------------------------------------------------------------------+
233. #undef macroRemoveSec
234. #undef def_SymbolReplay
235. #undef def_CheckLoopService
236. #undef def_MaxSlider
237. //+------------------------------------------------------------------+

Código fonte final do arquivo C_Replay.mqh


Atualizando o tempo de barra e o percentual da cotação

Este problema será relativamente simples de ser resolvido. Isto por que tudo que precisamos fazer, será enviar mensagens, para o Indicador de mouse, a fim de que ele consiga compreender e nos apresentar tais mensagens de maneira adequada. O problema agora se trata, em fazer o indicador nos dizer quanto tempo está faltando, até uma nova barra se iniciar e qual é a variação percentual entre o fechamento do dia anterior com o valor que está atualmente sendo cotado.

Para simplificar a nossa vida, iremos fazer em primeiro lugar, justamente este o ajuste no percentual. Se você desejar pode criar o seu próprio método, com base no que mostrarei aqui. Fique à vontade de fazer as coisas da maneira como melhor lhe agradar. Vamos então começar a entender o problema do percentual. Se você observar o indicador de mouse, irá ver que o percentual da variação entre o fechamento e a cotação atual não estará mostrando valores corretos. No entanto, o valor baseado na posição do mouse estará correto. Por que está acontecendo tal diferença? Você pode pensar que o problema é se deve ao fato de existir algum motivo, pelo qual o indicador de mouse, não está conseguindo entender, onde os dados prévios, de fato terminaram e onde está começando a simulação ou replay. Mas não é bem isto que está acontecendo. O problema na verdade é outro. O indicador de mouse, de fato está conseguindo saber ler e interpretar os dados. E posso afirmar isto olhando a variação no momento que estamos movendo o mouse. Mas está acontecendo algo, que faz o indicador de mouse, se confundir de alguma forma e apresentar um valor estranho. Mas de tempos em tempos ele apresenta o valor correto. É este problema que precisamos resolver.

Para resolver isto, será preciso fazer algo muito simples. Porém quero alertar que você deve evitar ao máximo usar o que mostrarei aqui. O motivo é que se você se descuidar poderá perder o controle do que está sendo desenvolvido. Então vamos ver como o problema foi solucionado.

A primeira coisa a ser feita, será modificar o código do indicador de mouse. Isto é visto no fragmento abaixo.
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. //+------------------------------------------------------------------+
15. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
16. //+------------------------------------------------------------------+
17. C_Study *Study       = NULL;
18. //+------------------------------------------------------------------+
19. input color user02   = clrBlack;                         //Price Line
20. input color user03   = clrPaleGreen;                     //Positive Study
21. input color user04   = clrLightCoral;                    //Negative Study
22. //+------------------------------------------------------------------+
23. C_Study::eStatusMarket m_Status;
24. int m_posBuff = 0;
25. double m_Buff[];
26. //+------------------------------------------------------------------+
27. int OnInit()
28. {
29.    ResetLastError();
30.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
31.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
32.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
33.    {
34.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
35.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
36.       m_Status = C_Study::eCloseMarket;
37.    }else
38.       m_Status = C_Study::eInReplay;
39.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
40.    ArrayInitialize(m_Buff, EMPTY_VALUE);
41.    
42.    return INIT_SUCCEEDED;
43. }
44. //+------------------------------------------------------------------+
45. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
46.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
47.                 const long& volume[], const int& spread[]) 
48. //int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
49. {
50.    GL_PriceClose = close[rates_total - 1];
51.    m_posBuff = rates_total;
52.    (*Study).Update(m_Status);   
53.    
54.    return rates_total;
55. }
56. //+------------------------------------------------------------------+

Fragmento de código do Indicador de Mouse

Observe que na linha 13 adicionei uma variável. Esta variável é GLOBAL. E não estou falando que ela é global por não estar em um procedimento ou função. Mas ela é global por conta de sua localização e ponto de declaração. Esta mesma variável não faz absolutamente nada de fantástico. No entanto, na linha 50 ela receberá o valor que o MetaTrader 5 estará nos informando. Note também que a linha 48, que era a antiga declaração da função de evento OnCalculate foi trocada por outra. Isto é importante. Agora já podemos fazer uso desta variável declarada na linha 13, para solucionar o problema do percentual. Então a próxima mudança a ser feita, deverá acontecer no código do arquivo de cabeçalho C_Study.mqh. A mesma é vista logo abaixo:

41. //+------------------------------------------------------------------+
42.       void Draw(void)
43.          {
44.             double v1;
45.             
46.             if (m_Info.bvT)
47.             {
48.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
49.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
50.             }
51.             if (m_Info.bvD)
52.             {
53.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
54.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
55.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
56.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
57.             }
58.             if (m_Info.bvP)
59.             {
60.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
61.                v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
62.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
63.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
64.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
65.             }
66.          }
67. //+------------------------------------------------------------------+

Fragmento de código do arquivo C_Study.mqh

Veja que a linha 61, que é o antigo código presente no indicador, foi modificado por um novo código, que se encontra na linha 60. Vejam que aquela variável declarada no código do indicador está sendo referenciada aqui. Mas como isto é possível? O motivo é justamente o fato de estamos declarando esta variável como sendo global. Ou melhor dizendo, estamos declarando-a no corpo do código. Isto permite que ela possa ser observada por qualquer ponto dentro do código que está sendo construído. Este tipo de coisa costuma dar problemas. Por conta disto você sempre tem que ser bastante criterioso ao fazer uso deste tipo de coisa, declarar variáveis como sendo globais.

Quando preciso, por algum motivo fazer isto, faço de forma a saber que a variável poderá me dar problemas e que preciso tomar cuidado com ela. Então normalmente a defino antes dos includes e com um GL_ declarado nela. Apesar de que na linha 25 temos outra que também tem um escopo global. Esta da linha 25 não me preocupa muito, já que ela tem um propósito muito específico e dificilmente mexerei nela. Mas a da linha 13, está sim é algo a se tomar cuidado, pois você pode mexer nela sem perceber que está fazendo isto.

Desta forma bastante simples, resolvemos o problema do percentual está sendo mostrado de maneira as vezes estranha. Além disto conseguimos ganhar alguma agilidade na execução do código. O motivo é que não precisamos mais ficar capturando via chamada iClose, o valor de fechamento. O próprio MetaTrader 5 nos informa isto, nos poupando todo o trabalho em procurar esta informação.


Conclusão

Apesar de ter ficado faltando, explicar como solucionar a questão, referente ao tempo de fechamento da barra, no caso de estamos usando o replay/simulador. Fizemos grandes progressos aqui neste artigo. A aplicação de fato, se aproximou bastante do que havia durante o período que fizemos uso das variáveis globais de terminal. Mas acredito que muitos de vocês estão surpresos com a quantidade de coisas que vocês simplesmente não faziam ideia de que era possível fazer usando MQL5 puro. Mas ainda estamos apenas começando. Existem muitas coisas a serem feitas e cada uma irá nos representar um desafio e um novo aprendizado a ser realizado.

Apesar de não ter explicado, ainda, como fazer o Metatrader 5, nos informar o tempo restante da barra. Isto quando estamos usando a aplicação de replay/simulador. Farei isto já no início do próximo artigo. Não percam o mesmo, pois ali também começaremos a ajustar uma outra coisa, que também precisa ser melhorada a fim de funcionar adequadamente no que estamos já usando.

Infelizmente ainda não será neste artigo que permitirei você, que não é programador a fazer uso da aplicação. Isto por que ainda ficou faltando este detalhe, referente ao tempo de fechamento da barra. Mas se você já sabe programar e está conseguindo acompanhar as explicações. Fazendo as modificações que estou mostrando, verá que a aplicação de replay/simulador estará se comportando como é visto no vídeo logo abaixo. Então nos vemos no próximo artigo desta série. 


Vídeo de demonstração

Arquivos anexados |
Anexo.zip (420.65 KB)
Superando Desafios de Integração com ONNX Superando Desafios de Integração com ONNX
ONNX é uma ótima ferramenta para integrar códigos complexos de IA entre diferentes plataformas, sendo uma ferramenta excelente, mas que vem com alguns desafios que devem ser superados para aproveitar ao máximo suas capacidades. Neste artigo, discutimos os problemas mais comuns que você pode enfrentar e como mitigá-los.
Como adicionar Trailing Stop com o indicador Parabolic SAR Como adicionar Trailing Stop com o indicador Parabolic SAR
Ao criar uma estratégia de negociação, precisamos testar diversas opções de stops de proteção. Aqui, surge a ideia de ajustar dinamicamente o nível do Stop Loss acompanhando o movimento do preço. O melhor candidato para isso é o indicador Parabolic SAR, porque é difícil pensar em algo mais simples e claro.
Redes neurais de maneira fácil (Parte 88): Codificador denso de séries temporais (TiDE) Redes neurais de maneira fácil (Parte 88): Codificador denso de séries temporais (TiDE)
O desejo de obter previsões mais precisas leva os pesquisadores a complicar os modelos de previsão. Isso, por sua vez, aumenta os custos de treinamento e manutenção do modelo. Mas será que isso sempre é justificado? Neste artigo, proponho que você conheça um algoritmo que utiliza a simplicidade e a velocidade dos modelos lineares, e demonstra resultados no nível dos melhores com uma arquitetura mais complexa.
Redes neurais de maneira fácil (Parte 87): Segmentação de séries temporais Redes neurais de maneira fácil (Parte 87): Segmentação de séries temporais
A previsão desempenha um papel importante na análise de séries temporais. No novo artigo, falaremos sobre as vantagens da segmentação de séries temporais.