preview
Desenvolvendo um sistema de Replay (Parte 68): Acertando o tempo (I)

Desenvolvendo um sistema de Replay (Parte 68): Acertando o tempo (I)

MetaTrader 5Exemplos | 7 outubro 2024, 14:49
48 0
Daniel Jose
Daniel Jose

Introdução

No artigo Desenvolvendo um sistema de Replay (Parte 66): Dando play no serviço (VII) mostrei como fazer para o indicador de mouse, nos informar quanto tempo resta para que a barra atual finalize e uma nova barra comece a ser plotada no gráfico. Porém apesar de aquele método funcionar muito bem, e de forma bastante eficiente. Temos um problema no mesmo. Apesar de muitos acharem que o problema é no método, ele na verdade está na liquidez do ativo. Se você prestou atenção a explicação presente no artigo, deve ter notado que o indicador de mouse somente irá, de fato atualizar a informação, quando uma nova cotação ocorrer.

O motivo disto é justamente pelo fato, de que precisamos que o MetaTrader 5 gere uma chamada de evento OnCalculate, a fim de que o indicador receba um novo valor em termos de segundos, a fim de saber como calcular o tempo restante da barra. Este valor é colocado no spread, como você deve ter visto no artigo. No entanto, justamente por conta que precisamos, do valor colocado pelo serviço de c, no spread. Não temos como em um ativo, ou mesmo em um momento de baixa liquidez, informar corretamente o tempo restante da barra. E em caso do ativo entrar em leilão, teremos sim um grande problema, já que durante o leilão podemos ficar diversos minutos em estado de suspensão. Com relação a questão do leilão, não temos como contornar adequadamente a situação. Mesmo em ativos com uma boa liquidez, ainda assim por conta do leilão, não teremos nenhum evento OnCalculate sendo gerado. Assim o serviço de replay/simulação, precisa ser atualizado de forma a permitir que o usuário seja informado corretamente nestes dois casos. O primeiro é quando a liquidez é baixa, tendo poucos negócios sendo gerados por segundo. E o outro problema é quando o ativo entra em leilão. Precisamos resolver estes dois casos, antes de progredimos para uma próxima etapa de desenvolvimento.


Implementando a solução quando a liquidez é baixa

O primeiro caso, onde temos uma baixa liquidez é relativamente simples e fácil de resolvermos, já que precisaremos atuar apenas no que rege o código do serviço. Bem, pelo menos em princípio. Talvez seja necessário mudar alguma outra coisa. Mas vamos tentar resolver isto mexendo apenas no código fonte do serviço de replay/simulação.

Para verificar, de forma eficiente, se isto realmente será possível ser feito. Vamos abandonar, de forma temporária, o nosso código fonte original. E vamos criar um outro código apenas para testar se é realmente possível fazer com que o serviço, quando parado consiga atualizar o indicador de mouse sobre o tempo restante para que a barra atual seja fechada.

O código que precisaremos de fato criar, será o mais simples, quanto for possível ser feito. Nada de muito complicado ou elaborado. A ideia aqui é testar e verificar se podemos enviar dados do servidor para o indicador, sem que isto modifique algo a respeito das informações presentes no gráfico. Assim sendo, o código fonte a ser usado é apresentado logo abaixo.

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. #include <Market Replay\Defines.mqh>
07. //+------------------------------------------------------------------+
08. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
09. //+------------------------------------------------------------------+
10. void OnStart()
11. {
12.    long id;
13.    int handle;
14.    MqlRates Rate[1];
15.    
16.    Print("Starting Test Service...");
17.    SymbolSelect(def_SymbolReplay, false);
18.    CustomSymbolDelete(def_SymbolReplay);
19.    CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
20.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0.5);
21.    CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 5);
22.    Rate[0].close = 105;
23.    Rate[0].open = 100;
24.    Rate[0].high = 110;
25.    Rate[0].low = 95;
26.    Rate[0].tick_volume = 5;
27.    Rate[0].spread = 1;
28.    Rate[0].real_volume = 10;
29.    Rate[0].time = D'10.03.2023 09:00';
30.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
31.    Rate[0].time = D'10.03.2023 09:30';
32.    CustomRatesUpdate(def_SymbolReplay, Rate, 1);
33.    SymbolSelect(def_SymbolReplay, true);
34.    id = ChartOpen(def_SymbolReplay, PERIOD_M30);
35.    Sleep(1000);
36.    if ((handle = iCustom(NULL, 0, "\\Indicators\\Mouse Study.ex5")) != INVALID_HANDLE)
37.       ChartIndicatorAdd(id, 0, handle);
38.    IndicatorRelease(handle);
39.    Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
40.    while (def_Loop)
41.    {
42.       CustomRatesUpdate(def_SymbolReplay, Rate, 1);
43.       Sleep(250);
44.       Rate[0].spread++;
45.       if (Rate[0].spread == 60)
46.       {
47.          Rate[0].time += 60;
48.          Rate[0].spread = 0;
49.          Print(TimeToString(Rate[0].time, TIME_DATE | TIME_SECONDS));
50.          EventChartCustom(id, evSetServerTime, (long)(Rate[0].time), 0, "");
51.       }
52.    }
53.    ChartClose(id);
54.    SymbolSelect(def_SymbolReplay, false);
55.    CustomSymbolDelete(def_SymbolReplay);
56.    Print("Finished Test Service...");   
57. }
58. //+------------------------------------------------------------------+

