English Русский 中文 Español Deutsch 日本語
Criamos um auxiliar na negociação manual

Criamos um auxiliar na negociação manual

MetaTrader 5Exemplos | 13 junho 2016, 15:04
7 788 4
Dmitriy Gizlyk
Dmitriy Gizlyk

Introdução

Neste artigo, vou apresentar um outro exemplo de como criar a partir do zero um painel de negociação que irá ajudar os traders seguidores da negociação manual no mercado Forex.

1. Definimos a funcionalidade necessária para o painel de negociação

Primeiro precisamos definir para nós mesmos o que é que queremos para obter o resultado final. Temos que decidir qual é a funcionalidade que esperamos do nosso painel e que desenho será conveniente para nós. Neste artigo, eu proponho a minha própria visão do painel de negociação, mas vou aceitar de bom grado as suas sugestões. Espero que nos meus novos artigos eles sejam discutidos em detalhe. 

Assim, o nosso painel certamente deve incluir os seguintes elementos.

  1. Botões de compra e venda.
  2. Botão para fechar todas as posições de acordo com o símbolo e a conta ou numa direção separada (ordens de compra ou venda).
  3. Possibilidade de especificar os níveis de Stop-Loss e Take-Profit tanto em pontos como na moeda do depósito (se indicar um parâmetro, o outro será automaticamente ajustado).
  4. O painel deve calcular automaticamente os níveis Stop-Loss e Take-Profit de acordo com os parâmetros definidos manualmente (p. 2) e exibi-los no gráfico.
  5. O trader deve ter a possibilidade de deslocar os níveis Stop-Loss e/ou Take-Profit no gráfico. Além disso, todas as alterações devem ser reflectidas no painel com as alterações dos valores correspondentes.
  6. O painel deve ter em conta o próximo volume de transações sobre os parâmetros especificados de risco (na moeda do depósito ou como uma porcentagem do saldo atual).
  7. O trader deve ter a possibilidade de definir de maneira independente o volume da transação. Ao mesmo tempo, dependendo dele, devem ser recalculados automaticamente os parâmetros respetivos.
  8. O painel tem que se lembrar de quais configurações foram feitas pelo trader e quais calculadas automaticamente. Isto é necessário para que os recálculos subseqüentes das configurações, feitas pelo trader, tanto quanto possível, não sejam alteradas.
  9. O painel deve salvar todas as configurações já definidas, de modo que nas reinicializações não seja preciso as definir novamente.

2. Criamos um rascunho gráfico do painel

Pegamos uma folha de papel em branco e desenhamos o nosso futuro painel de negociação, colocando nele todos os elementos necessários.

Ao projetar o desenho do futuro painel de negociação, temos que pensar sobre a praticidade da sua aplicação. Em primeiro lugar, ele deve ser bastante informativo, fácil de ler e não estar sobrecarregado com elementos desnecessários. Devemos sempre lembrar que é uma ferramenta para um trader real trabalhar, e não apenas uma imagem bonita na tela.

Aqui está a minha versão.

Desenho

3. Construção do rascunho do painel na linguagem MQL5

3.1.  Predefinição

Agora que temos uma ideia do objetivo final, trazemo-la à vida no código MQL5. Para este fim, tanto quanto possível, vamos utilizar as bibliotecas padrão, isso vai facilitar o nosso trabalho. Na MQL5 há uma classe CAppDialog que é a base para a construção de caixas de diálogo. Com base nesta classe, construiremos o nosso painel.  
Para fazer isso, criamos uma duplicata da classe e inicializamo-la na função OnInit().

#include <Controls\Dialog.mqh>

class CTradePanel : public CAppDialog
  {
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  };

CTradePanel TradePanel;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   // Creat Trade Panel
   if(!TradePanel.Create(ChartID(),"Trade Panel",0,20,20,320,420))
     {
      return (INIT_FAILED);
     }
   // Run Trade Panel
   TradePanel.Run();
//---
   return(INIT_SUCCEEDED);
  }

O resultado desta manipulação simples é a preparação do nosso próximo painel.

Predefinição

3.2. Anúncio dos objetos necessários

Agora anexamos à na nossa predefinição os controles desejados. Para fazer isso, criamos os objetos das classes respetivas para cada elemento de controle. Vamos criar os objetos utilizando as classes padrão CLabel, CEdit, CButton e CBmpButton.

Adicionamos os arquivos incluídos e criamos a função Creat() para a classe CTradePanel:

#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

Eu intencionalmente não incluí os arquivos "Edit.mqh" e "BmpButton.mqh", pois eles já são chamados a partir de "Dialog.mqh".

O próximo passo será: para cada objeto no painel na classe CTradePanel, anunciamos as variáveis do tipo adequado, e lá mesmo declaramos o procedimento Creat(..), no qual colocamos todos os elementos nos seus lugares. Nota: nós anunciamos no bloco "private" as declarações das variáveis e outras ações dentro da classe CTradePanel. As funções, disponíveis para chamar de fora da classe, como Creat (...), devem ser declaradas no bloco "public".

class CTradePanel : public CAppDialog
  {
private:

   CLabel            ASK, BID;                        // Display Ask and Bid prices
   CLabel            Balance_label;                   // Display label "Account Balance"
   CLabel            Balance_value;                   // Display Account balance
   CLabel            Equity_label;                    // Display label "Account Equity"
   CLabel            Equity_value;                    // Display Account Equity
   CLabel            PIPs;                            // Display label "Pips"
   CLabel            Currency;                        // Display Account currency
   CLabel            ShowLevels;                      // Display label "Show"
   CLabel            StopLoss;                        // Display label "Stop Loss"
   CLabel            TakeProfit;                      // Display label "TakeProfit"
   CLabel            Risk;                            // Display label "Risk"
   CLabel            Equity;                          // Display label "% to Equity"
   CLabel            Currency2;                       // Display Account currency
   CLabel            Orders;                          // Display label "Opened Orders"
   CLabel            Buy_Lots_label;                  // Display label "Buy Lots"
   CLabel            Buy_Lots_value;                  // Display Buy Lots value 
   CLabel            Sell_Lots_label;                 // Display label "Sell Lots"
   CLabel            Sell_Lots_value;                 // Display Sell Lots value 
   CLabel            Buy_profit_label;                // Display label "Buy Profit"
   CLabel            Buy_profit_value;                // Display Buy Profit value 
   CLabel            Sell_profit_label;               // Display label "Sell Profit"
   CLabel            Sell_profit_value;               // Display Sell profit value 
   CEdit             Lots;                            // Display volume of next order
   CEdit             StopLoss_pips;                   // Display Stop loss in pips
   CEdit             StopLoss_money;                  // Display Stop loss in accaunt currency
   CEdit             TakeProfit_pips;                 // Display Take profit in pips
   CEdit             TakeProfit_money;                // Display Take profit in account currency
   CEdit             Risk_percent;                    // Display Risk percent to equity
   CEdit             Risk_money;                      // Display Risk in account currency
   CBmpButton        StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton        TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton        StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton        StopLoss_money_b;                // Select Stop loss in accaunt currency
   CBmpButton        TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton        TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton        Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton        Risk_money_b;                    // Select Risk in account currency
   CBmpButton        Increase,Decrease;               // Increase and Decrease buttons
   CButton           SELL,BUY;                        // Sell and Buy Buttons
   CButton           CloseSell,CloseBuy,CloseAll;     // Close buttons
   
public:
                     CTradePanel(void){};
                    ~CTradePanel(void){};
  //--- Create function
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
   
  };

