English Русский Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 25): Preparação para a próxima etapa

Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 25): Preparação para a próxima etapa

MetaTrader 5Testador | 8 setembro 2023, 09:53
520 2
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay - Simulação de mercado (Parte 24): FOREX (V) mostrei como poderíamos unir de uma forma bastante harmoniosa, dois mundos aparentemente diferentes. Um o mercado onde o preço de plotagem é o BID e o outro onde o preço de plotagem era o LAST. Mas o grande e verdadeiro trabalho ali, foi criar uma forma de conseguir simular, ou melhor dizendo, gerar algum movimento provável de movimentação do preço. Quando tudo que temos a nossa disposição, ou em mãos, seriam as barras, que preferencialmente devem representar o tempo gráfico de 1 minuto. De fato aquilo foi um desafio bastante interessante de ser solucionado. Mas aquela solução que foi mostrada como implementação, na verdade não é o único, e possível caminho, para alcançar aquele objetivo em particular. Mas já que a solução conseguiu dar cabo do trabalho, já considero aquela etapa superada. Pelo menos até que aquela solução não consiga resolver um determinado modelo especifico. Quando isto acontecer, iremos voltar e melhorar aquela solução que foi proposta, de forma que ela consiga abranger um modelo que ela não esteja conseguindo resolver.

Mas existem algumas coisas das quais precisamos ainda modificar. Se bem que na verdade, não iremos de fato fazer uma mudança. Iremos remover algumas coisas, que irão atrapalhar, e muito algumas coisas que inda iremos implementar. Uma destas coisa é voltar no tempo. A questão de voltar no tempo, utilizando o indicador de controle, é algo que deve ser removido do sistema. Além de termos que iniciar a construção da estrada necessária para que as futuras mudanças, possa ser de fato criadas e mantidas com um mínimo de esforço. Você pode ter achado, bastante interessante poder voltar no tempo, usando o indicador de controle. De fato devo concorda, é algo interessante, porém não é de fato prático. Mas apesar disto, até o momento não nos ter gerado, nenhum tipo de problema, o mesmo não acontecerá no momento em que determinadas coisas vierem a ser de fato implementadas. Pois o fato de podermos voltar no tempo, em diversos casos, irá e com toda a certeza nos gerar uma baita e tremenda dor de cabeça ao tentar solucionar os problemas que esta simples questão de voltar no tempo irá nos fornecer.

Remover este sistema de retorno no tempo, não é algo muito difícil de fato em ser feito. Sendo apenas um pouco chato, já que precisaremos adicionar alguns testes e verificações no indicador de controle. Mas ao mesmo tempo estou pensando seriamente em remover uma outra coisa do serviço. Mas isto irei decidir no decorrer do artigo. Além desta mudança  no indicador de controle, irei resolver algumas poucas questões que ainda precisam ser refinadas para deixar o serviço lisinho. Isto para que ele rode redondinho, sem muitos gargalos. Então me acompanhe no desenvolvimento que será mostrado neste artigo. A coisa em si será bastante interessante de ser vista e existirá muitas coisas que irei mostrar, e com toda a certeza irá lhe ajudar a aprender um pouco mais sobre programação e desenvolvimento de sistemas. Passemos então ao primeiro tópico deste artigo.


Restringindo o indicador de controle

Vamos começar criando algumas restrições no indicador de controle, de forma a impedir que o usuário possa voltar no tempo. E quando eu digo voltar no tempo, significa que uma vez ocorrido um determinado avanço, você não poderá utilizar o indicador de controle, de forma a retornar em uma posição anterior. Para fazer isto, você terá que encerrar o serviço de replay / simulação e iniciar todo o processo do inicio. Sei que isto soa muito desmotivador. Mas acreditem, é melhor fazer assim do que tentar resolver os problemas que irão aparecer no futuro, quando se tentar usar este recurso de retorno no tempo, irão ser gerados.

Gerar esta restrição não é complicado, mas é algo bastante chato de ser feito. Já que iremos precisar adicionar alguns testes no sistema. Estes tem que ser feitos, em pontos bastante específicos e de uma forma, a não gerar conflitos com outros pontos. Isto para não impedir, que o indicador possa fazer o seu trabalho. Vamos dividir esta tarefa em etapas. Isto para que a mudança e implementação, aconteça de maneira a conseguir cumprir o objetivo desejado.


