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

Desenvolvendo um sistema de Replay (Parte 66): Dando play no serviço (VII)

MetaTrader 5Exemplos | 18 setembro 2024, 10:14
13 0
Daniel Jose
Daniel Jose

Introdução

Neste artigo começaremos fazendo algo um pouco diferente. Mas antes vamos relembrar o que foi feito no artigo anterior Desenvolvendo um sistema de Replay (Parte 65): Dando play no serviço (VI). Lá resolvemos o problema da indicação no percentual que nos é informado pelo indicador de mouse. Mas que durante o uso deste mesmo indicador, só que no replay ou simulação, tínhamos um valor incorreto nos sendo mostrado. Além disto, implementamos o sistema que nos permite executar um avanço rápido, nos permitindo ir para um determinado ponto sem ter que esperar vários minutos ou até mesmo horas em alguns casos. Porém gostaria que você visse o vídeo abaixo, antes de continuar a leitura deste artigo. Isto para que você entenda o que explicarei aqui no início deste artigo.


Algo a se ver...

Imagino que o vídeo já diz por si só. Mas ainda assim gostaria de explicar um pouco do que está acontecendo. Esta aplicação de replay/simulador é focada em fazer uso massivo de um ativo customizado. Isto é fato, e acredito que todos concordam com isto. Porém existem falhas que não são causadas pela programação, que estamos de fato fazendo. Tais falhas, tem outras origens. Muitas delas estranhas e outras ocorrem de forma quase que aleatória. Se a origem da falha poder ser a analisada, podemos resolver a mesma. No entanto, quando ela é aleatória a coisa muda de figura, e passamos a ter um problema do qual devemos aprender a conviver com ele.

No momento que escrevo este artigo, a versão mais recente da plataforma MetaTrader 5 é justamente a que é mostrada no vídeo. Não sei onde de fato se encontra o problema. Mas ele existe e muito provavelmente você irá, ou já deverá ter tido contato com ele. Então até que esta falha que é mostrada no vídeo seja de fato sanada, aprenda a conviver com ela. Ou seja, antes de carregar a aplicação que fará o replay ou simulação, configure ela da maneira que você irá de fato usá-la durante todo o tempo. Isto para que não seja preciso mudar o tempo gráfico, já que é justamente neste momento, que estranhamente o MetaTrader 5 perde o contato com os ticks ou barras que estão no ativo customizado. Talvez no momento que você esteja lendo este artigo, a falha vista no vídeo já deva ter sido solucionada. Se este for o caso, ótimo. Senão, faça como acabei de falar, para não ter uma experiência desagradável ao usar a aplicação de replay/simulador.

Dita estas palavras, podemos prosseguir para o que faremos neste artigo daqui. Muito bem, no artigo anterior, ficou faltando uma questão pendente. Sendo está mais imediata, vamos começar por ela.


Implementando o tempo restante para a próxima barra

Uma coisa que muitos operadores de mercado financeiro, gostam de ter e costumam olhar é o tempo que resta até a próxima barra começar. Talvez isto pareça bobagem e pura perda de tempo. Mas existem modelos operacionais, onde tal informação é crucial, já que o operador muitas das vezes posiciona a ordem alguns segundos antes da próxima barra se iniciar.

Muito bem, em determinados tempos gráficos ou mesmo por conta da experiência do operador, ele simplesmente tem uma certa noção do tempo que ainda falta. Mas operadores que estão começando, não tem esta habilidade ainda. Então se faz necessário que elas precisem de alguma maneira de serem informadas de tal coisa. No caso do mercado físico, fazer tal coisa é relativamente simples, isto por conta que estaremos sempre conectados ao servidor de negociação, seja em conta demo, seja em conta real. Mas além deste fato, o que mais facilita a nossa vida, para implementar tal indicação, é o fato de que as negociações estão sempre ocorrendo, ou melhor dizendo, o tempo não para. Mas é justamente este último fato é que torna as coisas um pouco mais complicadas aqui, na aplicação de replay/simulação. O fato de que você ou o usuário, poder pausar por um tempo indeterminado a aplicação, ou mesmo avançar ela para uma posição onde a barra, pode estar no seu início ou próximo ao seu final. Complica muito as coisas. Isto a tão ponto que precisaremos de um verdadeiro malabarismo para conseguir não criar um Frankenstein, apenas para informar o tempo restante da barra.