3.3. Criação de procedimentos de inicialização para grupos de objetos

Chegou a hora de especificar o corpo da função Creat (...). Repare que, nesta função, nós devemos inicializar todos os objetos declarados acima. É fácil calcular que acima declaramos 45 objetos de 4 tipos. E, portanto, é aconselhável especificar 5 procedimentos de inicialização de objetos, um para cada tipo.  No bloco "private" anunciamos as funções de inicialização de classes.

É claro, que era possível declarar objetos na matriz, mas, nesse caso, teríamos perdido a ligação entre o nome da variável do objecto e a sua funcionalidade, o que subseqüentemente complicaria o trabalho com objetos. Portanto, a escolha foi a favor da transparência do código e a facilidade de trabalhar com ele (não vamos criar uma barreira para nós mesmos as superar de modo heróico).

Classe CLabel

Vamos usar a classe CLabel para exibir informações no nosso painel. Ao criar uma função de inicialização, é preciso determinar que funções serão comuns para todos os elementos desta classe, e quais deles é que vão ser diferentes. Neste caso, as diferenças são as seguintes:

  • nome do objeto;
  • texto exibido;
  • coordenadas do elemento;
  • alinhamento do objecto em relação ao ponto de ancoragem.

Definindo as diferenças, vamos determinar quais serão transferidas usando as configurações da função para as tornar universais, e quais serão geradas no próprio procedimento.

Ao trabalhar com objetos, devemos estar cientes de que todos os objetos no gráfico devem ter nomes individuais. Como sempre, a escolha é do programador: definir cada nome de objeto sozinho ou gerá-los usando algum programa. Criando uma função universal, eu escolhi gerar os nomes dos objetos dentro do programa. Para fazer isso, eu defini o nome do objecto para a classe com a adição de um número de série.

string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);

Vamos transferir o texto exibido, as coordenadas do objeto e o alinhamento do objeto em relação ao ponto de ancoragem, para a função usando as configurações. Por uma questão de facilidade de leitura do código do programa e de trabalho do programador, criamos uma enumeração para o alinhamento do objeto:

  enum label_align
     {
      left=-1,
      right=1,
      center=0
     };

Além disso, nas configurações do processo, devemos especificar o número da sub-janela, o código do gráfico e uma referência para o objeto criado.

Na função mesmo, escrevemos os procedimentos que devem ser seguidos para cada objeto dessa classe.

  • Criamos o objeto usando a função Create (...) da classe pai.
  • Em seguida, colocamos no objeto o texto desejado.
  • Alinhamos o objecto em relação ao ponto de ancoragem.
  • Adicionamos o objeto para o "contêiner" da caixa de diálogo.
bool CTradePanel::CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align)
  {
   // All objects must to have separate name
   string name=m_name+"Label"+(string)ObjectsTotal(chart,-1,OBJ_LABEL);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,0,0))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Aling text to Dialog box's grid
   ObjectSetInteger(chart,object.Name(),OBJPROP_ANCHOR,(align==left ? ANCHOR_LEFT_UPPER : (align==right ? ANCHOR_RIGHT_UPPER : ANCHOR_UPPER)));
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Classe CButton

A classe CButton está projetada para criar botões retangulares com uma referência. Estes são os nossos botões padrão de abertura e fechamento de ordens.

Para começar a trabalhar com esta classe de objetos, usamos a mesma abordagem tal como no caso anterior. Além disso, devemos levar em conta as peculiaridades do seu trabalho. Primeiro de tudo, nós não precisamos de alinhar o texto no botão, pois na classe pai, ele já está alinhado no meio. No entanto neste caso já aparece o tamanho do botão que vamos transferir nas configurações.

Além disso, no botão aparece o seu estado atual: pressionado ou não. Ademais, ao estar pressionado, o botão pode ficar bloqueado ou não. Dessa forma, nós devemos descrever estas opções adicionais no procedimento de inicialização do objeto. Para os nossos botões, nós desligamos o bloqueio e definimos "Pressionado" no estado.

bool CTradePanel::CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must to have separate name
   string name=m_name+"Button"+(string)ObjectsTotal(chart,-1,OBJ_BUTTON);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- set button flag to unlock
   object.Locking(false);
   //--- set button flag to unpressed
   if(!object.Pressed(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Classe CEdit

A classe CEdit está projetada para criar objetos de entrada de dados. No nosso painel, a esses objetos pertencem as células de entrada do volume de transação, os níveis Stop-Loss e Take-Profit (em pips e moeda do depósito) e o nível de risco.

Usamos a mesma abordagem que para as duas classes descritas anteriormente. Mas, ao contrário dos botões, no procedimento de inicialização dessa classe, é necessário especificar como alinhar o texto na célula. Além disso, é preciso estar ciente de que qualquer informação que for introduzida ou transmitida para a célula, sempre será percebida como texto. Portanto, transferindo ao objeto para exibir o número, é necessário primeiro convertê-los em texto.

Os objetos da classe CEdit, ao contrário dos botões, não têm o estado "Pressionado" / "Solto", mas, ao mesmo tempo, esta classe permite criar objetos que não estão disponíveis para edição pelo usuário durante a execução. Neste caso, todos os objetos devem estar disponíveis para a edição pelo usuário. Mencionamos isto na nossa função de inicialização. 

bool CTradePanel::CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size)
  {
   // All objects must to have separate name
   string name=m_name+"Edit"+(string)ObjectsTotal(chart,-1,OBJ_EDIT);
   //--- Call Create function
   if(!object.Create(chart,name,subwindow,x,y,x+x_size,y+y_size))
     {
      return false;
     }
   //--- Addjust text
   if(!object.Text(text))
     {
      return false;
     }
   //--- Align text in Edit box
   if(!object.TextAlign(ALIGN_CENTER))
     {
      return false;
     }
   //--- set Read only flag to false
   if(!object.ReadOnly(false))
     {
      return false;
     }
   //--- Add object to controls
   if(!Add(object))
     {
      return false;
     }
   return true;
  }

Classe CBmpButton

A classe CBmpButton está projetada para criar botões personalizados com uso de objetos gráficos em vez de etiquetas. Esses botões além de serem intuitivos para todos os usuários, são utilizados na criação dos elementos de controle padrão para uma variedade de aplicativos. No nosso caso, usando esta classe, nós vamos criar:

  • o rádio-botão de seleção, no qual serão exibidos o Stop-Loss, o Take-Profit e o risco: em pontos ou em formato de moeda (ou em porcentagem para o risco);
  • caixas de verificação para fixação de "exibir ou não" os níveis Stop-Loss e Take-Profit no gráfico;
  • botões aumento e diminuição do volume da transação.

O trabalho com essa classe de objetos é semelhante ao trabalho com a classe CButton. A diferença reside na transmissão de objetos gráficos para os estados pressionados e solto do botão em vez do texto. Para o nosso painel usamos as imagens dos botões fornecidas juntamente com a MQL5. Além disso, para distribuir o software acabado num arquivo, escrevemos estas imagens na qualidade de recursos.

#resource "\\Include\\Controls\\res\\RadioButtonOn.bmp"
#resource "\\Include\\Controls\\res\\RadioButtonOff.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOn.bmp"
#resource "\\Include\\Controls\\res\\CheckBoxOff.bmp"
#resource "\\Include\\Controls\\res\\SpinInc.bmp"
#resource "\\Include\\Controls\\res\\SpinDec.bmp"

Observe também que todos os elementos desta classe são bloqueados (ou seja, mantêm o se estado "Pressionado" ou "Solto"), exceto os botões de aumento e diminuição do lote. Portanto, adicionamos opções adicionais à função de inicialização.

//+------------------------------------------------------------------+
//| Create BMP Button                                                |
//+------------------------------------------------------------------+
bool CTradePanel::CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock)
  {
   // All objects must to have separate name
   string name=m_name+"BmpButton"+(string)ObjectsTotal(chart,-1,OBJ_BITMAP_LABEL);
   //--- Calculate coordinates
   uint y1=(uint)(y-(Y_STEP-CONTROLS_BUTTON_SIZE)/2);
   uint y2=y1+CONTROLS_BUTTON_SIZE;
   //--- Call Create function
   if(!object.Create(m_chart_id,name,m_subwin,x-CONTROLS_BUTTON_SIZE,y1,x,y2))
      return(false);
   //--- Assign BMP pictuers to button status
   if(!object.BmpNames(BmpOFF,BmpON))
      return(false);
   //--- Add object to controls
   if(!Add(object))
      return(false);
   //--- set Lock flag to true
   object.Locking(lock);
//--- succeeded
   return(true);
  }

