English Русский Español
preview
Desenvolvendo um sistema de Replay (Parte 72): Uma comunicação inusitada (I)

Desenvolvendo um sistema de Replay (Parte 72): Uma comunicação inusitada (I)

MetaTrader 5Exemplos |
153 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 71): Acertando o tempo (IV), expliquei e mostrei, como você caro leitor, deveria proceder, a fim de adicionar ao serviço de replay / simulador o que foi mostrado em outro artigo.

No caso o artigo Desenvolvendo um sistema de Replay (Parte 70): Acertando o tempo (III). Onde fizemos uso de um serviço de teste, para conseguir compreender mais claramente como trabalhar com eventos de book. Isto quando o foco se encontra em um ativo customizado.

Nestes dois últimos artigos, a coisa toda foi bastante interessante. Isto devido a forma que precisamos trabalhar, de maneira a conseguir obter os resultados que desejávamos conseguir no final. Acredito que muitos tenha aprendido e entendido, a maneira correta e adequada de conseguir fazer com que o MetaTrader 5, faça uso do book. Novamente o foco aqui é inteiramente em um ativo customizado. Não se esqueça disto em nenhum momento.

Foi algo bastante curioso, ver que poderíamos, com o simples fato de adicionar o book. Permitir que o indicador de mouse, pudesse fazer uso da função OnCalculate, em que os dados são colocados em arrays pelo MetaTrader 5. Isto agiliza bastante as coisas. Já que não precisamos fazer uso da função iSpread, apenas para conseguir obter o spread da barra.

Sem aquele conhecimento, mostrado nos dois artigos, não é possível obter os dados entre os argumentos da função OnCalculate. Isto quando o tempo gráfico, venha a ser maior do que o de um minuto. Salvo este fato, não temos problemas em obter os dados via argumentos de OnCalculate. Mas a partir do momento em que eventos customizados de book, começam a ser utilizados. Passamos a contar com a possibilidade de ler o spread diretamente entre os argumentos de OnCalculate.

Mas no final no último artigo, expliquei um problema que temos de resolver. Isto por conta, de que se o problema não for resolvido. Você ou o usuário da aplicação, ficará totalmente sem condição, de fazer o correto uso da mesma. Então para quem está chegando agora, neste artigo. Vamos relembrar rapidamente do que se trata.


Relembrando o problema

O problema que temos de corrigir, é uma falha que acontece sempre que modificamos o tempo gráfico. Apesar de não ser uma falha catastrófica, ela é bastante incomoda. Acontecendo da seguinte forma: Quando iniciamos o serviço de replay/simulador, podemos selecionar o tempo gráfico. Uma vez que o gráfico tenha sido aberto, ele estará neste tempo definido.

Caso você ou o usuário, venha a modificar o tempo gráfico, usando para isto o MetaTrader 5, a informação dada pelo indicador de mouse, sairá de ativo em leilão para mercado fechado. E o indicador de controle irá de fato, desaparecer do gráfico. Impedindo assim que possamos fazer qualquer tipo de interação.

Apesar do incomodo que isto nos provoca. O fato é que esta falha se dá pelo motivo de que o MetaTrader 5, apenas recoloca os indicadores no gráfico, e logo em seguida chama a função OnInit. De qualquer maneira, o MetaTrader 5, está fazendo o que ele foi projetado para fazer. Nos é que estamos criando formas de o utilizar de uma maneira completamente diferente. Isto por que, precisamos que ele de fato funcione de maneira diferente.

Então o que acontece nestas situações. Quando a falha é disparada, precisamos esperar que o serviço, venha a disparar um evento customizado para o indicador de controle. A fim de que o indicador de controle, seja inicializado com novos valores. O problema é que isto pode vim a demorar um bocado de tempo. E outra para que o serviço de fato venha a disparar um evento customizado. Isto para que o indicador de controle seja inicializado com outros valores. Será necessário que o serviço esteja no modo play.

Se por um acaso o serviço não estiver no modo play. O indicador de controle jamais receberá o tal evento customizado, necessário para sua inicialização. Ficando assim totalmente inacessível para o usuário. Neste caso, seria preciso encerrar o serviço e logo depois tornar a abrir o serviço novamente. Ou seja, um puro transtorno. Agora supondo que o serviço esteja no modo play, teremos que esperar o tido evento customizado vim a ser disparado. Quando o evento customizado for disparado, os controles do indicador de controle, voltarão a ficar acessíveis. Neste momento, o usuário deverá dar pause no serviço de replay/simulação e logo depois dar play novamente. Isto fará com que a informação que deverá ser apresentada pelo indicador de mouse, que é o tempo restante da barra, seja novamente apresentada corretamente. Se novamente o usuário, voltar a modificar o tempo gráfico, todo o problema irá de fato se repetir.