Antes de fazer qualquer coisa, vamos entender o que já temos em mãos. No artigo anterior, modificamos o código do indicador de mouse, de maneira que passamos a usar uma outra versão da função de tratamento do evento OnCalculate. Esta mudança de certa forma vem a nos ser bastante útil neste caso. O motivo é que nela temos um array que receberá o valor de tempo. Então fazendo uma pequena mudança, teremos o fragmento mostrado abaixo:
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. {
49.    Print(TimeToString(time[rates_total - 1], TIME_DATE | TIME_SECONDS)); // To Testing ...
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 do código fonte: Mouse Study.mq5

Observe que foi adicionada uma nova linha. A linha 49 irá nos permitir visualizar o último valor, que se encontra no array de tempo. Agora preste bastante atenção a uma coisa aqui. Este valor que se encontra neste array, é o valor que foi ajustado de maneira que a barra de um minuto seja construída de forma adequada. Ou seja, dentro da janela de um minuto.

Se você rodar a aplicação de replay/simulador, com este novo código presente no indicador de mouse. Verá na caixa de ferramentas a informação, muito perecida com a vista na animação abaixo.

Anime 01

Muito bem, você pode notar que o indicador de mouse está imprimindo uma informação, cada vez que a função OnCalculate está sendo chamada. Mas o valor em termos de segundos não está sendo modificado. Então você de forma sagas, pode pensar: Bem e se fosse colocado um valor em termos de segundos nesta informação. Será que poderíamos assim fazer um cálculo a fim de saber quanto tempo resta para a próxima barra surgir? Se você pensou assim, isto significa que você compreendeu o que precisaremos de fato fazer para que a indicação seja criada. Então vamos testar esta ideia. Para isto vamos ter que fazer uma pequena mudança. Porém esta mudança será no código do arquivo de cabeçalho C_Replay.mqh. Tal mudança pode ser vista no fragmento logo abaixo.

69. //+------------------------------------------------------------------+
70. inline void CreateBarInReplay(bool bViewTick)
71.          {
72.             bool    bNew;
73.             double dSpread;
74.             int    iRand = rand();
75. 
76.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
77.             {
78.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
79.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
80.                {                  
81.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
82.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
83.                   {
84.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
85.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
86.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
87.                   {
88.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
89.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
90.                   }
91.                }
92.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
93.                m_Infos.Rate[0].time = m_MemoryData.Info[m_Infos.CountReplay].time; //< To Testing...
94.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
95.             }
96.             m_Infos.CountReplay++;
97.          }
98. //+------------------------------------------------------------------+

Fragmento do código fonte: C_Replay.mqh

Olha só que interessante e simples de ser feito. Bastou adicionar a linha 93 ao código do fragmento para que fosse possível adicionar o valor dos segundos. Mais simples que isto, impossível. Porém será que isto realmente dará certo? Lembre-se do seguinte fato: A função CustomRatesUpdate, informa na documentação da mesma, que o valor de tempo, no qual deverá ser criado os Rates deverá estar dentro da janela de um minuto. Você pode então pensar, que isto não é problema, já que não estamos mudando a janela, estamos apenas adicionando o valor de segundos e muito provavelmente este será ignorado pela função, mas repassado para o indicador de mouse. Isto para que seja notado na função OnCalcule. De certa forma devo concordar com a sua teoria, mas até que de fato venhamos a testar a hipótese levantada, ela não passa disto. Uma hipótese teórica. Então, compilamos o código e o resultado é o que pode ser visto no vídeo logo a seguir.


Uma rápida demonstração

Mas, o que foi isto? Que coisa mais maluca, foi está, que aconteceu? De fato, não esperava que isto viesse de fato a ocorrer. Esperava que funciona-se. De fato, a ideia não está de toda errada, você deve ter notado que o indicador de mouse nos informou o que desejávamos. Porém o conteúdo do gráfico. Bem. É melhor procurar uma outra forma de fazer isto. Mas você deve ter notado, que sim podemos fazer algumas coisas, bem interessantes. Tanto que esta mesma ideia, usei para criar um indicador no passado. Você pode ver este indicador, pelo menos a versão aberta do mesmo, no artigo Desenvolvendo um EA de negociação do zero (Parte 13): Times And Trade (II). Aquela versão já está obsoleta, mas serve para que você entenda justamente o que aconteceu aqui. E antes que alguém pergunte: NÃO. Eu não irei vender, dar, emprestar ou mostrar a versão mais atual daquele indicador. O mesmo é de uso pessoal.

Muito bem. Acredito que você deva ter entendido como de fato a coisa funciona. E por que algo aparentemente simples, na verdade precisa que façamos algum tipo de manipulação mais elaborada a fim de conseguir os resultados que desejamos obter.

Você pode estar pensando que teremos que bolar uma forma nova de fazer as coisas. Usando algum tipo de manipulação de dados. Mas a solução já existia na versão que usava variáveis globais de terminal. Porém, agora não mais usaremos aquela mesma solução, precisamos então ser criativos e entender de fato como os programas repassam informações entre si. Nos de certa forma já estamos fazendo isto, usando o indicador de controle, a fim de controlar o serviço e o serviço reportar ao indicador qual a posição em que estamos. A mesma coisa deverá ser feita aqui. Porém com um detalhe: Não podemos ficar lançando eventos customizados a cada segundo, ou pior, a cada tick, a fim de informar ao indicador de mouse, o tempo onde estamos. Se isto for feito, acabaremos eventualmente, degradando muito a performance da aplicação, nos forçando assim a reduzir ainda mais o número máximo de ticks por minuto. E este tipo de coisa eu quero evitar a todo custo. Porém precisamos ter algum tipo de sincronismo, entre a informação presente nos ticks e a informação que nos será dada referente ao tempo da barra atual. Está sim é a principal preocupação. Então vamos desfazer as alterações que foram vistas nos fragmentos e nos focar em tentar criar um outro tipo de solução a fim de realmente resolver o problema. Mas isto já será tema para um próximo tópico.


Pensando em outra forma de informar o tempo restante

Diferente do que acontece quando estamos conectados a um servidor de negociação real. Aqui no replay/simulador a coisa é bem mais complicada de ser resolvida. Isto da forma como quero resolver, pois não desejo fazer uso de uma variável global de terminal para transferir as informações. O fato é que quando estamos ligados ao servidor, todos os relógios tendem e precisam ficar sincronizados, no mínimo em termos de segundos. Talvez você não entenda como isto afeta imensamente as coisas. Mas para fazer qualquer verificação em termos de segundos restantes, tudo que você precisa verificar é quanto tempo, em termos de segundos, falta para um determinado horário. No caso de estarmos conectados a um servidor real, isto é muito simples, pois podemos usar o relógio interno do próprio sistema para fazer a tal fatoração e obter o tempo faltante.

Porém quando fazemos uso do replay/simulação, a coisa toda se torna muito mais complicada. O motivo de que venhamos a ter um aumento na complicação, se deve justamente pelo fato de estarmos fazendo um replay ou simulação. Você, caro leitor, muito provavelmente deve estar pensando e se indagando: Mas como o simples fato de estarmos fazendo um replay/simulação, torna a questão de saber o tempo restante tão mais complicado? Isto não faz sentido. De fato, se você pensar nas coisas de maneira superficial, realmente não faz sentido que o fato de estarmos executando um replay ou simulação torne tão complicado saber quanto tempo falta para a abertura de uma nova barra. Mas o problema todo de fato se deve justamente, a questão de saber olhar as horas no relógio.

Vamos pensar o seguinte: Suponhamos que a primeira barra de um minuto, a ser plotada no gráfico, de fato venha a utilizar este um minuto para ser totalmente plotada. Muito bem. Esta é a primeira condição para entender o problema. A barra será plotada em um minuto. Agora se você vier a dar play na aplicação a fim de que a plotagem se inicie exatamente no segundo zero. A cada minuto do relógio do sistema, teremos uma nova barra. Perfeito, problema solucionado. Isto por que a sincronização estará perfeita. Assim tudo que precisaremos fazer será olhar o relógio do sistema e saberemos quanto tempo resta até aproxima barra iniciar.

Porém, raramente, isto para não dizer outra coisa, você terá a primeira barra sendo de exatamente um minuto. Outro problema, você dificilmente conseguirá fazer com que a aplicação comece a plotar a barra no instante zero. Isto olhando o relógio do sistema. Mas neste caso poderíamos fazer um pequeno ajuste, ou melhor dizendo: Implementar algum tipo de testagem, que fizesse o replay/simulador, iniciar a fim de conseguir gerar a sincronização com o relógio do sistema. Este tipo de solução é viável e plausível. No entanto, tendo em vista que se você viesse a sinalizar que a aplicação está liberada, de forma que a barra pudesse ser plotada. Você deveria ter que esperar um certo tempo até que a sincronização viesse de fato a ocorrer. Este tempo de fato não é muito longo. Ele seria de no máximo 59 segundos. Isto na pior das hipóteses. Este tipo de coisa é viável, quando a aplicação será desenvolvida para uso pessoal. Mas mesmo para uso pessoal, mais hora ou menos hora, você acabaria por se cansar de ter que esperar no máximo 59 segundos toda a vez que viesse a dar play na aplicação.

De fato, devo concordar e você acabaria por concordar também. Que apesar de forçar a aplicação de replay/simulador, a ficar sincronizada com o relógio do sistema, simplificar a nossa vida no que rege saber o tempo restante da barra, isto torna o uso um pouco irritante. E o pior dos casos realmente se dá quando fazemos uma pausa no meio da simulação ou do replay. Neste caso realmente teríamos que esperar um tempo máximo de 59 segundos para que a aplicação pudesse voltar a plotar o gráfico. Isto por que ela teria que esperar o relógio do sistema voltar no ponto exato. Então a solução não parece muito adequada, isto pelo ponto de vista prático.

Porém, podemos encontrar um meio termo. Algo que nos permita fazer algum tipo de ajuste, a fim de nos manter sincronizados com o relógio do sistema. Ao mesmo tempo, não venhamos a precisar de ter que aguardar os 59 segundos, a fim que o relógio do sistema, habilite a aplicação executar a plotagem das barras. Isto para que saibamos o tempo restante da barra. Ok, agora a coisa parece ter ficado um pouco mais interessante.

Estou fazendo esta descrição, não para complicar as coisas ou lhe deixar confuso, caro leitor. Mas sim para lhe mostrar, que antes de realmente nos darmos ao trabalho de codificar algo, precisamos antes pensar. Analisar as possibilidades e os custos em termos de dificuldades na implementação. Muita gente acha, que programar é jogar um monte de código em um arquivo e que tudo estará resolvido. Na verdade, a parte da codificação em sim, é apenas uma pequena parte de todo o trabalho. A maior parte do trabalho, realmente se dá em planejar e analisar a solução que deveremos implementar. E é isto que fizemos até este momento. Mas com este pensamento, conseguimos de fato construir uma boa ideia do que precisaremos fazer. Porém será preciso mudar algumas coisas. Ou melhor dizendo: Precisaremos melhorar um pouco a nossa organização. Mas para entender realmente o que faremos, vamos para um novo tópico.


Implementando a versão base para informar o tempo restante

O que iremos de fato fazer neste tópico, será algo que ao meu ver é relativamente bem simples, porém será algo realmente bastante audacioso. Vamos implementar um meio de repassar rapidamente a informação do tempo atual da barra, para o indicador de mouse. Vamos começar fazendo algumas mudanças no código do indicador de mouse. Estas mudanças serão, na verdade a adição de um pouco mais de código, conforme pode ser visto no fragmento abaixo.

12. //+------------------------------------------------------------------+
13. double GL_PriceClose;
14. datetime GL_TimeAdjust;
15. //+------------------------------------------------------------------+
16. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
17. //+------------------------------------------------------------------+
18. C_Study *Study       = NULL;
19. //+------------------------------------------------------------------+
20. input color user02   = clrBlack;                         //Price Line
21. input color user03   = clrPaleGreen;                     //Positive Study
22. input color user04   = clrLightCoral;                    //Negative Study
23. //+------------------------------------------------------------------+
24. C_Study::eStatusMarket m_Status;
25. int m_posBuff = 0;
26. double m_Buff[];
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {
30.    ResetLastError();
31.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
32.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
33.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
34.    {
35.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
36.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
37.       m_Status = C_Study::eCloseMarket;
38.    }else
39.       m_Status = C_Study::eInReplay;
40.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
41.    ArrayInitialize(m_Buff, EMPTY_VALUE);
42.    
43.    return INIT_SUCCEEDED;
44. }
45. //+------------------------------------------------------------------+
46. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[],
47.                 const double& high[], const double& low[], const double& close[], const long& tick_volume[], 
48.                 const long& volume[], const int& spread[]) 
49. {
50.    GL_PriceClose = close[rates_total - 1];
51.    GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0);
52.    m_posBuff = rates_total;
53.    (*Study).Update(m_Status);   
54.    
55.    return rates_total;
56. }
57. //+------------------------------------------------------------------+

