English Русский 中文 Español Deutsch 日本語
preview
Melhore seus gráficos de negociação com uma GUI interativa baseada em MQL5 (Parte I): GUI móvel (II)

Melhore seus gráficos de negociação com uma GUI interativa baseada em MQL5 (Parte I): GUI móvel (II)

MetaTrader 5Sistemas de negociação | 8 novembro 2023, 09:30
552 0
Kailash Bai Mina
Kailash Bai Mina

Introdução

Na primeira parte, vimos como criar um painel móvel simples. No entanto, agora descreveremos uma abordagem mais eficiente para alcançar o mesmo objetivo, adequada para EAs/indicadores completos.

Por exemplo, se desejamos ter duas telas móveis no monitor, precisaríamos duplicar o código existente e criar seis variáveis globais adicionais com nomes diferentes. Quando precisarmos de três telas móveis, a complexidade do código aumenta significativamente e fica muito mais difícil de gerenciar. Claramente, precisamos de uma abordagem mais racional.

Felizmente, podemos recorrer aos arquivos .mqh para simplificar esse processo.

Aqui está o que discutiremos neste artigo:

  1. Conceito de classes
  2. Criando um painel mediante um arquivo .mqh
  3. Configurando dois painéis em um único gráfico mediante o arquivo .mqh


Conceito de classes

Primeiramente, vamos entender o conceito de classes. A questão é ampla e complexa, mas, por enquanto, só precisamos dos conceitos básicos.

Em termos simples, uma classe é um tipo de dado complexo, semelhante a int, string e outros, mas um pouco mais complexo.

Existem muitas definições de classes, mas, essencialmente, você pode considerá-las como agrupamentos de código. Mas que tipo de código é esse? Normalmente, eles consistem em um conjunto de funções, frequentemente chamadas de métodos, e variáveis. Alguém pode dizer que esta é uma definição vaga ou imprecisa, mas não estamos fazendo um exame aqui. Nosso objetivo principal é usar as capacidades das classes para tornar a escrita de código mais gerenciável e eficiente, e, para isso, uma definição rigorosa não é crucial.

Em essência, as classes são um conjunto de funções e variáveis que podemos usar para nossos propósitos.

Essa definição naturalmente leva a quatro perguntas fundamentais:

  1. Onde as criamos?
  2. Como as declaramos?
  3. Como as escrevemos?
  4. Como as usamos?

