English Русский 中文 Español Deutsch 日本語
preview
Criando um painel MQL5 interativo usando a classe Controls (Parte 2): Adicionando responsividade aos botões

Criando um painel MQL5 interativo usando a classe Controls (Parte 2): Adicionando responsividade aos botões

MetaTrader 5Negociação |
30 9
Allan Munene Mutiiria
Allan Munene Mutiiria

Introdução

No artigo anterior, criamos com sucesso os componentes principais do nosso painel usando a linguagem MetaQuotes Language 5. Até este ponto, os botões e rótulos que montamos permaneciam estáticos, fornecendo uma estrutura básica, mas inativa. Agora chegou a hora de ir além do visual. Neste artigo, vamos focar em tornar o painel realmente interativo. Vamos dar vida aos componentes, adicionando a capacidade de responder às ações do usuário, transformando nosso painel de controle em uma ferramenta dinâmica, pronta para interação de trading em tempo real.

Vamos abordar a automatização da funcionalidade dos botões criados na primeira parte, para que eles respondam ao clique e à edição. Aprenderemos como configurar eventos que disparam ações específicas, permitindo ao usuário interagir de forma significativa com o painel. Veremos os seguintes pontos-chave:

  1. Elementos que serão automatizados – uma visão detalhada dos componentes que ganharão funcionalidade.
  2. Automatização da interação com a interface gráfica do usuário em MQL5 – o código necessário para que os botões respondam de forma eficaz às ações do usuário.
  3. Considerações finais – encerramento.

Vamos nos aprofundar nesses temas para melhorar nossa interface de negociação!


Elementos que serão automatizados

Vamos focar na automatização dos botões que criamos na primeira parte do nosso painel MQL5. Cada botão tem uma função específica, e queremos garantir que eles sejam intuitivos e respondam aos comandos do usuário. Essa resposta é essencial, pois, ao contrário de um programa que roda em segundo plano, o painel de negociação precisa ser prático e acessível ao usuário. Primeiramente, temos um botão no canto superior direito do painel, que serve para fechar toda a interface. Se o ambiente de negociação estiver aberto no gráfico do MetaTrader 5, deve haver uma maneira de fechar o painel da mesma forma que se fecha um aplicativo.

Enquanto a botão de trading estiver ativa, vamos posicionar botões que executam operações específicas de negociação. Entre eles estão: Buy (compra), Sell (venda), Sell Stop (ordem stop de venda), Sell Limit (ordem limitada de venda), Buy Stop (ordem stop de compra) e Buy Limit (ordem limitada de compra). Esses botões permitirão colocar ordens rapidamente e vão possibilitar uma resposta imediata ao mercado em constante mudança. Também vamos automatizar os botões de fechamento, que de fato vão gerenciar as operações enquanto a botão de fechamento estiver ativa. Entre eles estão: Close All (fechar tudo) e Close All Profit Trades (fechar todas as operações com lucro), além de um que, só de mencionar, me dá até um arrepio nos dedos – Close All Pending Orders (fechar todas as ordens pendentes).

Por fim, vamos automatizar o botão de informação, que ao ser clicado, expande a interface de botões contendo dados detalhados sobre a conta do usuário e informações adicionais. Isso ajudará a manter os traders informados sobre detalhes importantes relacionados às suas contas, ajudando-os a tomar decisões mais fundamentadas. O objetivo de tudo isso é criar um painel de negociação adaptável que simplifique a execução das operações necessárias para o trader.

Para facilitar o entendimento desses processos e dos componentes de automação, abaixo está uma descrição detalhada com base na etapa anterior.

Componentes

Vamos começar a automatizar. Se você ainda não sabe como criar a montagem estática dos elementos da interface gráfica, recomendo a leitura do artigo anterior.


Automatização da interação com a interface gráfica em MQL5

Vamos avançar dos processos simples para os mais complexos, de forma que nossa estrutura esteja organizada em ordem cronológica. Vamos atualizar as informações da conta a cada alteração de tick ou cotação de preço. Para isso, precisaremos do manipulador de eventos OnTick, uma função nativa do MQL5 que é chamada sempre que há uma alteração nas cotações de preço. A função tem o tipo de dado void, o que significa que ela executa diretamente e não deve retornar nenhuma saída. A função deve ter uma aparência semelhante ao exemplo abaixo.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

  ...
}
//+------------------------------------------------------------------+

Esse é o manipulador de eventos responsável por atualizar os preços e, portanto, é a base da nossa lógica. Vamos adicionar a lógica de controle a essa função, como mostrado abaixo:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   //--- Start of the OnTick function, called on every price tick

   //--- Check if the background color of the INFO button is yellow
   if (obj_Btn_INFO.ColorBackground() == clrYellow) {
      //--- Update the account equity display on the panel
      obj_Btn_ACC_EQUITY.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2));

      //--- Update the account balance display on the panel
      obj_Btn_ACC_BALANCE.Text(DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2));

      //--- Update the server trade time display on the panel
      obj_Btn_TIME.Text(TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS));
   }

   //--- End of the OnTick function
}
//+------------------------------------------------------------------+

A primeira ação que realizamos na função OnTick é verificar a cor de fundo do botão de informação (obj_Btn_INFO). Se o fundo do botão estiver amarelo, isso indica que ativamos o modo de exibição das informações. Quando essa condição for satisfeita, atualizaremos diversas exibições no painel relacionadas à conta. Em particular, atualizamos a exibição do saldo da conta, extraindo o saldo atual com a função AccountInfoDouble e passando a propriedade ACCOUNT_EQUITY como parâmetro de entrada, formatando o valor com dois dígitos decimais usando a função DoubleToString. Em seguida, atribuímos esse valor ao texto do botão obj_Btn_ACC_EQUITY, garantindo que sempre tenhamos as informações mais recentes do equity à mão.

