Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXIV): classe básica de negociação, correção automática de parâmetros errados

Artyom Trishkin | 18 fevereiro, 2020

Sumário

No último artigo, adicionamos um controle de parâmetros errados à classe de negociação. Tudo o que é feito ao controlar os valores de uma ordem de negociação é verificar que os valores transferidos para os métodos de negociação estejam corretos e, se forem detectados valores incorretos em qualquer um dos parâmetros, a partir do método de negociação será retornada uma mensagem de erro. Esse comportamento não permitirá que o EA carregue o servidor de negociação com pedidos deliberadamente errados, mas também não dará controle total sobre o comportamento do EA. Afinal, é possível verificar valores incorretos que podem ser corrigidos e corrigi-los, se possível, e enviar uma ordem de negociação já ajustada ao servidor.

Em geral, o EA deve ser capaz de agir de acordo com as circunstâncias e com a lógica predefinida pelo usuário para o tratamento de erros nas ordens de negociação. Assim, nas configurações é possível fornecer ao EA uma indicação de como agir quando encontrados erros nas ordens de negociação:

  1. Se na ordem de negociação for detectado um erro, simplesmente saímos do método de negociação, permitindo que o usuário crie por conta própria um manipulador de parâmetros incorretos para uma ordem incorreta.
  2. Após determinar se possível corrigir um valor incorreto numa ordem de negociação, corrigimos imediatamente os valores e enviamos a ordem de negociação correta.
  3. Ou, se precisarmos saber o que aconteceu em essência com o erro recebido, repetimos a ordem após uma pausa ou simplesmente fazemos novamente a ordem com os mesmos parâmetros.

Ao processar um erro nos parâmetros de uma ordem de negociação, são prováveis vários resultados:

Hoje, nas ordens de negociação criaremos um manipulador de erros que corresponderá à funcionalidade listada, ou seja, o erro e sua causa serão verificados e será retornado o método de tratamento do erro:

Ideia

A realização de operações de negociação deve ser restrita quando é proibida a negociação no servidor ou quando é proibida usando EAs. Em tal situação é completamente irracional tentar enviar solicitações de negociação desnecessárias, pois o EA só pode trabalhar como assistente analítico. Para fazer isso, teremos uma sinalizador global que será definido ao tentar negociar pela primeira vez e ao determinar se é possível fazer isso.

Interrupção de operação de negociação — se houver algum erro, sairemos do método de negociação, deixando ao usuário a oportunidade de tomar uma decisão programática para continuar com as tentativas de negociação com os mesmos parâmetros inalterados.

A correção de parâmetros incorretos funcionará da seguinte forma: ao verificar se uma solicitação de negociação é correta, será compilada uma lista de erros encontrados. O método de verificação de parâmetros examinará todos os erros desta lista e retornará o código do comportamento do método de negociação. Se for encontrado um erro que não permita continuar negociando, o método retornará o código de saída do método de negociação, pois, seja como for, o envio de ordem de negociação não levará a um resultado positivo. Se for encontrado um erro corrigível, serão chamados os métodos para corrigir os valores correspondentes da ordem de negociação e será retornado um resultado indicando uma verificação bem-sucedida. O método também retornará códigos do comportamento do método de negociação, como "Aguardar e repetir", "Atualizar dados e repetir" e "Criar solicitação pendente".

O que significa isso?

O comportamento "Aguardar e repetir" é necessário, por exemplo, quando o mercado esteja próximo de um dos stops da ordem ou do seu preço de ativação, enquanto isso tentamos alterar o valor dos stops ou excluir a ordem/fechar a posição. Se o preço para acionar os níveis de stop estiver dentro da zona de congelamento das operações de negociação, o servidor retornará que é proibido alterar os valores da ordem. Nessa situação, existe apenas uma saída, isto é, esperar um pouco na esperança de que o preço saia da zona de congelamento das ordens de negociação e enviar uma solicitação de negociação para alterar os parâmetros de ordem ou posição ou excluir a ordem após espera.
O comportamento "Atualizar dados e repetir" é necessário, por exemplo, se os preços estiverem desatualizados durante o processamento de uma ordem de negociação e recebemos uma requote.

Comportamento "Criar solicitação pendente". O que significa isso?
Se observarmos atentamente os dois métodos de processamento anteriores, fica claro que, ao aguardar, simplesmente esperaremos no método de negociação até o tempo de espera expirar e enviaremos a solicitação de negociação. Em princípio, esse comportamento é justificado se não for necessário realizar simultaneamente uma análise do estado do ambiente de negociação. Portanto, para liberar o programa da necessidade de "permanecer" dentro do método de negociação, simplesmente criamos uma solicitação de negociação pendente na qual serão registrados os parâmetros e o tempo de espera com o número de tentativas.

Assim, a criação de uma solicitação pendente elimina completamente a necessidade do comportamento “Atualizar dados e repetir” e “Aguardar e repetir”, pois esses dois comportamentos são essencialmente solicitações de negociação pendentes, um com espera mínima e outro com um determinado tempo de espera. Bem, a capacidade de criar solicitações pendentes no programa dará ao usuário outro método de realização de operações de negociação. Neste artigo, não criaremos solicitações pendentes, já que deixaremos para os artigos próximos.

Antes de começar, quero lembrá-lo de que no último artigo começamos a fazer alterações na definição de evento de negociação:

Várias vezes, os usuários relataram um erro ao receber o último evento de negociação. Acontece que, no EA de teste dos artigos que descrevem o recebimento de eventos de negociação, o recebimento da ocorrência de um evento de negociação foi criado usando como base a comparação do evento anterior com o atual. Isso era suficiente para testar como funcionava a biblioteca ao rastrear eventos de negociação, pois, na época em que escrevemos artigos sobre eventos de negociação, ainda não havia uma intenção de usar uma versão incompleta da biblioteca. Mas, resulta que agora a obtenção de ocorrências de eventos está sendo procurada, e precisamos saber exatamente qual é o último evento.

Aquela maneira de obter o evento de negociação nem sempre informava sobre o evento, bastava duas vezes seguidas fazer uma ordem pendente que já a segunda não era rastreada no programa (todos os eventos eram rastreados na biblioteca), uma vez que o penúltimo e o último evento eram iguais, mas, de fato, as ordens colocadas eram diferentes.

Por isso, vamos corrigir esse comportamento. Hoje, simplesmente criaremos um sinalizador que informará o programa sobre a ocorrência de um evento e já no programa poderemos ver de que tipo de evento se trata. No próximo artigo, concluiremos o recebimento de eventos de negociação no programa, para isso, criaremos uma lista completa contendo todos os eventos ocorrendo simultaneamente e a forneceremos ao programa. Assim, poderemos no programa não apenas descobrir a presença de um evento de negociação, mas também de ver todos os eventos ocorrendo simultaneamente, como foi realizado para eventos de conta e para eventos de coleção de símbolos.

Hoje, antes de continuar a trabalhar na classe de negociação, concluiremos o trabalho de alteração dessa funcionalidade.

Corrigindo a definição de eventos de negociação

Como quase todos nossos objetos são criados com base no objeto base de objetos de biblioteca e nele já há uma lista contendo eventos e um método que retorna o sinalizador do evento deste objeto, agora adicionamos todos os eventos de negociação à lista de eventos do objeto base e podemos obter o sinalizador de evento dessa classe através do método IsEvent(). Os sinalizadores de eventos são definidos pela classe automaticamente. Mas precisaremos poder definir por conta própria o sinalizador do evento de negociação, ocorrido a partir de outras classes, e seus manipuladores de eventos.
Para isso adicionamos um método para definir o sinalizador de evento do objeto base para a classe CEventBaseObj no arquivo BaseObj.mqh:

//--- Set/return the occurred event flag to the object data
   void              SetEvent(const bool flag)                       { this.m_is_event=flag;                   }
   bool              IsEvent(void)                             const { return this.m_is_event;                 }

Agora na classe de coleção de eventos de negociação CEventsCollection quando qualquer novo evento aparecer, precisamos criar uma descrição desse evento, colocá-la na lista de novos eventos da classe base de todos os objetos e definir o sinalizador para o novo evento.

Assim, todos os eventos recém-ocorridos - suas descrições - serão colocados na lista de eventos de negociação da classe base da coleção de símbolos. E já nesta lista, podemos ler facilmente essa lista no programa e processar cada evento contido nessa lista.

Faremos todas as melhorias necessárias no arquivo de classe do evento de negociação EventsCollection.mqh.

Na seção pública da classe, inserimos a definição de dois novos métodos
método para obter o objeto de evento base por seu índice na lista
e
método que retorna o número de novos eventos:

//--- Return (1) the last trading event on an account, (2) base event object by index and (3) number of new events
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)                const { return this.m_trade_event;                 }
   CEventBaseObj    *GetTradeEventByIndex(const int index)        { return this.GetEvent(index,false);         }
   int               GetTradeEventsTotal(void)              const { return this.m_list_events.Total();         }
//--- Reset the last trading event

O método que retorna o objeto de evento base por índice chama o método do objeto base GetEvent(), ao qual transferimos o índice do evento necessário e o sinalizador redefinido (false) que permite verificar se o índice fica fora da lista de eventos — para não ajustar o evento retornado se o índice ficar fora da lista de eventos. Em outras palavras, se transferirmos um índice inexistente, o método retornará NULL. Se transferíssemos no valor do sinalizador true, o método retornaria o último evento, que não precisamos aqui.

Método que retorna o número de novos eventos apenas retorna o tamanho da lista de eventos do objeto base.

Como, na classe de coleção de eventos de negociação no temporizador, as alterações das listas de ordens e posições históricas e de mercado são constantemente visualizadas, no método Refresh() é necessário limpar a lista de eventos básicos de negociação e definir sinalizador de lista classificada:

//+------------------------------------------------------------------+
//| Update the event list                                            |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                CArrayObj* list_changes,
                                CArrayObj* list_control,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals,
                                const double changed_volume)
  {
//--- Exit if the lists are empty
   if(list_history==NULL || list_market==NULL)
      return;
//---
   this.m_is_event=false;
   this.m_list_events.Clear();
   this.m_list_events.Sort();
//--- If the event is in the market environment
   if(is_market_event)
     {

Em todos os métodos de criação de um novo evento CreateNewEvent(), após a linha de envio do evento, precisamos acrescentar a adição deste evento à lista de eventos básicos:

         event.SendEvent();
         CBaseObj::EventAdd(this.m_trade_event,order.Ticket(),order.Price(),order.Symbol());

Tudo isso já está incluído na lista de métodos, portanto, vamos economizar espaço no artigo, pois tudo pode ser baixado dos arquivos anexados ao artigo e lido.

Agora, na seção pública da classe do objeto principal da biblioteca CEngine inserimos o método que retorna o objeto de evento base por seu índice na lista e o método que retorna o número de novos eventos:

//--- Return (1) the list of order, deal and position events, (2) base trading event object by index and the (3) number of new trading events
   CArrayObj           *GetListAllOrdersEvents(void)                    { return this.m_events.GetList();                     }
   CEventBaseObj       *GetTradeEventByIndex(const int index)           { return this.m_events.GetTradeEventByIndex(index);   }
   int                  GetTradeEventsTotal(void)                 const { return this.m_events.GetTradeEventsTotal();         }
//--- Reset the last trading event

Esses métodos simplesmente chamam os métodos da classe da coleção de eventos de negociação com o mesmo nome, discutidos acima.

Essas são todas as alterações necessárias para que possamos rastrear todos os eventos ocorridos simultaneamente e enviados ao programa num único pacote. Isso será visto mais tarde, ao testar a funcionalidade descrita no artigo.

Agora podemos começar a refinar ainda mais a classe de negociação.

Manipulador de erros nos parâmetros da solicitação de negociação

Primeiro, adicionamos os índices de mensagens necessárias ao arquivo Datas.mqh:

   MSG_LIB_TEXT_REQUEST_REJECTED_DUE,                 // Request was rejected before sending to the server due to:
   MSG_LIB_TEXT_INVALID_REQUEST,                      // Invalid request:
   MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR,                 // Insufficient funds for performing a trade

   MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ,        // Unsupported price parameter type in a request
   MSG_LIB_TEXT_TRADING_DISABLE,                      // Trading disabled for the EA until the reason is eliminated
   MSG_LIB_TEXT_TRADING_OPERATION_ABORTED,            // Trading operation is interrupted
   MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST,              // Correcting trading request parameters
   MSG_LIB_TEXT_CREATE_PENDING_REQUEST,               // Creating a pending request
   MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT,          // Unable to correct a lot
   
  };

e os textos correspondentes a esses índices:

   {"Запрос отклонён до отправки на сервер по причине:","The request was rejected before being sent to the server due to:"},
   {"Ошибочный запрос:","Invalid request:"},
   {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"},

   {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"},
   {"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"},
   {"Торговая операция прервана","Trading operation aborted"},
   {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."},
   {"Создание отложенного запроса","Create a pending request"},
   {"Нет возможности скорректировать лот","There is no possibility to correct the lot"},
   
  };

No arquivo Defines.mqh inserimos as enumerações necessárias para determinar e retornar métodos para processar erros em solicitações de negociação e erros retornados pelo servidor de negociação.

Para definir o comportamento do erro diretamente para o EA
adicionamos uma enumeração que descreva o possível comportamento do EA quando um erro for detectado numa solicitação de negociação ou quando um erro for retornado por um servidor de negociação:

//+------------------------------------------------------------------+
//| EA behavior when handling errors                                 |
//+------------------------------------------------------------------+
enum ENUM_ERROR_HANDLING_BEHAVIOR
  {
   ERROR_HANDLING_BEHAVIOR_BREAK,                           // Abort trading attempt
   ERROR_HANDLING_BEHAVIOR_CORRECT,                         // Correct invalid parameters
   ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST,                 // Create a pending request
  };
//+------------------------------------------------------------------+

Nas configurações do EA, será possível definir seu comportamento ao processar erros, especificando um dos parâmetros desta enumeração.

Ao verificar os valores dos parâmetros de uma solicitação de negociação, são possíveis vários métodos de tratamento de erros. Para que possamos saber quais erros são detectados ao verificar vários parâmetros da ordem de negociação e quais condições de negociação influenciam o aparecimento tais erros,
adicionamos uma enumeração com sinalizadores de possíveis métodos para processamento de situações de erro:

//+------------------------------------------------------------------+
//| Flags indicating the trading request error handling methods      |
//+------------------------------------------------------------------+
enum ENUM_TRADE_REQUEST_ERR_FLAGS
  {
   TRADE_REQUEST_ERR_FLAG_NO_ERROR                 =  0,    // No error
   TRADE_REQUEST_ERR_FLAG_FATAL_ERROR              =  1,    // Disable trading for an EA (critical error) - exit
   TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR             =  2,    // Library internal error - exit
   TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST            =  4,    // Error in the list - handle (ENUM_ERROR_CODE_PROCESSING_METHOD)
  };
//+------------------------------------------------------------------+

Ao verificar os parâmetros de uma ordem de negociação e a capacidade de executá-la, adicionaremos sinalizadores de comportamento ao processar erros:

O método de verificação de erros retornará maneiras de lidar com os erros encontrados, para que seja possível processá-los corretamente posteriormente.
Para isso, adicionamos uma enumeração de possíveis métodos para processar os erros das ordens de negociação e de retornos do servidor de negociação:

//+------------------------------------------------------------------+
//| The methods of handling errors and server return codes           |
//+------------------------------------------------------------------+
enum ENUM_ERROR_CODE_PROCESSING_METHOD
  {
   ERROR_CODE_PROCESSING_METHOD_OK,                         // No errors
   ERROR_CODE_PROCESSING_METHOD_DISABLE,                    // Disable trading for the EA
   ERROR_CODE_PROCESSING_METHOD_EXIT,                       // Exit the trading method
   ERROR_CODE_PROCESSING_METHOD_REFRESH,                    // Update data and repeat
   ERROR_CODE_PROCESSING_METHOD_WAIT,                       // Wait and repeat
   ERROR_CODE_PROCESSING_METHOD_PENDING,                    // Create a pending request
  };
//+------------------------------------------------------------------+

Nas descrições das constantes da enumeração, também são claros os métodos para lidar com situações de erro.

Também aprimoraremos o objeto básico de negociação.

Dependendo do método de construção de barras no gráfico, a negociação é realizada com base nos preços de Ask e Bid ou nos preços de Ask e Last. Atualmente, na classe básica de negociação é possível negociar apenas com base nos preços de Ask e Bid. Adicionamos uma rotina para verificar em que preços está baseado o gráfico e que preços usaremos ao negociar. Além disso, para verificar os códigos retornados pelo servidor de negociação, neste caso, após enviar a ordem de negociação para o servidor, em MQL5, existe a estrutura de resultado da solicitação de negociação MqlTradeResult, os campos retcode e comment conterão um código de erro e uma descrição do código de erro, respectivamente. Para MQL4, não é fornecida essa possibilidade, em cujo caso é necessário ler o código de erro com a função GetLastError(), que retorna o último código de erro. Como nossa biblioteca é multiplataforma, para MQL4, precisamos preencher os campos da estrutura do resultado da solicitação de negociação por conta própria após enviá-la ao servidor.

Ao verificar a distância de posicionamento de ordens de stop em relação ao preço, também observamos a distância de colocação do tamanho mínimo permitido dos níveis de stop (StopLevel), que definido para o símbolo. Se o valor StopLevel retornado pela função SymbolInfoInteger() com o ID da propriedade SYMBOL_TRADE_STOPS_LEVEL for igual a zero, isso não significa a ausência de um recuo mínimo expresso em pontos em relação ao preço para ordens de stop, mas, sim, que esse nível é flutuante. Portanto, para ajustar o tamanho do recuo das ordens de stop a partir do preço, é necessário selecionar o nível "em vigor" ou usar para o tamanho do recuo o tamanho do spread atual multiplicado por um determinado valor. Normalmente, para um ajuste suave do nível de stop, é usado o valor de spread duplo. multiplicadorPara definir o valor desse fator para cada objeto de cada símbolo, adicionamos ao objeto de negociação o multiplicador em si e seus os métodos de retorno e configuração.

Faremos as alterações necessárias na classe do objeto básico de negociação CTradeObj no arquivo TradeObj.mqh.

Na seção privada da classe, declaramos duas variáveis-membro da classe, para armazenar o tipo de preço para a construção de barras e
multiplicador de spread para ajustar os níveis de ordem de stop:

   SActions                   m_datas;
   MqlTick                    m_tick;                                            // Tick structure for receiving prices
   MqlTradeRequest            m_request;                                         // Trade request structure
   MqlTradeResult             m_result;                                          // trade request execution result
   ENUM_SYMBOL_CHART_MODE     m_chart_mode;                                      // Price type for constructing bars
   ENUM_ACCOUNT_MARGIN_MODE   m_margin_mode;                                     // Margin calculation mode
   ENUM_ORDER_TYPE_FILLING    m_type_filling;                                    // Filling policy
   ENUM_ORDER_TYPE_TIME       m_type_expiration;                                 // Order expiration type
   int                        m_symbol_expiration_flags;                         // Flags of order expiration modes for a trading object symbol
   ulong                      m_magic;                                           // Magic number
   string                     m_symbol;                                          // Symbol
   string                     m_comment;                                         // Comment
   ulong                      m_deviation;                                       // Slippage in points
   double                     m_volume;                                          // Volume
   datetime                   m_expiration;                                      // Order expiration time (for ORDER_TIME_SPECIFIED type order)
   bool                       m_async_mode;                                      // Flag of asynchronous sending of a trade request
   ENUM_LOG_LEVEL             m_log_level;                                       // Logging level
   int                        m_stop_limit;                                      // Distance of placing a StopLimit order in points
   bool                       m_use_sound;                                       // The flag of using sounds of the object trading events
   uint                       m_multiplier;                                      // The spread multiplier to adjust levels relative to StopLevel
   

Na seção pública da classe, escreveremos o método que define o valor do multiplicador de spread e o método que retorna o valor do multiplicador:

public:
//--- Constructor
                              CTradeObj();

//--- Set/return the spread multiplier
   void                       SetSpreadMultiplier(const uint value)        { this.m_multiplier=(value==0 ? 1 : value);  }
   uint                       SpreadMultiplier(void)                 const { return this.m_multiplier;                  }
//--- Set default values

Ao definir o valor do multiplicador de spread, verificamos o valor passado ao método para que a igualdade seja zero e, se for igual, atribuímos o valor de 1.

Também na seção pública da classe, inseriremos dois métodos: um que define o código de erro da solicitação de negociaçãoe
outro que define a descrição do código de erro da solicitação de negociação:

//--- Set the error code in the last request result
   void                       SetResultRetcode(const uint retcode)                     { this.m_result.retcode=retcode;       }
   void                       SetResultComment(const string comment)                   { this.m_result.comment=comment;       }
//--- Data on the last request result:

No construtor da classe atribuímos ao multiplicador de spread um valor padrãoigual a 1:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTradeObj::CTradeObj(void) : m_magic(0),
                             m_deviation(5),
                             m_stop_limit(0),
                             m_expiration(0),
                             m_async_mode(false),
                             m_type_filling(ORDER_FILLING_FOK),
                             m_type_expiration(ORDER_TIME_GTC),
                             m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"),
                             m_log_level(LOG_LEVEL_ERROR_MSG)
  {
   //--- Margin calculation mode
   this.m_margin_mode=
     (
      #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE)
      #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif 
     );
   //--- Spread multiplier
   this.m_multiplier=1;
   //--- Set default sounds and flags of using sounds
   this.m_use_sound=false;
   this.InitSounds();
  }
//+------------------------------------------------------------------+

No método Init() que define os valores padrão dos parâmetros do objeto de negociação,
definimos o valor através da variável m_chart_mode, que armazenam os preços de construção de barras:

//+------------------------------------------------------------------+
//| Set default values                                               |
//+------------------------------------------------------------------+
void CTradeObj::Init(const string symbol,
                     const ulong magic,
                     const double volume,
                     const ulong deviation,
                     const int stoplimit,
                     const datetime expiration,
                     const bool async_mode,
                     const ENUM_ORDER_TYPE_FILLING type_filling,
                     const ENUM_ORDER_TYPE_TIME type_expiration,
                     ENUM_LOG_LEVEL log_level)
  {
   this.SetSymbol(symbol);
   this.SetMagic(magic);
   this.SetDeviation(deviation);
   this.SetVolume(volume);
   this.SetExpiration(expiration);
   this.SetTypeFilling(type_filling);
   this.SetTypeExpiration(type_expiration);
   this.SetAsyncMode(async_mode);
   this.SetLogLevel(log_level);
   this.m_symbol_expiration_flags=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE);
   this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN);
   this.m_chart_mode=#ifdef __MQL5__ (ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) #else SYMBOL_CHART_MODE_BID #endif ;
  }
//+------------------------------------------------------------------+


Neste caso, para MQL5 obtemos os dados com a ajuda da função SymbolInfoInteger() com identificador SYMBOL_CHART_MODE, enquanto
para MQL4, registramos que as barras são construídas com base no preço Bid.

Agora precisamos adicionar a cada método de negociação o preenchimento da estrutura de retorno do servidor de negociação.
Vejamos um exemplo usando um método de abertura de posição:

//+------------------------------------------------------------------+
//| Open a position                                                  |
//+------------------------------------------------------------------+
bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type,
                             const double volume,
                             const double sl=0,
                             const double tp=0,
                             const ulong magic=ULONG_MAX,
                             const string comment=NULL,
                             const ulong deviation=ULONG_MAX)
  {
   ::ResetLastError();
   //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false'
   if(!::SymbolInfoTick(this.m_symbol,this.m_tick))
     {
      this.m_result.retcode=::GetLastError();
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode));
      return false;
     }
   //--- Clear the structures
   ::ZeroMemory(this.m_request);
   ::ZeroMemory(this.m_result);
   //--- Fill in the request structure
   this.m_request.action   =  TRADE_ACTION_DEAL;
   this.m_request.symbol   =  this.m_symbol;
   this.m_request.magic    =  (magic==ULONG_MAX ? this.m_magic : magic);
   this.m_request.type     =  OrderTypeByPositionType(type);
   this.m_request.price    =  (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last));
   this.m_request.volume   =  volume;
   this.m_request.sl       =  sl;
   this.m_request.tp       =  tp;
   this.m_request.deviation=  (deviation==ULONG_MAX ? this.m_deviation : deviation);
   this.m_request.comment  =  (comment==NULL ? this.m_comment : comment);
   //--- Return the result of sending a request to the server