A resposta à pergunta sobre por que precisamos de classes é bastante simples. As classes simplificam o processo de escrita e gerenciamento de código.

  1. Onde as criamos?

    A escolha do tipo de arquivo para criar classes - seja .mq5 ou .mqh - é flexível. No entanto, geralmente preferimos arquivos .mqh separados.

    A diferença entre a criação de classes em .mq5 e .mqh é bastante notável. Se você desenvolve suas classes em um arquivo .mqh, precisará importá-lo para um arquivo .mq5. Isso ocorre porque a criação de um EA/indicador é exclusiva para arquivos .mq5. No entanto, se você definir a classe diretamente no arquivo .mq5, não precisará de nenhum processo de importação.

    Normalmente, preferimos arquivos .mqh separados, pois eles melhoram a gerenciabilidade do código. O processo de importação é simples - requer apenas uma linha de código. Aqui, usaremos um arquivo .mqh separado.


  2. Como as declaramos?

    A declaração de uma classe é feita de forma bastante simples. Abaixo está um exemplo de declaração de uma classe vazia simples:

    class YourClassName
      {
      };
    No trecho de código acima, YourClassName é um espaço reservado. Substitua YourClassName pelo nome real que você deseja atribuir à sua classe.



  3. Como as escrevemos?

    Para responder a esta pergunta, vamos começar discutindo variáveis e depois passaremos para funções.

    Suponha que você queira declarar duas variáveis: uma do tipo int e outra do tipo bool. Você pode fazer isso da seguinte maneira:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1;
       bool var2;
      };
    //+------------------------------------------------------------------+
    

    Você não pode atribuir valores a essas variáveis diretamente na declaração da classe. Por exemplo, o código a seguir resultará em um erro:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class YourClassName
      {
       int var1 = "int var";
       bool var2 = true;
      };
    //+------------------------------------------------------------------+
    
    O erro seria o seguinte:
    '=' - illegal assignment use
    '=' - illegal assignment use

    Em cenários em que nossa lógica depende dos valores iniciais das variáveis, podemos usar o chamado construtor. Também temos o seu oposto, o destrutor.

    Construtores e destrutores são funções especiais sempre associadas a uma classe, quer você os declare explicitamente ou não. Se você não os declarar, eles serão implicitamente considerados funções vazias. O construtor é executado quando uma instância da classe é declarada, e o destrutor é executado quando uma instância da classe sai do escopo. Em MQL5, não há maneira de excluir explicitamente uma instância da classe.

    Por exemplo, a função OnInit() em nosso código atua como um construtor, e a função OnDeinit() atua como um destrutor. A classe aqui está oculta nos bastidores para simplificar. Esse comportamento é comum em muitas linguagens, incluindo o Java, que sempre inclui uma classe por padrão.

    Abaixo, examinaremos o que significa "instância".

    Por enquanto, é importante entender que podemos usar construtores para atribuir valores iniciais às nossas variáveis. O destrutor não será necessário por enquanto, mas certamente o veremos nas próximas partes da série.

    Eu recomendo fortemente o uso de construtores.

    Embora alguns programadores ignorem as advertências do compilador e assumam que as variáveis receberão um valor padrão implicitamente se não forem definidas explicitamente, essa abordagem não é completamente correta nem completamente incorreta.

    Isso ocorre porque em muitas linguagens de programação (mesmo em MQL4), variáveis sem definição explícita assumem algum valor padrão, o que não causa problemas no código. No entanto, em MQL5, você pode encontrar problemas em seu código se não definir variáveis explicitamente.

    Aqui estão os valores padrão presumidos para os tipos de dados mais comumente usados:

    Tipo Código Valor padrão assumido
     int  int test; 0
     double  double test; 0.0
     bool  bool test; false
     string  string test; NULL
     datetime  datetime test;   1970.01.01 00:00:00.

    O valor padrão presumido é o valor que você verá ao imprimir (Print) uma variável não inicializada. Se você verificar os valores com um operador if, alguns deles se comportarão conforme o esperado, enquanto outros não, o que pode levar a problemas.

    Nosso script de teste se parece com isso:

    void OnStart()
      {
       type test;
       if(test == presumedDefaultValue)
         {
          Alert("Yes, This is the default value of the test variable.");
         }
       else
         {
          Alert("No, This is NOT the default value of the test variable.");
         }
      }

    Substitua "type" pelo tipo de variável e "presumedDefaultValue" pelo valor que você acredita ser o valor padrão.

    Você verá que para bool e string, tudo funciona perfeitamente, e aparece a mensagem "Sim, este é o valor padrão da variável de teste". No entanto, para int, double e datetime, as coisas não são tão simples. Você receberá a mensagem "Não, este NÃO é o valor padrão da variável de teste". Esse resultado inesperado pode causar problemas lógicos.

    Agora que entendemos a importância do construtor, vamos ver como criá-lo:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name();
                        ~name();
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+
    

    Aqui, incluímos campos públicos e privados na classe, embora o campo privado esteja vazio no momento. É muito importante declarar o construtor com um modificador de acesso público para usá-lo em outros arquivos. Para uma melhor compreensão, vamos discutir os modificadores de acesso.

    Os modificadores de acesso determinam como o compilador pode acessar variáveis, membros de estruturas ou classes.

    Modificadores de acesso: Descrição
     público  Permite acesso ilimitado à variável ou método da classe.
     privado  Permite o acesso apenas a partir dos métodos desta classe e dos métodos publicamente herdados. Outros tipos de acesso não são possíveis;
     protegido  Permite o acesso às variáveis e métodos da classe apenas a partir dos métodos do mesmo classe.
     virtual  Aplica-se apenas a métodos da classe (mas não a métodos de estruturas) e informa ao compilador que este método deve ser colocado na tabela de funções virtuais da classe. 

    Neste artigo, estamos interessados apenas nos modificadores de acesso públicos e privados. Os outros serão abordados nas partes subsequentes da série.

    "Public" essencialmente significa que variáveis e funções podem ser usadas/modificadas em qualquer lugar, incluindo diferentes arquivos .mq5 ou .mqh. O modificador privado permite o acesso apenas à função definida na classe atual.

    Por que eles são necessários? Existem muitas razões, como ocultar dados, abstração, manutenção e reutilização de código. 

    O código do nosso construtor parece assim:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name()
      {
       var1 = 0;
       var2 = true;
      }
    //+------------------------------------------------------------------+

    O construtor/destrutor não tem um tipo de retorno.

    Aqui, simplesmente atribuímos valores às variáveis não inicializadas, porque às vezes podemos querer que a variável bool (var2 neste caso) seja inicializada como verdadeira.

    Existe uma maneira alternativa de fazer isso:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    name::name() : var1(0), var2(true)
      {
      }
    //+------------------------------------------------------------------+

    Este construtor não tem um corpo e simplesmente inicializa os valores das variáveis não inicializadas como desejamos. Você pode usar qualquer método, embora eu recomende o segundo. Por exemplo, se você estiver usando "const" ao declarar a variável para torná-la imutável, o primeiro método de atribuição de valor a uma variável não inicializada não funcionará, enquanto o segundo funcionará. Isso ocorre porque no primeiro método estamos atribuindo um valor, e no segundo, estamos inicializando.

    Ou você pode especificar o seguinte na lista de membros:

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class name
      {
    private:
       
    public:
       int               var1;
       bool              var2;
                         name() : var1(0), var2(true) {}
                        ~name();
      };
    //+------------------------------------------------------------------+



    Agora que exploramos como declarar variáveis, construtores e destrutores, criar uma função não será difícil.

    Se você deseja criar uma função chamada functionName() que aceita um parâmetro como uma variável de string e simplesmente imprime a variável, ficaria mais ou menos assim: 

    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    class className
      {
    public:
       void              functionName(string printThis)
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void className::functionName(string printThis)
      {
       Print(printThis);
      }
    //+------------------------------------------------------------------+


    Declaramos a função na lista de membros da classe com o nome className e um modificador de acesso público, para que possamos usar essa função em qualquer lugar. Em seguida, escrevemos o corpo da função.

    Observe que a declaração da função na classe, ou seja,

    functionName(string printThis)

    deve corresponder exatamente ao escrever o corpo da função.

    Com isso, concluímos nossa introdução básica à escrita de classes em MQL5.


  4. Como as usamos?


    Para entender melhor, vamos dar uma olhada na estrutura de nossas pastas:

    • Test Project
      • mainFile.mq5
      • includeFile.mqh

    Primeiro, vamos dar uma olhada no código completo de nossa classe, que escrevemos em includeFile.mqh:

    Neste exemplo, declaramos a classe className, que inclui um construtor, um destrutor, três variáveis (uma privada e duas públicas) e uma função pública.

    • Construtor: Inicializamos as variáveis var0, var1 e var2 com os valores 10, 0 e true, respectivamente.
    • Destrutor: Atualmente está vazio e, portanto, não faz nada.
    • var0: Variável inteira privada, inicializada com o valor 10 e usada na função (functionName).
    • var1: Variável inteira pública, inicializada com o valor 0 e também usada na função (functionName).
    • functionName: Função vazia functionName que recebe um número inteiro printThisNumber e imprime a soma de printThisNumber, var0 e var1.

    A seguir, examinaremos mainFile.mq5:

    #include "includeFile.mqh"
    className classInstance;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       classInstance.functionName(5)//This line will print (5+10+0) = 15
       classInstance.var1 = 50;//This will change the value of var1 to 50
       classInstance.functionName(5)//Now, this line will print (5+10+50) = 65
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+

    A seguir, vejamos mainFile.mq5: Em seguida, criamos uma instância da classe usando

    className classInstance;

    Essa instância nos permite modificar e usar variáveis ou funções da classe. Esse processo é chamado de instanciação, mas não vamos aprofundar nessa definição aqui.
    Você pode criar quantas instâncias desejar, e todas serão independentes umas das outras.

    Usamos "" em vez de <> para encontrar o arquivo .mqh, porque <> procura o arquivo .mqh na pasta include, enquanto "" procura o arquivo .mqh no diretório atual.


    Criando um painel mediante um arquivo .mqh

    Agora, vamos criar um painel do zero usando um arquivo .mqh. Se necessário, vamos emprestar partes do nosso código anterior. Para organizar eficientemente nossos arquivos, criaremos uma nova pasta chamada Movable Dashboard MQL5.

    Em seguida, criaremos dois novos arquivos: Movable_Dashboard_MQL5.mq5 servirá como nosso arquivo .mq5 principal, e o arquivo GUI_Movable.mqh conterá o código para mover o painel. A nomenclatura adequada desses arquivos é crucial para facilitar o gerenciamento de vários arquivos.

    Para começar, criaremos um painel branco de 200x200 usando o método ObjectCreate em nosso arquivo .mq5 principal (Movable_Dashboard_MQL5.mq5) no OnInit():

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //---
       //Set the name of the rectangle as "TestRectangle"
       string name = "TestRectangle";
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
       ChartRedraw();
    //---
       return(INIT_SUCCEEDED);
      }

    Resultado: 

    Figura 1. Painel Básico

    Fig. 1. Painel Básico


    Por que estamos criando o painel em nosso arquivo principal .mq5 (Movable_Dashboard_MQL5.mq5) e não em .mqh (GUI_Movable.mqh). Essa decisão é tomada principalmente em prol da simplicidade e pode depender de seus objetivos específicos. Vamos usar essa abordagem na próxima seção.

    Vamos dar uma olhada no arquivo .mqh (GUI_Movable.mqh), que atualmente se parece com isso:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
       
      };
    //+------------------------------------------------------------------+

    Aqui, simplesmente declaramos a classe sem definir explicitamente o construtor e o destrutor.

    Então, qual é o nosso objetivo? Estamos buscando adaptar este código para que possa ser implementado em nosso arquivo principal, tornando assim nosso painel móvel.

    Como podemos alcançar isso? Abaixo está o código que deve tornar o painel móvel em nosso arquivo .mq5 anterior (Movable_Dashboard_MQL5.mq5):

    //Declare some global variable that will be used in the OnChartEvent() function
    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
          
          string name = "TestRectangle";
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    

    Agora, vamos proceder da seguinte forma: 

    1. Vamos escrever o código para a classe GUI_Movable.
    2. Criaremos uma instância da classe no arquivo principal .mq5.
    3. Atribuiremos um nome a essa instância.
    4. Usaremos os métodos da classe GUI_Movable para tornar o painel móvel.

    No início, esses passos podem parecer complexos, mas com a prática, o processo se tornará intuitivo.

    1. Vamos escrever o código para a classe GUI_Movable:

      Precisamos planejar os componentes de nossa classe. Aqui está a divisão:

      1. Precisamos declarar seis variáveis com modificadores privados (previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance, movingState). Essas variáveis incluem cinco números inteiros e um valor lógico.
      2. Devemos declarar uma sétima variável pública que conterá o nome do painel de informações. Precisamos tornar essa variável pública, pois precisaremos alterá-la em nosso arquivo principal .mq5.
      3. Precisamos encontrar uma maneira de usar a função OnChartEvent no arquivo .mqh, pois todas as nossas variáveis declaradas estão lá, e precisamos dessas variáveis dentro da função OnChartEvent.


      1. Começaremos declarando seis variáveis privadas na classe, cinco delas são inteiros e uma é booleana. Usaremos um construtor para inicializar esses valores:

        //+------------------------------------------------------------------+
        //| Class GUI_Movable                                                |
        //+------------------------------------------------------------------+
        class GUI_Movable
          {
        private:
           int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
           bool              movingState;
        public:
                             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false) {};
          };
        //+------------------------------------------------------------------+

        Precisamos definir 0 para todos os inteiros e false para o booleano como nossos valores iniciais, então usamos o construtor para inicializá-los.

      2. Em seguida, declararemos uma variável pública para armazenar o nome do painel. A variável deve ser acessível a partir do nosso arquivo principal .mq5.

        public: 
           string Name;

        O valor inicial para isso, naturalmente, será NULL, mas, por formalidade, inicializaremos com NULL e alteraremos nosso construtor para (isso é apenas uma formalidade, porque o string não causa problemas de incompatibilidade)

        GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
        
      3. Este passo pode parecer um pouco complicado, mas quando você entender, tudo ficará mais fácil.

        Vamos criar uma função pública chamada OnEvent, que receberá os seguintes parâmetros: id, lparam, dparam e sparam. Como OnChartEvent() não retorna nada (void), também tornaremos OnEvent() vazia.

        A função OnEvent fará tudo o que a função OnChartEvent() original é destinada a fazer, mas ela fará isso no arquivo .mqh. Usaremos OnEvent() na função real OnChartEvent() no arquivo principal.

        Para evitar erros causados pela declaração de OnChartEvent() em ambos os .mqh e arquivos principais, criamos uma função separada chamada OnEvent(). Vamos declará-la:

        public:
           string            Name;
           void              OnEvent(int id, long lparam, double dparam, string sparam);

        Agora, escreveremos o código da função. Ela fará tudo o que a função OnChartEvent() original foi projetada para fazer:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           
          }
        //+------------------------------------------------------------------+

        Nós colocamos essa função na área de escopo global. Agora podemos simplesmente colocar o mesmo código aqui, e ele terá acesso às variáveis declaradas na classe.

        O código completo da função ficará assim:

        //+------------------------------------------------------------------+
        //|                                                                  |
        //+------------------------------------------------------------------+
        void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
          {
           //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
           if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //define X, Y, XDistance, YDistance, XSize, YSize
              int X = (int)lparam;
              int Y = (int)dparam;
              int MouseState = (int)sparam;
        
              string name = Name;
              int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
              int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
              int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
              int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
        
              if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
                {
                 mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
                 mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
                 mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
                 mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
        
                 if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                   {
                    movingState = true; //If yes the set movingState to True
                   }
        
                }
        
              if(movingState)//if movingState is true, Update the Dashboard position
                {
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
                 ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
                 ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
                 ChartRedraw(0); //Redraw Chart
                }
        
              if(MouseState == 0)//Check if MLB is not pressed
                {
                 movingState = false;//set movingState again to false
                 ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
                }
        
              previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
             }
          }
        //+------------------------------------------------------------------+
        

        A única coisa que estamos alterando é 

        string name = "TestRectangle";

        para

        string name = Name;

        porque precisamos usar a variável Name, que definimos no arquivo principal .mq5.


    2. Vamos criar uma instância da classe no arquivo principal .mq5:

      Isso pode ser feito muito facilmente da seguinte maneira:

      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;

      Aqui incluímos o arquivo .mqh, escolhendo "" em vez de <>, para especificar a localização do arquivo.<> procura o arquivo .mqh na pasta "include", enquanto "" procura o arquivo .mqh no diretório atual, que neste caso é a pasta "Movable Dashboard MQL5". Em seguida, declaramos uma instância da classe GUI_Movable e a nomeamos como Dashboard. Esse nome nos permite usar o código que escrevemos no arquivo .mqh.

    3. Vamos dar um nome a esta instância:

      Isso pode ser facilmente feito com a função OnInit(). Aqui está como nossa função OnInit() deve parecer:

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }

      No final, usamos
      //Give dashboard's name to the class instance
      Dashboard.Name = name;

      para definir a variável Name na instância Dashboard da classe GUI_Movable. Isso será usado posteriormente na função OnEvent() dentro da instância. É muito importante definir a propriedade CHART_EVENT_MOUSE_MOVE como true. Isso permite detectar eventos de mouse. Vamos repetir este passo no construtor mais tarde. Não há necessidade de complicar as coisas por enquanto.

    4. Usamos os métodos da classe GUI_Movable para tornar o painel de monitoramento móvel:

      Apesar do nome um pouco complicado, este passo é simples.

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }

      Nesta etapa, colocamos a função OnEvent() em OnChartEvent() para usar a funcionalidade OnChartEvent() no arquivo .mqh.

    Finalmente, aqui está o nosso código completo:

    Estrutura de pastas:

    • Movable Dashboard MQL5
      • Movable_Dashboard_MQL5.mq5
      • GUI_Movable.mqh

    1. Movable_Dashboard_MQL5.mq5
      #include "GUI_Movable.mqh"
      GUI_Movable Dashboard;
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         //---
         //Set the name of the rectangle as "TestRectangle"
         string name = "TestRectangle";
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, 200);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Dashboard.Name = name;
         
      //---
         return(INIT_SUCCEEDED);
        }
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
        {
         Dashboard.OnEvent(id, lparam, dparam, sparam);
        }
      //+------------------------------------------------------------------+
      
    2. GUI_Movable.mqh
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
        {
         //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
         if(id == CHARTEVENT_MOUSE_MOVE)
           {
            //define X, Y, XDistance, YDistance, XSize, YSize
            int X = (int)lparam;
            int Y = (int)dparam;
            int MouseState = (int)sparam;
      
            string name = Name;
            int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
            int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
            int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
            int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
      
            if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
              {
               mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
               mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
               mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
               mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
      
               if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
                 {
                  movingState = true; //If yes the set movingState to True
                 }
      
              }
      
            if(movingState)//if movingState is true, Update the Dashboard position
              {
               ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
               ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
               ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
               ChartRedraw(0); //Redraw Chart
              }
      
            if(MouseState == 0)//Check if MLB is not pressed
              {
               movingState = false;//set movingState again to false
               ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
              }
      
            previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
           }
        }
      //+------------------------------------------------------------------+
      

    Compile primeiro o arquivo .mqh e depois o arquivo .mq5. Isso criará um arquivo .ex5 que pode ser executado em um gráfico.

    Com essas etapas, repetimos o que foi alcançado na parte 1, usando um código mais eficiente. Observe a diferença significativa no volume de código usado no arquivo principal .mq5 nas partes 1 e 2. E o melhor ainda está por vir.


    Resultado:

    Figura. 2. Painel Móvel Simples

    Fig. 2. Painel Móvel Simples



    Configurando dois painéis em um único gráfico mediante o arquivo .mqh

    Agora, em vez de criar um painel de monitoramento com ObjectCreate em nosso arquivo principal .mq5, faremos isso em nosso arquivo .mqh. Você verá como tudo se tornará mais fácil posteriormente.

    Vamos aprofundar nas alterações que faremos no nsso arquivo .mqh:

    1. Precisamos alterar o modificador da variável string Name de público para privado. Name não é necessário no nosso arquivo principal - a variável é usada apenas no arquivo .mqh. Vamos torná-la privada. Isso pode ser alcançado da seguinte maneira:

      De:
      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
      public:
      	             GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         string            Name;
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      Para: 

      //+------------------------------------------------------------------+
      //| Class GUI_Movable                                                |
      //+------------------------------------------------------------------+
      class GUI_Movable
        {
      private:
         int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
         bool              movingState;
         string            Name;
      public:
      		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
         void              OnEvent(int id, long lparam, double dparam, string sparam);
        };

      Nós simplesmente mudamos a localização

      string            Name;
      Isso alterou o modificador de variável de público para privado.

    2. Em seguida, adicionamos um método público chamado CreateDashboard(). O método receberá os seguintes parâmetros de entrada: name (string), xDis (int), yDis (int), xSize (int), ySize (int).

      Primeiro, adicionamos tudo isso à lista de membros da classe:

      public:
         void              OnEvent(int id, long lparam, double dparam, string sparam);
         void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);

      Agora, vamos definir essa função no escopo global, copiando o código do nosso arquivo principal da seguinte maneira:

      //+------------------------------------------------------------------+
      //|                                                                  |
      //+------------------------------------------------------------------+
      void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
         //Create a Rectangle Label Object at (time1, price1)=(0,0)
         ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
         //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
         //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
         //Set XSize to 200px i.e. Width of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
         //Set YSize to 200px i.e. Height of Rectangle Label
         ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
         //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
         ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
         //Give dashboard's name to the class instance
         Name = name;
         //Redraw Chart
         ChartRedraw();
      }
      //+------------------------------------------------------------------+
      


    Depois disso, precisamos alterar nosso arquivo .mq5:

    #include "GUI_Movable.mqh"
    GUI_Movable Dashboard1;
    GUI_Movable Dashboard2;
    
    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       Dashboard1.CreateDashboard("Dashboard1", 100, 100, 200, 200);
       Dashboard2.CreateDashboard("Dashboard2", 100, 350, 200, 200);
    //---
       return(INIT_SUCCEEDED);
      }
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       Dashboard1.OnEvent(id, lparam, dparam, sparam);
       Dashboard2.OnEvent(id, lparam, dparam, sparam);
      }
    //+------------------------------------------------------------------+
    

    Vamos analisar este código:

    Começamos incluindo o arquivo GUI_Movable.mqh, que contém a definição da classe GUI_Movable. Nesta classe, existem métodos para criar e lidar com eventos relacionados a um painel informativo móvel.

    Em seguida, declaramos dois objetos da classe GUI_Movable - Dashboard1 e Dashboard2. Esses objetos representam dois painéis de monitoramento que criaremos e controlaremos em nosso programa.

    Na função OnInit(), que é automaticamente chamada ao iniciar o EA, criamos dois painéis de monitoramento chamando o método CreateDashboard() em nossos dois objetos, passando o nome do painel informativo, sua posição e tamanho (em pixels) como parâmetros para esse método. Em seguida, a função retorna INIT_SUCCEEDED, indicando que a inicialização foi bem-sucedida.

    Por fim, temos a função OnChartEvent(), que é acionada sempre que ocorre um evento no gráfico (por exemplo, um clique do mouse ou um movimento). Nesta função, chamamos o método OnEvent() para os dois objetos do nosso painel informativo, passando todos os parâmetros recebidos. Isso permite que cada painel informativo trate o evento de forma independente, de acordo com a lógica definida no método OnEvent() da classe GUI_Movable.

    Como você pode ver, essa abordagem é simples, compreensível e, ao mesmo tempo, mantém a mesma funcionalidade. Isso torna o código muito conveniente para uso em EAs/indicadores completos.

    Código completo do arquivo .mqh:

    //+------------------------------------------------------------------+
    //| Class GUI_Movable                                                |
    //+------------------------------------------------------------------+
    class GUI_Movable
      {
    private:
       int               previousMouseState, mlbDownX, mlbDownY, mlbDownXDistance, mlbDownYDistance;
       bool              movingState;
       string            Name;
    public:
    		     GUI_Movable() : previousMouseState(0), mlbDownX(0), mlbDownY(0), mlbDownXDistance(0), mlbDownYDistance(0), movingState(false), Name(NULL) {};
       void              OnEvent(int id, long lparam, double dparam, string sparam);
       void              CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize);
      };
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::OnEvent(int id, long lparam, double dparam, string sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = Name;
          int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click
            {
             mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X
             mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y
             mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance
             mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard
               {
                movingState = true; //If yes the set movingState to True
               }
    
            }
    
          if(movingState)//if movingState is true, Update the Dashboard position
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)
             ChartRedraw(0); //Redraw Chart
            }
    
          if(MouseState == 0)//Check if MLB is not pressed
            {
             movingState = false;//set movingState again to false
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again
            }
    
          previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value
         }
      }
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void GUI_Movable::CreateDashboard(string name, int xDis, int yDis, int xSize, int ySize) {
       //Create a Rectangle Label Object at (time1, price1)=(0,0)
       ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
       //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xDis);
       //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
       ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yDis);
       //Set XSize to 200px i.e. Width of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_XSIZE, xSize);
       //Set YSize to 200px i.e. Height of Rectangle Label
       ObjectSetInteger(0, name, OBJPROP_YSIZE, ySize);
       //Set CHART_EVENT_MOUSE_MOVE to true to detect mouse move event
       ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
       //Give dashboard's name to the class instance
       Name = name;
       //Redraw Chart
       ChartRedraw();
    }
    //+------------------------------------------------------------------+
    

    Resultado: 

    Fig. 3. Dois Painéis Móveis em um Único Gráfico

    Fig. 3. Dois Painéis Móveis em um Único Gráfico


    Em relação à hierarquia do painel, o painel criado anteriormente estará em uma posição inferior em comparação com o painel criado posteriormente.


    Conclusão

    Para aqueles que desejam tornar sua existente painel móvel, o processo é bastante simples. Ao estudar a seção "Criando um painel com um arquivo .mqh", você verá que pode transformar qualquer painel em móvel com apenas algumas linhas de código em um EA ou indicador existente. Tudo o que é necessário para isso é conectar o arquivo GUI_Movable.mqh e criar uma instância da classe atribuindo um nome ao painel. Com essas etapas simples, seu painel de controle se tornará interativo e você poderá movê-lo facilmente com o mouse.


    Aprendemos a aumentar a interatividade do painel de informações, tornando-o móvel. O conhecimento adquirido pode ser aplicado a qualquer EA ou indicador existente ou ao criar um novo do zero.

    Este artigo ficou um pouco extenso. O conceito de classes é bastante complexo de explicar e entender, mas acredito que esse conhecimento será muito útil em sua jornada de aprendizado em programação.

    Espero sinceramente que este artigo tenha sido útil para você de alguma forma.

    Tenha sucesso na programação e nos seus negócios!


    Traduzido do Inglês pela MetaQuotes Ltd.
    Artigo original: https://www.mql5.com/en/articles/12880

    Arquivos anexados |
    MQL5.zip (5.01 KB)
    Pode o Heiken-Ashi em combinação com médias móveis oferecer bons sinais? Pode o Heiken-Ashi em combinação com médias móveis oferecer bons sinais?
    Combinar estratégias pode aumentar a eficácia da negociação. Podemos combinar indicadores e padrões para obter confirmações adicionais. As médias móveis nos ajudam a confirmar a tendência e a segui-la. Este é o indicador técnico mais conhecido, o que se explica pela sua simplicidade e eficácia comprovada na análise.
    Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD Desenvolvimento de um Cliente MQTT para o MetaTrader 5: Metodologia TDD
    Este artigo apresenta a primeira tentativa de desenvolver um cliente MQTT nativo para o MQL5. MQTT é um protocolo de troca de dados no formato "publicador - assinante". Ele é leve, aberto, simples e projetado para ser facilmente implementado. Isso o torna aplicável em muitas situações.
    Agora é mais fácil criar painéis gráficos no MQL5 Agora é mais fácil criar painéis gráficos no MQL5
    Neste artigo, apresentaremos um guia simples e claro para quem deseja criar uma das ferramentas mais valiosas e úteis na negociação, nomeadamente um painel gráfico que simplifica as tarefas de negociação. Os painéis gráficos permitem que você economize tempo e se concentre mais na negociação em si.
    Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI(Parte 1): Usando RestAPIs em MQL5 Desenvolvendo um agente de Aprendizado por Reforço em MQL5 com Integração RestAPI(Parte 1): Usando RestAPIs em MQL5
    Este artigo aborda a importância das APIs (Interfaces de Programação de Aplicativos) na comunicação entre diferentes aplicativos e sistemas de software. Ele destaca o papel das APIs na simplificação da interação entre aplicativos, permitindo que eles compartilhem dados e funcionalidades de maneira eficiente.