Fragmento do código fonte: Mouse Study.mq5

Se você comparar este fragmento mostrado acima, com o último código fonte, do indicador de mouse, perceberá que adicionei a linha 14 ao código. Ou seja, agora temos uma nova variável global dentro do indicador. Não pretendo de maneira alguma adicionar novas variáveis globais aqui neste modulo do indicador de mouse. Mas esta daqui é especial, por um motivo que logo você poderá ver. Mas de qualquer forma está variável receberá na linha 51 um valor. Este valor será informado no spread. Porém aqui mora um perigo que ao mesmo tempo flerta com algo que pode nós auxiliar. De qualquer forma, esta variável global somente é usada de uma forma bem específica. Então para entender veja o novo código do arquivo C_Study.mqh, presente logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          datetime       TimeDevice;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       void Draw(void)
028.          {
029.             double v1;
030.             
031.             if (m_Info.bvT)
032.             {
033.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
034.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
035.             }
036.             if (m_Info.bvD)
037.             {
038.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
039.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
040.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
041.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
042.             }
043.             if (m_Info.bvP)
044.             {
045.                v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
046.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
047.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
048.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
049.             }
050.          }
051. //+------------------------------------------------------------------+
052. inline void CreateObjInfo(EnumEvents arg)
053.          {
054.             switch (arg)
055.             {
056.                case evShowBarTime:
057.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
058.                   m_Info.bvT = true;
059.                   break;
060.                case evShowDailyVar:
061.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
062.                   m_Info.bvD = true;
063.                   break;
064.                case evShowPriceVar:
065.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
066.                   m_Info.bvP = true;
067.                   break;
068.             }
069.          }
070. //+------------------------------------------------------------------+
071. inline void RemoveObjInfo(EnumEvents arg)
072.          {
073.             string sz;
074.             
075.             switch (arg)
076.             {
077.                case evHideBarTime:
078.                   sz = m_Info.szBtn1;
079.                   m_Info.bvT = false;
080.                   break;
081.                case evHideDailyVar:
082.                   sz = m_Info.szBtn2;
083.                   m_Info.bvD   = false;
084.                   break;
085.                case evHidePriceVar:
086.                   sz = m_Info.szBtn3;
087.                   m_Info.bvP = false;
088.                   break;
089.             }
090.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
091.             ObjectDelete(GetInfoTerminal().ID, sz);
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
093.          }
094. //+------------------------------------------------------------------+
095.    public   :
096. //+------------------------------------------------------------------+
097.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
098.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
099.          {
100.             if (_LastError != ERR_SUCCESS) return;
101.             ZeroMemory(m_Info);
102.             m_Info.Status = eCloseMarket;
103.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
104.             m_Info.corP = corP;
105.             m_Info.corN = corN;
106.             CreateObjInfo(evShowBarTime);
107.             CreateObjInfo(evShowDailyVar);
108.             CreateObjInfo(evShowPriceVar);
109.          }
110. //+------------------------------------------------------------------+
111.       void Update(const eStatusMarket arg)
112.          {
113.             int i0;
114.             datetime dt;
115.                      
116.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
117.             {
118.                case eCloseMarket   :
119.                   m_Info.szInfo = "Closed Market";
120.                   break;
121.                case eInReplay      :
122.                case eInTrading     :
123.                   i0 = PeriodSeconds();
124.                   dt = (m_Info.Status == eInReplay ? m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent());
125.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
126.                   m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
127.                   break;
128.                case eAuction      :
129.                   m_Info.szInfo = "Auction";
130.                   break;
131.                default            :
132.                   m_Info.szInfo = "ERROR";
133.             }
134.             Draw();
135.          }
136. //+------------------------------------------------------------------+
137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
138.          {
139.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
140.             switch (id)
141.             {
142.                case CHARTEVENT_CUSTOM + evHideBarTime:
143.                   RemoveObjInfo(evHideBarTime);
144.                   break;
145.                case CHARTEVENT_CUSTOM + evShowBarTime:
146.                   CreateObjInfo(evShowBarTime);
147.                   break;
148.                case CHARTEVENT_CUSTOM + evHideDailyVar:
149.                   RemoveObjInfo(evHideDailyVar);
150.                   break;
151.                case CHARTEVENT_CUSTOM + evShowDailyVar:
152.                   CreateObjInfo(evShowDailyVar);
153.                   break;
154.                case CHARTEVENT_CUSTOM + evHidePriceVar:
155.                   RemoveObjInfo(evHidePriceVar);
156.                   break;
157.                case CHARTEVENT_CUSTOM + evShowPriceVar:
158.                   CreateObjInfo(evShowPriceVar);
159.                   break;
160.                case (CHARTEVENT_CUSTOM + evSetServerTime):
161.                   m_Info.TimeDevice = (datetime)lparam;
162.                   break;
163.                case CHARTEVENT_MOUSE_MOVE:
164.                   Draw();
165.                   break;
166.             }
167.             ChartRedraw(GetInfoTerminal().ID);
168.          }
169. //+------------------------------------------------------------------+
170. };
171. //+------------------------------------------------------------------+
172. #undef def_ExpansionPrefix
173. #undef def_MousePrefixName
174. //+------------------------------------------------------------------+