Código fonte do serviço de teste

Lembrando que para a correta execução do que será explicando, dependeremos do fato, de que o indicador de mouse esteja compilado e na localização que mostrarei durante a explicação. Caso contrário o teste falhará. Então vamos entender, o que deverá ser executado e como poderemos usar isto depois a nosso favor. Isto caso o teste se mostre eficaz naquilo que pretendemos fazer.

As primeiras quatro linhas são declarações e propriedades sobre o tipo de executável que será gerado. Na linha seis, incluímos um arquivo de cabeçalho, apenas para evitar ter que declarar coisas extras aqui. Já na linha oito, definimos uma diretiva de controle para o loop que faremos em breve. De certa forma a única linha realmente importante entre estas oito, é apenas a linha dois. Todas, as demais, poderiam ser suprimidas e os códigos necessários serem feitos no local correto. No entanto, a linha dois é importante para informar ao MetaTrader 5, que o executável gerado deverá ser um serviço. Caso contrário ele seria um script. E não é bem isto que queremos testar.

Mas a partir da linha 10, é que as coisas começam a ficar mais agradáveis. Entre as linhas 12 e 14 temos declaração das variáveis que faremos uso. Já entre as linhas 17 a 21 começamos inicializando a criação do ativo customizado. Estes passos são clássicos então podemos avançar mais um pouco. Neste momento, entre as linhas 22 a 28, criamos um RATE para a barra. O que realmente nos importa é a linha 29, onde definimos o momento que a barra será criada. Na linha 30 dizemos ao MetaTrader 5 quais os dados da barra a ser criada. Agora preste atenção. Na linha 31, criaremos uma segunda barra com os mesmos dados, só que desta vez ela estará deslocada em 30 minutos. O motivo de deslocarmos ela em 30 minutos, se deve ao fato de que o gráfico será aberto com um TimeFrame de 30 minutos. Se você colocar um outro valor de tempo, nesta barra que estamos criando na linha 31, o indicador de mouse irá de fato calcular quanto tempo falta para esta barra ser fechada. Então atenção a este detalhe. Um outro cuidado a ser tomado, é NÃO e novamente NÃO informe nenhum valor de segundos. O valor deverá ser em termos de minutos e nunca em segundos. A próxima coisa que fazemos é criar a barra na linha 32.

Agora na linha 34 abriremos o gráfico. E como foi visto em um artigo passado, nesta mesma sequência. Antes do serviço adicionar qualquer coisa, seja objetos ou mesmo um indicador no gráfico. Precisamos fazer com que o serviço espere um pouco. Isto para que a plataforma MetaTrader 5, de fato construa o gráfico e o coloque na tela. Esta espera é feita pela linha 35. Aqui estamos esperando o tempo de um segundo, antes de continuar a execução do programa.