Depois, atualizamos da mesma forma a exibição do saldo da conta, extraindo-o com a propriedade ACCOUNT_BALANCE, formatando para duas casas decimais e definindo como texto do botão obj_Btn_ACC_BALANCE. Por fim, atualizamos a exibição da hora da negociação no servidor, obtendo o horário atual da negociação no servidor com a função TimeTradeServer, formatando-o para incluir tanto a data quanto os segundos, e atualizando o texto do botão obj_Btn_TIME com esse valor. Isso garante que estejamos sempre informados sobre os eventos de negociação mais recentes, o que é essencial para tomar decisões rápidas em cenários de trading. Abaixo estão os resultados.

Atualização ONTICK

A partir da visualização, é possível ver que o campo de tempo está sendo atualizado corretamente. Assim, o primeiro componente da automação está concluído. Até agora tudo parece relativamente simples. Vamos continuar com os outros componentes do nosso painel gráfico. A automatização dos demais elementos será realizada dentro do manipulador da função OnChartEvent, por isso vamos analisar com mais detalhes seus parâmetros de entrada e suas funções.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
      ...
}

A finalidade da função é lidar com as alterações no gráfico feitas pelo usuário ou por um programa em MQL5. Dessa forma, interações que o usuário executar, como movimentar o mouse, editar campos dos botões e clicar em rótulos e botões, serão detectadas e tratadas por esse manipulador de eventos. Vamos destrinchar seus argumentos para uma interpretação mais detalhada:

  • id – o parâmetro representa o identificador do evento e corresponde a um dos 11 tipos de eventos predefinidos. Entre eles estão eventos como pressionamento de teclas, movimentações do mouse, criação de objetos, mudanças no gráfico e eventos personalizados. Para eventos personalizados, é possível utilizar identificadores entre CHARTEVENT_CUSTOM e CHARTEVENT_CUSTOM_LAST. Abaixo estão listados os 11 tipos de eventos:

Tipos de eventos do gráfico

  • lparam – parâmetro do evento do tipo long. Seu valor depende do evento específico que está sendo tratado. Por exemplo, ele pode representar o código de uma tecla durante um evento de pressionamento de tecla.
  • dparam – parâmetro do evento do tipo double. Assim como o lparam, seu valor varia de acordo com o tipo de evento. Por exemplo, durante o movimento do mouse, ele pode transmitir a posição do cursor do mouse.
  • sparam – parâmetro do evento do tipo string. Novamente, seu valor depende do evento. Por exemplo, ao criar um objeto, ele pode conter o nome do objeto recém-criado.

Para uma demonstração mais clara, dentro da função vamos incluir um print que contenha todos os quatro argumentos do log.

// Print the 4 function parameters    
Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);

A função exibirá o identificador do evento do gráfico, seu valor do tipo long, o valor do tipo double e o valor em string. Vamos observar o GIF a seguir para uma melhor compreensão.

Eventos gerais do gráfico

A partir do GIF apresentado, agora tudo deve estar claro. Vamos passar para a detecção de cliques do mouse nos elementos da interface gráfica. Assim, nosso identificador será CHARTEVENT_OBJECT_CLICK.

   //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);

   if (id==CHARTEVENT_OBJECT_CLICK){
        
        //---
   }

Primeiramente, vamos comentar a linha de código anterior para não poluir nosso log com informações desnecessárias. Usamos duas barras (//), conhecidas como comentários de linha única, que comentam o código do início até o final da linha — daí o nome "linha única". Comentários são ignorados pelo computador durante a execução. Usamos a instrução if para verificar se houve um clique em um objeto. Isso é feito comparando o identificador do evento do gráfico com o enumerador de cliques em objetos. Após o clique no objeto, vamos imprimir os argumentos e observar os resultados. O código a seguir é utilizado. 

   if (id==CHARTEVENT_OBJECT_CLICK){
      Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam);

     ...
   }

Na função de impressão, simplesmente alteramos LPARAM para LP e DPARAM para DP, para focar apenas no identificador do evento do gráfico e no nome do objeto clicado. A partir daí, obtemos o identificador do objeto e tomamos uma ação, se necessário. Abaixo está uma ilustração da lógica:

Clique no objeto (GIF)

O primeiro botão que vamos automatizar é o botão Trade. 

      if (sparam==obj_Btn_TRADE.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name());

         // Reset the pressed states of all buttons to ensure only one button appears pressed at a time
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Change the background color of the Trade button to yellow to indicate it is active
         obj_Btn_TRADE.ColorBackground(clrYellow);
         // Set the background color of the Close and Info buttons to silver to indicate they are inactive
         obj_Btn_CLOSE.ColorBackground(clrSilver);
         obj_Btn_INFO.ColorBackground(clrSilver);

         // Set the border color of the Trade button to yellow to match its background
         obj_Btn_TRADE.ColorBorder(clrYellow);
         // Set the border color of the Close and Info buttons to silver to indicate they are inactive
         obj_Btn_CLOSE.ColorBorder(clrSilver);
         obj_Btn_INFO.ColorBorder(clrSilver);

         // Call a function to destroy the Close section if it exists
         destroySection_Close();
         // Call a function to destroy the Information section if it exists
         destroySection_Information();

         // Create the Trade section, bringing it to the forefront
         createSection_Trade();
      }