Primeira etapa: Ligando e desligando os botões de ajuste fino.

Nesta etapa, a tarefa será relativamente simples. Consistindo em ligar e desligar o acesso aos botões de controle de ajuste fino. Estes botões são aqueles que ficam nos extremos da barra de controle. Você pode notar eles na imagem abaixo:

Figura 01

Figura 01 : Localização dos botões de ajuste fino

Estes botões servem para que possamos ajustar, de forma fina, a quantidade de avanço que queremos dar. Desta forma podemos adiantar ou regredir de forma bastante fina, uma dada quantidade de tempo, o que não é ruim. Mas já que queremos evitar que o usuário venha a voltar no tempo, precisamos primeiramente ocultar ou mostrar estes botões conforme venha a ser necessário ou não a presença deles. Para facilitar a você entender isto, e o que estaremos fazendo, pense no seguinte: Por que precisamos do botão a esquerda quando o sistema não avançou nenhuma posição ?!?! Ou por que precisamos do botão a direita quando o sistema esta em seu limite superior de avanço ?!?! Ou seja o replay / simulação estará criando e lançando os últimos tickets de um sistema de barras, precisamos de fato do botão da direita ?!?! Não faz sentido não é mesmo ??? Pois esta é a primeira etapa. Mostrar para o usuário, que não podemos avançar ou retroagir além de um determinado ponto.

Fazer isto, é bastante simples e direto, pois tudo que temos que fazer é testar os limites. Se eles forem aqueles em que o movimento não poderá ser feito, impedimos que o botão seja mostrado. Mas ao invés de ocultar, irei fazer algo um pouco diferente, e que ao meu ver fica até mais agradável. Bem, para começar, não precisaremos criar muito código. Apenas ajustar algumas pequenas coisas. A primeira parte é adicionar os bitmaps correspondentes a imagem, de quando os botões estão desabilitados. Isto é feito da seguinte maneira:

#define def_ButtonPlay          "Images\\Market Replay\\Play.bmp"
#define def_ButtonPause         "Images\\Market Replay\\Pause.bmp"
#define def_ButtonLeft          "Images\\Market Replay\\Left.bmp"
#define def_ButtonLeftBlock     "Images\\Market Replay\\Left_Block.bmp"
#define def_ButtonRight         "Images\\Market Replay\\Right.bmp"
#define def_ButtonRightBlock    "Images\\Market Replay\\Right_Block.bmp"
#define def_ButtonPin           "Images\\Market Replay\\Pin.bmp"
#define def_ButtonWait          "Images\\Market Replay\\Wait.bmp"
#resource "\\" + def_ButtonPlay
#resource "\\" + def_ButtonPause
#resource "\\" + def_ButtonLeft
#resource "\\" + def_ButtonLeftBlock
#resource "\\" + def_ButtonRight
#resource "\\" + def_ButtonRightBlock
#resource "\\" + def_ButtonPin
#resource "\\" + def_ButtonWait

Estas linhas irão adicionar, como sendo um recurso interno do indicador de controle, os bitmaps que irão representar a figura de quando os botões estarão desabilitados. Ou seja, estaremos nos limites, seja ele superior ou inferior da escala. Isto torna a coisa mais atraente, já que se você desejar poderá criar uma versão de botões com uma aparência mais adequada ao que você deseja apresentar ao usuário. Sinta-se a vontade em fazer as mudanças. Uma vez que isto tenha sido feito. Precisaremos, fazer a referencia a estes valores. Mas o código já esta praticamente pronto, apenas temos que referenciar estes recursos. Isto é feito no fragmento logo abaixo:

