English Русский Español Deutsch 日本語
preview
Desenvolvendo um sistema de Replay (Parte 29): Projeto Expert Advisor — Classe C_Mouse (III)

Desenvolvendo um sistema de Replay (Parte 29): Projeto Expert Advisor — Classe C_Mouse (III)

MetaTrader 5Testador | 5 outubro 2023, 12:31
385 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 28): Projeto Expert Advisor — Classe C_Mouse (II), demonstrei a criação de códigos mais legível. Apesar daquele modelo ser bastante interessante, tornando o programa mais legível, você acabará tornando que é mais demorado programar seguindo aquilo. Não por conta de que a programação se torna confusa, muito pelo contrário. O problema é que aquela forma de tornar o programa mais legível tem suas limitações. Uma delas é a própria sintaxe de qualquer linguagem. Salvo o fato de ela ter sido criada para ter um tipo de formatação e sintaxe, usar definições ajuda, mas será como uma muleta, nos limitando em outras questões. Apesar de ter sido, no meu ponto de vista valido, mostrar aquilo em um código real.

Vamos nos manter na sintaxe original. Mas quem desejar fazer o estudo do código da forma como foi mostrada, sinta-se a vontade em criar as definições desejadas e converter o código de modo que você o consiga ler mais facilmente. Isto irá lhe ajudar a aprender algumas técnicas bem interessantes. Acredite foi assim que aprendi a programar em diversas outras linguagens. É algo trabalhoso, mas recompensador, pois existem técnicas e até mesmo algoritmos, que são mais simples de serem criados em uma determinada linguagem, do que em outra. Mas se você conseguir ler o código na língua original, conseguirá fazer coisas que outros não conseguem. Pense nisto, como um trabalho de tradução, onde você será o interprete entre dois mundos diferentes. Para conseguir fazer isto sua compreensão terá que ser bem mais ampla, do que alguém que se comunica sempre usando os mesmos sinais, símbolos e afins.

Amplie a sua mente, veja fora da caixa, e um universo inteiro se abrirá na sua frente.

Mas vamos ao que nos traz a este artigo. Aqui mostrarei como você pode, sem modificar uma classe e sem utilizar o sistema de herança, conseguir ampliar de uma maneira controlada, segura e robusta as capacidade de um sistema. Seja ele qual for. A tarefa em si pode parecer banal a principio, mas lhe dará muito mais conhecimento de como as coisas funcionam do que simplesmente ir construindo a coisa sempre da mesma forma.

O que mostrarei, neste artigo, é uma forma de ampliar o sistema de estudos de um ativo. Usaremos a classe C_Mouse, juntamente com o que ela herdou da classe C_Terminal, a fim de gerar um outro nicho de analise. Mas de uma forma bem interessante, onde criaremos uma classe, que usará o conteúdo da classe C_Mouse, mas sem herdar a mesma. Podendo esta nova classe, ser adicionada ou não no código final. Dependendo é claro, do que você pretende fazer. Mas independente disto, você irá aprender como criar um modelo de estudos particular, sem colocar em risco a integridade de um código já criado e testado. Este é o real objetivo deste artigo.


Ajustando as coisas antes da expansão

Antes de começar a programar uma classe que não irá herdar a classe C_Mouse. Mas expandirá a funcionalidade, ou melhor dizendo modificará a funcionalidade da classe C_Mouse. Vamos primeiro mudar alguns detalhes na classe original C_Mouse. Não por conta de algum problema que ela contenha, mas precisamos adicionar algumas coisinhas e modificar um pequeno detalhe, que nos ajudará a fazer qualquer tipo de expansão. Tornando assim qualquer modificação na funcionalidade, algo bastante pratico. Pois se algo der errado, poderemos simplesmente voltar a usar a nossa classe original sem nenhum tipo de problema. As mudanças que faremos são relativamente poucas, simples, mas importantes. Primeiro adicionaremos uma nova variável no código da classe C_Mouse.

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int     X,
                    Y;
            double  Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
         bool    ExecStudy;
      };

