Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte X): Compatibilidade com a MQL4 - Eventos de abertura de posição e ativação de ordens pendentes

22 agosto 2019, 16:11
Artyom Trishkin
0
708

Conteúdo


EA de teste

No artigo anterior, nós removemos os erros nos arquivos da biblioteca relacionados às diferenças entre a MQL4 e MQL5, e introduzimos uma coleção do histórico de ordens e posições para MQL4. Neste artigo, nós continuaremos a combinar as linguagens MQL4 e MQL5 na biblioteca e definiremos os eventos de abertura de posições e ativação de ordens pendentes.
A sequência de etapas de melhoria será revertida. Anteriormente, nós introduzimos a funcionalidade seguida pelo EA de teste. Agora, para entender o que precisa ser melhorado, nós precisamos iniciar o EA de teste e ver onde ele funciona e onde ele falha. As coisas que não funcionam são as que devem ser melhoradas.

Para conseguirmos isso, nós vamos utilizar o EA de teste TestDoEasyPart08.mq5 da oitava parte da descrição da biblioteca da pasta \MQL5\Experts\TestDoEasy\Part08 e salvá-lo sob o nome de TestDoEasyPart10.mq4 na pasta da MetaTrader 4 \MQL4\Experts\TestDoEasy\Part10.

Vamos tentar compilá-lo. Isso nos leva a 34 erros de compilação. Quase todos eles estão relacionados à ausência das classes de negociação na biblioteca padrão da MQL4:


Vamos para o primeiro erro indicando a ausência do arquivo de inclusão


e consertá-lo — o arquivo deve ser incluído apenas para a MQL5:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart08.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#ifdef __MQL5__
#include <Trade\Trade.mqh>
#endif 
//--- enums

A compilação termina em 33 erros. Moveremos para o primeiro erro novamente, que indica o tipo ausente ao declarar o objeto da classe de negociação CTrade — ele não está presente na MQL4.

Vamos usar a diretiva de compilação condicional como fizemos anteriormente:

//--- global variables
CEngine        engine;
#ifdef __MQL5__
CTrade         trade;
#endif 
SDataButt      butt_data[TOTAL_BUTT];

Compilamos. Agora, o objeto 'trade' da classe CTrade tornou-se desconhecido para a MQL4. Corrigimos o problema de maneira semelhante:

//--- setting trade parameters
#ifdef __MQL5__
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
#endif 
//---
   return(INIT_SUCCEEDED);
  }

Enquadramos todas as instâncias de objetos trade nas diretivas de compilação condicional em todo o código do EA usando a diretiva #elseo código em MQL4 deve ser colocado lá. Vamos usar o primeiro erro de tipo desconhecido trade após fazer as edições e compilações anteriores:

      //--- If the BUTT_BUY button is pressed: Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Open Buy position
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
                                            
         #endif 
        }

Depois de enquadrar todas as instâncias de objeto 'trade' na diretiva de compilação condicional, nós recebemos outro erro indicando que o compilador não pode definir com precisão qual função sobrecarregada chamada deve ser usada devido à falta de parâmetros:


Se nós olharmos atentamente para o código, o motivo da confusão do compilador fica clara:

//+------------------------------------------------------------------+
//| Return the flag of a prefixed object presence                    |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

Em MQL5, a função tem apenas uma única maneira de chamada:

int  ObjectsTotal(
   long  chart_id,           // chart ID
   int   sub_window=-1,      // window index
   int   type=-1             // object type     
   );

onde o primeiro parâmetro é um ID do gráfico (0 - atual),

enquanto em MQL4, já faz algum tempo que a função tem duas maneiras de chamada. A primeira é a mesma que em MQL5:

int  ObjectsTotal(
   long  chart_id,           // chart ID
   int   sub_window=-1,      // window index
   int   type=-1             // object type     
   );

e a segunda está desatualizada e tem apenas um parâmetro:

int  ObjectsTotal(
   int   type=EMPTY         // object type     
   );

Em MQL5, passar 0 como o ID do gráfico para a função (o gráfico atual) não causa contradições e dúvidas, mas em MQL4, o compilador deve usar os parâmetros passados para definir o tipo de chamada. Neste caso, não é possível definir com precisão se passamos o ID do gráfico atual (0) e usamos a primeira maneira de chamada (afinal de contas, os outros dois parâmetros são definidos com seus valores padrão, o que significa que não precisamos passá-los para a função), ou se passamos um índice de janela (ou um tipo de objeto) e usamos a segunda maneira de chamada.

A solução aqui é simples — passamos o índice da subjanela (0 = janela do gráfico principal) como segundo parâmetro:

//+------------------------------------------------------------------+
//| Return the flag of a prefixed object presence                    |
//+------------------------------------------------------------------+
bool IsPresentObects(const string object_prefix)
  {
   for(int i=ObjectsTotal(0,0)-1;i>=0;i--)
      if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE)
         return true;
   return false;
  }
//+------------------------------------------------------------------+

e

//+------------------------------------------------------------------+
//| Manage button status                                             |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0,0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+

Agora tudo é compilado sem erros. Antes de iniciar o teste, tenha em mente que o EA não possui funções de negociação em MQL4, pois nós excluímos do código usando as diretivas de compilação condicional, o que significa que nós precisamos adicioná-las.
À medida que nós escrevemos o código para o testador, nós não vamos implementar nenhuma verificação exigida ao negociar em uma conta real/demo, limitando-nos as verificações mínimas.
Como os tickets da ordem e posição, bem como os níveis de preços calculados, são passados na função, tudo o que nós devemos fazer é selecionar uma ordem/posição pelo seu ticket e verificar o tipo e o horário de fechamento. Se o tipo não coincidir com o ticket ou tipo de posição, exibimos a mensagem correspondente e saímos da função com um erro. Se uma ordem for removida ou uma posição for encerrada, exibimos a mensagem e saímos com um erro. Em seguida, chamamos a função de abertura/encerramento/modificação e retornamos o resultado de sua execução.

No final da listagem do arquivo DELib.mqh, escrevemos todas as funções necessárias do testador em MQL4:

#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL4 temporary functions for the tester                          |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Open Buy position                                                |
//+------------------------------------------------------------------+
bool Buy(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_ASK,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending BuyLimit order                                       |
//+------------------------------------------------------------------+
bool BuyLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending BuyStop order                                        |
//+------------------------------------------------------------------+
bool BuyStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Open Sell position                                               |
//+------------------------------------------------------------------+
bool Sell(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   double price=0;
   ResetLastError();
   if(!SymbolInfoDouble(sym,SYMBOL_BID,price))
     {
      Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(string)GetLastError());
      return false;
     }
   if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending SellLimit order                                      |
//+------------------------------------------------------------------+
bool SellLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Set pending SellStop order                                       |
//+------------------------------------------------------------------+
bool SellStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2)
  {
   string sym=(symbol==NULL ? Symbol() : symbol);
   ResetLastError();
   if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed))
     {
      Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Close position by ticket                                         |
//+------------------------------------------------------------------+
bool PositionClose(const ulong ticket,const double volume=0,const int deviation=2)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   double price=0;
   color  clr=clrNONE;
   if(type==ORDER_TYPE_BUY)
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID);
      clr=clrBlue;
     }
   else
     {
      price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK);
      clr=clrRed;
     }
   double vol=(volume==0 || volume>OrderLots() ? OrderLots() : volume);
   ResetLastError();
   if(!OrderClose((int)ticket,vol,price,deviation,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Close position by an opposite one                                |
//+------------------------------------------------------------------+
bool PositionCloseBy(const ulong ticket,const ulong ticket_by)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   ResetLastError();
   if(!OrderSelect((int)ticket_by,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed"));
      return false;
     }
   ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType();
   if(type_by>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderCloseBy((int)ticket,(int)ticket_by,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Remove a pending order by ticket                                 |
//+------------------------------------------------------------------+
bool PendingOrderDelete(const ulong ticket)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted"));
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderDelete((int)ticket,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not delete order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Modify position by ticket                                        |
//+------------------------------------------------------------------+
bool PositionModify(const ulong ticket,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type>ORDER_TYPE_SELL)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,OrderOpenPrice(),sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Modify pending order by ticket                                   |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
#endif 

Essas funções são temporárias. Em breve, nós escreveremos as classes de negociação completas para a MQL5 e MQL4 e removeremos essas funções da listagem.

Agora nós precisamos adicionar a chamada das novas funções escritas nos lugares do código do EA que deixamos para chamar as funções de negociação em MQL4. Pressionamos Ctrl+F e digitamos trade na caixa de busca. Assim, nós encontraremos rapidamente as linhas de código onde as chamadas de negociação das funções em MQL4 devem ser definidas.

Implementamos a chamada das funções de negociação em MQL4 onde se faz necessário iniciar com a função PressButtonEvents() para a manipulação de eventos de pressionamento de botão até o final da listagem. O código é bem grande e a seleção da função necessária não é ambígua. Portanto, eu não exibirei o código aqui. Você pode encontrá-lo nos arquivos anexados ao artigo. Nós vamos apenas dar uma olhada como pressionar dois botões — o botão para abrir uma posição Buy e o botão para colocar uma ordem pendente BuyLimit:

//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- Convert the button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- If the button is pressed
   if(ButtonState(button_name))
     {
      //--- If the BUTT_BUY button is pressed: Open Buy position
      if(button==EnumToString(BUTT_BUY))
        {
         //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- Open Buy position
         #ifdef __MQL5__
            trade.Buy(lot,Symbol(),0,sl,tp);
         #else 
            Buy(lot,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- If the BUTT_BUY_LIMIT button is pressed: Set BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- Get the correct order placement price relative to StopLevel
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- Set BuyLimit order
         #ifdef __MQL5__
            trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
         #else 
            BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp);
         #endif 
        }
      //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop

Ao testar o código da biblioteca, eu notei algo estranho: os eventos que a MQL4 vê, sem melhorar o código, são exibidos no diário somente após algum tempo. Depois de me aprofundar no assunto, eu percebi que o motivo está no contador do temporizador da coleção funcionando no timer da CEngine. Nós definimos o atraso mínimo de 16 milissegundos para o contador do timer da coleção que nós desenvolvemos na terceira parte da descrição da biblioteca ao criar o objeto básico da biblioteca. No entanto, como nós não trabalhamos com o timer no testador e chamamos o manipulador de biblioteca OnTimer() diretamente da OnTick() para funcionar por ticks, o atraso de 16 milissegundos se transforma em um atraso de 16 ticks. Para corrigir isso, eu modifiquei levemente a classe CEngine, introduzindo o método que retorna a flag do testador e lida com o trabalho no manipulador da OnTimer() no testador, que, por sua vez, é chamado da OnTick() do EA ao trabalhar no testador.

Para realizar as alterações, foram criadas uma variável membro da classe na seção privada e o método que retorna o valor da variável:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Collection of events
   CArrayObj            m_list_counters;                 // List of timer counters
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Flag of an account trading event
   bool                 m_is_history_trade_event;        // Flag of an account history trading event
   ENUM_TRADE_EVENT     m_acc_trade_event;               // Account trading event
//--- Return counter index by id

public:
   //--- Return the list of market (1) positions, (2) pending orders and (3) market orders
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- Reset the last trading event
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
   bool                 IsTester(void)                            const { return this.m_is_tester;             }
//--- Create the timer counter
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- Timer
   void                 OnTimer(void);
//--- Constructor/destructor
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

O valor dessa variável de flag do testador é definido no construtor da classe:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
         ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
         ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   #endif 
  }
//+------------------------------------------------------------------+

Verificamos o seu funcionamento no manipulador da OnTimer() da classe CEngine, se o trabalho é realizado no testador ou não, se funciona pelo contador do timer ou pelo tick:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the collections events
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

Compilamos o EA, iniciamos ele no testador e experimentamos os botões:


As mensagens indicam que a biblioteca vê alguns eventos: definição de uma ordem pendente e a modificação dos parâmetros de ordem e posição. Ele ainda não pode ver outros eventos.

Vamos lidar com os erros.

Melhorando a biblioteca

A primeira coisa que nós devemos analisar é por que a biblioteca não vê a remoção de uma ordem pendente.
Todos os eventos são monitorados no método da classe de coleção de eventos CEventsCollection::Refresh(). Nós estamos interessados nos eventos do histórico da conta. Vamos passar para o método e dar uma olhada no código responsável pelo monitoramento de alterações na coleção do histórico de ordens e negócios da MQL5:
     }
//--- If the event is in the account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased
      if(new_history_orders>0)
        {
         //--- Receive the list of removed pending orders only
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            Print(DFUN);
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list. If this is a removed pending order without a position ID, 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- If the number of deals increased

A propriedade da ordem que especifica o ID da posição não é preenchido (igual a zero). Depois de encontrar a passagem correta, podemos ver que nós usamos esse recurso para a identificação precisa da remoção de uma ordem pendente (em vez de sua ativação) em MQL5 (em MQL5, se uma ordem foi ativada, levando a um negócio e uma posição, o ID de posição seria igual ao ID da abertura da posição como resultado da ativação da ordem). Em MQL4, esse campo é imediatamente preenchido com o ticket da ordem, o que é incorreto.
Vamos para o construtor da classe abstrata de encerramento da ordem para encontrar a linha da propriedade da ordem contendo o ID da posição:

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket)
  {
//--- Save integer properties
   this.m_ticket=ticket;
   this.m_long_prop[ORDER_PROP_STATUS]                               = order_status;
   this.m_long_prop[ORDER_PROP_MAGIC]                                = this.OrderMagicNumber();
   this.m_long_prop[ORDER_PROP_TICKET]                               = this.OrderTicket();
   this.m_long_prop[ORDER_PROP_TIME_OPEN]                            = (long)(ulong)this.OrderOpenTime();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE]                           = (long)(ulong)this.OrderCloseTime();
   this.m_long_prop[ORDER_PROP_TIME_EXP]                             = (long)(ulong)this.OrderExpiration();
   this.m_long_prop[ORDER_PROP_TYPE]                                 = this.OrderType();
   this.m_long_prop[ORDER_PROP_STATE]                                = this.OrderState();
   this.m_long_prop[ORDER_PROP_DIRECTION]                            = this.OrderTypeByDirection();
   this.m_long_prop[ORDER_PROP_POSITION_ID]                          = this.OrderPositionID();
   this.m_long_prop[ORDER_PROP_REASON]                               = this.OrderReason();
   this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET]                    = this.DealOrderTicket();
   this.m_long_prop[ORDER_PROP_DEAL_ENTRY]                           = this.DealEntry();
   this.m_long_prop[ORDER_PROP_POSITION_BY_ID]                       = this.OrderPositionByID();
   this.m_long_prop[ORDER_PROP_TIME_OPEN_MSC]                        = this.OrderOpenTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_CLOSE_MSC]                       = this.OrderCloseTimeMSC();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE]                          = (long)(ulong)this.PositionTimeUpdate();
   this.m_long_prop[ORDER_PROP_TIME_UPDATE_MSC]                      = (long)(ulong)this.PositionTimeUpdateMSC();
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)]         = this.OrderOpenPrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)]        = this.OrderClosePrice();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)]             = this.OrderProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)]         = this.OrderCommission();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)]               = this.OrderSwap();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)]             = this.OrderVolume();
   this.m_double_prop[this.IndexProp(ORDER_PROP_SL)]                 = this.OrderStopLoss();
   this.m_double_prop[this.IndexProp(ORDER_PROP_TP)]                 = this.OrderTakeProfit();
   this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)]     = this.OrderVolumeCurrent();
   this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)]   = this.OrderPriceStopLimit();
   