Tendo escrita a função de criação de objetos, obrigatoriamente declaramos estas funções no bloco "private" de nossa classe.

private:

   //--- Create Label object
   bool              CreateLabel(const long chart,const int subwindow,CLabel &object,const string text,const uint x,const uint y,label_align align);
   //--- Create Button
   bool              CreateButton(const long chart,const int subwindow,CButton &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Cleate Edit object
   bool              CreateEdit(const long chart,const int subwindow,CEdit &object,const string text,const uint x,const uint y,const uint x_size,const uint y_size);
   //--- Create BMP Button
   bool              CreateBmpButton(const long chart,const int subwindow,CBmpButton &object,const uint x,const uint y,string BmpON,string BmpOFF,bool lock);

3.4. Colocamos todos os elementos nos seus lugares

Agora, tendo escrita a função de inicialização para cada classe do objeto, é hora de a escrever para o nosso painel de negociação. As principais tarefas desta função é o cálculo das coordenadas de cada um dos objetos do painel e a criação passo a passo de objetos, chamando a função de inicialização correspondente.

Mais uma vez, lembre-se que elementos do painel devem ser localizados conveniente para o usuário, e, portanto, tudo deve ter um visual esteticamente agradável. Tínhamos focado nossa atenção nesta questão ao criar o rascunho do painel, e agora vamos seguir este conceito. Ademais, deve entender-se que, utilizando a nossa classe no programa final, as dimensões do painel podem variar. E para que, ao ser alterado o tamanho do painel de negociação, seja preservado o conceito do nosso desenho, temos de calcular as coordenadas de cada objeto, e não especificar de modo claro. Para este fim, criamos para nós umas espécies de faróis:

  • distância entre a borda da janela e o primeiro elemento de controle;
  • distância entre os elementos de controles de acordo com a altura;
  • altura dos elementos de controle.
   #define  Y_STEP   (int)(ClientAreaHeight()/18/4)      // height step betwine elements
   #define  Y_WIDTH  (int)(ClientAreaHeight()/18)        // height of element
   #define  BORDER   (int)(ClientAreaHeight()/24)        // distance betwine boder and elements

Assim, podemos calcular as coordenadas do primeiro elemento de controle e cada um dos subseqüentes em relação ao anterior.
Além disso, determinando o tamanho ideal do nosso painel, podemos especificá-las como os valores padrão para as configurações transferidas para a função.

bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
      // At first call creat function of parents class
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
     {
      return false;
     }
   // Calculate coofrdinates and size of BID object
   // Coordinates calculate in dialog box, not in chart
   int l_x_left=BORDER;
   int l_y=BORDER;
   int y_width=Y_WIDTH;
   int y_sptep=Y_STEP;
   // Creat object
   if(!CreateLabel(chart,subwin,BID,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits),l_x_left,l_y,left))
     {
      return false;
     }
   // Adjust font size for object
   if(!BID.FontSize(Y_WIDTH))
     {
      return false;
     }
   // Repeat same functions for other objects
   int l_x_right=ClientAreaWidth()-20;
   if(!CreateLabel(chart,subwin,ASK,DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits),l_x_right,l_y,right))
     {
      return false;
     }
   if(!ASK.FontSize(Y_WIDTH))
     {
      return false;
     }
   l_y+=2*Y_WIDTH;
...................
  }

O código completo da função pode ser encontrado no exemplo anexado.

O seguinte painel tornou-se no resultado do nosso trabalho.

Painel

Mas, por agora, isso é apenas o rascunho, uma bela imagem no gráfico. Na próxima etapa, "daremos vida."

4. "A animação da imagem"

Agora que criamos o rascunho gráfico do nosso painel de negociação, é hora de o ensinar a reagir aos eventos. Por conseguinte, a fim de criar e configurar o processador de eventos, precisamos definir a que eventos e como ele deve reagir.

4.1. Alterando o preço do instrumento

Ao alterar o preço do instrumento pelo terminal MT5, gera-se o evento NewTick, que desencadeia a função OnTick() do conselheiro. Portanto, a partir de esta função, nós devemos chamar a função respetiva de nossa classe que irá processar esse evento. Vamos dar-lhe um nome semelhante OnTick() e declaramo-la no bloco "public", uma vez que ela vai ser chamada a partir de um programa externo.

public:

   virtual void      OnTick(void);
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   TradePanel.OnTick();
  }
Que mudanças ocorrem no painel, alterando o valor do instrumento? A primeira coisa que precisamos fazer é alterar os valores de Ask e Bid no nosso painel.
//+------------------------------------------------------------------+
//| Event "New Tick                                                  |
//+------------------------------------------------------------------+
void CTradePanel::OnTick(void)
  {
   //--- Change Ask and Bid prices on panel
   ASK.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_ASK),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));
   BID.Text(DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_BID),(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));

A seguir, se houver posições abertas na conta, vamos alterar o valor dos fundos (Equity) no painel. Como garantia, eu adicionei uma verificação para constatar que os fundos reais na conta estão em concordância com os exibidos no painel, mesmo quando não há posições abertas. Isto irá mostrar a soma real de dinheiro após a alguma "situação de emergência". Assim, não é necessário verificar se há posições abertas, pois nós verificamos imediatamente a concordância entre a soma atual de dinheiro na conta e a exibida no painel. Se necessário, inserimos o valor verdadeiro no painel.