Esta variável permitirá expandir, ou modificar a forma com a classe C_Mouse funciona. Mas sem que façamos a herança da classe, ou fazer uso do polimorfismo. Apesar de estes serem os caminhos mais naturais, seguiremos um caminho diferente. Na verdade, a técnica que mostrarei, permite fazer isto com qualquer classe, de maneira que teremos um funcionamento baseado no tipo de coisa que precisamos. Mas, e esta é a parte importante, sem mexer em nenhuma linha da classe original. Antes de fazer as mudanças, que de fato ampliará as capacidades de se fazer as coisas, precisamos adicionar uma pequena linha de código na classe C_Mouse. Esta será adicionada no seguinte procedimento. Algo simples.

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

Esta linha em especial, garante que mesmo se a linha de preço do mouse, venha a ficar como sendo uma linha de primeiro plano, ela não irá de forma alguma receber nenhum evento. Isto caso ela esteja sobre algum objeto, no qual estamos tentando clicar. A linha de preço somente receberá eventos de clique, caso nenhum objeto esteja sobre o foco do cursor do mouse. Mas, e é importante observar isto, o fato de adicionar esta linha, não impede de maneira algum a geração de estudos. Mesmo que o clique aconteça sobre um objeto. Já que esta linha de código, impede que o objeto receba diretamente o clique. Mas, e isto também é importante, não impede que o evento CHARTEVENT_MOUSE_MOVE, seja disparado e capturado pela classe C_Mouse.

NOTA IMPORTANTE: No passado, tive problemas com um algo justamente por conta da falta desta linha de código. No artigo, Deixando o gráfico mais interessante: Adicionando uma tela de fundo, existe uma falha, que na época não consegui resolver, por mais que tentasse a falha era persistente. Então para sanar a falha que impedia de você acessar objetos presente no gráfico. No objeto que era usado para colocar o fundo no gráfico, você deve adicionar esta mesma linha mostrada aqui em destaque. Poderia ter dado esta dica no passado. Mas quero recompensar de alguma forma quem de fato lê os artigos. Então agora você já sabe como resolver o problema no artigo mencionado.