//--- Save string properties
   this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)]             = this.OrderSymbol();
   this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)]            = this.OrderComment();
   this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)]             = this.OrderExternalID();
   
//--- Save additional integer properties
   this.m_long_prop[ORDER_PROP_PROFIT_PT]                            = this.ProfitInPoints();
   this.m_long_prop[ORDER_PROP_TICKET_FROM]                          = this.OrderTicketFrom();
   this.m_long_prop[ORDER_PROP_TICKET_TO]                            = this.OrderTicketTo();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_SL]                          = this.OrderCloseByStopLoss();
   this.m_long_prop[ORDER_PROP_CLOSE_BY_TP]                          = this.OrderCloseByTakeProfit();
   this.m_long_prop[ORDER_PROP_GROUP_ID]                             = 0;
   
//--- Save additional real properties
   this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]        = this.ProfitFull();
  }
//+------------------------------------------------------------------+

Isso é feito pelo método OrderPositionID(). Como nós podemos ver, em MQL4, o ticket é definido diretamente como o seu ID:

//+------------------------------------------------------------------+
//| Return position ID                                               |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderTicket();
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Inicialmente, o valor 0 deve ser definido lá (sem posição aberta ao remover a ordem). É isso que nós fazemos:

//+------------------------------------------------------------------+
//| Return position ID                                               |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return 0;
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_IDENTIFIER);            break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_POSITION_ID);                 break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID);   break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

Compilamos o EA, iniciamos ele no testador e, então, definimos e removemos uma ordem pendente:


Agora, o evento de remoção de uma ordem pendente é monitorado.

Se nós esperarmos pela ativação da ordem pendente, nós veremos novamente que esse evento, assim como uma simples abertura de posição, não é visível para a biblioteca. Vamos definir os motivos.

Como nos lembramos, tudo começa a partir do manipulador da OnTimer() da classe CEngine:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- Timer of historical orders, deals, market orders and positions collections
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      if(counter!=NULL)
        {
         //--- If this is not a tester
         if(!this.IsTester())
           {
            //--- If unpaused, work with the collections events
            if(counter.IsTimeDone())
               this.TradeEventsControl();
           }
         //--- If this is a tester, work with collection events by tick
         else 
           {
            this.TradeEventsControl();
           }
        }
     }
  }
//+------------------------------------------------------------------+