Bem, no artigo anterior, expliquei com mais detalhes este problema. Aqui estou apenas relembrando do que se trata. E ao mesmo tempo, estou apresentando o problema, para quem está começando a ver esta sequência, neste artigo. De qualquer forma, deixei no artigo anterior, um pequeno desafio para meus caros leitores. O desafio seria imaginar uma forma de corrigir esta falha. Apesar de muitos imaginarem, que programação é a arte de escrever código, que podem ser executados pelo computador. Programação é muito mais do que isto. Escrever códigos é a parte fácil. O difícil mesmo é pensar em uma forma de resolver os problemas. Então programação, é na verdade, a arte de resolver, por meio de programas, os problemas que nos são apresentados.

Bem, sinceramente, não me importa, se você, caro leitor, conseguiu ou não resolver o problema. Apenas gostaria que você, se não o fez, tivesse pelo menos tentando fazer isto. Isto por que, resolver problemas e pensar na solução é sim uma arte. Digitar códigos é uma mera formalidade. Mas então, vamos ver como será feita a resolução de tal problema.


Primeira alternativa de solução

Se você de fato compreendeu o problema, irá logo de cara, pensar na seguinte solução: Vamos implementar uma forma de verificar quando o tempo gráfico foi modificado. Assim que isto vier a ser feito, faremos com que os indicadores consigam repor o seu estado, antes que eles tenham sido removidos do gráfico. Desta forma, quando o MetaTrader 5, os recolocar no gráfico, os indicadores, tanto o de controle, quando o de mouse, saberão o último estado e poderão retorna neste ponto. Ótima ideia. Mas neste caso temos um problema. Qualquer que seja a forma de você anotar o último estado, quem que o indicador se encontrava. Este não poderá, de forma alguma, ter algum tipo de ligação com o indicador. Precisando para isto, que esta informação, esteja disponível para o indicador, assim que o código OnInit for iniciado. Caso contrário teremos problemas.

Bem, existem diversas formas de se fazer isto. Mas todas, envolvem uma implementação e testes extras. Estes irão, de fato ser executados a fim de saber que os dados de último estado, poderão ou não ser usado. Pessoalmente acho esta solução bastante trabalhosa. Isto devido aos testes extras que precisariam ser implementados, apenas para atestar se a informação pode ou não ser usada.

Mas vamos supor, que você de fato deseje fazer assim. Como isto seria realmente implementado? Bem, vamos primeiro pensar no que deveríamos armazenar. No caso do indicador de controle, precisaríamos armazenar, se ele estaria no modo pause ou modo play. Ok. Isto parece ser o suficiente. Já no caso do indicador de mouse, bastaria apenas implementar uma forma de ele voltar, indicando que o ativo estaria em leilão. Muito bem. Armazenar tais informações é algo bastante simples. Você poderia colocar as mesmas em um arquivo, ou quem sabe colocar elas em uma variável global de terminal. De qualquer forma, esta parte seria bastante simples de ser feita. Agora vamos aos problemas que isto iria nos gerar.

No caso do indicador de controle, não haveria muito o que fazer. Já que ele, somente seria colocado no gráfico, quando o serviço de replay/simulador estivesse em execução. Então qualquer uma das duas soluções: Usando um arquivo ou usando variáveis globais de terminal, dariam cabo da coisa. Você testaria se o arquivo ou variável existe. Caso positivo, usaria o valor que estaria ali. Mas tem o problema, de que se passasse algum tempo, os dados poderiam ser incompatíveis com o atual status do serviço. Pense no seguinte: Você precisaria garantir que o serviço, removesse a variável global de terminal, ou o arquivo, assim que o serviço fosse finalizado.

