English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay (Parte 69): Acertando o tempo (II)

Desenvolvendo um sistema de Replay (Parte 69): Acertando o tempo (II)

MetaTrader 5Exemplos |
382 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 68): Acertando o tempo (I), expliquei a parte do código referente ao indicador de mouse. Mas aquele código não tem nenhum valor sem que você veja o código do serviço de replay/simulador. De qualquer forma, se você não leu o artigo anterior, sugiro que o faça antes de começar a tentar entender este daqui. Isto por que um irá de fato complementar o outro.

O foco aqui, por enquanto será a questão de informar o tempo restante da barra, quando o ativo estiver com baixa liquidez. Isto por que nestes momentos, não teremos a geração espontânea, ou melhor dizendo, tradicional de eventos OnCalculate. Assim o indicador de mouse, não irá de maneira alguma receber os valores corretos referentes aos segundos que já se passaram. Mas com base no que foi visto no artigo anterior, podemos sim passar os valores, de modo que o indicador consiga calcular a quantidade de segundos restantes.

Neste início vamos focar basicamente no serviço de replay/simulador. Para ser mais exato, o nosso foco estará concentrado no arquivo C_Replay.mqh. Então vamos começar a ver o que teve de ser modificado, ou adicionado ao código.


Ajustando o arquivo C_Replay.mqh

As mudanças que terão de ser feitas, não são tantas. Porém elas farão com que o código visto no artigo anterior, passe a ter algum sentido. Principalmente a parte relacionada ao uso da função de biblioteca iSpread no evento OnCalculate. Muito provavelmente você deve ter ficado na dúvida do motivo pelo qual usei a função iSpread. Isto por que seria mais prático ler o valor de spread diretamente do array, que é recebido pela função OnCalculate.

De fato, isto é algo bastante curioso. Porém para entender o motivo, temos que entender como as coisas de fato funcionam. Para isto é preciso que possamos ver e compreender como o código do serviço de replay/simulador está conseguindo fazer as coisas. Além é claro, entender como as informações estão sendo trabalhadas pelo MetaTrader 5.

Vamos começar pela parte mais simples. Entender o código presente no arquivo C_Replay.mqh. Isto por que ele é o responsável por gerar as informações que nos são apresentadas no gráfico. O código do mesmo, já modificado, pode ser visto logo abaixo. Na íntegra:

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.             static int st_Spread = 0;
075. 
076.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
077.             {
078.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
079.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
080.                {                  
081.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
082.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
083.                   {
084.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
085.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
086.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
087.                   {
088.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
089.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
090.                   }
091.                }
092.                if (bViewTick)
093.                {
094.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
095.                   if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, "");
096.                }
097.                st_Spread = (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time);
098.                m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time);
099.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
100.             }
101.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
102.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
103.             m_Infos.CountReplay++;
104.          }
105. //+------------------------------------------------------------------+
106.       void AdjustViewDetails(void)
107.          {
108.             MqlRates rate[1];
109. 
110.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
111.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
112.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
113.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
114.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
115.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
116.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
117.             if (rate[0].close > 0)
118.             {
119.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
120.                   m_Infos.tick[0].last = rate[0].close;
121.                else
122.                {
123.                   m_Infos.tick[0].bid = rate[0].close;
124.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
125.                }               
126.                m_Infos.tick[0].time = rate[0].time;
127.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
128.             }else
129.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
130.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
131.          }
132. //+------------------------------------------------------------------+
133.       void AdjustPositionToReplay(void)
134.          {
135.             int nPos, nCount;
136.             
137.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
138.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
139.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
140.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
141.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
142.                CreateBarInReplay(false);
143.          }
144. //+------------------------------------------------------------------+
145.    public   :
146. //+------------------------------------------------------------------+
147.       C_Replay()
148.          :C_ConfigService()
149.          {
150.             Print("************** Market Replay Service **************");
151.             srand(GetTickCount());
152.             SymbolSelect(def_SymbolReplay, false);
153.             CustomSymbolDelete(def_SymbolReplay);
154.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
155.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
156.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
157.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
158.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
159.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
160.             SymbolSelect(def_SymbolReplay, true);
161.             m_Infos.CountReplay = 0;
162.             m_IndControl.Handle = INVALID_HANDLE;
163.             m_IndControl.Mode = C_Controls::ePause;
164.             m_IndControl.Position = 0;
165.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
166.          }
167. //+------------------------------------------------------------------+
168.       ~C_Replay()
169.          {
170.             SweepAndCloseChart();
171.             IndicatorRelease(m_IndControl.Handle);
172.             SymbolSelect(def_SymbolReplay, false);
173.             CustomSymbolDelete(def_SymbolReplay);
174.             Print("Finished replay service...");
175.          }
176. //+------------------------------------------------------------------+
177.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
178.          {
179.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
180.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
181.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
182.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
183.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
184.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
185.             SweepAndCloseChart();
186.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
187.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
188.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
189.             else
190.                Print("Apply template: ", szNameTemplate, ".tpl");
191. 
192.             return true;
193.          }
194. //+------------------------------------------------------------------+
195.       bool InitBaseControl(const ushort wait = 1000)
196.          {
197.             Print("Waiting for Mouse Indicator...");
198.             Sleep(wait);
199.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
200.             if (def_CheckLoopService)
201.             {
202.                AdjustViewDetails();
203.                Print("Waiting for Control Indicator...");
204.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
205.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
206.                UpdateIndicatorControl();
207.             }
208.             
209.             return def_CheckLoopService;
210.          }
211. //+------------------------------------------------------------------+
212.       bool LoopEventOnTime(void)
213.          {         
214.             int iPos;
215. 
216.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
217.             {
218.                UpdateIndicatorControl();
219.                Sleep(200);
220.             }
221.             m_MemoryData = GetInfoTicks();
222.             AdjustPositionToReplay();
223.             EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, "");
224.             iPos = 0;
225.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
226.             {
227.                if (m_IndControl.Mode == C_Controls::ePause) return true;
228.                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);
229.                CreateBarInReplay(true);
230.                while ((iPos > 200) && (def_CheckLoopService))
231.                {
232.                   Sleep(195);
233.                   iPos -= 200;
234.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
235.                   UpdateIndicatorControl();
236.                }
237.             }
238. 
239.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
240.          }
241. };
242. //+------------------------------------------------------------------+
243. #undef def_SymbolReplay
244. #undef def_CheckLoopService
245. #undef def_MaxSlider
246. //+------------------------------------------------------------------+

