Como Lidar Com o Erro 146, ("Trade context busy")

Andrey Khatimlianskii | 28 abril, 2016

1. O que é o "Trade Context" (Contexto de Negociação) em termos do Terminal Cliente MetaTrader 4

Extraído da Referência do MetaEditor:

    Para operar com Experts e Scripts, é fornecido apenas uma thread (linha de instrução) que roda no contexto de negociação do programa (contexto da negociação automática de Experts e scripts). É por isso que, se este contexto estiver ocupado com uma operação de negociação pelo Expert, um outro Expert ou Script não poderá chamar as funções de negociação, naquele momento, devido ao erro 146 (ERR_TRADE_CONTEXT_BUSY).

Em outras palavras, apenas um Expert (Script) poderá negociar por vez. Todos os outros Experts que tentarem iniciar a negociação serão interrompidos pelo erro 146. Este artigo irá encontrar soluções para este problema.



2. Função IsTradeAllowed()

A maneira mais simples de descobrir se o contexto de negociação está ocupado é usar a função chamada IsTradeAllowed().

Extraído da Referência do MetaEditor:

    "bool IsTradeAllowed()

    Retorna true se é permitido o Expert negociar e se a thread para negociação não estiver ocupada, caso contrário, ele retornará false.

Isto significa que se pode tentar negociar apenas se a função IsTradeAllowed() retorna TRUE.

A verificação deve ser feita pouco antes de uma operação de negociação.

Um exemplo errado do uso da função:

int start()
  {
    // verifica se o contexto de negociação está livre
    if(!IsTradeAllowed())
      {
        // se a função IsTradeAllowed() retornar FALSE, ela informa o usuário sobre isso,
        Print("O Contexto de negociação está ocupado! O Expert não poderá abrir posição! ");
        // e encerra a operação do Expert. Ele será reiniciado quando o próximo tick 
        // aparecer
        return(-1);
      }
    else
      {
        // se a função IsTradeAllowed() retornar TRUE, ele informa o usuário sobre isso 
        // e continua a trabalhar
        Print("O contexto de negociação está livre! Nós continuamos a trabalhar...");
      }
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // Abre uma posição
    if(OrderSend(...) < 0) 
        Alert("Erro ao abrir uma posição # ", GetLastError());
    return(0);
  }

Neste exemplo, o status do contexto de negociação é verificado no início da função start(). É uma ideia errada: O contexto de negociação pode ser ocupado por um outro Expert durante o tempo gasto pelo nosso Expert em calcular tudo (a necessidade da entrada no mercado, os níveis de Stop Loss e Take Profit, tamanho do lote, etc.). Nesse caso, a tentativa de abrir uma posição não terá sucesso.

Um exemplo adequado do uso da função:

int start()
  {
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // agora vamos verificar se o contexto de negociação está livre
    if(!IsTradeAllowed())
      {
        Print("O Contexto de negociação está ocupado! O Expert não poderá abrir posição! ");
        return(-1);
      }
    else
        Print("O contexto de negociação está livre! Tentando abrir uma posição ... ");
    // Se a verificação teve sucesso, abre uma posição
    if(OrderSend(...) < 0) 
        Alert("Erro ao abrir uma posição # ", GetLastError());
    return(0);
  }

O estado do contexto de negociação é verificado aqui imediatamente antes da abertura da posição, e a probabilidade de que outro Expert interponha entre estas duas ações será muito menor. (Apesar de tudo, ela ainda existe. Isso será considerado abaixo).

Este método possui duas desvantagens essenciais:

  • ainda é provável que os Experts verifiquem o estado simultaneamente e, tendo recebido os resultados positivos, ele irá tentar negociar ao mesmo tempo
  • se a verificação falhar, o Expert irá tentar negociar novamente apenas no próximo tick; tal atraso é altamente indesejável

O segundo problema pode ser resolvido com bastante facilidade: ele precisa apenas esperar que o contexto de negociação se torne livre. Em seguida, o Expert irá começar a negociar imediatamente após o outro Expert ter terminado.