Ou seja, apenas mais trabalho para se programar e se preocupar. Agora voltemos no caso do indicador de mouse. Se você viesse, a inicializar ele, com o valor de ativo em leilão. Isto poderia causar problemas, quando você o colocasse no gráfico de um ativo real. O motivo é que o mercado poderia estar fechado. Mas o indicador de mouse estaria de fato, indicando ativo em leilão. E outra tenho planos, para melhorar ainda mais o indicador de mouse. Então, fazer com que ele inicialize indicando que o ativo está em leilão, não nos ajuda em nada. Pelo contrário, cria uma camada extra de complicação futura. E quero ao máximo evitar o surgimento de tais camadas extras.

Então resumo da ópera: Usar uma variável global de terminal, está completamente fora de cogitação. Isto por que iriamos criar uma dupla forma de inicializar os valores dos indicadores. Assim como usar um arquivo irá nos gerar o mesmo tipo de incomodo. Então esta solução não nos serve. Precisamos de uma outra solução. Que esteja ao mesmo tempo, ligada ao indicador. Mas nos permita testar o que está acontecendo no gráfico.


Segunda alternativa de solução

Muito provavelmente, alguém pode sugerir, fazer com que o serviço olhe o tempo gráfico atual, do ativo customizado. Está de fato seria uma solução muito sagas. Isto por que, você se livraria do problema, de anotar qualquer coisa sobre os indicadores. Lembre-se de que o serviço de replay/simulação sabe, qual o status do indicador. Isto a qualquer tempo ou momento da execução. Mas apesar desta solução ser bastante sagas, ela tem um pequeno problema. Não existe um meio de você obter o tempo gráfico, via serviço.

Mas antes de desistirmos desta alternativa, vamos pensar um pouco. Se conseguirmos detectar, de alguma forma, a mudança de tempo gráfico. Nos bastaria fazer com que o serviço viesse a executar uma chamada UpdateIndicatorControl, presenta no arquivo de cabeçalho C_Replay.mqh que tudo estaria resolvido. De fato, isto é quase verdade. Teríamos na verdade, que fazer uma pequena edição no dito procedimento UpdateIndicatorControl, mas é muito melhor do que criar uma segunda forma de iniciar os valores no indicador. Então temos um pequeno problema. Fazer com que o serviço, consiga de alguma forma perceber que o tempo gráfico foi modificado. Assim ele poderia utilizar, os meios já implementados, para conseguir fazer com que os indicadores, viessem de fato a saber, como deveriam ser inicializados. Mas mesmo que fosse necessário fazer alguma mudança, na implementação do serviço, a fim de usar UpdateIndicatorControl. Esta seria bem menor, do que fazer toda uma mudança nos indicadores a fim se ajustar as coisas. Então de fato vale pensar nesta alternativa.


Terceira alternativa de solução

Muito bem, que tal então tentarmos fazer algo um pouco mais exótico? Vamos unir os dois mundos, ou melhor as duas alternativas em uma só. Mas vamos fazer isto sem usar variáveis globais de terminal e também sem usar arquivos. Vamos na verdade usar o buffer de dados como uma forma de transferir as informações que precisamos. Neste caso o valor do tempo gráfico. Isto para que o serviço consiga acompanhar o que está acontecendo. Então no momento que o tempo gráfico for modificado, o serviço irá de fato perceber isto. Quando isto ocorrer, podemos fazer com que o serviço, diga aos indicadores que eles precisam ser atualizados.

Assim sendo, nossa real dificuldade é colocar a informação, de tempo gráfico no buffer de dados. Depois ler esta informação no serviço, e quando ela for modificada pelo usuário, toda a aplicação irá de fato receber do serviço, um evento customizado. Este evento fará com que os indicadores de mouse, receba o valor correto a ser mostrado ao usuário. Assim como também o indicador de controle. Evitando que, este último venha a ficar indisponível para controlar, se estaremos no modo play ou pause.


Iniciando o teste de implementação

Ao contrário do que muitos as vezes pensam, nos programadores, testamos as coisas antes de realmente as colocar em prática. Não começamos modificando diretamente um programa, ou aplicação que já está em um estágio avançando. Sempre que algo novo, precisa ser implementado, primeiro criamos uma versão de teste. Esta versão de teste, é a mais simples quanto for possível ser feita. E nela ajustamos as coisas e estudamos como o processo final deverá ser de fato implementado. Isto nos poupa todo um trabalho, de ter que remover partes, que foram adicionadas e se mostraram inadequadas ao código final.

