English Русский 中文 Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay (Parte 31): Projeto Expert Advisor - Classe C_Mouse (V)

Desenvolvendo um sistema de Replay (Parte 31): Projeto Expert Advisor - Classe C_Mouse (V)

MetaTrader 5Testador | 17 outubro 2023, 16:02
360 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay(Parte 30): Projeto Expert Advisor - Classe C_Mouse (IV), demonstrei como você pode modificar, adicionando ou adequando ao seu estilo um sistema de classes, a fim de testar um recurso ou modelo novo. Desta forma você não irá criando dependências em seu código principal, deixando ele sempre bem robusto, estável e confiável, já que qualquer novo recurso adicionado será sempre colocado dentro do sistema principal somente depois que já estiver completamente adequado o modelo que você estará criando. A grande questão em se desenvolver um sistema de replay / simulador, e talvez esta seja de fato a questão que torna este trabalho tão desafiador. É criar mecanismos que seja o mais próximo, para não dizer idêntico, ao sistema que iremos usar quando estivermos em uma conta REAL. Não faz muito sentido você ter um sistema a fim de criar ou executar um replay / simulação, se no momento em que for usar a conta real, não vim a ter os mesmos recursos presentes.

Observando o sistema da classe C_Mouse, e das classes de estudo, mostrados nos artigos anteriores. Pode-se notar que durante o uso no mercado real, seja conta demo ou conta real, o cronometro sempre irá lhe dizer quando a próxima barra iniciará. Mas ao usar o sistema de replay / simulador, não contamos com isto. O que nos surge é uma mensagem. Este tipo de quebra de simetria, pode a principio parecer ser algo sem muito valor. Mas se você permitir que coisas sem valor vão se acumulando, sem você as corrigir, ou as remover, no final você terá um monte de tralha totalmente inútil e que apenas irá lhe atrapalhar nas questões que você precisa de fato resolver. Desenvolver uma forma de colocar o cronometro, de modo que durante um replay / simulação, ele consiga nos dizer quanto tempo falta, pode parecer a principio uma tarefa simples e de rápida solução. Muitos iriam simplesmente tentar adaptar e usar o mesmo sistema que é usado quando temos o servidor de negociação ao nosso lado. Mas aqui mora um ponto que muitos talvez não se atentem ao  pensar em tal solução. Quando você está fazendo um replay, e isto para não falar do fato da simulação, o relógio não funciona da mesma forma. Isto por alguns motivos que posso enumerar logo de cara:

  • O replay sempre irá se referir ao passado. Assim o relógio da plataforma, ou do computador, não é de maneira alguma adequado para marcar a passagem do tempo;
  • Quando estamos executando um replay / simulação, podemos adiantar, pausar ou até mesmo regredir o tempo. Este último caso não é mais possível, e isto vem de longa data e por vários motivos que foram explicados no decorrer dos artigos. Mas ainda assim, você poderá adiantar ou pausar o sistema. Então cronometrar o tempo, da mesma forma como fazemos quando o servidor de negociação esta do nosso lado, deixa de ser adequado.

Para você ter uma ideia, de com o que estamos de fato lidando e o quanto colocar um cronometro no sistema de replay / simulação, pode vim a ser complicado. Veja a figura 01.

Figura 01

Figura 01 - Cronometro em um mercado real

Nesta figura 01, vemos como o cronometro que indica em que momento uma nova barra irá surgir no gráfico funciona. Isto forma bem resumida. Notem que de tempos em tempos, teremos a geração de um evento OnTime. Este irá disparar o evento Update que irá fazer a atualização do valor do cronometro. Assim podemos visualizar o tempo restante, para que a nova barra surja. Mas para que a função Update saiba qual o valor a ser apresentado, ela pergunta a função GetBarTime quanto tempo ainda irá demorar para que a barra apareça. Mas GetBarTime fará uso da função TimeCurrent que não é executada no servidor, mas que irá realmente capturar a hora local no servidor. Desta maneira, podemos saber quanto tempo faz que o servidor disparou a última barra e sobre este valor podemos calcular quanto tempo ainda resta até que a nova barra surja. Esta é a parte fácil da história, já que não precisamos nos preocupar se o sistema está pausado, ou avançou uma determinada quantidade de tempo. Isto acontecerá quando usamos um ativo que tem os dados vindos diretamente do servidor de negociação. Mas quando estamos lidando com replay / simulação, a coisa se torna bem mais complicada. O grande problema, não é o fato de estarmos lidando com replay / simulação. O problema é bolar alguma forma de quando estivermos fazendo um replay ou simulação, possamos burlar a chamada TimeCurrent. Já que é neste ponto em que todo o problema acontece. Mas isto deve ser feito com o mínimo de modificações. Queremos apenas burlar o sistema da chamada TimeCurrent. No entanto, quando estivermos a um servidor, queremos que o sistema funcione com mostrado na figura 01.

