English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Processamento de eventos trade no Expert Advisor usando a função OnTrade()

Processamento de eventos trade no Expert Advisor usando a função OnTrade()

MetaTrader 5Exemplos | 23 dezembro 2013, 16:38
6 739 0
KlimMalgin
KlimMalgin

Introdução

Qualquer negociador, que escreva Experts em MQL, mais cedo ou mais tarde enfrentará a necessidade de relatar como seu Expert está funcionando. Ou ele pode precisar implementar notificações via SMS ou por e-mail sobre as ações do Expert. Em qualquer caso, temos que "captar" determinados eventos, que ocorrem no mercado ou ações feitas pelo expert, e notificar aos usuários.

Neste artigo, quero contar como você pode implementar o processamento de eventos trade e oferecer a você minha implementação.

Consideremos neste artigo o processamento dos seguintes eventos:

  • Posições
    1. Abrir
    2. Adicionar
    3. Modificar (mudar Stop Loss e Take Profit)
    4. Reverso
    5. Fechar a posição inteira
    6. Fechar parte da posição
  • Ordens pendentes
    1. Colocar
    2. Modificar

1. Como isso funciona?

Antes de começarmos, em termos gerais, vou descrever como os eventos trade funcionam e todos os detalhes necessários serão explicados rapidamente.

Existem eventos predefinidos e personalizados no MQL5. Estamos interessados nos predefinidos, particularmente no evento Trade.

O evento Trade é gerado a todo momento, quando uma operação de negócio é concluída. Toda vez após a geração do evento de negócio a função OnTrade() é chamada. O processamento de ordens e posições será feito dentro da função OnTrade().

2. Template do Expert

Então, vamos criar um novo Expert Advisor. No MetaEditor clique em File (Arquivo) -> New (Novo) para abrir o Assistente MQL5. Selecione Expert Advisor e clique em Next (Próximo) . No diálogo "General properties of the Expert Advisor" ("Propriedades gerais do Expert Advisor") insira o Nome do Expert Advisor e seus próprios dados, se necessário. Denominei o meu Expert Advisor como "TradeControl". Você pode escolher esse nome ou escolher outro de sua preferência, isso não é importante. Não iremos especificar nenhum parâmetro, já que será criado rapidamente ao escrever um expert.

Pronto! O template Expert Advisor foi criado, temos que adicionar a função OnTrade() a ele.

Como resultado, você deve obter o seguinte código:

//+------------------------------------------------------------------+
//|                                              TradeControl_en.mq5 |
//|                                             Copyright KlimMalgin |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "KlimMalgin"
#property link      ""
#property version   "1.00"
//+------------------------------------------------------------------+
//| Função Expert de inicialização                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função Expert de desinicialização                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Função OnTrade                                                   |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

//---
  }
//+------------------------------------------------------------------+
//| Função tick do Expert                                            |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

3. Trabalhando com posições

Vamos começar com o evento trade mais simples - abrindo e fechando posições. Primeiro, você deve entender quais processos ocorrem após pressionar os botões "Sell" (Vender) e "Buy" (Comprar).

Se posicionarmos uma chamada na função OnTrade():

Alert("O evento Trade ocorreu");

Então veremos, que após a abertura pela função de mercado OnTrade() e junto com isso, nosso Alerta foi executado quatro vezes:

Figura 1. Alertas

Figura 1. Alertas

Por que a OnTrade() é chamada quatro vezes e como podemos responder a esses alertas? Para entender isso, veremos a documentação:

 OnTrade

A função é chamada quando o evento Trade ocorre. Isso acontece quando a lista de ordens posicionadas, posições em aberto, histórico de ordens e histórico de negociações é alterada.

Aqui eu devo mencionar uma coisa:

Ao escrever este artigo e me comunicar com desenvolvedores, descobri que mudanças no histórico não levam à chamada da OnTrade()! A verdade é que a função OnTrade() é chamada somente quando a lista de ordens posicionadas e de posições em aberto for alterada! Ao desenvolver manipuladores de eventos de trade você pode se deparar com o fato de que as ordens executadas e as negociações podem aparecer no histórico com atraso, e você não será capaz de processá-las quando a função OnTrade() estiver em execução.