Código fonte do arquivo C_Replay.mqh

Neste código acima, você pode notar que existem algumas linhas que foram riscadas. Estas deverão ser removidas do código que existia antes deste artigo. Não são tantas linhas assim. Mas as consequências geradas serão enormes.

A primeira coisa que você deve notar, é que na linha 74 temos uma nova variável. Esta variável tem como objetivo, algo relativamente simples. Contar os segundos, quando a liquidez desmontar ou ficar muito baixa. Apesar de não está sendo feito este tipo de coisa, neste momento exato. Precisamos entender o que está acontecendo, para conseguir entender como isto será feito. 

Primeiramente note que na linha 223, o evento customizado foi removido do código original. Mas também observe uma coisa. A cada interação do laço que se inicia na linha 225, temos uma chamada a CreateBarInReplay. Isto na linha 229. Agora atenção ao seguinte detalhe: A cada, mais ou menos, 195 milissegundos, isto por conta da linha 232 e pelo tempo necessário para executar as chamadas do loop da linha 225. É que a chamada a CreateBarInReplay será executada. Então teremos cerca de cinco chamadas a cada segundo. Isto quando não houver nenhuma chamada entre elas. Você agora deve se esquecer que não estamos mais lidando um momento de boa liquidez. Estou tentando mostrar como o serviço de replay/simulador, irá de fato funcionar quando a liquidez ficar muito baixa. Então guarde este número. Temos cinco chamadas, aproximadamente a cada segundo, a função CreateBarInReplay.

Então vamos voltar ao procedimento CreateBarInReplay, para entender o que acontece quando a liquidez é adequada, ou seja, quando temos no mínimo de cinco chamadas sendo feitas a cada segundo.

Neste caso a o teste na linha 76, será verdadeiro. Então teremos a execução do conteúdo entre as linhas 77 e 100. Não se esqueça de notar que dentro deste range de linhas, temos algumas que foram removidas do código. Observe isto vendo as linhas riscadas. Entre estas linhas riscadas se encontra a linha 95 que gerava um evento customizado a cada nova barra de um minuto. Este fato pesará muito para que a função iSpread, apareça no procedimento OnCalculate. Mas por enquanto, não se preocupe com isto. Vamos entender o básico primeiro. Note que na linha 97, foi adicionado um novo código. Este inicializa o valor da variável.

