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
Artyom Trishkin | 22 agosto, 2019
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 #else — o 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.
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.