Felizmente existe uma maneira, usando o MetaTrader 5, de se fazer isto, com o mínimo de dificuldade possível e causando um numero consideravelmente baixo de modificações ou adições ao código já implementado. Mostrar como podemos fazer isto, é que será o tema deste artigo.


Planejamento

Planejar como fazer isto talvez seja a parte simples da história. Já que vamos simplesmente enviar o valor de tempo calculado pelo serviço de replay / simulador para dentro sistema. Esta é a parte fácil, Bastará usar uma variável global de terminal para isto, e pronto. Já mostrei nesta sequencia como fazemos pra usar tais variáveis, isto quando apresentei o indicador de controle a fim de poder dizer ao serviço, o que o usuário está desejando fazer. Muitos acham e acreditam que podemos apenas enviar dados do tipo double por meio destas variáveis. Mas estas mesmas pessoas, se esquecem de um fato: Números binários são apenas números binários. Eles de forma alguma representam algum tipo de informação, que não seja zeros e uns. Assim você pode transmitir qualquer tipo de informação por estas variáveis globais de terminal. Desde é claro, você consiga colocar os bits de uma maneira lógica, para depois poder recompor a informação, quando for preciso saber o que foi transmitido. Felizmente o tipo DateTime, diferente do que muitos pensa, é na verdade um valor uLong, onde temos o uso de 64 bits, ou seja 8 bytes de informação são necessários para acomodar um valor DateTime, onde teremos a representação de uma data e tempo completos incluindo Ano, mês, dia, hora, minuto e segundos. Tudo o que precisamos para burlar a chamada TimeCurrent, já que a mesma utiliza e retorna este mesmo valor em 8 bytes. Já que o tipo Double utiliza precisamente 64 bits para transferir a informação dentro da plataforma, está ai a nossa solução.

Mas nem tudo são flores. Temos alguns probleminhas que serão melhor compreendidos durante a explicação da implementação deste sistema. Apesar de ser bastante simples, e fácil de ser construído, tem alguns pequenos percalços relacionados ao modo como o serviço terá que ser modificado a ponto de conseguir suprir a classe, com a informação que poderá ser apresentada no cronometro. No final da construção teremos o seguinte resultado visto na figura 02:

Figura 02

Figura 02 - Cronometro genérico.

Este cronometro genérico, será capaz de responder adequadamente e de maneira totalmente transparente, para que o usuário venha a ter uma experiência no replay / simulação muito próximo que teria em uma conta demo / real. É justamente este o intuito quando se pensa em criar tal sistema. A de que a experiência seja a mesma, para que o usuário saiba exatamente como agir, não precisando reaprender do zero a usar o sistema.


Implementação

Esta fase é a parte mais interessante deste sistema. Neste momento, veremos como podemos burlar a chamada TimeCurrent, de maneira adequada. O usuário não deve perceber se esta usando um replay ou se esta em contato com o servidor. Para começar a implementar o sistema, precisamos adicionar, conforme deve ter ficado claro nos tópicos anteriores, uma nova variável global de terminal. Ao mesmo tempo que precisamos de meios corretos para enviar a informação de DateTime via variável Double. Para isto usaremos o arquivo de cabeçalho Interprocess.mqh. Adicionando os seguintes pontos:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_SymbolReplay                "RePlay"
#define def_GlobalVariableReplay        def_SymbolReplay + " Infos"
#define def_GlobalVariableIdGraphics    def_SymbolReplay + " ID"
#define def_GlobalVariableServerTime    def_SymbolReplay + " Time"
#define def_MaxPosSlider                400
#define def_ShortName                   "Market " + def_SymbolReplay
//+------------------------------------------------------------------+
union u_Interprocess
{
   union u_0
   {
      double  df_Value;       // Value of the terminal global variable...
      ulong   IdGraphic;      // Contains the Graph ID of the asset...
   }u_Value;
   struct st_0
   {
      bool    isPlay;         // Indicates whether we are in Play or Pause mode...
      bool    isWait;         // Tells the user to wait...
      ushort  iPosShift;      // Value between 0 and 400...
   }s_Infos;
   datetime   ServerTime;
};
//+------------------------------------------------------------------+

