English Русский Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay (Parte 73): Uma comunicação inusitada (II)

Desenvolvendo um sistema de Replay (Parte 73): Uma comunicação inusitada (II)

MetaTrader 5Exemplos |
226 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 72): Uma comunicação inusitada (I) comecei a mostrar, como você pode fazer, para usar um indicador a fim de transmitir um tipo de informação, que de outra forma seria impossível se obter. Muito bem, mas apesar de dar uma série de explicações naquele artigo. Implementar de fato o código, na nossa aplicação de replay/simulador, não é algo assim tão simples. Você talvez e muito provavelmente, esteja pensando, que eu esteja fazendo graça. Que implementar tal coisa é algo bem simples e direto. Que na verdade, estou apenas fazendo suspense.

Pois bem, até queria está fazendo suspense, com relação a este assunto. Mas, o grande detalhe, é que de fato a coisa é bem mais complicada, do que você estaria de fato imaginando. E tenho dado um foco especial, em fazer os artigos voltando a explicação, a todos os aspirantes a aprender de fato, fazer as coisas no MQL5. E o atual tema, de fato é algo que até o momento, em que escrevo este artigo, não vi absolutamente ninguém explorando. Não que seja algo impensável, mas é no mínimo, algo bastante exótico e muito pouco comum. Por conta disto, estou tentando mostrar ao máximo os detalhes de como você deve proceder, quando tiver que fazer algo, que não existem precedentes.


Continuando a implementação

Antes de entrar na questão do serviço em si. Pois esta será de fato e com toda a certeza, a parte mais complicada de toda a implementação. Vamos ver como é o código do indicador de controle. Isto por que no artigo anterior, apenas apresentei o código do arquivo de cabeçalho C_Controls.mqh. E por conta que já havia muita informação ali. Tomei a decisão, de finalizar a explicação, sobre o indicador de controle, neste artigo. Então, vamos começar vendo o código fonte do mesmo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.73"
07. #property link "https://www.mql5.com/pt/articles/12363"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal = 0;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
24.       SetUserError(C_Terminal::ERR_PointerInvalid);
25.    if ((_LastError >= ERR_USER_ERROR_FIRST) || (user00 == 0))
26.    {
27.       Print("Control indicator failed on initialization.");
28.       return INIT_FAILED;
29.    }
30.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
31.    ArrayInitialize(m_Buff, EMPTY_VALUE);
32.       
33.    return INIT_SUCCEEDED;
34. }
35. //+------------------------------------------------------------------+
36. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
37. {
38.    (*control).SetBuffer(m_RatesTotal = rates_total, m_Buff);
39.    
40.    return rates_total;
41. }
42. //+------------------------------------------------------------------+
43. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
44. {
45.    (*control).DispatchMessage(id, lparam, dparam, sparam);
46.    (*control).SetBuffer(m_RatesTotal, m_Buff);
47. }
48. //+------------------------------------------------------------------+
49. void OnDeinit(const int reason)
50. {
51.    switch (reason)
52.    {
53.       case REASON_TEMPLATE:
54.          Print("Modified template. Replay // simulation system shutting down.");
55.       case REASON_INITFAILED:
56.       case REASON_PARAMETERS:
57.       case REASON_REMOVE:
58.       case REASON_CHARTCLOSE:
59.          ChartClose(user00);
60.          break;
61.    }
62.    delete control;
63. }
64. //+------------------------------------------------------------------+

Código fonte do Indicador de controle

Se você comparar este código acima, com os mais antigos. Irá logo notar, que na linha 25, temos uma diferença no código. Antes o teste da variável _LastError era feita sobre a constante ERR_SUCESS. Porém, fazer isto, estava causando alguns problemas. Isto por que as vezes o indicador era colocado no gráfico, quando a variável _LastError, continha algum valor diferente de ERR_SUCESS.

Demorei um tempo para compreender o motivo pelo qual, as vezes a inicialização falhava. É estranho, pois mesmo depois de fazer uso da chamada de biblioteca ResetLastError, e debugando o código a fim de eliminar qualquer falha. Ainda assim quando o constructor da classe C_Controls retornava, o mesmo as vezes tinha _LastError com algum tipo de valor.

E o mais estranho nesta história, é o fato de que o erro muitas das vezes, estava relacionado a algum outro ativo, ou gráfico aberto. Então, aparentemente e que isto fique bem claro, pois não estou afirmando nada aqui. Existe vazamento de informações entre gráficos diferentes. Mas mesmo quando, apenas o gráfico do ativo customizado, estava presente, havia um retorno de erro que nada tinha a ver com a aplicação. Assim sendo decidi, isolar os erros. Sendo que apenas os erros definidos com a chamada de biblioteca, SetUserError, é que de fato fará o indicador ser encerrado. Evitando que ele permaneça no gráfico.