Agora muita atenção ao seguinte fato: Observe que as linhas 98 e 99 foram riscadas. Mas o seu código não deixou de existir, ele apenas mudou de posição. Antes ele estava dentro do código a ser executado caso a condição na linha 76 fosse verdadeira. Agora ele será executado de qualquer maneira. Isto por conta que ele passou a ser executado nas linhas 101 e 102. Mas atenção, a linha 101, apesar de ser diferente, faz a mesma coisa que era feita na linha 98. Só que agora faremos uso de uma máscara. Isto para que o indicador de mouse, saiba que o valor visto no spread, foi originado aqui no serviço de replay/simulador. Observe que tudo que estamos fazendo é usar uma operação OR. Assim a máscara fica configurada adequadamente. Mas isto tem um problema, pois se o valor da variável st_Spread invadir a região de bits da máscara, o indicador de mouse, não conseguirá entender os valores que estão chegando.

Desta forma se algo ficar estranho, ou vier a dar errado, bastará que você venha e verifique se o valor da variável st_Spread não está invadindo a região de bits da máscara. Mas isto não irá de fato acontecer em situações normais. Já que o replay/simulador é voltado para fazer estudos e análises em termos de intraday. Então, apenas se e somente se, o serviço de replay/simulador for levado ao seu limite extremo de funcionamento em termos de horário teremos tal condição ocorrendo. Mas de qualquer forma, pelo sim ou pelo não, posso adiantar que o tempo limite, são quase 12 dias. Isto em termos de segundos. O que para nosso propósito é muito mais do que o suficiente.

Mas vamos continuar a entender como a coisa funciona. Então se você compilar e executar o serviço de replay/simulador junto com o código compilado do indicador de mouse, visto no artigo anterior. Conseguirá se o ativo estiver com uma liquidez adequada, ou seja, pelo menos um tick por segundo. Receber a atualização correta do tempo restante para a barra atual encerrar e uma nova se iniciar.

Muito bem, mas isto não explica o motivo, pelo qual não foi feito o uso do array spread, que está presente em uma das versões da chamada OnCalculate. E por que foi preciso usar iSpread, para obter o valor do spread, que o serviço estará informando segundo pode ser visto na linha 101. Para entender o motivo disto acontecer, será preciso entender uma outra coisa agora.


Entendendo por que usar iSpread

No momento que escrevo este artigo, a versão mais recente do MetaTrader 5, pode ser vista na imagem abaixo:

Image 01

Até nesta versão mencionada, e pode ser que no momento que você, caro leitor, esteja lendo este artigo. O MetaTrader 5 ainda trate as barras, pelo menos no que diz respeito aos ativos customizados, de uma forma engraçada. Talvez não todas as informações, das quais se refere a barra. Mas como estamos transmitindo informações pelo spread, este pode ser verificado que funciona de uma forma engraçada.

Para demonstrar isto, vamos fazer pequenas modificações no código, presente no arquivo de cabeçalho C_Replay.mqh e no indicador de mouse. Assim acredito que ficará mais claro demonstrar o que realmente acontece. Pois apenas falar não vai ser o suficiente. Então no arquivo C_Replay.mqh, mude o código no seguinte fragmento mostrado abaixo:

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

Fragmento do arquivo C_Replay.mqh

Observe que o código do fragmento já se encontra limpo, então a enumeração das linhas pode estar ligeiramente diferente. Mas o código será idêntico ao que foi visto um pouco mais acima neste artigo. A diferença está na linha 96, que foi adicionada a fim de mostrar no terminal, o valor que estamos dentro do spread da barra. Como resultado da execução deste código modificado, teremos o que pode ser visto abaixo:

Anime 01

Note que o valor passado, é exatamente igual, ao valor que estamos vendo como sendo o tempo, no gráfico de ticks. É muito importante você perceber isto. Então já temos a confirmação, de que o valor que está sendo colocado no spread da barra, de fato é o valor de tempo no gráfico. Agora vamos ver uma outra coisa. Vamos modificar levemente o indicador de controle, algo bem sutil, apenas para analisar como o sistema está funcionando. A modificação que faremos será no código do arquivo de cabeçalho C_Study.mqh. Esta modificação pode ser vista logo abaixo:

109. //+------------------------------------------------------------------+
110.       void Update(const eStatusMarket arg)
111.          {
112.             int i0;
113.             datetime dt;
114.                      
115.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
116.             {
117.                case eCloseMarket :
118.                   m_Info.szInfo = "Closed Market";
119.                   break;
120.                case eInReplay    :
121.                case eInTrading   :
122.                   i0 = PeriodSeconds();
123.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
124.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
125.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time/* - dt*/, TIME_SECONDS);
126.                   break;
127.                case eAuction     :
128.                   m_Info.szInfo = "Auction";
129.                   break;
130.                default           :
131.                   m_Info.szInfo = "ERROR";
132.             }
133.             Draw();
134.          }
135. //+------------------------------------------------------------------+