Então vamos criar um indicador, que seja bastante simples, apenas para verificar a questão das mudanças no tempo gráfico. O código fonte deste indicador pode ser visto logo abaixo. Isto na íntegra.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. int OnInit()
08. {
09.    Print(_Period);
10. 
11.    return
     INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Código fonte do Indicador de teste

O que este pequeno código está fazendo? Bem, tudo que ele faz é feito na linha nove. Ou seja, ele imprime no terminal, a informação que se encontra na variável interna _Period. Um exemplo de execução do mesmo é visto na figura abaixo:

Image 01

Resultado da execução

Observe, uma coisa curiosa aqui. Nesta imagem acima. Na coluna SOURCE temos o nome do indicador, mais o que nos interessa mesmo é o que está entre os parênteses. Para ser mais preciso, o que está depois da vírgula. Observe que modifiquei o tempo gráfico algumas vezes. E em cada uma delas temos um valor impresso na coluna de mensagens diferente. Isto talvez seja obvio. Mas esqueça o fato, de que os valores seriam diferentes. E também esqueça o fato de tentar conseguir obter algum tipo de lógica aqui. O que nos interessa realmente é: Qual o maior valor que _Period irá de fato conter? Bem. O MetaTrader 5, nos permite usar o período mensal. Na imagem em questão você pode ver este valor na penúltima linha, ou seja, o maior valor é 49153. Por que saber deste valor nos é importante? O motivo é que precisamos de alguma forma, fazer com que o serviço note que o valor mudou. Mas não podemos nos dar ao luxo de usar qualquer comprimento de bits para fazer isto. Precisamos fazer, com que a quantidade de bits usada, seja a menor possível. O motivo você irá de fato entender em breve.

Então vamos fazer uma pequena modificação, neste código acima. Assim o mesmo irá de fato ficar como mostrado abaixo:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. //+------------------------------------------------------------------+
07. int OnInit()
08. {
09.    Print(_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))));
10. 
11.    return INIT_SUCCEEDED;
12. }
13. //+------------------------------------------------------------------+
14. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
15. {
16.    return rates_total;
17. }
18. //+------------------------------------------------------------------+

Código fonte do indicador de teste

Muito bem, antes de ver o que esta linha nove agora está fazendo. Vamos ver na imagem abaixo o resultado da execução.

Image 02

Resultado da execução

Agora preste atenção ao seguinte fato. Antes era preciso 16 bits, para que consigamos colocar o valor, a fim de que venhamos a perceber que o tempo gráfico foi modificado. Agora observe o maior valor possível. Agora o valor passou a ser de: 96. Uau. Mas podemos fazer isto? Sim. Podemos e faremos, isto por que aparentemente o MetaTrader 5, já consolidou estes tempos gráficos. Então para nos podemos realmente modificar as coisas de forma a ter este valor sendo usado.

Agora voltemos ao código, veja que o processo para fazer a redução foi bastante simplificado. Este pode ser colocado em uma única linha e usando sempre constantes. Então já nos é mais do que o suficiente. Pois agora poderemos usar 7 bits para repassar o valor. Então precisamos pensar um pouco sobre como isto será feito. Lembrando que não nos interessa saber o tempo gráfico, e sim se ele mudou ou não.


Começando a transferir a informação para o serviço

Para transferir a informação do tempo gráfico, isto para que o serviço detecte que ele mudou. Precisaremos fazer uso do buffer do indicador. Visto que não temos outra forma de fazer isto. Correção, até teríamos, mas não quero fazer uso delas, quero de fato usar o buffer do indicador para tal coisa. Isto torna a coisa toda invisível para o usuário. E ao mesmo tempo nos dá um certo nível de encapsulamento das informações transferidas.