Aqui, gerenciamos a interação ao clicar no botão Trade, verificando se o parâmetro do tipo string corresponde ao nome do botão Trade, utilizando a função Name. Primeiro usamos a função Print para registrar o nome do objeto clicado, o que ajuda na depuração, confirmando qual botão foi pressionado. Em seguida, redefinimos o estado pressionado de todos os botões relevantes — obj_Btn_TRADE, obj_Btn_CLOSE e obj_Btn_INFO — chamando a função Pressed para cada um deles e passando o valor false. Isso garante que apenas um botão esteja visualmente pressionado por vez. Após o reset, indicamos visualmente que o botão Trade está ativo, alterando a cor do seu fundo para amarelo com a função ColorBackground, e definimos a cor de fundo dos botões Close e Info como prata, para mostrar que estão inativos. Da mesma forma, atualizamos as cores das bordas usando a função ColorBorder — amarelo para o botão Trade e prata para os demais.

Em seguida, limpamos a interface chamando as funções destroySection_Close e destroySection_Information para remover qualquer conteúdo exibido nas seções Close ou Information. Por fim, chamamos a função createSection_Trade para criar e exibir dinamicamente a seção de negociação, garantindo que o usuário tenha acesso à interface de trading. Realizamos uma ação semelhante com o botão Close.

      // Check if the clicked object is the Close button
      else if (sparam==obj_Btn_CLOSE.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name());

         // Reset the pressed states of all buttons
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Set the background color of the Trade button to silver, indicating it's inactive
         obj_Btn_TRADE.ColorBackground(clrSilver);
         // Change the background color of the Close button to yellow to indicate it is active
         obj_Btn_CLOSE.ColorBackground(clrYellow);
         obj_Btn_INFO.ColorBackground(clrSilver);

         // Set the border color of the Trade button to silver
         obj_Btn_TRADE.ColorBorder(clrSilver);
         // Set the border color of the Close button to yellow
         obj_Btn_CLOSE.ColorBorder(clrYellow);
         obj_Btn_INFO.ColorBorder(clrSilver);

         // Call a function to destroy the Trade section if it exists
         destroySection_Trade();
         // Call a function to destroy the Information section if it exists
         destroySection_Information();

         // Create the Close section, bringing it to the forefront
         createSection_Close();
      }

Aqui aplicamos praticamente a mesma lógica utilizada no botão Trade. Processamos o cenário do clique no botão Close verificando se o parâmetro do tipo string corresponde ao nome do botão Close, usando a função Name. Primeiro, registramos o nome do botão pressionado com a função Print, para fins de depuração. Isso nos ajuda a confirmar que o botão correto foi clicado. Em seguida, redefinimos os estados pressionados dos botões obj_Btn_TRADE, obj_Btn_CLOSE e obj_Btn_INFO usando a função Pressed. Isso garante que nenhum dos botões permaneça visualmente pressionado após o clique. Depois atualizamos a interface alterando a cor de fundo do botão Close para amarelo com a função ColorBackground, indicando que está ativo, e definimos a cor de fundo dos botões Trade e Information como prata, sinalizando que estão inativos.

De forma semelhante, configuramos as cores das bordas dos botões com a função ColorBorder, deixando a borda do botão Close em amarelo para combinar com seu fundo, e definimos as bordas dos botões Trade e Information como prateadas, indicando que estão inativos. Para limpar o conteúdo, chamamos as funções destroySection_Trade e destroySection_Information, removendo qualquer conteúdo existente das seções de negociação ou informação. Por fim, chamamos a função createSection_Close para criar e exibir dinamicamente a interface relacionada ao fechamento de posições ou ordens, garantindo que o usuário possa interagir com as opções de encerramento na interface da plataforma. O botão Information utiliza a mesma lógica. O trecho de código é o seguinte:

      // Check if the clicked object is the Information button
      else if (sparam==obj_Btn_INFO.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_INFO.Name());

         // Reset the pressed states of all buttons
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Set the background color of the Trade and Close buttons to silver, indicating they are inactive
         obj_Btn_TRADE.ColorBackground(clrSilver);
         obj_Btn_CLOSE.ColorBackground(clrSilver);
         // Change the background color of the Info button to yellow to indicate it is active
         obj_Btn_INFO.ColorBackground(clrYellow);

         // Set the border color of the Trade and Close buttons to silver
         obj_Btn_TRADE.ColorBorder(clrSilver);
         obj_Btn_CLOSE.ColorBorder(clrSilver);
         // Set the border color of the Info button to yellow
         obj_Btn_INFO.ColorBorder(clrYellow);

         // Call a function to destroy the Trade section if it exists
         destroySection_Trade();
         // Call a function to destroy the Close section if it exists
         destroySection_Close();

         // Create the Information section, bringing it to the forefront
         createSection_Information();
      }

Ao compilar e executar o programa, obtemos a seguinte saída ao interagir com o botão de controle.

Cliques nos botões de controle

Tudo está funcionando. Agora queremos remover o painel ao clicar no botão Windows.

      // Check if the clicked object is the exit button (X button)
      else if (sparam==obj_Btn_X.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_X.Name());

         // Call functions to destroy all sections, effectively closing the entire panel
         destroySection_Trade();
         destroySection_Close();
         destroySection_Information();

         // Call a function to destroy the main panel itself
         destroySection_Main_Panel();
      }

Neste caso, tratamos o clique no botão de saída verificando se o parâmetro do tipo string corresponde ao nome do botão de saída, usando a função Name. Primeiro, registramos o nome do objeto selecionado para fins de depuração usando a função Print, o que ajuda a confirmar que o botão de saída foi realmente clicado.