Código fonte do arquivo: C_Study.mqh

Muito bem. Preste atenção pois o que irei a partir deste momento explicar é importante para entender como a coisa de fato estará funcionando. Você pode observar que o código do arquivo de cabeçalho está diferente. Porém existem apenas dois pontos realmente importantes aqui. O primeiro é na linha 160, onde estamos tratando um evento customizado. Note que na linha 161, armazenamos um valor que o evento nos passa em uma variável privativa da classe. Este é o primeiro ponto. Agora vamos a parte onde realmente a mágica acontece. Para isto iremos subir algumas linhas e nos dirigir para a linha 111, onde temos o procedimento de Update. Este criará a informação da qual iremos visualizar depois. Então atenção. Na linha 123, capturamos a quantidade de segundos existente dentro do período de tempo do gráfico do ativo customizado. Na linha 124, efetuamos um pequeno cálculo, ou usamos o valor que o MetaTrader 5 irá nos informar. Quem decide se faremos o cálculo ou usaremos o valor é o status do indicador. Quando estamos usando o indicador de mouse em um ativo de replay, faremos o cálculo. Caso contrário usaremos o valor que o MetaTrader 5 nos informar.

Observe que o cálculo leva em consideração o valor que capturamos no fragmento do indicador de mouse e mais um outro valor, que nos será informado pela aplicação de replay/simulador. Depois mostrarei como este segundo valor é repassado para o indicador de mouse.