E novamente precisamos prestar atenção a uma nova informação, que se encontra presente na linha 36. O que estamos fazendo nesta linha 36 é tentar criar um manipulador a fim de poder colocar o indicador de mouse no gráfico. A função iCustom nos retorna o valor que precisamos para adicionar o indicador. Isto se o indicador for encontrado. Caso ele não seja encontrado, teremos um valor invalido, e não faremos uso dele. Assim o indicador de mouse, deverá, obrigatoriamente está na localização indicada. Caso contrário o teste falhará. Se obtivermos êxito em encontrar o indicador, faremos com que ele seja adicionado ao gráfico. Isto é feito na linha 37. E já que o manipulador retornado pela chamada iCustom, não será mais necessário, o eliminamos na linha 38.

Agora vem a parte do teste. A parte que realmente nos interessa, em verificar se de fato irá ou não funcionar. Se funcionar teremos como sanar o problema em caso de ativos com baixa liquidez. Se não funcionar teremos que procurar uma outra solução. Mas então vamos entender como este teste irá está sendo feito. Sendo que ele está dentro de um loop que se inicia na linha 40 e termina na linha 52. O resto do código, entre as linhas 53 e 56 são apenas para encerramento do teste. Não sendo assim de grande interesse para este artigo. Então vamos entender o teste.


Entendendo como o teste funciona

Tudo se inicia na linha 40, onde entramos no loop. Se o gráfico estiver aberto e o usuário não tiver parado o serviço, o loop será executado eternamente. De qualquer forma uma vez dentro do loop, temos a linha 42. Esta linha irá de forma forçada atualizar os dados da barra. Mas você deve ficar atento ao seguinte fato: Nenhuma nova informação, de fato irá está sendo repassada para a barra. Mas apenas o que estamos modificando aqui no teste. Ok. Na linha 43, geramos um pequeno delay, a fim de que a atualização não seja muito rápida. Mas preste atenção ao valor de delay. Não estamos aguardando próximo de um segundo. O valor é de um quarto de segundo e isto é importante depois, quando você for ver o resultado sendo mostrado no MetaTrader 5.

Na linha 44, incrementando o valor do spread. Isto tem como finalidade dar a impressão ao indicador de mouse, que se passou um segundo, quando na verdade o tempo é bem menor. Isto por conta do delay que estamos usando. Até neste ponto, o funcionamento deverá ser o seguinte: Assim que o gráfico for aberto e o indicador de mouse for mostrado. O MetaTrader 5 deverá gerar para nos os eventos OnCalculate, a fim de que o tempo restante para o fechamento da barra seja decrementado. Você pode verificar se isto de fato está acontecendo. Mas como eu já testei e verifique que isto de fato acontece. Tive a calma e curiosidade, de saber o que aconteceria, no momento em que o valor de spread viesse a ultrapassar o valor de 60. E por que este valor de 60 é importante para nos? O motivo é que quando este valor é atingido, uma nova barra de um minuto deverá aparecer, mesmo que você esteja utilizando um tempo gráfico mensal. Isto não importa, para que de fato tenhamos uma correta indicação, precisamos que o MetaTrader 5, seja informado que uma nova barra de um minuto surgiu.

De qualquer maneira, temos que fazer com que nosso código, consiga lidar com isto. Caso contrário, o MetaTrader 5 sozinho não conseguirá resolver este tipo de problema. Assim sendo, temos a linha 45, que fará a verificação deste valor de 60 interações do laço. Quando este valor for atingido, na linha 47, faremos a soma de um minuto, que no caso são 60 segundos ao tempo original da barra. Assim quando a função na linha 42 for executada, o MetaTrader 5, saberá que temos uma nova barra de um minuto. Porém isto não nos é totalmente suficiente. Na linha 48 reiniciamos o contador de interações, para que um novo ciclo venha a ser novamente analisado. Para nossa conferência, na linha 49, imprimimos o valor que estamos informando ao MetaTrader 5.

Agora vem um detalhe: Até este momento, apenas o MetaTrader 5, foi informado que temos uma nova barra. Porém como você deve ter notado no artigo em que foi implementado o sistema que nos informa o tempo restante da barra. Não podemos usar o valor de tempo que está sendo informado pela função OnCalculate. Bem pelo menos até este momento. Estou pensando em uma forma de burlar esta limitação, assim não será preciso que a linha 50 seja executada. No entanto, até que uma forma de burlar esta limitação, seja realmente conseguida, precisamos que a linha 50, force o MetaTrader 5 a gerar um evento customizado, para que possamos informar ao indicador de mouse, que uma nova barra foi formada. Desta maneira, ele saberá em que ponto no tempo estamos de fato trabalhando. Conseguindo assim, nos informar quanto tempo resta, para que a barra que atualmente está sendo plotada, seja fechada e uma nova barra se inicie.