Mas o que realmente precisamos nos atentar, aqui, neste código é com relação a função OnCalculate. E por que esta função tanto nos interessa. O fato é que a função OnChartEvent, é disparada em diversos momentos. Principalmente por conta dos movimentos do mouse. Lembre-se do fato de que estamos usando e precisamos do mouse, em nossa aplicação. Mas a função OnCalculate, é disparada sempre que um novo tick, ou cotação chega ao gráfico do ativo. Ok. Então mesmo que você venha a fazer uso apenas do teclado. Ou de alguma interface, que o pressionar das teclas, venha a produzir certos eventos. Teríamos uma chamada a OnChartEvent. Mas podemos garantir, que a coisa se desenhe mais rapidamente. Ou melhor dizendo, podemos garantir que a informação no buffer, será a mais atual possível. Então para garantir tal coisa usamos a função OnCalculate para isto.

Observe então a linha 38. Ela é bem simples e direta. Fazendo o mesmo trabalho da linha 46. Só que ao contrário da linha 46, aqui na linha 38 teremos a atualização do buffer, a cada nova contação que o indicador vier a receber. Mesmo que o mouse tenha travado, ou o evento de mouse não venha a ser disparado. Fazendo com que OnChartEvent, somente seja executada depois da mudança, OnCalculate, é chamada quase a todo momento.

Então agora você já sabe, o que temos como indicador de controle. Mas não se esqueça, de que no momento de troca do tempo gráfico. A função OnDeInit é executada, removendo o indicador do gráfico e logo em seguida a função OnInit e chamada. Então antes que qualquer coisa seja colocada no gráfico, OnCalculate é executada para somente depois OnChartEvent. E precisamos que os dados no buffer estejam sempre atualizados.

Muito bem, então isto significa, que já podemos partir para modificar, o arquivo de cabeçalho C_Replay.mqh? Bem, até que poderíamos fazer isto agora. Porém, toda via e, entretanto, não tenho certeza de que os aspirantes, de fato tenham compreendido como esta comunicação se dará. Por conta disto, peço a você caro leitor, um pouco mais de paciência. Se você já sabe como isto irá, ou deverá acontecer. Vamos ter paciência para que, aqueles que ainda não sabem, ou não fazem ideia, possam também aprender e compreender como isto acontece. Por isto, vamos ver um próximo tópico.


Entendendo a troca de informações rápidas

A maior parte dos aspirantes, ou pessoas com menos experiência no MQL5, muito provavelmente ficará imaginando que trocar informações, ou melhor ler o buffer do indicador pelo serviço é algo simples. Tudo que você precisará fazer é usar um handle, ou mais conhecido como manipulador. De fato, isto funciona bem, e muito bem, diga-se de passagem. Mas tem um porém. Ou para ser mais preciso, uma falha em usar um handle neste tipo de caso.

Neste momento, talvez você não se sinta assim tal experiente. Ou imagine que estou de sacanagem. Como assim tem uma falha em utilizar um handle? Eu sempre fiz isto, e sempre deu certo. Ok, não vou discutir com a sua lógica, ou experiência. Discussões infundadas não nos leva em lugar nenhum. No lugar de ficar discutindo e tentando provar algo, que tão tirar a prova real. Assim ninguém irá de fato poder argumentar a respeito. Pois algo que foi provado e comprovado, não tem como contestar. A coisa passa a ser uma verdade, e ponto final.

Então para provar, que usar um handle, ou manipulador, a fim de acessar os dados de um buffer. Isto quando o buffer faz parte de um indicador. Não é algo sustentável quando fazemos o acesso via serviço. E que fique bem claro. A coisa NÃO É SUSTENTÁVEL quando você modifica o tempo gráfico. É muito importante, você ter isto em mente. O problema é a troca do tempo gráfico. Desta forma vamos fazer o seguinte: Vamos criar dois códigos para testar isto. Não se preocupe, serão códigos bastante simples e curtos. Então logo abaixo temos o primeiro deles. O código do indicador.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property version   "1.00"
04. #property indicator_chart_window
05. #property indicator_plots 0
06. #property indicator_buffers 1
07. //+------------------------------------------------------------------+
08. #include <Market Replay\Defines.mqh>
09. //+------------------------------------------------------------------+
10. double m_Buff[];
11. //+------------------------------------------------------------------+
12. int OnInit()
13. {
14.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
15.    ArrayInitialize(m_Buff, EMPTY_VALUE);
16.    IndicatorSetString(INDICATOR_SHORTNAME, "TEST");
17. 
18.    return INIT_SUCCEEDED;
19. }
20. //+------------------------------------------------------------------+
21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
22. {
23.    m_Buff[rates_total - 1] = 1.0 * def_IndicatorTimeFrame;
24.    
25.    return rates_total;
26. }
27. //+------------------------------------------------------------------+

Código fonte do indicador de teste