De qualquer forma, o que temos é um dado que mudará de tempos em tempo. Porém precisamos de um outro valor e este é calculado logo abaixo, na linha 125. Nesta linha estamos calculando em que momento uma nova barra será lançada no gráfico. Agora na linha 126 fazemos um último calculo a fim de apresentar ao usuário o valor de tempo restante para o fechamento da barra.

Todo este sistema, irá de fato funcionar, por conta que o responsável por nos dizer quanto tempo resta até que uma nova barra seja criada é o servidor de negociação. Ou no caso de estamos usando o replay/simulação, o responsável será o serviço que promoverá a atualização das barras, para que assim o MetaTrader 5 possa fazer a plotagem.

Muito bem. Você deve ter notado que precisamos modificar o código do serviço, devido as explicações acima. Mas, não é exatamente o código do serviço que precisaremos mexer. Devemos mexer, na verdade é no código presente no arquivo C_Replay.mqh. Mas antes de fazer isto precisamos fazer uma pequena modificação em dois pontos no código do serviço. O primeiro ponto é adicionar algumas coisas no arquivo de cabeçalho Macros.mqh. Este pode ser visto logo abaixo:

1. //+------------------------------------------------------------------+
2. #property copyright "Daniel Jose"
3. //+------------------------------------------------------------------+
4. #define macroRemoveSec(A) (A - (A % 60))
5. #define macroGetDate(A)   (A - (A % 86400))
6. #define macroGetSec(A)    (A - (A - (A % 60)))
7. //+------------------------------------------------------------------+