//--- Сheck and change (if neccesory) equity
   if(Equity_value.Text()!=DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
     {
      Equity_value.Text(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
     }
  

Faremos iterações semelhantes para exibir o saldo.
Eu já prevejo a pergunta: "Para que verificar o saldo em cada tick, poise ele muda apenas quando executamos a negociação?" Sim, é assim, e um pouco mais tarde, vamos falar sobre a resposta aos eventos durante a negociação. Mas há uma pequena possibilidade de negociar nos momentos em que o nosso painel não está em execução ou não existe uma conexão do terminal com o servidor. Foi precisamente para que no painel sempre seja exibido o saldo atual, mesmo após a situações extraordinárias, é que eu acrescentei esta operação.

A seguir, ao alterar o preço, vamos verificar a presença de alguma posição aberta segundo o instrumento atual, se ela existir, iremos verificar e ajustar o valor do volume aberto e do lucro atual de acordo com a posição nos campos Buy e Sell.

//--- Check and change (if neccesory) Buy and Sell lots and profit value.
   if(PositionSelect(_Symbol))
     {
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           Buy_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Buy_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Buy_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Sell_Lots_value.Text()!=DoubleToString(0,2))
              {
               Sell_Lots_value.Text(DoubleToString(0,2));
              }
           break;
         case POSITION_TYPE_SELL:
           Sell_profit_value.Text(DoubleToString(PositionGetDouble(POSITION_PROFIT),2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
           if(Sell_Lots_value.Text()!=DoubleToString(PositionGetDouble(POSITION_VOLUME),2))
              {
               Sell_Lots_value.Text(DoubleToString(PositionGetDouble(POSITION_VOLUME),2));
              }
           if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
              {
               Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
              }
           if(Buy_Lots_value.Text()!=DoubleToString(0,2))
              {
               Buy_Lots_value.Text(DoubleToString(0,2));
              }
           break;
        }
     }
   else
     {
      if(Buy_Lots_value.Text()!=DoubleToString(0,2))
        {
         Buy_Lots_value.Text(DoubleToString(0,2));
        }
      if(Sell_Lots_value.Text()!=DoubleToString(0,2))
        {
         Sell_Lots_value.Text(DoubleToString(0,2));
        }
      if(Buy_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Buy_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
      if(Sell_profit_value.Text()!=DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY))
        {
         Sell_profit_value.Text(DoubleToString(0,2)+" "+AccountInfoString(ACCOUNT_CURRENCY));
        }
     }

Além disso, não devemos esquecer o estado das caixas de verificação que exibem os níveis Stop-Loss e Take-Profit no gráfico. Se necessário, ajustamos a posição da linha. Adicionamos no código a chamada dessas funções. Mais detalhes sobre eles podem ser encontrados abaixo.

   //--- Move SL and TP lines if necessary
   if(StopLoss_line.Pressed())
     {
      UpdateSLLines();
     }
   if(TakeProfit_line.Pressed())
     {
      UpdateTPLines();
     }
   return;
  }

4.2. Inserindo valores nos campos editáveis

No nosso painel existe uma série de campos editáveis, e, claro, precisamos configurar o recebimento e processamento das informações inseridas.

A inserção de informações nos campos editáveis é uma alteração do objeto gráfico que pertence ao grupo de eventos ChartEvent. Os eventos deste grupo são processados pela ​​função OnChartEvent. Ela tem 4 parâmetros de entrada, a saber: identificação do evento e 3 parâmetros para caracterizar o evento e que pertencem aos tipos long, double e string. Como no caso anterior, vamos criar um processador de eventos na nossa classe e vamos chamá-lo a partir da função OnChartEvent com a transferência de todos os parâmetros de entrada que caracterizam o evento. Olhando um pouco para frente, eu quero dizer que esta função também processará o evento de clique no botão do painel de negociação. Portanto, essa função vai ser o controlador de eventos analisados que irá chamar a função de processamento de um evento específico. Em seguida, as informações sobre o evento serão transferidas às funções da classe pai para executar os procedimentos escritos na classe pai.

public:

   virtual bool      OnEvent(const int id,const long &lparam, const double &dparam, const string &sparam);

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

Para construir tal controlador usaremos as substituições macro.

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
EVENT_MAP_END(CAppDialog)

Por conseguinte, devemos declarar todas as funções de processamento no bloco "private" da nossa classe

private:

   //--- On Event functions
   void              LotsEndEdit(void);                              // Edit Lot size
   void              SLPipsEndEdit(void);                            // Edit Stop Loss in pips
   void              TPPipsEndEdit(void);                            // Edit Take Profit in pips
   void              SLMoneyEndEdit(void);                           // Edit Stop Loss in money
   void              TPMoneyEndEdit(void);                           // Edit Take Profit in money
   void              RiskPercentEndEdit(void);                       // Edit Risk in percent
   void              RiskMoneyEndEdit(void);                         // Edit Risk in money

 Para armazenar os dados obtidos a partir dos campos editáveis, introduzimos as variáveis ​​adicionais no bloco "private"

private:

   //--- variables of current values
   double            cur_lot;                         // Lot of next order
   int               cur_sl_pips;                     // Stop Loss in pips
   double            cur_sl_money;                    // Stop Loss in money
   int               cur_tp_pips;                     // Take Profit in pips
   double            cur_tp_money;                    // Take Profit in money
   double            cur_risk_percent;                // Risk in percent
   double            cur_risk_money;                  // Risk in money

Consideramos o exemplo de um evento específico, isto é, a inserção do volume pela transação preparada. Gostaria de lembrar que qualquer informação nesses campos é percebida como uma inserção de texto, independentemente do seu conteúdo. Em essência, quando você digita texto num campo, gera-se uma série de eventos, a saber: colocar o mouse sobre o objeto, pressionar o botão do mouse, começar a edição do campo, pressionar a tecla, finalizar a edição do campo, etc. Estamos interessados apenas no último evento, quando termina a inserção das informações. Portanto, vamos produzir a chamada da função de acordo com o evento "ON_END_EDIT".

A primeira coisa que devemos fazer na função de processamento desse evento é ler o texto inserido e tentar convertê-lo num valor do tipo double.

A seguir, é preciso realizar uma "normalização" do valor obtido, ou seja, faze-lo coincidir com as condições do instrumento de negociação (o volume mínimo e máximo de uma ordem, bem como o incremento na alteração do volume). Para realizar esta operação, escrevemos uma função separada, porque vamos precisar dela ao pressionar os botões para aumentar e diminuir o volume da transação. Teremos que retornar o valor resultante ao painel, para informar o trader sobre o volume real da futura transação.

//+------------------------------------------------------------------+
//| Read lots value after edit                                       |
//+------------------------------------------------------------------+
void CTradePanel::LotsEndEdit(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

Além disso, dependendo das configurações atuais do rádio-botões, vamos ter de recalcular e alterar no painel o valor de todos os outros campos editáveis. Isso é necessário porque quando alteramos o volume da transação, muda a soma do risco ao fechar a transação de acordo com o Stop-Loss (se indicar o Stop-Loss em pontos) ou o nível Stop-Loss em pontos (no caso de indicar o Stop-Loss em termos de dinheiro). O nível de risco será puxado para o Stop-Loss. A situação é semelhante com os valores de Take-Profit. Naturalmente, estas operações serão organizadas através das respectivas funções.

   //--- Check and modify value of other labels 
   if(StopLoss_money_b.Pressed())
     {
      StopLossPipsByMoney();
     }
   if(TakeProfit_money_b.Pressed())
     {
      TakeProfitPipsByMoney();
     }
   if(StopLoss_pips_b.Pressed())
     {
      StopLossMoneyByPips();
     }
   if(TakeProfit_pips_b.Pressed())
     {
      TakeProfitMoneyByPips();
     }

Quando construímos uma ferramenta para o trabalho diário do usuário, devemos sempre ter em mente o conceito de "usabilidade". E aqui devemos recordar o ponto número 8 descrito no início da nossa funcionalidade do painel de negociação: "O painel tem que se lembrar de quais configurações foram feitas pelo trader e quais calculadas automaticamente. Isto é necessário para que os recálculos subseqüentes das configurações, feitas pelo trader, tanto quanto possível, não sejam alteradas". "Em outras palavras, no futuro, ao alterar o Stop-Loss em pontos, devemos lembrar que o trader alterou usando o último, isto é, o volume da transação ou o nível de risco. Se necessário, é possível ser obrigado a deixar inalterada a entrada mais recente.
Para este fim, introduzimos no bloco "private" a variável RiskByValue, e na função de processamento de este evento atribuímos o valor true.

private:
   bool              RiskByValue;                     // Flag: Risk by Value or Value by Risk
   RiskByValue=true;
   return;
  }

Os princípios de organização da função de ajuste dos campos editáveis ​​relacionados são considerados como exemplo da função StopLossMoneyByPips, porque ela tem a funcionalidade mais abrangente.

1. Em essência, esta função será chamada em três casos, a saber: ao alterar o lote, ao inserir um valor no campo de Stop-Loss em pips e ao deslocar a linha de Stop-Loss. Por isso a primeira coisa que precisamos fazer é verificar o valor atual do volume da próxima negociação. Se ele não coincidir com a especificação do instrumento e a realidade do mercado, será necessário ajustar o valor exibido no painel.

//+------------------------------------------------------------------+
//|  Modify SL money by Order lot and SL pips                        |
//+------------------------------------------------------------------+
void CTradePanel::StopLossMoneyByPips(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));