Observe que na linha cinco, estamos dizendo ao compilador, que não iremos de fato plotar nenhuma informação. Assim ele não ficará emitindo alertas a cada nova tentativa de compilação. Já na linha seis, estamos dizendo que faremos uso de um buffer. Na linha oito estamos fazendo a inclusão de um arquivo de cabeçalho, que é usado na aplicação de replay/simulação. Isto é importante, já que a ideia aqui, será compreender como o sistema de replay/simulação irá de fato acessar dos dados. Pois bem, na linha 10 temos o nosso buffer. No corpo do código OnInit, temos a inicialização do buffer assim como a declaração do nome do indicador. Atenção a este fato, pois precisaremos desta informação depois.

Agora preste atenção, ao corpo da função OnCalculate. Exatamente na linha 23 iremos de fato colocar no buffer o valor do tempo gráfico. Este valor é o mesmo que foi mostrado no artigo anterior. Por isto é importante entender aquele artigo. Ok. Acredito que todos aqui, com mais ou menos experiência sabem, exatamente o que um indicador faz e como ele trabalha. Pelo menos no nível mais básico da coisa. Assim sendo, agora vamos ver o segundo código.

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. input string user01 = "IBOV";         //Accompanying Symbol
07. //+------------------------------------------------------------------+
08. void OnStart()
09. {
10.    int ret, handle;
11.    long id;
12.    double Buff[1];
13.             
14.    if ((id = ChartFirst()) > 0) do
15.    {
16.       if (ChartSymbol(id) == user01) break;
17.    }while ((id = ChartNext(id)) > 0);
18.    handle = ChartIndicatorGet(id, 0, "TEST");
19.    do
20.    {
21.       ret = CopyBuffer(handle, 0, 0, 1, Buff);
22.       PrintFormat("CopyBuffer: [ %d ]  Value: [ %f ]", ret, Buff[0]);
23.       Sleep(250);
24.    } while ((!_StopFlag) && (ret > 0));
25.    IndicatorRelease(handle);
26. }
27. //+------------------------------------------------------------------+

Código fonte do serviço de teste

Veja que na linha dois, estamos dizendo ao compilador que o código será um serviço. Já a linha seis, adicionamos uma possibilidade, de que o usuário, venha a poder configurar, qual o ativo será observado pelo serviço. Até ai, nenhum problema. Entre as linhas 10 e 12 declaramos as variáveis. Agora vem um detalhe: Para que o serviço funcione, é preciso quer pelo menos um gráfico aberto. Caso contrário teremos problemas, já que não existem testes aqui. Porém, se tivermos pelo menos um gráfico aberto, a linha 14 irá de fato capturar a ID deste gráfico e faremos uma varredura a procura do gráfico do ativo indicado na linha seis. Uma vez que o encontremos, temos a ID correta.

Com a ID do gráfico, pedimos ao MetaTrader 5, para que ele nos forneça o handle, ou manipulador do indicador. Reparem no nome usado aqui. Este deverá ser o mesmo indicado lá na linha 16 do código fonte do indicador. Agora entramos em um loop, já que temos o valor do handle, podemos usar ele para ler o buffer do indicador. Beleza, isto é feito na linha 21. Já na linha 22 imprimimos no terminal as informações capturadas do buffer, assim como o valor retornado na leitura do buffer. Este loop executará até a que as condições na linha 24, não sejam mais satisfeitas. Ou seja, que o serviço seja encerrado, ou a leitura seja negativa. Assim que isto ocorrer na linha 25 liberamos o handle.

Ok, todos concordam que este código irá de fato ler o buffer do indicador. E o valor que deverá ser impresso, irá de fato corresponder ao valor esperado devido aos testes executados no artigo anterior. Então vamos por isto para executar no MetaTrader 5. Para facilitar, você pode ver o resultado no vídeo logo abaixo.


Mas como? O que aconteceu aqui? Você está mentindo para mim. Não acredito neste vídeo. Você está tentando me tapear. Tudo bem. Você é livre para acreditar no que quiser. Na verdade, você não precisa acreditar em mim. Faça você mesmo, os testes que você desejar. Mas mantendo exatamente a ideia de usar um handle ou manipulador a fim de ler o buffer do indicador.

Isto que você viu no vídeo acima irá de fato acontecer. Mas por que? O segredo está no handle ou manipulador. Assim que o MetaTrader 5, modifica o tempo gráfico, o valor de identificação do handle mudará. Porém o código continua ainda olhando um manipulador que ele conhece. Mas este já não é o que o MetaTrader 5, mantém no gráfico. Quando você faz uso de um Expert Advisor, ou outro indicador para fazer a captura do handle e assim ler o buffer do indicador. O handle será atualizado. Mesmo que o seu valor permaneça o mesmo. Isto por que tanto o Expert Advisor, quando outro indicador será recolocado no gráfico, forçando com que as chamadas sejam refeitas. Neste momento, o handle será de fato atualizado, para que assim você conseguir ler o buffer correto.