Mas antes de fazer isto, vamos adicionar este último código que testamos no nosso sistema. Ele irá de fato ser adicionado como uma definição. Então o código do arquivo de cabeçalho Defines.mqh, será modificado para a versão mostrada 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. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. //+------------------------------------------------------------------+
18. union uCast_Double
19. {
20.    double    dValue;
21.    long      _long;                                 // 1 Information
22.    datetime _datetime;                              // 1 Information
23.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
24.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
25.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
26. };
27. //+------------------------------------------------------------------+
28. enum EnumEvents    {
29.          evHideMouse,               //Hide mouse price line
30.          evShowMouse,               //Show mouse price line
31.          evHideBarTime,             //Hide bar time
32.          evShowBarTime,             //Show bar time
33.          evHideDailyVar,            //Hide daily variation
34.          evShowDailyVar,            //Show daily variation
35.          evHidePriceVar,            //Hide instantaneous variation
36.          evShowPriceVar,            //Show instantaneous variation
37.          evCtrlReplayInit           //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Código fonte do arquivo Defines.mqh

Muito bem. Agora observe o que temos na linha 16. Exatamente o código, que foi utilizado nos testes, vistos no tópico anterior. Legal. Isto garante que temos exatamente um código já testado e que esteja funcionando perfeitamente. Agora vem a parte complicada. Isto se você não tem acompanhado esta sequência de artigos, ou esteja chegando agora.

O buffer de dados do indicador, é sempre do tipo double, ou seja, ele, tem 8 bytes de comprimento. Por motivos que já expliquei em artigos anteriores nesta mesma sequência, devemos colocar qualquer informação dentro deste 8 bytes. Não podemos nos dar ao luxo de precisar de mais do que 8 bytes, para transferir informações do indicador para o serviço. Tudo deve estar contido em 8 bytes. Ok. Agora vamos pensar. O indicador de mouse, pode ser utilizado, em um mercado real. Ou seja, podemos usar o indicador de mouse, em um ativo que esteja recebendo dados, do servidor real de negociação. Mas o nosso problema realmente se dá quando fazemos uso do serviço de replay/simulação. Até o momento, o único indicador que realmente é necessário, apenas e somente no replay/simulação é o indicador de controle. Sendo assim, nada mais justo do que observar, como o buffer de dados do indicador de controle está configurado. Podemos ver isto na imagem abaixo:

Image 3

Vamos entender uma coisa: QWORD é uma definição vinda do ASSEMBLY. Ela indica que estamos lidando com um valor de 64 bits. O byte na extrema direita é o byte zero, assim como o byte na extrema esquerda é o byte sete. No caso, O indicador de controle, atualmente está fazendo uso de 4 bytes. Para entender veja o fragmento de código do arquivo de cabeçalho C_Controls.mqh, que se encontra logo abaixo:

168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             if (rates_total > 0)
176.                Buff[rates_total - 1] = info.dValue;
177.          }
178. //+------------------------------------------------------------------+

Fragmento do arquivo C_Controls.mqh

Observe que na linha 171, declaramos a união que pode ser vista no arquivo Defines.mqh visto mais acima. Observe que estamos usando dois valores de array, onde cada um faz uso de 16 bits. Isto nos dá um total de 32 bits. Se bem que nem todos os bits estão de fato sendo usado. Mas vamos considerar que estão sendo integralmente utilizados. Então quatro bytes dos oito possíveis, já estão em uso. Mas como nosso sistema de modelagem, fez com que possamos passar o tempo gráfico em um byte, isto fará com que passemos a utilizar cinco bytes dos oito disponíveis. Mas o maior cuidado que temos de tomar é qual o índex que deveremos usar aqui? Este tipo de coisa, costuma confundir muito quem está iniciando na programação. Pois muitas das vezes, ele se esquece de que quando utilizamos uma união, alguns bytes já estão ocupados com alguma informação útil. E se você utilizar o índex errado, acabará por destruir uma informação que ficará com um valor incorreto. Então vamos novamente nos voltar a imagem do QWORD. Nela temos que marcar quatro bytes, começando do índex zero. Isto faz com que até o índex três, esteja ocupado. Não devendo assim ser utilizado para nenhum outro propósito. Então o primeiro índex livre é o índex quatro.

Assim sendo, fazemos uma nova adição ao arquivo de cabeçalho Defines.mqh. De forma que ele agora ficará como mostrado 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. #define def_IndicatorTimeFrame   (_Period < 60 ? _Period : (_Period < PERIOD_D1 ? _Period - 16325 : (_Period == PERIOD_D1 ? 84 : (_Period == PERIOD_W1 ? 91 : 96))))
17. #define def_IndexTimeFrame       4
18. //+------------------------------------------------------------------+
19. union uCast_Double
20. {
21.    double    dValue;
22.    long      _long;                                 // 1 Information
23.    datetime _datetime;                              // 1 Information
24.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
25.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
26.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
27. };
28. //+------------------------------------------------------------------+
29. enum EnumEvents    {
30.          evHideMouse,               //Hide mouse price line
31.          evShowMouse,               //Show mouse price line
32.          evHideBarTime,             //Hide bar time
33.          evShowBarTime,             //Show bar time
34.          evHideDailyVar,            //Hide daily variation
35.          evShowDailyVar,            //Show daily variation
36.          evHidePriceVar,            //Hide instantaneous variation
37.          evShowPriceVar,            //Show instantaneous variation
38.          evCtrlReplayInit           //Initialize replay control
39.                   };
40. //+------------------------------------------------------------------+