Isso provavelmente será parecido com este:

int start()
  {
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // verifica se o contexto de negociação está livre
    if(!IsTradeAllowed())
      {
        Print("O Contexto de negociação está ocupado! Aguarda até que ele esteja livre...");
        // loop infinito
        while(true)
          {
            // se o Expert foi interrompido pelo usuário, parar a operação
            if(IsStopped()) 
              { 
                Print("O Expert foi parada pelo utilizador!"); 
                return(-1); 
              }
            // se contexto de negociação ficou livre, encerra o loop e inicia a negociação
            if(IsTradeAllowed())
              {
                Print("O contexto de negociação ficou livre!");
                break;
              }
            // Se nenhuma condição de quebra do loop for satisfeita, "esperar" por 0.1 segundos
            // e reiniciar a verificação
            Sleep(100);
          }
      }
    else
        Print("O contexto de negociação está livre! Tentando abrir uma posição...");
    // tentando abrir uma posição
    if(OrderSend(...) < 0) 
        Alert("Erro ao abrir uma posição # ", GetLastError());
    return(0);
  }
Nesta realização atual, temos alguns pontos problemáticos novamente:
  •  já que a função IsTradeAllowed() é responsável não só pelo status do contexto de negociação, mas também pela ativação desativação da negociaçao dos Experts, o Expert pode "travar" em um loop infinito; ele somente irá parar, se este for removido do gráfico manualmente
  •  se o Expert aguardar, por apenas alguns segundos, até que o contexto de negociação esteja livre, os preços podem se alterar, sendo impossível de negociar com eles - os dados devem ser atualizados, a abertura, os níveis de Take Profit e Stop Loss da posição a ser aberta devem ser recalculados

O código corrigido será parecido com este:

// tempo (em segundos) em que o Expert irá aguardar até a negociação 
// contexto está livre (se ele está ocupado)
int MaxWaiting_sec = 30;
int start()
  {
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // verifica se o contexto de negociação está livre
    if(!IsTradeAllowed())
      {
        int StartWaitingTime = GetTickCount();
        Print("O Contexto de negociação está ocupado! Aguarda até que ele esteja livre...");
        // loop infinito
        while(true)
          {
            // se o Expert foi interrompido pelo usuário, parar a operação
            if(IsStopped()) 
              { 
                Print("O Expert foi parada pelo utilizador!"); 
                return(-1); 
               }
            // se ele esperou mais tempo do que o especificado na variável chamada 
            // MaxWaiting_sec, interrompe o funcionamento também
            if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) 
              {
                Print("O limite de espera (" + MaxWaiting_sec + " sec) foi excedido!");
                return(-2);
              }
            // se o contexto de negociação ficou livre,
            if(IsTradeAllowed())
              {
                Print("O contexto de negociação está livre!");
                // atualiza as informações do mercado
                RefreshRates();
                // recalcula os níveis de Stop Loss e Take Profit
                ...
                // sai do loop e inicia a negociação                
                break;
              }
            // Se nenhuma condição de quebra do loop for satisfeita, "esperar" por 0.1 
            // segundos e, em seguida, reiniciar a verificação
            Sleep(100);
          }
      }
    else
        Print("O contexto de negociação está livre! Tentando abrir uma posição...");

    // tentando abrir uma posição
    if(OrderSend(...) < 0) 
        Alert("Erro ao abrir uma posição # ", GetLastError());
 
    return(0);
  }
No exemplo acima, nós adicionamos:
  • atualização da informação de mercado (RefreshRates()) e consequentemente o recálculo do Stop Loss e Take Profit
  • tempo máximo de espera MaxWaiting_sec, após a superação dos quais o Expert irá parar a operação

O código acima já pode ser utilizado em seus Experts.

O toque final: Vamos colocar toda a verificação relativa em uma função separada. Isto irá simplificar a integração nos Experts e sua utilização.