Em seguida, iniciamos o processo de encerramento completo do painel, chamando diversas funções. Primeiro, chamamos destroySection_Trade, destroySection_Close e destroySection_Information para remover todas as seções relacionadas à negociação, ao fechamento de posições ou à exibição de informações da conta, caso estejam atualmente visíveis. Essas funções garantem a limpeza adequada de todas as seções interativas do painel. Por fim, chamamos a função destroySection_Main_Panel, que é responsável por remover a própria estrutura principal do painel. Isso elimina todos os elementos do painel da interface, encerrando-o completamente.

Encerramento do painel

Agora precisamos processar as operações de trading ao clicar nos botões correspondentes. Para facilitar isso, devemos incluir uma instância de classe que nos ajudará nessa tarefa. Assim, incluímos uma instância de trading usando a #include no início do código-fonte. Isso nos dá acesso à classe CTrade, que será usada para criar o objeto de negociação necessário à execução das operações de compra e venda.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

O pré-processador substituirá a linha #include<Trade/Trade.mqh> pelo conteúdo do arquivo Trade.mqh. Os sinais de menor e maior indicam que o arquivo Trade.mqh será carregado a partir do diretório padrão (geralmente localizado em pasta_de_instalação_do_terminal\MQL5\Include). O diretório atual não é incluído na busca. A linha pode ser colocada em qualquer parte do código, mas, por convenção e organização, todos os includes são colocados no início do arquivo-fonte, facilitando a estruturação e a referência do código. Graças aos desenvolvedores do MQL5, a declaração do objeto obj_Trade da classe CTrade nos fornecerá acesso direto aos métodos disponíveis nessa classe.

Classe CTRADE

Depois de incluir a biblioteca de negociação, podemos começar a implementar a lógica de abertura de uma posição de venda ao pressionar o botão Sell. A lógica é apresentada abaixo.

      else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
         double entry_price = Bid; //--- Set the entry price for selling to the current bid price
         double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
         double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
         
         Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
         obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order
      }

Aqui, assim que confirmamos que o botão Sell foi pressionado, registramos o evento de clique usando a função Print para fins de depuração. Com isso, o nome do botão pressionado é exibido. Em seguida, começamos a coletar os dados essenciais da operação de trading. Primeiro, extraímos e normalizamos os preços atuais de ask e bid utilizando a função SymbolInfoDouble, que obtém os preços de mercado. A função NormalizeDouble garante que esses preços sejam corretamente formatados de acordo com o número de casas decimais definido por _Digits.

Em seguida, obtemos o tamanho do lote a partir do campo de entrada, convertendo o texto do campo Lots em um número com a função StringToDouble. O preço de entrada é definido com base no preço atual de bid, pois a ordem de venda utiliza esse preço para execução. Também calculamos os valores de stop loss e take profit com base nos dados inseridos pelo usuário. O stop loss é calculado somando o valor definido pelo usuário (do campo SL) ao preço ask, ajustado pelo menor incremento de preço do símbolo — _Point. Da mesma forma, o take profit é calculado subtraindo o valor fornecido pelo usuário (do campo TP) do preço ask.

Por fim, registramos os dados da ordem, como tamanho do lote, preço de entrada, stop loss e take profit, utilizando a função Print. A ordem de venda é executada por meio da função Sell do objeto obj_Trade, passando os parâmetros necessários: tamanho do lote, símbolo, preço de entrada, stop loss e take profit. Para a posição de compra, a lógica é semelhante, conforme mostrado abaixo.

      else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
         double entry_price = Ask; //--- Set the entry price for buying to the current ask price
         double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
         double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
         
         Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
         obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order
      }

Ao compilar e testar o progresso, obtemos a seguinte saída.

BOTÕES SELL/BUY

Para abrir ordens limitadas e ordens stop, usamos uma lógica semelhante. No entanto, como elas não usam diretamente as cotações de mercado atuais, será necessário configurar e incluir um algoritmo adicional para verificação de segurança. Começaremos pelo botão Sell Stop.

      else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order
         
         if (user_price > valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message
         }
         else if (user_price <= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the sell stop order
            double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order
         }
      }

Aqui, tratamos a lógica de automação ao clicar no botão Sell Stop, verificando se o parâmetro da variável string corresponde ao nome do botão Sell Stop ao usar a função Name. Assim que isso for confirmado, registramos o evento como de costume com a função Print, exibindo o nome do botão pressionado para depuração. Começamos extraindo e normalizando os preços ask e bid com a função SymbolInfoDouble para obter as cotações de mercado, e utilizamos NormalizeDouble para formatá-las de acordo com a quantidade de casas decimais definida por _Digits.

Depois, obtemos o "preço definido pelo usuário" a partir do campo Price, convertendo-o de texto para valor numérico com a função StringToDouble. Além disso, extraímos o nível mínimo de stop para o símbolo, necessário para a validação de ordens stop, usando a função SymbolInfoInteger com o parâmetro SYMBOL_TRADE_STOPS_LEVEL. Calculamos o preço válido subtraindo o nível de stop (convertido em pontos) do preço bid.

Em seguida, verificamos se o "preço definido pelo usuário" é válido, comparando-o com o preço válido calculado. Se o preço informado pelo usuário estiver acima do limite permitido, registramos uma mensagem de erro indicando "preço de stop inválido". Se o preço for válido, seguimos com os próximos passos. Extraímos o tamanho do lote do campo de entrada, usando StringToDouble para converter o texto em número. O preço de entrada é definido com base no valor fornecido pelo usuário. Depois, calculamos o stop loss somando o valor fornecido no campo SL ao preço de entrada, ajustando conforme o incremento de preço do símbolo _Point. Da mesma forma, o take profit é calculado subtraindo o valor inserido no campo TP do preço de entrada.