De acordo com o código, os eventos são gerenciados no método TradeEventsControl(). Em caso de qualquer evento, nós chamamos o método para atualizar os eventos da classe de coleção de eventos CEventsCollection::Refresh():

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- First launch actions
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Aqui nós enviamos para o método as listas de coleções do histórico e de mercado, as flags de alterações nas coleções, o número de novas ordens do histórico, ordens de mercado ativas e posições, bem como o número de novos negócios. Mas um olhar mais atento revela que, em vez do número de novas posições de mercado, o método recebe o número de novas ordens de mercado que nós não usamos na biblioteca ainda. Este foi um erro meu. Inicialmente, tudo foi desenvolvido para a MQL5, enquanto o número de novas posições deve ser enviado para o método MQL4. Em MQL5, as novas posições são definidas pelo número de negócios. O erro ocorreu quando eu preenchi os dados passados para o método em MQL4. Agora, fica claro por que o método não consegue enxergar as novas posições de mercado.
Vamos consertar isso e resolver outros problema ao longo do caminho:
Ao contrário da MQL5, a MQL4 não possui os recursos para localizar uma ordem que levou à abertura de uma posição. No entanto, nós já temos uma lista de ordens de controle para monitorar as alterações das propriedades de ordens e posições. Nós ainda não limpamos esta lista de dados que são desnecessários. Essa lista nos ajudará a monitorar uma ordem que levou à abertura de uma posição e a identificar o evento — uma ordem à mercado ou uma ativação da ordem pendente.


Adicionamos o método público que retorna a lista de ordens de controle para a coleção de ordens e posições de mercado (classe CMarketCollection no arquivo MarketCollection.mqh):

public:
//--- Return the list (1) of all pending orders and open positions, (2) control orders and positions
   CArrayObj*        GetList(void)                                                                       { return &this.m_list_all_orders;                                       }
   CArrayObj*        GetListChanges(void)                                                                { return &this.m_list_changed;                                          }
   CArrayObj*        GetListControl(void)                                                                { return &this.m_list_control;                                          }
//--- Return the list of orders and positions with an open time from begin_time to end_time
   CArrayObj*        GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition
   CArrayObj*        GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
   CArrayObj*        GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode);  }
//--- Return the number of (1) new market orders, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume
   int               NewMarketOrders(void)                                                         const { return this.m_new_market_orders;                                      }
   int               NewPendingOrders(void)                                                        const { return this.m_new_pendings;                                           }
   int               NewPositions(void)                                                            const { return this.m_new_positions;                                          }
   bool              IsTradeEvent(void)                                                            const { return this.m_is_trade_event;                                         }
   double            ChangedVolumeValue(void)                                                      const { return this.m_change_volume_value;                                    }
//--- Constructor
                     CMarketCollection(void);
//--- Update the list of pending orders and positions
   void              Refresh(void);
  };
//+------------------------------------------------------------------+
Para usarmos os dados da lista, nós precisamos passá-lo ao método Refresh() da classe CEventsCollection.

Para isso, vamos escrever todas as alterações necessárias, que foram descritas acima:

//+------------------------------------------------------------------+
//| Check trading events                                             |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- Initialize the trading events code and flags
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- Update the lists 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- First launch actions
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- Check the changes in the market status and account history 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it
   int change_total=0;
   CArrayObj* list_changes=this.m_market.GetListChanges();
   if(list_changes!=NULL)
      change_total=list_changes.Total();
   if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,                                  
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),                                 
                            this.m_market.NewPositions(),this.m_history.NewDeals());                                     
      //--- Get the account's last trading event
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+

Aqui, no método TradeEventsControl() da classe CEngine, nós adicionamos a passagem de mais uma outra lista — a lista de ordens de controle ao método Refresh() da classe CEventsCollection e substituímos a passagem errônea de um número de novas ordens de mercado para o método com a passagem do número de novas posições.

Vamos fazer as correções na definição do método Refresh() no corpo da classe CEventsCollection:

public:
//--- Select events from the collection with time within the range from begin_time to end_time
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- Return the full event collection list "as is"
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- Update the list of events
   void              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);
//--- Set the control program chart ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- Return the last trading event on the account
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- Reset the last trading event
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- Constructor
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+

e na sua implementação fora do corpo da classe:

//+------------------------------------------------------------------+
//| 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)
  {

O método para atualizar a lista de eventos dos eventos da classe de coleção de eventos ainda está ausente ao manipular o evento de abertura de posição para a MQL4. Nós vamos precisar de alguns métodos para isso.
Para obter a lista de posições abertas, nós devemos ter o método de sua obtenção. Além disso, nós não temos o método para usar a lista de ordens de controle para definir o tipo de ordem, que levou à abertura da posição.
Nós também precisaremos de dois membros privados da classe para armazenar o tipo da ordem de abertura, encontrado na lista de ordens de controle, e o ID da posição. O tipo e o ID devem ser definidos no bloco de código para lidar com os eventos de abertura de posição de mercado para a MQL4.
Adicionamos ele à seção privada da classe:

//+------------------------------------------------------------------+
//| Collection of account events                                     |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // List of events
   bool              m_is_hedge;                      // Hedge account flag
   long              m_chart_id;                      // Control program chart ID
   int               m_trade_event_code;              // Trading event code
   ENUM_TRADE_EVENT  m_trade_event;                   // Account trading event
   CEvent            m_event_instance;                // Event object for searching by property
   MqlTick           m_tick;                          // Last tick structure
   ulong             m_position_id;                   // Position ID (MQL4)  
   ENUM_ORDER_TYPE   m_type_first;                    // Opening order type (MQL4)
   
//--- Create a trading event depending on the order (1) status and (2) change type
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
   void              CreateNewEvent(COrderControl* order);
//--- Create an event for a (1) hedging account, (2) netting account
   void              NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
   void              NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market);