Mas se você não desejar compilar este serviço e testar o mesmo no MetaTrader 5, não tem problema. No vídeo abaixo mostro como o serviço se comportou e se é ou não viável usar este código de teste no serviço de replay/simulação. Muito bem, ao meu ver, o teste funcionou maravilhosamente bem. Se mostrando sim, possível e viável que seja adicionado o mecanismo testando na aplicação de replay / simulador. Isto para que em momentos ou em ativos com baixa liquidez, tenhamos a informação de quanto tempo resta até que uma nova barra venha a surgir no gráfico.


Para conseguirmos fazer isto, adicionar o mecanismo testado, na aplicação de replay/simulador. Teremos que mudar levemente uma parte bem específica no código. Esta parte se encontra no arquivo de cabeçalho C_Replay.mqh. Mas antes de partimos para implementar as modificações. Vamos entender uma outra coisa. Mas para separar as explicações, vamos a um novo tópico.


Tempo, tempo, tempo. Com podemos entender o tempo

Apesar de parecer confuso fazer certas coisas. Aqui não tem como, terei que mostrar algo que poderá ser extremamente confuso, mas que é perfeitamente possível e plausível de ser feito. E tudo está ligado com o comprimento dos dados.

Se você observar a função OnCalculate, não terá dificuldades em notar o fato de que o valor de spread é fornecido como um int. Isto significa em processadores de 64 bits que temos 32 bits sendo utilizados. E fazendo uma simples verificação, você também notará que o comprimento em termos de bits de datetime são 64 bits. Ok. Mas no que estas informações podem nos ajudar aqui? Calma meu caro leitor. Calma. Agora precisaremos fazer algumas contas. Mas nada de complicado. Coisa simples.

Precisamos que o serviço force o MetaTrader 5, a gerar algum tipo de evento a fim de atualizar o cronometro do indicador de mouse. Isto é fato e está sendo feito de duas formas diferentes. Uma é pela chamada a OnCalculate e outra é via um evento customizado, que disparamos de tempos em tempos. Para ver onde isto está acontecendo no serviço, veja o fragmento abaixo, que pertence ao arquivo de cabeçalho C_Replay.mqh:

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. //+------------------------------------------------------------------+

Fragmento do arquivo C_Replay.mqh

O mesmo foi explicado no artigo Desenvolvendo um sistema de Replay (Parte 66): Dando play no serviço (VII), mas aqui vamos dar uma reforçada na explicação. Sempre que este procedimento é chamado e temos a adição de um novo tick a barra que está sendo plotada no gráfico. Temos na linha 96, a inclusão do valor em termos de segundos ao spread, este tem como finalidade permitir que façamos o calculo de tempo restante da barra. Muito bem, e sempre que uma nova barra de um minuto se apresenta, temos na linha 94 um teste que se torna verdadeiro, que forçará o MetaTrader 5 a gerar um evento customizado. Este permitirá que o indicador de mouse reajuste o tempo, a fim de conseguir saber quando a nova barra foi gerada. O fato de fazermos assim, é para evitar adicionar uma chamada extra, que será executada de forma muito intensiva no indicador de mouse. Expliquei sobre isto no artigo acima mencionado.

De qualquer maneira, estamos desperdiçando tempo e espaço, ao fazer as coisas desta forma. Podemos ser um pouco mais eficientes. Conseguindo assim passar mais informações do que da forma como está sendo feito. Agora vem a parte das contas. Precisamos cronometrar as coisas a cada segundo, mais rápido do que isto, não é de fato necessário. Então já temos de onde partir. Temos 60 segundos em um minuto e 60 minutos em uma hora. 24 horas em um dia. Ok. Fazendo as contas isto nos dá 86400 segundos por dia (aproximadamente, já que um dia não tem 24 horas). Mas para o nosso propósito, assumiremos que um dia tem 24 horas. Muito bem. Um valor de 32 bits pode comportar um valor máximo de 4294967295, isto se ele for sem sinal. Novamente vamos assumir que não estamos lidando com um valor com sinal. Então em 32 bits podemos colocar aproximadamente 49 dias em temos de segundos. Preste atenção a este fato. Dentro de um único valor de spread, cujo comprimento é de 32 bits, podemos colocar aproximadamente 49 dias. Porém, nossa aplicação de replay/simulador, dificilmente será utilizada para análise e estudos superiores a um ou dois dias consecutivos. Assim sendo podemos sincronizar perfeitamente os dados usando apenas e somente a informação presente no spread. Não precisando assim forçar o MetaTrader 5, e fazer uma chamada customizada para informar ao indicador de mouse que uma nova barra surgiu.