Neste ponto definimos o nome da variável global de terminal que será usada para comunicação. Já neste, definimos a variável que será usada para acessar os dados no formato datetime. Feito isto, partimos para a classe C_Replay, e vamos logo de cara adicionar no destructor da classe, a seguinte linha:

~C_Replay()
   {
      ArrayFree(m_Ticks.Info);
      ArrayFree(m_Ticks.Rate);
      m_IdReplay = ChartFirst();
      do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         ChartClose(m_IdReplay);
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
      CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
      CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
      CustomSymbolDelete(def_SymbolReplay);
      GlobalVariableDel(def_GlobalVariableReplay);
      GlobalVariableDel(def_GlobalVariableIdGraphics);
      GlobalVariableDel(def_GlobalVariableServerTime);
      Print("Finished replay service...");
   }

Adicionando esta linha, garantimos que quando o serviço de replay / simulação for encerrado, removeremos a variável global de terminal, responsável por repassar o valor de tempo para dentro do cronometro. Agora, precisamos fazer com que esta variável global de terminal, seja de fato criada, no momento correto. Não devemos de maneira alguma, criar a mesma durante o procedimento de loop. Já que este pode ser disparado em momentos diferentes. E o sistema, assim que entrar, precisa já ter acesso ao conteúdo da variável global de terminal. De certa forma, cogitei fazer uso de um sistema de criação similar ao do indicador controlador. Mas já que neste momento podemos ter ou não o Expert Advisor presente no gráfico, iremos, pelo menos por enquanto, fazer com que a variável de terminal responsável pelo cronometro, seja criada pelo serviço. Assim teremos um controle minimamente adequado ao que estamos fazendo. Talvez futuramente isto venha a mudar. Então o ponto de criação é visto no código abaixo:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = m_Ticks.Info[m_ReplayCount].time;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

Em meio a todo este emaranhado de linhas, temos este ponto que inicializa o valor da variável. Isto para que o sistema já tenha um valor para qual trabalhar, ou para que possamos testar se o replay / simulador esta de fato sincronizado. Logo em seguida temos esta chamada que faz a criação e inicialização da variável global de terminal. Este código de inicialização, pode ser visto logo abaixo:

void CreateGlobalVariable(const string szName, const double value)
   {
      GlobalVariableDel(szName);
      GlobalVariableTemp(szName);     
      GlobalVariableSet(szName, value);
   }

Note que o código de criação e inicialização é bem direto. Ele é montado de maneira que se a plataforma for encerrada e for feito o pedido para se armazenar as variáveis globais de terminal, as variáveis usadas no sistema de replay / simulador não serão armazenadas. Isto não é nenhum tipo de problema, já que não queremos ou precisamos que tais valores sejam armazenados e recuperados depois. Com isto, já podemos começar a cogitar a leitura da variável pelo cronometro. Mas se isto for feito neste momento, o valor contido ali será sempre o mesmo, não tendo necessariamente nenhuma utilidade prática para nos. Seria como se o serviço de replay / simulador estivesse pausado. Queremos no momento em que ele esteja ativo, ou seja, em modo play, que o cronometro se mova. Assim teremos uma simulação do que seria os requerimentos a função TimeCurrent, onde recebemos os dados pertinentes da hora e data encontrados no servidor. Para que isto aconteça, precisamos que a cada segundo, aproximadamente, o valor da variável global mude. O correto é termos um temporizador para isto. Mas já que não podemos nos dar ao luxo de fazer isto, precisamos de um outro meio para gerar tal mudança no valor da variável global.

Para tentar cronometrar as coisas. Vamos precisar fazer alguma adições ainda na classe C_Replay. Estas podem ser vista no código abaixo:

bool LoopEventOnTime(const bool bViewBuider)
   {
      u_Interprocess Info;
      int iPos, iTest, iCount;
                                
      if (!m_Infos.bInit) ViewInfos();
      iTest = 0;
      while ((iTest == 0) && (!_StopFlag))
      {
         iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
         iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
         iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
         if (iTest == 0) Sleep(100);
      }
      if ((iTest < 0) || (_StopFlag)) return false;
      AdjustPositionToReplay(bViewBuider);
      Info.ServerTime = m_Ticks.Info[m_ReplayCount].time;
      GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
      iPos = iCount = 0;
      while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
      {
         iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
         CreateBarInReplay(true);
         while ((iPos > 200) && (!_StopFlag))
         {
            if (ChartSymbol(m_IdReplay) == "") return false;
            GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            if (!Info.s_Infos.isPlay) return true;
            Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            Sleep(195);
            iPos -= 200;
	    iCount++;
            if (iCount > 4)
            {
               iCount = 0;
               GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
               Info.ServerTime += 1;
               GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
            }
         }
      }                               
      return (m_ReplayCount == m_Ticks.nTicks);
   }                               

Adicionamos uma nova variável, que nos ajudará a tentar aproximar um pouco do tempo onde uma nova barra irá se iniciar. Mas isto não será feito de qualquer forma. Já que podemos avançar o tempo, precisamos fazer com que o valor indicado pelo nosso " servidor " seja reajustado. Isto será feito neste momento, onde a função já estará indicado um novo ponto de observação. Mesmo que fiquemos durante um tempo em modo pausa, precisamos garantir que o valor ainda será o adequado. O real problema, se dá justamente quando iremos fazer o ajuste do temporizador. Não podemos simplesmente capturar um valor qualquer. O sistema poderá estar levemente adiantado ou atrasado com relação ao tempo real visto no relógio do computador. Por conta disto, tentamos neste primeiro momento nos aproximar do tempo correto. Temos um temporizador embutido no gerador de barras. Usaremos este para nos guiar. Ele dá um tick a cada aproximadamente 195 milissegundos, isto nos faz aproximar de uma contagem de 5 unidades. Já que começamos com o valor zero, testaremos se o valor do contador é maior que 4. Quando ocorrer incrementaremos uma unidade de tempo, ou seja 1 segundo, e este valor será colocado na variável global de terminal, para que o restante do sistema possa usar ele. Então todo o ciclo, se repetirá.

Você pode imaginar que isto nos faz saber quando uma nova barra aparecerá. Sim esta é a ideia, mas existem pequenas variações entre um tempo e outro. Mas conforme elas vão se acumulando, a coisa acaba ficando bastante fora de sincronia. Assim precisamos fazer com que a sincronia se mantenha o mais próximo possível de algo aceitável. Não que de fato atingiremos um ponto perfeito. Mas na grande maioria dos casos, podemos nos aproximar bastante. Para isto modificaremos um pouco mais o procedimento acima, de maneira a garantir um aumento da sincronia entre a barra e o cronometro. As mudanças que proponho, são vista no fragmento abaixo:

//...

   Sleep(195);
   iPos -= 200;
   iCount++;
   if (iCount > 4)
   {
      iCount = 0;
      GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
      Info.ServerTime += 1;
      Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time);
      GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
   }

//...