#ifdef __MQL5__
   return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result));
#else 
   ::ResetLastError();
   int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE);
   if(ticket!=WRONG_VALUE)
     {
      ::SymbolInfoTick(this.m_symbol,this.m_tick);
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.deal=ticket;
      this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price);
      this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume);
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return true;
     }
   else
     {
      ::SymbolInfoTick(this.m_symbol,this.m_tick);
      this.m_result.retcode=::GetLastError();
      this.m_result.ask=this.m_tick.ask;
      this.m_result.bid=this.m_tick.bid;
      this.m_result.comment=CMessage::Text(this.m_result.retcode);
      return false;
     }
#endif 
  }
//+------------------------------------------------------------------+

Neste caso, para MQL5, nós, como antes, retornamos o resultado da função OrderSend(), enquanto, para MQL4, verificamos o número do ticket que retorna a função MQL4 de envio de ordem. Se a solicitação de negociação for concluída com êxito, a função retornará o ticket da ordem aberta. Se houve erro — WRONG_VALUE. Portanto, verificamos que a função não nos retorna -1, e, se for assim, atualizamos a cotação do símbolo, preenchemos os campos da estrutura do resultado da solicitação de negociação com os dados apropriados e retornamos true (função concluída com sucesso).
Se a função de envio de ordem retorna -1, na estrutura do resultado da solicitação de negociação, escrevemos o código do último erro, os preços atuais, a descriptografia do código do último erro. Todos os outros campos da estrutura são deixados zerados. Como resultado, retornamos false — erro ao enviar ordem de negociação.

Como resultado desta modificação, podemos ver o resultado da solicitação usando esses métodos de classe para qualquer resultado do envio de uma ordem de negociação:

//--- Data on the last request result:
//--- Return (1) operation result code, (2) performed deal ticket, (3) placed order ticket,
//--- (4) deal volume confirmed by a broker, (5) deal price confirmed by a broker,
//--- (6) current market Bid (requote) price, (7) current market Ask (requote) price
//--- (8) broker comment to operation (by default, it is filled by the trade server return code description),
//--- (9) request ID set by the terminal when sending, (10) external trading system return code
   uint                       GetResultRetcode(void)                             const { return this.m_result.retcode;        }
   ulong                      GetResultDeal(void)                                const { return this.m_result.deal;           }
   ulong                      GetResultOrder(void)                               const { return this.m_result.order;          }
   double                     GetResultVolume(void)                              const { return this.m_result.volume;         }
   double                     GetResultPrice(void)                               const { return this.m_result.price;          }
   double                     GetResultBid(void)                                 const { return this.m_result.bid;            }
   double                     GetResultAsk(void)                                 const { return this.m_result.ask;            }
   string                     GetResultComment(void)                             const { return this.m_result.comment;        }
   uint                       GetResultRequestID(void)                           const { return this.m_result.request_id;     }
   uint                       GetResultRetcodeEXT(void)                          const { return this.m_result.retcode_external;}

Os outros métodos de negociação são modificados de maneira semelhante, aqui não faz sentido considerá-los, pois tudo está nos arquivos anexados no final do artigo.

Na classe do objeto-conta CAccount no arquivo Account.mqh modificamos um pouco o método que retorna a margem necessária para abrir uma posição ou fazer um pedido pendente:

//+------------------------------------------------------------------+
//| Return the margin required for opening a position                |
//| or placing a pending order                                       |
//+------------------------------------------------------------------+
double CAccount::MarginForAction(const ENUM_ORDER_TYPE action,const string symbol,const double volume,const double price) const
  {
   double margin=EMPTY_VALUE;
   #ifdef __MQL5__
      return(!::OrderCalcMargin(ENUM_ORDER_TYPE(action%2),symbol,volume,price,margin) ? EMPTY_VALUE : margin);
   #else 
      return this.MarginFree()-::AccountFreeMarginCheck(symbol,ENUM_ORDER_TYPE(action%2),volume);
   #endif
  }
//+------------------------------------------------------------------+

Tudo o que precisamos adicionar aqui é uma conversão do tipo de ordem transferido ao método em dois valores - ORDER_TYPE_BUY ou ORDER_TYPE_SELL -, uma vez que as funções MQL5 e MQL4 com as quais trabalha o método exigem apenas esse tipo de ordem.
Deixe-me lembrá-lo de que o restante da divisão por 2 valores da constante do tipo de ordem sempre retorna um dos dois valores:

O que precisamos converter no tipo correto de ordem.

Na classe CTrading no arquivo Trading.mqh criamos anteriormente uma estrutura personalizada para preencher os parâmetros de preço da ordem de negociação:

   struct SDataPrices
     {
      double            open;                // Open price
      double            limit;               // Limit order price
      double            sl;                  // StopLoss price
      double            tp;                  // TakeProfit price
     };
   SDataPrices          m_req_price;         // Trade request prices

Mas em MQL existe uma estrutura especial MqlTradeRequest, e para não confundir com uma estrutura desnecessária,
substituímos na seção privada da classe a estrutura personalizada pela padrão
, também
declaramos uma variável-membro da classe para armazenar sinalizadores de causas de erros na solicitação de negociação e
uma variável para armazenar o comportamento do EA em caso de erros no envio de ordens de negociação
:

//+------------------------------------------------------------------+
//| Trading class                                                    |
//+------------------------------------------------------------------+
class CTrading
  {
private:
   CAccount            *m_account;                       // Pointer to the current account object
   CSymbolsCollection  *m_symbols;                       // Pointer to the symbol collection list
   CMarketCollection   *m_market;                        // Pointer to the list of the collection of market orders and positions
   CHistoryCollection  *m_history;                       // Pointer to the list of the collection of historical orders and deals
   CArrayInt            m_list_errors;                   // Error list
   bool                 m_is_trade_disable;              // Flag disabling trading
   bool                 m_use_sound;                     // The flag of using sounds of the object trading events
   ENUM_LOG_LEVEL       m_log_level;                     // Logging level
   MqlTradeRequest      m_request;                       // Trade request prices
   ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags;    // Flags of error source in a trading method
   ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error
//--- Add the error code to the list
   bool                 AddErrorCodeToList(const int error_code);

Também na seção privada da classe, escreveremos o método que retorna a presença de sinalizador na variável que armazena os sinalizadores de causas de erros,
o método que retorna a presença de código de erro na lista de erros e
os métodos de definição e retorno de ações durante o tratamento de erros:

//--- Return the flag presence in the trading event error reason
   bool                 IsPresentErrorFlag(const int code)     const { return (this.m_error_reason_flags & code)==code;                               }
//--- Return the error code in the list
   bool                 IsPresentErorCode(const int code)            { this.m_list_errors.Sort(); return this.m_list_errors.Search(code)>WRONG_VALUE; }
//--- Set/return the error handling action
   void                 SetErrorHandlingBehavior(const ENUM_ERROR_HANDLING_BEHAVIOR behavior) { this.m_err_handling_behavior=behavior;                }
   ENUM_ERROR_HANDLING_BEHAVIOR  ErrorHandlingBehavior(void)   const { return this.m_err_handling_behavior;                                           }

//--- Check trading limitations

Do método que verifica se há suficientes fundos removemos o código de exibição de mensagem sobre fundos insuficientes no log:

   if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif )
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
        {
         //--- создаём текст сообщения
         string message=
           (
            symbol_obj.Name()+" "+::DoubleToString(volume,symbol_obj.DigitsLot())+" "+
            (
             order_type>ORDER_TYPE_SELL ? OrderTypeDescription(order_type,false,false) : 
             PositionTypeDescription(PositionTypeByOrderType(order_type))
            )+" ("+::DoubleToString(money_free,(int)this.m_account.CurrencyDigits())+")"
           );
         //--- выводим сообщение в журнал
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR),": ",message);
         this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR);
        }
      return false;
     }

Agora, a partir de outro método será exibida uma mensagem sobre falta de fundos.
Neste método, apenas adicionamos um sinalizador indicando que o erro deve ser pesquisado na lista de erros, e
adicionamos o código de erro à lista de erros:

//+------------------------------------------------------------------+
//| Check if the funds are sufficient                                |
//+------------------------------------------------------------------+
bool CTrading::CheckMoneyFree(const double volume,
                              const double price,
                              const ENUM_ORDER_TYPE order_type,
                              const CSymbol *symbol_obj,
                              const string source_method,
                              const bool mess=true)
  {
   ::ResetLastError();
   //--- Get the type of a market order by a trading operation type
   ENUM_ORDER_TYPE action=this.DirectionByActionType((ENUM_ACTION_TYPE)order_type);
   //--- Get the value of free funds to be left after conducting a trading operation
   double money_free=
     (
      //--- For MQL5, calculate the difference between free funds and the funds required to conduct a trading operation
      #ifdef __MQL5__  this.m_account.MarginFree()-this.m_account.MarginForAction(action,symbol_obj.Name(),volume,price)
      //--- For MQL4, use the operation result of the standard function returning the amount of funds left
      #else/*__MQL4__*/::AccountFreeMarginCheck(symbol_obj.Name(),action,volume) #endif 
     );
   //--- If no free funds are left, inform of that and return 'false'
   if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif )
     {
      this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR);
      return false;
     }
   //--- Verification successful
   return true;
  }
//+------------------------------------------------------------------+

Declaramos os métodos para ajustar os preços de ordens de stop e o preço para definir uma ordem pendente, o método para ajustar o volume numa ordem de negociação,
o método que indica como lidar com o erro e ométodo de correção de erros na ordem de negociação:

//--- Return the correct (1) StopLoss, (2) TakeProfit and (3) order placement price
   double               CorrectStopLoss(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double stop_loss,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
   double               CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double take_profit,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
   double               CorrectPricePending(const ENUM_ORDER_TYPE order_type,
                                       const double price_set,
                                       const double price,
                                       const CSymbol *symbol_obj,
                                       const uint spread_multiplier=1);
//--- Return the volume, at which it is possible to open a position
   double               CorrectVolume(const double price,
                                       const ENUM_ORDER_TYPE order_type,
                                       const CSymbol *symbol_obj,
                                       const string source_method);

//--- Return the error handling method
   ENUM_ERROR_CODE_PROCESSING_METHOD   ResultProccessingMethod(void);
//--- Correct errors
   ENUM_ERROR_CODE_PROCESSING_METHOD   RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj);

public:

Complementamos a especificação di método para verificar restrições e erros, e alteramos o tipo de retorno de bool para ENUM_ERROR_CODE_PROCESSING_METHOD:

//--- Check limitations and errors
   ENUM_ERROR_CODE_PROCESSING_METHOD CheckErrors(const double volume,
                                                 const double price,
                                                 const ENUM_ACTION_TYPE action,
                                                 const ENUM_ORDER_TYPE order_type,
                                                 CSymbol *symbol_obj,
                                                 const CTradeObj *trade_obj,
                                                 const string source_method,
                                                 const double limit=0,
                                                 double sl=0,
                                                 double tp=0);

O método agora se torna mais completo, ele verifica imediatamente possíveis métodos para corrigir erros na ordem de negociação, e agora o método retorna um método para processar erros encontrados. Anteriormente, ele simplesmente retornava o sinalizador de verificação bem-sucedida.

Declaramos um método para definir o multiplicador de spread:

//--- Set the following for symbol trading objects:
//--- (1) correct filling policy, (2) filling policy,
//--- (3) correct order expiration type, (4) order expiration type,
//--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date,
//--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) spread multiplier
   void                 SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL);
   void                 SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL);
   void                 SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL);
   void                 SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL);
   void                 SetMagic(const ulong magic,const string symbol=NULL);
   void                 SetComment(const string comment,const string symbol=NULL);
   void                 SetDeviation(const ulong deviation,const string symbol=NULL);
   void                 SetVolume(const double volume=0,const string symbol=NULL);
   void                 SetExpiration(const datetime expiration=0,const string symbol=NULL);
   void                 SetAsyncMode(const bool mode=false,const string symbol=NULL);
   void                 SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL);
   void                 SetSpreadMultiplier(const uint value=1,const string symbol=NULL);

Adicionamos os métodos de definição e de retorno de sinalizador de permissão de negociação para o EA:

//--- Set/return the flag enabling sounds
   void                 SetUseSounds(const bool flag);
   bool                 IsUseSounds(void)                            const { return this.m_use_sound;       }

//--- Set/return the flag enabling trading
   void                 SetTradingDisableFlag(const bool flag)             { this.m_is_trade_disable=flag;  }
   bool                 IsTradingDisable(void)                       const { return this.m_is_trade_disable;}

//--- Open (1) Buy, (2) Sell position

Existem erros que após serem identificados não é possível realizar operações de negociação adicionais, restringindo completamente a negociação para a conta. Este sinalizador é definido quando esses erros são detectados e não permite o envio de novas ordens de negociação desnecessárias.

No construtor da classe redefinimos o sinalizador de proibição de negociação, e
definimos o comportamento padrão do EA em caso de erros de solicitação de negociação como "ajustar parâmetros":

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTrading::CTrading()
  {
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
   this.m_log_level=LOG_LEVEL_ALL_MSG;
   this.m_is_trade_disable=false;
   this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT;
   ::ZeroMemory(this.m_request);
  }
//+------------------------------------------------------------------+

O comportamento do EA em caso de erros nos métodos de negociação pode ser definido nas configurações do EA. Mas até que todos os manipuladores estejam prontos, usaremos o método de correção automática de erros.

A implementação do método para processar códigos de retorno do servidor de negociação nesta execução retorna apenas um sinalizador de sucesso:

//+------------------------------------------------------------------+
//| Return the error handling method                                 |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(void)
  {
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

Porque é assim? Só que neste artigo não consideramos esse método, uma vez que faremos o processamento dos códigos de retorno do servidor de negociação no próximo artigo. Mas o método já foi descrito e implementado numa execução mínima.

Implementação do método de correção de erros numa ordem de negociação:

//+------------------------------------------------------------------+
//| Correct errors                                                   |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request,
                                                                    const ENUM_ORDER_TYPE order_type,
                                                                    const uint spread_multiplier,
                                                                    CSymbol *symbol_obj)
  {
//--- The empty error list means no errors are detected, return success
   int total=this.m_list_errors.Total();
   if(total==0)
      return ERROR_CODE_PROCESSING_METHOD_OK;
//--- In the current implementation, all these codes are temporarily handled by interrupting a trading request
   if(
      this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED)       || // Trading is disabled for the current account
      this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED)    || // Trading on the trading server side is disabled for EAs on the current account
      this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED)      || // Trading operations are disabled in the terminal
      this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED)            || // Trading operations are disabled for the EA
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED)                  || // Trading on a symbol is disabled 
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY)                 || // Close only
      this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED)                || // Market orders disabled
      this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED)                 || // Limit orders are disabled
      this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED)                  || // Stop orders are disabled
      this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED)            || // StopLimit orders are disabled
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY)                 || // Only short positions are allowed
      this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY)                  || // Only long positions are allowed
      this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED)              || // CloseBy orders are disabled
      this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED)       || // Exceeded maximum allowed aggregate volume of orders and positions in one direction
      this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED)        || // Close by is disabled
      this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL)        || // Symbols of opposite positions are not equal
      this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ)   || // Unsupported price parameter type in a request
      this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE)                 || // Trading disabled for the EA until the reason is eliminated
      this.IsPresentErorCode(10006)                                        || // Request rejected
      this.IsPresentErorCode(10011)                                        || // Request handling error
      this.IsPresentErorCode(10012)                                        || // Request rejected due to expiration
      this.IsPresentErorCode(10013)                                        || // Invalid request
      this.IsPresentErorCode(10017)                                        || // Trading disabled
      this.IsPresentErorCode(10018)                                        || // Market closed
      this.IsPresentErorCode(10023)                                        || // Order status changed
      this.IsPresentErorCode(10025)                                        || // No changes in the request
      this.IsPresentErorCode(10026)                                        || // Auto trading disabled by server
      this.IsPresentErorCode(10027)                                        || // Auto trading disabled by client terminal
      this.IsPresentErorCode(10032)                                        || // Transaction is allowed for live accounts only
      this.IsPresentErorCode(10033)                                        || // The maximum number of pending orders is reached
      this.IsPresentErorCode(10034)                                           // You have reached the maximum order and position volume for this symbol
     ) return ERROR_CODE_PROCESSING_METHOD_EXIT;
//--- View the full list of errors and correct trading request parameters
   for(int i=0;i<total;i++)
     {
      int err=this.m_list_errors.At(i);
      if(err==NULL)
         continue;
      switch(err)
        {
         //--- Correct an invalid volume and stop levels in a trading request
         case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME :
         case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME :
         case MSG_LIB_TEXT_INVALID_VOLUME_STEP     :  request.volume=symbol_obj.NormalizedLot(request.volume);                                              break;
         case MSG_SYM_SL_ORDER_DISABLED            :  request.sl=0;                                                                                         break;
         case MSG_SYM_TP_ORDER_DISABLED            :  request.tp=0;                                                                                         break;
         case MSG_LIB_TEXT_PR_LESS_STOP_LEVEL      :  request.price=this.CorrectPricePending(order_type,request.price,0,symbol_obj,spread_multiplier);      break;
         case MSG_LIB_TEXT_SL_LESS_STOP_LEVEL      :  request.sl=this.CorrectStopLoss(order_type,request.price,request.sl,symbol_obj,spread_multiplier);    break;
         case MSG_LIB_TEXT_TP_LESS_STOP_LEVEL      :  request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp,symbol_obj,spread_multiplier);  break;
         //--- If unable to select the position lot, return "abort trading attempt" since the funds are insufficient even for the minimum lot
         case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR    :  request.volume=this.CorrectVolume(request.volume,request.price,order_type,symbol_obj,DFUN);
                                                      if(request.volume==0)
                                                         return ERROR_CODE_PROCESSING_METHOD_EXIT;                                                                                      break;
         //--- Proximity to the order activation level is handled by five-second waiting - during this time, the price may go beyond the freeze level
         case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL    :
         case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL    :
         case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL    :  return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - wait 5 seconds
         default:
           break;
        }
     }
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

A lógica do método é descrita nos comentários no código. Em resumo: se encontrarmos códigos de erro não processados, retornamos o método de processamento "interromper tentativa de negociar"; quando encontramos erros que podem ser corrigidos, corrigimos os valores dos parâmetros e retornamos OK.

Modificação do método que verifica restrições para negociação e erros da solicitação de negociação:

//+------------------------------------------------------------------+
//| Check limitations and errors                                     |
//+------------------------------------------------------------------+
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::CheckErrors(const double volume,
                                                        const double price,
                                                        const ENUM_ACTION_TYPE action,
                                                        const ENUM_ORDER_TYPE order_type,
                                                        CSymbol *symbol_obj,
                                                        const CTradeObj *trade_obj,
                                                        const string source_method,
                                                        const double limit=0,
                                                        double sl=0,
                                                        double tp=0)
  {
//--- Check the previously set flag disabling trading for an EA
   if(this.IsTradingDisable())
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_FATAL_ERROR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
      return ERROR_CODE_PROCESSING_METHOD_DISABLE;
     }
//--- result of all checks and error flags
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   bool res=true;
//--- Clear the error list
   this.m_list_errors.Clear();
   this.m_list_errors.Sort();
//--- Check trading limitations
   res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp);
//--- Check the funds sufficiency for opening positions/placing orders
   if(action<ACTION_TYPE_CLOSE_BY)
      res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method);
//--- Check parameter values by StopLevel and FreezeLevel
   res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method);
   
//--- If there are limitations, display the header and the error list
   if(!res)
     {
      //--- Request was rejected before sending to the server due to:
      int total=this.m_list_errors.Total();
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
        {
         //--- For MQL5, first display the list header followed by the error list
         #ifdef __MQL5__
         ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST));
         for(int i=0;i<total;i++)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         //--- For MQL4, the journal messages are displayed in the reverse order: the error list in the reverse loop is followed by the list header
         #else    
         for(int i=total-1;i>WRONG_VALUE;i--)
            ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i)));
         ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST));
         #endif 
        }
      //--- If the action is performed at the "abort trading operation" error
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
         return ERROR_CODE_PROCESSING_METHOD_EXIT;
      //--- If the action is performed at the "create a pending request" error
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
         return ERROR_CODE_PROCESSING_METHOD_PENDING;
      //--- If the action is performed at the "correct parameters" error
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_CORRECT)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST));
         //--- Return the result of an attempt to correct the request parameters
         return this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj);
        }
     }
//--- No limitations
   return ERROR_CODE_PROCESSING_METHOD_OK;
  }
//+------------------------------------------------------------------+

O código completo está marcado em amarelo. Agora, no método, primeiro é verificado o sinalizador de proibição de negociação definido e, se definido, é retornado o tipo de processamento de erro "proibir que o EA opere". Além disso, dependendo do comportamento predeterminado do EA durante erros e de acordo com o código de erro, é retornado o método de processamento de erro necessário. Se não houver erros, será retornado um código que não exige processar erros.

No método de verificação de restrições de negociação foram feitas melhorias semelhantes, mas todas se referem apenas à adição de sinalizadores necessários que indicam a presença de diferentes tipos de erros e como lidar com eles.
Como todas as ações executadas no método e sua lógica são bem descritas nos comentários do código, veremos apenas a versão final do método já desenvolvido:

//+------------------------------------------------------------------+
//| Check trading limitations                                        |
//+------------------------------------------------------------------+
bool CTrading::CheckTradeConstraints(const double volume,
                                     const ENUM_ACTION_TYPE action_type,
                                     const CSymbol *symbol_obj,
                                     const string source_method,
                                     double sl=0,
                                     double tp=0)
  {
//--- the result of conducting all checks
   bool res=true;
//--- Check connection with the trade server (not in the test mode)
   if(!::TerminalInfoInteger(TERMINAL_CONNECTED))
     {
      if(!::MQLInfoInteger(MQL_TESTER))
        {
         //--- Write the error code to the list and return 'false' - there is no point in further checks
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(10031);
         return false;
        }
     }
//--- Check if trading is enabled for an account (if there is a connection with the trade server)
   else if(!this.m_account.TradeAllowed())
     {
      //--- Write the error code to the list and return 'false' - there is no point in further checks
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED);
      return false;
     }
//--- Check if trading is allowed for any EAs/scripts for the current account
   if(!this.m_account.TradeExpert())
     {
      //--- Write the error code to the list and return 'false' - there is no point in further checks
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED);
      return false;
     }
//--- Check if auto trading is allowed in the terminal.
//--- AutoTrading button (Options --> Expert Advisors --> "Allowed automated trading")
   if(!::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      //--- Write the error code to the list and return 'false' - there is no point in further checks
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED);
      return false;
     }
//--- Check if auto trading is allowed for the current EA.
//--- (F7 --> Common --> Allow Automated Trading)
   if(!::MQLInfoInteger(MQL_TRADE_ALLOWED))
     {
      //--- Write the error code to the list and return 'false' - there is no point in further checks
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED);
      return false;
     }
//--- Check if trading is enabled on a symbol.
//--- If trading is disabled, write the error code to the list and return 'false' - there is no point in further checks
   if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
      this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_DISABLED);
      return false;
     }