Código fonte do arquivo Defines.mqh

Observe que na linha 17 temos uma nova definição, que nos permitirá, acessar de forma mais segura o índex correto. Muito bem, agora já temos a base do que precisamos. Mas como a coisa, em si, e por si só, é um pouco mais complicada, do que você deve de fato está imaginando. Vamos primeiramente ver como o código do arquivo de cabeçalho C_Controls.mqh, deverá de fato ficar. O código completo pode ser visto na íntegra logo abaixo:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonCtrl        def_PathBMP + "Ctrl.bmp"
011. #define def_ButtonCtrlBlock   def_PathBMP + "Ctrl_Block.bmp"
012. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
013. #resource "\\" + def_ButtonPlay
014. #resource "\\" + def_ButtonPause
015. #resource "\\" + def_ButtonCtrl
016. #resource "\\" + def_ButtonCtrlBlock
017. #resource "\\" + def_ButtonPin
018. //+------------------------------------------------------------------+
019. #define def_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
020. #define def_PosXObjects         120
021. //+------------------------------------------------------------------+
022. #define def_SizeButtons         32
023. #define def_ColorFilter         0xFF00FF
024. //+------------------------------------------------------------------+
025. #include "..\Auxiliar\C_Mouse.mqh"
026. //+------------------------------------------------------------------+
027. class C_Controls : private C_Terminal
028. {
029.    protected:
030.    private   :
031. //+------------------------------------------------------------------+
032.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
033.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
034. //+------------------------------------------------------------------+
035.       struct st_00
036.       {
037.          string   szBarSlider,
038.                   szBarSliderBlock;
039.          ushort   Minimal;
040.       }m_Slider;
041.       struct st_01
042.       {
043.          C_DrawImage *Btn;
044.          bool         state;
045.          short        x, y, w, h;
046.       }m_Section[eObjectControl::eNull];
047.       C_Mouse   *m_MousePtr;
048. //+------------------------------------------------------------------+
049. inline void CreteBarSlider(short x, short size)
050.          {
051.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
052.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
053.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
054.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
055.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
060.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
066.          }
067. //+------------------------------------------------------------------+
068.       void SetPlay(bool state)
069.          {
070.             if (m_Section[ePlay].Btn == NULL)
071.                m_Section[ePlay].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
072.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0, state ? "Press to Pause" : "Press to Start");
073.             if (!state) CreateCtrlSlider();
074.          }
075. //+------------------------------------------------------------------+
076.       void CreateCtrlSlider(void)
077.          {
078.             if (m_Section[ePin].Btn != NULL) return;
079.             CreteBarSlider(77, 436);
080.             m_Section[eLeft].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock);
081.             m_Section[eRight].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonCtrl, "::" + def_ButtonCtrlBlock, true);
082.             m_Section[ePin].Btn = new C_DrawImage(GetPointer(this), def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin, NULL);
083.             PositionPinSlider(m_Slider.Minimal);
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveCtrlSlider(void)
087.          {         
088.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
089.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
090.             {
091.                delete m_Section[c0].Btn;
092.                m_Section[c0].Btn = NULL;
093.             }
094.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
095.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
096.          }
097. //+------------------------------------------------------------------+
098. inline void PositionPinSlider(ushort p)
099.          {
100.             int iL, iR;
101.             string szMsg;
102.             
103.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
104.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
105.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
106.             m_Section[ePin].x += def_PosXObjects;
107.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
108.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)   if (m_Section[c0].Btn != NULL)
109.              {
110.                 switch (c0)
111.                 {
112.                    case eLeft  : szMsg = "Previous Position";            break;
113.                    case eRight : szMsg = "Next Position";                break;
114.                    case ePin   : szMsg = "Go To: " + IntegerToString(p); break;
115.                    default     : szMsg = "\n";
116.                 }
117.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)), szMsg);
118.             }
119. 
120.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
121.          }
122. //+------------------------------------------------------------------+
123. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
124.          {
125.             C_Mouse::st_Mouse InfoMouse;
126.             
127.             InfoMouse = (*m_MousePtr).GetInfoMouse();
128.             x = (short) InfoMouse.Position.X_Graphics;
129.             y = (short) InfoMouse.Position.Y_Graphics;
130.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
131.             {   
132.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
133.                   return c0;
134.             }
135.             
136.             return eNull;
137.          }
138. //+------------------------------------------------------------------+
139.    public   :
140. //+------------------------------------------------------------------+
141.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
142.          :C_Terminal(Arg0),
143.           m_MousePtr(MousePtr)
144.          {
145.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError >= ERR_USER_ERROR_FIRST) return;
147.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
148.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
149.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
150.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
151.             {
152.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
153.                m_Section[c0].y = 25;
154.                m_Section[c0].Btn = NULL;
155.             }
156.             m_Section[ePlay].x = def_PosXObjects;
157.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
158.             m_Section[eRight].x = m_Section[ePlay].x + 511;
159.             m_Slider.Minimal = eTriState;
160.          }
161. //+------------------------------------------------------------------+
162.       ~C_Controls()
163.          {
164.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
165.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
166.             delete m_MousePtr;
167.          }
168. //+------------------------------------------------------------------+
169.       void SetBuffer(const int rates_total, double &Buff[])
170.          {
171.             uCast_Double info;
172.             
173.             info._16b[eCtrlPosition] = m_Slider.Minimal;
174.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));
175.             info._8b[def_IndexTimeFrame] = (uchar) def_IndicatorTimeFrame;
176.             if (rates_total > 0)
177.                Buff[rates_total - 1] = info.dValue;
178.          }
179. //+------------------------------------------------------------------+
180.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
181.          {
182.             short x, y;
183.             static ushort iPinPosX = 0;
184.             static short six = -1, sps;
185.             uCast_Double info;
186.             
187.             switch (id)
188.             {
189.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
190.                   info.dValue = dparam;
191.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
192.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
193.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
194.                   break;
195.                case CHARTEVENT_OBJECT_DELETE:
196.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
197.                   {
198.                      if (sparam == def_ObjectCtrlName(ePlay))
199.                      {
200.                         delete m_Section[ePlay].Btn;
201.                         m_Section[ePlay].Btn = NULL;
202.                         SetPlay(m_Section[ePlay].state);
203.                      }else
204.                      {
205.                         RemoveCtrlSlider();
206.                         CreateCtrlSlider();
207.                      }
208.                   }
209.                   break;
210.                case CHARTEVENT_MOUSE_MOVE:
211.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
212.                   {
213.                      case ePlay:
214.                         SetPlay(!m_Section[ePlay].state);
215.                         if (m_Section[ePlay].state)
216.                         {
217.                            RemoveCtrlSlider();
218.                            m_Slider.Minimal = iPinPosX;
219.                         }else CreateCtrlSlider();
220.                         break;
221.                      case eLeft:
222.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
223.                         break;
224.                      case eRight:
225.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
226.                         break;
227.                      case ePin:
228.                         if (six == -1)
229.                         {
230.                            six = x;
231.                            sps = (short)iPinPosX;
232.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
233.                         }
234.                         iPinPosX = sps + x - six;
235.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
236.                         break;
237.                   }else if (six > 0)
238.                   {
239.                      six = -1;
240.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
241.                   }
242.                   break;
243.             }
244.             ChartRedraw(GetInfoTerminal().ID);
245.          }
246. //+------------------------------------------------------------------+
247. };
248. //+------------------------------------------------------------------+
249. #undef def_PosXObjects
250. #undef def_ButtonPlay
251. #undef def_ButtonPause
252. #undef def_ButtonCtrl
253. #undef def_ButtonCtrlBlock
254. #undef def_ButtonPin
255. #undef def_PathBMP
256. //+------------------------------------------------------------------+