Fragmento do arquivo C_Study.mqh

Observe com bastante atenção, pois é algo bem sutil. Na linha 125, foi removido o ajuste dt, ou seja, agora a informação que será mostrada: É quando uma nova barra é esperada. Atenção a isto, não é quanto tempo resta para uma nova barra e sim quando a nova barra irá de fato aparecer. Então compilamos novamente o indicador de mouse, a fim de podermos testar o valor que será mostrado. Na animação abaixo você pode verificar o que acontece de fato.

Anime 2

Note que o tempo gráfico usado é de dois minutos. Então o cálculo executado, indica quando a nova barra, irá de fato surgir. Isto é mostrado no indicador de mouse. Veja que quando o tempo gráfico alcança a posição, automaticamente o indicador passará a informar quando uma nova barra surgirá. Ou seja. O sistema está funcionando de fato. Mas nestes testes não foi verificado o valor que o serviço de replay/simulação está nos informando. O que estamos fazendo é apenas verificando as informações que sabíamos que estariam ali. Mas agora vamos verificar o valor que o serviço está nos repassando. Devemos nos assegurar que o tempo gráfico não seja de um minuto, caso contrário o teste será invalido. Então vamos manter o tempo de dois minutos, pois este já nos será adequado para analisar o que está acontecendo.

Mas para que o teste ocorra como o esperado, teremos que fazer uma pequena mudança. Novamente preste atenção ao código que se encontra no fragmento abaixo.

109. //+------------------------------------------------------------------+
110.       void Update(const eStatusMarket arg)
111.          {
112.             int i0;
113.             datetime dt;
114.                      
115.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
116.             {
117.                case eCloseMarket :
118.                   m_Info.szInfo = "Closed Market";
119.                   break;
120.                case eInReplay    :
121.                case eInTrading   :
122.                   i0 = PeriodSeconds();
123.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
124.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
125.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)/*m_Info.Rate.time -*/ dt, TIME_SECONDS);
126.                   break;
127.                case eAuction     :
128.                   m_Info.szInfo = "Auction";
129.                   break;
130.                default           :
131.                   m_Info.szInfo = "ERROR";
132.             }
133.             Draw();
134.          }
135. //+------------------------------------------------------------------+

Fragmento do arquivo C_Study.mqh

Agora o que estamos fazendo, é isolar o valor que o serviço está nos informando e mostrando ele no indicador de mouse. O resultado pode ser visto na animação abaixo:

Anime 3

Veja que corresponde exatamente ao que era esperado. Agora não vamos mexer neste código de cabeçalho. Mas iremos de fato fazer uma outra coisa. E ainda dentro do indicador de mouse. Então vamos verificar, o que aconteceria se estivéssemos utilizando o valor de spread, obtido durante a chamada de OnCalculate. Para fazer isto, precisemos modificar o código do indicador de mouse. Mas não se esqueça do seguinte fato: O valor que será mostrado no indicador, será o que está sendo capturado e colocado na variável GL_TimeAdjust. Lembrar deste fato é muito importante. Então vamos modificar o código do indicador, a fim de podermos testar se é ou não adequado usar o valor de spread obtido na chamada OnCalculate. O novo código pode ser visto no fragmento abaixo.