Agora a coisa ficou interessante, não é mesmo? Mas para deixar as coisas ainda mais interessantes, irei de fato fazer uma modelagem bem mais ousada assim não teremos o limite de 49 dias. Se bem que duvido que alguém de fato experimente este limite. Mas de qualquer forma, implementarei algo diferentes. Onde usarei os bits de uma forma um pouco mais controlada, por assim dizer. Mas fazer isto nos forçará a fazer algumas mudanças no indicador de mouse. Mas nada muito complicado, frente ao que poderemos obter, ao fazer tais mudanças.

Mas será que fazer isto, irá nos dar o tipo de solução que pretendemos implementar? Para saber se sim ou não, precisaremos visualizar um outro fragmento, também pertencente ao arquivo de cabeçalho C_Replay.mqh. Este pode ser visto abaixo:

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. //+------------------------------------------------------------------+

Fragmento do arquivo C_Replay.mqh

Muito bem, o nosso problema é quando o ativo está tendo pouca liquidez. Ou seja, não temos ticks a cada segundo e de forma contínua. Pode ser que em alguns momentos a liquidez, esteja fazendo com que um tick seja gerado a cada segundo. O que é desejado para nosso propósito. Porém em alguns momentos esta liquidez não esteja sendo alcançada. Mesmo assim, você pode observar que na linha 225, estamos fazendo a chamada para o fragmento visto a pouco. E mesmo se o tempo entre ticks for acima de um segundo, ainda assim a chamada na linha 225 irá de fato acontecer. Então sim. Usar o spread de maneira a informar o tempo é de fato viável. Porém pode ser um pouco complicado a implementação de todas as questões envolvidas. Por conta disto, não será possível explicar todos os detalhes apenas neste artigo. Mas vamos iniciar as coisas aqui.


Iniciando uma nova etapa