Por fim, registramos os dados da ordem, incluindo o tamanho do lote, "preço de entrada", "stop loss" e "take profit", utilizando a função Print. A ordem stop de venda é executada chamando a função Sell Stop do objeto obj_Trade, passando os parâmetros correspondentes, como tamanho do lote, preço de entrada, símbolo, stop loss e take profit, além de posicionar a ordem stop de venda conforme as instruções do usuário. Para executar as demais ordens, usamos uma abordagem semelhante.

      else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order
         
         if (user_price < valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message
         }
         else if (user_price >= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the buy stop order
            double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order
         }
      }
      else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order
         
         if (user_price < valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message
         }
         else if (user_price >= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the sell limit order
            double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order
         }
      }
      else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order
         
         if (user_price > valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message
         }
         else if (user_price <= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the buy limit order
            double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order
         }
      }

Durante os testes com os botões, obtemos as seguintes saídas.

Ordens pendentes

Tudo está funcionando corretamente. Passamos para o fechamento de posições e remoção de ordens pendentes. Começamos com a lógica de encerramento de todas as posições de mercado abertas.

      else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     obj_Trade.PositionClose(pos_ticket); //--- Close the position
                  }
               }
            }
         }
      }

Aqui, simplesmente tratamos a lógica de automação do botão Close All, que ao ser pressionado fecha todas as posições ativas. Começamos verificando se o botão foi pressionado e registramos o evento no log, exibindo o nome do botão pressionado para fins de depuração usando a função Print. Em seguida, iniciamos um laço for que percorre todas as posições abertas na conta, utilizando a função PositionsTotal, que retorna o número total de posições abertas. O laço começa a partir da última posição da lista (representada como "PositionsTotal() - 1") e percorre em ordem reversa, decrementando i a cada iteração para garantir que todas as posições sejam tratadas.

Para cada iteração, usamos a função PositionGetTicket para obter o número do tíquete da posição atual. O tíquete identifica de forma única cada posição. Depois, verificamos se o tíquete é válido, assegurando que seu valor seja maior que 0. Se for válido, usamos a função PositionSelectByTicket para selecionar a posição associada a esse tíquete.

Após selecionar a posição, verificamos se ela corresponde ao símbolo com o qual estamos lidando, usando a função PositionGetString com o parâmetro POSITION_SYMBOL e comparando-o com _Symbol, que representa o símbolo de trading atual. Se a posição corresponder ao símbolo, chamamos a função PositionClose do objeto obj_Trade e passamos o número do tíquete como parâmetro para fechar a posição. Esse processo continua até que todas as posições referentes ao símbolo de negociação sejam encerradas. Para fechar apenas as posições de venda, usaremos a mesma função, mas adicionando uma lógica de controle extra da seguinte forma.

      else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        obj_Trade.PositionClose(pos_ticket); //--- Close the sell position
                     }
                  }
               }
            }
         }
      }

Essa abordagem é semelhante à que utilizamos para o fechamento de todas as posições. Assim, destacamos uma nova seção para focar na lógica adicional usada para fechar apenas as posições de venda ao clicar no botão Close All Sell. Após percorrer todas as posições, extraímos o tipo de cada uma usando a função PositionGetInteger com a constante POSITION_TYPE. Esse valor é então convertido para o enumerador ENUM_POSITION_TYPE para facilitar a compreensão (tipagem). Depois de determinar o tipo da posição, verificamos especificamente se a posição atual é uma posição de venda, comparando com a propriedade POSITION_TYPE_SELL. Se a condição for avaliada como verdadeira, o que indica que se trata realmente de uma venda, procedemos com o fechamento usando a função PositionClose do objeto obj_Trade. O mesmo fazemos ao fechar posições de compra.

      else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        obj_Trade.PositionClose(pos_ticket); //--- Close the buy position
                     }
                  }
               }
            }
         }
      }

Ao fechar todas as posições de venda que estão em prejuízo, ainda precisamos expandir a abordagem atual, incluindo nela o lucro da posição selecionada, o qual usaremos para comparar e determinar se ela está lucrativa ou com perdas. A lógica é apresentada abaixo.

      else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss < 0){ //--- Check if the position is at a loss
                           obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position
                        }
                     }
                  }
               }
            }
         }
      }

Aqui, ao percorrer todas as posições e confirmar que a posição atual é do tipo sell, incluímos uma verificação de lucro/prejuízo para cada posição. A função PositionGetDouble extrai o lucro/prejuízo atual da posição selecionada, utilizando a constante POSITION_PROFIT, para determinar se a posição está positiva ou negativa. Em seguida, adicionamos uma instrução condicional para verificar se o valor do lucro/prejuízo é menor que zero, o que indica que a posição está com prejuízo. Se essa condição for satisfeita, procedemos ao fechamento da posição de venda em prejuízo, chamando a função PositionClose. Essa lógica adicional, destacada em amarelo, garante que apenas as posições de venda atualmente em prejuízo sejam encerradas. Usamos uma abordagem semelhante para os demais botões.

      else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss < 0){ //--- Check if the position is at a loss
                           obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position
                        }
                     }
                  }
               }
            }
         }
      }
      
      else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss >= 0){ //--- Check if the position is profitable
                           obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position
                        }
                     }
                  }
               }
            }
         }
      }

      else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss >= 0){ //--- Check if the position is profitable
                           obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position
                        }
                     }
                  }
               }
            }
         }
      }      
      else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                     if (profit_loss < 0){ //--- Check if the position is at a loss
                        obj_Trade.PositionClose(pos_ticket); //--- Close the losing position
                     }
                  }
               }
            }
         }
      }            
      else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                     if (profit_loss >= 0){ //--- Check if the position is profitable
                        obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position
                     }
                  }
               }
            }
         }
      }            