Porém, o serviço está sendo executado fora do gráfico. Então ele não terá o valor do handle atualizado. Por isto que por que o valor lido é sempre o mesmo. Mas você pode estar achando que isto não se aplica. Que o serviço, vive de alguma outra forma fora do mundo real. Beleza. Vamos então testar esta hipótese. Com isto teremos de fato uma prova real do que eu falei sobre o handle. Para fazer isto, vamos modificar o apenas o código do serviço. Então o novo código é visto logo abaixo:

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. //+------------------------------------------------------------------+
06. input string user01 = "IBOV";         //Accompanying Symbol
07. //+------------------------------------------------------------------+
08. void OnStart()
09. {
10.    int ret;
11.    long id;
12.    double Buff[1];
13.             
14.    if ((id = ChartFirst()) > 0) do
15.    {
16.       if (ChartSymbol(id) == user01) break;
17.    }while ((id = ChartNext(id)) > 0);
18.    do
19.    {
20.       ret = CopyBuffer(ChartIndicatorGet(id, 0, "TEST"), 0, 0, 1, Buff);
21.       PrintFormat("CopyBuffer: [ %d ]  Value: [ %f ]", ret, Buff[0]);
22.       Sleep(250);
23.    } while ((!_StopFlag) && (ret > 0));
24. }
25. //+------------------------------------------------------------------+

Código fonte do serviço de teste

Note que o código, sofreu pequenas modificações. Sendo elas quase que imperceptíveis. Mas o grande detalhe, e quero enfatizar isto, está na linha 20. Agora o handle, ou manipulador deixou de ser estático, e passou a ser dinâmico. Ou seja, mesmo que o serviço não saiba qual o tempo gráfico, o que tipo de modificação está acontecendo no gráfico. Ele será capaz de olhar para o indicador correto, e assim ler o buffer do mesmo. Mais uma vez, para facilitar as coisas, você pode ver o resultado da execução no vídeo imediatamente abaixo:


Agora sim, percebo que você realmente está de sacanagem. Como isto é possível? Calma meu caro leitor, como eu havia dito: Não queria de fato mostrar as mudanças no arquivo C_Replay.mqh. Sem antes lhe mostrar isto daqui.

Percebem, como algo aparentemente simples, porém perfeitamente aceitável é capaz de mudar completamente o que acontece? O detalhe é que como eu disse lá no começo do artigo. Devemos sempre experimentar as coisas em algo o mais simples possível. Vejo muita gente querendo fazer códigos elaborados, mas que não tem uma base sólida e bem consolidada. No final, acaba desistindo por que não consegue entender estes pequenos detalhes.

Mas a verdade é que você deve sempre pesar o custo computacional com o custo da implementação ou execução. Nada adianta você ter um código que funciona, se ele é lento. A mesma coisa ao contrário: Ter um código rápido que não funciona.

O fato de fazer com que o handle, ou manipulador, seja neste segundo código dinâmico. Faz com que a execução do mesmo, seja ligeiramente mais lenta do que a primeira versão deste mesmo serviço. Isto por conta que nesta segunda versão, temos o peso de uma chamada a cada execução. E na primeira versão, sabemos de antemão o handle, então é muito mais rápido, simplesmente olhar o valor em uma variável.

Este é o tal custo que você deve sempre pesar. Por isto, é bom sempre testar as coisas em um código mais simples. Mas então, como isto irá de fato ser colocado e implementado, no arquivo de cabeçalho C_Replay.mqh. Já que este é o que controla o serviço de replay/simulador. Para responder isto, vamos a um novo tópico.


Modificando o arquivo C_Replay.mqh

Muito bem. Aqui fiquei com um pequeno dilema: Apresentar o código já modificado, ou apresentar o código sendo modificado? Este tipo de dilema, as vezes torna escrever o artigo um pouco mais lento de ser escrito, do que eu gostaria. Criar o código e testar o mesmo é sempre muito rápido. Mas explicar o que aconteceu, isto toma bastante tempo.