46. //+------------------------------------------------------------------+
47. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
48.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
49.                 const long& volume[], const int& spread[])
50. //int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[])
51. {
52.    GL_PriceClose = close[rates_total - 1];
53. //   GL_PriceClose = price[rates_total - 1];
54.    GL_TimeAdjust = (spread[rates_total - 1] & (~def_MaskTimeService);
55. //   if (_Symbol == def_SymbolReplay)
56. //      GL_TimeAdjust = iSpread(NULL, PERIOD_M1, 0) & (~def_MaskTimeService);
57.    m_posBuff = rates_total;
58.    (*Study).Update(m_Status);   
59.    
60.    return rates_total;
61. }
62. //+------------------------------------------------------------------+

Fragmento do arquivo do indicador de mouse

Observe, exatamente o que estamos fazendo, neste fragmento acima. Estamos isolando o código de modo que as linhas riscadas são o código atual, visto no artigo anterior. Devendo assim ser removido de forma temporária. No lugar dele, colocamos um outro código, desta vez, a fim de usar os dados fornecidos pelo MetaTrader 5 a função OnCalculate. Ou seja, agora não mais faremos uso da chamada iSpread. Vamos usar o valor, que o MetaTrader 5, estará nos fornecendo no array spread. Então para manter a compatibilidade com o serviço, precisamos fazer uma pequena mudança e esta é vista na linha 54. Veja que é a mesma coisa que era feito antes, quando usávamos iSpread. Só que agora o valor a ser usado, vem como argumento na chamada OnCalculate. Isto sim, iria de fato fazer uma grande diferença para nós. Já que eliminaria a necessidade, de uma chamada extra, apenas para buscar o valor que o MetaTrader 5, nos fornece como argumento da OnCalculate.

Mas vamos ver o resultado da execução deste código. Isto pode ser visto na animação, logo abaixo:

Anime 4

Ops. Mas o que foi isto? O que aconteceu aqui? Por que o valor do indicador de mouse congelou? A resposta a estas perguntas não é simples de se dar. Ao contrário do que muitos podem achar, eu não sei a resposta. Mas como assim? Como você não sabe a resposta? É verdade que suspeito de algumas coisas, mas entre suspeitas e afirmar algo, prefiro ficar calado. Acho mais adequado, simplesmente mostrar a você, caro leitor, algo que talvez você imaginasse que não acontecia, acontecendo. Isto de forma que você possa verificar por si só e tirar suas próprias conclusões.

Mas de qualquer maneira, o serviço continua lançando os valores da mesma forma, como vimos nas animações anteriores. O indicador continua a capturar o valor do spread. Porém por que o valor congelou no indicador? Bem. Isto eu não sei explicar. Só sei que quando uma nova barra surgir no gráfico, e por isto, é precisamos estar em um tempo diferente de um minuto, para verificar esta falha, o valor presente no array do spread será atualizado adequadamente.

Lembrando novamente. O que estou mostrando aqui, pode não está acontecendo, no momento em que você, caro leitor estiver lendo este artigo. O motivo é que possivelmente o MetaTrader 5, já terá sofrido alguma atualização que corrige este problema. De qualquer forma, até que isto aconteça, vou quebrando o galho, fazendo uso da função iSpread. Mas assim que estiver este pequeno problema estiver sido corrigido. Não iremos mais fazer uso da chamada iSpread. Iremos de fato, usar o valor que será repassado pelo MetaTrader 5 a função OnCalculate. Então não se apegue a nenhuma parte do código, pois conforme as coisas forem progredindo, ele será melhorado a fim de se manter cada vez melhor. Muito bem, agora acredito, que você tenha compreendido o por que estou fazendo uso da função iSpread, e não usando o valor que vem como argumento na chamada OnCalculate. Mas ainda não terminamos. Precisamos ainda bolar uma forma de fazer com que o serviço nos diga o tempo restante de uma barra, quando a liquidez, faz com que não tenhamos ticks, ou melhor dizendo eventos que chamam a função OnCalculate, a cada segundo. Para fazer isto, vamos remover as modificações que fizemos neste tópico, a fim de explicar por que da função iSpread, está sendo usada no indicador e retornar a trabalhar no serviço.


Corrigindo uma falha no serviço

Infelizmente, para que as coisas venham a funcionar, quando o tempo entre os ticks é maior do que um segundo. Teremos que fazer uma abordagem um pouco diferente do que foi planejado no começo deste artigo. O detalhe é que eu estava desejando colocar o contador, dentro da rotina de criação das barras. Porém eu havia me esquecido de um detalhe. O TEMPO. Volte no início do artigo e veja o código fonte do arquivo de cabeçalho C_Replay.mqh. Na linha 230 temos um laço que serve para segurar o serviço de replay/simulador, por algum tempo, até que tenha se passado o tempo necessário para que um novo tick venha a surgir. E aqui temos um problema.

O problema é que, durante todo este tempo, eu estava testando o serviço de replay/simulação, em ativos com uma boa liquidez. Ou melhor dizendo, que tinha no histórico, um tempo entre ticks que era inferior a um segundo. E ao começar a fazer as mudanças, a fim de cobrir o fato de que pode acontecer de termos um tempo entre ticks maior do que um segundo. Uma falha surgiu. Não que ela tenha aparecido do nada. Na verdade, ela já estava ali. Só que por conta do tempo entre os ticks, a mesma estava passando desapercebida. Mas quero que você se atente justamente no laço, que se encontra entre as linhas 230 e 236. O que tem de errado ali? O erro está no fato de que, não estou tratando a pausa que o usuário possa está vindo a dar no sistema. Mas como assim? Se o serviço está em um laço, esperando o tempo até que o próximo tick. Não existe falha ali! De fato, mas quando o tempo a se aguardar é acima de um segundo, temos um grande problema.

Vamos supor que você esteja fazendo um replay de dados do forex. No início da série diária, normalmente o tempo entre os ticks costuma ser bem grande. Então se você der play no serviço e ele detecte que terá de ficar 40 segundos aguardando, para que o próximo tick seja alcançado. Mesmo que você venha a dar pause e mova o pino do indicador de controle para uma outra posição. E logo depois volte a dar play no serviço, o mesmo não responderá a este pedido de play. E o motivo é por que, o serviço está preso no laço entre as linhas 230 e 236. Até que todo o tempo detectado de 40 segundos tenha se passado. Então a primeira coisa a se corrigir será isto. Mas para não perder tempo, corrigindo algo, para logo depois implementar a solução, onde podemos ver o tempo restante mesmo quando a liquidez está baixa. Vamos fazer tudo de uma só fez. Então o novo código para o arquivo C_Replay.mqh, 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 int RateUpdate(bool bCheck)
070.          {
071.             static int st_Spread = 0;
072. 
073.             st_Spread = (bCheck ? (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time) : st_Spread + 1);
074.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
075.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
076.             
077.             return 0;
078.          }
079. //+------------------------------------------------------------------+
080. inline void CreateBarInReplay(bool bViewTick)
081.          {
082.             bool    bNew;
083.             double dSpread;
084.             int    iRand = rand();
085.             static int st_Spread = 0;
086. 
087.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
088.             {
089.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
090.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
091.                {                  
092.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
093.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
094.                   {
095.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
096.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
097.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
098.                   {
099.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
100.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
101.                   }
102.                }
103.                if (bViewTick)
104.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
105.                RateUpdate(true);
106.                st_Spread = (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time);
107.             }
108.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
109.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
110.             m_Infos.CountReplay++;
111.          }
112. //+------------------------------------------------------------------+
113.       void AdjustViewDetails(void)
114.          {
115.             MqlRates rate[1];
116. 
117.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
118.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
119.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
120.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
121.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
122.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
123.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
124.             if (rate[0].close > 0)
125.             {
126.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
127.                   m_Infos.tick[0].last = rate[0].close;
128.                else
129.                {
130.                   m_Infos.tick[0].bid = rate[0].close;
131.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
132.                }               
133.                m_Infos.tick[0].time = rate[0].time;
134.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
135.             }else
136.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
137.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
138.          }
139. //+------------------------------------------------------------------+
140.       void AdjustPositionToReplay(void)
141.          {
142.             int nPos, nCount;
143.             
144.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
145.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
146.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
147.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
148.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
149.                CreateBarInReplay(false);
150.          }
151. //+------------------------------------------------------------------+
152.    public   :
153. //+------------------------------------------------------------------+
154.       C_Replay()
155.          :C_ConfigService()
156.          {
157.             Print("************** Market Replay Service **************");
158.             srand(GetTickCount());
159.             SymbolSelect(def_SymbolReplay, false);
160.             CustomSymbolDelete(def_SymbolReplay);
161.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
162.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
163.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
164.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
165.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
166.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
167.             SymbolSelect(def_SymbolReplay, true);
168.             m_Infos.CountReplay = 0;
169.             m_IndControl.Handle = INVALID_HANDLE;
170.             m_IndControl.Mode = C_Controls::ePause;
171.             m_IndControl.Position = 0;
172.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
173.          }
174. //+------------------------------------------------------------------+
175.       ~C_Replay()
176.          {
177.             SweepAndCloseChart();
178.             IndicatorRelease(m_IndControl.Handle);
179.             SymbolSelect(def_SymbolReplay, false);
180.             CustomSymbolDelete(def_SymbolReplay);
181.             Print("Finished replay service...");
182.          }
183. //+------------------------------------------------------------------+
184.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
185.          {
186.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
187.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
188.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
189.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
190.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
191.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
192.             SweepAndCloseChart();
193.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
194.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
195.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
196.             else
197.                Print("Apply template: ", szNameTemplate, ".tpl");
198. 
199.             return true;
200.          }
201. //+------------------------------------------------------------------+
202.       bool InitBaseControl(const ushort wait = 1000)
203.          {
204.             Print("Waiting for Mouse Indicator...");
205.             Sleep(wait);
206.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
207.             if (def_CheckLoopService)
208.             {
209.                AdjustViewDetails();
210.                Print("Waiting for Control Indicator...");
211.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
212.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
213.                UpdateIndicatorControl();
214.             }
215.             
216.             return def_CheckLoopService;
217.          }
218. //+------------------------------------------------------------------+
219.       bool LoopEventOnTime(void)
220.          {         
221.             int iPos, iCycles;
222. 
223.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
224.             {
225.                UpdateIndicatorControl();
226.                Sleep(200);
227.             }
228.             m_MemoryData = GetInfoTicks();
229.             AdjustPositionToReplay();
230.             iPos = iCycles = 0;
231.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
232.             {
233.                if (m_IndControl.Mode == C_Controls::ePause) return true;
234.                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);
235.                CreateBarInReplay(true);
236.                while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause))
237.                {
238.                   Sleep(195);
239.                   iPos -= 200;
240.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
241.                   UpdateIndicatorControl();
242.                   iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1);
243.                }
244.             }
245. 
246.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
247.          }
248. };
249. //+------------------------------------------------------------------+
250. #undef def_SymbolReplay
251. #undef def_CheckLoopService
252. #undef def_MaxSlider
253. //+------------------------------------------------------------------+