Código fonte do arquivo C_Controls.mqh

Por conta de algumas questões, que foram modificadas nos artigos anteriores. É necessário que você entenda e faça, no mínimo algumas modificações, no arquivo de cabeçalho C_Controls.mqh. Isto entre outras modificações que irei de fato mostrar agora. Observe na linha 10, esta linha está fazendo referência a imagem Ctrl.bmp. Mas que imagem é esta? Esta imagem, é a antiga imagem LEFT.BMP. Assim como a linha 11, que faz referência a antiga imagem LEFT_BLOCK.BMP. Agora usaremos um outro nome, para estas mesmas imagens. De qualquer forma, no anexo deixarei as imagens para que você em caso de dúvida possa colocar elas no projeto.

Existe uma outra mudança aqui. Esta está no constructor da classe C_Controls. Observe na linha 146 que agora, o constructor somente irá de fato retornar antes de cumprir o seu trabalho. Se o erro indicado, for um dos que nos como programadores, estamos indicando no código. Outros tipos de erro serão ignorados pelo menos por hora.

Mas a questão que realmente nos importa está na linha 175. Nesta linha é que estamos dizendo, onde e o que será adicionado no buffer do indicador. Como foi proposto durante todo este artigo. Mas por conta que fazer uso desta informação, é um pouco mais complicado, do que simplesmente a colocar no buffer do indicador. Não irei de fato mostrar todo o código agora. Sendo que é necessário explicar, para quem esteja tentando aprender a programar para o MetaTrader 5, como as coisas realmente funcionam. Então, caro leitor, se você já tem mais experiência, peço desculpas por não mostrar todo o código agora.