void CreteCtrlSlider(void)
   {
      u_Interprocess Info;
                                
      m_Slider.szBarSlider = def_NameObjectsSlider + " Bar";
      m_Slider.szBtnLeft   = def_NameObjectsSlider + " BtnL";
      m_Slider.szBtnRight  = def_NameObjectsSlider + " BtnR";
      m_Slider.szBtnPin    = def_NameObjectsSlider + " BtnP";
      m_Slider.posY = 40;
      CreteBarSlider(82, 436);
      CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
      CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
      CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
      if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
      PositionPinSlider(Info.s_Infos.iPosShift);

Adicionando estas referencias, o objeto que apresenta os botões já irá conseguir trabalhar com eles de forma a gerar o que estamos querendo. Notem que não adicionei absolutamente nada até o momento. Apenas a referencia aos recursos, e mesmo assim o sistema já conseguirá fazer o trabalho desejado. Mas para que os botões sejam modificados, quando alcançarmos os limites possíveis de ajuste, precisamos adicionar um pouco de código. Mas não se preocupem, pois é algo bastante tranquilo e fácil de ser entendido. O código que precisaremos adicionar é visto logo abaixo:

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < 0 ? 0 : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

Tive que adicionar um parâmetro novo na chamada. Mas como iremos usar o sistema no modo padrão neste primeiro momento, este parâmetro é inicializado com um valor zero. Assim não precisaremos mudar nada neste primeiro momento. Uma vez feito isto, podemos testar os limites nos seguintes pontos: Para ligar ou desligar o botão que ficará a esquerda do controle, utilizaremos este cálculo daqui. Agora para desligar o botão que ficará no canto direito do controle, o cálculo será este. No caso do botão do canto direito, o cálculo irá levar em consideração apenas o fato de o controle deslizante estar ou não no limite superior. Mas o botão do canto esquerdo irá trabalhar de uma outra forma. Por enquanto vamos deixar ele apenas usando como base o valor zero. Ao compilar o código do indicador de controle, e logo depois executar o serviço de replay / simulação, teremos o seguinte comportamento, que pode ser visto na animação logo abaixo:

Animação 01

Animação 01 : Demonstração do sistema de habilita / desabilita botão

Vejam que a coisa ficou super simples de ser entendida e foi muitíssimo simples e fácil de ser implementada. Mas isto foi somente o primeiro passo, para o que realmente precisamos implementar. Mas já é um bom começo. Agora precisamos fazer uma coisa um pouco mais chata. No entanto, necessária para que o usuário compreenda o que estará acontecendo. Mas para separar as coisas, vamos ver isto no próximo tópico.


Informando o usuário que o limite mudou

De certa forma poderíamos manter as coisas bastante simples. Apenas ligando e desligando o botão de limite inferior quando o controle deslizante toca-se o ponto mínimo que o serviço de replay / simulação estaria informando. Mas isto poderia deixar o usuário um pouco confuso ao observar que não estará mais conseguindo levar o controle, até a parte inferior, ou seja até o ponto zero. Para entender o que estou falando vejam a animação abaixo:

Animação 02

Animação 02 : Mas porque não posso ir até o Zero ?!?!

Vejam que na animação 02, fica claro que o usuário não irá entender, por que o controle não vai até o zero. Mesmo que o botão da esquerda indique que o movimento não é mais possível. No entanto, para o usuário isto não é evidente, e podemos melhorar a forma como isto é de fato feito, deixando as coisas um pouco mais claras para o usuário. Desta forma ele irá  entender que existe uma limitação, ou barreira a partir daquele ponto, que impede do controle deslizante continuar a sua descida além daquela região especifica. Mas antes de ver como iremos implementar esta indicação. Talvez você esteja curioso para saber como foi feito, para travar o controle antes que ele toca-se o ponto zero. Não é mesmo ?!?! Pode confessar, você esta se roendo de curiosidade. Mas não foi preciso fazer nenhum malabarismo em termos de programação. Apenas estipulei um ponto onde o controle irá travar, mas onde ?!?! O local pode ser visto abaixo:

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ChartRedraw();
   }

Mas como assim ?!?! O que você fez aqui ?!?! Calma, se você não percebeu, existe uma coisa aqui bastante sutil, mas poderosa. Observe que o valor da variável minimal esta declarada como zero. E se este valor fosse trocada para outro, por exemplo 100, ou 80, oque aconteceria ?!?! Aconteceria que ao testar o valor neste ponto, o controle no canto esquerdo seria desligado. Mas isto não impediria do sistema continuar a reduzir o valor caso o usuário clica-se no botão da esquerda ou arrasta-se o controle deslizante para a esquerda. Isto é verdade. Mas observem que agora estou travando o controle deslizante em uma posição que é indicada justamente pela variável minimal. Entenderam ?!?! Não importa o quanto o usuário insista, em arrastar o controle deslizante ou clicar no botão a esquerda. O ponto indicado, não irá abaixo do valor indicado, como ponto mínimo possível.