2. O segundo componente para calcular o valor monetário de risco possível é a soma da alteração dos fundos quando as mudanças de preços do instrumento para um tick na posição aberta é de 1 lote. Para fazer isso, temos o custo de um tick e o tamanho mínimo da mudança do preço do instrumento:

   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
   double tick_size=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);

3. De acordo com os dados obtidos calculamos as perdas potenciais, e exibimos o valor resultante no painel no campo respetivo.

   cur_sl_money=NormalizeDouble(tick_value*cur_lot*(tick_size/_Point)*cur_sl_pips,2);
   StopLoss_money.Text(DoubleToString(cur_sl_money,2));

4 Reparemos em que a soma das perdas potenciais ao fechar a ordem segundo o Stop-Loss, na verdade, é o nosso risco em termos de dinheiro. Portanto, precisamos de duplicar o valor calculado no campo de risco em termos de dinheiro, e, em seguida, calcular o valor do risco relativo (porcentagem de risco).

   cur_risk_money=cur_sl_money;    Risk_money.Text(DoubleToString(cur_risk_money,2));    cur_risk_percent=NormalizeDouble(cur_risk_money/AccountInfoDouble(ACCOUNT_BALANCE)*100,2);    Risk_percent.Text(DoubleToString(cur_risk_percent,2));
return;
}

A função para calcular o Stop-Loss em pontos a partir do valor do termo monetário é o inverso da função acima mencionada, com a exceção de que não se altera o risco, mas é necessário ajustar a posição das linhas de exibição do nível Stop-Loss no gráfico.

Da mesma forma, são prescritas as funções para ajustar os valores de Take-Profit.

Da mesma forma, juntamos as funções para o processamento de eventos de edição de outros campos. Além disso, vale a pena lembrar que, ao editar os campos, também vamos ter que alterar o estado dos rádio-botões. Ao mesmo tempo, de modo a não duplicar os estados prescritos dos botões em cada função, vamos chamar a função de processamento pressionando o botão correspondente. 

4.3. Processamento de eventos de toque no rádio-botão.

O rádio-botão é um elemento da interface que permite aos usuários selecionar uma opção (item) a partir de um conjunto pré-definido (grupo).
Portanto, ao pressionar num rádio-botão, é preciso mudar o estado relacionado com o fim dos botões. No entanto, alternar rádio-botões em si não conduz a quaisquer recalculo de parâmetros.
Assim, as funções de processamento de eventos de toque do rádio-botão só vão mudar o estado relacionado com o fim dos botões, ou seja, levarão o rádio-botão pressionado a um estado "pressionado", e levarão os outros botões soltos a um estado "Solto".

Em quanto ao lado técnico, o toque no botão pertence ao grupo de eventos ChartEvent. Portanto, o processamento será realizado da mesma maneira que a edição de campos. Declaramos as funções de processamento de eventos no bloco "private":

private:

   //--- On Event functions
   void              SLPipsClick();                                  // Click Stop Loss in pips
   void              TPPipsClick();                                  // Click Take Profit in pips
   void              SLMoneyClick();                                 // Click Stop Loss in money
   void              TPMoneyClick();                                 // Click Take Profit in money
   void              RiskPercentClick();                             // Click Risk in percent
   void              RiskMoneyClick();                               // Click Risk in money

Complementamos as substituições macro do processador de eventos:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
EVENT_MAP_END(CAppDialog)

A função para o processamento do evento em si ficará do seguinte modo:

//+------------------------------------------------------------------+
//| Click Stop Loss in pips                                          |
//+------------------------------------------------------------------+
void CTradePanel::SLPipsClick(void)
  {
   StopLoss_pips_b.Pressed(cur_sl_pips>0);
   StopLoss_money_b.Pressed(false);
   Risk_money_b.Pressed(false);
   Risk_percent_b.Pressed(false);
   return;
  }

 É possível encontrar todas as funções para o processamento do evento no código anexado.

4.4. Toque nos botões de alteração do volume da transação.

Ao contrário dos rádio-botões, ao clicar nos botões de alteração do volume da transação, o programa deve executar uma série de operações que nós temos que especificar no código. Em primeiro lugar, ele é um aumento e diminuição do valor da variável cur_lot na magnitude do incremento da alteração do volume da transação. Em seguida, precisamos comparar o valor obtido com o valor máximo e mínimo possível para o instrumento. Eu gostaria sugerir que se verifique de modo opcional a disponibilidade de fundos para abertura de tamanha ordem, pois, posteriormente, o trader ao tentar abrir a ordem, pode não ter fundos suficientes na conta. Depois, vamos ter que trazer um novo significado para o volume da transação no painel e editar os valores associados, como no caso de entrada manual do valor do volume da transação no campo editável.

Assim como foi feito anteriormente, vamos declarar a função no bloco private:

private:
................
   //--- On Event functions
................
   void              IncreaseLotClick();                             // Click Increase Lot
   void              DecreaseLotClick();                             // Click Decrease Lot

Completamos a função para o processamento da interrupção de substituições macro:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
EVENT_MAP_END(CAppDialog)

Consideramos a função para processar o evento:

//+------------------------------------------------------------------+
//|  Increase Lot Click                                              |
//+------------------------------------------------------------------+
void CTradePanel::IncreaseLotClick(void)
  {
   //--- Read and normalize lot value
   cur_lot=NormalizeLots(StringToDouble(Lots.Text())+SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP));
   //--- Output lot value to panel
   Lots.Text(DoubleToString(cur_lot,2));
   //--- Call end edit lot function
   LotsEndEdit();
   return;
  }

Em primeiro lugar, nós consideramos o valor atual do volume da transação e aumentamo-lo um passo a partir das especificações do instrumento. Em seguida, fazemos com que o valor obtido coincida com a especificação do instrumento da função NormalizeLots que já tínhamos visto antes.

A seguir, chamamos a função para o processamento da alteração do volume do lote na janela de entrada, pois já tínhamos especificado nesta função todos os procedimentos necessários.

Da mesma forma construímos a função de diminuição do lote.

4.5. Alterando o estado das caixas de verificação.

Na próxima etapa, vamos construir um processador de eventos de reação ao pressionar as caixas de verificação. No nosso painel, existem duas caixas de verificação para ativar e desativar a exibição dos níveis Stop-Loss e Take-Profit no gráfico.

O que deve acontecer ao alterar o estado da caixa de verificação? Em princípio, a função principal do evento deve ser a exibição das linhas no gráfico. Este problema pode ser resolvido de duas maneiras:

  1. a cada toque, criar ou excluir linhas;
  2. criar uma vez as linhas no gráfico juntamente com todos os objetos do painel, e ao alterar o estado das caixas de verificação, exibi-las ou ocultá-las.