//--- If not closing/removal/modification
   if(action_type<ACTION_TYPE_CLOSE_BY)
     {
      //--- In case of close-only, write the error code to the list and return 'false' - there is no point in further checks
      if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)
        {
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY);
         return false;
        }
      //--- Check the minimum volume
      if(volume<symbol_obj.LotsMin())
        {
         //--- The volume in a request is less than the minimum allowed one.
         //--- add the error code to the list
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME);
         //--- If the EA behavior during the trading error is set to "abort trading operation",
         //--- return 'false' - there is no point in further checks
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- If the EA behavior during a trading error is set to
         //--- "correct parameters" or "create a pending request",
         //--- write 'false' to the result
         else res &=false;
        }
      //--- Check the maximum volume
      else if(volume>symbol_obj.LotsMax())
        {
         //--- The volume in the request exceeds the maximum acceptable one.
         //--- add the error code to the list
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME);
         //--- If the EA behavior during the trading error is set to "abort trading operation",
         //--- return 'false' - there is no point in further checks
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- If the EA behavior during a trading error is set to
         //--- "correct parameters" or "create a pending request",
         //--- write 'false' to the result
         else res &=false;
        }
      //--- Check the minimum volume gradation
      double step=symbol_obj.LotsStep();
      if(fabs((int)round(volume/step)*step-volume)>0.0000001)
        {
         //--- The volume in the request is not a multiple of the minimum gradation of the lot change step
         //--- add the error code to the list
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP);
         //--- If the EA behavior during the trading error is set to "abort trading operation",
         //--- return 'false' - there is no point in further checks
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- If the EA behavior during a trading error is set to
         //--- "correct parameters" or "create a pending request",
         //--- write 'false' to the result
         else res &=false;
        }
     }

//--- When opening a position
   if(action_type<ACTION_TYPE_BUY_LIMIT)
     {
      //--- Check if sending market orders is allowed on a symbol.
      //--- If using market orders is disabled, write the error code to the list and return 'false' - there is no point in further checks
      if(!symbol_obj.IsMarketOrdersAllowed())
        {
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_MARKET_ORDER_DISABLED);
         return false;
        }
     }
//--- When placing a pending order
   else if(action_type>ACTION_TYPE_SELL && action_type<ACTION_TYPE_CLOSE_BY)
     {
      //--- If there is a limitation on the number of pending orders on an account and placing a new order exceeds it
      if(this.m_account.LimitOrders()>0 && this.OrdersTotalAll()+1 > this.m_account.LimitOrders())
        {
         //--- The limit on the number of pending orders is reached - write the error code to the list and return 'false' - there is no point in further checks
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(10033);
         return false;
        }  
      //--- Check if placing limit orders is allowed on a symbol.
      if(action_type==ACTION_TYPE_BUY_LIMIT || action_type==ACTION_TYPE_SELL_LIMIT)
        {
         //--- If setting limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks
         if(!symbol_obj.IsLimitOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_LIMIT_ORDER_DISABLED);
            return false;
           }
        }
      //--- Check if placing stop orders is allowed on a symbol.
      else if(action_type==ACTION_TYPE_BUY_STOP || action_type==ACTION_TYPE_SELL_STOP)
        {
         //--- If setting stop orders is disabled, write the error code to the list and return 'false' - there is no point in further checks
         if(!symbol_obj.IsStopOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_STOP_ORDER_DISABLED);
            return false;
           }
        }
      //--- For MQL5, check if placing stop limit orders is allowed on a symbol.
      #ifdef __MQL5__
      else if(action_type==ACTION_TYPE_BUY_STOP_LIMIT || action_type==ACTION_TYPE_SELL_STOP_LIMIT)
        {
         //--- If setting stop limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks
         if(!symbol_obj.IsStopLimitOrdersAllowed())
           {
            this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_SYM_STOP_LIMIT_ORDER_DISABLED);
            return false;
           }
        }
      #endif 
     }

//--- In case of opening/placing/modification
   if(action_type!=ACTION_TYPE_CLOSE_BY)
     {
      //--- If not modification
      if(action_type!=ACTION_TYPE_MODIFY)
        {
         //--- When buying, check if long trading is enabled on a symbol
         if(this.DirectionByActionType(action_type)==ORDER_TYPE_BUY)
           {
            //--- If only short positions are enabled, write the error code to the list and return 'false' - there is no point in further checks
            if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)
              {
               this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_SHORTONLY);
               return false;
              }
            //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction   
            if(symbol_obj.VolumeLimit()>0)
              {
               //--- (If the total volume of placed long orders and open long positions)+open volume exceed the maximum one
               if(this.OrdersTotalVolumeLong()+this.PositionsTotalVolumeLong()+volume > symbol_obj.VolumeLimit())
                 {
                  //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction
                  //--- write the error code to the list and return 'false' - there is no point in further checks
                  this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
                  this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
                  return false;
                 }
              }
           }
         //--- When selling, check if short trading is enabled on a symbol
         else if(this.DirectionByActionType(action_type)==ORDER_TYPE_SELL)
           {
            //--- If only long positions are enabled, write the error code to the list and return 'false' - there is no point in further checks
            if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)
              {
               this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_LONGONLY);
               return false;
              }
            //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction   
            if(symbol_obj.VolumeLimit()>0)
              {
               //--- (If the total volume of placed short orders and open short positions)+open volume exceed the maximum one
               if(this.OrdersTotalVolumeShort()+this.PositionsTotalVolumeShort()+volume > symbol_obj.VolumeLimit())
                 {
                  //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction
                  //--- write the error code to the list and return 'false' - there is no point in further checks
                  this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
                  this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
                  return false;
                 }
              }
           }
        }
      //--- If the request features StopLoss and its placing is not allowed
      if(sl>0 && !symbol_obj.IsStopLossOrdersAllowed())
        {
         //--- add the error code to the list
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_SL_ORDER_DISABLED);
         //--- If the EA behavior during the trading error is set to "abort trading operation",
         //--- return 'false' - there is no point in further checks
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- If the EA behavior during a trading error is set to
         //--- "correct parameters" or "create a pending request",
         //--- write 'false' to the result
         else res &=false;
        }
      //--- If the request features TakeProfit and its placing is not allowed
      if(tp>0 && !symbol_obj.IsTakeProfitOrdersAllowed())
        {
         //--- add the error code to the list
         this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_SYM_TP_ORDER_DISABLED);
         //--- If the EA behavior during the trading error is set to "abort trading operation",
         //--- return 'false' - there is no point in further checks
         if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK)
            return false;
         //--- If the EA behavior during a trading error is set to
         //--- "correct parameters" or "create a pending request",
         //--- write 'false' to the result
         else res &=false;
        }
     }

//--- When closing by an opposite position
   else if(action_type==ACTION_TYPE_CLOSE_BY)
     {
      //--- When closing by an opposite position is disabled
      if(!symbol_obj.IsCloseByOrdersAllowed())
        {
         //--- write the error code to the list and return 'false'
         this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
         this.AddErrorCodeToList(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED);
         return false;
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

No método que verifica os valores dos parâmetros pelos níveis StopLevel e FreezeLevel, a cada erro encontrado adicionamos um sinalizador indicando que o erro deve ser visto na lista de erros:

//+------------------------------------------------------------------+
//| Check parameter values by StopLevel and FreezeLevel              |
//+------------------------------------------------------------------+
bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action,
                           const ENUM_ORDER_TYPE order_type,
                           double price,
                           double limit,
                           double sl,
                           double tp,
                           const CSymbol *symbol_obj,
                           const string source_method)
  {
//--- the result of conducting all checks
   bool res=true;
//--- StopLevel
//--- If this is not a position closure/order removal
   if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY)
     {
      //--- When placing a pending order
      if(action>ACTION_TYPE_SELL)
        {
         //--- If the placement distance in points is less than StopLevel
         if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj))
           {
            //--- add the error code to the list and write 'false' to the result
            this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL);
            res &=false;
           }
        }
      //--- If StopLoss is present
      if(sl>0)
        {
         //--- If StopLoss distance in points from the open price is less than StopLevel
         double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price);
         if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj))
           {
            //--- add the error code to the list and write 'false' to the result
            this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL);
            res &=false;
           }
        }
      //--- If TakeProfit is present
      if(tp>0)
        {
         double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price);
         //--- If TakeProfit distance in points from the open price is less than StopLevel
         if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj))
           {
            //--- add the error code to the list and write 'false' to the result
            this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
            this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL);
            res &=false;
           }
        }
     }
//--- FreezeLevel
//--- If this is a position closure/order removal/modification
   if(action>ACTION_TYPE_SELL_STOP_LIMIT)
     {
      //--- If this is a position
      if(order_type<ORDER_TYPE_BUY_LIMIT)
        {
         //--- StopLoss modification
         if(sl>0)
           {
            //--- If the distance from the price to StopLoss is less than FreezeLevel
            if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj))
              {
               //--- add the error code to the list and write 'false' to the result
               this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
         //--- TakeProfit modification
         if(tp>0)
           {
            //--- If the distance from the price to StopLoss is less than FreezeLevel
            if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj))
              {
               //--- add the error code to the list and write 'false' to the result
               this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
        }
      //--- If this is a pending order
      else
        {
         //--- Placement price modification
         if(price>0)
           {
            //--- If the distance from the price to the order activation price is less than FreezeLevel
            if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj))
              {
               //--- add the error code to the list and write 'false' to the result
               this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST;
               this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL);
               res &=false;
              }
           }
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

No método de definição de preço de solicitação de negociação adicionamos a atualização de preço e a saída ao falhar a atualização com indicação do código de erro:

//+------------------------------------------------------------------+
//| Set trading request prices                                       |
//+------------------------------------------------------------------+
template <typename PR,typename SL,typename TP,typename PL> 
bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj)
  {
//--- Reset prices
   ::ZeroMemory(this.m_request);
//--- Update all data by symbol
   if(!symbol_obj.RefreshRates())
     {
      this.AddErrorCodeToList(10021);
      return false;
     }

//--- Open/close price

Além disso, foi alterado o cálculo dos preços no método de definição de preço de solicitação de negociação:

         //--- Calculate the order price
         switch((int)action)
           {
            //--- Pending order
            case ORDER_TYPE_BUY_LIMIT       :  this.m_request.price=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits());      break;
            case ORDER_TYPE_BUY_STOP        :
            case ORDER_TYPE_BUY_STOP_LIMIT  :  this.m_request.price=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits());      break;
            
            case ORDER_TYPE_SELL_LIMIT      :  this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits());  break;
            case ORDER_TYPE_SELL_STOP       :
            case ORDER_TYPE_SELL_STOP_LIMIT :  this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits());  break;
            //--- Default - current position open prices
            default  :  this.m_request.price=
              (
               this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : 
               ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits())
              ); break;
           }

Agora, o método de classe do objeto-símbolo Bid() é substituído pelo método BidLast(), que retorna o preço Bid ou Last, dependendo do modo de construção de gráficos.

Método que define o multiplicador de spread para objetos de negociação todos os símbolos:

//+------------------------------------------------------------------+
//| Set the spread multiplier                                        |
//| for trading objects of all symbols                               |
//+------------------------------------------------------------------+
void CTrading::SetSpreadMultiplier(const uint value=1,const string symbol=NULL)
  {
   CSymbol *symbol_obj=NULL;
   if(symbol==NULL)
     {
      CArrayObj *list=this.m_symbols.GetList();
      if(list==NULL || list.Total()==0)
         return;
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         symbol_obj=list.At(i);
         if(symbol_obj==NULL)
            continue;
         CTradeObj *trade_obj=symbol_obj.GetTradeObj();
         if(trade_obj==NULL)
            continue;
         trade_obj.SetSpreadMultiplier(value);
        }
     }
   else
     {
      CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol,DFUN);
      if(trade_obj==NULL)
         return;
      trade_obj.SetSpreadMultiplier(value);
     }
  }
//+------------------------------------------------------------------+

Ao método é transferido o valor do multiplicador (por padrão1) e o nome do símbolo (por padrão NULL)

Se como símbolo for transferido NULL, o valor do multiplicador será definido para os objetos de negociação de todos os símbolos da coleção de símbolos existente.
Caso contrário, o valor será definido para o objeto de negociação do símbolo cujo nome é transferido para o método.

Devido ao novo tratamento de erros, foram modificados todos os métodos de negociação.
Vejamos o código do método de abertura de posição Buy:

//+------------------------------------------------------------------+
//| Open Buy position                                                |
//+------------------------------------------------------------------+
template<typename SL,typename TP> 
bool CTrading::OpenBuy(const double volume,
                       const string symbol,
                       const ulong magic=ULONG_MAX,
                       const SL sl=0,
                       const TP tp=0,
                       const string comment=NULL,
                       const ulong deviation=ULONG_MAX)
  {
//--- Set the trading request result as 'true' and the error flag as "no errors"
   bool res=true;
   this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR;
   ENUM_ACTION_TYPE action=ACTION_TYPE_BUY;
   ENUM_ORDER_TYPE order_type=ORDER_TYPE_BUY;
//--- Get a symbol object by a symbol name. If failed to get
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(symbol_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ));
      return false;
     }
//--- get a trading object from a symbol object
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
//--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false'
   if(trade_obj==NULL)
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ));
      return false;
     }
//--- Set the prices
//--- If failed to set - write the "internal error" flag, display the message in the journal and return 'false'
   if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj))
     {
      this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(10021));
      return false;
     }

//--- Write the volume to the request structure
   this.m_request.volume=volume;
//--- Get the method of handling errors from the CheckErrors() method while checking for errors
   ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp);
//--- In case of trading limitations, funds insufficiency,
//--- if there are limitations by StopLevel or FreezeLevel ...
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
      //--- If trading is disabled completely, display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "abort trading operation" - display a journal message, play the error sound and exit
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED));
         if(this.IsUseSounds())
            trade_obj.PlaySoundError(action,order_type);
         return false;
        }
      //--- If the check result is "waiting", display the message in the journal
      if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
         //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned)
         ::Sleep(method);
         //--- after waiting, update all data
         symbol_obj.Refresh();
        }
      //--- If the check result is "create a pending request", do nothing temporarily
      if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)
        {
         if(this.m_log_level>LOG_LEVEL_NO_MSG)
            ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST));
        }
     }
   
//--- Send the request
   res=trade_obj.OpenPosition(POSITION_TYPE_BUY,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);
//--- If the request is successful, play the success sound set for a symbol trading object for this type of trading operation
   if(res)
     {
      if(this.IsUseSounds())
         trade_obj.PlaySoundSuccess(action,order_type);
     }
//--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation
   else
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
      if(this.IsUseSounds())
         trade_obj.PlaySoundError(action,order_type);
     }
//--- Return the result of sending a trading request in a symbol trading object
   return res;
  }
//+------------------------------------------------------------------+

Todas as explicações são descritas em detalhes nos comentários do código. Os demais métodos de negociação foram modificados de maneira semelhante. Espero que esteja tudo claro. Em qualquer caso, você sempre pode tirar dúvidas nos comentários do artigo.

Métodos que retornam os preços corretos calculados para colocar ordens stop e ordens pendentes:

//+------------------------------------------------------------------+
//| Return correct StopLoss relative to StopLevel                    |
//+------------------------------------------------------------------+
double CTrading::CorrectStopLoss(const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   if(stop_loss==0) return 0;
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set);
   return
     (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY            ?
      ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits())  :
      ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits())
     );
  }
//+------------------------------------------------------------------+
//| Return correct TakeProfit relative to StopLevel                  |
//+------------------------------------------------------------------+
double CTrading::CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   if(take_profit==0) return 0;
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set);
   return
     (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY             ?
      ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) :
      ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),take_profit),symbol_obj.Digits())
     );
  }
//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CTrading::CorrectPricePending(const ENUM_ORDER_TYPE order_type,const double price_set,const double price,const CSymbol *symbol_obj,const uint spread_multiplier=1)
  {
   uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel());
   double pp=0;
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? symbol_obj.Ask()     : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? symbol_obj.Ask()     : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits());
      default                          :  if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),::EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

Aqui tudo deve ficar claro, mesmo sem comentar o código, pois basta comparar os preços passados aos métodos com o preço recebido como o recuo do StopLevel em relação ao preço de abertura. O preço correto (mais alto/mais baixo, dependendo do tipo de ordem) é retornado ao programa de chamada.

Método que retorna o volume com qual é possível abrir uma posição:

//+------------------------------------------------------------------+
//| Return the volume, at which it is possible to open a position    |
//+------------------------------------------------------------------+
double CTrading::CorrectVolume(const double price,const ENUM_ORDER_TYPE order_type,CSymbol *symbol_obj,const string source_method)
  {
//--- If funds are insufficient for the minimum lot, inform of that and return zero
   if(!this.CheckMoneyFree(symbol_obj.LotsMin(),price,order_type,symbol_obj,source_method))
     {
      if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT));
      return 0;
     }

//--- Update account and symbol data
   this.m_account.Refresh();
   symbol_obj.RefreshRates();
//--- Calculate the lot, which is closest to the acceptable one
   double vol=symbol_obj.NormalizedLot(this.m_account.Equity()*this.m_account.Leverage()/symbol_obj.TradeContractSize()/(symbol_obj.CurrencyBase()=="USD" ? 1.0 : symbol_obj.BidLast()));
//--- Calculate a sufficient lot
   double margin=this.m_account.MarginForAction(order_type,symbol_obj.Name(),1.0,price);
   if(margin!=EMPTY_VALUE)
      vol=symbol_obj.NormalizedLot(this.m_account.MarginFree()/margin);

//--- If the calculated lot is invalid or the margin calculation returns an error
   if(!this.CheckMoneyFree(vol,price,order_type,symbol_obj,source_method))
     {
      //--- In the do-while loop, while the calculated valid volume exceeds the minimum lot
      do
        {
         //--- Subtract the minimum lot from the valid lot value
         vol-=symbol_obj.LotsStep();
         //--- If the calculated lot allows opening a position/setting an order, return the lot value
         if(this.CheckMoneyFree(symbol_obj.NormalizedLot(vol),price,order_type,symbol_obj,source_method))
            return vol;
        }
      while(vol>symbol_obj.LotsMin() && !::IsStopped());
     }
//--- If the lot is calculated correctly, return the calculated lot
   else
      return vol;
//--- If the current stage is reached, the funds are insufficient. Inform of that and return zero
   if(this.m_log_level>LOG_LEVEL_NO_MSG)
      ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT));
   return 0;
  }
//+------------------------------------------------------------------+

Aqui também o código está comentado.
Eu saliento que, primeiro, verificamos a capacidade de abrir um lote mínimo, e se isso for impossível, outros cálculos não terão sentido, portanto, retornamos zero.
Em seguida, calculamos o lote permitido aproximado (para que, com seu possível ajuste, não inicie a "seleção" do lote necessário a partir do seu valor máximo).
Logo, calculamos o lote máximo que permite abrir uma posição com todos os fundos disponíveis (por que razão dessa maneira? Bem, simplesmente porque se não temos dinheiro suficiente para abrir uma posição, isso implica que o volume necessário é grande, o que significa que precisamos calcular o volume que será o máximo possível).