Código fonte do arquivo: Macros.mqh

Por ser algo muito simples de entender, não vou entrar em detalhes. Uma vez feito isto, modificaremos o arquivo de cabeçalho C_FilesTick.mqh. Mas a mudança é bem localizada e é vista no fragmento logo abaixo:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_FileBars.mqh"
05. #include "C_Simulation.mqh"
06. #include "..\..\Auxiliar\Macros.mqh"
07. //+------------------------------------------------------------------+
08. //#define macroRemoveSec(A)   (A - (A % 60))
09. #define def_MaxSizeArray    16777216 // 16 Mbytes
10. //+------------------------------------------------------------------+
11. class C_FileTicks
12. {
13.    protected:
14.       enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};

Fragmento do código fonte: C_FilesTick.mqh

Observe que neste caso, foi adicionado a linha 06 e a linha 08 que se encontra riscada deverá ser removida do código. O motivo é justamente o fato de que o arquivo de cabeçalho Macros.mqh conterá o código riscado. Muito bem. Uma vez que estas modificações foram feitas, podemos realmente partir para o arquivo C_Replay.mqh. O novo código deste arquivo, 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. 
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)
092.                {
093.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
094.                   if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, "");
095.                }
096.                m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time);
097.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
098.             }
099.             m_Infos.CountReplay++;
100.          }
101. //+------------------------------------------------------------------+
102.       void AdjustViewDetails(void)
103.          {
104.             MqlRates rate[1];
105. 
106.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
107.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
108.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
109.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
110.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
111.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
112.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
113.             if (rate[0].close > 0)
114.             {
115.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
116.                   m_Infos.tick[0].last = rate[0].close;
117.                else
118.                {
119.                   m_Infos.tick[0].bid = rate[0].close;
120.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
121.                }               
122.                m_Infos.tick[0].time = rate[0].time;
123.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
124.             }else
125.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
126.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
127.          }
128. //+------------------------------------------------------------------+
129.       void AdjustPositionToReplay(void)
130.          {
131.             int nPos, nCount;
132.             
133.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
134.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
135.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
136.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
137.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
138.                CreateBarInReplay(false);
139.          }
140. //+------------------------------------------------------------------+
141.    public   :
142. //+------------------------------------------------------------------+
143.       C_Replay()
144.          :C_ConfigService()
145.          {
146.             Print("************** Market Replay Service **************");
147.             srand(GetTickCount());
148.             SymbolSelect(def_SymbolReplay, false);
149.             CustomSymbolDelete(def_SymbolReplay);
150.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
151.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
152.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
153.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
154.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
155.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
156.             SymbolSelect(def_SymbolReplay, true);
157.             m_Infos.CountReplay = 0;
158.             m_IndControl.Handle = INVALID_HANDLE;
159.             m_IndControl.Mode = C_Controls::ePause;
160.             m_IndControl.Position = 0;
161.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
162.          }
163. //+------------------------------------------------------------------+
164.       ~C_Replay()
165.          {
166.             SweepAndCloseChart();
167.             IndicatorRelease(m_IndControl.Handle);
168.             SymbolSelect(def_SymbolReplay, false);
169.             CustomSymbolDelete(def_SymbolReplay);
170.             Print("Finished replay service...");
171.          }
172. //+------------------------------------------------------------------+
173.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
174.          {
175.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
176.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
177.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
178.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
179.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
180.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
181.             SweepAndCloseChart();
182.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
183.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
184.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
185.             else
186.                Print("Apply template: ", szNameTemplate, ".tpl");
187. 
188.             return true;
189.          }
190. //+------------------------------------------------------------------+
191.       bool InitBaseControl(const ushort wait = 1000)
192.          {
193.             Print("Waiting for Mouse Indicator...");
194.             Sleep(wait);
195.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
196.             if (def_CheckLoopService)
197.             {
198.                AdjustViewDetails();
199.                Print("Waiting for Control Indicator...");
200.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
201.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
202.                UpdateIndicatorControl();
203.             }
204.             
205.             return def_CheckLoopService;
206.          }
207. //+------------------------------------------------------------------+
208.       bool LoopEventOnTime(void)
209.          {         
210.             int iPos;
211. 
212.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
213.             {
214.                UpdateIndicatorControl();
215.                Sleep(200);
216.             }
217.             m_MemoryData = GetInfoTicks();
218.             AdjustPositionToReplay();
219.             EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, "");
220.             iPos = 0;
221.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
222.             {
223.                if (m_IndControl.Mode == C_Controls::ePause) return true;
224.                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);
225.                CreateBarInReplay(true);
226.                while ((iPos > 200) && (def_CheckLoopService))
227.                {
228.                   Sleep(195);
229.                   iPos -= 200;
230.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
231.                   UpdateIndicatorControl();
232.                }
233.             }
234. 
235.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
236.          }
237. };
238. //+------------------------------------------------------------------+
239. #undef def_SymbolReplay
240. #undef def_CheckLoopService
241. #undef def_MaxSlider
242. //+------------------------------------------------------------------+