A primeira coisa de fato que faremos será modificar o arquivo de cabeçalho Defines.mqh. As modificações podem ser vistas abaixo:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay      "RePlay"
14. #define def_MaxPosSlider      400
15. #define def_MaskTimeService   0xFED00000
16. //+------------------------------------------------------------------+
17. union uCast_Double
18. {
19.    double   dValue;
20.    long     _long;                                  // 1 Information
21.    datetime _datetime;                              // 1 Information
22.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
23.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
24.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
25. };
26. //+------------------------------------------------------------------+
27. enum EnumEvents    {
28.          evHideMouse,               //Hide mouse price line
29.          evShowMouse,               //Show mouse price line
30.          evHideBarTime,             //Hide bar time
31.          evShowBarTime,             //Show bar time
32.          evHideDailyVar,            //Hide daily variation
33.          evShowDailyVar,            //Show daily variation
34.          evHidePriceVar,            //Hide instantaneous variation
35.          evShowPriceVar,            //Show instantaneous variation
36.          evSetServerTime,           //Replay/simulation system timer
37.          evCtrlReplayInit,          //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Código fonte do arquivo Defines.mqh

Note que a linha 36 está riscada, sendo assim ela deixou de existir no arquivo final. Da mesma forma agora na linha 15 temos uma nova definição, que servirá como máscara para que possamos trabalhar com mais tranquilidade. Mas o simples fato de que a linha 36, que era o evento responsável por atualizar o cronometro foi removida. Teremos algumas mudanças, a fim de tornar o novo código compatível, com a nova política de mensagens.

Da mesma forma que foi feita aqui no arquivo Defines.mqh. Temos mais algumas mudanças em outro arquivo de cabeçalho, desta vez o arquivo Macros.mqh. O novo arquivo pode ser visto abaixo:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define macroRemoveSec(A) (A - (A % 60))
05. #define macroGetDate(A)   (A - (A % 86400))
06. #define macroGetSec(A)    (A - (A - (A % 60)))
07. #define macroGetTime(A)   (A % 86400)
08. //+------------------------------------------------------------------+
09. #define macroColorRGBA(A, B) ((uint)((B << 24) | (A & 0x00FF00) | ((A & 0xFF0000) >> 16) | ((A & 0x0000FF) << 16)))
10. #define macroTransparency(A) (((A > 100 ? 100 : (100 - A)) * 2.55) / 255.0)
11. //+------------------------------------------------------------------+

Código fonte do arquivo Macros.mqh

Aqui, no caso apenas adicionamos a linha sete, que é uma macro para que possamos separar o valor de horário, de dentro da estrutura de alguma variável cujo tipo seja datetime. Por ser algo muito simples, podemos seguir em frente, para agora podermos ver um outro arquivo de cabeçalho. Desta vez o arquivo de cabeçalho é o C_Stuty.mqh, que pode ser visualizado na íntegra logo a seguir. Este faz parte do indicador de mouse.

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 ? (datetime) m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent());
125.                   dt = (m_Info.Status == eInReplay ? (datetime) GL_TimeAdjust : TimeCurrent());
126.                   m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time);
127.                   if (dt > 0) m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS);
128.                   break;
129.                case eAuction      :
130.                   m_Info.szInfo = "Auction";
131.                   break;
132.                default            :
133.                   m_Info.szInfo = "ERROR";
134.             }
135.             Draw();
136.          }
137. //+------------------------------------------------------------------+
138. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
139.          {
140.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
141.             switch (id)
142.             {
143.                case CHARTEVENT_CUSTOM + evHideBarTime:
144.                   RemoveObjInfo(evHideBarTime);
145.                   break;
146.                case CHARTEVENT_CUSTOM + evShowBarTime:
147.                   CreateObjInfo(evShowBarTime);
148.                   break;
149.                case CHARTEVENT_CUSTOM + evHideDailyVar:
150.                   RemoveObjInfo(evHideDailyVar);
151.                   break;
152.                case CHARTEVENT_CUSTOM + evShowDailyVar:
153.                   CreateObjInfo(evShowDailyVar);
154.                   break;
155.                case CHARTEVENT_CUSTOM + evHidePriceVar:
156.                   RemoveObjInfo(evHidePriceVar);
157.                   break;
158.                case CHARTEVENT_CUSTOM + evShowPriceVar:
159.                   CreateObjInfo(evShowPriceVar);
160.                   break;
161.                case (CHARTEVENT_CUSTOM + evSetServerTime):
162.                   m_Info.TimeDevice = (datetime)lparam;
163.                   break;
164.                case CHARTEVENT_MOUSE_MOVE:
165.                   Draw();
166.                   break;
167.             }
168.             ChartRedraw(GetInfoTerminal().ID);
169.          }
170. //+------------------------------------------------------------------+
171. };
172. //+------------------------------------------------------------------+
173. #undef def_ExpansionPrefix
174. #undef def_MousePrefixName
175. //+------------------------------------------------------------------+

Código fonte do arquivo C_Study.mqh