Eu prefiro a segunda opção. Para este efeito, habilitamos mais uma biblioteca:

#include <ChartObjects\ChartObjectsLines.mqh>

Em seguida, no bloco private declaramos os objetos das linhas horizontais e anunciamos a função da sua inicialização:

private:
.................
   CChartObjectHLine BuySL, SellSL, BuyTP, SellTP;    // Stop Loss and Take Profit Lines
   
   //--- Create Horizontal line
   bool              CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment);

Especificamos o procedimento de inicialização das linhas horizontais. Primeiro, criamos a linha no gráfico.

//+------------------------------------------------------------------+
//| Create horizontal line                                           |
//+------------------------------------------------------------------+
bool CTradePanel::CreateHLine(long chart, int subwindow,CChartObjectHLine &object,color clr, string comment)
  {
   // All objects must to have separate name
   string name="HLine"+(string)ObjectsTotal(chart,-1,OBJ_HLINE);
   //--- Create horizontal line
   if(!object.Create(chart,name,subwindow,0))
      return false;

Em seguida, definimos a cor, tipo de linha, e adicionamos um comentário para ser exibido ao passar o mouse sobre o objeto.

   //--- Set color of line
   if(!object.Color(clr))
      return false;
   //--- Set dash style to line
   if(!object.Style(STYLE_DASH))
      return false;
   //--- Add comment to line
   if(!object.Tooltip(comment))
      return false;

Ocultamos a linha a parti do gráfico e movemos a exibição da linha para o fundo.

   //--- Hide line 
   if(!object.Timeframes(OBJ_NO_PERIODS))
      return false;
   //--- Move line to background
   if(!object.Background(true))
      return false;

Como uma das opções do nosso painel permite que o trader desloque os níveis de linha de Stop-Loss e Take-Profit no gráfico, nós proporcionaremos ao usuário a possibilidade de alocar a linha:

   if(!object.Selectable(true))
      return false;
   return true;
  }

Agora adicionamos a inicialização das linhas para a função de criação do nosso painel de negociação.