Código fonte do arquivo C_Replay.mqh

Então vamos as explicações, do como este código agora funciona. Vamos começar pelo final, que é onde o serviço funcionava, de forma estranha, quando a liquidez era muito baixa. Observe que agora, na linha 236, houve uma correção no código. Assim ele passou a não ficar bloqueado, esperando que um tempo detectado e que fosse alto, o mantivesse fora do alcance do usuário. Tudo que foi preciso fazer, neste caso, foi adicionar o teste a fim de verificar, se o usuário deu pause no sistema. Se isto ocorrer, o laço será finalizado, e quando a execução atingir a linha 233, a função será encerrada, voltando ao código principal. Este por sua vez fará uma nova chamada a função e ficaremos no aguarde. Porém desta vez ficaremos no laço da linha 223. Isto permite ao usuário ajustar o pino no indicador de controle e partir para uma nova posição mais a frente. Este tipo de coisa, nos fornece um funcionamento interessante. Isto quando o ativo está com baixa liquidez ou quando ele entra em leilão. Talvez você não consiga entender, o que estou dizendo, apenas olhando este procedimento LoopEventOnTime. Mas conforme a explicação for se dando você conseguirá entender.

Vamos entender as modificações que foram feitas, a fim de conseguir informar o tempo restante da barra, quando a liquidez está baixa. Na linha 221, adicionei uma nova variável. Esta é inicializada na linha 230. Pois bem, agora atenção na linha 242, onde usamos esta mesma variável. Veja que estamos contando de zero a quatro. E quando atingimos o valor quatro, faremos uma chamada a RateUpdate. Mas que função é esta? Esta tal de RateUpdate? Calma me caro leitor. Nós vamos chegar lá. De qualquer forma, veja que a chamada é feita com um argumento falso e que o retorno da função é atribuído a variável. Isto é importante, então preste atenção a este detalhe. Lembra que falei neste artigo, que teremos aproximadamente cinco ciclos em um segundo? Pois é justamente por isto que temos este contador. A ideia aqui, é fazer com que o indicador de mouse, receba um valor indicando que se passou um segundo no tempo. Mas a coisa não é bem assim. Este tempo não é exatamente de um segundo. Ele é algo aproximado. E o motivo é que não estamos temporizando, em exatamente um segundo o tempo. Mas o intuito não é termos uma precisão neste cronometro, mas sim dar uma ideia ao usuário de quanto tempo resta para que a barra se feche.