Todas as linhas riscadas deste arquivo deverão ser removidas do arquivo original. Apesar das linhas que foram riscadas, fazerem de alguma forma parte do antigo código, que era responsável por permitir que o evento customizado, mantivesse o cronometro do indicador sincronizado. Isto agora não será mais necessário, pois passaremos a usar um outro método para fazer este trabalho. Porém ainda assim é preciso que você observe um detalhe neste arquivo de cabeçalho. Olhe na linha 124 e compare ela com a linha 125. Apesar de estar sendo indicando que toda a linha foi substituída. O que de fato aconteceu foi a remoção da variável m_Info.TimeDevice. Isto por que ela não será mais necessária, já que ela era usada como uma forma de mantermos o cronometro sincronizado. Mas agora temos um problema, por assim dizer, que está presente no código do indicador de mouse. O código do indicador pode ser visto logo abaixo, na íntegra:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.68"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. #property indicator_applied_price PRICE_CLOSE
13. //+------------------------------------------------------------------+
14. double GL_PriceClose;
15. datetime GL_TimeAdjust;
16. //+------------------------------------------------------------------+
17. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
18. //+------------------------------------------------------------------+
19. C_Study *Study       = NULL;
20. //+------------------------------------------------------------------+
21. input color user02   = clrBlack;                         //Price Line
22. input color user03   = clrPaleGreen;                     //Positive Study
23. input color user04   = clrLightCoral;                    //Negative Study
24. //+------------------------------------------------------------------+
25. C_Study::eStatusMarket m_Status;
26. int m_posBuff = 0;
27. double m_Buff[];
28. //+------------------------------------------------------------------+
29. int OnInit()
30. {
31.    ResetLastError();
32.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
33.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
34.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
35.    {
36.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
37.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
38.       m_Status = C_Study::eCloseMarket;
39.    }else
40.       m_Status = C_Study::eInReplay;
41.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
42.    ArrayInitialize(m_Buff, EMPTY_VALUE);
43.    
44.    return INIT_SUCCEEDED;
45. }
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] < 60 ? spread[rates_total - 1] : 0);
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. //+------------------------------------------------------------------+
63. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
64. {
65.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
66.    (*Study).SetBuffer(m_posBuff, m_Buff);
67.    
68.    ChartRedraw((*Study).GetInfoTerminal().ID);
69. }
70. //+------------------------------------------------------------------+
71. void OnBookEvent(const string &symbol)
72. {
73.    MqlBookInfo book[];
74.    C_Study::eStatusMarket loc = m_Status;
75.    
76.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
77.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
78.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
79.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
80.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
81.    if (loc != m_Status) (*Study).Update(m_Status);
82. }
83. //+------------------------------------------------------------------+
84. void OnDeinit(const int reason)
85. {
86.    if (reason != REASON_INITFAILED)
87.    {
88.       if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
89.          MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
90.    }
91.    delete Study;
92. }
93. //+------------------------------------------------------------------+

Código fonte do Indicador de Mouse

Este código, em alguns momentos, está tendo um comportamento, por conta de algumas coisas nele, que estão me fazendo passar raiva em alguns momentos durante os testes. E tem outras coisas, que foram testadas nele, mas que entrarei em detalhes em outro momento, que fazem muita, mas muita raiva, por não fazerem nenhum sentido. Tais coisas deveriam funcionar, mas por algum motivo bizarro e sem explicação alguma, não funcionam como deveriam. Mas vou deixar isto para outra ocasião, para explicar com mais calma. Aqui quero chamar a sua atenção, caro leitor, para algumas mudanças que foram necessárias serem feitas.

Vamos começar do final para poder entender o começo. Observe que as linhas entre a 47 e 49 foram riscadas e as mesmas foram substituídas pela linha 50. Ou seja, agora o tratador de eventos OnCalculate, não mais receberá todos aqueles parâmetros. Você, caro leitor, deve estar bastante confuso a do por que desta minha mudança de tática. Isto por que a pouco tempo, falei que não seria uma boa ideia fazer o que estou fazendo aqui e agora. Mas tentarei explicar o motivo de tal mudança, mas não será aqui, será quando eu for explicar o código do serviço. Observe que a linha 52, foi substituída pela linha 53. e que a linha 54 foi substituída pelas linhas 55 e 56. Além destas mudanças que acabei de informar, temos uma outra, que é o surgimento da linha 12. Mas antes de falar desta linha 12, vamos ver o que está acontecendo nas linhas 55 e 56.

Quando o serviço atualizar o RATE e veremos como isto é feito depois. O MetaTrader 5, dispara um evento Calculate, que será tratado na função OnCalculate. Ok. No entanto, se o tempo gráfico do ativo, for diferente de um minuto, o valor de rates_total NÃO e repito, NÃO SERÁ ATUALIZADO até que uma nova barra venha a surgir. Como o spread somente será usado, por nos, quando estivermos no replay / simulador. Verificamos se o símbolo corresponde ao esperado. Se isto for verdadeiro, teremos que capturar o valor de spread via função iSpread. O motivo para isto será explicado no próximo artigo. Assim como o restante do cálculo que estamos fazendo na linha 56. Pois neste momento ele não faz nenhum sentido.