Você pode até pensar que isto é maluquice. De certa forma devo concordar, que é um pouco de insanidade da minha parte. Mas adicionando esta linha em particular, podemos fazer com que a sincronia se mantenha dentro de níveis aceitáveis. Caso o ativo tenha uma boa liquidez a ponto de que os negócios saiam em um período relativamente curto de tempo, de preferencia em menos de 1 segundo, teremos um sistema bastante sincronizado. Podendo se aproximar enormemente de um sistema quase perfeito. Mas isto é somente conseguido se o ativo realmente tiver uma liquidez adequada. Você pode estar se perguntando: Em sã consciência, que maluquice é esta, que está acontecendo nesta linha em destaque ?!?! Vamos pensar um pouco: A cada 5 ciclos de aproximadamente 195 milissegundos, teremos a execução do código a fim de atualizar o cronometro. Isto faz com que a taxa de atualização seja por volta de 975 milissegundos, ou seja fica faltando 25 milissegundos a cada ciclo. Mas este valor não é de fato constante. As vezes, ele é um pouco maior e outras vezes, um pouco menor. Não é adequado tentar ajustar a sincronia usando um novo comando sleep a fim de fazer o sistema ficar parado cobrindo esta diferença. A primeira vista isto funcionaria. Mas conforme o tempo fosse passando, esta micro diferença acabaria por se tornar suficientemente grande a ponto de que todo o sistema ficaria fora de sincronia. Para resolver este problema, fazemos algo um pouco diferente. Ao invés de tentar cravar a temporização, usamos a própria barra para gerar a sincronia. Quando a função CreateBarInReplay é executada ela sempre apontará para o ticket atual. Comparando o valor de tempo deste ticket com o valor de tempo que está na variável global, poderemos ter em alguns casos, um valor maior que uma unidade, no caso 1 segundo. Caso este valor seja inferior, ou seja, a variável Info.ServerTime esteja atrasada por conta dos 25 milissegundos acumulados no tempo. O valor presente no tempo do ticket será usado, ajustando assim a diferença e fazendo com que o cronometro se mantenha bem próximo do valor perfeito. Mas como informei no inicio da explicação, este mecanismo ajusta de forma automática o sistema caso o ativo que estamos usando, tenha um liquidez adequada. Se a negociação ficar muito tempo parada, saindo negócios a cada 5 ou 10 minutos, isto entre um negocio e outro, a precisão do sistema de cronometragem será abalada. Já que a cada segundo ele irá em média se atrasar em 25 milissegundos ( este é um valor médio, não é um valor exato ).

Feita esta parte. Podemos passar para a próxima parte. Que é no arquivo de cabeçalho C_Study.mqh, onde faremos com que o sistema informe os dados de maneira que tenhamos uma estimativa adequada, de quando surgirá uma nova barra no gráfico.


Adequando a classe C_Study

Para começar de fato as modificações. A primeira coisa a ser feita é a troca é vista logo abaixo:

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                             break;
         case eAuction    : m_Info.szInfo = "Auction";                                   break;
         case eInReplay   :
         case eInTrading  : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS);    break;
         case eInReplay   : m_Info.szInfo = "In Replay";                                 break;
         default          : m_Info.szInfo = "ERROR";
      }
      Draw();
   }

Notem que removemos a linha que se encontra riscada, e a subimos para um nível acima, de maneira que ela usufruirá do mesmo status e a mesma função usada durante o uso do sistema em um mercado físico. Assim nivelamos as coisa, ou seja você irá ter o mesmo comportamento em ambas situações, seja no replay / simulador, seja em uma conta demo / real. Agora que já fizemos isto, podemos ver a primeira modificação feita no código do procedimento GetBarTime. Este pode ser visto logo a seguir:

const datetime GetBarTime(void)
   {
      datetime dt;
      u_Interprocess info;
                                
      if (m_Info.Status == eInReplay)
      {
         if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX;
         dt = info.ServerTime;
      }else dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(GetInfoTerminal().szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

Aqui é onde a mágica acontece dentro do sistema de estudos. Na versão antiga desta mesma função, o cronometro era ajustado por esta chamada. Mas ela não é adequada para se trabalhada no sistema de replay / simulador. Por isto estamos criando um método para burlar justamente esta chamada, a fim de conseguir fazer com que o sistema consiga ter os mesmos conceitos, e informações, independente de onde ela esteja sendo usada. Por conta disto, adicionamos os seguintes pontos que são destacados em verde. Estes pontos serão usados durante o período que o código estiver em um gráfico, cujo ativo é o usado no replay. Observem que é algo simples, não contando com nenhuma coisa assim extraordinária. Apenas pegamos o valor informado e colocado na variável global e usamos como se ele estivesse vindo do servidor de negociação. Esta é a parte, onde de fato burlamos o sistema. Mas, porém, toda via, e entretanto, temos um problema aqui. Quando estivermos em um tempo gráfico baixo, onde o numero de negócios não for suficientemente alto a fim de que a construção das barras, se de de maneira adequada. E isto acontece especialmente quando os dados usados, contem um leilão no intraday ( caso muito comum em alguns ativos ) teremos uma falha. Tal falha se apresentará como um gap entre a informação que esta sendo apresentada e a visualizada. Não que o serviço tenha parado de fazer as atualizações. Mas o indicador não irá mostrar nenhum tipo de informação e isto lhe deixará no escuro, sem de fato saber o que está acontecendo. Mesmo que o ativo, não tenha entrado em leilão. Pode e é bastante comum em alguns pares negociados no mercado de forex, de somente termos uma negociação em um momento diferente do de abertura da barra. Isto pode ser visto nos arquivos do anexo, onde você irá notar que temos no inicio do dia, um gap entre o ponto de abertura da barra e o ponto onde realmente aconteceu um negócio. Durante aquela fase, não seria visto nenhum tipo de informação, dizendo o que estará acontecendo. Isto precisa ser de alguma forma corrigido. Seja pelo fato de termos um negócio fora do ponto onde o sistema espera. Seja pelo motivo de o ativo usado ter entrado em leilão. Precisamos realmente deixar as coisas o mais próximas da realidade.

Para resolver estas questões, temos que fazer duas mudanças no código da classe. Isto de forma mais imediata. A primeira modificação é vista logo abaixo:

void Update(void)
   {
      datetime dt;
                                
      switch (m_Info.Status)
      {
         case eCloseMarket:
            m_Info.szInfo = "Closed Market";
            break;
         case eInReplay   :
         case eInTrading  :
            dt = GetBarTime();
            if (dt < ULONG_MAX)
            {
               m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
               break;
            }
         case eAuction    :
            m_Info.szInfo = "Auction";
            break;
         default          :
            m_Info.szInfo = "ERROR";
      }
      Draw();
   }

Este código Update, visto acima, apesar de parecer estranho e complicado, é muito mais simples e simpático do que parece. O que temos aqui, é o seguinte cenário. Caso estejamos em um sistema de replay ou mesmo em um mercado real, e recebemos da função GetBarTime um valor de ULONG_MAX, teremos impresso a mensagem de leilão. Caso o valor seja menor que este ULONG_MAX, e isto sempre será verdadeiro em situações normais, teremos o valor do cronometro sendo impresso.

Com base nesta informação, já podemos voltar a função GetBarTime e a gerar os dados que precisamos para que a função Update imprima o dado correto no gráfico, para que o usuário saiba como as coisas estão sendo conduzidas. Assim a nova função GetBarTime pode ser vista no código a seguir:

const datetime GetBarTime(void)
   {
      datetime dt;
      u_Interprocess info;
      int i0 = PeriodSeconds();
                                
      if (m_Info.Status == eInReplay)
      {
         if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX;
         dt = info.ServerTime;
         if (dt == ULONG_MAX) return ULONG_MAX;
      }else dt = TimeCurrent();
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0)) + i0;

      return m_Info.Rate.time - dt;
   }

Este simpático código acima resolve completamente o nosso problema. Pelo menos por enquanto. Já que teremos de fazer a adições ao código do serviço. Mas por enquanto vamos entender o que está se passando aqui. No caso de um mercado físico onde usamos a função TimeCurrent, nada muda permanecemos da mesma forma. Isto a principio. Mas quando estamos em um sistema de replay, a coisa muda de forma bem peculiar. Então atenção, para entender como o sistema realmente consegue mostrar as coisas, independente do que esteja acontecendo com os dados do replay ou simulação. Caso o serviço coloque o valor ULONG_MAX, na variável global de terminal, ou se esta variável não for encontrada. Esta função GetBarTime irá retornar o valor ULONG_MAX. Assim o procedimento Update irá informar que estamos em modo leilão.  Isto é feito nestes pontos. Não será possível avançar mais no que diz respeito ao cronometro. Agora vem a parte interessante, que resolve o nosso segundo problema. Ao contrário de quanto estamos usando o sistema, em um ativo que está ligado ao servidor de negociação, onde sempre estaremos sincronizados. Quando estamos usando o replay / simulação, a coisa pode sair um pouco de controle, e podemos nos deparar com algumas situações meio quanto inusitadas. Para resolver este problema, usamos este calculo, que tanto serve para um mercado físico, quanto para o nosso sistema em desenvolvimento. O que estamos fazendo, neste calculo é substituindo o antigo método de saber qual foi o momento de abertura da barra atual. Assim conseguimos solucionar ambos os problemas que havia ao usar o replay / simulador.

Mas temos que voltar para a classe C_Replay, para fazer com que o sistema consiga indicar quando o ativo entrou em leilão. Esta parte é relativamente simples, já que tudo que temos que fazer é lançar o valor ULONG_MAX, dentro a variável global de terminal. Vejam que foi dito relativamente simples, já que temos outros problemas a vista. Mas vamos ver como realmente isto se dará na prática.