Vamos voltar agora aos eventos. Como temos visto - quando você abre a mercado, o evento Trade ocorre 4 vezes:

  1. Criação de ordem para abrir a mercado.
  2. Execução de negociação.
  3. Passagem da ordem completa para o histórico.
  4. Abertura de posição.

  Para rastrear esse processo no terminal, preste atenção à lista de ordens na guia "Trade" da janela do MetaTrader:

Figura 2. Lista de ordens na aba "Negociação"

Figura 2. Lista de ordens na aba "Negociação"

Uma vez que você abra uma posição (por ex., para baixo), na lista de ordens aparece uma ordem que tem o estado iniciada (Fig. 2). Isto muda a lista de ordens posicionadas e o evento Trade é chamado. É a primeira vez em que a função OnTrade() é ativada. Então, a negociação é executada por ordem criada. Neste estágio, a função OnTrade() é executada uma segunda vez. Logo que a negociação é executada, a ordem completa e sua negociação executada serão enviadas para o histórico e a função OnTrade() é chamada uma terceira vez. No último estágio, uma posição é aberta pela negociação executada e a função OnTrade() é chamada pela quarta vez.

Para "capturar" o momento da abertura de posição a cada vez que você chama a OnTrade() você tem que analisar a lista de ordens, o histórico de ordens e o histórico de negociações. Isso é o que faremos agora!

OK, a função OnTrade() é chamada, e precisamos saber se o número de ordens mudou na aba "Negociação". Para isso, temos que comparar o número de ordens na lista no momento da chamada OnTrade() anterior e agora. Para descobrir quantas ordens existem na lista no momento, usaremos a função OrdersTotal() . E para saber quantas ordens foram listadas na chamada anterior, teremos que manter o valor OrdersTotal() em cada chamada OnTrade(). Para isso, criaremos uma variável especial:

int OrdersPrev = 0;        // Número de ordens  no momento da chamada anterior da OnTrade()

No final da função OnTrade() a variável OrdersPrev será atribuída com o valor de OrdersTotal().

Você também pode considerar a situação quando você roda o Expert Advisor e já existem ordens pendentes na lista. O Expert deve ser capaz de identificá-las, então, na função OnInit() a variável OrdersPrev também deve ser atribuída com o valor OrdersTotal(). As mudanças, que acabamos de criar no Expert, aparecerão desta forma:

int OrdersPrev = 0;        // Número de ordens  no momento da chamada anterior da OnTrade()


//+------------------------------------------------------------------+
//| Função Expert de inicialização                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   OrdersPrev = OrdersTotal();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Função OnTrade                                               |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

OrdersPrev = OrdersTotal();
//---
  }

Agora que sabemos o número de ordens para as chamadas atuais e anteriores - podemos descobrir quando a ordem apareceu na lista e quando, por qualquer motivo, desapareceu. Para isso, usaremos a seguinte condição:

if (OrdersPrev < OrdersTotal())
{
  // Ordem apareceu
}
else if(OrdersPrev > OrdersTotal())
{
  // Ordem desaparaceu
}

Então, acontece que se na chamada anterior tivermos menos ordens que agora, a ordem aparece na lista (múltiplas ordens não podem aparecer simultaneamente), mas se for o contrário, por ex., agora temos menos ordens que em uma chamada OnTrade() anterior, então a ordem é ou executada ou cancelada por algum motivo. Quase todo o trabalho com posições começa com estas duas condições.

Apenas Stop Loss e Take Profit requerem um trabalho separado com eles. Para a função OnTrade() vou adicionar o código que funciona com posições. Vamos considerar o seguinte:

void OnTrade()
  {
//---
Alert("Evento de negociação ocorreu");

HistorySelect(start_date,TimeCurrent());

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Selecione a última ordem para trabalhar com ela
   _GetLastError=GetLastError();
   Print("Erro #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"A ordem chegou para processamento");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Salvando o ingresso da ordem para trabalhos futuros
   }
   
}
else if(OrdersPrev > OrdersTotal())
{
   state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);

   // Se a ordem não for encontrado, gerar um erro
   _GetLastError=GetLastError();
   if (_GetLastError != 0){Alert("Erro #",_GetLastError," ordem não encontrada!");LastOrderTicket = 0;}
   Print("Erro #",_GetLastError," state: ",state);ResetLastError();


   // Se a ordem foi totalmente executada
   if (state == ORDER_STATE_FILLED)
   {
      // Então analise o último negócio
      // --
      Alert(LastOrderTicket, "Ordem executada, indo para negociação");
      switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
      {
         
         // Entrando no mercado
         case DEAL_ENTRY_IN:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " ordem chamou negociação #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // Se os volumes de posição e de negócio são iguais, então posição acabou de ser aberta
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Posição de compra foi aberta no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // Se os volumes de posição e acordo não são iguais, houve aumento da posição
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Houve aumento da posição de compra no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
               break;
               
               case 1:
               // Se os volumes de posição e de negócio são iguais, então posição acabou de ser aberta
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Posição de venda foi aberta no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // Se os volumes de posição e acordo não são iguais, houve aumento da posição
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Houve aumento da posição de venda no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  
               break;
               
               default:
                  Alert("Tipo de código não processado ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Saindo do mercado
         case DEAL_ENTRY_OUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " ordem chamou negociação #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // Se a posição, que tentamos fechar, ainda estiver em aberto, entao, nós fechamos apenas parte dela
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Parte da posição de venda foi fechada no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " com lucro = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // Se a posição nao foi encontrada, então, ela foi fechada totalmente
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Posição de venda foi fechada no par ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " com lucro = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
               break;
               
               case 1:
               // Se a posição, que tentamos fechar, ainda estiver em aberto, entao, nós fechamos apenas parte dela
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Parte da posição de compra foi fechada no par ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " com lucro = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // Se a posição nao foi encontrada, então, ela foi fechada totalmente
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Posição de compra foi fechada no par ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " com lucro = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  
               break;
               
               default:
                  Alert("Tipo de código não processado ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Reverso
         case DEAL_ENTRY_INOUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " ordem chamou negociação #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
                  Alert("Venda revertida para compra no par ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " com lucro = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               case 1:
                  Alert("Compra revertida no par ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " com lucro = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               default:
                  Alert("Tipo de código não processado ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Indica o registro de estado
         case DEAL_ENTRY_STATE:
            Alert("Indica o registro de estado. Tipo de código não processado ", 
            HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
         break;
      }
      // --
   }
}

OrdersPrev = OrdersTotal();

//---
  }

Também se certifique que no começo do programa você tenha declarado as seguintes variáveis:

datetime start_date = 0;   // Data, do qual o histórico comecou a ser lido

int OrdersPrev = 0;        // Número de ordens  no momento da chamada anterior da OnTrade()
int PositionsPrev = 0;     // Número de posições no momento da chamada anterior da OnTrade()
ulong LastOrderTicket = 0; // Ingresso da última ordem processada

int _GetLastError=0;       // Erro no código
long state=0;              // Estado da ordem

Vamos voltar aos conteúdos da OnTrade().

Você pode comentar a função Alert no começo, mas eu deixarei. A seguir, vem a função HistorySelect() . Ela gera uma lista de histórico de negociações e ordens para um período específico de tempo, que é definido por dois parâmetros da função. Se esta função não for chamada antes de ir para o histórico de ordens e negociações, não teremos nenhuma informação porque as listas do histórico estarão vazias. Após chamar HistorySelect(), as condições são avaliadas já que acabaram de ser escritas.

Quando chegam novas ordens, primeiro a selecionamos e verificamos por erros:

OrderGetTicket(OrdersTotal()-1);// Selecionar a última ordem para trabalhar
_GetLastError=GetLastError();
Print("Erro #",_GetLastError);ResetLastError();

Após selecionar a ordem, obtemos o código de erro usando a função GetLastError() . Depois, usando a função Print() imprimimos o código no diário e usando a função ResetLastError() resetamos o código de erro para zero, então para a próxima chamada GetLastError() para outras situações, não teremos o mesmo código de erro.

Após a verificação por erros, se a ordem tiver sido selecionado com sucesso, verifique seu estado:

if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
   Alert(OrderGetTicket(OrdersTotal()-1),"A ordem chegou para processamento");
   LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Salvando o ingresso da ordem para trabalhos futuros
}

Se a ordem tiver o estado iniciado, por ex., estiver verificada pela exatidão, mas ainda não foi aceita, então é esperado que seja executada em um futuro próximo, e apenas fornecemos um Alert() notificando que a ordem está sendo processada e salvamos seu ingresso até a próxima chamada OnTrade(). Em vez da função Alert() você pode usar qualquer outro tipo de notificação.

No código acima

OrderGetTicket(OrdersTotal()-1)

irá retornar o último ingresso da ordem da lista completa de ordens.

OrdersTotal()-1 indica que precisamos obter a última ordem. Devido a função OrdersTotal() retornar o número total de ordens (por ex., se existe 1 ordem na lista, então a OrdersTotal() irá retornar 1), e o número índice da ordem é contado a partir de 0, então, para obter o número índice da última ordem temos que diminuir 1 do número total de ordens (se OrdersTotal() retornar 1, então número índice desta ordem será igual a 0). E a função OrderGetTicket() por sua vez irá retornar o ingresso da ordem, no qual o número será passado a ele.

Esta foi a primeira condição, geralmente ela é acionada quando for a primeira chamada OnTrade(). Em seguida vem a segunda condição, que é conseguida na segunda chamada OnTrade(), quando a ordem é executada, ela vai para o histórico e a posição deve abrir.

Se a ordem não estiver presente na lista, então ela foi para o histórico, ela definitivamente deve estar lá! Então, recorremos ao histórico de ordens usando a função HistoryOrderGetInteger() para obter o estado da ordem. E para ler os dados do histórico para uma ordem em particular, precisamos de seu ingresso. Para isso, se for a primeira condição, o ingresso de ordem de entrada terá sido armazenado na variável LastOrderTicket.

Assim obtemos o estado da ordem, indicando o ingresso da ordem como o primeiro parâmetro para HistoryOrderGetInteger(), e tipo de propriedade necessária - como o segundo. Após tentar obter o estado da ordem, obtemos o código de erro e escrevemos no diário. É necessário, no caso de sua ordem que precisamos trabalhar, ainda não ter entrado no histórico, e recorremos a ele (experiências mostram que isto é possível e bastante. Escrevi sobre isso no começo deste artigo).

Se ocorrer um erro, o processamento para porque não há dados para trabalhar e nenhuma das seguintes condições serão alcançadas. E se a chamada HistoryOrderGetInteger() foi bem sucedida e a ordem tiver o estado "Order is fully executed" (Ordem completamente executada):

// Se a ordem foi totalmente executada
if (state == ORDER_STATE_FILLED)

Então forneça outra notificação:

// Então analise o último negócio
// --
  Alert(LastOrderTicket, "Ordem executada, indo para negociação");

E vamos processar a negociação que foi evocada por esta ordem. Primeiro, encontre a direção da negociação (propriedade DEAL_ENTRY ). Direção não é comprar ou vender, mas a Entrada no mercado, Saída do mercado, Reverso ou Indicação do registro de estado. Assim, usando a propriedade DEAL_ENTRY podemos descobrir se a ordem foi enviada para a posição em abert, posição fechada ou reverso.

Para analisar a negociação e seus resultados, vamos recorrer também ao histórico usando a seguinte construção:

switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
{
  ...
}

Funciona da mesma forma com as ordens:

HistoryDealsTotal() retorna o número total de negociações. Para obter o número da última negociação diminuímos 1 do valor HistoryDealsTotal(). O número resultante da negociação é passado para a função HistoryDealGetTicket() que por sua vez passo o ingresso da negociação selecionada para a função HistoryDealGetInteger() . E a função HistoryDealGetInteger(), pelo ingresso especificado e tipo de propriedade, irá retornar a direção da negociação.

Vamos examinar detalhadamente a direção da Entrada de mercado. As outras direções serão cobertas brevemente, conforme são processadas quase da mesma forma.

O valor da expressão, obtido da HistoryDealGetInteger(), é comparado com os valores dos blocos de caso, até uma combinação ser encontrada. Suponha que estamos entrando no mercado, por ex., abrindo uma ordem de venda. Então o primeiro bloco será executado:

// Entrando no mercado
case DEAL_ENTRY_IN:

No início do bloco você é notificado sobre a criação da negociação. Ao mesmo tempo, esta notificação garante que tudo saiu OK e a negociação está sendo processada.

Após a notificação, vem outro bloco de troca que analisa o tipo de negociação:

   switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
   {
      case 0:
      // Se os volumes de posição e de negócio são iguais, então posição acabou de ser aberta
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Posição de compra foi aberta no par ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // Se os volumes de posição e acordo não são iguais, houve aumento da posição
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Houve aumento da posição de compra no par ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
      break;
      
      case 1:
      // Se os volumes de posição e de negócio são iguais, então posição acabou de ser aberta
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Posição de venda foi aberta no par ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // Se os volumes de posição e acordo não são iguais, houve aumento da posição
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Houve aumento da posição de venda no par ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         
      break;
      
      default:
         Alert("Tipo de código não processado ",
               HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
      break;
   }

Informe-se sobre a negociação pelo histórico - da mesma forma que anteriormente, exceto a propriedade especificada. Nessa hora você deve especificar o DEAL_TYPE para saber se foi feita uma ordem de venda ou de compra. Analisei apenas os tipos compra e venda, mas além deles existem mais quatro. Mas, estes quatro tipos restantes de negociações são menos comuns, então em vez de quatro blocos de caso, apenas um bloco padrão é usado para eles. Ele fornecerá um Alert() como o código do tipo.

Como você provavelmente percebeu no código, não apenas a abertura das posições de compra e venda é processada, mas também seus incrementos. Para determinar quando a posição foi incrementada, e quando foi aberta - você precisa comparar o volume da negociação executada e a posição que tornou-se o resultado desta negociação. Se o volume da posição for igual ao volume da negociação executada - esta posição foi aberta, e se os volumes da posição e negociação são diferentes - esta posição foi incrementada. Isto se aplica tanto para as posições de compra(no bloco '0' de caso) e as posições de venda(no bloco '1' de caso). O último bloco é o padrão, que lida com todas as situações diferentes de compra e venda. O processamento completo consiste na notificação sobre o código de tipo retornado pela função HistoryDealGetInteger().

E finalmente, a última preocupação sobre o trabalho com posições. Isso é o processamento das alterações nos valores Stop Loss e Take Profit. Para saber qual dos parâmetros de posição mudou, precisamos comparar o estado atual e anterior de seus parâmetros. Os valores atuais dos parâmetros de posição podem sempre ser obtidos usando as funções de serviço, mas os valores anteriores devem ser salvos.

Para isso, escreveremos uma função especial, que irá salvar os parâmetros de posição ao array de estruturas:

void GetPosition(_position &Array[])
  {
   int _GetLastError=0,_PositionsTotal=PositionsTotal();

   int temp_value=(int)MathMax(_PositionsTotal,1);
   ArrayResize(Array, temp_value);

   _ExpertPositionsTotal=0;
   for(int z=_PositionsTotal-1; z>=0; z--)
     {
      if(!PositionSelect(PositionGetSymbol(z)))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect() - Erro #",_GetLastError);
         continue;
        }
      else
        {
            // se a posição foi encontrada, coloque suas informações no array
            Array[z].type         = PositionGetInteger(POSITION_TYPE);
            Array[z].time         = PositionGetInteger(POSITION_TIME);
            Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
            Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
            Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
            Array[z].sl           = PositionGetDouble(POSITION_SL);
            Array[z].tp           = PositionGetDouble(POSITION_TP);
            Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            Array[z].comission    = PositionGetDouble(POSITION_Comissão);
            Array[z].swap         = PositionGetDouble(POSITION_SWAP);
            Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
            Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
            Array[z].comment      = PositionGetString(POSITION_COMMENT);
        _ExpertPositionsTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertPositionsTotal,1);
   ArrayResize(Array,temp_value);
  }

Para usar esta função devemos adicionar o seguinte código no bloco da declaração de variáveis globais:

/*
 *
 * Estrutura que armazena informações sobre as posiçoes
 *
 */
struct _position
{

long     type,          // Tipo de posição
         magic;         // Número mágico para a posição
datetime time;          // Hora da abertuda de posição

double   volume,        // Volume da posição
         priceopen,     // Preço da posição
         sl,            // Nível de Stop Loss para a posição em aberto
         tp,            // Nível de Take Profit da posição em aberto
         pricecurrent,  // Preço atual do símbolo
         comission,     // Comissão
         swap,          // Swap acumulado
         profit;        // Lucro atual

string   symbol,        // Símbolo, pelo qual foi aberta a posição
         comment;       // Comentário da posição
};

int _ExpertPositionsTotal = 0;

_position PositionList[],     // Array que armazena informações sobre a posição
          PrevPositionList[];

O protótipo da função GetPosition() foi encontrado há muito tempo nos artigos do site www.mql4.com, mas eu não o encontrei e não posso especificar a fonte. Não vou discutir o trabalho desta função em detalhes. O ponto é que esta função como um parâmetro por referência passou um array do tipo _position (estrutura com campos correspondentes aos campos de posição) no qual todas as informações sobre as posições e valores atualmente abertos de seus parâmetros passaram.

Para rastrear convenientemente as alterações nos parâmetros de posição, vamos criar dois arrays do tipo _position. Estes arrays são PositionList[] (o estado atual das posições) e PrevPositionList[] (o estado anterior das posições).

Para começar a trabalhar com posições, devemos adicionar a próxima chamada em OnInit() e no final de OnTrade():

GetPosition(PrevPositionList);

Também no começo de OnTrade() devemos adicionar a chamada:

GetPosition(PositionList);

Agora nos arrays PositionList[] e PrevPositionList[] à nossa disposição haverá informações sobre posições sobre a chamada OnTrade atual e anterior respectivamente.

Agora vamos considerar o código atual das alterações de rastreamento em sl e tp:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
   string _alerts = "";
   bool modify = false;
     
   for (int i=0;i<_ExpertPositionsTotal;i++)
   {
      if (PrevPositionList[i].sl != PositionList[i].sl)
      {
         _alerts += "No par "+PositionList[i].symbol+" Stop Loss foi alterado de "+ PrevPositionList[i].sl +" para "+ PositionList[i].sl +"\n";
         modify = true;
      }
      if (PrevPositionList[i].tp != PositionList[i].tp)
      {
         _alerts += "No par "+PositionList[i].symbol+" Take Profit foi alterado de "+ PrevPositionList[i].tp +" para "+ PositionList[i].tp +"\n";
         modify = true;
      }
      
   }
   if (modify == true)
   {
      Alert(_alerts);
      modify = false;
   }
}

Como vemos, o código não é muito grande, mas isso é apenas por causa do trabalho preparatório considerável. Vamos nos aprofundar nisso.

Tudo começa com a condição:

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))

Aqui vemos, que nem as ordens ou as posições foram posicionadas ou excluídas. Se a condição for satisfeita, então muito provavelmente algumas posições ou ordens foram alteradas.

No começo da função duas variáveis são declaradas:

  • _alerts -armazena todas as notificações sobre as alterações
  • modify - permite que você exiba as mensagens sobre as alterações apenas se elas realmente existiram.

Em seguida no circuito verificamos a combinação dos valores de Take Profit e Stop Losse na chamada OnTrade() atual e anterior para cada posição. As informações sobre todas as incompatibilidades são armazenadas na variável _alerts e depois serão exibidas pela função Alert() . A propósito, o processamento da modificação das ordens pendentes será realizado da mesma forma.

Por hora vamos finalizar com as posições e seguir para o posicionamento de ordens pendentes.

4. Trabalhando com ordens

Vamos começar com o posicionamento do evento de ordens.

Quando uma nova ordem pendente aparece, o evento Trade é gerado apenas uma vez, mas é o suficiente para processar! Coloque o código, que funcione com ordens pendentes, no corpo do operador:

if (OrdersPrev < OrdersTotal())

E obtenha o seguinte:

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Selecione a última ordem para trabalhar com ela
   _GetLastError=GetLastError();
   Print("Erro #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"A ordem chegou para processamento");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Salvando o ingresso da ordem para trabalhos futuros
   }
   
   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {
      switch(OrderGetInteger(ORDER_TYPE))
      {
         case 2:
            Alert("Ordem pendente Buy Limit #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;
         
         case 3:
            Alert("Ordem pendente Sell Limit #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;
         
         case 4:
            Alert("Ordem pendente Buy Stop #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;
         
         case 5:
            Alert("Ordem pendente Sell Stop #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;
         
         case 6:
            Alert("Ordem pendente Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;
                 
         case 7:
            Alert("Ordem pendente Sell Stop Limit  #", OrderGetTicket(OrdersTotal()-1)," aceita!");
         break;         
      }
   }
}

Aqui o código, que funciona com ordens pendentes, começa com o seguinte:

   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {

Primeiro o estado da ordem é verificado. A ordem deve ter o estado ORDER_STATE_PLACED, por ex., deve ser aceita. E se esta condição for satisfeita, então vem o operador switch que imprime uma mensagem dependendo do tipo da ordem.

Depois, trabalharemos com eventos, que ocorrem quando as ordens são modificadas. A modificação de ordens é similar a modificação de posições. Similarmente, a estrutura que armazena as propriedades das ordens é criada:

/*
 *
 * Estrutura que armazena infomações sobre ordens
 *
 */
struct _orders
{

datetime time_setup,       // hora da colocação de ordem
         time_expiration,  // hora da expiração da ordem
         time_done;        // hora da execução ou cancelamento da ordem
         
long     type,             // Tipo de ordem
         state,            // Estado da ordem
         type_filling,     // tipo de execução por lembrança
         type_time,        // tempo de vida da ordem
         ticket;           // ingresso da ordem
         
long     magic,            // ID do Expert Advisor, que colocou a ordem
                           // (destinado a garantir que cada Expert 
                           // deve colocar o seu próprio número único)
                           
         position_id;      // ID posição, que é colocado na ordem,
                           // Quando ele é executado. Cada ordem executada invoca uma
                           // negócio, que abre novas ou altera posções 
                           // existentes. O ID de posição le colocado 
                           // no momento em que a ordem foi executada.
                           
double volume_initial,     // volume inicial para colocar um ordem
       volume_current,     // volume sem preencher
       price_open,         // preço, especificado na ordem
       sl,                 // nível de Stop Loss
       tp,                 // nível de Take Profit
       price_current,      // Preço atual pela símbolo da ordem
       price_stoplimit;    // Preço de colocação de ordem limite quando StopLimit ordem é acionada
       
string symbol,             // símbolo, pelo qual foi colocada a ordem
       comment;            // Comentário
                           
};

int _ExpertOrdersTotal = 0;

_orders OrderList[],       // Arrays que armazenam informações de ordens
        PrevOrderList[];

Cada campo da estrutura corresponde a uma das propriedades da ordem. Após declarar a estrutura, a variável do tipo int e dois arrays do tipo _orders são declarados. A variável _ExpertOrdersTotal armazenará o número total de ordens e os arrays OrderList[] e PrevOrderList[] irão armazenar informações sobre ordens na chamada OnTrade atual e anterior respectivamente.

A função por si parecerá com o seguinte:

void GetOrders(_orders &OrdersList[])
  {
   
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

   int temp_value=(int)MathMax(_OrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z--)
     {
      if(!OrderGetTicket(z))
        {
         _GetLastError=GetLastError();
         Print("GetOrders() - Erro #",_GetLastError);
         continue;
        }
      else
        {
        OrdersList[z].ticket          = OrderGetTicket(z);
        OrdersList[z].time_setup      = OrderGetInteger(ORDER_TIME_SETUP);
        OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION);
        OrdersList[z].time_done       = OrderGetInteger(ORDER_TIME_DONE);
        OrdersList[z].type            = OrderGetInteger(ORDER_TYPE);
        
        OrdersList[z].state           = OrderGetInteger(ORDER_STATE);
        OrdersList[z].type_filling    = OrderGetInteger(ORDER_TYPE_FILLING);
        OrdersList[z].type_time       = OrderGetInteger(ORDER_TYPE_TIME);
        OrdersList[z].magic           = OrderGetInteger(ORDER_MAGIC);
        OrdersList[z].position_id     = OrderGetInteger(ORDER_POSITION_ID);
        
        OrdersList[z].volume_initial  = OrderGetDouble(ORDER_VOLUME_INITIAL);
        OrdersList[z].volume_current  = OrderGetDouble(ORDER_VOLUME_CURRENT);
        OrdersList[z].price_open      = OrderGetDouble(ORDER_PRICE_OPEN);
        OrdersList[z].sl              = OrderGetDouble(ORDER_SL);
        OrdersList[z].tp              = OrderGetDouble(ORDER_TP);
        OrdersList[z].price_current   = OrderGetDouble(ORDER_PRICE_CURRENT);
        OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT);
        
        OrdersList[z].symbol          = OrderGetString(ORDER_SYMBOL);
        OrdersList[z].comment         = OrderGetString(ORDER_COMMENT);
        
        _ExpertOrdersTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertOrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

  }

Similar à função GetPosition(), ela lê a informação sobre as propriedades de cada ordem posicionada e as coloca no array, passada para ele como parâmetro de entrada. O código de função deve ser posicionado no final do seu expert e suas chamadas - como segue:

GetOrders(PrevOrderList);

Colocado em OnInit() e no final de OnTrade().

GetOrders(OrderList);

Colocado no começo de OnTrade().

Agora considere o código que processará a modificação das ordens. É um loop e complementa o código da modificação de posições:

   for (int i = 0;i<_ExpertOrdersTotal;i++)
   {
      if (PrevOrderList[i].sl != OrderList[i].sl)
      {
         _alerts += "Ordem "+OrderList[i].ticket+" de Stop Loss foi alterada de "+ PrevOrderList[i].sl +" para "+ OrderList[i].sl +"\n";
         modify = true;
      }
      if (PrevOrderList[i].tp != OrderList[i].tp)
      {
         _alerts += "Ordem "+OrderList[i].ticket+" de Take Profit foi alterado de "+ PrevOrderList[i].tp +" para "+ OrderList[i].tp +"\n";
         modify = true;
      }
   }

O loop processa todas as ordens e compara com os valores de Stop Losses e Take Profits nas chamadas OnTrade() atual e anterior. Se houverem diferenças, serão salvas na variável _alerts e quando o loop for concluído, elas serão exibidas pela função Alert().

Este código é colocado no corpo do operador.

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
        
Imediatamente após o loop, que funciona com posições.
            

Agora o trabalho com eventos trade não termina. Este artigo cobre apenas os princípios principais do trabalho com o evento Trade . Em geral, as possibilidades oferecidas por este método são bastante amplas e estão além do escopo deste artigo.

Conclusão

A habilidade para trabalhar com eventos trade (como parte da linguagem MQL5) é potencialmente uma forte ferramenta que permite não apenas implementar algorítimos relativamente rápido de verificação de ordem e gerar relatórios de negociação, mas também reduzir o custo com recursos de sistema e volume de código fonte, o que será um benefício indubitável para os desenvolvedores.

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

Arquivos anexados |
tradecontrol_pt.mq5 (20.46 KB)
Como chamar indicadores no MQL5 Como chamar indicadores no MQL5
Com a nova versão da linguagem de programação MQL disponível, não apenas a abordagem para lidar com indicadores mudou, mas também, existem novas formas de como criar indicadores. Além disso, você tem a flexibilidade adicional trabalhando com os buffers do indicador - agora você pode especificar a direção desejada de indexação e obter exatamente quantos valores de indicadores você quiser. Este artigo explica os métodos básicos de chamada de indicadores e recuperar dados dos buffers do indicador.
Indicadores personalizados no MQL5 para novatos Indicadores personalizados no MQL5 para novatos
Qualquer assunto novo parece complicado e difícil de aprender para um principiante. Os assuntos que conhecemos parecem muito simples e claros para nós. Mas, simplesmente não lembramos, que todos têm que estudar algo desde o início, até a nossa língua materna. O mesmo é com a linguagem de programação MQL5 que oferece amplas possibilidades para desenvolver estratégias próprias de negociação - você pode aprender a partir de noções básicas e dos exemplos mais simples. A interação de um indicador técnico com o terminal de cliente MetaTrader 5 é considerada neste artigo sobre o exemplo de um indicador personalizado simples SMA.
Estilos de desenhos no MQL5 Estilos de desenhos no MQL5
Existem 6 estilos de desenho no MQL4 e 18 estilos de desenho no MQL5. Então pode valer a pena escrever um artigo para introduzir os estilos de desenhos do MQL5. Neste artigo consideraremos os detalhes de estilos de desenho no MQL5. Além disso, criaremos um indicador para demonstrar como usar esses estilos de desenhos e refinar a diagramação.
Usando os Ponteiros de Objeto no MQL5 Usando os Ponteiros de Objeto no MQL5
Predefinidamente, todos os objetos no MQL5 são passados por referência, mas há a possibilidade de usar os ponteiros de objeto. Porém, é necessário realizar a verificação do ponteiro, porque o objeto pode não ser inicializado. Neste caso, o programa MQL5 será finalizado com o erro crítico e descarregado. Os objetos, criados automaticamente, não causam tal erro, então, neste sentido, são bastante seguros. Neste artigo, tentaremos entender a diferença entre a referência do objeto e o ponteiro do objeto, e considere como escrever o código seguro, que usa os ponteiros.