Vamos então para um outro ponto, neste mesmo código. Vamos agora para o procedimento, que se inicia na linha 80. Observe que temos linhas que foram riscadas do código. E novamente uma chamada a RateUpdate aparece no nosso código. Mas repare que desta vez o argumento passado é verdadeiro. Então existe algo diferente aqui. Caso estejamos adicionando um novo tick, o argumento deverá ser verdadeiro. Caso estejamos apenas atualizando o tempo, o argumento deverá ser falso. Hum, interessante. Então vamos ver este procedimento RateUpdate, que pode ser visto a partir da linha 69.

Esta função, RateUpdate, teve que ser criada, justamente por conta, que durante a atualização do tempo, haveria o perigo de pularmos alguns ticks. Isto por conta da linha 110. Já que não devemos correr este risco, é melhor fazer as coisas em outro local. Assim surgiu esta função. Repare que a variável declarada na linha 85, foi movida para a linha 71. Assim como o trabalho que era feito, nas linhas 108 e 109, passou a ser feito nas linhas 74 e 75. Ou seja, esta função é quase uma cópia do que já existia. A diferença está na linha 73. Mas repare o fato de que esta função sempre retorna o valor zero. Isto por conta, do que é esperado na linha 242.

Mas vamos voltar a questão da linha 73. O que esta linha faz é algo bastante engraçado. E o motivo é justamente por que não existe uma necessidade de que o tempo, seja totalmente exato. Mas que ele deva ser o mais preciso quanto for possível. Quando um tick é lançado no gráfico, a chamada vem da função CreateBarInReplay. Neste caso o valor de st_Spread, será o tempo que estará informado no tick. Agora quando a chamada vem de LoopEventOnTime, o valor de st_Spread será simplesmente somado em um. Isto seria equivalente a passagem de um segundo no tempo. Agora não importa qual seja o valor de st_Spread, assim que um novo tick venha a acontecer, o valor será reajustado para o valor mais próximo do real. Então, se a liquidez baixa durante um longo período de tempo, por exemplo 50 segundos. O cronometro, pode se adiantar ou atrasar ligeiramente, e você verá o indicador de mouse, mostrar um valor e logo depois mostrar um valor ligeiramente diferente do que a diferença de um segundo. Isto não deve ser considerada uma falha. Mesmo por que existe uma pequena vantagem aqui. Caso a liquidez fique baixa por vários segundos, você pode pausar e imediatamente dar play no serviço. O resultado será que o serviço irá de fato ignorar todo aquele tempo que ele deveria ficar esperando. Legal isto não é mesmo.