Legal, não é mesmo ?!?!? Mas quem irá definir este valor mínimo possível ?!?! Quem irá definir isto é o serviço de replay / simulador, e ele fará isto de maneira automática, conforme o replay / simulação vai se desenvolvendo. Mas o usuário poderá mudar o ponto, isto deste que o serviço, não tenha mudado o valor mínimo que poderá ser usado. Você deve estar achando que isto é muito complicado. Mas na verdade é bem simples de ser feito do que parece. Mas isto será visto depois. Vamos primeiro resolver o problema que é visto na animação 02, onde não existe nenhuma indicação para o usuário, de que o limite inferior esta naquela posição particular. Bem, existem algumas forma de fazer isto. Mas algumas são esteticamente estranhas e outras; Bem, existem outras que são muito bizarras. Mas podemos ficar em um meio termo. Que tão criar uma indicação de parede ?!?! Ao meu ver parece ser uma boa escolha, já que pode ser que dê uma estética bem bacana. Se você for um artista gráfico, ficará ainda melhor do que a que estou usando. Para fazer isto iremos utilizar o código abaixo:

inline void CreteBarSlider(int x, int size)
   {
      ObjectCreate(m_id, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
      ObjectSetInteger(m_id, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
//---
      ObjectCreate(m_id, m_Slider.szBarSliderBlock, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, x);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Slider.posY - 9);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
   }

Todas as linhas em destaque na cor verde, marcam o código que cria a tal indicação do limite inferior. E sim estamos usando um objeto para fazer isto. Mas se você desejar pode usar uma imagem bitmap com resultados bastante interessantes, com relação a questão do estilo visual. Mas aqui quero focar em manter o código mais simples. Isto por conta que muitos que devem estar lendo estes artigos, devem ser pessoas com pouco conhecimento em programação. Sendo assim, um código mais simples é melhor para que todos compreendam como as coisas foram implementadas. Mas para adicionar uma imagem bitmap, ou mesmo um padrão de textura, não é algo assim tão complicado. E os resultados ficarão bem mais interessantes, ainda mais se você usar uma programação via DirectX. E sim o MQL5, nos permite fazer isto. Mas isto irá ficar para um outro momento. Por enquanto vamos deixar as coisas assim. Simples, porém funcionais. Com isto teremos o resultado irá ficar conforme mostrado na animação 03, logo a seguir:

Animação 03

Animação 03 : Agora temos uma indicação de limite inferior ...

Veja que ficou bem mais simples do usuário entender, por que não é possível ir para um ponto, ainda anterior na questão do replay / simulação. Mas se você prestou atenção no código, não viu como a tal barrinha de indicação de limite inferior irá crescer. Ou melhor dizendo, qual é o tamanho que ela irá ter ?!?! Pois bem, o código onde este tamanho é definido é mostrado logo abaixo:

inline void PositionPinSlider(int p, const int minimal = 0)
   {
      m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
      ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
      ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
      ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
      ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
      ChartRedraw();
   }

Nesta linha de código é que estaremos indicando quanto a barrinha de indicação de limite terá de tamanho. Notem que o tamanho dela é definido pela variável minimal, ou seja, conforme o serviço de replay for atualizando a coisa, a barrinha irá crescer. Mas agora precisamos fazer com que esta limitação, seja corretamente ajustada em pelo serviço de replay / simulador. E novamente, vamos deixar isto para ser visto, no próximo tópico.


Conversando com o serviço de replay / simulador

Muito bem, agora que já montamos a parte básica do sistema, de forma que o indicador de controle, não permita mais ao usuário voltar no tempo. Precisamos fazer com que o serviço de replay / simulador, mostre ao indicador de controle, a partir de qual ponto o usuário não pode mais retornar. Basicamente esta é uma tarefa relativamente muito mais simples do que tudo que fizemos antes. Já que tudo que precisamos fazer, é verificar em qual posição o serviço de replay / simulador se encontra, no momento que for dado o pause. Esta é a parte fácil e direta. Então vamos ver como fazer para obter o funcionamento que precisamos. Para começar, temos que mudar uma coisinha no código. Então a coisa ficará da seguinte forma:

class C_Controls
{
   private :
//+------------------------------------------------------------------+
      string  m_szBtnPlay;
      long    m_id;
      bool    m_bWait;
      struct st_00
      {
         string  szBtnLeft,
                 szBtnRight,
                 szBtnPin,
                 szBarSlider,
                 szBarSliderBlock;
         int     posPinSlider,
                 posY,
                 Minimal;
      }m_Slider;
//+------------------------------------------------------------------+
      void CreteCtrlSlider(void)
         {
            u_Interprocess Info;
                                
            m_Slider.szBarSlider      = def_NameObjectsSlider + " Bar";
            m_Slider.szBarSliderBlock = def_NameObjectsSlider + " Bar Block";
            m_Slider.szBtnLeft        = def_NameObjectsSlider + " BtnL";
            m_Slider.szBtnRight       = def_NameObjectsSlider + " BtnR";
            m_Slider.szBtnPin         = def_NameObjectsSlider + " BtnP";
            m_Slider.posY = 40;
            CreteBarSlider(82, 436);
            CreateObjectBitMap(52, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock);
            CreateObjectBitMap(516, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock);
            CreateObjectBitMap(def_MinPosXPin, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin);
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER);
            if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0;
            m_Slider.Minimal = Info.s_Infos.iPosShift;
            PositionPinSlider(Info.s_Infos.iPosShift);
         }
//+------------------------------------------------------------------+
inline void PositionPinSlider(int p, const int minimal = 0)
         {
            m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            m_Slider.posPinSlider = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
            ObjectSetInteger(m_id, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_MinPosXPin);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != m_Slider.Minimal);
            ObjectSetInteger(m_id, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2);
            ObjectSetInteger(m_id, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
            ChartRedraw();
         }
//+------------------------------------------------------------------+

Notem que o código sofreu algumas poucas mudanças relativamente simples. No entanto, as mesmas são o suficiente, para que a barrinha de limitação, seja criada e corretamente configurada. Assim como os botões de controle. Mas para isto, foi preciso retirar a variável da chamada da função e colocar ela dentro da estrutura. Estarei inicializando ela em um ponto bem especifico do código, e depois poderemos acessa-la nos locais indicados e necessários. E por que fiz desta forma ?!?! Isto é para evitar que fosse preciso mudar as coisas, em outros pontos do código. Já que sempre quando ocorrer a pausa no serviço de replay / simulação, teremos uma chamada a função CreateCtrlSlider. Mesmo que algum dos objetos sejam destruídos ainda assim teremos a chamada a esta mesma função. Desta forma nos é facilitado toda a lógica de criação.

Agora que resolvemos a questão do indicador de controle, vamos voltar para o código do serviço. Pois precisamos fazer algumas pequenas modificações ali, é bem verdade que muitas destas mudanças, serão mais para fins estéticos do que para outra coisa. Mas é bom ter um sistema rodando lisinho, antes de começarmos a trabalhar o com algumas coisas bem mais complicadas.


Resolvendo os problemas estéticos no serviço de replay / simulação

O primeiro problema de fato, que precisa ser corrigido, e este nem é um problema estético, na verdade é uma falha mesmo. Acontece se você pedir para o serviço de replay / simulador, se deslocar para uma posição futura e antes mesmo de ter dado um play no sistema. Ou melhor explicando, quando você acabou de abrir o serviço, o gráfico é mostrado na tela, assim como o indicador de controle. Mas ao invés de dar play, você resolve avançar algumas posições e somente depois dá play, nesta nova posição. Ao fazer isto, é gerado um problema na plotagem do gráfico. Assim o gráfico irá ficar incorreto. Para solucionar isto, precisaremos forçar o sistema a um falso play, para somente depois de fato ir para a posição indicada pelo controle deslizante. Então é necessário fazer a seguinte mudança no código abaixo:

void AdjustPositionToReplay(const bool bViewBuider)
   {
      u_Interprocess Info;
      MqlRates       Rate[def_BarsDiary];
      int            iPos, nCount;
                                
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))
         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
      iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
      Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
      if (iPos < m_ReplayCount)
      {
         CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
         CustomTicksDelete(def_SymbolReplay, m_Ticks.Info[iPos].time_msc, LONG_MAX);
         if ((m_dtPrevLoading == 0) && (iPos == 0)) FirstBarNULL(); else
         {
            for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
            m_ReplayCount++;
         }
      }else if (iPos > m_ReplayCount)
      {
      CreateBarInReplay(true);
      if (bViewBuider)
      {
         Info.s_Infos.isWait = true;
         GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
      }else
      {
         for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++);
         for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
         nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
      }
      for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false);
      CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount);
      Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
      Info.s_Infos.isWait = false;
      GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
   }