Neste cálculo, é usada a função OrderCalcMargin(), que em caso de erro pode retornar false, já o método MarginForAction() da classe CAccount, que usa esta função, em tal situação retorna EMPTY_VALUE, o que corresponde ao valor da constante DBL_MAX (valor máximo que pode ser representado pelo tipo double). Se obtivermos esse valor, significa que houve um erro e nosso lote não foi calculado.

Neste caso (e não apenas se acontecer um erro, mas também ao verificar se o cálculo está certo), recorreremos à "seleção" do lote máximo necessário, subtraindo simplesmente o passo do lote do volume máximo possível calculado na ordem de negociação. É aqui que é útil o volume permitido aproximado calculado anteriormente, pois se não for possível calcular o volume exato, o ciclo de redução do lote começará não a partir do valor máximo do lote definido para o símbolo, mas, sim, a partir do mais próximo, o que reduzirá bastante o número de iterações do ciclo.

A propósito, durante a verificação, não recebi erros de função OrderCalcMargin() ao calcular o lote, mas ocorreram cálculos errôneos de cerca de um passo de alteração do lote.

Essas são todas as mudanças e melhorias na classe de negociação.

Teste

Para testar, pegamos o EA do artigo anterior e o salvamos na nova pasta \MQL5\Experts\TestDoEasy\ Part24\ usando o novo nome TestDoEasyPart24.mq5.

À lista de variáveis globais adicionamos uma variável-sinalizador para trabalhar no testador de estratégia:

//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
bool           trailing_on;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         used_symbols;
string         array_used_symbols[];
bool           testing;
//+------------------------------------------------------------------+

No manipulador OnInit() definimos o valor da variável-sinalizador para trabalhar no testador de estratégia:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal 
//--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }


Para enviar eventos para o manipulador de eventos da biblioteca OnDoEasyEvent() ao trabalhar no testador, temos uma função especial EventsHandling().
Ela sofreu uma modificação menor:

//+------------------------------------------------------------------+
//| Working with events in the tester                                |
//+------------------------------------------------------------------+
void EventsHandling(void)
  {
//--- If a trading event is present
   if(engine.IsTradeEvent())
     {
      //--- Number of trading events occurred simultaneously
      int total=engine.GetTradeEventsTotal();
      for(int i=0;i<total;i++)
        {
         //--- Get the next event from the list of simultaneously occurred events by index
         CEventBaseObj *event=engine.GetTradeEventByIndex(i);
         if(event==NULL)
            continue;
         long   lparam=i;
         double dparam=event.DParam();
         string sparam=event.SParam();
         OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
        }
     }
//--- If there is an account event
   if(engine.IsAccountsEvent())
     {
      //--- Get the list of all account events occurred simultaneously
      CArrayObj* list=engine.GetListAccountEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- If there is a symbol collection event
   if(engine.IsSymbolsEvent())
     {
      //--- Get the list of all symbol events occurred simultaneously
      CArrayObj* list=engine.GetListSymbolsEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Tudo aqui é claro se vermos os comentários do código.

Desde que criamos uma lista de novos eventos de negociação,
no manipulador de eventos da biblioteca OnDoEasyEvent() obtemos cada evento da lista de todos os novos eventos de negociação pelo índice do evento na lista, e simplesmente exibimos no log uma descrição de cada um dos eventos recebidos da lista:

//+------------------------------------------------------------------+
//| Handling DoEasy library events                                   |
//+------------------------------------------------------------------+
void OnDoEasyEvent(const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
   ushort msc=engine.EventMSC(lparam);
   ushort reason=engine.EventReason(lparam);
   ushort source=engine.EventSource(lparam);
   long time=TimeCurrent()*1000+msc;
   
//--- Handling symbol events
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=engine.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Event text description
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=DoubleToString(dparam,digits);
      
      //--- Check event reasons and display its description in the journal
      if(reason==BASE_EVENT_REASON_INC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Handling account events
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=engine.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Event text description
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=DoubleToString(dparam,digits);
      
      //--- Checking event reasons and handling the increase of funds by a specified value,
      
      //--- In case of a property value increase
      if(reason==BASE_EVENT_REASON_INC)
        {
         //--- Display an event in the journal
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
         //--- if this is an equity increase
         if(idx==ACCOUNT_PROP_EQUITY)
           {
            //--- Get the list of all open positions
            CArrayObj* list_positions=engine.GetListMarketPosition();
            //--- Select positions with the profit exceeding zero
            list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE);
            if(list_positions!=NULL)
              {
               //--- Sort the list by profit considering commission and swap
               list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL);
               //--- Get the position index with the highest profit
               int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL);
               if(index>WRONG_VALUE)
                 {
                  COrder* position=list_positions.At(index);
                  if(position!=NULL)
                    {
                     //--- Get a ticket of a position with the highest profit and close the position by a ticket
                     engine.ClosePosition(position.Ticket());
                    }
                 }
              }
           }
        }
      //--- Other events are simply displayed in the journal
      if(reason==BASE_EVENT_REASON_DEC)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Handling market watch window events
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Market Watch window event
      string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
//--- Handling trading events
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      //--- Get the list of trading events
      CArrayObj *list=engine.GetListAllOrdersEvents();
      if(list==NULL)
         return;
      //--- get the event index shift relative to the end of the list
      //--- in the tester, the shift is passed by the lparam parameter to the event handler
      //--- outside the tester, events are sent one by one and handled in OnChartEvent()
      int shift=(testing ? (int)lparam : 0);
      CEvent *event=list.At(list.Total()-1-shift);
      if(event==NULL)
      return;
      //--- Accrue the credit
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional charges
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Correction
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Enumerate bonuses
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional commissions
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Interest rate
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled buy deal
      if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled sell deal
      if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Dividend operations
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Accrual of franked dividend
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Tax charges
      if(event.TypeEvent()==TRADE_EVENT_TAX)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Replenishing account balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Withdrawing funds from balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      
      //--- Pending order placed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order removed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order partially activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by partial market order execution (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial execution of a market order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial activation of a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position partially closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- StopLimit order activation
      if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and StopLoss price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order, StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position's StopLoss and TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS)
        {
         Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT)
        {
         Print(DFUN,event.TypeEventDescription());
        }
     }
  }
//+------------------------------------------------------------------+


Neste caso, por simplicidade, obtemos o evento na lista pelo seu índice (no caso do testador, o índice é transferido ao parâmetro lparam com a ajuda da função EventsHandling(), enquanto, na versão demo e na vida real o índice é igual a zero, pois cada evento é enviado para OnChartEvent() como evento independente e não a partir da lista), imprimimos no log a descrição do evento recebido.

Cada um pode encontrar uma maneira própria de realizar seu processamento. É possível diretamente neste código quer inserir o processamento, quer declarar a lista de sinalizadores de eventos, e, neste caso, somente definir os sinalizadores de eventos ocorridos, enquanto delegamos seu processamento a outras funções.

Estas são todas as alterações e aprimoramentos necessários para controlar todos os eventos de negociação ocorridos de maneira simultânea. O que é necessário para corrigir automaticamente os erros nos parâmetros da solicitação de negociação está pronto na biblioteca em si mesma, por isso, por enquanto, não precisamos fazer nada no EA para conseguir isso. Em seguida, após a criação de todas as maneiras de processar erros, inserimos um parâmetro de entrada adicional que mostra o comportamento do EA em caso de situação de erro.

Compilamos o EA e o iniciamos no testador. Definimos algumas ordens pendentes e, depois, as removemos todas num ciclo:


O EA exibe no log quatro eventos ocorridos ao remover quatro ordens pendentes num ciclo após clicar o botão "Delete pending".

Agora nas configurações do EA no testador de estratégia inserimos um lote maior, por exemplo, 100.0 e tentamos definir uma ordem pendente ou abrir uma posição:

Após a tentativa de definir uma ordem pendente ou abrir uma posição com volume de 100.0 lotes, recebemos uma mensagem no log indicando fundos insuficientes e a posterior correção do volume. Depois disso, é definida com sucesso uma ordem e aberta uma posição.

O que vem agora?

No próximo artigo, trataremos dos erros retornados pelo servidor de negociação.

Abaixo estão anexados todos os arquivos da versão atual da biblioteca e os arquivos do EA de teste. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.

Complementos

Artigos desta série:

Parte 1. Conceito, gerenciamento de dados e primeiros resultados
Parte 2. Coleção do histórico de ordens e negócios
Parte 3. Coleção de ordens e posições de mercado, busca e ordenação
Parte 4. Eventos de Negociação. Conceito
Parte 5. Classes e coleções de eventos de negociação. Envio de eventos para o programa
Parte 6. Eventos da conta netting
Parte 7. Eventos de ativação da ordem stoplimit, preparação da funcionalidade para os eventos de modificação de ordens e posições
Parte 8. Eventos de modificação de ordens e posições
Parte 9. Compatibilidade com a MQL4 - preparação dos dados
Parte 10. Compatibilidade com a MQL4 - eventos de abertura de posição e ativação de ordens pendentes
Parte 11. Compatibilidade com a MQL4 - eventos de encerramento de posição
Parte 12. Implementação da classe de objeto "conta" e da coleção de objetos da conta
Parte 13. Eventos do objeto conta
Parte 14. O objeto símbolo
Parte 15. Coleção de objetos-símbolos
Parte 16. Eventos de coleção de símbolos
Parte 17. Interatividade de objetos de biblioteca
Parte 18. Interatividade do objeto-conta e quaisquer de outros objetos da biblioteca
Parte 19. Classe de mensagens de biblioteca
Parte 20. Criação e armazenamento de recursos de programas
Parte 21 Classes de negociação - objeto básico de negociação multiplataforma
Parte 22. Classes de negociação - classe básica de negociação, controle de restrições
Parte 23. Classes de negociação - classe básica de negociação, controle de parâmetros válidos