Adequando a classe C_Replay ao sistema de comunicação

A primeira coisa que iremos fazer na classe C_Replay, é modificar o seguinte código:

bool ViewReplay(ENUM_TIMEFRAMES arg1)
   {
#define macroError(A) { Print(A); return false; }
      u_Interprocess info;
                                
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
         macroError("Asset configuration is not complete, it remains to declare the size of the ticket.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
         macroError("Asset configuration is not complete, need to declare the ticket value.");
      if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
         macroError("Asset configuration not complete, need to declare the minimum volume.");
      if (m_IdReplay == -1) return false;
      if ((m_IdReplay = ChartFirst()) > 0) do
      {
         if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
         {
            ChartClose(m_IdReplay);
            ChartRedraw();
         }
      }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
      Print("Waiting for [Market Replay] indicator permission to start replay ...");
      info.ServerTime = ULONG_MAX;
      info.ServerTime = m_Ticks.Info[m_ReplayCount].time;
      CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value);
      info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
      ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
      CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
      while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);

      return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
#undef macroError
   }

Observe que removemos uma linha e no lugar dela colocamos uma outra. Exatamente isto, que fará com que ao iniciar o serviço de replay / simulador, e o ativo for lançado no gráfico, seja indicado a mensagem de leilão. Nos mostrando que o sistema agora está se comunicando de forma adequada. Já que antes de darmos play no sistema, devemos pensar que ele realmente se encontra em processo de leilão. Esta foi a parte fácil, agora vamos a parte complicada. Acontece que quando o serviço já esta em execução, precisamos saber se o ativo entrou ou não em leilão. Este tipo de coisa tem lá os seus motivos. O ativo pode entrar em leilão por diversos motivos e cada um deles pode ser completamente inesperado. Seja por que ocorreu uma variação muito maior do que de fato poderia acontecer. Seja por que o book simplesmente travou, por uma ordem acabou limpando o book completamente. Ou qualquer outro. O motivo do ativo ter entrado em leilão, de fato não nos interessa. O que nos interessa, é o que acontece quanto ele entra em leilão. Existem regras especificas que dizem como o leilão acontecerá. Mas a regra que mais nos importa é: Qual é o tempo mínimo que o ativo precisa ficar em leilão ?!?! Este é o ponto. Se este tempo for inferior a 1 minuto, com toda a certeza o ativo em um mercado real pode vim a entrar e sair do leilão sem que o sistema de replay / simulador possa de fato detectar isto. Ou melhor dizendo, não será possível percebermos esta variação, já que ela estará sempre ocorrendo dentro do menor prazo que pode ser usado para definir o tempo de uma barra.

O mecanismo mais simples para ser usado em um replay / simulação, a fim de detectar que o ativo entrou em leilão é justamente este. Verificar a diferença de tempo entre uma barra e outra. Caso esta diferença seja superior a 1 minuto, devemos informar ao usuário que o ativo acaba de entrar em processo de leilão, ficando assim suspenso por todo aquele período. Este tipo de mecanismo será bastante útil depois. Por enquanto vamos nos preocupar em apenas desenvolver e implementar o mesmo, deixando outras questões para um outro momento. Mas vamos ver como resolvermos este problema. Isto pode ser visto no código abaixo:

bool LoopEventOnTime(const bool bViewBuider)
   {
      u_Interprocess Info;
      int iPos, iTest, iCount;
                                
      if (!m_Infos.bInit) ViewInfos();
      iTest = 0;
      while ((iTest == 0) && (!_StopFlag))
      {
         iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
         iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
         iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
         if (iTest == 0) Sleep(100);
      }
      if ((iTest < 0) || (_StopFlag)) return false;
      AdjustPositionToReplay(bViewBuider);
      Info.ServerTime = m_Ticks.Info[m_ReplayCount].time;
      GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
      iPos = iCount = 0;
      while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
      {
         iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0);
         CreateBarInReplay(true);
         while ((iPos > 200) && (!_StopFlag))
         {
            if (ChartSymbol(m_IdReplay) == "") return false;
            GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            if (!Info.s_Infos.isPlay) return true;
            Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
            Sleep(195);
            iPos -= 200;
            iCount++;
            if (iCount > 4)
            {
               iCount = 0;
               GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
               if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else
               {
                  Info.ServerTime += 1;
                  Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time);
               };
               GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value);
            }
         }
      }                               
      return (m_ReplayCount == m_Ticks.nTicks);
   }                               

