Библиотека для простого и быстрого создания программ для MetaTrader (Часть X): Совместимость с MQL4 - События открытия позиции и активации отложенных ордеров
Содержание
Тестовый советник
В прошлой статье мы удалили ошибки в файлах библиотеки, связанные с различием MQL4 и MQL5 и сделали коллекцию исторических ордеров и позиций для MQL4. В данной статье мы продолжим слияние в библиотеке MQL4 с MQL5 и сделаем определение событий открытия позиций и активации отложенных ордеров.
Последовательность шагов по доработке у нас будет обратной. Ранее мы всегда делали функционал, а затем тестовый советник. Сейчас же, чтобы понять что необходимо доработать, нам нужно запустить тестовый советник и посмотреть где он работает, а где — нет. И то, что не работает будем приводить в порядок.
Для работы возьмём тестовый советник TestDoEasyPart08.mq5 из восьмой части описания библиотеки из папки \MQL5\Experts\TestDoEasy\Part08 и сохраним его под новым именем TestDoEasyPart10.mq4 в папке MetaTrader 4 \MQL4\Experts\TestDoEasy\Part10.
Попробуем его скомпилировать, что в итоге приведёт к 34 ошибкам компиляции. Практически все они связаны с отсутствием в стандартной библиотеке для MQL4 торговых классов:
Перейдём к первой ошибке, которая говорит нам об отсутствии подключаемого файла
и исправим — файл будем подключать только для MQL5:
//+------------------------------------------------------------------+ //| TestDoEasyPart08.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> #ifdef __MQL5__ #include <Trade\Trade.mqh> #endif //--- enums
Скомпилируем. 33 ошибки. Опять перейдём к самой первой ошибке, которая указывает нам об отсутствующем типе при объявлении объекта торгового класса CTrade — его нет в MQL4.
Делаем точно так же — используем директиву условной компиляции:
//--- global variables CEngine engine; #ifdef __MQL5__ CTrade trade; #endif SDataButt butt_data[TOTAL_BUTT];
Скомпилируем. Теперь объект trade класса CTrade стал неизвестным для MQL4, исправим точно так же:
//--- 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); }
И далее по всему коду советника просто все вхождения объектов trade заключим в директивы условной компиляции, но с использованием директивы #else — там будет размещён код для MQL4. Посмотрим на примере самой первой ошибки с неизвестным типом trade после внесения предыдущей правки и компиляции:
//--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy if(button==EnumToString(BUTT_BUY)) { //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Открываем позицию Buy #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else #endif }
После заключения всех вхождений объекта trade в директивы условной компиляции у нас появляется ещё одна ошибка, говорящая нам, что компилятор не может точно определить какой использовать вызов перегруженной функции из-за недостатка параметров:
И если поглядеть внимательно в код, то станет ясна причина замешательства компилятора:
//+------------------------------------------------------------------+ //| Возвращает флаг наличия объекта с префиксом | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
В MQL5 данная функция имеет только одну форму вызова:
int ObjectsTotal( long chart_id, // идентификатор графика int sub_window=-1, // индекс окна int type=-1 // тип объекта );
Где первый параметр — это идентификатор графика (текущего если 0)
В то время как в MQL4 с некоторых пор функция имеет две формы вызова. Первая такая же, как и в MQL5:
int ObjectsTotal( long chart_id, // идентификатор графика int sub_window=-1, // индекс окна int type=-1 // тип объекта );
и вторая — устаревшая, с одним лишь параметром:
int ObjectsTotal( int type=EMPTY // тип объекта );
В MQL5 передача в функцию в качестве идентификатора графика 0 (текущий график) никаких противоречий и сомнений не вызывает, но вот в MQL4 компилятор должен определить по переданным параметрам какой тип вызова использовать. И тут он не может точно определить то ли мы передаём идентификатор текущего графика (0) и необходимо использовать первую форму вызова (ведь у двух других параметров заданы умолчательные значения, а значит их можно не передавать в функцию), то ли мы передаём индекс окна, то ли тип объекта, и необходимо использовать вторую форму вызова.
Решение простое — передадим вторым параметром номер подокна (0 = главное окно графика):
//+------------------------------------------------------------------+ //| Возвращает флаг наличия объекта с префиксом | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
и
//+------------------------------------------------------------------+ //| Контроль состояния кнопок | //+------------------------------------------------------------------+ 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); } } //+------------------------------------------------------------------+
Теперь всё компилируется без ошибок. Прежде, чем запускать тестирование, вспомним, что все торговые функции мы попросту исключили из кода, "вырезав" их директивами условной компиляции, и в советнике нет торговых функций для MQL4. Значит, необходимо их дописать.
Помним, что код пишем для тестера, и соответственно, мы не будем делать никаких проверок, требующихся при торговле на демо/реальном счёте — ограничимся лишь минимальными проверками.
Так как в функции передаются тикеты ордеров и позиций и уже рассчитанные ценовые уровни, то всё, что необходимо сделать — это выбрать ордер/позицию по тикету, проверить тип и время закрытия. Если тип не совпадает с типом ордера или позиции, то выдать об этом сообщение и выйти из функции с ошибкой. Если ордер удалён или позиция закрыта, то так же выдаём сообщение и выходим с ошибкой. Далее вызываем функцию открытия/закрытия/модификации и возвращаем результат её выполнения.
В файле DELib.mqh в конце листинга напишем все необходимые нам тестерные функции для MQL4:
#ifdef __MQL4__ //+------------------------------------------------------------------+ //| Временные функции MQL4 для тестера | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Открытие позиции Buy | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Установка отложенного ордера BuyLimit | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Установка отложенного ордера BuyStop | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Открытие позиции Sell | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Установка отложенного ордера SellLimit | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Установка отложенного ордера SellStop | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Закрытие позиции по тикету | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Закрытие позиции встречной | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Удаление отложенного ордера по тикету | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Модификация позиции по тикету | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Модификация отложенного ордера по тикету | //+------------------------------------------------------------------+ 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 order modify. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ #endif
Функции эти будут временными — скоро напишем полноценные торговые классы для MQL5 и MQL4, и эти функции удалим из данного листинга.
Теперь нам необходимо везде, где мы оставили место в коде советника под вызов торговых функций для MQL4, дописать вызов только что написанных функций. Нажмём Ctrl+F и в поле ввода строки для поиска введём trade — таким образом мы быстро будем находить места в коде, где необходимо прописать вызовы торговых MQL4-функций.
Начиная от функции обработки нажатий кнопок PressButtonEvents() и до конца листинга введём в нужные места вызов торговых функций MQL4. Так как кода там много, и выбор нужной функции однозначен и не вызывает сомнений, то весь код здесь приводить не буду — он есть в прикреплённых к статье файлах. Посмотрим лишь на примере обработки нажатий двух кнопок — кнопки открытия позиции Buy и кнопки установки отложенного ордера BuyLimit:
//+------------------------------------------------------------------+ //| Обработка нажатий кнопок | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Преобразуем имя кнопки в её строковый идентификатор string button=StringSubstr(button_name,StringLen(prefix)); //--- Если кнопка в нажатом состоянии if(ButtonState(button_name)) { //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy if(button==EnumToString(BUTT_BUY)) { //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Открываем позицию Buy #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else Buy(lot,Symbol(),magic_number,sl,tp); #endif } //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Получаем корректную цену установки ордера относительно уровня StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Получаем корректные цены StopLoss и TakeProfit относительно уровня установки ордера с учётом StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Устанавливаем ордер BuyLimit #ifdef __MQL5__ trade.BuyLimit(lot,price_set,Symbol(),sl,tp); #else BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp); #endif } //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop
При тестировании кода библиотеки, заметил странную особенность: события, которые MQL4 видит без доработки кода, отображались в журнале не сразу, а спустя некоторое время. Стал разбираться в причинах, и понял, что всё дело в счётчике таймера коллекций, работающего в таймере CEngine. У нас стоит минимальная задержка в 16 милисекунд для счётчика таймера коллекций, созданного нами в третьей части описания библиотеки при создании базового объекта библиотеки. Но так как мы при работе в тестере не работаем с таймером, и сразу из OnTick() советника вызываем обработчик OnTimer() библиотеки и работаем по тикам, то задержка в 16 милисекунд вытекает в задержку в 16 тиков. Для исправления этой неприятной особенности, пришлось немного доработать класс CEngine, введя в него метод, возвращающий флаг работы в тестере и обработку работы в тестере в обработчике OnTimer(), который в свою очередь вызывается из OnTick() советника при работе в тестере.
Для внесения изменений была создана приватная переменная-член класса и метод, возвращающий значение этой переменной:
//+------------------------------------------------------------------+ //| Класс-основа библиотеки | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // Коллекция исторических ордеров и сделок CMarketCollection m_market; // Коллекция рыночных ордеров и сделок CEventsCollection m_events; // Коллекция событий CArrayObj m_list_counters; // Список счётчиков таймера bool m_first_start; // Флаг первого запуска bool m_is_hedge; // Флаг хедж-счёта bool m_is_tester; // Флаг работы в тестере bool m_is_market_trade_event; // Флаг торгового события на счёте bool m_is_history_trade_event; // Флаг торгового события в истории счёта ENUM_TRADE_EVENT m_acc_trade_event; // Торговое событие на счёте //--- Возвращает индекс счётчика по id
public: //--- Возвращает список рыночных (1) позиций, (2) отложенных ордеров и (3) маркет-ордеров CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- Возвращает список исторических (1) ордеров, (2) удалённых отложенных ордеров, (3) сделок, (4) всех маркет-ордеров позиции по её идентификатору CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Сбрасывает последнее торговое событие void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); } //--- Возвращает (1) последнее торговое событие, (2) флаг счёта-хедж, (3) флаг работы в тестере 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; } //--- Создаёт счётчик таймера void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Таймер void OnTimer(void); //--- Конструктор/Деструктор CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
Значение этой переменной-флага работы в тестере устанавливается в конструкторе класса:
//+------------------------------------------------------------------+ //| CEngine конструктор | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
В обработчике OnTimer() класса CEngine проверяем работу в тестере и, в зависимости от работы не в тестере или в тестере, работаем соответственно либо по счётчику таймера, либо по тику:
//+------------------------------------------------------------------+ //| CEngine таймер | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- Если это не тестер if(!this.IsTester()) { //--- Если пауза завершилась - работаем с событиями коллекций if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- Если тестер - работаем с событиями коллекций по тику else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
Скомпилируем советник, запустим его в тестере и понажимаем на кнопки:
По сообщениям в журнале видно, что библиотека наша видит некоторые события: установку отложенного ордера и модификацию параметров ордеров и позиций. Остальные события пока не видит.
Ну что ж. Будем разбираться с ошибками.
Доработка
Первое, что поглядим — почему библиотека не видит удаление отложенного ордера.Все события у нас отслеживаются в методе класса коллекции событий CEventsCollection::Refresh(). Нас интересуют события в истории счёта. Переходим к методу и смотрим код, отвечающий за отслеживание изменения в коллекции исторических ордеров и сделок MQL5:
} //--- Если событие в истории счёта if(is_history_event) { //--- Если увеличилось количество исторических ордеров if(new_history_orders>0) { //--- Получаем список только удалённых отложенных ордеров CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { Print(DFUN); //--- Сортируем новый список по времени удаления ордера list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, //--- то это удаление ордера - устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- Если увеличилось количество сделок
Находим нужное место и видим, что для точной идентификации именно удаления отложенного ордера (не срабатывания) в MQL5 мы использовали факт, что свойство ордера, указывающее на идентификатор позиции не заполнено — имеет нулевое значение (в MQL5 если бы ордер сработал и породил сделку, а за ней и позицию, то идентификатор позиции имел бы значение идентификатора открытой в результате срабатывания ордера позиции). В MQL4 у нас это поле сразу же заполняется тикетом данного ордера, что не верно.
Идём в закрытый конструктор класса абстрактного ордера и находим строку с записью свойства ордера об идентификаторе позиции:
//+------------------------------------------------------------------+ //| Закрытый параметрический конструктор | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Сохранение целочисленных свойств 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(); //--- Сохранение вещественных свойств 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(); //--- Сохранение строковых свойств 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(); //--- Сохранение дополнительных целочисленных свойств 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; //--- Сохранение дополнительных вещественных свойств this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); } //+------------------------------------------------------------------+
Это делает метод OrderPositionID(), переходим к нему и видим, что для MQL4 в идентификатор сразу записывается тикет:
//+------------------------------------------------------------------+ //| Возвращает идентификатор позиции | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
Изначально туда нужно записывать 0 (нет открытой позиции при удалении данного ордера), что мы и сделаем:
//+------------------------------------------------------------------+ //| Возвращает идентификатор позиции | //+------------------------------------------------------------------+ 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 } //+------------------------------------------------------------------+
Скомпилируем советник, запустим в тестере и установим, а затем удалим отложенный ордер:
Теперь событие удаления отложенного ордера отслеживается.
Но если дождаться срабатывания отложенного ордера, то опять увидим, что это событие, равно как и простое открытие позиции, для библиотеки не видны. Ищем причины.
Вспоминаем, что всё начинается с обработчика OnTimer() класса CEngine:
//+------------------------------------------------------------------+ //| CEngine таймер | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- Если это не тестер if(!this.IsTester()) { //--- Если пауза завершилась - работаем с событиями коллекций if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- Если тестер - работаем с событиями коллекций по тику else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
По коду видим, что контроль событий осуществляется в методе TradeEventsControl(), переходим к нему и видим, что при наличии любого события мы вызываем метод обновления событий класса-коллекции событий CEventsCollection::Refresh():
//+------------------------------------------------------------------+ //| Проверка торговых событий | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Инициализация кода и флагов торговых событий this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Обновление списков this.m_market.Refresh(); this.m_history.Refresh(); //--- Действия при первом запуске if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Проверка изменений в рыночном состоянии и в истории счёта this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий 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()); //--- Получаем последнее торговое событие на счёте this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Здесь мы отправляем в метод списки исторической и рыночной коллекций, флаги изменения в этих коллекциях, количество новых исторических ордеров и новых активных рыночных ордеров и позиций, а также количество новых сделок. Но если внимательно поглядеть, то мы увидим, что вместо количества новых рыночных позиций, в метод передаётся количество новых маркет-ордеров, которые мы нигде в библиотеке ещё не используем. Это моя ошибка — ведь создавалось всё изначально для MQL5, а количество новых позиций нужно отправлять в метод для MQL4, ведь в MQL5 новые позиции мы определяем по количеству сделок. Ну и вышло, что я ошибся, "на глазок" заполняя передаваемые данные в метод для MQL4. Теперь понятно почему метод не видит новых рыночных позиций.
Исправим. А заодно решим вот ещё какую проблему:
В MQL4, к сожалению, нет штатной возможности узнать от какого ордера была порождена позиция, как это легко можно сделать в MQL5. Но. У нас уже готов и работает список контрольных ордеров для отслеживания изменений свойств ордеров и позиций — их модификации. И этот список мы ещё нигде не очищали от ненужных данных. Вот именно он нам и поможет отслеживать ордер, на основании которого была открыта позиция, а заодно и идентифицировать событие — либо это открытие маркет-ордером, либо это срабатывание отложенного ордера.
В коллекцию рыночных ордеров и позиций (класс CMarketCollection в файле MarketCollection.mqh)
добавим публичный метод, возвращающий список контрольных ордеров:
public: //--- Возвращает список (1) всех отложенных ордеров и открытых позиций, (2) контрольных ордеров и позиций 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; } //--- Возвращает список ордеров и позиций со временем открытия в диапазоне от begin_time до end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Возвращает список ордеров и позиций по выбранному (1) double, (2) integer и (3) string свойству, удовлетворяющему сравниваемому условию 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); } //--- Возвращает количество (1) новых маркет-ордеров, (2) новых отложенных ордеров, (3) новых позиций, (4) флаг произошедшего торгового события (5) величину изменённого объёма 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; } //--- Конструктор CMarketCollection(void); //--- Обновляет список отложенных ордеров и позиций void Refresh(void); }; //+------------------------------------------------------------------+Чтобы использовать данные из этого списка, нам нужно его передать в метод Refresh() класса CEventsCollection.
Для этого допишем все необходимые и описанные выше изменения:
//+------------------------------------------------------------------+ //| Проверка торговых событий | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Инициализация кода и флагов торговых событий this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Обновление списков this.m_market.Refresh(); this.m_history.Refresh(); //--- Действия при первом запуске if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Проверка изменений в рыночном состоянии и в истории счёта this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- Если есть любое событие, отправляем списки, флаги и количество новых ордеров и сделок в коллекцию событий и обновляем коллекцию событий 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()); //--- Получаем последнее торговое событие на счёте this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Здесь: в методе TradeEventsControl() класса CEngine добавили передачу ещё одного списка — списка контрольных ордеров в метод Refresh() класса CEventsCollection, и исправили ошибочную передачу в этот метод количества новых маркет-ордеров на передачу количества новых позиций.
Внесём исправления в определение метода Refresh() в теле класса CEventsCollection:
public: //--- Выбирает события из коллекции со временем в диапазоне от begin_time до end_time CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Возвращает полный список-коллекцию событий "как есть" CArrayObj *GetList(void) { return &this.m_list_events; } //--- Возвращает список по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию 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); } //--- Обновляет список событий 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); //--- Устанавливает идентификатор графика управляющей программы void SetChartID(const long id) { this.m_chart_id=id; } //--- Возвращает последнее торговое событие на счёте ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- Сбрасывает последнее торговое событие void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Конструктор CEventsCollection(void); }; //+------------------------------------------------------------------+
и в его реализации за пределами тела класса:
//+------------------------------------------------------------------+ //| Обновляет список событий | //+------------------------------------------------------------------+ 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) {
В методе обновления списка событий класса-коллекции событий у нас ещё не написана обработка открытия позиции для MQL4. И нам потребуются для неё ещё несколько методов.
Для получения списка открытых позиций у нас не хватает метода его получения. И нет метода для определения по списку контрольных ордеров типа ордера, в результате срабатывания которого была открыта позиция.
Так же нам потребуются два приватных члена класса для хранения найденного в списке контрольных ордеров типа
открывающего ордера и идентификатора позиции, которые будут определяться
в блоке кода обработки событий открытия рыночных позиций для MQL4.
Добавим их в приватную секцию класса:
//+------------------------------------------------------------------+ //| Коллекция событий счёта | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // Список событий bool m_is_hedge; // Флаг хедж-счёта long m_chart_id; // Идентификатор графика управляющей программы int m_trade_event_code; // Код торгового события ENUM_TRADE_EVENT m_trade_event; // Торговое событие на счёте CEvent m_event_instance; // Объект-событие для поиска по свойству MqlTick m_tick; // Структура последнего тика ulong m_position_id; // Идентификатор позиции (MQL4) ENUM_ORDER_TYPE m_type_first; // Тип открывающего ордера (MQL4) //--- Создаёт торговое событие в зависимости от (1) статуса и (2) типа изменения ордера void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Создаёт событие для (1) хеджевого счёта, (2) неттингового счёта void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Выбирает из списка и возвращает список (1) рыночных отложенных ордеров, (2) открытых позиций CArrayObj* GetListMarketPendings(CArrayObj* list); CArrayObj* GetListPositions(CArrayObj* list); //--- Выбирает из списка и возвращает список исторических (1) удалённых отложенных ордеров, (2) сделок, (3) всех закрывающих ордеров CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Возвращает список (1) всех ордеров позиции по её идентификатору, (2) всех сделок позиции по её идентификатору //--- (3) всех сделок на вход в рынок по идентификатору позиции, (4) всех сделок на выход из рынка по идентификатору позиции, //--- (5) всех сделок на разворот позиции по идентификатору позиции 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); //--- Возвращает суммарный объём всех сделок (1) IN, (2) OUT позиции по её идентификатору double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Возвращает (1) первый, (2) последний и (3) закрывающий ордер из списка всех ордеров позиции, //--- (4) ордер по тикету, (5) рыночную позицию по идентификатору, //--- (6) последнюю и (7) предпоследнюю сделку InOut по идентификатору позиции 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); //--- Возвращает тип открывающего ордера по тикету позиции (MQL4) ENUM_ORDER_TYPE GetTypeFirst(CArrayObj* list,const ulong ticket); //--- Возвращает флаг наличия объекта-события в списке событий bool IsPresentEventInList(CEvent* compared_event); //--- Обработчик события изменения существующего ордера/позиции void OnChangeEvent(CArrayObj* list_changes,const int index); public:
Напишем за пределами тела класса реализацию метода получения списка открытых позиций:
//+------------------------------------------------------------------+ //| Выбирает из списка только рыночные позиции | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
В метод передаётся полный список рыночных ордеров и позиций, затем фильтруется по статусу "рыночная позиция" и полученный в результате фильтрации список возвращается в вызывающую программу.
Напишем метод, возвращающий тип ордера, в результате срабатывания которого была открыта позиция:
//+------------------------------------------------------------------+ //| Возвращает тип открывающего ордера по тикету позиции (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; } //+------------------------------------------------------------------+
В метод передаётся список контрольных ордеров и тикет только что открытой позиции. Затем в цикле от начала списка (в расчёте, что отложенный ордер был выставлен ранее, чем другие открытые позиции, и его тикет попадётся быстрее) получаем из списка контрольный ордер и сравниваем его тикет с переданным в функцию. Если тикет нашли, то этот ордер и является открывающим для позиции, тикет которой передан в метод — возвращаем тип этого ордера. Если ордера с таким тикетом не найдено, возвращаем -1.
Теперь можно дописать обработку событий с позициями для MQL4.
Добавим в метод обновления списка событий обработку открытия позиции для MQL4:
//+------------------------------------------------------------------+ //| Обновляет список событий | //+------------------------------------------------------------------+ 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) { //--- Если списки пустые - выход if(list_history==NULL || list_market==NULL) return; //--- Если событие в рыночном окружении if(is_market_event) { //--- если было изменение свойств ордера 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(new_market_pendings>0) { //--- Получаем список только установленных отложенных ордеров CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Сортируем новый список по времени установки ордера list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Берём в цикле с конца списка количество ордеров, равное количеству новых установленных ордеров (последние N событий) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем ордер из списка, и если это отложенный ордер - устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } #ifdef __MQL4__ //--- Если увеличилось количество позиций if(new_market_positions>0) { //--- Получаем список открытых позиций CArrayObj* list=this.GetListPositions(list_market); if(list!=NULL) { //--- Сортируем новый список по времени открытия позиции list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Берём в цикле с конца списка количество позиций, равное количеству новых открытых позиций (последние N событий) int total=list.Total(), n=new_market_positions; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем позицию из списка, и если это позиция - ищем данные открывающего ордера и устанавливаем торговое событие COrder* position=list.At(i); if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION) { //--- Находим ордер и устанавливаем (1) тип ордера, на основании которого была открыта позиция и (2) идентификатор позиции 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(is_history_event) { //--- Если увеличилось количество исторических ордеров if(new_history_orders>0) { //--- Получаем список только удалённых отложенных ордеров CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Сортируем новый список по времени удаления ордера list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Берём в цикле с конца списка количество ордеров, равное количеству новых удалённых отложенных ордеров (последние N событий) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем ордер из списка, и если это удалённый отложенный ордер и у него нет идентификатора позиции, //--- то это удаление ордера - устанавливаем торговое событие 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(new_deals>0) { //--- Получаем список только сделок CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Сортируем новый список по времени совершения сделки list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Берём в цикле с конца списка количество сделок, равное количеству новых сделок (последние N событий) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Получаем сделку из списка и устанавливаем торговое событие COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
Все действия по обработке открытия новой позиции, или срабатыванию отложенного ордера для MQL4 подписаны в комментариях к коду и, надеюсь, в пояснениях не нуждаются.
Теперь перейдём к методу создания нового события CEventsCollection::CreateNewEvent() и найдём блок кода, отвечающий за создание события открытия позиции для MQL4 (начало этого блока прописано в комментариях к коду) и допишем определение события открытия позиции, и причины её открытия, а также впишем в данные открытой позиции данные открывшего её ордера и идентификатор позиции:
//--- Открыта позиция (__MQL4__) if(status==ORDER_STATUS_MARKET_POSITION) { //--- Установим код торгового события "позиция открыта" this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED; //--- Установим причину "запрос исполнен полностью"" ENUM_EVENT_REASON reason=EVENT_REASON_DONE; //--- Если открывающий ордер - отложенный if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE) { //--- установим причину "срабатывание отложенного ордера" reason=EVENT_REASON_ACTIVATED_PENDING; //--- добавим к коду события флаг активации отложенного ордера 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.SetProperty(EVENT_PROP_REASON_EVENT,reason); // Причина события (из перечисления ENUM_EVENT_REASON) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first); // Тип сделки события event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Тикет сделки события event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first); // Тип ордера, на основании которого открыта сделка события (последний ордер позиции) event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first); // Тип ордера, на основании которого открыта сделка позиции (первый ордер позиции) event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Тикет ордера, на основании которого открыта сделка события (последний ордер позиции) event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Тикет ордера, на основании которого открыта сделка позиции (первый ордер позиции) event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id); // Идентификатор позиции event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID()); // Идентификатор встречной позиции event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Магический номер встречной позиции event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder()); // Тип ордера позиции до смены направления event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Тикет ордера позиции до смены направления event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Тип ордера текущей позиции event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Тикет ордера текущей позиции event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen()); // Цена установки ордера до модификации event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss()); // Цена StopLoss до модификации event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit()); // Цена TakeProfit до модификации event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask); // Цена Ask в момент события event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid); // Цена Bid в момент события event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Магический номер ордера/сделки/позиции event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC()); // Время ордера, на основании которого открыта сделка позиции (первый ордер позиции) event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen()); // Цена, на которой произошло событие event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen()); // Цена открытия ордера/сделки/позиции event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose()); // Цена закрытия ордера/сделки/позиции event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // Цена StopLoss позиции event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // Цена TakeProfit позиции event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Запрашиваемый объём ордера event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Исполненный объём ордера event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent()); // Оставшийся (неисполненный) объём ордера event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume()); // Исполненный объём позиции event.SetProperty(EVENT_PROP_PROFIT,order.Profit()); // Профит event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Символ ордера event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Символ встречной позиции //--- Установка идентификатора графика управляющей программы и расшифровка кода события и установка типа события event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Если объекта-события нет в списке - добавляем if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Отправляем сообщение о событии и устанавливаем значение последнего торгового события event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- Если это событие уже есть в списке - удаляем новый объект-событие и выводим отладочное сообщение else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list.")); delete event; } } } //--- Новая сделка (__MQL5__)
После внесения всех этих изменений, библиотека должна "увидеть" открытие позиций и активацию отложенных ордеров на MQL4.
Тестирование
Проверим внесённые изменения. Скомпилируем советник TestDoEasyPart10.mq4, запустим его в тестере, откроем-закроем позиции, установим отложенные ордера и дождёмся активации одного из них, а также проверим выставление стоп-уровней и работу тралов (модификация позиций и отложенных ордеров). Все события, которые библиотека уже "видит" для MQL4, будут отображены в журнале тестера:
Если внимательно понаблюдать за журналом тестера, то видно, что закрытие позиций библиотека ещё не видит. Но при срабатывании отложенного ордера BuyLimit #3 в журнале видим запись о том, что активирован отложенный ордер [BuyLimit #3], породив позицию Buy #3 — события активации отложенных ордеров теперь библиотека видит, и к тому же знает от какого ордера произошла позиция. Так же можно заметить незначительное упущение в функции модификации — цвет значка модифицированного трейлингом отложенного ордера BuyStop #1 меняется на красный. Но все события модификации ордеров и позиций библиотека видит.
Добавим исправления в торговые MQL4-функции для тестера в файле DELib.mqh. Просто сделаем ещё одну функцию, которая будет возвращать тип позиции Buy или Sell в зависимости от типа отложенного ордера, переданного в неё, и заменим в строках выбора цвета стрелки проверку типа ордера на проверку типа ордера по направлению:
//+------------------------------------------------------------------+ //| Модификация отложенного ордера по тикету | //+------------------------------------------------------------------+ 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 order modify. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Возвращает тип по направлению отложенного ордера | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
Что дальше
В следующей статье сделаем отслеживание закрытия позиций и исправим ошибки, которые могут возникать в текущей реализации отслеживания событий для MQL4 — ведь установка и удаление ордеров у нас отслеживается кодом для MQL5, а там могут возникать некоторые нюансы, которые необходимо учитывать при работе под MQL4.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового
советника. Их можно скачать и протестировать всё самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
Статьи этой серии:
Часть 1. Концепция, организация данных.
Часть 2. Коллекция исторических ордеров и сделок.
Часть 3. Коллекция рыночных ордеров и позиций, организация поиска.
Часть 4. Торговые события. Концепция.
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу.
Часть 6. События на счёте с типом неттинг.
Часть 7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций.
Часть 8. События модификации ордеров и позиций.
Часть 9. Совместимость с MQL4 - Подготовка данных.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Я в другую строку пишу.
Спасибо Вам, Артем, за такой замечательный и познавательный цикл статей
Пожалуйста, Алексей. Мы ж вродь на "Ты" давно...
Всем спасибо за ответы. Я прекрасно понимаю предназначение этого эксперта. Мне просто понравилась панель. А свой вопрос я кстати решил путем сортировки листов позиций и ордеров по маджику. Честно просто лень было сразу вникать в код.
Ну вот и хорошо.
Действительно, уже сейчас можно получать все списки, а из них любой объект. И списки можно сортировать как нужно и фильтровать по любому из значений. Для этого используется CSelect() для полученного списка - примеры есть в методах классов библиотеки.
В итоге будет организован простой доступ, выбор и фильтрация. Пока только идёт создание необходимой базы данных.
Задумка хорошая. Но по моему она уже реализована fxsaber'ом.
Вы можете пользоваться работами fxsaber