/////////////////////////////////////////////////////////////////////////////////
// int _IsTradeAllowed( int MaxWaiting_sec = 30 )
//
// a função verifica o estado do contexto de negociação. Códigos de retorno:
//  1 - o contexto de negociação está livre, a negociação é permitida
//  0 - o contexto de negociação está ocupado, mas ficou livre. A negociação é permitida somente após 
//      as informações do mercado terem sido atualizadas.
// -1 - o contexto de negociação está ocupado, esperar a interrupção pelo usuário (Expert foi removido do 
//      gráfico, o terminal foi fechado, o período de gráfico e/ou símbolo foi alterado, etc.)
// -2 - o contexto de negociação está ocupado, o limite de espera for atingido (MaxWaiting_sec). 
//      Possivelmente, o Expert não tem permissão para realizar negociações (opção "Permitir negociação automatizada" 
//      nas configurações do Expert).
//
// MaxWaiting_sec - tempo (em segundos) durante o qual a função irá esperar 
// até que o contexto de negociação esteja livre (caso ela esteja ocupada). Por padrão ela é igual a 30.
/////////////////////////////////////////////////////////////////////////////////
int _IsTradeAllowed(int MaxWaiting_sec = 30)
  {
    // verifica se o contexto de negociação está livre
    if(!IsTradeAllowed())
      {
        int StartWaitingTime = GetTickCount();
        Print("O Contexto de negociação está ocupado! Aguarda até que ele esteja livre...");
        // loop infinito
        while(true)
          {
            // se o Expert foi interrompido pelo usuário, parar a operação
            if(IsStopped()) 
              { 
                Print("O Expert foi encerrado pelo usuário!"); 
                return(-1); 
              }
            // se o tempo de espera exceder o tempo especificado no 
            // MaxWaiting_sec, interrompe o funcionamento também
            if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
              {
                Print("O limite de espera foi excedido(" + MaxWaiting_sec + " segundos)!");
                return(-2);
              }
            // se o contexto de negociação ficou livre,
            if(IsTradeAllowed())
              {
                Print("O contexto de negociação ficou livre!");
                return(0);
              }
              // Se nenhuma condição de quebra do loop for satisfeita, "esperar" por 0.1 
              // segundos e, em seguida, reiniciar a verificação
              Sleep(100);
          }
      }
    else
      {
        Print("O contexto de negociação está livre!");
        return(1);
      }
  }

Um modelo do Expert que utiliza a função:

int start()
  {
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // verifica se o contexto de negociação está livre
    int TradeAllow = _IsTradeAllowed();
    if(TradeAllow < 0) 
      { 
        return(-1); 
      }
    if(TradeAllow == 0)
      {
        RefreshRates();
        // recalcula os níveis de Take Profit e Stop Loss
        ...
      }
    // Abre uma posição
    if(OrderSend(...) < 0) 
        Alert("Erro ao abrir uma posição # ", GetLastError());
    return(0);
  }

Vamos tirar algumas conclusões:

A função IsTradeAllowed() é fácil de usar e serve idealmente para a diferenciação de acessos ao contexto de negociação para dois ou três Experts que trabalham simultaneamente. Devido a algumas desvantagens dos mesmos, seu uso não garante  que o erro 146 não ocorra quando há muitos Experts trabalhando simultaneamente. Ela também pode causar que o Expert fique "pendurado", caso a opção "Permitir negociação automatizada" estiver desativada.

É por isso que vamos considerar uma solução alternativa para este problema - a variável global como um "semáforo".



3. Variáveis Globais do Terminal do Cliente

Em primeiro lugar, a definição:

As variáveis ​​globais do terminal do cliente são variáveis ​​acessíveis a todos os Experts, scripts e indicadores. Isto significa que uma variável global criada por um Expert pode ser utilizada em outros Experts (no nosso caso, a distribuição de acessos).