//+------------------------------------------------------------------+
//| Creat Trade Panel function                                       |
//+------------------------------------------------------------------+
bool CTradePanel::Create(const long chart,const string name,const int subwin=0,const int x1=20,const int y1=20,const int x2=320,const int y2=420)
  {
...................
...................
   //--- Create horizontal lines of SL & TP
   if(!CreateHLine(chart,subwin,BuySL,SL_Line_color,"Buy Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellSL,SL_Line_color,"Sell Stop Loss"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,BuyTP,TP_Line_color,"Buy Take Profit"))
     {
      return false;
     }
   if(!CreateHLine(chart,subwin,SellTP,TP_Line_color,"Sell Take Profit"))
     {
      return false;
     }
    return true;
  }

Uma vez que criamos as linhas, especificamos diretamente a função de processamento do evento. Vamos construir a função de processamento do evento da mesma maneira como já tínhamos feito anteriormente com as funções de processamento de eventos. Declaramos as funções de processamento no bloco private:

private:
...............
   void              StopLossLineClick();                            // Click StopLoss Line 
   void              TakeProfitLineClick();                          // Click TakeProfit Line

Adicionamos a chamada da função para o processador de eventos:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
   ON_EVENT(ON_END_EDIT,Lots,LotsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_pips,SLPipsEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_pips,TPPipsEndEdit)
   ON_EVENT(ON_END_EDIT,StopLoss_money,SLMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,TakeProfit_money,TPMoneyEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_percent,RiskPercentEndEdit)
   ON_EVENT(ON_END_EDIT,Risk_money,RiskMoneyEndEdit)
   ON_EVENT(ON_CLICK,StopLoss_pips_b,SLPipsClick)
   ON_EVENT(ON_CLICK,TakeProfit_pips_b,TPPipsClick)
   ON_EVENT(ON_CLICK,StopLoss_money_b,SLMoneyClick)
   ON_EVENT(ON_CLICK,TakeProfit_money_b,TPMoneyClick)
   ON_EVENT(ON_CLICK,Risk_percent_b,RiskPercentClick)
   ON_EVENT(ON_CLICK,Risk_money_b,RiskMoneyClick)
   ON_EVENT(ON_CLICK,Increase,IncreaseLotClick)
   ON_EVENT(ON_CLICK,Decrease,DecreaseLotClick)
   ON_EVENT(ON_CLICK,StopLoss_line,StopLossLineClick)
   ON_EVENT(ON_CLICK,TakeProfit_line,TakeProfitLineClick)
EVENT_MAP_END(CAppDialog)

E, finalmente, especificamos a função para processar eventos. No início da função, verificamos o estado da caixa de verificação. O que acontece a seguir irá depender do estado da caixa de verificação. Se ela estiver pressionada, será necessário atualizar os níveis de exibição antes da exibição de linhas. Depois, colocamos as linhas no gráfico.

//+------------------------------------------------------------------+
//| Show and Hide Stop Loss Lines                                    |
//+------------------------------------------------------------------+
void CTradePanel::StopLossLineClick()
  {
   if(StopLoss_line.Pressed()) // Button pressed
     {
      if(BuySL.Price(0)<=0)
        {
         UpdateSLLines();
        }
      BuySL.Timeframes(OBJ_ALL_PERIODS);
      SellSL.Timeframes(OBJ_ALL_PERIODS);
     }

Se a caixa de verificação não estiver pressionada, as linhas ficarão escondidas.

   else                         // Button unpressed
     {
      BuySL.Timeframes(OBJ_NO_PERIODS);
      SellSL.Timeframes(OBJ_NO_PERIODS);
     }
   ChartRedraw();
   return;
  }

No final da função, chamamos o redesenho do gráfico.

4.6. Operações de negociação

Agora que estão descritas as funções de processamento de eventos para os principais elementos de controle no painel, vamos começar a processar os eventos de toque no botão de operações de negociação. Para realizar transações na conta, nós também usamos a biblioteca padrão MQL5 "Trade.mqh", nas quais está descrita a classe de negociações CTrade.

#include <Trade\Trade.mqh>

Declaramos a classe de negociações no bloco private:

private:
................
   CTrade            Trade;                           // Class of trade operations

E na função de inicialização da nossa classe, inicializamos a classe de negociação. Neste caso vamos definir o número mágico de transações, o nível de desviação para negociar e as políticas de execução das transações.

//+------------------------------------------------------------------+
//| Class initialization function                                    |
//+------------------------------------------------------------------+
CTradePanel::CTradePanel(void)
  {
   Trade.SetExpertMagicNumber(0);
   Trade.SetDeviationInPoints(5);
   Trade.SetTypeFilling((ENUM_ORDER_TYPE_FILLING)0);
   return;
  }

Se desejar, você pode adicionar aqui as funções adicionais para definir o número mágico e o nível de desviação a partir de um programa externo. Não se esqueça que essas funções deverão ser declaradas no bloco public.

Após a preparação, definimos a função para processamento de eventos de toque no botão. Em primeiro lugar, como tínhamos feito antes, vamos declarar a função no bloco private:

private:
.....................
   void              BuyClick();                                     // Click BUY button
   void              SellClick();                                    // Click SELL button
   void              CloseBuyClick();                                // Click CLOSE BUY button
   void              CloseSellClick();                               // Click CLOSE SELL button
   void              CloseClick();                                   // Click CLOSE ALL button

Em seguida, complementamos o controlador do processamento de eventos usando novas funções:

//+------------------------------------------------------------------+
//| Event Handling                                                   |
//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CTradePanel)
...................
   ON_EVENT(ON_CLICK,BUY,BuyClick)
   ON_EVENT(ON_CLICK,SELL,SellClick)
   ON_EVENT(ON_CLICK,CloseBuy,CloseBuyClick)
   ON_EVENT(ON_CLICK,CloseSell,CloseSellClick)
   ON_EVENT(ON_CLICK,CloseAll,CloseClick)
EVENT_MAP_END(CAppDialog)

E, naturalmente, definimos diretamente as funções para processamento de eventos. Examinemos, por exemplo, a função de compra. Quais ações deve executar o nosso programa, ao clicar no botão "BUY"?

Provavelmente, primeiro temos que actualizar o volume da próxima negociação. Lemos o valor no campo do lote, fazemos com que coincida com a especificação do instrumento e verificamos que haja fundos suficientes para a abertura da ordem, em seguida, retornamos o valor atualizado para o painel.

void CTradePanel::BuyClick(void)
  {
   cur_lot=NormalizeLots(StringToDouble(Lots.Text()));
   Lots.Text(DoubleToString(cur_lot,2));

O próximo passo será obter o preço de mercado do instrumento e calcular os níveis de preços de Stop-Loss e Take-Profit, de acordo com os parâmetros especificados no painel:

   double price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double SL=(cur_sl_pips>0 ? NormalizeDouble(price-cur_sl_pips*_Point,_Digits) : 0);
   double TP=(cur_tp_pips>0 ? NormalizeDouble(price+cur_tp_pips*_Point,_Digits) : 0);

E, finalmente, enviamos um pedido de colocação da ordem para o servidor da corretora. Além disso, se ocorrer um erro, é preciso adicionar aqui uma função de mensagem para o reportar ao trader.

   if(!Trade.Buy(NormalizeLots(cur_lot),_Symbol,price,SL,TP,"Trade Panel"))
      MessageBox("Error of open BUY ORDER "+Trade.ResultComment(),"Trade Panel Error",MB_ICONERROR|MB_OK);;
   return;
  }

Da mesma forma, construímos a função para o processamento do toque nos outros botões de negociação. No arquivo anexado, é possível encontrar mais informações sobre código destas funções.

5. Deslocação "manual" dos níveis Stop-Loss e Take-Profit.

Lembramos que, muitas vezes, os traders deslocam os níveis Stop-Loss e Take-Profit para alguns níveis significativos no gráfico. E, na minha opinião, seria errado forçar o usuário a calcular o número de pips entre o preço atual e esse nível. Portanto, permitimos o usuário deslocar a linha até o ponto desejado, enquanto o programa faz tudo o mais.

Para mim, eu decidi não sobrecarregar o programa com o código de processamento dos movimentos do mouse no gráfico, e usar a função padrão do terminal para deslocação de objetos. Para este fim, deixamos o usuário selecionar e deslocar as linhas horizontais. Usando o programa, vamos processar o evento "CHARTEVENT_OBJECT_DRAG"

Como sempre, primeiro, declaramos a função de processamento no bloco public, porque vamos chamar esta função a partir de um programa externo:

public:
................
   virtual bool      DragLine(string name);
Vamos chamar esta função a partir da função OnChartEvent do programa principal, ao acontecer o evento com transferência do nome do objeto.
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      if(TradePanel.DragLine(sparam))
        {
         ChartRedraw();
        }
     }
...........

Na mesma função para processamento do evento, nós devemos:

  • determinar qual linha foi deslocada (Stop-Loss ou Take-Profit);
  • calcular o valor do índice em pontos;
  • exibir o valor obtido na célula respetiva do painel;
  • recalcular o valor de todos os indicadores relacionados no painel;
  • se necessário, alterar o valor dos rádio-botões.

Na função para processar eventos, completamos os três primeiros pontos, e para levar a cabo os últimos pontos, chamamos a função de editação "manual" dos campos respetivos. E, claro, depois de processar os eventos desmarcamos a linha.

//+------------------------------------------------------------------+
//| Function of moving horizontal lines                              |
//+------------------------------------------------------------------+
bool CTradePanel::DragLine(string name)
  {
   if(name==BuySL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(BuySL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      SLPipsEndEdit();
      BuySL.Selected(false);
      return true;
     }
   if(name==SellSL.Name())
     {
      StopLoss_pips.Text(DoubleToString(MathAbs(SellSL.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      SLPipsEndEdit();
      SellSL.Selected(false);
      return true;
     }
   if(name==BuyTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(BuyTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_ASK))/_Point,0));
      TPPipsEndEdit();
      BuyTP.Selected(false);
      return true;
     }
   if(name==SellTP.Name())
     {
      TakeProfit_pips.Text(DoubleToString(MathAbs(SellTP.Price(0)-SymbolInfoDouble(_Symbol,SYMBOL_BID))/_Point,0));
      TPPipsEndEdit();
      SellTP.Selected(false);
      return true;
     }
   return false;
  }

6. Salvar as configurações atuais ao reiniciar

Gostaria de lembrar que, ao reiniciar o programa, é provável que o usuário não queira introduzir novamente todos os valores para o painel. Muitas vezes, no gráfico, o constante puxão do painel para uma zona confortável para o usuário vai irritar muitos de vocês. É possível que alguem já esteja resignado com isto, pois é uma ação forçada apenas ao reiniciar o terminal. Mas não esqueçamos que o programa se reinicia ao alterar levemente o timeframe do gráfico. Isto acontece frequentemente. Adicionalmente, muitos sistemas de negociação exigem estudar os gráficos em vários timeframes. Por isso, nós apenas temos que salvar o estado dos rádio-botões e da caixa de verificação, bem como dos valores de todos os campos introduzidos manualmente pelo usuário. E, claro, o painel deve memorizar o estado e colocação da janela.

Quanto à última ação, ela já esta implementada na classe mãe. Só resta implementar a leitura da informação armazenada ao reiniciar o programa.

E no que se refere aos campos editáveis e ao estado dos botões, vamos ter algo de trabalho. No entanto, sinto a necessidade de dizer que os desenvolvedores já realizaram a maior parte do trabalho, pelo que lhes agradeço muito.

Não vou aprofundar muito na herança de classes, mas devo dizer que a partir do progenitor da classe CObject, todas as classes que sucedem têm a função Save e Load. A partir da sua classe pai de chamadas, nossa classe CTradePanel herdou a função de armazenamento de todos os objetos incluídos ao deinicializar [sic] a classe. No entanto, neste caso já temos que estar esperando uma surpresa desagradável, isto é, as classes CEdit e CBmpButton herdaram funções "vazias":

   //--- methods for working with files
   virtual bool      Save(const int file_handle)                         { return(true);   }
   virtual bool      Load(const int file_handle)                         { return(true);   }
Por conseguinte, precisamos de reescrever estas funções para objetos cujos dados queremos manter. Para este fim, criamos duas classes novas, CEdit_new e CBmpButton_new, que vão ser os herdeiros das classes CEdit e CBmpButton, respetivamente. Nelas especificamos as funções de armazenamento e leitura de dados.
class CEdit_new : public CEdit
  {
public:
                     CEdit_new(void){};
                    ~CEdit_new(void){};
   virtual bool      Save(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      string text=Text();
      FileWriteInteger(file_handle,StringLen(text));
      return(FileWriteString(file_handle,text)>0); 
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      int size=FileReadInteger(file_handle);
      string text=FileReadString(file_handle,size);
      return(Text(text));
     }
   
  };

class CBmpButton_new : public CBmpButton
  {
public:
                     CBmpButton_new(void){};
                    ~CBmpButton_new(void){};
   virtual bool      Save(const int file_handle)
    {
     if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(FileWriteInteger(file_handle,Pressed()));
     }
   virtual bool      Load(const int file_handle)
     {
      if(file_handle==INVALID_HANDLE)
        {
         return false;
        }
      return(Pressed((bool)FileReadInteger(file_handle)));
     }
  };

E, claro, mudamos para novos os tipos de objetos armazenados.

   CEdit_new         Lots;                            // Display volume of next order
   CEdit_new         StopLoss_pips;                   // Display Stop loss in pips
   CEdit_new         StopLoss_money;                  // Display Stop loss in accaunt currency
   CEdit_new         TakeProfit_pips;                 // Display Take profit in pips
   CEdit_new         TakeProfit_money;                // Display Take profit in account currency
   CEdit_new         Risk_percent;                    // Display Risk percent to equity
   CEdit_new         Risk_money;                      // Display Risk in account currency
   CBmpButton_new    StopLoss_line;                   // Check to display StopLoss Line
   CBmpButton_new    TakeProfit_line;                 // Check to display TakeProfit Line
   CBmpButton_new    StopLoss_pips_b;                 // Select Stop loss in pips
   CBmpButton_new    StopLoss_money_b;                // Select Stop loss in accaunt currency
   CBmpButton_new    TakeProfit_pips_b;               // Select Take profit in pips
   CBmpButton_new    TakeProfit_money_b;              // Select Take profit in account currency
   CBmpButton_new    Risk_percent_b;                  // Select Risk percent to equity
   CBmpButton_new    Risk_money_b;                    // Select Risk in account currency

Mas não é suficiente armazenar a informação, também deve-se ler. Para fazer isso, reescrevemos a função de execução do nosso painel de negociação:

public:
.................
   virtual bool      Run(void);

Primeiro, lemos os dados armazenados:

//+------------------------------------------------------------------+
//| Run of Trade Panel                                               |
//+------------------------------------------------------------------+
bool CTradePanel::Run(void)
  {
   IniFileLoad();

Em seguida, atualizamos os valores das variáveis:

   cur_lot=StringToDouble(Lots.Text());
   cur_sl_pips=(int)StringToInteger(StopLoss_pips.Text());     // Stop Loss in pips
   cur_sl_money=StringToDouble(StopLoss_money.Text());         // Stop Loss in money
   cur_tp_pips=(int)StringToInteger(TakeProfit_pips.Text());   // Take Profit in pips
   cur_tp_money=StringToDouble(TakeProfit_money.Text());       // Take Profit in money
   cur_risk_percent=StringToDouble(Risk_percent.Text());       // Risk in percent
   cur_risk_money=StringToDouble(Risk_money.Text());           // Risk in money
   RiskByValue=true;
E, finalmente, chamamos as funções para o processamento nas caixas de verificação que atualizam o estado dos níveis Stop-Loss e Take-Profit:
   StopLossLineClick();
   TakeProfitLineClick();
   return(CAppDialog::Run());
  }

7. "Limpeza geral"

Fizemos um grande trabalho e esperamos que o usuário tenha ficado satisfeito. No entanto, chega um momento em que o usuário, por qualquer motivo desabilita o nosso programa. E, ao sair, temos que limpar por si, isto é, remover do gráfico todos os objetos criados por nós, mas deixar os objetos criados pelo usuário ou por programas de terceiros.

Ao deinicializar [sic] o programa, gera-se o evento Deinit que, pela sua vez, chama a função OnDeinit indicando as razões da deinicialização [sic]. Portanto, de entre as funções mencionadas do programa principal, precisamos chamar a função de deinicialização [sic] da nossa classe:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   TradePanel.Destroy(reason);
   return;
  }

Devemos declarar esta função no bloco "private" da nossa classe

public:
.............
   virtual void      Destroy(const int reason);
No corpo desta função, vamos remover as linhas horizontais do gráfico e chamar a função de deinicialização [sic] da classe pai, que manterá todas as informações necessárias e removerá do gráfico os objetos do painel de negociação.
//+------------------------------------------------------------------+
//| Application deinitialization function                            |
//+------------------------------------------------------------------+
void CTradePanel::Destroy(const int reason)
  {
   BuySL.Delete();
   SellSL.Delete();
   BuyTP.Delete();
   SellTP.Delete();
   CAppDialog::Destroy(reason);
   return;
  }

Conclusão

Caros leitores, colegas e amigos!

Espero, sinceramente, que tenham lido o meu artigo até o fim, e eu gostaria que ele seja útil para vocês.

Aqui eu tentei, usando uma linguagem acessível, contar sobre a minha própria experiência na criação de painéis de negociação e dar um instrumento acabado para trabalhar no mercado.

Por favor, enviem as suas ideias e desejos, contem para mim o que é quer gostariam de ver no nosso painel de negociação. Em troca, eu prometo trazer à vida as ideias mais interessantes e conversar sobre isso em artigos futuros.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2281

Arquivos anexados |
tradepanel.ex5 (319.32 KB)
tradepanel.mq5 (56.85 KB)
Últimos Comentários | Ir para discussão (4)
Pedro Quina
Pedro Quina | 8 set 2016 em 03:18
MetaQuotes Software Corp.:

Novo artigo Criamos um auxiliar na negociação manual foi publicado:

Autor: Dmitriy Gizlyk

 

Parabéns, excelente exemplo.

 

Minha sugestão é  ter um Botton SCALPER , Edit INCREMENT e Edit TIMES .

Dmitriy Gizlyk
Dmitriy Gizlyk | 8 set 2016 em 09:23
PedroQuina:

 

Parabéns, excelente exemplo.

 

Minha sugestão é  ter um Botton SCALPER , Edit INCREMENT e Edit TIMES .

Obrigado
Antonio Padilha
Antonio Padilha | 21 jul 2018 em 21:52
Bom dia! Fiz alguns incrementos no painel, como pode observar no anexo, porem não sou programar profissional, tudo esta funcionando, porem existe alguns problemas, caso tenha interesse posso lhe enviar o codigo para analise e solução dos problemas. 
Felipe Pereira
Felipe Pereira | 2 jul 2021 em 17:13

Ola Dmitriy Gizlyk, tudo bom ?

gostaria de saber se tem como adicionar uma opção para abrir a ordem de compra, venda e fechamento da ordem com atalhos no teclado que eu definir, e se tem como colocar a opção de trailling stop ?

agradeço a atenção

Calculadora de sinais Calculadora de sinais
A calculadora de sinais funciona diretamente a partir do terminal MetaTrader 5, e esta é a sua grande vantagem, pois o terminal pré-seleciona e classifica os sinais. Assim, no terminal MetaTrader 5, o usuário vê apenas os sinais com o máximo de compatibilidade com a sua conta de negociação.
Exemplo de Expert Advisor Exemplo de Expert Advisor
Este artigo exibe os princípios de desenvolvimento de programas em MQL4 mediante a criação de um sistema Expert Advisor simples de exemplo com base no indicador MACD padrão.
Método da área Método da área
O sistema "Método da área" funciona com base na interpretação pouco comum das leituras do oscilador RSI. Neste artigo fala-se tanto do indicador que processa o método da área, como do conselheiro que negocia de acordo com este sistema. Além disso, adicionamos resultados de teste detalhados do conselheiro para vários símbolos, timeframes e valores de área.
Qualidade de Modelagem dos Dados de Um Minuto Qualidade de Modelagem dos Dados de Um Minuto
Qualidade de Modelagem dos Dados de Um Minuto