Notem que testamos a diferença de tempo, entre um ticket e outro. Caso esta diferença seja maior que 60 segundos, ou seja maior que o menor tempo de criação de uma barra. Iremos informar isto como sendo uma chamada de leilão, então o todo o sistema de replay / simulador irá indicar um leilão. Se a diferença de tempo for inferior ou igual a 60 segundos, significa que o ativo ainda permanece em atividade, e que o cronometro deverá se manter funcionado, conforme foi visto durante este artigo. Com isto concluímos mais uma etapa.


Conclusão

Aqui vimos como adicionar de uma maneira bastante prática, segura, robusta e eficaz o cronometro de indicação de surgimento de uma nova barra. Passamos por diversos momentos, onde a coisa parecia não ser possível. Mas nada de fato é impossível. Pode ser apenas um pouco mais difícil de ser superado, ou cujo objetivo é mais trabalhoso de ser alcançado. Mas sempre podemos encontrar um caminho adequado para solucionar os problemas. A principal coisa aqui, foi mostrar que temos sempre de tentar criar uma solução que quando for utilizada deva ser aplicável a todas as situações onde ela for exigida. Não faz sentido você programar algo para que o usuário, ou mesmo você aprenda e analise as possibilidades, e quando for aplicar tal modelo este precise usar ferramentas diferentes daquelas usadas para desenvolver a analise. Isto acaba desmotivando completamente o uso de uma das ferramentas, ou desmotivando a pesquisa. Então tenha sempre isto em mente: Se é para fazer algo, que esta seja feita de maneira adequada. Que não seja feita apenas para a fase de testes e quando você for usa-la na prática ela tenha um comportamento totalmente diferente.

Arquivos anexados |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Desenvolvendo um sistema de Replay (Parte 32): Sistema de Ordens (I) Desenvolvendo um sistema de Replay (Parte 32): Sistema de Ordens (I)
De todas as coisas desenvolvidas até aqui. Esta com toda a certeza, vocês também irão notar, e com o tempo irão concordar, que é a mais desafiadora de todas. O que temos de fazer é algo simples. Fazer com que o nosso sistema, simule o que um servidor de negociação efetua na prática. Isto de ter que implementar uma forma de simular, exatamente o que seria feito, pelo servidor de negociação, parece simples. Pelo menos nas palavras. Mas precisamos fazer isto de uma maneira, que para o usuário do sistema de replay / simulação, tudo venha a acontecer, de forma o mais invisível, ou transparente, possível.
Iniciando o VPS MetaTrader pela primeira vez - Instruções passo a passo Iniciando o VPS MetaTrader pela primeira vez - Instruções passo a passo
Para todos que usam Expert Advisors ou assinaturas de sinais, mais cedo ou mais tarde, será necessário um serviço de hospedagem confiável 24 horas por dia para a plataforma de negociação. Recomendamos o uso do VPS MetaTrader por vários motivos. Você pode pagar e gerenciar o serviço através da sua conta na MQL5.community.
Desenvolvendo um sistema de Replay (Parte 33): Sistema de Ordens (II) Desenvolvendo um sistema de Replay (Parte 33): Sistema de Ordens (II)
Vamos continuar o desenvolvimento do sistema de ordens. Mas você irá ver que iremos fazer uma reutilização massiva de coisas já vistas em outros artigos. Mesmo assim teremos um bônus neste artigo. Iremos desenvolver, primeiramente um sistema que consiga ser operado junto ao servidor de negociação real, seja usando uma conta demo, seja usando uma conta real. Vamos fazer uso massivo e extensivo da plataforma MetaTrader 5, para nos fornecer todo o suporte do qual precisaremos neste inicio de jornada
Redes neurais de maneira fácil (Parte 47): Espaço contínuo de ações Redes neurais de maneira fácil (Parte 47): Espaço contínuo de ações
Neste artigo, estamos ampliando o escopo das tarefas do nosso agente. No processo de treinamento, incluiremos alguns aspectos de gerenciamento de dinheiro e risco, que são partes integrantes de qualquer estratégia de negociação.