Mas, a minha intensão é que você, caro leitor, entenda e aprenda como fazer as coisas. Eu já estou bastante calejado e tenho cicatrizes profundas adquiridas por anos programando. De qualquer forma, acredito que mostrar o código sendo modificado é o melhor caminho. Mesmo que você caro leitor, tenha um pequeno trabalho em remover as partes obsoletas. Sendo assim, vamos ver o arquivo de cabeçalho C_Replay.mqh, totalmente na íntegra logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl    "Market Replay Control"
013. #define def_MaxSlider             (def_MaxPosSlider + 1)
014. //+------------------------------------------------------------------+
015. class C_Replay : public C_ConfigService
016. {
017.    private   :
018.       struct st00
019.       {
020.          C_Controls::eObjectControl Mode;
021.          uCast_Double               Memory;
022.          ushort                     Position;
023.          int                        Handle;
024.       }m_IndControl;
025.       struct st01
026.       {
027.          long     IdReplay;
028.          int      CountReplay;
029.          double   PointsPerTick;
030.          MqlTick  tick[1];
031.          MqlRates Rate[1];
032.       }m_Infos;
033.       stInfoTicks m_MemoryData;
034. //+------------------------------------------------------------------+
035. inline bool MsgError(string sz0) { Print(sz0); return false; }
036. //+------------------------------------------------------------------+
037. inline void SendEventCustom(const ENUM_BOOK_TYPE Arg1 = BOOK_TYPE_BUY_MARKET)
038.          {
039.             MqlBookInfo book[1];
040.             
041.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
042.             m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
043.             m_IndControl.Memory._8b[7] = 'D';
044.             m_IndControl.Memory._8b[6] = 'M';
045.             EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
046.             book[0].price = 1.0;
047.             book[0].volume = 1;
048.             book[0].type = Arg1;
049.             CustomBookAdd(def_SymbolReplay, book, 1);
050.          }
051. //+------------------------------------------------------------------+
052. inline void CheckIndicatorControl(void)
053.          {
054.             static uchar memTimeFrame = 0;
055.             static C_Controls::eObjectControl memMode = m_IndControl.Mode;
056.             double Buff[];
057.             
058.             if (CopyBuffer(ChartIndicatorGet(m_Infos.IdReplay, 0, "Market Replay Control"), 0, 0, 1, Buff) < 0) ChartClose(m_Infos.IdReplay);
059.             m_IndControl.Memory.dValue = Buff[0];
060.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] >= m_IndControl.Position)
061.             {
062.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
063.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
064.                if (m_IndControl.Memory._8b[def_IndexTimeFrame] == memTimeFrame)
065.                {
066.                   memMode = m_IndControl.Mode;
067.                   return;
068.                }
069.                memTimeFrame = m_IndControl.Memory._8b[def_IndexTimeFrame];
070.                m_IndControl.Mode = memMode;
071.             }
072.             SendEventCustom(m_IndControl.Mode != C_Controls::ePlay ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY);
073.          }
074. //+------------------------------------------------------------------+
075. inline void UpdateIndicatorControl(void)
076.          {
077.             double Buff[];
078.                                  
079.             if (m_IndControl.Handle == INVALID_HANDLE) return;
080.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
081.             {
082.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
083.                   m_IndControl.Memory.dValue = Buff[0];
084.                if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
085.                   m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
086.             {
087.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
088.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
089.                m_IndControl.Memory._8b[7] = 'D';
090.                m_IndControl.Memory._8b[6] = 'M';
091.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
092.             }
093.          }
094. //+------------------------------------------------------------------+
095.       void SweepAndCloseChart(void)
096.          {
097.             long id;
098.             
099.             if ((id = ChartFirst()) > 0) do
100.             {
101.                if (ChartSymbol(id) == def_SymbolReplay)
102.                   ChartClose(id);
103.             }while ((id = ChartNext(id)) > 0);
104.          }
105. //+------------------------------------------------------------------+
106. inline int RateUpdate(bool bCheck)
107.          {
108.             static int st_Spread = 0;
109. 
110.             st_Spread = (bCheck ? (int)macroGetTime(m_MemoryData.Info[m_Infos.CountReplay].time) : st_Spread + 1);
111.             m_Infos.Rate[0].spread = (int)(def_MaskTimeService | st_Spread);
112.             CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
113.             
114.             return 0;
115.          }
116. //+------------------------------------------------------------------+
117. inline void CreateBarInReplay(bool bViewTick)
118.          {
119.             bool   bNew;
120.             double dSpread;
121.             int    iRand = rand();
122. 
123.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
124.             {
125.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
126.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
127.                {                  
128.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
129.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
130.                   {
131.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
132.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
133.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
134.                   {
135.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
136.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
137.                   }
138.                }
139.                if (bViewTick)
140.                   CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
141.                RateUpdate(true);
142.             }
143.             m_Infos.CountReplay++;
144.          }
145. //+------------------------------------------------------------------+
146.       void AdjustViewDetails(void)
147.          {
148.             MqlRates rate[1];
149. 
150.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
151.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
152.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
153.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
154.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
155.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
156.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
157.             if (rate[0].close > 0)
158.             {
159.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
160.                   m_Infos.tick[0].last = rate[0].close;
161.                else
162.                {
163.                   m_Infos.tick[0].bid = rate[0].close;
164.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
165.                }               
166.                m_Infos.tick[0].time = rate[0].time;
167.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
168.             }else
169.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
170.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
171.          }
172. //+------------------------------------------------------------------+
173.       void AdjustPositionToReplay(void)
174.          {
175.             int nPos, nCount;
176.             
177.             if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return;
178.             nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider);
179.             for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread);
180.             if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1);
181.             while ((nPos > m_Infos.CountReplay) && def_CheckLoopService)
182.                CreateBarInReplay(false);
183.          }
184. //+------------------------------------------------------------------+
185.       void WaitIndicatorLoad(const string szArg, const bool ViewCtrl = true)
186.          {
187.             Print("Waiting for ", szArg);
188.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, szArg) == INVALID_HANDLE))
189.             {
190.                if (ViewCtrl) CheckIndicatorControl();
191.                Sleep(100);
192.             }
193.          }
194. //+------------------------------------------------------------------+
195.    public   :
196. //+------------------------------------------------------------------+
197.       C_Replay()
198.          :C_ConfigService()
199.          {
200.             Print("************** Market Replay Service **************");
201.             srand(GetTickCount());
202.             SymbolSelect(def_SymbolReplay, false);
203.             CustomSymbolDelete(def_SymbolReplay);
204.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
205.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
206.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
207.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
208.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
209.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
210.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TICKS_BOOKDEPTH, 1);
211.             SymbolSelect(def_SymbolReplay, true);
212.             m_Infos.CountReplay = 0;
213.             m_IndControl.Handle = INVALID_HANDLE;
214.             m_IndControl.Mode = C_Controls::ePause;
215.             m_IndControl.Position = 0;
216.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
217.          }
218. //+------------------------------------------------------------------+
219.       ~C_Replay()
220.          {
221.             SweepAndCloseChart();
222.             IndicatorRelease(m_IndControl.Handle);
223.             SymbolSelect(def_SymbolReplay, false);
224.             CustomSymbolDelete(def_SymbolReplay);
225.             Print("Finished replay service...");
226.          }
227. //+------------------------------------------------------------------+
228.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
229.          {
230.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
231.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
232.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
233.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
234.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
235.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
236.             SweepAndCloseChart();
237.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
238.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
239.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
240.             else
241.                Print("Apply template: ", szNameTemplate, ".tpl");
242. 
243.             return true;
244.          }
245. //+------------------------------------------------------------------+
246.       bool InitBaseControl(const ushort wait = 1000)
247.          {
248.             int handle;
249.             
250.             Sleep(wait);
251.             AdjustViewDetails();
252.             Print("Loading Control Indicator...");
253.             if ((handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
254.             ChartIndicatorAdd(m_Infos.IdReplay, 0, handle);
255.             IndicatorRelease(handle);
256.             WaitIndicatorLoad("Market Replay Control", false);
257.             SendEventCustom();
258.             WaitIndicatorLoad("Indicator Mouse Study");
259.             UpdateIndicatorControl();
260.             SendEventCustom();
261. 
262.             return def_CheckLoopService;
263.          }
264. //+------------------------------------------------------------------+
265.       bool LoopEventOnTime(void)
266.          {         
267.             int iPos, iCycles;
268.             MqlBookInfo book[1];
269.             ENUM_BOOK_TYPE typeMsg, memBook;
270.             
271.             book[0].price = 1.0;
272.             book[0].volume = 1;
273.             book[0].type = BOOK_TYPE_BUY_MARKET;
274.             CustomBookAdd(def_SymbolReplay, book, 1);
275.             SendEventCustom();
276.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
277.             {
278.                UpdateIndicatorControl();
279.                CheckIndicatorControl();
280.                Sleep(200);
281.             }
282.             m_MemoryData = GetInfoTicks();
283.             AdjustPositionToReplay();
284.             iPos = iCycles = 0;
285.             SendEventCustom(memBook = BOOK_TYPE_BUY);
286.             book[0].type = BOOK_TYPE_BUY;
287.             CustomBookAdd(def_SymbolReplay, book, 1);
288.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
289.             {
290.                if (m_IndControl.Mode == C_Controls::ePause) return true;
291.                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);
292.                if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != book[0].type)
293.                if ((typeMsg = (iPos >= 60000 ? BOOK_TYPE_BUY_MARKET : BOOK_TYPE_BUY)) != memBook)
294.                   SendEventCustom(memBook = typeMsg);
295.                {
296.                   book[0].type = typeMsg;
297.                   CustomBookAdd(def_SymbolReplay, book, 1);
298.                }
299.                CreateBarInReplay(true);
300.                while ((iPos > 200) && (def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePause))
301.                {
302.                   Sleep(195);
303.                   iPos -= 200;
304.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks);
305.                   UpdateIndicatorControl();
306.                   CheckIndicatorControl();
307.                   iCycles = (iCycles == 4 ? RateUpdate(false) : iCycles + 1);
308.                }
309.             }
310. 
311.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
312.          }
313. };
314. //+------------------------------------------------------------------+
315. #undef def_SymbolReplay
316. #undef def_CheckLoopService
317. #undef def_MaxSlider
318. //+------------------------------------------------------------------+