Concluída a lógica das posições, podemos passar para a lógica dos pedidos pendentes. Aqui, vamos remover todos os ordens pendentes. Isso é feito com a lógica a seguir.

      else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event
         
         for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders
            ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order
            if (order_ticket > 0){ //--- Check if the order ticket is valid
               if (OrderSelect(order_ticket)){ //--- Select the order by ticket
                  if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol
                     obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order
                  }
               }
            }
         }
      }

Aqui, vamos focar na funcionalidade de encerramento de ordens pendentes ao clicar no botão Close Pending. Começamos verificando se o parâmetro do tipo string corresponde ao nome do botão Close Pending, o que indica que o botão foi ativado. Após confirmar o clique, registramos a ação para depuração com a função Print, que exibe o nome do botão pressionado. Em seguida, iniciamos um laço que percorre todas as ordens pendentes, utilizando a função OrdersTotal para obter o número total de ordens e diminuindo o índice em ordem reversa para evitar que alguma ordem seja ignorada durante a exclusão. Para cada ordem, extraímos o número do tíquete com a função OrderGetTicket. Depois, verificamos se o tíquete é válido, assegurando que seu valor seja maior que zero. Se for válido, prosseguimos selecionando a ordem com a função OrderSelect.

Após selecionar a ordem, verificamos se ela corresponde ao símbolo definido em _Symbol, usando a função OrderGetString com a constante ORDER_SYMBOL. Se corresponder, chamamos a função OrderDelete para excluir a ordem pendente vinculada ao tíquete recuperado. Todo esse processo permite encerrar de forma eficiente todas as ordens pendentes associadas ao símbolo de negociação especificado quando o botão é ativado, garantindo que o usuário possa gerenciar suas ordens com eficiência. Após isso, atualizamos o gráfico com a função ChartRedraw, como mostrado abaixo, para garantir que as alterações sejam refletidas no gráfico.

   ChartRedraw(0);

Isso é tudo que precisamos para a automatização do painel. Ao executar o programa e testar a interface de encerramento, vemos o seguinte.

Seção de encerramento

A lógica do manipulador de encerramento OnChartEvent, que integramos ao sistema para tratar os eventos de clique no gráfico:

//+------------------------------------------------------------------+
//| Handling chart events                                            |
//+------------------------------------------------------------------+
void OnChartEvent(
   const int       id,       // Event ID indicating the type of event (e.g., mouse click, timer, etc.)
   const long&     lparam,   // Long type parameter associated with the event, usually containing data like mouse coordinates or object IDs
   const double&   dparam,   // Double type parameter associated with the event, used for floating-point values related to the event
   const string&   sparam    // String type parameter associated with the event, typically the name of the object that triggered the event
){
   // Print the 4 function parameters    
   //Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);

   // Check if the event is a click on a chart object
   if (id == CHARTEVENT_OBJECT_CLICK){
      //Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam);
      // Check if the clicked object is the Trade button
      if (sparam==obj_Btn_TRADE.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name());

         // Reset the pressed states of all buttons to ensure only one button appears pressed at a time
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Change the background color of the Trade button to yellow to indicate it is active
         obj_Btn_TRADE.ColorBackground(clrYellow);
         // Set the background color of the Close and Info buttons to silver to indicate they are inactive
         obj_Btn_CLOSE.ColorBackground(clrSilver);
         obj_Btn_INFO.ColorBackground(clrSilver);

         // Set the border color of the Trade button to yellow to match its background
         obj_Btn_TRADE.ColorBorder(clrYellow);
         // Set the border color of the Close and Info buttons to silver to indicate they are inactive
         obj_Btn_CLOSE.ColorBorder(clrSilver);
         obj_Btn_INFO.ColorBorder(clrSilver);

         // Call a function to destroy the Close section if it exists
         destroySection_Close();
         // Call a function to destroy the Information section if it exists
         destroySection_Information();

         // Create the Trade section, bringing it to the forefront
         createSection_Trade();
      }
      // Check if the clicked object is the Close button
      else if (sparam==obj_Btn_CLOSE.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name());

         // Reset the pressed states of all buttons
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Set the background color of the Trade button to silver, indicating it's inactive
         obj_Btn_TRADE.ColorBackground(clrSilver);
         // Change the background color of the Close button to yellow to indicate it is active
         obj_Btn_CLOSE.ColorBackground(clrYellow);
         obj_Btn_INFO.ColorBackground(clrSilver);

         // Set the border color of the Trade button to silver
         obj_Btn_TRADE.ColorBorder(clrSilver);
         // Set the border color of the Close button to yellow
         obj_Btn_CLOSE.ColorBorder(clrYellow);
         obj_Btn_INFO.ColorBorder(clrSilver);

         // Call a function to destroy the Trade section if it exists
         destroySection_Trade();
         // Call a function to destroy the Information section if it exists
         destroySection_Information();

         // Create the Close section, bringing it to the forefront
         createSection_Close();
      }
      // Check if the clicked object is the Information button
      else if (sparam==obj_Btn_INFO.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_INFO.Name());

         // Reset the pressed states of all buttons
         obj_Btn_TRADE.Pressed(false);
         obj_Btn_CLOSE.Pressed(false);
         obj_Btn_INFO.Pressed(false);

         // Set the background color of the Trade and Close buttons to silver, indicating they are inactive
         obj_Btn_TRADE.ColorBackground(clrSilver);
         obj_Btn_CLOSE.ColorBackground(clrSilver);
         // Change the background color of the Info button to yellow to indicate it is active
         obj_Btn_INFO.ColorBackground(clrYellow);

         // Set the border color of the Trade and Close buttons to silver
         obj_Btn_TRADE.ColorBorder(clrSilver);
         obj_Btn_CLOSE.ColorBorder(clrSilver);
         // Set the border color of the Info button to yellow
         obj_Btn_INFO.ColorBorder(clrYellow);

         // Call a function to destroy the Trade section if it exists
         destroySection_Trade();
         // Call a function to destroy the Close section if it exists
         destroySection_Close();

         // Create the Information section, bringing it to the forefront
         createSection_Information();
      }
      // Check if the clicked object is the exit button (X button)
      else if (sparam==obj_Btn_X.Name()){
         // Print to the log which object was clicked for debugging purposes
         Print("OBJECT CLICKED = ", obj_Btn_X.Name());

         // Call functions to destroy all sections, effectively closing the entire panel
         destroySection_Trade();
         destroySection_Close();
         destroySection_Information();

         // Call a function to destroy the main panel itself
         destroySection_Main_Panel();
      }
      else if (sparam==obj_Btn_SELL.Name()){ //--- Check if the Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
         double entry_price = Bid; //--- Set the entry price for selling to the current bid price
         double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
         double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
         
         Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
         obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the sell order
      }
      else if (sparam==obj_Btn_BUY.Name()){ //--- Check if the Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
         double entry_price = Ask; //--- Set the entry price for buying to the current ask price
         double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
         double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
         
         Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
         obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); //--- Execute the buy order
      }
      else if (sparam==obj_Btn_SELLSTOP.Name()){ //--- Check if the Sell Stop button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Bid - stopslevel*_Point; //--- Calculate the valid price for placing a sell stop order
         
         if (user_price > valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message
         }
         else if (user_price <= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the sell stop order
            double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell stop order
         }
      }
      else if (sparam==obj_Btn_BUYSTOP.Name()){ //--- Check if the Buy Stop button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Ask + stopslevel*_Point; //--- Calculate the valid price for placing a buy stop order
         
         if (user_price < valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message
         }
         else if (user_price >= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the buy stop order
            double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy stop order
         }
      }
      else if (sparam==obj_Btn_SELLLIMIT.Name()){ //--- Check if the Sell Limit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Bid + stopslevel*_Point; //--- Calculate the valid price for placing a sell limit order
         
         if (user_price < valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); //--- Log an error message
         }
         else if (user_price >= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the sell limit order
            double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the sell limit order
         }
      }
      else if (sparam==obj_Btn_BUYLIMIT.Name()){ //--- Check if the Buy Limit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); //--- Log the button click event
         
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get and normalize the ask price
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get and normalize the bid price
         
         double user_price = StringToDouble(obj_Edit_PRICE.Text()); //--- Get the user-defined price from input field
         long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); //--- Get the minimum stop level for the symbol
         double valid_price = Ask - stopslevel*_Point; //--- Calculate the valid price for placing a buy limit order
         
         if (user_price > valid_price){ //--- Check if the user-defined price is valid
            Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); //--- Log an error message
         }
         else if (user_price <= valid_price){ //--- If the user-defined price is valid
            double lots = StringToDouble(obj_Edit_LOTS.Text()); //--- Get the lot size from input field
            double entry_price = user_price; //--- Set the entry price for the buy limit order
            double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; //--- Calculate stop loss based on user input
            double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; //--- Calculate take profit based on user input
            
            Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); //--- Log order details
            obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); //--- Execute the buy limit order
         }
      }

      
      else if (sparam==obj_Btn_CLOSE_ALL.Name()){ //--- Check if the Close All button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     obj_Trade.PositionClose(pos_ticket); //--- Close the position
                  }
               }
            }
         }
      }
      else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ //--- Check if the Close All Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        obj_Trade.PositionClose(pos_ticket); //--- Close the sell position
                     }
                  }
               }
            }
         }
      }
      else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ //--- Check if the Close All Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        obj_Trade.PositionClose(pos_ticket); //--- Close the buy position
                     }
                  }
               }
            }
         }
      }
      else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ //--- Check if the Close Loss Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss < 0){ //--- Check if the position is at a loss
                           obj_Trade.PositionClose(pos_ticket); //--- Close the losing sell position
                        }
                     }
                  }
               }
            }
         }
      }
      else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ //--- Check if the Close Loss Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss < 0){ //--- Check if the position is at a loss
                           obj_Trade.PositionClose(pos_ticket); //--- Close the losing buy position
                        }
                     }
                  }
               }
            }
         }
      }
      
      else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ //--- Check if the Close Profit Sell button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_SELL){ //--- Check if the position is a sell position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss >= 0){ //--- Check if the position is profitable
                           obj_Trade.PositionClose(pos_ticket); //--- Close the profitable sell position
                        }
                     }
                  }
               }
            }
         }
      }

      else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ //--- Check if the Close Profit Buy button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get the position type
                     if (pos_type == POSITION_TYPE_BUY){ //--- Check if the position is a buy position
                        double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                        if (profit_loss >= 0){ //--- Check if the position is profitable
                           obj_Trade.PositionClose(pos_ticket); //--- Close the profitable buy position
                        }
                     }
                  }
               }
            }
         }
      }      
      else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ //--- Check if the Close All Loss button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                     if (profit_loss < 0){ //--- Check if the position is at a loss
                        obj_Trade.PositionClose(pos_ticket); //--- Close the losing position
                     }
                  }
               }
            }
         }
      }            
      else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ //--- Check if the Close All Profit button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); //--- Log the button click event
         
         for (int i = PositionsTotal() -1; i >= 0; i--){ //--- Loop through all positions
            ulong pos_ticket = PositionGetTicket(i); //--- Get the ticket of the position
            if (pos_ticket > 0){ //--- Check if the position ticket is valid
               if (PositionSelectByTicket(pos_ticket)){ //--- Select the position by ticket
                  if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position matches the symbol
                     double profit_loss = PositionGetDouble(POSITION_PROFIT); //--- Get the profit/loss of the position
                     if (profit_loss >= 0){ //--- Check if the position is profitable
                        obj_Trade.PositionClose(pos_ticket); //--- Close the profitable position
                     }
                  }
               }
            }
         }
      }            
      else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ //--- Check if the Close Pending button is clicked
         Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); //--- Log the button click event
         
         for (int i = OrdersTotal() -1; i >= 0; i--){ //--- Loop through all pending orders
            ulong order_ticket = OrderGetTicket(i); //--- Get the ticket of the order
            if (order_ticket > 0){ //--- Check if the order ticket is valid
               if (OrderSelect(order_ticket)){ //--- Select the order by ticket
                  if (OrderGetString(ORDER_SYMBOL)==_Symbol){ //--- Check if the order matches the symbol
                     obj_Trade.OrderDelete(order_ticket); //--- Delete the pending order
                  }
               }
            }
         }
      }

      
   }
   
   
   ChartRedraw(0);
   
}