Esta chamada aqui, irá fazer exatamente o que precisamos. Que é gerar um falso play no sistema. Sem esta chamada o tal erro de plotagem no gráfico irá aparecer. Mas também fiz uma pequena adição extra no código. Esta linha daqui irá adicionar os tickets que estarão faltando na janela de observação de mercado. Assim teremos algo um pouco mais interessante e próximo de uma movimentação mais realista. Além disto, fizemos mais algumas mudanças no código, como você pode notar pelas linhas cortadas. Mas isto tudo se deve ao fato de que, este teste aqui, irá impedir o sistema entrar, caso estejamos na mesma posição de deslocamento. Porém isto de fato é consequência de não estamos mais permitindo o usuário voltar no tempo. Então os códigos necessários para isto podem ser removidos sem problema algum.

Agora que resolvemos esta falha, vamos resolver um problema estético, que esta presente a um bom tempo. Mas que já podemos resolver ele, de uma maneira a deixar as coisas, um pouco mais agradáveis ao usuário do serviço de replay / simulação. Este detalhe estético, acontece quando você indica algum arquivo, que será usado como sendo barras prévias. No momento que o gráfico é aberto pelo serviço de replay / simulação, não temos inicialmente a indicação das linhas de preço. É bem verdade, que isto não afeta muito todo o sistema, mas esteticamente fica estranho, você olhar o gráfico aberto e não ver nenhuma linha de preço ali. Para corrigir, ou melhor solucionar este detalhe, precisamos fazer umas pequenas mudanças. A primeira é vista no código abaixo:

bool LoopEventOnTime(const bool bViewBuider)
   {
      u_Interprocess Info;
      int iPos, iTest;
                                
      if (!m_Infos.bInit) ViewInfos();
      if (!m_Infos.bInit)
      {
         ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
         ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
         m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
         m_MountBar.Rate[0].time = 0;
         m_Infos.bInit = true;
         ChartRedraw(m_IdReplay);
      }
      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);
      iPos = 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;
         }
      }                               
      return (m_ReplayCount == m_Ticks.nTicks);
   }                               

O que vamos fazer é remover as partes riscadas do código, e adicionar esta nova linha em destaque. Poderíamos até colocar o código desta chamada aqui. Mas muito provavelmente, este código poderá vir a sair desta função, indo parar em outra função futuramente. Então para facilitar esta portabilidade, prefiro deixar o código que precisaremos montar em um outro local.

O código que precisamos adicionar para resolver o problema estético das linhas, não serem mostradas logo que o gráfico é aberto pelo serviço de replay / simulação, pode ser visto logo abaixo:

void ViewInfos(void)
   {
      MqlRates Rate[1];
                                
      ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX);
      ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE);
      m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
      m_MountBar.Rate[0].time = 0;
      m_Infos.bInit = true;
      CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate);
      if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE))

         for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
      if (Rate[0].close > 0)
      {
         if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else
         {
            m_Infos.tick[0].bid = Rate[0].close;
            m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick);
         }                                       
         m_Infos.tick[0].time = Rate[0].time;
         m_Infos.tick[0].time_msc = Rate[0].time * 1000;
      }else
         m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount];
      CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
      ChartRedraw(m_IdReplay);
   }