Código fonte do arquivo C_Replay.mqh

Observe que todas e absolutamente todas, as linhas que se encontram riscadas deverão ser removidas do código. Mas vamos entender, por que tantas linhas foram removidas do código.

O primeiro ponto a ser observado é a linha 23. Como você observou no tópico anterior, NÃO DEVEMOS USAR UM HANDLE ESTÁTICO. Por isto, este valor de handle se tornou obsoleto. Por conta disto, diversas partes, que faziam alguma referência a ele foram removidas. Mas você também pode observar, que todo um procedimento, foi removido do código. Estou falando do UpdateIndicatorControl, que pode ser notado entre as linhas 75 a 93. Sendo assim, toda e qualquer referência a ele, também foi removido junto. Com isto temos que, de alguma forma, substituir o que antes era executado por este procedimento UpdateIndicatorControl. Mas em breve iremos de fato chegar neste ponto.

Mas antes de chegar lá. Vamos dar uma olhada rápida em duas funções. A primeira é a InitBaseControl, está se inicia na linha 243. Note que agora temos algumas pequenas mudanças, nesta função. Tais mudanças, tem como objetivo, melhorar a experiência do usuário da aplicação, ao mesmo tempo, padronizar a inicialização dos indicadores. Vamos então ver o que está acontecendo aqui.