Os resultados são mostrados abaixo.

Funcionamento do painel

Perfeito! Conseguimos dar vida ao nosso painel, tornando-o totalmente interativo e adaptável. Agora ele oferece suporte a cliques nos botões, atualização de dados em tempo real e resposta aos estados ativos, o que melhora a experiência do usuário e a funcionalidade geral da nossa interface de trading.


Considerações finais

As melhorias que implementamos no nosso painel gráfico em MetaQuotes Language 5 aumentam significativamente sua interatividade. Com a adição de atualizações em tempo real e resposta aos cliques nos botões, agora podemos interagir com o painel de maneira fácil e intuitiva. Essas funcionalidades não só simplificam a execução de ordens de compra e venda, como também oferecem acesso instantâneo a informações da conta de negociação em tempo real, permitindo decisões rápidas e bem fundamentadas à medida que as condições do mercado mudam.

Além disso, a automatização de diversos componentes, como o gerenciamento de posições e a exibição de informações da conta, torna o processo de trading ainda mais conveniente e eficiente. O painel gráfico se transforma em uma ferramenta poderosa para os traders modernos, permitindo fechar posições e ordens com um único clique e oferecendo parâmetros personalizáveis. Um ambiente de trabalho mais limpo e organizado facilita a concentração e aumenta a produtividade. Espero que o artigo tenha trazido informações valiosas sobre como aprimorar o painel gráfico no MQL5 e que as explicações tenham sido claras e úteis. Boas negociações!

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

Arquivos anexados |
Últimos Comentários | Ir para discussão (9)
Sergei Poliukhov
Sergei Poliukhov | 27 mai. 2025 em 20:52
Allan Munene Mutiiria #:

Você ao menos leu o artigo?

Estou procurando um painel desse tipo. O que eu fazia antes parou de funcionar, e só me resta encontrar um já pronto ou usar variáveis globais ou arquivos e um aplicativo Python...
Acabei de ler um pouco.
Sergei Poliukhov
Sergei Poliukhov | 27 mai. 2025 em 20:53
O painel é bonito e funcional. Muito obrigado.
Allan Munene Mutiiria
Allan Munene Mutiiria | 27 mai. 2025 em 20:57
Sergei Poliukhov #:
O painel é bonito e funcional. Muito obrigado.

Seja bem-vindo

SERGEI NAIDENOV
SERGEI NAIDENOV | 28 mai. 2025 em 08:04

E há planos para (minimizar/desmontar) o painel? e seria bom implementar a movimentação da janela do painel no gráfico!

Line00
Line00 | 2 jun. 2025 em 10:20

Painel incrível. Ótimo artigo! O artigo não apenas descreve como criar um painel perfeitamente funcional para um trader, mas as informações sobre ele são apresentadas de forma tão clara que podem ser usadas por iniciantes como um guia. E o guia não é apenas para criar um painel, mas também um guia sobre como escrever códigos de forma correta e competente para MQL5. Artigo muito valioso e informativo. Obrigado ao autor, Allan Munene Mutiiria!!!

Abraços,

B.V. Dolgikh

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Recursos do Assistente MQL5 que você precisa conhecer (Parte 43): Aprendizado por reforço com SARSA Recursos do Assistente MQL5 que você precisa conhecer (Parte 43): Aprendizado por reforço com SARSA
O SARSA (State-Action-Reward-State-Action, estado–ação–recompensa–estado–ação) é outro algoritmo que pode ser utilizado na implementação de aprendizado por reforço. Vamos analisar como esse algoritmo pode ser implementado como um modelo independente (e não apenas como um mecanismo de aprendizado) em Expert Advisors gerados no Wizard, de forma semelhante ao que fizemos nos casos de Q-learning e DQN.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Simulação de mercado: Position View (VI) Simulação de mercado: Position View (VI)
Neste artigo, faremos diversas melhorias, visando obter com que o indicador de posição, venha a refletir o que de fato está ocorrendo no servidor de negociação em termos de posições e seu status atual. Devo lembrar, que estas aplicações que serão mostradas aqui, não visam de maneira alguma substituir qualquer elemento presente no MetaTrader 5. E tal pouco devem ser utilizadas sem os devidos cuidados e critérios. Já que elas tem como objetivo terem um código didático. Ou seja, para fins de aprendizado de como as coisas funcionam. E o motivo para que eu diga que o código é didático. É pelo fato de que o uso de mensagens em alguns casos não é a melhor forma de implementar as coisas.