//--- Select from the list and return the list of (1) market pending orders, (2) open positions
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
   CArrayObj*        GetListPositions(CArrayObj* list);
//--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID
//--- (3) all market entry deals by position ID, (4) all market exit deals by position ID,
//--- (5) all position reversal deals by position ID
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- Return the (1) first, (2) last and (3) closing order from the list of all position orders,
//--- (4) an order by ticket, (5) market position by ID,
//--- (6) the last and (7) penultimate InOut deal by position ID
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket);
   COrder*           GetPositionByID(CArrayObj* list,const ulong position_id);
//--- Return the type of the opening order by the position ticket (MQL4)
   ENUM_ORDER_TYPE   GetTypeFirst(CArrayObj* list,const ulong ticket);
//--- Return the flag of the event object presence in the event list
   bool              IsPresentEventInList(CEvent* compared_event);
//--- Existing order/position change event handler
   void              OnChangeEvent(CArrayObj* list_changes,const int index);

public:

Implementamos o método para receber a lista de posições abertas fora do corpo da classe:

//+------------------------------------------------------------------+
//| Select only market positions from the list                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListPositions(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection"));
      return NULL;
     }
   CArrayObj* list_positions=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list_positions;
  }
//+------------------------------------------------------------------+

A lista completa de ordens e posições de mercado é passada ao método e ordenada pelo estado da "posição de mercado". A lista resultante é retornada ao programa que realizou a chamada.

Vamos escrever o método que retorna o tipo da ordem, que levou a abertura de uma posição:

//+------------------------------------------------------------------+
//| Return the type of an opening order by position ticket (MQL4)    |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket)
  {
   if(list==NULL)
      return WRONG_VALUE;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      COrderControl* ctrl=list.At(i);
      if(ctrl==NULL)
         continue;
      if(ctrl.Ticket()==ticket)                   
         return (ENUM_ORDER_TYPE)ctrl.TypeOrder();
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Passamos para o método a lista de ordens de controle e o ticket de uma posição foi aberta recentemente. Em seguida, através de um loop a partir do começo da lista (assumindo que uma ordem pendente foi colocada antes de outras posições abertas, assim, o seu ticket aparece de forma mais rápida), obtemos a ordem de controle da lista e comparamos com o ticket que foi passado para a função. Se o ticket for encontrado, esta ordem serviu de abertura para a posição cujo ticket foi passado para o método — o tipo da ordem é retornado. Se nenhuma ordem com tal ticket for encontrada, é retornado -1.

Agora nós podemos melhorar o tratamento de eventos com as posições para a MQL4.

Adicionamos o tratamento de abertura de posições para a MQL4 ao método que atualiza a lista de eventos:

//+------------------------------------------------------------------+
//| 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)
  {
//--- Exit if the lists are empty
   if(list_history==NULL || list_market==NULL)
      return;
//--- If the event is in the market environment
   if(is_market_event)
     {
      //--- if the order properties were changed
      int total_changes=list_changes.Total();
      if(total_changes>0)
        {
         for(int i=total_changes-1;i>=0;i--)
           {
            this.OnChangeEvent(list_changes,i);
           }
        }
      //--- if the number of placed pending orders increased
      if(new_market_pendings>0)
        {
         //--- Receive the list of the newly placed pending orders
         CArrayObj* list=this.GetListMarketPendings(list_market);
         if(list!=NULL)
           {
            //--- Sort the new list by order placement time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_market_pendings;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list, if this is a pending order, set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      #ifdef __MQL4__
      //--- If the number of positions increased
      if(new_market_positions>0)
        {
         //--- Get the list of open positions
         CArrayObj* list=this.GetListPositions(list_market);
         if(list!=NULL)
           {
            //--- Sort the new list by a position open time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_market_positions;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event
               COrder* position=list.At(i);
               if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION)
                 {
                  //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID 
                  this.m_type_first=this.GetTypeFirst(list_control,position.Ticket());
                  this.m_position_id=position.Ticket();
                  this.CreateNewEvent(position,list_history,list_market);
                 }
              }
           }
        }
      #endif 
     }
//--- If the event is in the account history
   if(is_history_event)
     {
      //--- If the number of historical orders increased
      if(new_history_orders>0)
        {
         //--- Receive the list of removed pending orders only
         CArrayObj* list=this.GetListHistoryPendings(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by order removal time
            list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
            //--- Take the number of orders equal to the number of newly removed pending ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_history_orders;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive an order from the list. If this is a removed pending order without a position ID, 
               //--- this is an order removal - set a trading event
               COrder* order=list.At(i);
               if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
      //--- If the number of deals increased
      if(new_deals>0)
        {
         //--- Receive the list of deals only
         CArrayObj* list=this.GetListDeals(list_history);
         if(list!=NULL)
           {
            //--- Sort the new list by deal time
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events)
            int total=list.Total(), n=new_deals;
            for(int i=total-1; i>=0 && n>0; i--,n--)
              {
               //--- Receive a deal from the list and set a trading event
               COrder* order=list.At(i);
               if(order!=NULL)
                  this.CreateNewEvent(order,list_history,list_market);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Todas as ações para lidar com a abertura de uma nova posição ou o acionamento de uma ordem pendente para a MQL4 estão descritas nos comentários do código e não precisam de nenhuma explicação adicional.

Agora, vamos para o método CEventsCollection::CreateNewEvent() que cria um novo evento e encontra o bloco de código responsável ao criar um evento de abertura de posição para a MQL4 (o início do bloco é marcado nos comentários do código) e suplementar a definição do evento de abertura de posição e os motivos para a sua abertura, bem como adicionar os dados na ordem e o ID da posição correspondentes aos dados da posição em aberto:

//--- Position opened (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      //--- Set the "position opened" trading event code
      this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      //--- Set the "request executed partially" reason
      ENUM_EVENT_REASON reason=EVENT_REASON_DONE;
      //--- If an opening order is a pending one
      if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE)
        {
         //--- set the "pending order activated" reason
         reason=EVENT_REASON_ACTIVATED_PENDING;
         //--- add a pending order activation flag to the event code
         this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
        }
      CEvent* event=new CEventPositionOpen(this.m_trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                             // Event time
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                                        // Event reason (from the ENUM_EVENT_REASON enumeration)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first);                          // Event deal type
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                           // Event deal ticket
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first);                         // Type of the order that triggered an event deal (the last position order)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first);                      // Type of an order that triggered a position deal (the first position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                          // Ticket of an order, based on which a deal event is opened (the last position order)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                       // Ticket of an order, based on which a position event is opened (the first position order)
         event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id);                             // Position ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                        // Opposite position ID
         event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0);                                              // Opposite position magic number
            
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder());                      // Position order type before direction changed
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket());                       // Position order ticket before direction changed
         event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder());                     // Current position order type
         event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket());                      // Current position order ticket
      
         event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen());                        // Order price before modification
         event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss());                           // StopLoss before modification
         event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit());                         // TakeProfit before modification
         event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask);                            // Ask price during an event
         event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid);                            // Bid price during an event
         
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                                  // Order/deal/position magic number
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());                    // Time of an order, based on which a position deal is opened (the first position order)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                              // Event price
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                               // Order/deal/position open price
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                             // Order/deal/position close price
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                                  // StopLoss position price
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                                // TakeProfit position price
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume());                        // Requested order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume
         event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent());                 // Remaining (unexecuted) order volume
         event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume());                    // Executed position volume
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                      // Profit
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                      // Order symbol
         event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol());                                // Opposite position symbol
         //--- Set control program chart ID, decode the event code and set the event type
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- Add the event object if it is not in the list
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- Send a message about the event and set the value of the last trading event
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- If the event is already present in the list, remove a new event object and display a debugging message
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list."));
            delete event;
           }
        }
     }
//--- New deal (__MQL5__)

Depois de fazer todas as alterações, a biblioteca deve "ver" a abertura de posição e a ativação das ordens pendentes em MQL4.


Teste

Vamos verificar as mudanças aplicadas. Compilamos o TestDoEasyPart10.mq4, iniciamos ele no testador, abrimos e fechamos algumas posições, colocamos algumas ordens pendentes, esperamos até que uma delas seja ativada e verificamos se os níveis de stop e trailing estão ativados (modificando as posições e ordens pendentes). Todos os eventos que a biblioteca "vê" para a MQL4 devem ser exibidos no diário do testador:


Se nós observarmos atentamente o diário do testador, nós podemos ver que a biblioteca ainda não conseguiu visualizar o encerramento de posições. Quando a ordem pendente BuyLimit #3 é acionada, o diário informa que a [BuyLimit #3] está ativada, levando à posição Buy #3. Agora, a biblioteca vê os eventos de ativação da ordem pendente e conhece a ordem que originou a posição. Além disso, nós podemos ver uma leve omissão na função de modificação — o rótulo da ordem pendente BuyStop #1 modificado pelo trailing está em vermelho. Mas a biblioteca vê todos os eventos de modificação de ordem e posição.