Existem várias funções fornecidas em MQL 4 para se trabalhar com variáveis ​​globais:

  • GlobalVariableCheck() - para verificar se existe uma variável global
  • GlobalVariableDel() - para remover uma variável global
  • GlobalVariableGet() - para obter o valor da variável global
  • GlobalVariableSet() - para criar ou modificar uma variável global
  • GlobalVariableSetOnCondition() - para alterar o valor da variável global especificada pelo usuário. Ela difere da GlobalVariableSet() em que o novo valor será definido apenas em um determinado valor anterior. É esta função, que é a função chave para se criar um semáforo.
  • GlobalVariablesDeleteAll() - para apagar todas as variáveis ​​globais (Eu não posso imaginar quem possa precisar disso:)

Por que a GlobalVariableSetOnCondition() é utilizada, mas não a combinação das funções GlobalVariableGet() e GlobalVariableSet()? Pelas mesmas razões: Pode decorrer algum tempo entre o uso de duas funções. E outro Expert pode interpor para o chaveamento do semáforo. Mas isso não é o que precisamos.



4. O Conceito Básico do Semáforo

O Expert que fará a negociação deve verificar o estado do semáforo. Se o semáforo mostrar a "luz vermelha" (variável global = 1), isso significa que um outro Expert está negociando, por isso é necessário esperar. Se ele mostrar "luz verde" (variável global = 0), a negociação pode ser iniciada imediatamente (mas não se esqueça de definir a "luz vermelha" para outros Experts).

Assim, temos que criar 2 funções: uma para definir a "luz vermelha", outra para definir a "luz verde". Em face disso, a tarefa é simples. Mas não vamos tirar conclusões precipitadas, mas tentar formular a sequência de ações a serem executadas por cada função (vamos nomeá-las de TradeIsBusy() e TradeIsNotBusy()) e, finalmente, executa-las.



5. Função TradeIsBusy()

Como já foi dito antes, a principal tarefa da função será de esperar até que a "luz verde" apareça e para ligar a "luz vermelha". Além disso, temos de verificar se existe a variável global, e criá-la, caso contrário. Esta verificação seria mais razoável (e mais eficiente) de realizar a partir da função init() do Expert. Mas, em seguida, há uma probabilidade de que o usuário apague ela e nenhum dos Experts em funcionamento seriam capazes de negociar. É por isso que nós vamos colocá-la no corpo da função criada.

Tudo isto deve ser acompanhado por informações visualizadas e processamento de erros que podem ocorrer quando se trabalha com a variável global. A "suspensão" não deve ser esquecida também: O tempo de operação da função deve ser limitada.

Isto é o que finalmente teremos:

/////////////////////////////////////////////////////////////////////////////////
// int TradeIsBusy( int MaxWaiting_sec = 30 )
//
// A função substitui o valor de TradeIsBusy de 0 para 1.
// Se TradeIsBusy = 1, no momento do lançamento, a função espera até que TradeIsBusy seja 0, 
// e, em seguida, substitui ela.
// Se não houver a variável global TradeIsBusy, a função cria ela.
// Códigos de retorno:
//  1 - concluído com sucesso. A variável global TradeIsBusy foi designada com o valor 1
// -1 - TradeIsBusy = 1 no momento do lançamento da função, a espera foi interrompida pelo utilizador
//      (O expert foi removido do gráfico, o terminal foi fechado, o período e/ou um símbolo gráfico 
//      foi alterado, etc.)
// -2 - TradeIsBusy = 1 no momento do lançamento da função, o limite de espera foi excedido
//      (MaxWaiting_sec)
/////////////////////////////////////////////////////////////////////////////////
int TradeIsBusy( int MaxWaiting_sec = 30 )
  {
    // No teste, não há nenhuma razão para dividir o contexto de negociação - apenas encerrar 
    // a função
    if(IsTesting()) 
        return(1);
    int _GetLastError = 0, StartWaitingTime = GetTickCount();
    //+------------------------------------------------------------------+
    //| Verifica se existe uma variável global e, se não, cria ela       |
    //+------------------------------------------------------------------+
    while(true)
      {
        // se o Expert foi interrompido pelo usuário, parar a operação
        if(IsStopped()) 
          { 
            Print("O Expert foi encerrado pelo usuário!"); 
            return(-1); 
          }
        // se o tempo de espera exceder ao especificado na variável 
        // MaxWaiting_sec, interrompe o funcionamento também
        if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
          {
            Print("Tempo de espera (" + MaxWaiting_sec + " sec) excedido!");
            return(-2);
          }
        // verifica se existe a variável global
        // se isso acontecer, deixa o loop e vai para o bloco de alteração 
        // valor TradeIsBusy
        if(GlobalVariableCheck( "TradeIsBusy" )) 
            break;
        else
        // se a GlobalVariableCheck retornar FALSE, isso significa que ela não existe ou  
        // ocorreu um erro durante a verificação
          {
            _GetLastError = GetLastError();
            // se ainda for um erro, exibe a informação, aguarda 0,1 segundo, e 
            // reinicia a verificação
            if(_GetLastError != 0)
             {
              Print("TradeIsBusy()-GlobalVariableCheck(\"TradeIsBusy\")-Erro #",
                    _GetLastError );
              Sleep(100);
              continue;
             }
          }
        // se não há nenhum erro, isso significa que não há apenas nenhuma variável global, ele tenta criar
        // ela
        // se a GlobalVariableSet > 0, isso significa que a variável global foi criado com sucesso. 
        // Sai da função
        if(GlobalVariableSet( "TradeIsBusy", 1.0 ) > 0 ) 
            return(1);
        else
        // se a GlobalVariableSet retornar um valor <= 0, isso significa que ocorreu um erro 
        // na criação da variável
         {
          _GetLastError = GetLastError();
          // exibe a informação, aguarda 0,1 segundo, e tenta novamente
          if(_GetLastError != 0)
            {
              Print("TradeIsBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0 )-Erro #",
                    _GetLastError );
              Sleep(100);
              continue;
            }
         }
      }
    //+----------------------------------------------------------------------------------+
    //| Se a execução da função tiver chegado a este ponto, isso significa que variável global  | 
    //| existe.                                                                          |
    //| Aguarda até que TradeIsBusy fique = 0 e altera o valor de TradeIsBusy para 1     |
    //+----------------------------------------------------------------------------------+
    while(true)
     {
     // se o Expert foi interrompido pelo usuário, parar a operação
     if(IsStopped()) 
       { 
         Print("O Expert foi encerrado pelo usuário!"); 
         return(-1); 
       }
     // se o tempo de espera exceder ao especificado na variável 
     // MaxWaiting_sec, interrompe o funcionamento também
     if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
       {
         Print("O tempo de espera (" + MaxWaiting_sec + " sec) excedeu!");
         return(-2);
       }
     // tenta mudar o valor de TradeIsBusy de 0 para 1
     // se teve sucesso, deixa a função retornar 1 ( "concluída com sucesso")
     if(GlobalVariableSetOnCondition( "TradeIsBusy", 1.0, 0.0 )) 
         return(1);
     else
     // se não, 2 razões para isso são possíveis: TradeIsBusy = 1 (então é necessário aguardar), ou 

     // ocorreu um erro (isto é o que vamos verificar)
      {
      _GetLastError = GetLastError();
      // Se ainda for um erro, exibe a informação e tenta novamente
      if(_GetLastError != 0)
      {
   Print("TradeIsBusy()-GlobalVariableSetOnCondition(\"TradeIsBusy\",1.0,0.0 )-Erro #",
         _GetLastError );
       continue;
      }
     }
     // se não há nenhum erro, isso significa que TradeIsBusy = 1 (um outro Expert está negociando), então exibe 
     // a informação e espera...
     Comment("Espere até que o outro Expert termine a negociação...");
     Sleep(1000);
     Comment("");
    }
  }

Bem, tudo parece estar claro por aqui:

  • verificar se existe a variável global e, se não, cria ela
  • na tentativa de alterar o valor da variável global de 0 para 1; ele irá acionar somente se seu valor for = 0.

A quantidade máxima de segundos durante os quais a função pode trabalhar é MaxWaiting_sec. A função não é nenhuma objeção a exclusão do Expert do gráfico.