Entre as linhas 253 e 255, tentamos carregar o indicador de controle. Agora atenção ao seguinte fato. Esta colocação do indicador, não acontece instantaneamente. Ela tem uma pequena latência na sua execução. Então antes de executar a linha 257, precisamos certificar, que o indicador de controle se encontra no gráfico. Isto é feito na chamada da linha 253. Veja que os nomes presentes nas linhas 256 e 258, são os nomes dos indicadores que estaremos de fato esperando para serem carregados. Estas chamadas são executadas, na linha 185. Deste momento em diante, a coisa começa a ficar interessante. Então preste bastante atenção, caso contrário, você ficará a ver navios.

O laço na linha 188, irá de fato aguardar que o indicador informado, tenha sido carregado no gráfico. Quando a linha 256, pede para aguardar o carregamento do indicador de controle, o teste da linha 187 evitará checarmos o indicador de controle. No entanto, quando a linha 255, pede para aguardar o indicador de mouse ser carregado, o teste da linha 190, fará a checagem do buffer do indicador de controle. Mas por que? O motivo, é que o usuário possa a vim a trocar o tempo gráfico. Mas para entender melhor, vamos para a linha 52.

Nesta linha 52, temos o procedimento que fará a leitura do buffer do indicador de controle. Agora vem a parte interessante. Temos aqui duas variáveis estáticas. Elas estão na linha 54 e 55. Mas neste exato momento, a que nos interessa é a declarada na linha 54. Note que iniciamos ela em zero. Agora se a linha 58, onde tentamos ler o buffer do indicador de controle, retornar um valor menor que zero. Indica que o indicador foi removido do gráfico. Como o usuário, não poderá recolocar ele novamente no gráfico e precisamos dele, fechamos o gráfico. Ao fazer isto a aplicação será encerrada. Por isto, não podemos checar o indicador de controle, quando ele está sendo aguardado.

Então na linha 60, testamos se o valor do indicador de controle é maior, que o da posição analisada no serviço. Se este é o caso, indica que temos que fazer um avanço rápido, assim que o usuário der play. Mas a parte que realmente nos interessa, é o teste na linha 64. Este teste é o calcanhar de aquiles da trava de tempo. A mesma trava que expliquei no tópico anterior. Se o usuário não modificou o tempo gráfico, este teste será verdadeiro.Neste caso não fazemos nada.

Agora se este teste for falso, na linha 69, vamos armazenar o novo tempo gráfico, e na linha 70 vamos recuperar o último status do indicador. Isto por que na linha 72, chamaremos um outro procedimento. Assim sendo, temos que ir para a linha 37. Agora que a coisa realmente fica interessante. Pois é justamente aqui, que pedimos ao MetaTrader 5, disparar os eventos no gráfico. O fato de separar estas duas coisas, se deve justamente pelo motivo, de que em alguns momentos apenas desejamos disparar os eventos. E em outros, o que realmente desejamos, é verificar e atestar, se ocorreu alguma alteração nos valores do indicador de controle.

Note que todo o conteúdo presente no procedimento SendEventCustom, já existia no código da versão anterior. Por este motivo, não vejo necessidade de explicar o que está acontecendo aqui. Mesmo por que, o código é muito simples e direto. Com isto, creio ter explicado, de forma bastante clara o que começamos a ter dentro do código. Mas ainda falta falar, sobre as mudanças ocorridas na função LoopEventOnTime. Apesar das mudanças, não serem realmente drásticas e mudarem completamente a forma da função ser executada. Nela fica mais claro, o motivo da separação, do antigo procedimento UpdateIndicatorControl, em dois outros procedimentos. Então acompanhe a explicação, pois ela será bem rápida.

Na linha 275, pedimos ao MetaTrader 5, para enviar os eventos customizados para nos. Isto fará com que o indicador tanto de mouse, quando de controle sejam corretamente ajustados para começarmos a utilizar o sistema. Apesar de que esta linha 275, poderia ser descartada na primeira execução. Mas depois que for dado o primeiro play, precisamos que ela seja executada, para que os dados sejam revisto, pois ela será executada novamente por conta que entramos no modo pausado.

Já na linha 279, não precisamos enviar nenhum tipo de evento, mas precisamos observar, o que acontece no indicador de controle. Assim que o usuário der play, o laço da linha 276 será encerrado, dando inicio ao processo de simulação e replay.

Então na linha 285, fazemos um novo pedido de envio de eventos customizados. Mas desta vez, é para permitir que o indicador de mouse, possa apresentar o tempo restante da barra.