Agora faremos pequenas mudanças na seguinte função:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
               if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price);
                  m_Info.Study = eStudyExecute;
               }
               if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
               m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
               break;
            case CHARTEVENT_OBJECT_DELETE:
               if (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

Tais mudanças, não trazem nenhum beneficio da classe C_Mouse. Mas sim, para todo o programa que será construído sobre a classe C_Mouse. Talvez você não consiga notar nenhuma diferença no código visto, nos artigos anteriores e estes acima. Isto se deve ao fato da diferença ser muito pequena e bem pontual. Não fazendo assim absolutamente nada no código. Mas trazendo diversos benefícios em termos de usabilidade e possibilidade de ajustes. Não é muito simples notar o que temos aqui de diferente. Mas basicamente estas três linhas a mais, que foram adicionadas ao código, nos ajudará muito. Vamos ver o que cada uma faz:

  1. Esta linha irá ajustar o valor do tempo de forma adequada, para que possamos de fato usar qualquer objeto no gráfico. Não somente usando coordenadas de tela ( X e Y ), mas sim coordenadas do ativo ( Preço e Tempo ). Isto era uma questão que a bastante tempo eu estava procurando uma forma de obter. Dado o fato de que é muito mais interessante trabalhar em coordenadas do ativo do que usando coordenadas de tela em alguns momentos. O nível de liberdade que isto nos dá, salta ao olhos. Mas você pode notar que estamos fazendo uma chamada e esta esta na classe C_Terminal por motivos práticos.
  2. Foi adicionada esta chamada a função ChartTimePriceToXY, de modo que as coordenadas do ativo sejam convertidas para coordenadas de tela.
  3. E o último ponto foi justamente este. Onde indicamos se a classe C_Mouse esta ou não em modo de estudo. Atenção a sintaxe para não confundir as coisas.

Estas são todas as modificações produzidas na classe C_Mouse. Mas como foi mencionado a classe C_Terminal, agora conta com uma nova função em seu portfolio. Então vamos dar uma olhada para saber do que se trata. A função adicionada pode ser vista abaixo:

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

Se esta função lhe parece extremamente estranha e confusa, não se sinta mal por isto. Ela de fato é bastante confusa a primeira vista e apenas olhando para ela pode dar cala frios, já que a mesma não faz nenhum sentido. Mas bem lá no fundo ela é capaz de nos proporcionar algo maravilhoso, e ao mesmo tempo extremamente interessante. Para entender a mágica por traz desta função, você precisa compreender uma coisa sobre conversão de tipos. Além de alguns poucos detalhes extras. Mas vamos primeiro ver a parte sobre a conversão de tipos.

Quando esta função é chamada, ela irá receber como parâmetro um valor datetime. Por favor, não olhe este valor desta forma. Você o deve olhar como sendo um valor ulong. Pois é isto que de fato ele é. Um valor de 8 bytes de comprimento. Entenda isto primeiramente. A próxima coisa que você deve entender, é que por datetime ser um valor ulong, a informação sobre a data e hora estão compactados de uma maneira bastante especifica dentro da variável. Mas para nos o que de fato interessa são os bits menos significativos ( LSB ). É neles que se encontra os valores de segundo, minuto, hora, dia, mês e ano. Exatamente nesta sequencia, do bit menos significativo para o mais significativo.

Agora atenção ao fato de que nSeconds, contem o valor em segundos do período usado no gráfico. Isto é conseguido ao se usar a função PeriodSeconds que nos diz tal informação. Agora a variável dt contem o valor de criação da ultima barra presente no gráfico. Pouco importa qual o ativo que estaremos utilizando. Mas este fato é importante depois para nos. Pois bem, caso o valor dt, seja menor que o valor do parâmetro da chamada, indicará em que ponto do tempo estamos, no caso estaremos no futuro. Desta forma, a posição de tempo em relação a coordenada gráfica de tela ( X e Y ), é uma posição onde uma barra futura estará, ou será criada. Neste momento não tem como sabermos, usando a função iTime, onde isto estará, pois o gráfico ainda não foi construído até aquele ponto. Mas mesmo assim precisamos saber onde isto estará de fato. Caso estejamos fazendo um estudo relacionado ao futuro.

Para saber onde, em termos de coordenadas de tela ( X e Y ). Usamos o fato de que datetime, é na verdade um valor ulong. Então dividindo este valor pelo valor de nSeconds, teremos um valor double, ou fracionário, como queira. Agora vem a parte importante. Se este valor fracionário, for multiplicado por nSeconds, iremos obter o valor original, ou seja se você dividir 10 por 3 e multiplicar o resultado da divisão por 3 você irá obter 10. Mas usando a conversão de tipos, e isto é feito exatamente por este ponto, tornamos o valor double em um valor ulong, ou melhor, um valor do tipo datetime. Este não contem a parte fracionária. Por conta disto, ao multiplicar o valor por nSeconds, iremos ter o valor no futuro e já ajustado. Esta é a parte interessante da coisa. Mas isto tem um problema.

Quando olhamos para o passado, apesar do calculo se dar muito bem para series continuas, ou seja quando não existe um GAP no tempo. Este calculo, não é de fato adequado para o passando, ainda mais em ativos que contem tais GAPs. Este são bem comuns em ativos negociados dentro de uma dada faixa de horário, ou dia. Resumindo: ativos de bolsa. Onde temos a criação das barras apenas em uma dada janela de tempo. Fora desta, o mercado se encontrará fechado. Para este tipo de coisa, usamos um ajuste um pouco diferente, onde procuramos qual foi a barra onde se encontra o valor informado como argumento. Para fazer isto verificamos a quantidade de barras entre a barra atual e a barra no tempo indicado. Com isto obtemos um valor. Este é usado como deslocamento, a fim de capturar exatamente o ponto no tempo. Assim temos também o ajuste no tempo passado, independente do que tenha acontecido.

Você pode pensar que esta função é completamente desnecessária. E por que ter o trabalho em desenvolver ela. Mas usando esta função você pode usar coordenadas de ativo ( Preço e Tempo ), como coordenadas de tela ( X e Y ). Assim conseguiremos usar qualquer tipo de objeto gráfico. Não ficando mais limitados a usar objetos de coordenadas de tela ou objetos de coordenadas de ativo. Teremos condições de converter um tipo de coordenada em outra, e para isto usaremos as chamadas: ChartXYToTimePrice ( para converter coordenadas de tela em coordenadas de ativo ) e ChartTimePriceToXY ( para converter coordenadas de ativo em coordenadas de tela ). Lembre-se do seguinte fato: Alguns tipos de estudos precisamos de uma informação que seja a mais precisa possível, e quando queremos que a exata barra seja usada como ponto de indicação para algo, precisamos de fato deste tipo de conversão. Além é claro nos propiciar um tipo de informação bastante interessante, que mostrarei depois.


Criando a classe C_Studys

Agora que a classe C_Mouse foi melhorada. Podemos focar em criar uma classe que será usada para promover uma base completamente diferente de estudos. Mas como expliquei no inicio do artigo, não iremos usar herança ou polimorfismo para gerar esta nova classe. Iremos modificar, ou melhor dizendo, agregar alguns objetos novos a linha de preço. Isto neste primeiro momento, no próximo artigo mostrarei como modificar os estudos. Mas faremos isto sem mexer no código da classe C_Mouse. Sei que na pratica, isto seria mais simples ser feito usando herança ou polimorfismo. No entanto, existem técnicas diferentes para se conseguir a mesma coisa. Este tipo de técnica é interessante ser aprendida, já que o uso da mesma não nos traz grandes transtornos, caso o novo código tenha defeitos.

Podemos simplesmente remover ele, corrigir as falhas e depois retornar com ele, sem fazer qualquer tipo de modificação no código já testado. Lembre-se: Muitas vezes a correção de falhas faz com que um código fique incompatível com a hierarquia de classes que foi construída. Não nos permitindo usar a herança ou polimorfismo, de modo a agregar o novo código a um já existente. Então aprender algo que nos permita isto, é algo bastante importante e que pode lhe ajudar em diversos momentos. Principalmente quando, você desenvolve algo que gostaria de ter em seu programa finalizado, mas não quer reprogramar grande parte do código já existente e testado.

A primeira coisa a ser feita, será criar um novo arquivo. Mas podem ser vários arquivos distintos e que possuam uma hierarquia própria entre as classes envolvidas. Isto não irá importa. Pois este arquivo, ou arquivos, não farão parte da hierarquia da classe principal. Assim poderemos ter uma outra hierarquia independente, podendo esta ser alterada ou melhorada conforme a necessidade e de diversas formas. Isto por conta, que ela não tem nenhum tipo de envolvimento direto com a hierarquia principal que será desenvolvida. Sendo quase um projeto paralelo.

Vamos começar com um sistema mais básico. Mas iremos ver algo mais elaborado no futuro. Então o arquivo começa da seguinte forma:

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

Apenas observando este código, podemos logo notar que a coisa aqui será bastante intensiva. Observem que estamos incluindo dois arquivos de cabeçalho e eles estão localizados em uma posição diferente de onde este arquivo daqui estará. Mas isto já foi explicado antes. Depois disto definimos nomes para alguns objetos que iremos usar. Isto é importante para depois não ficarmos perdidos tentando acessar os objetos corretos. Mas também definimos um tipo de alias para facilitar a nossa codificação, já que iremos usar algo muito similar a ponteiros, que é um dos recursos mais poderosos e ao mesmo tempo mais arriscados que existe. Mas por conta da forma como iremos programar as coisas, este recurso terá um risco bastante controlado. Podendo ser usado maneiras bem interessantes.

Este tipo de definição ( alias ) é bastante comum quando queremos acessar determinadas coisas, mas não queremos correr o risco de digitar algo errado durante a codificação. É sempre um recurso bem interessante de ser usado.

Feita esta primeira parte. Começaremos o código da classe, esta se inicia da forma como se pode ver abaixo:

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

Aqui temos apenas e somente a parte relacionada as variáveis privativas da classe. Reparem bem em uma delas. Esta variável daqui faz o viés de ponteiro para a classe C_Mouse. Novamente ponteiros são uma das forma de programar mais poderosas que existe, mas se você não tomar cuidado com eles, pode acabar tendo uma tremenda dor de cabeça tentando solucionar os problemas que surgem ao se fazer uso de tal instrumento. Então muito cuidado ao usar ponteiro, apesar de que em MQL5 ponteiros não são, e não tem as mesma características presentes em C/C++. Mas ainda assim, você deve tomar os devidos cuidado ao usá-los. Nunca subestime os ponteiros. O restante do código não merece assim tanto destaque.

A primeira das rotinas dentro da classe, é vista logo abaixo:

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

Aqui temos uma rotina bem simples, que faz o calculo do tempo restante para o surgimento de uma nova barra. O calculo em sim acontece apenas neste ponto daqui, mas sempre que for detectado via teste que o tempo limite da barra foi atingido, faremos a leitura e o ajuste de forma a conseguir levar o calculo até a próxima barra. Assim esta chamada iTime acontecerá apenas 1 vez a cada barra criada. E não a cada interação, ou chamada a procedimento. A próxima função já é uma velha conhecida, e responsável por criar os objetos de uma maneira padronizada.

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

Aqui praticamente fazemos a mesma coisa que é feita na função encontrada em C_Mouse. De forma que em breve poderemos pensar em unificar ambas, em uma ainda mais genérica. Logo em seguida, temos um próximo procedimento que para alguns pode ser interessante fazer alguns testes nele.

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

Este procedimento tem a capacidade de adequar o tamanho do objeto baseando-se no tamanho e tipo de fonte que estará sendo utilizada. Mas também no maior texto que poderemos imprimir dentro do objeto. Tudo isto, é conseguido usando esta chamada TextGetSize, onde se baseando nas informações prestadas, irá nos dar um tamanho aproximado do texto. Mas para que ele não fique muito justinho dentro do objeto, adicionamos um pouco nas dimensões do mesmo. Assim teremos uma pequena folga entre o texto e os limites do objeto no qual o texto se encontrará. Por conta de outros motivo, retornamos o valor calculado. Assim podemos criar uma linha de objetos.

Seguindo, temos a rotina que plota as informações no nosso gráfico.

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

Esta é justamente a que gera um tipo de estudo, que estará sempre presente e acompanhará de alguma forma a linha de preço. Assim podemos saber de algumas coisas facilmente. O tipo de informação e o que será informado, depende do interesse de cada um. Aqui para fins de demonstração iremos ter três tipos de informação. Então vamos entender o que será informado aqui.

Neste ponto poderemos ter informações diferentes dependendo de como o programa esta interagindo com a plataforma e com o servidor de negociação. Mas basicamente poderemos ser informados das seguintes coisas:

  • Tempo restante até que a próxima barra surja no gráfico;
  • Informação dizendo que o mercado se encontra fechado;
  • Informação dizendo que estamos lidando com um replay;
  • Informação dizendo que o ativo se encontra em leilão;
  • E em casos excepcionalmente raros uma mensagem de erro.

Notem que não é lá grandes coisas que estamos fazendo. Mas isto é apenas para demonstrar a técnica que estaremos usando. Seguindo temos o destructor.

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

Este código acima serve justamente para fazer com que a classe diga a plataforma que desejamos remover os elementos presentes que foram criados pela classe. Reparem que não estamos preocupados se isto irá gerar eventos por parte da plataforma, já que não temos de fato pretensão em recriar tais objetos, caso sejam removidos.

A próxima coisa a ser vista é a rotina mostrada abaixo:

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

Um detalhe sobre esta rotina é que ela não trabalha sozinha. Temos um outra com o nome muito parecido, vista logo a seguir:

void Update(const MqlBookInfo &book[])
   {
      m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
      for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
         if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
      this.Update();
   }

Mas como assim ... temos duas rotinas com o nome de Update ?!?! Pode isto ?!?! Sim podemos declarar funções com o mesmo nome aparente. Isto é conhecido como sobrecarga. Mas apesar dos nomes serem parecidos, eles não são. Para o compilador o nome de ambas rotinas são diferentes. Isto por conta dos parâmetros. Você pode sobrecarregar rotinas e procedimentos desta forma, mas a única regra é que os parâmetros devem ser diferentes. Observem, na segunda rotina que temos uma chamada para a primeira rotina, de modo a usar o método onde nenhum parâmetro é necessário. Este tipo de coisa é bastante comum quando programamos métodos que sofrem sobrecarga, a fim de criar algo que seja significativo para depuração.

Agora tem um ponto que é interessante. Este é justamente a forma como podemos saber se ativo se encontra ou não em leilão. Normalmente teremos informações de preço sendo informados no book. Mas pode ser que o book do ativo contenha um destes valores, e quando isto acontece significa que o ativo se encontra em leilão. Atentem-se a isto pois pode ser algo interessante a ser adicionado em um Expert Advisor automático. Já expliquei sobre isto em outra serie de artigos :  Aprendendo a construindo um EA que opera de forma automática (Parte 14): Automação (VI), mas não expliquei como saber se o ativo entrou ou não em leilão. A ideia não era fazer uma minucia geral de todos os pontos envolvidos no processo de negociação com um Expert Advisor automático.

E para responder a alguns eventos da plataforma, temos a seguinte função:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

Simples, mas com tudo que precisamos já que os objetos devem seguir o mouse, e  analisar o mouse é tarefa da classe C_Mouse. Novamente estamos modificado o funcionamento da classe C_Mouse, sem usar herança ou polimorfismo. Então o trabalho de ajustar e corrigir o posicionamento do mouse é responsabilidade da classe C_Mouse. A apenas usaremos os dados.

Passamos por praticamente todo o código. Mas precisamos ver onde realmente a mágica acontece. Vamos primeiro começar com o código do Expert Advisor. Este código pode ser visto na integra logo abaixo:

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

Observem que aqui estou dando a capacidade deste Expert Advisor de trabalhar tanto em um ativo real, quando em um ativo simulado. E usando as mesmas ferramentas. Isto é conseguido ao se fazer este tipo de testagem. Neste caso quando e no atual estágio de desenvolvimento, teremos uma informação diferente dependendo do que o Expert Advisor esteja vendo no gráfico.

O que estamos fazendo passar para o constructor da classe, o ponteiro que foi criado ao se inicializar a classe C_Mouse. Assim a classe C_Studys não precisará de fato herdar a classe C_Mouse, Isto para conseguir usar o conteúdo dela. Nem precisaremos inicializar a classe C_Mouse dentro da classe C_Studys. Este tipo de modelagem é bastante interessante quando precisamos passar algumas informações ou queremos trabalhar com coisas sem de fato venhamos a herdar ou fazermos uso do polimorfismo. Caso você simplesmente deseje retirar, modificar ou alterar a classe C_Studys, de maneira a criar uma funcionalidade completamente diferente, mas de alguma forma ligada a classe C_Mouse. Poderá fazer isto de maneira bem simples. Sem mexer em nenhuma linha da classe C_Mouse.

Mas a principal vantagem, é poder continuar desenvolvendo as coisas de uma maneira paralela. Onde caso a classe C_Studys, não venha de fato a fazer parte do projeto final, você simplesmente remover o código dela. Não precisando se preocupar com mudanças ou pendencias, que surgem ao fazer fazer herança. Assim a classe C_Studys, passa a ser uma classe paralela, ao sistema de classes que de fato irá fazer parte do código final.

Então para entender como isto é repassado para a classe. Veremos finalmente o código do constructor.

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

Como você pode ver este código é bem peculiar, onde temos um parâmetro que é bem pouco usual.  Um ponteiro para uma classe. Observem como tudo vai se ligando de maneira que teremos exatamente o sistema que precisamos construir. Mas mesmo aqui temos uma outra coisa bastante peculiar e pode não fazer muito sentido. A função TextSetFont. Esta serve justamente para que ajustemos as dimensões dos objetos ao tipo de informação que iremos mostrar. Atenção ao fato de estamos fazendo uma fatoração aqui. Porque estas fatoração ser tão estranha, usando um numero negativo ?!?! Para entender segue a explicação dada justamente na documentação:

O tamanho da fonte é definido usando valores positivos ou negativos. Esse fato define a dependência do tamanho do texto a partir das configurações do sistema operacional (escala de tamanho).

  • Se o tamanho é especificado por um número positivo, este tamanho é transformado em unidades de medidas físicas de um dispositivo (pixels) quando se muda de uma fonte lógica para uma física, e este tamanho corresponde à altura dos símbolos glifos escolhidos entre as fontes disponíveis. Este caso não é recomendado quando os textos exibidos pela função TextOut() e os exibidos pelo OBJ_LABEL ("Etiquetas"), onde objetos gráfico estão a ser utilizados em conjunto no gráfico.
  • Se o tamanho é determinado por um número negativo, este número deve ser definido em décimos de um ponto lógico (-350 é igual a 35 pontos lógicos) dividido em 10. Um valor obtido é então transformado em unidades de medidas físicas de um dispositivo (pixels) e corresponde ao valor absoluto da altura de um símbolo escolhido a partir das fontes disponíveis. Multiplicar o do tamanho de fonte determinada nas propriedades do objeto por -10 para tornar o tamanho de um texto na tela semelhante ao de um objeto OBJ_LABEL.

 Por este motivo esta fatoração é feita desta forma. Se você não fizer este ajuste aqui, terá problemas ao usar a função TextGetSize, que é usada na rotina de criação dos objetos. Já que a fonte utilizada, ou as dimensões poderão ser diferentes das que você esteja querendo de fato fazer uso.


Conclusão

Não se esqueça de experimentar o anexo. Nele você terá acesso ao sistema atual. Mas faça as experiências, usando o Expert Advisor tanto no replay / simulador, quanto em uma conta com o mercado funcionando ( DEMO ou REAL )

No próximo artigo, mostrarei como estender ainda mais este tipo de abordagem, a fim de usar estudos mais elaborados. Mas NÃO irei modificar uma única linha do código de classe principal. Prometo.

Arquivos anexados |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
Como detectar tendências e padrões gráficos usando MQL5 Como detectar tendências e padrões gráficos usando MQL5
Neste artigo, é apresentado um método de detecção automática de padrões de ação de preços usando o MQL5, como tendências (de alta, de baixa e laterais) e padrões gráficos (topo duplo, fundo duplo).
Redes neurais de maneira fácil (Parte 46): Aprendizado por reforço condicionado a metas (GCRL) Redes neurais de maneira fácil (Parte 46): Aprendizado por reforço condicionado a metas (GCRL)
Convido você a conhecer mais uma abordagem no campo do aprendizado por reforço. É chamada de aprendizado por reforço condicionado a metas, conhecida pela sigla GCRL (Goal-conditioned reinforcement learning). Nessa abordagem, o agente é treinado para alcançar diferentes metas em cenários específicos.
Desenvolvimento de um indicador Heiken Ashi personalizado usando MQL5 Desenvolvimento de um indicador Heiken Ashi personalizado usando MQL5
Neste artigo, aprenderemos a criar nosso próprio indicador usando MQL5 com base em nossas preferências, que será usado no MetaTrader 5 para interpretar gráficos ou como parte de Expert Advisors.
Avaliando modelos ONNX usando métricas de regressão Avaliando modelos ONNX usando métricas de regressão
A regressão é uma tarefa de prever um valor real a partir de um exemplo não rotulado. Para avaliar a precisão das previsões de modelos de regressão, são utilizadas as chamadas métricas de regressão.