As informações sobre todos os erros que ocorrem é exibido no log.



6. Função TradeIsNotBusy()

A função TradeIsNotBusy resolve o problema inverso: Ligad a "luz verde".

Ela não é limitada pelo tempo de operação e não pode ser encerrada pelo usuário. A motivação é bastante simples: se a "luz verde" está desligada, nenhum Expert será capaz de negociar.

Ele naturalmente não tem nenhum códigos de retorno: O resultado pode ser apenas uma conclusão bem sucedida.

Segue abaixo como ele se parece:

/////////////////////////////////////////////////////////////////////////////////
// void TradeIsNotBusy()
//
// A função define o valor da variável global TradeIsBusy = 0.
// Se TradeIsBusy não existir, a função criará ele.
/////////////////////////////////////////////////////////////////////////////////
void TradeIsNotBusy()
  {
    int _GetLastError;
    // no teste, não há sentido de dividir o contexto de negociação - apenas encerrar 
    // a função
    if(IsTesting()) 
      { 
        return(0); 
      }
    while(true)
      {
        // Se o Expert foi encerrado pelo usuário, encerra seu funcionamento
        if(IsStopped()) 
          { 
            Print("O Expert foi encerrado pelo usuário!"); 
            return(-1); 
          }
        // tenta definir o valor da variável global = 0 (ou criar uma variável 
        // global)
        // se a GlobalVariableSet retornar um valor > 0, isso significa que tudo 
        // foi bem sucedido. Deixa a função
        if(GlobalVariableSet( "TradeIsBusy", 0.0 ) > 0) 
            return(1);
        else
        // se a GlobalVariableSet retornar um valor <= 0, isso significa que ocorreu um erro 
        // Exibe a informação, espera, e tenta novamente
         {
         _GetLastError = GetLastError();
         if(_GetLastError != 0 )
           Print("TradeIsNotBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0)-Erro #", 
                 _GetLastError );
         }
        Sleep(100);
      }
  }


7. Integração nos Experts e seu Uso

Agora temos 3 funções para distribuir o acesso ao fluxo de negociação. Para simplificar a sua integração nos Experts, nós podemos criar o arquivo TradeContext.mq4 e habilitá-lo usando a diretiva #include (arquivo anexado).

Aqui está o modelo do Expert que usa as funções TradeIsBusy() e TradeIsNotBusy():

#include <TradeContext.mq4>
 
int start()
  {
    // verifica se a entrada no mercado deve ser feita agora
    ...
    // calcula os níveis de Stop Loss e Take Profit e o tamanho do lote
    ...
    // espera até que o contexto de negociação esteja livre e, em seguida, ocupa ele (se ocorrer um erro, 
    // deixa-o)
    if(TradeIsBusy() < 0) 
        return(-1); 
    // atualiza as informações do mercado
    RefreshRates();
    // recalcula os níveis de StopLoss e TakeProfit
    ...
    // Abre uma posição
    if(OrderSend(...) < 0) 
      { 
        Alert("Erro ao abrir uma posição # ", GetLastError()); 
      }

    // define o contexto de negociação como livre
    TradeIsNotBusy();

    return(0);
  }
 

Na utilização das funções TradeIsBusy() e TradeIsNotBusy(), apenas um problema pode ocorrer: Se o Expert for removido a partir do gráfico após o contexto de negociação ficar ocupado, a variável TradeIsBusy irá permanecer igual a 1. Outros Experts não serão capazes de negociar após isso.

O problema pode ser resolvido facilmente: O Expert não deve ser removido a partir do gráfico quando estiver negociando ;)

É também possível que a variável TradeIsBusy não foi zerada na no desligamento abrupto do terminal. Neste caso, a função TradeIsNotBusy() a partir da função init() do Expert poderá ser usada.

E, evidentemente, o valor da variável pode ser alterado manualmente em qualquer momento: botão F3 no terminal (isto é uma possibilidade não documentada para desativar todos os Experts de negociar).

komposter (komposterius@mail.ru), 2006.04.11