Библиотека для простого и быстрого создания программ для MetaTrader (Часть XXXIV): Отложенные торговые запросы - удаление ордеров, модификация ордеров и позиций по условиям
Содержание
Концепция
В данной статье мы завершим раздел, посвящённый торговле при помощи отложенных торговых запросов — создадим функционал для удаления отложенных ордеров, а также для модификации уровней StopLoss и TakeProfit позиций и параметров отложенных ордеров.
Таким образом у нас будет в наличии весь функционал, при помощи которого можно будет впоследствии создавать несложные пользовательские стратегии, а вернее — некоторую логику поведения советника при наступлении заданных пользователем условий. При наличии графической оболочки это может дать инструментарий, например, для создания визуального конструктора поведения советника прямо из самого советника во время его работы (вполне может быть, что мы и сделаем небольшой пример в будущем — когда будет создана графическая оболочка библиотеки).
В данный момент у нас уже есть возможность создать дополнительные типы отложенных ордеров. Например, для MQL4 можно создать
StopLimit-ордер. Но его созданием, вернее — созданием объекта-StopLimit-ордера для MQL4, я планирую заняться при наличии достаточного
функционала библиотеки.
В итоге можно будет создать совсем новые типы отложенных ордеров, таких как, например, BuyTime, SellTime,
BuyTimeStop, SellTimeStop, и т.д. ордеры.
Для создания полноценных пользовательских ордеров пока не хватает некоторых
графических построений. И, соответственно, мы вернёмся к этой теме уже при наличии такого функционала в библиотеке.
Реализация
Для вывода описаний отложенных ордеров у нас, как оказалось, нет функции, которая просто выведет его название. Но есть функция
OrderTypeDescription(), выводящая его описание+название. Нужно просто из результата возврата данной функции убрать текст описания,
оставив только название ордера.
В файле сервисных функций \MQL5\Include\DoEasy\Services\DELib.mqh доработаем
функцию, возвращающую наименование ордера:
//+------------------------------------------------------------------+ //| Возвращает наименование ордера | //+------------------------------------------------------------------+ string OrderTypeDescription(const ENUM_ORDER_TYPE type,bool as_order=true,bool prefix_for_market_order=true,bool descr=true) { string pref= ( !prefix_for_market_order ? "" : #ifdef __MQL5__ CMessage::Text(MSG_ORD_MARKET) #else/*__MQL4__*/(as_order ? CMessage::Text(MSG_ORD_MARKET) : CMessage::Text(MSG_ORD_POSITION)) #endif ); return ( type==ORDER_TYPE_BUY_LIMIT ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Buy Limit" : type==ORDER_TYPE_BUY_STOP ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Buy Stop" : type==ORDER_TYPE_SELL_LIMIT ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Sell Limit" : type==ORDER_TYPE_SELL_STOP ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Sell Stop" : #ifdef __MQL5__ type==ORDER_TYPE_BUY_STOP_LIMIT ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Buy Stop Limit" : type==ORDER_TYPE_SELL_STOP_LIMIT ? (descr ? CMessage::Text(MSG_ORD_PENDING) : "")+" Sell Stop Limit" : type==ORDER_TYPE_CLOSE_BY ? CMessage::Text(MSG_ORD_CLOSE_BY) : #else type==ORDER_TYPE_BALANCE ? CMessage::Text(MSG_LIB_PROP_BALANCE) : type==ORDER_TYPE_CREDIT ? CMessage::Text(MSG_LIB_PROP_CREDIT) : #endif type==ORDER_TYPE_BUY ? pref+" Buy" : type==ORDER_TYPE_SELL ? pref+" Sell" : CMessage::Text(MSG_ORD_UNKNOWN_TYPE) ); } //+------------------------------------------------------------------+
Функция всегда возвращает перед типом отложенного ордера текст
"отложенный ордер".
Чтобы функция могла выводить просто описание типа ордера без этого предварительного текста, добавлен
флаг, указывающий на необходимость вывода текста
"отложенный ордер". Таким образом, если флаг снят (значение равно false), то
предварительный текст "отложенный ордер" выводиться не будет.
Далее.
Во всех файлах классов объектов-наследников базового отложенного запроса, в их методах вывода краткого наименования
запроса подправим вывод краткого описания отложенного запроса — добавим к тексту описания запроса тип
ордера/позиции, после него добавим тикет (если он
доступен в классе), а далее — через запятую — идентификатор отложенного запроса.
Сейчас там сделано не очень удачно: сначала выводится описание запроса, затем идентификатор и далее через запятую тикет.
Изменения в классе объекта-отложенного запроса на открытие позиции:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqOpen::Header(void) { string type=PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE)); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_OPEN)+" "+type+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Изменения в классе объекта-отложенного запроса на закрытие позиции:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqClose::Header(void) { string type=PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE)); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_CLOSE)+" "+type+" #"+(string)this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Изменения в классе объекта-отложенного запроса на изменения уровней StopLoss и/или TakeProfit позиции:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqSLTP::Header(void) { string type=PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE)); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_SLTP)+" "+type+" #"+(string)this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Изменения в классе объекта-отложенного запроса на установку отложенного ордера:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqPlace::Header(void) { string type=OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE),true,false,false); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_PLACE)+type+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Изменения в классе объекта-отложенного запроса на удаление отложенного ордера:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqRemove::Header(void) { string type=OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE),true,false,false); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_REMOVE)+type+" #"+(string)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER)+", ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
Изменения в классе объекта-отложенного запроса на модификацию свойств отложенного ордера:
//+------------------------------------------------------------------+ //| Возвращает краткое наименование запроса | //+------------------------------------------------------------------+ string CPendReqModify::Header(void) { string type=OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE),true,false,false); return CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_MODIFY)+type+", #"+(string)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER)+" ID #"+(string)this.GetProperty(PEND_REQ_PROP_ID); } //+------------------------------------------------------------------+
В файле TradingControl.mqh класса управления торговлей CTradingControl, в его публичной секции впишем
объявление методов создания отложенного запроса на удаление отложенного
ордера, на модификацию StopLoss/TakeProfit позиции и на
модификацию параметров отложенного ордера:
//--- Создаёт отложенный запрос (1) на полное и частичное закрытие позиции, (2) на закрытие позиции встречной, (3) на удаление ордера int CreatePReqClose(const ulong ticket,const double volume=WRONG_VALUE,const string comment=NULL,const ulong deviation=ULONG_MAX); int CreatePReqCloseBy(const ulong ticket,const ulong ticket_by); int CreatePreqDelete(const ulong ticket); //--- Создаёт отложенный запрос на модификацию (1) стоп-приказов позиции, (2) ордера template<typename SL,typename TP> int CreatePReqModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE); template<typename PS,typename PL,typename SL,typename TP> int CreatePReqModifyOrder(const ulong ticket, const PS price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL limit=WRONG_VALUE, datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); //--- Устанавливает критерии активации отложенного запроса bool SetNewActivationProperties(const uchar id, const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value); }; //+------------------------------------------------------------------+
И за пределами тела класса напишем их реализацию:
//+------------------------------------------------------------------+ //| Создаёт отложенный запрос на удаление отложенного ордера | //+------------------------------------------------------------------+ int CTradingControl::CreatePreqDelete(const ulong ticket) { //--- Если установлен флаг глобального запрета торговли - выходим с возвратом WRONG_VALUE if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return WRONG_VALUE; } //--- Устанавливаем флаг ошибки как "нет ошибок" this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_CLOSE; //--- Получаем объект-ордер по тикету COrder *order=this.GetOrderObjByTicket(ticket); if(order==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ)); return WRONG_VALUE; } ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)order.TypeOrder(); //--- Получаем объект-символ по тикету ордера CSymbol *symbol_obj=this.GetSymbolObjByOrder(ticket,DFUN); if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return WRONG_VALUE; } //--- получаем торговый объект из объекта-символа CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return WRONG_VALUE; } //--- Обновляем котировки по символу if(!symbol_obj.RefreshRates()) { trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); this.AddErrorCodeToList(10021); // Отсутствуют котировки для обработки запроса if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); return WRONG_VALUE; } //--- Ищем наименьший из возможных идентификаторов, и если найти не удалось - возвращаем WRONG_VALUE int id=this.GetFreeID(); if(id<1) { //--- Нет свободных идентификаторов для создания отложенного запроса if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS)); return WRONG_VALUE; } //--- Устанавливаем тип торговой операции, символ и тикет удаляемого ордера в структуру запроса this.m_request.action=TRADE_ACTION_REMOVE; this.m_request.symbol=symbol_obj.Name(); this.m_request.order=ticket; this.m_request.type=order_type; this.m_request.volume=order.Volume(); this.m_request.price=order.PriceOpen(); //--- В результате создания отложенного торгового запроса возвращаем либо его идентификатор, либо -1 при неудаче if(this.CreatePendingRequest(PEND_REQ_STATUS_REMOVE,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,order)) return id; return WRONG_VALUE; } //+------------------------------------------------------------------+ //| Создаёт отложенный запрос на модификацию стоп-приказов позиции | //+------------------------------------------------------------------+ template<typename SL,typename TP> int CTradingControl::CreatePReqModifyPosition(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE) { //--- Если установлен флаг глобального запрета торговли - выходим с возвратом WRONG_VALUE if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return WRONG_VALUE; } //--- Устанавливаем флаг ошибки как "нет ошибок" this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_MODIFY; //--- Получаем объект-ордер по тикету COrder *order=this.GetOrderObjByTicket(ticket); if(order==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ)); return WRONG_VALUE; } ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)order.TypeOrder(); //--- Получаем объект-символ по тикету позиции CSymbol *symbol_obj=this.GetSymbolObjByPosition(ticket,DFUN); if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return WRONG_VALUE; } //--- Получаем торговый объект из объекта-символа CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return WRONG_VALUE; } //--- Устанавливаем цены //--- Если установить не удалось - записываем флаг "внутренняя ошибка" устанавливаем код ошибки в структуру возврата, //--- выводим сообщение в журнал и возвращаем false if(!this.SetPrices(order_type,0,(sl==WRONG_VALUE ? order.StopLoss() : sl),(tp==WRONG_VALUE ? order.TakeProfit() : tp),0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // Отсутствуют котировки для обработки запроса return WRONG_VALUE; } //--- Ищем наименьший из возможных идентификаторов, и если найти не удалось - возвращаем false int id=this.GetFreeID(); if(id<1) return WRONG_VALUE; //--- Записываем тип проводимой операции, символ и тикет модифицируемой позиции в структуру запроса this.m_request.action=TRADE_ACTION_SLTP; this.m_request.symbol=symbol_obj.Name(); this.m_request.position=ticket; this.m_request.type=order_type; this.m_request.volume=order.Volume(); //--- В результате создания отложенного торгового запроса возвращаем либо его идентификатор, либо -1 при неудаче if(this.CreatePendingRequest(PEND_REQ_STATUS_SLTP,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,order)) return id; return WRONG_VALUE; } //+------------------------------------------------------------------+ //| Создаёт отложенный запрос на модификацию отложенного ордера | //+------------------------------------------------------------------+ template<typename PS,typename PL,typename SL,typename TP> int CTradingControl::CreatePReqModifyOrder(const ulong ticket, const PS price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL limit=WRONG_VALUE, datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { //--- Если установлен флаг глобального запрета торговли - выходим с возвратом WRONG_VALUE if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return WRONG_VALUE; } //--- Устанавливаем флаг ошибки как "нет ошибок" this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_MODIFY; //--- Получаем объект-ордер по тикету COrder *order=this.GetOrderObjByTicket(ticket); if(order==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_ORD_OBJ)); return false; } ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)order.TypeOrder(); //--- Получаем объект-символ по тикету ордера CSymbol *symbol_obj=this.GetSymbolObjByOrder(ticket,DFUN); if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- получаем торговый объект из объекта-символа CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Устанавливаем цены //--- Если установить не удалось - записываем флаг "внутренняя ошибка" устанавливаем код ошибки в структуру возврата, //--- выводим сообщение в журнал и возвращаем false if(!this.SetPrices(order_type, (price>0 ? price : order.PriceOpen()), (sl>0 ? sl : sl<0 ? order.StopLoss() : 0), (tp>0 ? tp : tp<0 ? order.TakeProfit() : 0), (limit>0 ? limit : order.PriceStopLimit()), DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // Отсутствуют котировки для обработки запроса return false; } //--- Ищем наименьший из возможных идентификаторов, и если найти не удалось - возвращаем false int id=this.GetFreeID(); if(id<1) return WRONG_VALUE; //--- Записываем магик, объём, тип заливки, дату и тип экспирации в структуру запроса this.m_request.magic=order.GetMagicID((uint)order.Magic()); this.m_request.volume=order.Volume(); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : order.TypeFilling()); this.m_request.expiration=(expiration>WRONG_VALUE ? expiration : order.TimeExpiration()); this.m_request.type_time=(type_time>WRONG_VALUE ? type_time : order.TypeTime()); //--- Устанавливаем тип торговой операции, символ и тикет модифицируемого ордера в структуру запроса this.m_request.action=TRADE_ACTION_MODIFY; this.m_request.symbol=symbol_obj.Name(); this.m_request.order=ticket; this.m_request.type=order_type; //--- В результате создания отложенного торгового запроса возвращаем либо его идентификатор, либо -1 при неудаче if(this.CreatePendingRequest(PEND_REQ_STATUS_MODIFY,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,order)) return id; return WRONG_VALUE; } //+------------------------------------------------------------------+
Логика методов абсолютно идентична ранее созданным нами методам создания отложенных запросов на открытие/закрытие позиций/установку
отложенных ордеров по условиям, в коде всё прокомментировано, и мы не будем останавливаться на повторном разборе устройства этих
методов.
Теперь добавим доступ из программы к созданным методам. Для этого пропишем вызов этих методов из методов класса основного объекта библиотеки.
Впишем в класс CEngine объявление методов создания отложенных запросов на
удаление отложенного ордера, на модификацию StopLoss/TakeProfit
позиции и на модификацию параметров отложенного ордера:
//--- Создаёт отложенный запрос на закрытие позиции (1) полностью, (2) частично, (3) встречной, (4) на удаление ордера int ClosePositionPending(const ulong ticket,const string comment=NULL,const ulong deviation=ULONG_MAX); int ClosePositionPartiallyPending(const ulong ticket,const double volume,const string comment=NULL,const ulong deviation=ULONG_MAX); int ClosePositionByPending(const ulong ticket,const ulong ticket_by); int DeleteOrderPending(const ulong ticket); //--- Создаёт отложенный запрос на модификацию (1 позиции), (2) ордера template<typename SL,typename TP> int ModifyPositionPending(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE,const string comment=NULL); template<typename PR,typename SL,typename TP,typename PL> int ModifyOrderPending(const ulong ticket, const PR price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL stoplimit=WRONG_VALUE, datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE);
За пределами тела класса напишем реализацию объявленных методов:
//+------------------------------------------------------------------+ //| Создаёт отложенный запрос на удаление отложенного ордера | //+------------------------------------------------------------------+ int CEngine::DeleteOrderPending(const ulong ticket) { return this.m_trading.CreatePreqDelete(ticket); } //+------------------------------------------------------------------+ //| Создаёт отложенный запрос на модификацию позиции | //+------------------------------------------------------------------+ template<typename SL,typename TP> int CEngine::ModifyPositionPending(const ulong ticket,const SL sl=WRONG_VALUE,const TP tp=WRONG_VALUE,const string comment=NULL) { return this.m_trading.CreatePReqModifyPosition(ticket,sl,tp); } //+------------------------------------------------------------------+ //| Создаёт отложенный запрос на модификацию ордера | //+------------------------------------------------------------------+ template<typename PR,typename SL,typename TP,typename PL> int CEngine::ModifyOrderPending(const ulong ticket, const PR price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL stoplimit=WRONG_VALUE, datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { return this.m_trading.CreatePReqModifyOrder(ticket,price,sl,tp,stoplimit,expiration,type_time,type_filling); } //+------------------------------------------------------------------+
Методы просто возвращают результат вызова соответствующих методов создания отложенных запросов класса CTradingControl, написанных нами
выше.
И это все доработки, требуемые для добавления в библиотеку функционала работы с отложенными торговыми запросами для удаления
ордеров и модификации ордеров и позиций.
Конечно же, дополнительно были внесены в коды дорабатываемых классов
малозначительные изменения (имена шаблонных параметров), но они касаются только визуального восприятия кода методов, поэтому такие
изменения здесь не рассматриваем.
Тестирование
Для тестирования созданного функционала возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part34\ под новым именем TestDoEasyPart34.mq5.
Для этого советника мы так же, как и для прошлых советников по тестированию работы с отложенными запросами, создадим кнопки активизации режимов работы при помощи отложенных запросов — для кнопок удаления всех отложенных ордеров (Delete pending), закрытия всех позиций (Close all), и кнопок установки StopLoss и TakeProfit ордерам и позициям, не имеющих этих стоп-уровней (Set StopLoss и Set TakeProfit).
Так как нажатие этих кнопок приводит к пакетной обработке всех существующих ордеров и позиций, то включение соответствующих кнопок
активации работы при помощи отложенных запросов позволит нам проверить работу отложенных запросов сразу для множества ордеров и
позиций.
Кнопки управления торговлей торговой панели тестового советника в текущей реализации расположены не очень удобно — между кнопками удаления ордеров и закрытия позиций и кнопками установки стоп-приказов расположена кнопка снятия заработанных средств. Переместим эту кнопку ниже — после кнопки Set TakeProfit. Для этого достаточно просто поменять расположение констант в перечислении всех кнопок торговой панели тестового советника:
//+------------------------------------------------------------------+ //| TestDoEasyPart34.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_PROFIT_WITHDRAWAL, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20)
В список глобальных переменных добавим флаги состояний кнопок активации режимов работы отложенными запросами на удаление отложенных ордеров, закрытие позиций и модификации стоп-уровней ордеров и позиций, а также добавим две переменные для хранения значений Point и Digits текущего символа:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint distance_pending_request; uint bars_delay_pending_request; uint slippage; bool trailing_on; bool pressed_pending_buy; bool pressed_pending_buy_limit; bool pressed_pending_buy_stop; bool pressed_pending_buy_stoplimit; bool pressed_pending_close_buy; bool pressed_pending_close_buy2; bool pressed_pending_close_buy_by_sell; bool pressed_pending_sell; bool pressed_pending_sell_limit; bool pressed_pending_sell_stop; bool pressed_pending_sell_stoplimit; bool pressed_pending_close_sell; bool pressed_pending_close_sell2; bool pressed_pending_close_sell_by_buy; bool pressed_pending_delete_all; bool pressed_pending_close_all; bool pressed_pending_sl; bool pressed_pending_tp; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; double g_point; int g_digits; //+------------------------------------------------------------------+
В обработчике OnInit() советника присвоим значения Point и Digits текущего символа соответствующим переменным:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Вызов данной функции выводит в журнал список констант перечисления, //--- заданного в файле DELib.mqh в строках 22 и 25, для проверки корректности констант //EnumNumbersTest(); //--- Установка глобальных переменных советника prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq); bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq); g_point=SymbolInfoDouble(NULL,SYMBOL_POINT); g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS); //--- Инициализация случайных номеров групп group1=0; group2=0; srand(GetTickCount()); //--- Инициализация библиотеки DoEasy OnInitDoEasy(); //--- Проверка и удаление неудалённых графических объектов советника if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Создание панели кнопок if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Установка состояния кнопки активизации трейлингов ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Сброс состояний кнопок активизации работы отложенными запросами for(int i=0;i<14;i++) { ButtonState(butt_data[i].name+"_PRICE",false); ButtonState(butt_data[i].name+"_TIME",false); } //--- Проверка воспроизведения стандартного звука по макроподстановке и пользовательского звука по описанию engine.PlaySoundByDescription(SND_OK); Sleep(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","The sound of a falling coin 2")); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
В функции создания кнопок подкорректируем код для создания дополнительных кнопок активации режима работы с отложенными запросами:
//+------------------------------------------------------------------+ //| Создаёт панель кнопок | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=20,const int shift_y=0) { int h=18,w=82,offset=2,wpt=14; int cx=offset+shift_x+wpt*2+2,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-6) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-6 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } h=18; offset=2; cx=offset+shift_x; cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; x=cx; y=cy; shift=0; for(int i=0;i<18;i++) { y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name+"_PRICE",((i>6 && i<14) || i>17 ? x+wpt*2+w*2+5 : x),y,wpt,h,"P",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"P\""); return false; } if(!ButtonCreate(butt_data[i].name+"_TIME",((i>6 && i<14) || i>17 ? x+wpt*2+w*2+5+wpt+1 : x+wpt+1),y,wpt,h,"T",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"T\""); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+
Все изменения коснулись только контроля порядкового номера создаваемой кнопки для установки нужных координат.
Основные изменения в советнике коснулись функции обработки нажатий кнопок торговой панели советника.
Допишем коды
обработки нажатых кнопок торговой панели:
//+------------------------------------------------------------------+ //| Обработка нажатий кнопок | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Временная переменная для выбора использования составного магика со случайными идентификаторами групп string comment=""; //--- Преобразуем имя кнопки в её строковый идентификатор string button=StringSubstr(button_name,StringLen(prefix)); //--- Случайные номера групп 1 и 2 в диапазоне 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- Если кнопка в нажатом состоянии if(ButtonState(button_name)) { //--- Если нажата кнопка BUTT_BUY: Открыть позицию Buy if(button==EnumToString(BUTT_BUY)) { //--- Если не нажаты кнопки создания отложенного запроса - открываем позицию Buy if(!pressed_pending_buy) engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); // Нет комментария - будет установлен комментарий по умолчанию //--- Иначе - создаём отложенный запрос на открытие позиции Buy else { int id=engine.OpenBuyPending(lot,Symbol(),magic,stoploss,takeprofit); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY,EQUAL_OR_LESS,ask,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_BUY_LIMIT: Выставить BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyLimit if(!pressed_pending_buy_limit) engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending order BuyLimit")); //--- Иначе - создаём отложенный запрос на установку ордера BuyLimit со значением дистанции установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceBuyLimitPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_LIMIT,EQUAL_OR_LESS,ask,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_BUY_STOP: Выставить BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyStop if(!pressed_pending_buy_stop) engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending order BuyStop")); //--- Иначе - создаём отложенный запрос на установку ордера BuyStop со значением дистанции установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceBuyStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_STOP,EQUAL_OR_LESS,ask,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_BUY_STOP_LIMIT: Выставить BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер BuyStopLimit if(!pressed_pending_buy_stoplimit) engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit")); //--- Иначе - создаём отложенный запрос на установку ордера BuyStopLimit со значениями дистанций установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceBuyStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_BUY_STOP_LIMIT,EQUAL_OR_LESS,ask,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_SELL: Открыть позицию Sell else if(button==EnumToString(BUTT_SELL)) { //--- Если не нажаты кнопки создания отложенного запроса - открываем позицию Sell if(!pressed_pending_sell) engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit); // Нет комментария - будет установлен комментарий по умолчанию //--- Иначе - создаём отложенный запрос на открытие позиции Sell else { int id=engine.OpenSellPending(lot,Symbol(),magic,stoploss,takeprofit); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL,EQUAL_OR_MORE,bid,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_SELL_LIMIT: Выставить SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellLimit if(!pressed_pending_sell_limit) engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending order SellLimit")); //--- Иначе - создаём отложенный запрос на установку ордера SellLimit со значением дистанции установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceSellLimitPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_LIMIT,EQUAL_OR_MORE,bid,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_SELL_STOP: Выставить SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellStop if(!pressed_pending_sell_stop) engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending order SellStop")); //--- Иначе - создаём отложенный запрос на установку ордера SellStop со значением дистанции установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceSellStopPending(lot,Symbol(),distance_pending,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_STOP,EQUAL_OR_MORE,bid,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_SELL_STOP_LIMIT: Выставить SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем ордер SellStopLimit if(!pressed_pending_sell_stoplimit) engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending order SellStopLimit")); //--- Иначе - создаём отложенный запрос на установку ордера SellStopLimit со значениями дистанций установки //--- и задаём условия в зависимости от активных кнопок else { int id=engine.PlaceSellStopLimitPending(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SELL_STOP_LIMIT,EQUAL_OR_MORE,bid,TimeCurrent()); } } } //--- Если нажата кнопка BUTT_CLOSE_BUY: Закрыть Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy и только по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { //--- Получаем объект-позицию Buy и закрываем позицию по тикету COrder* position=list.At(index); if(position!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию if(!pressed_pending_close_buy) engine.ClosePosition((ulong)position.Ticket()); //--- Иначе - создаём отложенный запрос на закрытие позиции по тикету //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionPending(position.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY,EQUAL_OR_MORE,bid,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_CLOSE_BUY2: Закрыть половину Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Buy и только по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { //--- Получаем объект-позицию Buy и закрываем позицию по тикету COrder* position=list.At(index); if(position!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию по тикету if(!pressed_pending_close_buy2) engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0); //--- Иначе - создаём отложенный запрос на частичное закрытие позиции по тикету //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionPartiallyPending(position.Ticket(),position.Volume()/2.0); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY2,EQUAL_OR_MORE,bid,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_CLOSE_BUY_BY_SELL: Закрыть Buy с максимальной прибылью встречной Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- Если счёт хеджевый if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) return; //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Выбираем из списка только позиции Buy list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) return; //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- Выбираем из списка только позиции Sell list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) return; //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- Выбираем позицию Buy с наибольшей прибылью COrder* position_buy=list_buy.At(index_buy); //--- Выбираем позицию Sell с наибольшей прибылью COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позиции по тикету if(!pressed_pending_close_buy_by_sell) engine.ClosePositionBy((ulong)position_buy.Ticket(),(ulong)position_sell.Ticket()); //--- Иначе - создаём отложенный запрос на закрытие позиции Buy встречной позицией Sell //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionByPending(position_buy.Ticket(),position_sell.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(bid+distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_BUY_BY_SELL,EQUAL_OR_MORE,bid,TimeCurrent()); } } } } } } //--- Если нажата кнопка BUTT_CLOSE_SELL: Закрыть Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell и только по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { //--- Получаем объект-позицию Sell и закрываем позицию по тикету COrder* position=list.At(index); if(position!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию if(!pressed_pending_close_sell) engine.ClosePosition((ulong)position.Ticket()); //--- Иначе - создаём отложенный запрос на закрытие позиции по тикету //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionPending(position.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL,EQUAL_OR_LESS,ask,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_CLOSE_SELL2: Закрыть половину Sell с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции Sell и только по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { //--- Получаем объект-позицию Sell и закрываем позицию по тикету COrder* position=list.At(index); if(position!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позицию по тикету if(!pressed_pending_close_sell2) engine.ClosePositionPartially((ulong)position.Ticket(),position.Volume()/2.0); //--- Иначе - создаём отложенный запрос на частичное закрытие позиции по тикету //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionPartiallyPending(position.Ticket(),position.Volume()/2.0); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL2,EQUAL_OR_LESS,ask,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_CLOSE_SELL_BY_BUY: Закрыть Sell с максимальной прибылью встречной Buy с максимальной прибылью else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- Если счёт хеджевый if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) return; //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Выбираем из списка только позиции Sell list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) return; //--- Сортируем список по прибыли с учётом комиссии и свопа list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Sell с наибольшей прибылью int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- Выбираем из списка только позиции Buy list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) return; //--- Сортируем список по прибыли с учётом комиссии и свопа list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Получаем индекс позиции Buy с наибольшей прибылью int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- Выбираем позицию Sell с наибольшей прибылью COrder* position_sell=list_sell.At(index_sell); //--- Выбираем позицию Buy с наибольшей прибылью COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- Если не нажаты кнопки создания отложенного запроса - закрываем позиции по тикету if(!pressed_pending_close_sell_by_buy) engine.ClosePositionBy((ulong)position_sell.Ticket(),(ulong)position_buy.Ticket()); //--- Иначе - создаём отложенный запрос на закрытие позиции Sell встречной позицией Buy //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionByPending(position_sell.Ticket(),position_buy.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(ask-distance_pending_request*g_point,g_digits); ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_SELL_BY_BUY,EQUAL_OR_LESS,ask,TimeCurrent()); } } } } } } //--- Если нажата кнопка BUTT_CLOSE_ALL: Закрыть все позиции, начиная от позиции с наименьшим профитом else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- Получаем список всех открытых позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); if(list!=NULL) { //--- Сортируем список по прибыли с учётом комиссии и свопа list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- В цикле от позиции с наименьшей прибылью for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- Если не нажаты кнопки создания отложенного запроса - закрываем каждую позицию по её тикету if(!pressed_pending_close_all) engine.ClosePosition((ulong)position.Ticket()); //--- Иначе - создаём отложенный запрос на закрытие каждой позиции //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ClosePositionPending(position.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(position.PriceOpen()+distance_pending_request*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(position.TypeOrder()==POSITION_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_ASK); price_activation=NormalizeDouble(position.PriceOpen()-distance_pending_request*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_CLOSE_ALL,comparer,price,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_DELETE_PENDING: Удалить отложенные ордера, начиная с самого раннего else if(button==EnumToString(BUTT_DELETE_PENDING)) { //--- Получаем список всех ордеров CArrayObj* list=engine.GetListMarketPendings(); //--- Выбираем из списка только ордера по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); if(list!=NULL) { //--- Сортируем список по времени установки list.Sort(SORT_BY_ORDER_TIME_OPEN); int total=list.Total(); //--- В цикле от ордера с наибольшим временем for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; //--- Если не нажаты кнопки создания отложенного запроса - удаляем каждый ордер по его тикету if(!pressed_pending_delete_all) engine.DeleteOrder((ulong)order.Ticket()); //--- Иначе - создаём отложенный запрос на удаление каждого ордера //--- и задаём условия в зависимости от активных кнопок else { int id=engine.DeleteOrderPending(order.Ticket()); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(order.PriceOpen()+(distance_pending+distance_pending_request)*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(order.TypeByDirection()==ORDER_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_BID); price_activation=NormalizeDouble(order.PriceOpen()-(distance_pending+distance_pending_request)*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_DELETE_PENDING,comparer,price,TimeCurrent()); } } } } } //--- Если нажата кнопка BUTT_SET_STOP_LOSS: Установить StopLoss всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } //--- Если нажата кнопка BUTT_SET_TAKE_PROFIT: Установить TakeProfit всем ордерам и позициям, где его нету if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } //--- Если нажата кнопка BUTT_PROFIT_WITHDRAWAL: Вывести средства со счёта if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- Если программа запущена в тестере if(MQLInfoInteger(MQL_TESTER)) { //--- Эмулируем вывод средств TesterWithdrawal(withdrawal); } } //--- Подождём 1/10 секунды Sleep(100); //--- "Отожмём" кнопку (если это не кнопка трейлинга и не кнопки активации работы отложенными запросами) if(button!=EnumToString(BUTT_TRAILING_ALL) && StringFind(button,"_PRICE")<0 && StringFind(button,"_TIME")<0) ButtonState(button_name,false); //--- Если нажата кнопка BUTT_TRAILING_ALL или кнопки включения работы отложенными запросами else { //--- Поставим цвет активной кнопки для кнопки включения трейлинга if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,true); trailing_on=true; } //--- Покупки //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для открытия Buy по цене или времени if(button==EnumToString(BUTT_BUY)+"_PRICE" || button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,true); pressed_pending_buy=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyLimit по цене или времени if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,true); pressed_pending_buy_limit=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyStop по цене или времени if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,true); pressed_pending_buy_stop=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки BuyStopLimit по цене или времени if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pressed_pending_buy_stoplimit=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Buy по цене или времени if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_buy=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия 1/2 Buy по цене или времени if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_buy2=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Buy встречной Sell по цене или времени if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_buy_by_sell=true; } //--- Продажи //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для открытия Sell по цене или времени if(button==EnumToString(BUTT_SELL)+"_PRICE" || button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,true); pressed_pending_sell=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellLimit по цене или времени if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,true); pressed_pending_sell_limit=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellStop по цене или времени if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,true); pressed_pending_sell_stop=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки SellStopLimit по цене или времени if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pressed_pending_sell_stoplimit=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Sell по цене или времени if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_sell=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия 1/2 Sell по цене или времени if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_sell2=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия Sell встречной Buy по цене или времени if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_sell_by_buy=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для удаления ордеров по цене или времени if(button==EnumToString(BUTT_DELETE_PENDING)+"_PRICE" || button==EnumToString(BUTT_DELETE_PENDING)+"_TIME") { ButtonState(button_name,true); pressed_pending_delete_all=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для закрытия позиций по цене или времени if(button==EnumToString(BUTT_CLOSE_ALL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_ALL)+"_TIME") { ButtonState(button_name,true); pressed_pending_close_all=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки StopLoss по цене или времени if(button==EnumToString(BUTT_SET_STOP_LOSS)+"_PRICE" || button==EnumToString(BUTT_SET_STOP_LOSS)+"_TIME") { ButtonState(button_name,true); pressed_pending_sl=true; } //--- Поставим цвет активной кнопки для кнопки активации работы отложенными запросами для установки TakeProfit по цене или времени if(button==EnumToString(BUTT_SET_TAKE_PROFIT)+"_PRICE" || button==EnumToString(BUTT_SET_TAKE_PROFIT)+"_TIME") { ButtonState(button_name,true); pressed_pending_tp=true; } } //--- перерисуем чарт ChartRedraw(); } //--- Вернём цвет неактивным кнопкам else { //--- кнопка трейлинга if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; } //--- Покупки //--- кнопка активации работы отложенными запросами для открытия Buy по цене if(button==EnumToString(BUTT_BUY)+"_PRICE") { ButtonState(button_name,false); pressed_pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME")); } //--- кнопка активации работы отложенными запросами для открытия Buy по времени if(button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,false); pressed_pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки BuyLimit по цене if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE") { ButtonState(button_name,false); pressed_pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки BuyLimit по времени if(button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,false); pressed_pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки BuyStop по цене if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE") { ButtonState(button_name,false); pressed_pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки BuyStop по времени if(button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,false); pressed_pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки BuyStopLimit по цене if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE") { ButtonState(button_name,false); pressed_pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки BuyStopLimit по времени if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,false); pressed_pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия Buy по цене if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия Buy по времени if(button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия 1/2 Buy по цене if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия 1/2 Buy по времени if(button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия Buy встречной Sell по цене if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия Buy встречной Sell по времени if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE")); } //--- Продажи //--- кнопка активации работы отложенными запросами для открытия Sell по цене if(button==EnumToString(BUTT_SELL)+"_PRICE") { ButtonState(button_name,false); pressed_pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME")); } //--- кнопка активации работы отложенными запросами для открытия Sell по времени if(button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,false); pressed_pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки SellLimit по цене if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE") { ButtonState(button_name,false); pressed_pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки SellLimit по времени if(button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,false); pressed_pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки SellStop по цене if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE") { ButtonState(button_name,false); pressed_pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки SellStop по времени if(button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,false); pressed_pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки SellStopLimit по цене if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE") { ButtonState(button_name,false); pressed_pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки SellStopLimit по времени if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,false); pressed_pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия Sell по цене if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия Sell по времени if(button==EnumToString(BUTT_CLOSE_SELL)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия 1/2 Sell по цене if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия 1/2 Sell по времени if(button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия Sell встречной Buy по цене if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия Sell встречной Buy по времени if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для удаления ордеров по цене if(button==EnumToString(BUTT_DELETE_PENDING)+"_PRICE") { ButtonState(button_name,false); pressed_pending_delete_all=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_DELETE_PENDING)+"_TIME")); } //--- кнопка активации работы отложенными запросами для удаления ордеров по времени if(button==EnumToString(BUTT_DELETE_PENDING)+"_TIME") { ButtonState(button_name,false); pressed_pending_delete_all=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_DELETE_PENDING)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для закрытия позиций по цене if(button==EnumToString(BUTT_CLOSE_ALL)+"_PRICE") { ButtonState(button_name,false); pressed_pending_close_all=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_ALL)+"_TIME")); } //--- кнопка активации работы отложенными запросами для закрытия позиций по времени if(button==EnumToString(BUTT_CLOSE_ALL)+"_TIME") { ButtonState(button_name,false); pressed_pending_close_all=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_ALL)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки StopLoss по цене if(button==EnumToString(BUTT_SET_STOP_LOSS)+"_PRICE") { ButtonState(button_name,false); pressed_pending_sl=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SET_STOP_LOSS)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки StopLoss по времени if(button==EnumToString(BUTT_SET_STOP_LOSS)+"_TIME") { ButtonState(button_name,false); pressed_pending_sl=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SET_STOP_LOSS)+"_PRICE")); } //--- кнопка активации работы отложенными запросами для установки TakeProfit по цене if(button==EnumToString(BUTT_SET_TAKE_PROFIT)+"_PRICE") { ButtonState(button_name,false); pressed_pending_tp=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SET_TAKE_PROFIT)+"_TIME")); } //--- кнопка активации работы отложенными запросами для установки TakeProfit по времени if(button==EnumToString(BUTT_SET_TAKE_PROFIT)+"_TIME") { ButtonState(button_name,false); pressed_pending_tp=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SET_TAKE_PROFIT)+"_PRICE")); } //--- перерисуем чарт ChartRedraw(); } } //+------------------------------------------------------------------+
В функциях установки StopLoss и TakeProfit всем ордерам и позициям тоже добавим блоки кода для создания отложенных запросов на установку StopLoss/TakeProfit:
//+------------------------------------------------------------------+ //| Установка StopLoss всем ордерам и позициям | //+------------------------------------------------------------------+ void SetStopLoss(void) { if(stoploss_to_modify==0) return; //--- Установка StopLoss всем позициям, где его нету //--- Получаем список всех позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- выбираем из списка позиции с нулевым StopLoss list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify); //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем StopLoss каждой позиции по её тикету if(!pressed_pending_sl) engine.ModifyPosition((ulong)position.Ticket(),sl,-1); //--- Иначе - создаём отложенный запрос на установку StopLoss каждой позиции //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ModifyPositionPending(position.Ticket(),sl,-1); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(position.PriceOpen()+distance_pending_request*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(position.TypeByDirection()==ORDER_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_ASK); price_activation=NormalizeDouble(position.PriceOpen()-distance_pending_request*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SET_STOP_LOSS,comparer,price,TimeCurrent()); } } } //--- Установка StopLoss всем отложенным ордерам, где его нету //--- Получаем список всех ордеров list=engine.GetListMarketPendings(); //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- выбираем из списка ордера с нулевым StopLoss list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify); //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем StopLoss каждому ордеру по его тикету if(!pressed_pending_sl) engine.ModifyOrder((ulong)order.Ticket(),-1,sl,-1,-1); //--- Иначе - создаём отложенный запрос на установку StopLoss каждому ордеру //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ModifyOrderPending(order.Ticket(),-1,sl,-1,-1); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(order.PriceOpen()+(distance_pending+distance_pending_request)*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(order.TypeByDirection()==ORDER_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_BID); price_activation=NormalizeDouble(order.PriceOpen()-(distance_pending+distance_pending_request)*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SET_STOP_LOSS,comparer,price,TimeCurrent()); } } } } //+------------------------------------------------------------------+ //| Установка TakeProfit всем ордерам и позициям | //+------------------------------------------------------------------+ void SetTakeProfit(void) { if(takeprofit_to_modify==0) return; //--- Установка TakeProfit всем позициям, где его нету //--- Получаем список всех позиций CArrayObj* list=engine.GetListMarketPosition(); //--- Выбираем из списка только позиции по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- выбираем из списка позиции с нулевым TakeProfit list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify); //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем TakeProfit каждой позиции по её тикету if(!pressed_pending_tp) engine.ModifyPosition((ulong)position.Ticket(),-1,tp); //--- Иначе - создаём отложенный запрос на установку TakeProfit каждой позиции //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ModifyPositionPending(position.Ticket(),-1,tp); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_BID); double price_activation=NormalizeDouble(position.PriceOpen()+distance_pending_request*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(position.TypeByDirection()==ORDER_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_ASK); price_activation=NormalizeDouble(position.PriceOpen()-distance_pending_request*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SET_TAKE_PROFIT,comparer,price,TimeCurrent()); } } } //--- Установка TakeProfit всем отложенным ордерам, где его нету //--- Получаем список всех ордеров list=engine.GetListMarketPendings(); //--- Выбираем из списка только ордера по текущему символу list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- выбираем из списка ордера с нулевым TakeProfit list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify); //--- Если не нажаты кнопки создания отложенного запроса - устанавливаем TakeProfit каждому ордеру по его тикету if(!pressed_pending_sl) engine.ModifyOrder((ulong)order.Ticket(),-1,-1,tp,-1); //--- Иначе - создаём отложенный запрос на установку TakeProfit каждому ордеру //--- и задаём условия в зависимости от активных кнопок else { int id=engine.ModifyOrderPending(order.Ticket(),-1,-1,tp,-1); if(id>0) { //--- устанавливаем цену и время активации отложенного запроса и устанавливаем параметры активации double price=SymbolInfoDouble(NULL,SYMBOL_ASK); double price_activation=NormalizeDouble(order.PriceOpen()+(distance_pending+distance_pending_request)*g_point,g_digits); ENUM_COMPARER_TYPE comparer=EQUAL_OR_MORE; if(order.TypeByDirection()==ORDER_TYPE_SELL) { price=SymbolInfoDouble(NULL,SYMBOL_BID); price_activation=NormalizeDouble(order.PriceOpen()-(distance_pending+distance_pending_request)*g_point,g_digits); comparer=EQUAL_OR_LESS; } ulong time_activation=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); SetPReqCriterion((uchar)id,price_activation,time_activation,BUTT_SET_TAKE_PROFIT,comparer,price,TimeCurrent()); } } } } //+------------------------------------------------------------------+
Блоки кода по созданию отложенных запросов во всех рассмотренных функциях одинаковы по логике, подробно прокомментированы в коде, поэтому оставим их для самостоятельного изучения. В любом случае, если что-то не понятно, то всегда можно задать уточняющие вопросы в обсуждении к статье.
Скомпилируем советник и запустим его в визуальном режиме тестера. Для проверки удаления ордеров и модификации ордеров и позиций, сначала откроем две позиции на продажу и установим отложенный ордер на продажу без уровней StopLoss и TakeProfit. Затем создадим отложенные запросы на модификацию стоп-уровней ордерам и позициям по условию значения цены. Дождёмся активации отложенных запросов и выставлению заданных стоп-уровней и удалим ордера и позиции.
Далее откроем две позиции на покупку и установим отложенный ордер на покупку. После чего создадим отложенные запросы на удаление ордеров и закрытие позиций по времени.
Как видно, стоп-приказы были выставлены по пересечению заданного уровня цены активации отложенных запросов, позиции были закрыты по прошествии заданного времени и ордер был удалён.
Хочу отметить, что не всё пока работает гладко — есть проблемы с одновременным созданием нескольких отложенных запросов для одного и
того же тикета — не всегда эти запросы правильно отрабатывают. Т.е. на данный момент логика работает верно только в случае, если есть по
одному отложенному запросу на каждую позицию или ордер. После срабатывания отложенного запроса, его исполнения и удаления, можно
опять создавать новый отложенный запрос для этой позиции или ордера (если они остались в рынке).
Эту проблему будем решать не сейчас, а постепенно, наряду с созданием дальнейшего функционала библиотеки, и скорее всего — уже при наличии
некоторых графических объектов библиотеки.
Что дальше
Со следующей статьи начнём создавать функционал библиотеки по хранению, обработке и получению ценовых данных.
Ниже прикреплены все файлы текущей версии библиотеки и файлы тестового советника. Их можно скачать и протестировать всё
самостоятельно.
При возникновении вопросов, замечаний и пожеланий, вы можете озвучить их в комментариях к статье.
Статьи этой серии:
Часть 1. Концепция, организация данных
Часть
2. Коллекция исторических ордеров и сделок
Часть 3. Коллекция рыночных ордеров и
позиций, организация поиска
Часть 4. Торговые события. Концепция
Часть 5. Классы и коллекция торговых событий. Отправка событий в программу
Часть 6. События на счёте с типом неттинг
Часть
7. События срабатывания StopLimit-ордеров, подготовка функционала для регистрации событий модификации ордеров и позиций
Часть
8. События модификации ордеров и позиций
Часть 9. Совместимость с MQL4 —
Подготовка данных
Часть 10. Совместимость с MQL4 — События открытия позиций и
активации отложенных ордеров
Часть 11. Совместимость с MQL4 — События закрытия
позиций
Часть 12. Класс объекта "аккаунт", коллекция объектов-аккаунтов
Часть 13. События объекта "аккаунт"
Часть
14. Объект "Символ"
Часть 15. Коллекция объектов-символов
Часть
16. События коллекции символов
Часть 17. Интерактивность объектов библиотеки
Часть 18. Интерактивность объекта-аккаунт и любых других объектов библиотеки
Часть
19. Класс сообщений библиотеки
Часть 20. Создание и хранение ресурсов программы
Часть 21. Торговые классы — Базовый кроссплатформенный торговый объект
Часть 22. Торговые классы — Основной торговый класс, контроль ограничений
Часть 23. Торговые классы — Основной торговый класс, контроль допустимых параметров
Часть 24. Торговые классы — Основной торговый класс, автоматическая коррекция ошибочных
параметров
Часть 25. Торговые классы — Основной торговый класс, обработка
ошибок, возвращаемых торговым сервером
Часть 26. Работа с отложенными торговыми
запросами - первая реализация (открытие позиций)
Часть 27. Работа с отложенными
торговыми запросами - выставление отложенных ордеров
Часть 28. Работа с
отложенными торговыми запросами - закрытие, удаление, модификации
Часть 29.
Работа с отложенными торговыми запросами - классы объектов-запросов
Часть 30.
Работа с отложенными запросами - управление объектами-запросами
Часть 31.
Работа с отложенными запросами - открытие позиций по условиям
Часть 32. Работа с
отложенными запросами - установка отложенных ордеров по условиям
Часть 33.
Работа с отложенными запросами - закрытие позиций (полное, частичное и встречное) по условиям
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ок.
На мой взгляд вещь нужная, учитывая размер библиотеки и то, как долго она компилируется.
Ок.
На мой взгляд вещь нужная, учитывая размер библиотеки и то, как долго она компилируется.
Можно отключить оптимизацию при компилировании. Будет быстрее
Да, действительно, быстрее. Но всего раза в два.
У меня на ноутбуке время компилирования доходило до 60 сек., теперь, после отключения оптимизации, доходит до 30 сек. А если учесть, что это только лишь 34-я часть (далеко не последняя), то всё равно многовато. Ведь дальше библиотека будет только увеличиваться...
Я полагал, что её легко можно будет сделать как-нибудь в виде DLL-ки (например) и подключить к эксперту. Но попытался, и... не совсем понятно, как это сделать...
Я полагал, что её легко можно будет сделать как-нибудь в виде DLL-ки (например) и подключить к эксперту. Но попытался, и... не совсем понятно, как это сделать...
Есть аналоги dll, смотрите документацию
Спасибо.
Похоже это как раз то что надо. Во всяком случае теоретически. Ну а на практике, при попытке скомпилировать файл Engine c #property library, не получается экспортировать методы (которые по сути являются функциями) - выдаётся ошибка. Похоже надо добавлять ещё функции (экспортируемые), которые вызывают эти методы. К тому же, не будет всплывающих подсказок в программе, которая импортирует эти функции.
Вобщем всё это не совсем как хотелось бы...