Outra pequena diferença, que também se dá, está no conjunto das linhas 293 e 294. No entanto esta é bastante simples de ser compreendida, já que o trabalho é justamente para mudar o status do indicador de mouse. Isto para que possamos ser informados que o ativo entrou em leilão, ou saiu do mesmo.

E a última diferença está na linha 306, onde checamos se o tempo gráfico foi modificado. Se isto ocorrer, os indicadores serão recolocados como expliquei no início deste tópico.

E com isto, temos todas as mudanças que precisavam ser feitas. No vídeo abaixo, você pode ver, como o sistema estará se comportando agora. Isto quando é efetuada a mudança do tempo gráfico.




Conclusão

Nestes dois últimos artigos, mostrei e expliquei como é importante, o fato de que você pratique e tente levar a linguagem de programação ao seu extremo. Mesmo que nestes dois artigo, talvez muitos achem que não houve de fato algum aprendizado. Você não deve necessariamente pensar desta forma. Tenho certeza de que muitos, imaginavam que o que expliquei aqui, não era de fato possível. Ou se quer viável de ser feito, mantido ou construído. No entanto, antes de realmente tentar modificar o código principal. Mostrei que é preciso que você, pense primeiro em uma solução hipotética. Depois construa um código, que seja o mais simples, quanto for possível ser feito, a fim de testar a sua hipótese.

Mas o principal ensinamento, é o de nunca desistir na primeira tentativa. Se não deu certo na primeira tentativa. Mude a forma com as coisas estão sendo feitas, mas sempre tentando testar a mesma hipótese. E foi isto que ocorreu. Eu desejava colocar no buffer do indicador, algum tipo de informação que pudesse me dizer, de forma clara e segura, quando o tempo gráfico foi alterado. Fazer isto dentro do gráfico era simples. Então a hipótese, era: Será que é possível, fazer com que o serviço consiga perceber esta mudança? A primeira tentativa de implementação, estava falhando. Mas ainda assim, podíamos ler o valor que indicava o tempo gráfico. No entanto, este valor representava, exatamente o tempo gráfico, do momento em que o indicador era colocado pela primeira vez no gráfico.

Neste ponto, é que nasce o conceito. Se eu simplesmente tivesse desistido, ao contrário de tentar uma nova modelagem, onde o handle, ou manipulador, fosse obtido a cada chamada. Teria deixando de notar a possibilidade de acessar os dados reais, que estariam sendo modificados pelo indicador durante cada chamadas. Mas ao mudar a modelagem, abrimos uma nova porta, mostrando que podemos ir muito mais além do que muitos acham possível ser feito. É assim que as coisas vão sendo construídas. Você, tem uma hipótese, a testa e mesmo quando ela não está dando exatamente o que era esperado. Mas ainda assim funcionando parcialmente. Você modifica a modelagem, a fim de que os dados sejam obtidos de forma diferente. Mas sempre seguindo a hipótese inicial. No anexo, você terá acesso aos executáveis necessários para usar o replay/simulador.

No próximo artigo, vamos começar a ver outras coisas, que ainda precisamos colocar na aplicação de replay/simulador. Então nos vemos lá.

Arquivos anexados |
Versxo_Demo.zip (247.04 KB)
Gerenciador de riscos para trading algorítmico Gerenciador de riscos para trading algorítmico
Os objetivos deste artigo são: demonstrar a necessidade obrigatória de um gerenciador de riscos, adaptar os princípios de controle de risco para trading algorítmico em uma classe específica, permitindo que todos possam comprovar, de forma independente, a eficácia da abordagem de normalização de risco no day trading e em investimentos nos mercados financeiros. Neste artigo, exploraremos em detalhes a criação de uma classe de gerenciador de riscos para trading algorítmico, continuando o tópico abordado no artigo anterior sobre o gerenciador de riscos para trading manual.
Do básico ao intermediário: União (I) Do básico ao intermediário: União (I)
Neste artigo começaremos a ver o que seria uma união. Aqui faremos a lição de casa, experimentando as primeiras construções em que uma união poderia ser utilizada. Apesar de tudo, o que será visto aqui, é apenas a parte básica de todo um conjunto de conceitos e informações que ainda serão melhor exploradas em artigos futuros. 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.
Redes neurais de maneira fácil (Parte 90): Interpolação Frequencial de Séries Temporais (FITS) Redes neurais de maneira fácil (Parte 90): Interpolação Frequencial de Séries Temporais (FITS)
Ao estudarmos o método FEDformer, abrimos uma porta para a área de representação de séries temporais no domínio da frequência. No novo artigo, continuaremos o tema iniciado, e analisaremos um método que permite não apenas conduzir uma análise, mas também prever estados futuros no domínio frequencial.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 24): Médias Móveis Técnicas do MQL5 Wizard que você deve conhecer (Parte 24): Médias Móveis
Médias Móveis são um indicador muito comum, usado e compreendido pela maioria dos traders. Exploramos possíveis casos de uso que podem não ser tão comuns dentro dos Expert Advisors montados no MQL5 Wizard.