Adicionamos as correções às funções de negociação em MQL4 ao testador no arquivo DELib.mqh. Ainda vamos criar uma outra função que retorna o tipo da posição Buy/Sell dependendo do tipo da ordem pendente passada para ele e substituir a verificação do tipo da ordem com a verificação do tipo da ordem pela direção nas linhas que selecionam a cor da seta:

//+------------------------------------------------------------------+
//| Modifying a pending order by ticket                              |
//+------------------------------------------------------------------+
bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp)
  {
   ResetLastError();
   if(!OrderSelect((int)ticket,SELECT_BY_TICKET))
     {
      Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError());
      return false;
     }
   ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType();
   if(type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP)
     {
      Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket);
      return false;
     }
   if(OrderCloseTime()>0)
     {
      Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket);
      return false;
     }
   color clr=(TypeByPendingDirection(type)==ORDER_TYPE_BUY ? clrBlue : clrRed);
   ResetLastError();
   if(!OrderModify((int)ticket,price_set,sl,tp,0,clr))
     {
      Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError());
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Return the type by a pending order direction                     |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE TypeByPendingDirection(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY_LIMIT  || type==ORDER_TYPE_BUY_STOP)  return ORDER_TYPE_BUY;
   if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP) return ORDER_TYPE_SELL;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

Qual é o próximo?

No próximo artigo, nós implementaremos o monitoramento do encerramento de posição e corrigiremos os erros que possam surgir na versão atual de monitoramento de eventos da MQL4. Atualmente, a colocação e remoção de ordens são monitoradas pelo código em MQL5, e pode haver algumas nuances que devem ser levadas em conta ao trabalhar com a MQL4.

Todos os arquivos da versão atual da biblioteca estão anexados abaixo, juntamente com os arquivos do EA de teste para você testar e fazer o download.
Deixe suas perguntas, comentários e sugestões nos comentários.

Voltar ao conteúdo

Artigos anteriores da série:

Parte 1. Conceito, gerenciamento de dados.
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ção 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.



Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/6767

Arquivos anexados |
MQL5.zip (99.2 KB)
MQL4.zip (99.2 KB)
Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte IX): Compatibilidade com a MQL4 - Preparação dos dados Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte IX): Compatibilidade com a MQL4 - Preparação dos dados

Nos artigos anteriores, nós começamos a criar uma grande biblioteca multi-plataforma, simplificando o desenvolvimento de programas para as plataformas MetaTrader 5 e MetaTrader 4. Na oitava parte, nós implementamos a classe para monitorar os eventos de modificação de ordens e posições. Aqui, nós melhoraremos a biblioteca tornando-a totalmente compatível com a MQL4.

Criando um EA gradador multiplataforma (Parte III): grade baseada em correções com martingale Criando um EA gradador multiplataforma (Parte III): grade baseada em correções com martingale

Neste artigo, tentaremos criar o melhor EA possível trabalhando com base no princípio de um gradador. Como de costume, tratar-se-á de um Expert Advisor multiplataforma capaz de funcionar tanto no MetaTrader 4 quanto no MetaTrader 5. O primeiro EA era bom para todos, exceto que ele não trazia lucro em período longo. O segundo EA podia trabalhar em intervalos de mais de alguns anos. Mas ele não era capaz de trazer mais de 50% do lucro por ano com um rebaixamento máximo de menos de 50%.

Gerenciando otimizações (Parte I): Criando uma interface gráfica do usuário Gerenciando otimizações (Parte I): Criando uma interface gráfica do usuário

Este artigo descreve um processo para criar uma extensão projetada para o terminal MetaTrader. Essa solução ajuda a automatizar o processo de otimização através de sua execução em outros terminais. Outros artigos serão escritos com base neste artigo para desenvolver este tópico. A extensão será escrita usando linguagem C# e modelos de programação, o que, além do objetivo principal deste artigo, mostrará não apenas a capacidade do terminal de expandir os recursos originalmente criados através da escrita de templates próprios, mas também como criar facilmente gráficos personalizados numa linguagem com os recursos mais convenientes para isso.

Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte XI). Compatibilidade com a MQL4 - Eventos de encerramento de posição Biblioteca para desenvolvimento fácil e rápido de programas para a MetaTrader (parte XI). Compatibilidade com a MQL4 - Eventos de encerramento de posição

Nós continuamos com o desenvolvimento de uma grande biblioteca multi-plataforma, simplificando o desenvolvimento de programas para as plataformas MetaTrader 5 e MetaTrader 4. Na décima parte, nós retomamos nosso trabalho sobre a compatibilidade da biblioteca com a MQL4 e definimos os eventos de abertura de posições e ativação de ordens pendentes. Neste artigo, nós definiremos os eventos de encerramento de posições e nos livraremos das propriedades de ordem não utilizadas.