Estas linhas de código, estavam no código que foi riscado na função anterior. Mas a questão que realmente precisamos é justamente as demais linhas. O que fazemos aqui é capturar a última barra que foi lançada no gráfico pelo serviço de replay / simulador. Fazemos isto, usando uma função que bem comum para trabalhar com indicadores. Caso tenhamos sucesso ao tentar capturar a barra, ou melhor, se o valor de fechamento for maior que zero, iremos montar um ticket especial. Ele irá ser montado dependendo do modo de plotagem que estará sendo utilizado. Mas se o valor de fechamento for zero, iremos utilizar o primeiro ticket válido presente na lista de tickets carregados ou simulados. A função responsável por procurar um ticket válido é justamente estas duas linhas. Basicamente ela será mais requisitada, quando formos trabalhar no modo de plotagem LAST, pois no modo BID o primeiro ticket já será valido. Mas de uma forma ou de outra, no final iremos jogar este ticket especial criado aqui, na janela de observação de mercado. De forma que as linhas irão aparecer no gráfico, assim que o serviço disser para a plataforma MetaTrader 5, abri o gráfico.

Uma outra mudança que precisamos fazer, e apesar de ela estar funcionando, de uma forma um pouco precária, é com relação ao replay / simulação, que não é indicada nenhuma barra previa para o sistema. Neste caso você poderá estar experimentando uma plotagem da primeira barra sendo cortada. Para resolver isto de uma forma definitiva precisamos informar alguma barra com sendo previa de todo o conjunto que irá vim a seguir. Para fazer isto, vamos mudar um detalhe no código, de maneira que poderemos trabalhar, até mesmo em um gráfico, cujo tempo transitar de 1 minuto até mesmo o diário, ou quem sabe semanal. Já que um gráfico mensal é exagerado.

inline void FirstBarNULL(void)
   {
      MqlRates rate[1];
      int c0 = 0;
                                
      for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++);
      rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
      rate[0].open = rate[0].high = rate[0].low = rate[0].close;
      rate[0].tick_volume = 0;
      rate[0].real_volume = 0;
      rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400;
      CustomRatesUpdate(def_SymbolReplay, rate);
      m_ReplayCount = 0;
   }

Aqui primeiramente iremos procurar um ticket válido. Caso o sistema de plotagem seja do tipo que usa o preço LAST. Uma vez feito isto, passamos a construir uma barra previa, e isto será feito sobre o primeiro preço válido contido na serie de tickets que iremos utilizar para efetuar o replay ou simulação. Mas o detalhe é justamente no momento que indicamos a posição no tempo e esta posição é indicada neste local, onde estaremos subtraindo o valor de 1 dia em termos de minutos. Assim a barra que irá aparecer no gráfico como sendo uma barra previa. E esta estará deslocada de modo que até mesmo um gráfico diário, terá a barra sendo plotada, de forma que você a veja completamente. É importante notar que este sistema irá trabalhar muito bem, tanto para dados em um mercado do estilo visto no forex, quanto em mercados do tipo bolsa.


Conclusão

Nos anexos você terá todos os arquivos necessários para experimentar a atual implementação do serviço de replay / simulador. Todo o sistema básico, se encontra de fato pronto. Mas como não estamos usando algumas coisas, iremos precisar fazer mudanças, e alterações para que o sistema possa se adequar ao que seria um modo de treinamento. No entanto, considere de fato o sistema de replay / simulador terminado. Nos próximos artigos iremos explorar novas formas de deixa-lo ainda melhor. Mas esta já será um nova etapa no desenvolvimento deste sistema.