Considerações finais

Por conta que será necessário, explicar algumas mudanças, que serão feitas no código do serviço. Assim como uma mudança, que ainda é necessária no código do indicador, para que este sistema proposto funcione. Não irei de fato mostrar os códigos aqui, neste artigo. Lamento por aqueles que tem bem mais experiência no MQL5. Mas durante este tempo, que estou publicando estes artigos. Tenho notado que existem muitos, que de fato estão começando no MQL5. Sendo aspirantes a programadores em MQL5, tenho procurado atender e a explicar as coisas justamente para estes. No entanto, independentemente disto, no anexo, você terá duas imagens. Estas são as imagens referenciadas pelo arquivo de cabeçalho C_Controls.mqh.

Não se esqueça de fazer este ajuste, no projeto. Isto caso queira continuar a acompanhar o mesmo.

Então para o próximo artigo, ficará as seguintes coisas a serem feitas: Mostrar e explicar a modificação efetuada no código fonte do indicador de controle. Aqui vimos a mudança no arquivo de cabeçalho, mas ainda falta a mudança no código do indicador. Assim como também mostrar, como modificar e ajustar, o código fonte do arquivo de cabeçalho C_Replay.mqh. Sem estas mudanças e explicações, que serão feitas no próximo artigo. Se torna muito difícil que o serviço consiga detectar que o tempo gráfico foi modificado. Isto apenas observando diretamente o indicador que está no gráfico.

Então até o próximo artigo e não se esqueça de estudar bastante este conteúdo daqui.

Arquivos anexados |
Anexo.zip (420.65 KB)
Do básico ao intermediário: Array (IV) Do básico ao intermediário: Array (IV)
Neste artigo iremos ver como podemos fazer algo muito parecido com o encontrado em linguagens como C, C++ e Java. Onde podemos enviar um número quase infinito de parâmetros para dentro de uma função ou procedimento. Apesar de aparentemente ser um tópico avançado. Na minha visão, o que será visto aqui, pode muito bem ser implementado por qualquer iniciante. Desde que ele tenha compreendido os conceitos vistos arteriormente. 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.
Como visualizar operações diretamente no gráfico sem se perder no histórico de negociações Como visualizar operações diretamente no gráfico sem se perder no histórico de negociações
Neste artigo, criaremos uma ferramenta simples para visualização prática de posições e operações diretamente no gráfico, com navegação por teclas. Isso permitirá que traders estudem visualmente operações individuais e obtenham todas as informações sobre os resultados das negociações diretamente no local.
Redes neurais de maneira fácil (Parte 94): Otimização da sequência de dados iniciais Redes neurais de maneira fácil (Parte 94): Otimização da sequência de dados iniciais
Ao trabalhar com séries temporais, geralmente usamos os dados na sequência histórica. Mas isso é realmente o mais eficiente? Há quem acredite que modificar a sequência dos dados iniciais pode aumentar a eficácia dos modelos de aprendizado. Neste artigo, vou apresentar um desses métodos.
Elementos da análise correlacional em MQL5: Critério de independência qui-quadrado de Pearson e relação de correlação Elementos da análise correlacional em MQL5: Critério de independência qui-quadrado de Pearson e relação de correlação
O artigo aborda as ferramentas clássicas da análise correlacional. São apresentadas as bases teóricas breves, bem como a implementação prática do critério de independência qui-quadrado de Pearson e o coeficiente de relação de correlação.