De qualquer forma, por conta que usaremos a função de biblioteca iSpread, a fim de obter o valor correto. Não precisaremos usar a função OnCalculate com todos aqueles parâmetros anteriores. Por consequência, a linha 53, irá de fato capturar o valor do preço. E agora vem a pergunta: Mas qual preço? Pois existem mais de um, que podemos usar. E é neste ponto que entra a linha 12. Ela declara como propriedade do indicador, qual será de fato o preço que usaremos. Ali estamos definindo que o preço será o de fechamento da barra. Desta forma, mantemos a compatibilidade do indicador de mouse, aos valores que antes eram trabalhados no gráfico.


Considerações finais

Neste artigo abordei algumas questões, que ainda ficaram pendentes, mas que serão explicadas de uma forma mais aprofundada, nos próximos artigos. Existem diversas coisas que foram mostradas aqui, que não fazem nenhum sentido se você olhar apenas e somente o que está presente neste artigo. Entre elas o motivo de fazermos uso da função iSpread e não capturar o valor de spread, diretamente da chamada OnCalculate. Já que a mesma recebe o valor spread do MetaTrader 5. Mas também existe um outro assunto que ficou faltando mostrar e explicar, que é quando o ativo entra em leilão. Este assunto sobre o leilão é algo bastante estranho quando fazemos uso de um ativo customizado. Não por conta que o indicador de mouse não consiga nos mostrar que o ativo se encontra em leilão. A questão é outra. 

A forma de se fazer isto, já se encontra implementada e funcionando. Bastando para isto você observar o código presente na função OnBookEvent, no código do indicador de mouse. Você poderá ver que bastaria informarmos o valor BOOK_TYPE_BUY_MARKET ou BOOK_TYPE_SELL_MARKET, para que o indicador de mouse, viesse a nos mostrar que o ativo está em leilão. Mas em um outro momento, explicarei como fazer isto. Por hora me fico por aqui, e nos vemos no próximo artigo.

Arquivos anexados |
Anexo.zip (420.65 KB)
Do básico ao intermediário: Array e String (III) Do básico ao intermediário: Array e String (III)
Neste artigo iremos ver duas coisas. A primeira é como a biblioteca padrão consegue transformar valores binários em outras formas de representação, como octal, decimal e hexadecimal. A segunda coisa será a de como poderíamos com o conhecimento mostrado até aqui, definir uma largura para nossa senha, baseada em uma frase secreta. O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.
Algoritmo de Evolução do Casco da Tartaruga (Turtle Shell Evolution Algorithm, TSEA) Algoritmo de Evolução do Casco da Tartaruga (Turtle Shell Evolution Algorithm, TSEA)
Um algoritmo de otimização único, inspirado na evolução do casco da tartaruga. O algoritmo TSEA emula a formação gradual de áreas queratinizadas da pele, que representam as soluções ótimas para o problema. As melhores soluções tornam-se mais "duras" e se aproximam da superfície externa, enquanto as soluções menos bem-sucedidas permanecem "macias" e ficam na parte interna. O algoritmo utiliza a clusterização das soluções com base na qualidade e na distância, permitindo preservar as opções menos bem-sucedidas, garantindo flexibilidade e adaptabilidade.
Construindo um Modelo de Restrição de Tendências de Candlestick (Parte 3): Detectando mudanças nas tendências ao usar este sistema Construindo um Modelo de Restrição de Tendências de Candlestick (Parte 3): Detectando mudanças nas tendências ao usar este sistema
Este artigo explora como a divulgação de notícias econômicas, o comportamento dos investidores e vários fatores podem influenciar as reversões de tendências de mercado. Inclui uma explicação em vídeo e prossegue incorporando código MQL5 ao nosso programa para detectar reversões de tendência, nos alertar e tomar as ações apropriadas com base nas condições de mercado. Isso se baseia em artigos anteriores da série.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 20): Regressão Simbólica Técnicas do MQL5 Wizard que você deve conhecer (Parte 20): Regressão Simbólica
A Regressão Simbólica é uma forma de regressão que começa com poucas ou nenhuma suposição sobre qual seria o modelo subjacente que mapeia os conjuntos de dados em estudo. Embora possa ser implementada por Métodos Bayesianos ou Redes Neurais, analisamos como uma implementação com Algoritmos Genéticos pode ajudar a personalizar uma classe de sinal especialista utilizável no MQL5 Wizard.