Arquivos anexados |
Files_-_BOLSA.zip (1358.24 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_FUTUROS.zip (11397.51 KB)
Últimos Comentários | Ir para discussão (2)
fernandomsoares
fernandomsoares | 10 set 2023 em 23:30

Ei Daniel, boa noite !

Primeiramente muito obrigado por esta grande contribuição para todos que acessam seus conteúdos.

Posso dizer tranquilamente: "Você é o Cara !".

Daniel, gostaria de colocar um ponto que não sei se estou sendo apressado, mas que é de suma importancia para quem desenvolve um robo (EA) para operar em cima do seviço de Replay.

Tem como fazer o serviço disparar o evento OnTick do metatrader, para que o EA que estiver rodando possa receber cada tick processado ?

E o serviço esperar (não plotar outro tick) até que o evento ontick (caso exista) seja executado, assim poderiamos debugar o robo e o serviço respeitar esta parada (do degug).

Desde já obrigado !

Daniel Jose
Daniel Jose | 11 set 2023 em 19:37
fernandomsoares #:

Ei Daniel, boa noite !

Primeiramente muito obrigado por esta grande contribuição para todos que acessam seus conteúdos.

Posso dizer tranquilamente: "Você é o Cara !".

Daniel, gostaria de colocar um ponto que não sei se estou sendo apressado, mas que é de suma importancia para quem desenvolve um robo (EA) para operar em cima do seviço de Replay.

Tem como fazer o serviço disparar o evento OnTick do metatrader, para que o EA que estiver rodando possa receber cada tick processado ?

E o serviço esperar (não plotar outro tick) até que o evento ontick (caso exista) seja executado, assim poderiamos debugar o robo e o serviço respeitar esta parada (do degug).

Desde já obrigado !

Ok, vamos por partes 😁. Você não é a primeira pessoa a me perguntar tal coisa. O que de certa maneira me deixa bastante feliz. Pois vejo que muitos estão tendo a mesma ideia em particular. Cada um em um dado momento da implementação. Mas a resposta é um sim e um não. Mas por que desta ambiguidade ?!?! O motivo é que apesar de ser simples, não sei qual é exatamente seu nível de conhecimento sobre MQL5. Mas independente disto, você pode continuar a construir o seu Expert Advisor sem nenhum problema. Peço apenas que acompanhe atentamente e estude cada artigo que for postado. Pois para fazer o que você, e todos os demais estão querendo será necessário adicionar apenas uma única linha no seu Expert Advisor. Esta linha já poderia ser adicionada neste estagio de desenvolvimento do replay / simulador. Já que seu comentário está no artigo 25. Mas se você está perguntando isto, significa que você ainda não sabe que linha é esta a ser adicionada. Tenha calma. Em breve, os artigos começaram a explorar tal funcionalidade, onde o uso desta mesma linha será bastante frequente. Assim você e todos os demais compreenderão como fazer tal coisa. Ou seja, passarão a entender como criar suas próprias soluções. Isto com um mínimo de modificações, no sistema que estou mostrando como implementar.😁👍

PS: Obrigado pelo elogio. Estou aqui para mostrar que o MetaTrader 5 é muito mais do que parece. 😉👍

Representações no domínio da frequência de séries temporais: O espectro de potência Representações no domínio da frequência de séries temporais: O espectro de potência
Neste artigo, analisaremos os métodos relacionados à análise de séries temporais no domínio da frequência. Ele também se concentrará na utilidade do estudo de funções espectrais de séries temporais na criação de modelos preditivos. Além disso, discutimos algumas perspectivas promissoras para a análise de séries temporais no domínio da frequência usando a transformada discreta de Fourier (DFT).
Redes neurais de maneira fácil (Parte 43): Dominando habilidades sem função de recompensa Redes neurais de maneira fácil (Parte 43): Dominando habilidades sem função de recompensa
O problema com o aprendizado por reforço é a necessidade de definir uma função de recompensa, que pode ser complexa ou difícil de formular, porém abordagens baseadas no tipo de ação e na exploração do ambiente que permitem que as habilidades sejam aprendidas sem uma função de recompensa explícita estão sendo exploradas para resolver esse problema.
Desenvolvendo um sistema de Replay (Parte 26): Projeto Expert Advisor — Classe C_Terminal Desenvolvendo um sistema de Replay (Parte 26): Projeto Expert Advisor — Classe C_Terminal
Talvez já podemos começar a desenvolver um Expert Advisor a ser utilizado no replay / simulação. Mas não iremos criar qualquer coisa, este precisará ser algo um pouco mais bem elaborado. Mas não nos deixemos nos levar pelo grau de dificuldade neste primeiro momento. Temos de começar a fazer as coisas partindo de algum ponto. Caso contrário apenas iremos nos conformar, imaginando o qual difícil o desafio é, sem ao menos tentarmos de fato superar este obstáculo. Vida de programador de fato é isto: Encontrar um obstáculo e tentar superar ele, via estudo, testes e bastante pesquisa.
Redes neurais de maneira fácil (Parte 42): Procrastinação do modelo, causas e métodos de resolução Redes neurais de maneira fácil (Parte 42): Procrastinação do modelo, causas e métodos de resolução
A procrastinação de modelos no contexto do aprendizado por reforço pode ser causada por vários motivos, e a solução desse problema requer medidas apropriadas. Este artigo discute algumas das possíveis causas da procrastinação do modelo e métodos para superá-las.