Código fonte do arquivo: C_Replay.mqh

As questões aqui, são um pouco mais complicadas, do que você muito provavelmente possa de fato, estar pensando neste momento. O motivo, é que temos que informar diretamente ao indicador de mouse, em que ponto da simulação ou replay estamos executando. De certa forma, isto não seria de fato um problema, como você deve se lembrar. Já que no início deste artigo, mostrei como poderíamos fazer isto. Mas você percebeu que algo estranho acontecia e que seria preciso usarmos um outro meio para isto.

Este meio foi conseguido, fazendo uso de três novas linhas. Porém não se iluda achando que esta solução é perfeita, pois ela não é. Ela apenas resolve o nosso problema em um aspecto. No entanto, temos um outro aspecto que esta solução não é capaz de resolver. Pelo menos não por enquanto. Mas antes de entrar em detalhes, sobre este aspecto, que não será coberto, vamos ver que linhas são estas e o que elas estão fazendo.

Vamos começar pela linha mais simples e que será executada de forma quase que direta. A linha 96. Se você prestou atenção ao fragmento do código fonte do indicador de mouse, notou que estamos pegando o valor do spread para conseguir, ter aquele ajuste fino em termos de segundos. Pois bem, se não podemos passar o valor diretamente pelos Rates, vamos passá-lo de uma outra forma, que seja o mais rápido possível. Esta é a forma que encontrei, já que durante tudo o período de simulação ou replay, não estamos pelo menos até o momento fazendo uso do spread. Então vamos usá-lo de uma forma mais interessante. Neste ponto temos um pequeno problema, mas este não nos atrapalha ainda. Então podemos conviver com ele.