Considerações finais

Para você ter uma ideia do que acabei de falar, pode usar os ticks de simulação e replay de um ativo com baixa liquidez. Mas de qualquer forma no vídeo abaixo, você poderá ver o que falei acima sobre dar pause e logo depois dar play, a fim de evitar ficar parado durante vários segundos.

No entanto, existe mais um problema a ser resolvido ainda dentro deste assunto. O problema é fazer com que o indicador de mouse, venha a nos informar que o ativo, pode ter entrado em leilão. Esta é uma questão bastante complicada de ser explicada, merecendo assim um artigo apenas para ela. Já que a solução seria e já estaria de fato implementada no indicador de mouse. Falo isto, pois se você o colocar no gráfico e seguindo um ativo, cujos dados vem do servidor de negociação real. Notará que quando o ativo entra em leilão, o indicador de mouse nos informa isto. Porém aqui, no replay/simulador, onde fazemos uso de um ativo customizado. Existe um problema, por assim dizer, que dificulta as coisas para nós. Mas isto será tema do próximo artigo. Então nos vemos lá.


Vídeo de demonstração

Arquivos anexados |
Anexo.zip (420.65 KB)
Data Science e Machine Learning (Parte 23): Por que o LightGBM e o XGBoost superam muitos modelos de IA? Data Science e Machine Learning (Parte 23): Por que o LightGBM e o XGBoost superam muitos modelos de IA?
Essas técnicas avançadas de árvores de decisão com boosting de gradiente oferecem desempenho superior e flexibilidade, tornando-as ideais para modelagem financeira e trading algorítmico. Aprenda como aproveitar essas ferramentas para otimizar suas estratégias de trading, melhorar a precisão preditiva e ganhar uma vantagem competitiva nos mercados financeiros.
Como criar qualquer tipo de Trailing Stop e anexá-lo ao EA Como criar qualquer tipo de Trailing Stop e anexá-lo ao EA
Neste artigo, vamos analisar classes para a criação conveniente de diferentes tipos de trailing stops. Vamos aprender a anexar o trailing stop a qualquer EA.
Ganhe uma Vantagem sobre Qualquer Mercado (Parte II): Previsão de Indicadores Técnicos Ganhe uma Vantagem sobre Qualquer Mercado (Parte II): Previsão de Indicadores Técnicos
Você sabia que podemos obter mais precisão ao prever certos indicadores técnicos do que ao prever o preço subjacente de um símbolo negociado? Junte-se a nós para explorar como aproveitar essa percepção para melhores estratégias de negociação
Algoritmo da Cauda de Cometa (Comet Tail Algorithm, CTA) Algoritmo da Cauda de Cometa (Comet Tail Algorithm, CTA)
Neste artigo, vamos explorar o novo algoritmo de otimização autoral CTA (Comet Tail Algorithm), que se inspira em objetos cósmicos únicos, nomeadamente em cometas e suas impressionantes caudas, formadas quando se aproximam do Sol. Esse algoritmo é baseado no conceito de movimento dos cometas e suas caudas, e foi projetado para encontrar soluções ótimas em problemas de otimização.