Continuando na explicação sobre as novas linhas. Se você olhar um pouco acima, na linha 94, notará que a cada nova barra que o serviço de replay/simulação identificar, lançaremos um evento customizado a fim de informar ao indicador de mouse qual é o novo valor a ser utilizado. Da mesma forma, na linha 219, dizemos ao indicador qual o valor a ser usado. Em ambos os casos isto é feito via evento customizado.

Mas por que fazer assim? Não teria uma outra forma de fazer este tipo de coisa? Sim. Existe uma outra forma, mas ela não se mostrou suficientemente adequada. Não neste momento, mas sim para algo que ainda teremos que resolver. O fato é que estes eventos customizados somente serão lançados de maneira a sabermos que a barra de um minuto foi fechada. Poderíamos fazer isto de uma outra maneira, sem ter que lançar um evento customizado no gráfico. Para isto teríamos que usar a função de biblioteca iTime a fim de saber quando a barra de um minuto foi criada. Atenção a este detalhe: NÃO NOS IMPORTA O TEMPO DA BARRA NO TIMEFRAME USADO, O QUE NOS IMPORTA É O TEMPO DA BARRA DE UM MINUTO. Agora você pode estar confuso, mas observe o fragmento abaixo.

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

Fragmento mod do arquivo: C_Study.mqh

A mudança se encontra na linha 124. Usar esta função a fim de capturar o tempo da barra gera uma perda de tempo, totalmente desnecessária, já que o valor obtido aqui seria o mesmo que estaria na função OnCalculate. Porém se este mesmo fragmento visto acima, fosse modificado para o visto abaixo as coisas seriam bem diferentes.

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

Fragmento mod do arquivo: C_Study.mqh

Note que a mudança, está apenas no fato, de que agora estamos pedindo para o MetaTrader 5 nos dizer quando a barra de um minuto se iniciou. E o simples fato de fazer isto, nos libera de ter que fazer com que o serviço, lance o tal evento customizado para nos dar esta informação. Mas espere um pouco. Não entendi! O fato, meu caro leitor, é que não importa o tempo gráfico, a informação que o MetaTrader 5 irá nos passar é justamente quando a barra de um minuto começou. E é justamente isto que o evento customizado estará de fato informando. Porém ao fazer as coisas no serviço, eu garanto que a informação somente será passada de tempos em tempos. Diferente de colocar uma chamada no indicador, a fim de se obter a mesma informação.


Considerações finais

Apesar do que foi visto aqui não ser de fato uma solução final para o problema. A mesma se mostrou bastante adequada para este momento. Sendo assim não vejo motivos em não fazer uso da mesma. Porém será necessário que em breve, tenhamos uma solução mais adequada. Mas até lá já podemos saber quando uma nova barra será gerada.

No vídeo abaixo você pode ver o sistema atual em funcionamento.


Executando a versão DEMO

Arquivos anexados |
Anexo.zip (420.65 KB)
Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Critério de homogeneidade de Smirnov como indicador de não-estacionaridade de séries temporais Critério de homogeneidade de Smirnov como indicador de não-estacionaridade de séries temporais
Este artigo analisa um dos mais conhecidos critérios de homogeneidade não-paramétricos, o critério de Smirnov. São analisados tanto dados modelados quanto cotações reais. É apresentado um exemplo de construção do indicador de não-estacionaridade (iSmirnovDistance).
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 17): Negociação Multimoedas Técnicas do MQL5 Wizard que você deve conhecer (Parte 17): Negociação Multimoedas
Negociar com múltiplas moedas não está disponível por padrão quando um expert advisor é montado através do assistente. Examinamos dois hacks possíveis que os traders podem fazer ao tentar testar suas ideias com mais de um símbolo ao mesmo tempo.