DoEasy. Сервисные функции (Часть 3): Паттерн "Внешний бар"
Содержание
Концепция
Продолжаем раздел библиотеки DoEasy по работе с ценовыми паттернами.
В прошлой статье мы создали поиск и отображение двухбаровых паттернов Price Action "Внутренний Бар". Сегодня создадим поиск паттерна "Внешний Бар", являющегося по сути зеркальным отображением внутреннего бара.
Но есть и отличия. Если внутренний бар является двунаправленным паттерном, и вход может осуществляться по любой из сторон паттерна, то паттерн "Внешний Бар" делится на два направления — Бычий и Медвежий:
- BUOVB (Bullish Outside Vertical Bar) – бычий внешний вертикальный бар. Сигнальный бар полностью перекрывает предыдущий, цена его закрытия выше максимума предыдущего бара. Вход осуществляется при пробое максимума сигнального бара+фильтр (5-10 пунктов).
- BEOVB (Bearish Outside Vertical Bar) – медвежий внешний вертикальный бар. Сигнальный бар полностью перекрывает предыдущий, цена его закрытия ниже минимума предыдущего бара. Вход осуществляется при пробое минимума сигнального бара + фильтр (5-10 пунктов).
Прежде, чем начнём делать класс паттерна, необходимо внести доработки в уже готовые классы библиотеки. Во-первых, все методы в классах доступа к паттернам созданы не оптимально — это было лишь тестирование концепции, где всё было реализовано "в лоб" множеством методов, каждый для своего типа паттернов. Теперь же сделаем всего один метод для доступа к работе с указанными паттернами. Для каждого из действий — по собственному методу, где требуемый паттерн будет указываться переменной. Это сильно упростит и укоротит коды классов.
И во-вторых, за длительное время, пока не было работы над библиотекой, в языке MQL5 произошли некоторые изменения и добавления (не все анонсированные изменения ещё добавлены в язык), и мы эти изменения добавим в библиотеку. Также была проведена работа над найденными ошибками за всё время тестирования библиотеки — опишем все проведённые доработки.
Доработка классов библиотеки
При определении некоторых паттернов важно взаимное соотношение размеров соседних свечей, участвующих в формировании фигуры паттерна. Для целей определения соотношения размеров, добавим к свойствам паттерна новое значение. И также в свойствах каждого паттерна и класса управления паттернами добавим свойство, в котором будет указано то значение, по которому ищется соотношение свечей.
В файле библиотеки \MQL5\Include\DoEasy\Defines.mqh в перечисление вещественных свойств паттерна добавим новые свойства, а общее количество вещественных свойств увеличим с 10 до 12:
//+------------------------------------------------------------------+ //| Вещественные свойства паттерна | //+------------------------------------------------------------------+ enum ENUM_PATTERN_PROP_DOUBLE { //--- данные бара PATTERN_PROP_BAR_PRICE_OPEN = PATTERN_PROP_INTEGER_TOTAL,// Цена Open определяющего бара паттерна PATTERN_PROP_BAR_PRICE_HIGH, // Цена High определяющего бара паттерна PATTERN_PROP_BAR_PRICE_LOW, // Цена Low определяющего бара паттерна PATTERN_PROP_BAR_PRICE_CLOSE, // Цена Close определяющего бара паттерна PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE, // Отношение тела свечи к полному размеру свечи в % PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Отношение размера верхней тени к размеру свечи в % PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Отношение размера нижней тени к размеру свечи в % PATTERN_PROP_RATIO_CANDLE_SIZES, // Отношение размеров свечей паттерна PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION, // Установленный критерий отношения тела свечи к полному размеру свечи в % PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Установленный критерий отношения размера наибольшей тени к размеру свечи в % PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Установленный критерий отношения размера наименьшей тени к размеру свечи в % PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION, // Установленный критерий отношения размеров свечей паттерна }; #define PATTERN_PROP_DOUBLE_TOTAL (12) // Общее количество вещественных свойств паттерна #define PATTERN_PROP_DOUBLE_SKIP (0) // Количество неиспользуемых в сортировке свойств паттерна
Начиная с бета-версии 4540 клиентского терминала MetaTrader 5 в перечисление ENUM_SYMBOL_SWAP_MODE добавлено значение SYMBOL_SWAP_MODE_CURRENCY_PROFIT.
Если функция SymbolInfoInteger()возвращает такое значение, значит свопы на счете начисляются в деньгах в валюте расчета прибыли.
Добавим это значение в файлы библиотеки. В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений библиотеки:
MSG_SYM_SWAP_MODE_CURRENCY_MARGIN, // Свопы начисляются в деньгах в маржинальной валюте символа MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT, // Свопы начисляются в деньгах в валюте депозита клиента MSG_SYM_SWAP_MODE_CURRENCY_PROFIT, // Свопы начисляются в деньгах в валюте расчета прибыли MSG_SYM_SWAP_MODE_INTEREST_CURRENT, // Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа MSG_SYM_SWAP_MODE_INTEREST_OPEN, // Свопы начисляются в годовых процентах от цены открытия позиции по символу
...
MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE, // Отношение тела свечи к полному размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Отношение размера верхней тени к размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Отношение размера нижней тени к размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES, // Отношение размеров свечей паттерна MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT, // Установленный критерий отношения тела свечи к полному размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT, // Установленный критерий отношения размера наибольшей тени к размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT, // Установленный критерий отношения размера наименьшей тени к размеру свечи в % MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION, // Установленный критерий отношения размеров свечей паттерна MSG_LIB_TEXT_PATTERN_NAME, // Наименование
и тестовые сообщения, соответствующие вновь добавленным индексам:
{"Свопы начисляются в деньгах в маржинальной валюте символа","Swaps are charged in money in margin currency of the symbol"}, {"Свопы начисляются в деньгах в валюте депозита клиента","Swaps are charged in money, in client deposit currency"}, {"Свопы начисляются в деньгах в валюте расчета прибыли","Swaps are charged in money, in profit calculation currency"}, { "Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа", "Swaps are charged as the specified annual interest from the instrument price at calculation of swap" }, {"Свопы начисляются в годовых процентах от цены открытия позиции по символу","Swaps are charged as the specified annual interest from the open price of position"},
...
{"Отношение размера верхней тени к размеру свечи в %","Ratio of the size of the upper shadow to the size of the candle in %"}, {"Отношение размера нижней тени к размеру свечи в %","Ratio of the size of the lower shadow to the size of the candle in %"}, {"Отношение размеров свечей паттерна","Ratio of pattern candle sizes"}, {"Установленный критерий отношения тела свечи к полному размеру свечи в %","Criterion for the Ratio of candle body to full candle size in %"}, {"Установленный критерий отношения размера наибольшей тени к размеру свечи в %","Criterion for the Ratio of the size of the larger shadow to the size of the candle in %"}, {"Установленный критерий отношения размера наименьшей тени к размеру свечи в %","Criterion for the Ratio of the size of the smaller shadow to the size of the candle in %"}, {"Установленный критерий отношения размеров свечей паттерна","Criterion for the Ratio of pattern candle sizes"}, {"Наименование","Name"},
В массив сообщений ошибок времени выполнения добавим описание двух новых ошибок времени выполнения с кодами 4306 и 4307:
//+------------------------------------------------------------------+ //| Массив сообщений ошибок времени выполнения (4301 - 4307) | //| (MarketInfo) | //| (1) на языке страны пользователя | //| (2) на международном языке | //+------------------------------------------------------------------+ string messages_runtime_market[][TOTAL_LANG]= { {"Неизвестный символ","Unknown symbol"}, // 4301 {"Символ не выбран в MarketWatch","Symbol is not selected in MarketWatch"}, // 4302 {"Ошибочный идентификатор свойства символа","Wrong identifier of a symbol property"}, // 4303 {"Время последнего тика неизвестно (тиков не было)","Time of the last tick is not known (no ticks)"}, // 4304 {"Ошибка добавления или удаления символа в MarketWatch","Error adding or deleting a symbol in MarketWatch"}, // 4305 {"Превышен лимит выбранных символов в MarketWatch","Exceeded the limit of selected symbols in MarketWatch"}, // 4306 { "Неправильный индекс сессии при вызове функции SymbolInfoSessionQuote/SymbolInfoSessionTrade", // 4307 "Wrong session ID when calling the SymbolInfoSessionQuote/SymbolInfoSessionTrade function" }, };
В файле \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh в метод, возвращающий количество знаков после запятой в зависимости от метода расчёта свопа, впишем обработку нового значения перечисления:
//+------------------------------------------------------------------+ //| Возвращает количество знаков после запятой | //| в зависимости от метода расчёта свопа | //+------------------------------------------------------------------+ int CSymbol::SymbolDigitsBySwap(void) { return ( this.SwapMode()==SYMBOL_SWAP_MODE_POINTS || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID ? this.Digits() : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT || this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ? this.DigitsCurrency(): this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT || this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN ? 1 : 0 ); }
В метод, возвращающий описание модели расчета свопа, впишем возврат строки с описанием нового режима расчёта свопов:
//+------------------------------------------------------------------+ //| Возвращает описание модели расчета свопа | //+------------------------------------------------------------------+ string CSymbol::GetSwapModeDescription(void) const { return ( this.SwapMode()==SYMBOL_SWAP_MODE_DISABLED ? CMessage::Text(MSG_SYM_SWAP_MODE_DISABLED) : this.SwapMode()==SYMBOL_SWAP_MODE_POINTS ? CMessage::Text(MSG_SYM_SWAP_MODE_POINTS) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_SYMBOL ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_SYMBOL) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_MARGIN ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_MARGIN) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT) : this.SwapMode()==SYMBOL_SWAP_MODE_CURRENCY_PROFIT ? CMessage::Text(MSG_SYM_SWAP_MODE_CURRENCY_PROFIT) : this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_CURRENT ? CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_CURRENT) : this.SwapMode()==SYMBOL_SWAP_MODE_INTEREST_OPEN ? CMessage::Text(MSG_SYM_SWAP_MODE_INTEREST_OPEN) : this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_CURRENT ? CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_CURRENT) : this.SwapMode()==SYMBOL_SWAP_MODE_REOPEN_BID ? CMessage::Text(MSG_SYM_SWAP_MODE_REOPEN_BID) : CMessage::Text(MSG_SYM_MODE_UNKNOWN) ); }
В файле определений для языка MQL4 \MQL5\Include\DoEasy\ToMQL4.mqh в перечисление способов начисления свопов при переносе позиции добавим новое свойство:
//+------------------------------------------------------------------+ //| Способы начисления свопов при переносе позиции | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_SWAP_MODE { SYMBOL_SWAP_MODE_POINTS, // (MQL5 - 1, MQL4 - 0) Свопы начисляются в пунктах SYMBOL_SWAP_MODE_CURRENCY_SYMBOL, // (MQL5 - 2, MQL4 - 1) Свопы начисляются в деньгах в базовой валюте символа SYMBOL_SWAP_MODE_INTEREST_OPEN, // (MQL5 - 6, MQL4 - 2) Свопы начисляются в годовых процентах от цены открытия позиции по символу SYMBOL_SWAP_MODE_CURRENCY_MARGIN, // (MQL5 - 3, MQL4 - 3) Свопы начисляются в деньгах в маржинальной валюте символа SYMBOL_SWAP_MODE_DISABLED, // (MQL5 - 0, MQL4 - N) Нет свопов SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT, // Свопы начисляются в деньгах в валюте депозита клиента SYMBOL_SWAP_MODE_INTEREST_CURRENT, // Свопы начисляются в годовых процентах от цены инструмента на момент расчета свопа SYMBOL_SWAP_MODE_REOPEN_CURRENT, // Свопы начисляются переоткрытием позиции по цене закрытия SYMBOL_SWAP_MODE_REOPEN_BID, // Свопы начисляются переоткрытием позиции по текущей цене Bid SYMBOL_SWAP_MODE_CURRENCY_PROFIT // Свопы начисляются в деньгах в валюте расчета прибыли };
В файле класса базового объекта библиотеки \MQL5\Include\DoEasy\Objects\BaseObj.mqh исправим строки с потенциальными утечками памяти:
//+------------------------------------------------------------------+ //| Добавляет событие объекта в список | //+------------------------------------------------------------------+ bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam) { CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam); if(event==NULL) return false; this.m_list_events.Sort(); if(this.m_list_events.Search(event)>WRONG_VALUE) { delete event; return false; } return this.m_list_events.Add(event); } //+------------------------------------------------------------------+ //| Добавляет базовое событие объекта в список | //+------------------------------------------------------------------+ bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value) { CBaseEvent* event=new CBaseEvent(event_id,reason,value); if(event==NULL) return false; this.m_list_events_base.Sort(); if(this.m_list_events_base.Search(event)>WRONG_VALUE) { delete event; return false; } return this.m_list_events_base.Add(event); }
Здесь методы возвращают результат добавления объекта в список (true при удачном добавлении, и false — при ошибке). Если произойдёт ошибка добавления созданного посредством оператора newобъекта, то этот объект останется где-то в памяти без сохранения на него указателя в списке, что приведёт к утечкам памяти. Исправим:
//+------------------------------------------------------------------+ //| Добавляет событие объекта в список | //+------------------------------------------------------------------+ bool CBaseObjExt::EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam) { CEventBaseObj *event=new CEventBaseObj(event_id,lparam,dparam,sparam); if(event==NULL) return false; this.m_list_events.Sort(); if(this.m_list_events.Search(event)>WRONG_VALUE || !this.m_list_events.Add(event)) { delete event; return false; } return true; } //+------------------------------------------------------------------+ //| Добавляет базовое событие объекта в список | //+------------------------------------------------------------------+ bool CBaseObjExt::EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value) { CBaseEvent* event=new CBaseEvent(event_id,reason,value); if(event==NULL) return false; this.m_list_events_base.Sort(); if(this.m_list_events_base.Search(event)>WRONG_VALUE || !this.m_list_events_base.Add(event)) { delete event; return false; } return true; }
Теперь при неудачном добавлении объекта-события в список, вновь созданный объект будет так же удаляться, как и уже при наличии такого же объекта в списке, и метод вернёт false.
В файле \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh при подсчёте количества пунктов необходимо добавить округление полученного результата, иначе, при значениях меньше 1, но близких к единице, получаем нулевое количество пунктов при приведении вещественного числа к целочисленному:
//+------------------------------------------------------------------+ //| Возвращает профит ордера в пунктах | //+------------------------------------------------------------------+ int COrder::ProfitInPoints(void) const { MqlTick tick={0}; string symbol=this.Symbol(); if(!::SymbolInfoTick(symbol,tick)) return 0; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder(); double point=::SymbolInfoDouble(symbol,SYMBOL_POINT); if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0; if(this.Status()==ORDER_STATUS_HISTORY_ORDER) return int(type==ORDER_TYPE_BUY ? (::round(this.PriceClose()-this.PriceOpen())/point) : type==ORDER_TYPE_SELL ? ::round((this.PriceOpen()-this.PriceClose())/point) : 0); else if(this.Status()==ORDER_STATUS_MARKET_POSITION) { if(type==ORDER_TYPE_BUY) return (int)::round((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL) return (int)::round((this.PriceOpen()-tick.ask)/point); } else if(this.Status()==ORDER_STATUS_MARKET_PENDING) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT) return (int)fabs(::round((tick.bid-this.PriceOpen())/point)); else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT) return (int)fabs(::round((this.PriceOpen()-tick.ask)/point)); } return 0; }
В файле \MQL5\Include\DoEasy\Objects\Events\Event.mqh изначально была допущена ошибка при возврате long-значения как значения перечисления:
//--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции //--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции //--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);} ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); }
Пока значение тикета ордера, имеющего тип long, не превышало значения INT_MAX (тип данных для перечисления — int), тикеты ордеров возвращались корректно. Но стоило значению тикета превысить INT_MAX, как происходило переполнение, и возвращалось отрицательное число. Теперь всё исправлено:
//--- При смене направления позиции возвращает (1) тип ордера предыдущей позиции, (2) тикет ордера предыдущей позиции //--- (3) тип ордера текущей позиции, (4) тикет ордера текущей позиции //--- (5) тип и (6) тикет позиции до смены направления, (7) тип и (8) тикет позиции после смены направления ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT); } ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); }
В перечислении, возвращающем наименование статуса события ордерной системы, был пропущен статус модификации, и в некоторых случаях в описании торгового события указывался статус "Unknown". Исправим добавлением строки:
//+------------------------------------------------------------------+ //| Возвращает наименование статуса события | //+------------------------------------------------------------------+ string CEvent::StatusDescription(void) const { ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT); return ( status==EVENT_STATUS_MARKET_PENDING ? CMessage::Text(MSG_EVN_STATUS_MARKET_PENDING) : status==EVENT_STATUS_MARKET_POSITION ? CMessage::Text(MSG_EVN_STATUS_MARKET_POSITION) : status==EVENT_STATUS_HISTORY_PENDING ? CMessage::Text(MSG_EVN_STATUS_HISTORY_PENDING) : status==EVENT_STATUS_HISTORY_POSITION ? CMessage::Text(MSG_EVN_STATUS_HISTORY_POSITION) : status==EVENT_STATUS_BALANCE ? CMessage::Text(MSG_LIB_PROP_BALANCE) : status==EVENT_STATUS_MODIFY ? CMessage::Text(MSG_EVN_REASON_MODIFY) : CMessage::Text(MSG_EVN_STATUS_UNKNOWN) ); }
В классе торгового объекта поправим одно упущение: политика заполнения объёма (из перечисления ENUM_ORDER_TYPE_FILLING) неправильно передавалась в метод открытия позиции.
Внесём в торговые методы в файле MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh необходимые доработки. В методе открытия позиции в блоке заполнения структуры торгового запроса добавим установку политики заполнения объёма:
//+------------------------------------------------------------------+ //| Открывает позицию | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Очищаем структуры ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Заполняем структуру запроса this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = (ENUM_ORDER_TYPE)type; this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.type_filling= (type_filling>WRONG_VALUE ? type_filling : this.m_type_filling); this.m_request.comment = (comment==NULL ? this.m_comment : comment); //--- Возвращаем результат отсылки запроса на сервер #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::ResetLastError(); int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE); this.m_result.retcode=::GetLastError(); ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); if(ticket!=WRONG_VALUE) { this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); return true; } else { return false; } #endif }
В данном классе значение политики заполнения устанавливается в переменную m_type_filling изначально при инициализации библиотеки разрешёнными для ордеров значениями (метод CEngine::TradingSetCorrectTypeFilling). Если в метод открытия передать отрицательное значение для политики заполнения, то будет использовано значение из переменной m_type_filling, установленное при инициализации библиотеки. Если же необходимо указать иной тип заполнения объёма, то его нужно передать в параметре метода type_filling, и будет использоваться именно переданное значение.
Ранее добавленной строки не было, и политика заполнения, если нужно было указать иную политику, не по умолчанию, всегда имела значение Return (ORDER_FILLING_RETURN), так как поле type_filling структуры MqlTradeRequest не заполнялось и всегда имело нулевое значение. Теперь это исправлено.
Поправим аналогичные недоработки в иных методах класса, где так или иначе нужна политика заполнения объёма:
//+------------------------------------------------------------------+ //| Закрывает позицию | //+------------------------------------------------------------------+ bool CTradeObj::ClosePosition(const ulong ticket, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false if(!::PositionSelectByTicket(ticket)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); return false; } //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Получаем тип позиции и тип ордера, обратный типу позиции ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Очищаем структуры ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Заполняем структуру запроса this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.type = type; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = ::PositionGetDouble(POSITION_VOLUME); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling= this.m_type_filling; //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Возвращаем результат отсылки запроса на сервер #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif } //+------------------------------------------------------------------+ //| Частично закрывает позицию | //+------------------------------------------------------------------+ bool CTradeObj::ClosePositionPartially(const ulong ticket, const double volume, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError(); //--- Если не удалось выбрать позицию. Записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false if(!::PositionSelectByTicket(ticket)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,"#",(string)ticket,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_SELECT_POS),CMessage::Text(this.m_result.retcode)); return false; } //--- Если не удалось получить текущие цены - записываем код ошибки, описание ошибки, выводим сообщение в журнал и возвращаем false if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Получаем тип позиции и тип ордера, обратный типу позиции ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Получаем объём позиции double position_volume=::PositionGetDouble(POSITION_VOLUME); //--- Очищаем структуры ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Заполняем структуру запроса this.m_request.action = TRADE_ACTION_DEAL; this.m_request.position = ticket; this.m_request.symbol = this.m_symbol; this.m_request.magic = ::PositionGetInteger(POSITION_MAGIC); this.m_request.type = type; this.m_request.price = (position_type==POSITION_TYPE_SELL ? this.m_tick.ask : this.m_tick.bid); this.m_request.volume = (volume<position_volume ? volume : position_volume); this.m_request.deviation = (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); this.m_request.type_filling= this.m_type_filling; //--- Если счёт хеджевый, то в запишем в структуру тикет закрываемой позиции if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Возвращаем результат отсылки запроса на сервер #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; ::ResetLastError(); if(::OrderClose((int)this.m_request.position,this.m_request.volume,this.m_request.price,(int)this.m_request.deviation,clrNONE)) { this.m_result.retcode=::GetLastError(); this.m_result.deal=ticket; this.m_result.price=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderClosePrice() : this.m_request.price); this.m_result.volume=(::OrderSelect((int)ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif }
Теперь поправим аналогичные упущения в торговом классе библиотеки в файле \MQL5\Include\DoEasy\Trading.mqh.
В методе открытия позиции OpenPosition() структура торгового запроса заполняется значениями на основании значений, переданных в метод:
//--- Записываем объём, отклонение, комментарий и тип заливки в структуру запроса this.m_request.volume=volume; this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling());
а при вызове метода открытия позиции торгового объекта символа, в его параметрах указываются не значения, установленные в структуру торгового запроса, а те, которые были переданы в метод открытия позиции:
//--- В цикле по количеству попыток for(int i=0;i<this.m_total_try;i++) { //--- Отсылаем запрос res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling); //... ... ...
Исправим это:
//--- Отсылаем запрос res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,this.m_request.comment,this.m_request.deviation,this.m_request.type_filling);
Для того, чтобы отслеживать события, происходящие на всех открытых графиках, а не только на том, к которому прикреплена программа, работающая на основе библиотеки, на каждый открываемый (или уже открытый) график размещается индикатор, отслеживающий события графика и отправляющий их программе.
Если клиентский терминал был подключен сначала к одному серверу, и были открыты графики различных инструментов, а затем терминал был подключен к другому серверу, на котором нет инструментов, графики которых уже открыты, то программа не сможет разместить на таких графиках эти индикаторы, а в журнале будут записи об ошибках, которые говорят об ошибке создания индикатора, а не на отсутствие символов открытых графиков на сервере. Чтобы исправить эти неоднозначные записи в журнале, нужно добавить проверку наличия символа на сервере перед созданием индикатора.
В файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh в методе, создающем индикатор контроля событий, добавим проверку наличия символа на сервере:
//+------------------------------------------------------------------+ //| CChartObjectsControl: Создаёт индикатор контроля событий | //+------------------------------------------------------------------+ bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main) { //--- Если симола нет на сервере - возвращаем false bool is_custom=false; if(!::SymbolExist(this.Symbol(), is_custom)) { CMessage::ToLog(DFUN+" "+this.Symbol()+": ",MSG_LIB_SYS_NOT_SYMBOL_ON_SERVER); return false; } //--- Создаём индикатор this.m_chart_id_main=chart_id_main; string name="::"+PATH_TO_EVENT_CTRL_IND; ::ResetLastError(); this.m_handle_ind=::iCustom(this.Symbol(),this.Timeframe(),name,this.ChartID(),this.m_chart_id_main); if(this.m_handle_ind==INVALID_HANDLE) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR); CMessage::ToLog(DFUN,::GetLastError(),true); return false; } this.m_name_ind="EventSend_From#"+(string)this.ChartID()+"_To#"+(string)this.m_chart_id_main; ::Print ( DFUN,this.Symbol()," ",TimeframeDescription(this.Timeframe()),": ", CMessage::Text(MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR)," \"",this.m_name_ind,"\"" ); return true; }
Теперь при отсутствии символа на сервере, будет выведено сообщение об этом в журнал, и метод вернёт false.
В файле базового графического объекта библиотеки \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh в методе, возвращающем описание типа графического элемента, добавим один пропущенный тип — графический объект "Bitmap":
//+------------------------------------------------------------------+ //| Возвращает описание типа графического элемента | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_BITMAP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_BITMAP) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Контейнеры type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : //--- Стандартные элементы управления type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP) : type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR) : //--- Вспомогательные объекты элементов управления type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR) : type==GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GLARE_OBJ) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL) : type==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_THUMB) : "Unknown" ); }
Классы паттернов — их организацию и структуру, тоже немного переработаем. Класс базового абстрактного паттерна находится в файле \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, и в нём же далее пишутся классы паттернов, наследуемые от базового класса.
Разделим все классы паттернов по собственным файлам. Сейчас есть возможность выводить значки паттернов в виде обычных точек на графике, которые рисуются стандартными объектами "Трендовая линия" с обеими координатами цены и времени на одном баре. Решено было отказаться от этой возможности ради упрощения кода. Все значки будут рисоваться на графических объектах "Рисунок". Рисунки паттернов создаются по размерам баров, входящих в состав паттерна. Для изменения размеров объектов-рисунков при изменении горизонтального масштаба графика нужно ввести переменную, хранящую значение масштаба. Переменные для хранения остальных размеров графика уже есть в составе базового объекта паттерна. При изменении размеров графика, новые значения будут вписываться во все созданные объекты найденных паттернов, и они будут перерисовываться по новым размерам.
В папке библиотеки \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ создадим два новых файла PatternPinBar.mqh и PatternInsideBar.mqh — в них разместим методом Cut and Paste (вырезать и вставить) классы паттернов "Пин-Бар" и "Внутренний Бар", написанные прямо в файле класса базового абстрактного паттерна. Далее внесём в них изменения, а пока продолжим правку класса абстрактного паттерна.
Из защищённой секции класса удалим переменную-флаг m_draw_dots, указывающую на способ рисования значков паттерна точками, и объявим переменную для хранения ширины графика в пикселях:
protected: CForm *m_form; // Указатель на объект-форму CGCnvBitmap *m_bitmap; // Указатель на объект-рисунок int m_digits; // Значение Digits символа ulong m_symbol_code; // Символ в виде числа (сумма кодов символов наименования) string m_name_graph_obj; // Имя графического объекта, отображающего паттерн double m_price; // Уровень цены, на который устанавливается графический объект color m_color_bullish; // Цвет графического объекта, устанавливаемый значку бычьего паттерна color m_color_bearish; // Цвет графического объекта, устанавливаемый значку медвежьего паттерна color m_color_bidirect; // Цвет графического объекта, устанавливаемый значку двунаправленного паттерна color m_color; // Цвет графического объекта color m_color_panel_bullish; // Цвет панели бычьего паттерна color m_color_panel_bearish; // Цвет панели медвежьего паттерна color m_color_panel_bidirect; // Цвет панели двунаправленного паттерна int m_bars_formation; // Количество баров формации (вложенный паттерн) bool m_draw_dots; // Рисовать на графике точками int m_chart_scale; // Масштаб графика int m_chart_height_px; // Высота графика в пикселях int m_chart_width_px; // Ширина графика в пикселях double m_chart_price_max; // Максимум графика double m_chart_price_min; // Минимум графика public:
Методы, рассчитывающие ширину и высоту объекта-рисунка
//--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка int GetBitmapWidth(void); int GetBitmapHeight(void);
переименуем в правильные названия и объявим их как виртуальные:
//--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка virtual int CalculatetBitmapWidth(void); virtual int CalculatetBitmapHeight(void);
Всё же Get — это "Получить", а не "Рассчитать". Виртуальные методы позволят в наследуемых классах использовать собственные расчёты ширины и высоты рисунка, в зависимости от типа паттерна и способа его рисования.
В публичной секции класса удалим метод SetDrawAsDots():
public: //--- Удаляет графический объект bool DeleteGraphObj(bool redraw=false); //--- Устанавливает цвета графического объекта и цвет отображения паттерна void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Устанавливает флаг рисования меток паттернов как точки void SetDrawAsDots(const bool flag) { this.m_draw_dots=flag; } //--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна void SetColorPanelBullish(const color clr) { this.m_color_panel_bullish=clr; } void SetColorPanelBearish(const color clr) { this.m_color_panel_bearish=clr; } void SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr; } //--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна установкой значений RGB-компонент цвета void SetColorPanelBullish(const uchar R,const uchar G,const uchar B); void SetColorPanelBearish(const uchar R,const uchar G,const uchar B); void SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B); //--- Рисует значок паттерна на графике virtual void Draw(const bool redraw); //--- (1) Отображает, (2) скрывает значок паттерна на графике void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Отображает, (2) скрывает инфо-панель на графике void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
и объявим виртуальный метод Redraw():
public: //--- Удаляет графический объект bool DeleteGraphObj(bool redraw=false); //--- Устанавливает цвета графического объекта и цвет отображения паттерна void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна void SetColorPanelBullish(const color clr) { this.m_color_panel_bullish=clr; } void SetColorPanelBearish(const color clr) { this.m_color_panel_bearish=clr; } void SetColorPanelBiDirect(const color clr) { this.m_color_panel_bidirect=clr; } //--- Устанавливает цвет фона панели (1) бычьего, (2) медвежьего, (3) двунаправленного паттерна установкой значений RGB-компонент цвета void SetColorPanelBullish(const uchar R,const uchar G,const uchar B); void SetColorPanelBearish(const uchar R,const uchar G,const uchar B); void SetColorPanelBiDirect(const uchar R,const uchar G,const uchar B); //--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw) { return true; } //--- (1) Отображает, (2) скрывает значок паттерна на графике void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Отображает, (2) скрывает инфо-панель на графике void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
Метод Redraw() будет перерисовывать объект-рисунок с новыми размерами. А так, как у каждого типа паттерна могут быть свои типы рисунков, то метод объявлен виртуальным, и здесь просто возвращает true. В наследуемых классах метод будет переопределяться, чтобы перерисовывать именно тот рисунок, который рисуется для данного паттерна.
Там же, в публичной секции, напишем методы для установки и возврата ширины графика в пикселях:
//--- Устанавливает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика void SetChartScale(const int scale) { this.m_chart_scale=scale; } void SetChartHeightInPixels(const int height) { this.m_chart_height_px=height; } void SetChartWidthInPixels(const int width) { this.m_chart_width_px=width; } void SetChartPriceMax(const double price) { this.m_chart_price_max=price; } void SetChartPriceMin(const double price) { this.m_chart_price_min=price; } //--- Возвращает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика int ChartScale(void) const { return this.m_chart_scale; } int ChartHeightInPixels(void) const { return this.m_chart_height_px; } int ChartWidthInPixels(void) const { return this.m_chart_width_px; } double ChartPriceMax(void) const { return this.m_chart_price_max; } double ChartPriceMin(void) const { return this.m_chart_price_min; }
При изменении ширины графика, класс управления паттернами поочерёдно пропишет во все объекты паттернов новые размеры графика — чтобы в каждом из созданных объектов паттернов не получать свойства графика, а получить новые размеры лишь один раз при их изменении, а потом их вписать во все созданные объекты паттернов.
В конструкторе класса, в самом его конце, удалим строку с инициализацией уже не нужной переменной:
//--- Устанавливаем базовые цвета информационных панелей паттерна this.m_color_panel_bullish=clrLightGray; this.m_color_panel_bearish=clrLightGray; this.m_color_panel_bidirect=clrLightGray; this.m_form=NULL; this.m_bitmap=NULL; this.m_draw_dots=true; this.m_bars_formation=1; }
В методе, возвращающем описание вещественного свойства паттерна, добавим два блока кода для вывода описаний двух новых свойств паттерна:
//+------------------------------------------------------------------+ //| Возвращает описание вещественного свойства паттерна | //+------------------------------------------------------------------+ string CPattern::GetPropertyDescription(ENUM_PATTERN_PROP_DOUBLE property) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( property==PATTERN_PROP_BAR_PRICE_OPEN ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_HIGH ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_HIGH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_LOW ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_LOW)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_BAR_PRICE_CLOSE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_BAR_PRICE_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_CANDLE_SIZES ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : property==PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION ? CMessage::Text(MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),2) ) : "" ); }
В методе, отображающем инфо-панель на графике, теперь нет необходимости получать свойства графика, так как они уже прописаны в объекте паттерна при его создании, либо при изменении размеров графика.
Удалим из метода строки получения свойств графика:
//+------------------------------------------------------------------+ //| Отображает инфо-панель на графике | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- Если объекта панели ещё нету - создаём его if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Получаем ширину и высоту графика int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); //--- Рассчитываем координаты X и Y панели так, чтобы она не выходила за пределы графика
Теперь вместо ранее получаемых свойств графика используем эти свойства, предустановленные при создании объекта:
//+------------------------------------------------------------------+ //| Отображает инфо-панель на графике | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- Если объекта панели ещё нету - создаём его if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Рассчитываем координаты X и Y панели так, чтобы она не выходила за пределы графика int cx=(x+this.m_form.Width() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : x); int cy=(y+this.m_form.Height()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : y); //--- Устанавливаем панели рассчитанные координаты и отображаем панель if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy)) this.m_form.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Из метода рисования, отображения и скрытия значков паттерна удалим строки, связанные с рисованием значков паттерна точками:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ void CPattern::Draw(const bool redraw) { //--- Если графический объект ещё не создавался - создаём его if(::ObjectFind(this.m_chart_id,this.m_name_graph_obj)<0) this.CreateTrendLine(this.m_chart_id,this.m_name_graph_obj,0,this.Time(),this.m_price,this.Time(),this.m_price,this.m_color,5); //--- Иначе - отображаем else this.Show(redraw); } //+------------------------------------------------------------------+ //| Отображает значок паттерна на графике | //+------------------------------------------------------------------+ void CPattern::Show(const bool redraw=false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); return; } if(this.m_bitmap!=NULL) this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Скрывает значок паттерна на графике | //+------------------------------------------------------------------+ void CPattern::Hide(const bool redraw=false) { if(this.m_draw_dots) { ::ObjectSetInteger(this.m_chart_id,this.m_name_graph_obj,OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS); return; } if(this.m_bitmap!=NULL) this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Теперь эти методы выглядят проще:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ bool CPattern::Draw(const bool redraw) { this.Show(redraw); return true; } //+------------------------------------------------------------------+ //| Отображает значок паттерна на графике | //+------------------------------------------------------------------+ void CPattern::Show(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Скрывает значок паттерна на графике | //+------------------------------------------------------------------+ void CPattern::Hide(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Далее доработаем классы паттернов, коды которых были перенесены из файла абстрактного паттерна.
Откроем файл класса паттерна "Пин-Бар" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh и внесём изменения.
Ранее значки этого паттерна рисовались только точками при помощи стандартных графических объектов. Теперь же нам нужно добавить методы рисования точек на графическом объекте "Bitmap".
Добавим объявление новых методов в тело класса:
//+------------------------------------------------------------------+ //| PatternPinBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Нужно для mql4 //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| Класс паттерна "Пин-Бар" | //+------------------------------------------------------------------+ class CPatternPinBar : public CPattern { protected: //--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Рассчитывает (1) ширину, (2) высоту объекта-рисунка virtual int CalculatetBitmapWidth(void) { return(20); } virtual int CalculatetBitmapHeight(void) { return(40); } public: //--- Возвращает флаг поддержания паттерном указанного свойства virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Возвращает описание (1) статуса, (2) типа паттерна virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_PIN_BAR); } //--- Рисует значок паттерна на графике virtual bool Draw(const bool redraw); //--- Конструктор CPatternPinBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Виртуальные методы CalculatetBitmapWidth() и CalculatetBitmapHeight() всегда возвращают строго заданные размеры рисунка 20х40 пикселей потому, что для этого паттерна, рисуемого только на одном баре, не нужно рассчитывать ни высоту, ни ширину рисунка — он всегда должен быть одного размера. Точка привязки рисунка устанавливается по центру объекта, и всегда точки рисуются на холсте в верхней, либо в нижней половине рисунка, в зависимости от направления паттерна. Для бычьего паттерна точка рисуется в нижней половине рисунка, а для медвежьего — в верхней. Это даёт возможность отображать точки паттернов всегда на одном расстоянии от тени свечи, независимо от вертикального масштаба и периода графика, что достаточно удобно и практично.
В реализации метода, создающего внешний вид инфо-панели CreateInfoPanelView(), удалим строки получения свойств графика:
//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());
Теперь эти свойства предустановлены при создании объекта, либо обновляются при изменении размеров графика. Поэтому теперь используем значения из переменных, в которых записаны ширина и высота графика:
//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());
Напишем реализацию методов рисования значка паттерна.
Метод, рисующий значок паттерна на графике:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ bool CPatternPinBar::Draw(const bool redraw) { //--- Если объект-рисунок ещё не создавался - создаём его if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- отображаем this.Show(redraw); return true; }
В случае, если физически объекта-рисунка ещё нет — создаём его, и далее отображаем на графике.
Метод, создающий объект-рисунок:
//+------------------------------------------------------------------+ //| Создаёт объект-рисунок | //+------------------------------------------------------------------+ bool CPatternPinBar::CreateBitmap(void) { //--- Если объект-рисунок уже создан ранее - возвращаем true if(this.m_bitmap!=NULL) return true; //--- Рассчитываем координаты и размеры объекта datetime time=this.MotherBarTime(); double price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh()); int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Создаём объект Bitmap this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect); if(this.m_bitmap==NULL) return false; //--- Устанавливаем начало координат объекта по его центру и убираем всплывающую подсказку ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Рисуем внешний вид объекта-рисунка this.CreateBitmapView(); return true; }
Логика метода прокомментирована в коде. При установке координат объекта на графике учитываем направление паттерна. Если паттерн бычий, то координатой будет цена Low бара паттерна, если медвежий — координатой будет цена High.
Метод, создающий внешний вид объекта-рисунка:
//+------------------------------------------------------------------+ //| Создаёт внешний вид объекта-рисунка | //+------------------------------------------------------------------+ void CPatternPinBar::CreateBitmapView(void) { this.m_bitmap.Erase(CLR_CANV_NULL,0); int y=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_bitmap.Height()/2+6 : this.m_bitmap.Height()/2-6); int x=this.m_bitmap.Width()/2; int r=2; this.m_bitmap.DrawCircleFill(x,y,r,this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish); this.m_bitmap.Update(false); }
Сначала очищаем холст полностью прозрачным цветом. Затем определяем локальные координаты рисуемой точки. Для бычьего паттерна координатой Y будет половина высоты рисунка плюс 6 пикселей (ниже центра рисунка на 6 пикселей). Для медвежьего паттерна 6 пикселей вычитаем из координаты центра рисунка. Координатой X будет половина ширины рисунка. Радиус рисуемой окружности задан равным 2, что делает нормально видимую точку на любых таймфреймах графика.
Теперь аналогичные доработки сделаем в файле паттерна "Внутренний Бар" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh.
Объявим виртуальный метод перерисовки значка паттерна:
//+------------------------------------------------------------------+ //| PatternInsideBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Нужно для mql4 //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| Класс паттерна "Внутренний бар" | //+------------------------------------------------------------------+ class CPatternInsideBar : public CPattern { protected: //--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); public: //--- Возвращает флаг поддержания паттерном указанного свойства virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Возвращает описание (1) статуса, (2) типа паттерна virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_INSIDE_BAR); } //--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Конструктор CPatternInsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
За пределами тела класса напишем реализацию метода, перерисовывающего значок паттерна с новыми размерами:
//+------------------------------------------------------------------+ //| Перерисовывает с новым размером значок паттерна на графике | //+------------------------------------------------------------------+ bool CPatternInsideBar::Redraw(const bool redraw) { //--- Если объект-рисунок ещё не создавался - создаём и отображаем его в методе Draw() if(this.m_bitmap==NULL) return CPatternInsideBar::Draw(redraw); //--- Рассчитываем новые размеры объекта int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Если не удалось изменить размеры холста, возвращаем false if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Рисуем новый внешний вид объекта-рисунка с новыми размерами this.CreateBitmapView(); //--- отображаем и возвращаем true this.Show(redraw); return true; }
Логика метода прокомментирована в коде — здесь всё предельно просто: рассчитываем новые размеры холста, изменяем его размеры и рисуем на холсте новый рисунок по новым размерам.
В методе, создающем внешний вид инфо-панели, ранее была допущена ошибка при расчёте размера паттерна в барах:
//--- Создаём строки для описания паттерна, его параметров и критериев его поиска string name=::StringFormat("Inside Bar (%lu bars)",int(this.Time()-this.MotherBarTime())/::PeriodSeconds(this.Timeframe())+1); string param=this.DirectDescription();
Такой расчёт имеет право на существование, но только в том случае, если смежные бары паттерна не разделены выходными днями. Если же между левым и правым баром паттерна есть выходные дни, то они будут добавлены к количеству баров, и вместо 2 получим значение 4.
Поправим расчёт:
//--- Создаём строки для описания паттерна, его параметров и критериев его поиска string name=::StringFormat("Inside Bar (%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time())); string param=this.DirectDescription();
Теперь всегда будет возвращаться правильное количество баров между временем двух баров паттерна.
И так как теперь значения ширины и высоты графика заранее прописываются в переменных, в этом же методе удалим получение свойств графика:
//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика int chart_w=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); int chart_h=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); int cx=(this.m_form.RightEdge() >chart_w-1 ? chart_w-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>chart_h-1 ? chart_h-1-this.m_form.Height() : this.m_form.CoordY());
и поправим расчёт координат панели на использование предустановленных значений из переменных:
//--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY());
В методе рисования значка паттерна на графике удалим блок кода для рисования точками:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ void CPatternInsideBar::Draw(const bool redraw) { //--- Если установлен флаг рисования точками - вызываем метод родительского класса и уходим if(this.m_draw_dots) { CPattern::Draw(redraw); return; } //--- Если объект-рисунок ещё не создавался - создаём его if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return; } //--- отображаем this.Show(redraw); }
Теперь метод выглядит проще:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ bool CPatternInsideBar::Draw(const bool redraw) { //--- Если объект-рисунок ещё не создавался - создаём его if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- отображаем this.Show(redraw); return true; }
Все остальные доработки будем делать уже после создания класса нового паттерна "Внешний Бар"
Класс паттерна "Внешний бар"
В папке библиотеки \MQL5\Include\DoEasy\Objects\Series\Patterns\ создадим новый файл PatternOutsideBar.mqh класса CPatternOutsideBar.
Класс должен быть унаследован от базового класса объекта-паттерна, а его файл должен быть подключен к созданному файлу класса паттерна:
//+------------------------------------------------------------------+ //| PatternOutsideBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // Нужно для mql4 //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| Класс паттерна "Внешний бар" | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { }
Объявим стандартные для классов объектов паттернов методы:
//+------------------------------------------------------------------+ //| Класс паттерна "Внешний бар" | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { protected: //--- Создаёт (1) объект-рисунок, внешний вид (2) инфо-панели, (3) объекта-рисунка virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Рассчитывает высоту объекта-рисунка virtual int CalculatetBitmapHeight(void); public: //--- Возвращает флаг поддержания паттерном указанного свойства virtual bool SupportProperty(ENUM_PATTERN_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_PATTERN_PROP_STRING property) { return true; } //--- Возвращает описание (1) статуса, (2) типа паттерна virtual string StatusDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_STATUS_PA); } virtual string TypeDescription(void) const { return CMessage::Text(MSG_LIB_TEXT_PATTERN_TYPE_OUTSIDE_BAR);} //--- (1) Рисует, (2) перерисовывает с новым размером значок паттерна на графике virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Конструктор CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Все эти методы одинаковы для всех паттернов. Лишь их реализация немного отличается от паттерна к паттерну. Рассмотрим каждый метод.
В конструкторе классаопределим наименование паттерна, количество свечей, входящих в фигуру, и количество смежных, идущих подряд паттернов, равное количеству свечей паттерна:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CPatternOutsideBar::CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct) : CPattern(PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,id,direct,symbol,timeframe,rates) { this.SetProperty(PATTERN_PROP_NAME,"Outside Bar"); this.SetProperty(PATTERN_PROP_CANDLES,2); this.m_bars_formation=(int)this.GetProperty(PATTERN_PROP_CANDLES); }
Метод, создающий внешний вид инфо-панели:
//+------------------------------------------------------------------+ //| Создаёт внешний вид инфо-панели | //+------------------------------------------------------------------+ void CPatternOutsideBar::CreateInfoPanelView(void) { //--- Если объект-форма не создана - уходим if(this.m_form==NULL) return; //--- Изменяем тональность цветовой окраски для бычьих и медвежьих паттернов: бычий будет иметь синий оттенок, медвежий - красный color color_bullish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bullish,5),0,0,100); color color_bearish=this.m_form.ChangeRGBComponents(this.m_form.ChangeColorLightness(this.m_color_panel_bearish,5),100,0,0); color color_bidirect=this.m_color_panel_bidirect; //--- Объявляем массив для начального и конечного цветов градиентной заливки color clr[2]={}; //--- В зависимости от направления паттерна меняем светлоту соответствующих цветов - начальный чуть темнее, конечный - чуть светлее switch(this.Direction()) { case PATTERN_DIRECTION_BULLISH : clr[0]=this.m_form.ChangeColorLightness(color_bullish,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bullish,2.5); break; case PATTERN_DIRECTION_BEARISH : clr[0]=this.m_form.ChangeColorLightness(color_bearish,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bearish,2.5); break; default: clr[0]=this.m_form.ChangeColorLightness(color_bidirect,-2.5); clr[1]=this.m_form.ChangeColorLightness(color_bidirect,2.5); break; } //--- Устанавливаем цвета фона и рамки формы this.m_form.SetBackgroundColor(this.Direction()==PATTERN_DIRECTION_BULLISH ? color_bullish : this.Direction()==PATTERN_DIRECTION_BEARISH ? color_bearish : color_bidirect,true); this.m_form.SetBorderColor(clrGray,true); //--- Создаём строки для описания паттерна, его параметров и критериев его поиска string name=::StringFormat("Outside Bar (%.2f/%.2f)",this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE)); string param=::StringFormat("%s (%.2f/%.2f)",this.DirectDescription(),this.GetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION),this.GetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION)); //--- Устанавливаем координаты панели и рассчитываем её ширину и высоту в зависимости от размеров текстов, размещаемых на панели int x=3; int y=20; int w=4+(::fmax(20+this.m_form.TextWidth(name),::fmax(x+this.m_form.TextWidth(param),x+this.m_form.TextWidth(::TimeToString(this.Time()))))); int h=2+(20+this.m_form.TextHeight(this.DirectDescription())+this.m_form.TextHeight(::TimeToString(this.Time()))); //--- Устанавливаем ширину и высоту панели по рассчитанным значениям this.m_form.SetWidth(w); this.m_form.SetHeight(h); //--- В зависимости от размеров и координат графика, рассчитываем координаты панели так, чтобы она не выходила за пределы графика int cx=(this.m_form.RightEdge() >this.m_chart_width_px-1 ? this.m_chart_width_px-1-this.m_form.Width() : this.m_form.CoordX()); int cy=(this.m_form.BottomEdge()>this.m_chart_height_px-1 ? this.m_chart_height_px-1-this.m_form.Height() : this.m_form.CoordY()); this.m_form.SetCoordX(cx); this.m_form.SetCoordY(cy); //--- Заливаем фон градиентным цветом this.m_form.Erase(clr,200,true,false); //--- Рисуем рамку панели, иконку с (i), рисуем текст заголовка с пропорциями свечи и отделяем заголовок горизонтальной линией this.m_form.DrawFrameSimple(0,0,this.m_form.Width(),this.m_form.Height(),1,1,1,1,this.m_form.BorderColor(),200); this.m_form.DrawIconInfo(1,1,200); this.m_form.Text(20,3,name,clrBlack,200); this.m_form.DrawLine(1,18,this.m_form.Width()-1,18,clrDarkGray,250); //--- Под горизонтальной линией вписываем описание паттерна с критериями его поиска и датой определяющего паттерн бара y=20; this.m_form.Text(x,y,param,clrBlack,200); y+=this.m_form.TextHeight(::TimeToString(this.Time())); this.m_form.Text(x,y,::TimeToString(this.Time()),clrBlack,200); //--- Обновляем панель с перерисовкой графика this.m_form.Update(true); }
Логика метода прокомментирована в коде. Здесь рисуется панель с градиентной заливкой цветом с красным оттенком для медвежьего паттерна и с синим оттенком — для бычьего. В верхней части рисуется иконка со знаком (i) и пишется название паттерна с его характеристиками — соотношение двух баров паттерна. В нижней части описывается направление паттерна со значениями, заданными для поиска паттерна и временем найденного паттерна.
Метод, рисующий значок паттерна на графике:
//+------------------------------------------------------------------+ //| Рисует значок паттерна на графике | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Draw(const bool redraw) { //--- Если объект-рисунок ещё не создавался - создаём его if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- отображаем this.Show(redraw); return true; }
Метод, перерисовывающий с новым размером значок паттерна на графике:
//+------------------------------------------------------------------+ //| Перерисовывает с новым размером значок паттерна на графике | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Redraw(const bool redraw) { //--- Если объект-рисунок ещё не создавался - создаём и отображаем его в методе Draw() if(this.m_bitmap==NULL) return CPatternOutsideBar::Draw(redraw); //--- Рассчитываем новые размеры объекта int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Если не удалось изменить размеры холста, возвращаем false if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Рисуем новый внешний вид объекта-рисунка с новыми размерами this.CreateBitmapView(); //--- отображаем и возвращаем true this.Show(redraw); return true; }
Просто меняем размеры холста и заново рисуем на новом размере новый рисунок паттерна.
Метод, создающий объект-рисунок:
//+------------------------------------------------------------------+ //| Создаёт объект-рисунок | //+------------------------------------------------------------------+ bool CPatternOutsideBar::CreateBitmap(void) { //--- Если объект-рисунок уже создан ранее - возвращаем true if(this.m_bitmap!=NULL) return true; //--- Рассчитываем координаты и размеры объекта datetime time=this.MotherBarTime(); double price=(this.BarPriceHigh()+this.BarPriceLow())/2; int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Создаём объект Bitmap this.m_bitmap=this.CreateBitmap(this.ID(),this.GetChartID(),0,this.Name(),time,price,w,h,this.m_color_bidirect); if(this.m_bitmap==NULL) return false; //--- Устанавливаем начало координат объекта по его центру и убираем всплывающую подсказку ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Рисуем внешний вид объекта-рисунка this.CreateBitmapView(); return true; }
Если графический объект ранее уже создавался, то просто уходим из метода. Иначе — получаем цену и время, на которых будет размещаться графический объект, рассчитываем его ширину и высоту и создаём новый объект. Затем устанавливаем точку привязки по центру объекта и рисуем на нём его внешний вид. Цена, на которую встанет центральная точка графического объекта, рассчитывается по центру наибольшей свечи паттерна.
Метод, создающий внешний вид объекта-рисунка:
//+------------------------------------------------------------------+ //| Создаёт внешний вид объекта-рисунка | //+------------------------------------------------------------------+ void CPatternOutsideBar::CreateBitmapView(void) { this.m_bitmap.Erase(CLR_CANV_NULL,0); int x=this.m_bitmap.Width()/2-int(1<<this.m_chart_scale)/2; this.m_bitmap.DrawRectangleFill(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.m_color_bullish : this.m_color_bearish),80); this.m_bitmap.DrawRectangle(x,0,this.m_bitmap.Width()-1,this.m_bitmap.Height()-1,clrGray); this.m_bitmap.Update(false); }
Здесь сначала рисунок стирается с использованием полностью прозрачного цвета. Затем рассчитывается локальная X-координата начала закрашиваемой области, в зависимости от масштаба графика. Рисуется залитый цветом прямоугольник на всю высоту графического объекта с рассчитанной начальной координатой X, и сверху, по тем же координатам и с тем же размером, рисуется прямоугольная рамка.
Метод, рассчитывающий высоту объекта-рисунка:
//+------------------------------------------------------------------+ //| Рассчитывает высоту объекта-рисунка | //+------------------------------------------------------------------+ int CPatternOutsideBar::CalculatetBitmapHeight(void) { //--- Рассчитаем диапазон цен графика и диапазон цен паттерна double chart_price_range=this.m_chart_price_max-this.m_chart_price_min; double patt_price_range=this.BarPriceHigh()-this.BarPriceLow(); //--- По рассчитанным диапазонам цен рассчитаем и вернём высоту объекта-рисунка return (int)ceil(patt_price_range*this.m_chart_height_px/chart_price_range)+8; }
Здесь получаем диапазон цен графика — от максимальной цены на графике до минимальной цены на графике, и диапазон цен паттерна —от High определяющей свечи до Low определяющей свечи. Затем рассчитываем отношение одного диапазона к другому в пикселях и возвращаем полученное значение высоты объекта-рисунка, прибавив к нему 8 пикселей — по четыре сверху и снизу.
Класс паттерна "Внешний Бар" готов. Теперь нам нужно в классах таймсерий избавиться от множества одинаковых методов, каждый из которых выполняет одну и ту же работу для собственного паттерна. Просто сделаем по одному методу для каждого действия над паттерном, в котором будем указывать тип нужного паттерна.
Все требуемые параметры отношений свечей, входящих в паттерн, будем передавать в классы паттернов при помощи структуры MqlParam. Это позволит передавать в разные классы разных паттернов совершенно отличные друг от друга параметры, а не только сугубо заданные формальными переменными, одинаковыми для всех классов.
Пока названия этих переменных, в которых указываются различные соотношения свечей паттернов, менять не будем. Но при необходимости далее их переименуем в "безликие" переменные, типа "param1", "param2" и т.д. А в конструкторах классов уже будем назначать каждой такой переменной требуемый параметр паттерна. Но это только при необходимости. Пока же обойдёмся уже названными переменными, одинаковыми для всех паттернов.
В классе объекта-бара, расположенном в файле \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, поменяем наименование метода AddPattern() на AddPatternType():
//--- Возвращает себя CBar *GetObject(void) { return &this;} //--- Устанавливает (1) символ, таймфрейм и время бара, (2) параметры объекта-бар void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetProperties(const MqlRates &rates); //--- Добавляет тип паттерна на баре void AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type; } //--- Сравнивает объекты CBar между собой по всем возможным свойствам (для сортировки списков по указанному свойству объекта-бара)
Всё же данный метод добавляет тип паттерна в объект-бар, а не указатель на паттерн. Поэтому логичнее поменять наименование метода на более правильное.
Для того, чтобы можно было выбирать и получать из списков паттернов требуемые, нужно в файле \MQL5\Include\DoEasy\Services\Select.mqh класса CSelect подключить все файлы паттернов:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/ru/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/ru/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Включаемые файлы | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" #include "..\Objects\Symbols\Symbol.mqh" #include "..\Objects\PendRequest\PendRequest.mqh" #include "..\Objects\Series\\Patterns\PatternPinBar.mqh" #include "..\Objects\Series\\Patterns\PatternInsideBar.mqh" #include "..\Objects\Series\\Patterns\PatternOutsideBar.mqh" #include "..\Objects\Series\SeriesDE.mqh" #include "..\Objects\Indicators\Buffer.mqh" #include "..\Objects\Indicators\IndicatorDE.mqh" #include "..\Objects\Indicators\DataInd.mqh" #include "..\Objects\Ticks\DataTick.mqh" #include "..\Objects\Book\MarketBookOrd.mqh" #include "..\Objects\MQLSignalBase\MQLSignal.mqh" #include "..\Objects\Chart\ChartObj.mqh" #include "..\Objects\Graph\GCnvElement.mqh" #include "..\Objects\Graph\Standard\GStdGraphObj.mqh"
Это сделает классы паттернов доступными в классах таймсерий, где организована работа с паттернами.
Нам потребуется сравнивать на равенство массивы структур MqlParam. Напишем функции для сравнения полей структур и массива структур в файле \MQL5\Include\DoEasy\Services\DELib.mqh:
//+------------------------------------------------------------------+ //| Сравнивает структуры MqlParam между собой | //+------------------------------------------------------------------+ bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) { if(struct1.type!=struct2.type) return false; switch(struct1.type) { //--- целочисленные типы case TYPE_BOOL : case TYPE_CHAR : case TYPE_UCHAR : case TYPE_SHORT : case TYPE_USHORT : case TYPE_COLOR : case TYPE_INT : case TYPE_UINT : case TYPE_DATETIME : case TYPE_LONG : case TYPE_ULONG : return(struct1.integer_value==struct2.integer_value); //--- вещественные типы case TYPE_FLOAT : case TYPE_DOUBLE : return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==0); //--- строковый тип case TYPE_STRING : return(struct1.string_value==struct2.string_value); default : return false; } } //+------------------------------------------------------------------+ //| Сравнивает массив структур MqlParam между собой | //+------------------------------------------------------------------+ bool IsEqualMqlParamArrays(MqlParam &array1[],MqlParam &array2[]) { int total=ArraySize(array1); int size=ArraySize(array2); if(total!=size || total==0 || size==0) return false; for(int i=0;i<total;i++) { if(!IsEqualMqlParams(array1[i],array2[i])) return false; } return true; } //+------------------------------------------------------------------+
Классы управления паттернами находятся в файле класса таймсерии \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh. В классе управления абстрактным паттерном удалим теперь не нужную и добавим новые переменные:
//+------------------------------------------------------------------+ //| Класс управления абстрактным паттерном | //+------------------------------------------------------------------+ class CPatternControl : public CBaseObjExt { private: ENUM_TIMEFRAMES m_timeframe; // Период графика таймсерии паттерна string m_symbol; // Символ таймсерии паттерна double m_point; // Point символа bool m_used; // Флаг использования паттерна bool m_drawing; // Флаг отрисовки значка паттерна на графике bool m_draw_dots; // Флаг отрисовки значка паттерна на графике точками //--- Обрабатываемый паттерн ENUM_PATTERN_TYPE m_type_pattern; // Тип паттерна protected: //--- Пропорции свечи double m_ratio_body_to_candle_size; // Процентное отношение тела свечи к полному размеру свечи double m_ratio_larger_shadow_to_candle_size; // Процентное отношение размера большей тени к размеру свечи double m_ratio_smaller_shadow_to_candle_size; // Процентное отношение размера меньшей тени к размеру свечи double m_ratio_candle_sizes; // Процентное отношение размеров свечей uint m_min_body_size; // Минимальный размер тела свечи ulong m_object_id; // Уникальный код объекта на основе критериев поиска паттерна //--- Списки CArrayObj *m_list_series; // Указатель на список таймсерии CArrayObj *m_list_all_patterns; // Указатель на список всех паттернов CPattern m_pattern_instance; // Объект-паттерн для поиска по свойству //--- График ulong m_symbol_code; // Наименование символа графика в виде числа int m_chart_scale; // Масштаб графика int m_chart_height_px; // Высота графика в пикселях int m_chart_width_px; // Ширина графика в пикселях double m_chart_price_max; // Максимум графика double m_chart_price_min; // Минимум графика
Ранее в метод поиска паттерна мы передавали в переменной минимальный размер свечи для поиска паттерна:
virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const { return WRONG_VALUE; }
Теперь же этот размер будем передавать посредством MqlParam-переменной, поэтому сейчас данная переменная удалена из всех методов поиска паттерна во всех классах управления паттернами:
//--- (1) Ищет паттерн, возвращает направление или -1, если паттерн не найден, //--- (2) создаёт паттерн с указанным направлением, //--- (3) создаёт и возвращает уникальный код паттерна, //--- (4) возвращает список паттернов, управляемых этим объектом virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { return WRONG_VALUE; } virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar){ return NULL; } virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { return 0; } virtual CArrayObj*GetListPatterns(void) { return NULL; }
Из публичной секции класса удалим методы для установки и получения свойств рисования паттернов точками:
//--- (1) Устанавливает, (2) возвращает флаг рисования значков паттерна в виде точек
void SetDrawingAsDots(const bool flag,const bool redraw);
bool IsDrawingAsDots(void) const { return this.m_draw_dots; }
Объявим массив параметров паттерна, добавим методы для работы с новыми переменными, из виртуального метода CreateAndRefreshPatternList() удалим передачу параметра min_body_size, а в параметрическом конструкторе добавим передачу массива свойств паттерна посредством массива структур MqlParam. Объявим новый метод для перерисовки всех имеющихся паттернов на графике:
public: MqlParam PatternParams[]; // Массив параметров паттерна //--- Возвращает себя CPatternControl *GetObject(void) { return &this; } //--- (1) Устанавливает, (2) возвращает флаг использования паттерна void SetUsed(const bool flag) { this.m_used=flag; } bool IsUsed(void) const { return this.m_used; } //--- (1) Устанавливает, (2) возвращает флаг рисования паттерна void SetDrawing(const bool flag) { this.m_drawing=flag; } bool IsDrawing(void) const { return this.m_drawing; } //--- Устанавливает искомое процентное отношение (1) тела свечи к полному размеру свечи, //--- размера (2) верхней, (3) нижней тени к размеру свечи, (4) размеров свечей, (5) минимальный размер тела свечи void SetRatioBodyToCandleSizeValue(const double value) { this.m_ratio_body_to_candle_size=value; } void SetRatioLargerShadowToCandleSizeValue(const double value) { this.m_ratio_larger_shadow_to_candle_size=value; } void SetRatioSmallerShadowToCandleSizeValue(const double value) { this.m_ratio_smaller_shadow_to_candle_size=value; } void SetRatioCandleSizeValue(const double value) { this.m_ratio_candle_sizes=value; } void SetMinBodySize(const uint value) { this.m_min_body_size=value; } //--- Возвращает искомое процентное отношение (1) тела свечи к полному размеру свечи, //--- размера (2) верхней, (3) нижней тени к размеру свечи, (4) размеров свечей, (5) минимальный размер тела свечи double RatioBodyToCandleSizeValue(void) const { return this.m_ratio_body_to_candle_size; } double RatioLargerShadowToCandleSizeValue(void) const { return this.m_ratio_larger_shadow_to_candle_size; } double RatioSmallerShadowToCandleSizeValue(void) const { return this.m_ratio_smaller_shadow_to_candle_size; } double RatioCandleSizeValue(void) const { return this.m_ratio_candle_sizes; } int MinBodySize(void) const { return (int)this.m_min_body_size; } //--- Возвращает идентификатор объекта на основе критериев поиска паттерна virtual ulong ObjectID(void) const { return this.m_object_id; } //--- Возвращает (1) тип, (2) таймфрейм, (3) символ, (4) Point символа, (5) код символа паттерна ENUM_PATTERN_TYPE TypePattern(void) const { return this.m_type_pattern; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } string Symbol(void) const { return this.m_symbol; } double Point(void) const { return this.m_point; } ulong SymbolCode(void) const { return this.m_symbol_code; } //--- Устанавливает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика void SetChartScale(const int scale) { this.m_chart_scale=scale; } void SetChartHeightInPixels(const int height) { this.m_chart_height_px=height; } void SetChartWidthInPixels(const int width) { this.m_chart_width_px=width; } void SetChartPriceMax(const double price) { this.m_chart_price_max=price; } void SetChartPriceMin(const double price) { this.m_chart_price_min=price; } //--- Возвращает (1) масштаб графика, (2) высоту, (3) ширину графика в пикселях, (4) максимум, (5) минимум графика int ChartScale(void) const { return this.m_chart_scale; } int ChartHeightInPixels(void) const { return this.m_chart_height_px; } int ChartWidthInPixels(void) const { return this.m_chart_width_px; } double ChartPriceMax(void) const { return this.m_chart_price_max; } double ChartPriceMin(void) const { return this.m_chart_price_min; } //--- Сравнивает объекты CPatternControl между собой по всем возможным свойствам virtual int Compare(const CObject *node,const int mode=0) const; //--- Ищет паттерны и добавляет найденные в список всех паттернов virtual int CreateAndRefreshPatternList(void); //--- Выводит паттерны на график void DrawPatterns(const bool redraw=false); //--- Перерисовывает паттерны на графике с новым размером void RedrawPatterns(const bool redraw=false); //--- Защищённый параметрический конструктор protected: CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]); };
В параметрическом конструкторе класса получим недостающие свойства графика и заполним параметры паттерна из переданного в конструктор массива:
//+------------------------------------------------------------------+ //| CPatternControl::Защищённый параметрический конструктор | //+------------------------------------------------------------------+ CPatternControl::CPatternControl(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_PATTERN_STATUS status,const ENUM_PATTERN_TYPE type,CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]) : m_used(true),m_drawing(true) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERN_CONTROL; this.m_type_pattern=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT); this.m_object_id=0; this.m_list_series=list_series; this.m_list_all_patterns=list_patterns; for(int i=0;i<(int)this.m_symbol.Length();i++) this.m_symbol_code+=this.m_symbol.GetChar(i); this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE); this.m_chart_height_px=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); this.m_chart_width_px=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_price_max=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MAX); this.m_chart_price_min=::ChartGetDouble(this.m_chart_id,CHART_PRICE_MIN); //--- заполняем массив параметров данными из переданного в конструктор массива int count=::ArrayResize(this.PatternParams,::ArraySize(param)); for(int i=0;i<count;i++) { this.PatternParams[i].type = param[i].type; this.PatternParams[i].double_value = param[i].double_value; this.PatternParams[i].integer_value= param[i].integer_value; this.PatternParams[i].string_value = param[i].string_value; } }
В метод, ищущий паттерны и добавляющий найденные в список всех паттернов, внесём некоторые правки:
//+------------------------------------------------------------------+ //| CPatternControl::Ищет паттерны и добавляет | //| найденные в список всех паттернов | //+------------------------------------------------------------------+ int CPatternControl::CreateAndRefreshPatternList(void) { //--- Если не используется - уходим if(!this.m_used) return 0; //--- Сбрасываем флаг события таймсерии и очищаем список всех событий паттернов таймсерии this.m_is_event=false; this.m_list_events.Clear(); //--- Получаем дату открытия последнего (текущего) бара datetime time_open=0; if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open)) return 0; //--- Получаем список всех баров таймсерии кроме текущего CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS); if(list==NULL || list.Total()==0) return 0; //--- Структура данных "материнского" бара MqlRates pattern_mother_bar_data={}; //--- Сортируем полученный список по времени открытия баров list.Sort(SORT_BY_BAR_TIME); //--- В цикле от самого позднего бара for(int i=list.Total()-1;i>=0;i--) { //--- получаем очередной объект-бар из списка CBar *bar=list.At(i); if(bar==NULL) continue; //--- ищем паттерн относительно полученного бара ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data); //--- Если паттерна нет - идём к следующему бару if(direction==WRONG_VALUE) continue; //--- Паттерн на текущем баре цикла найден //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии ulong code=this.GetPatternCode(direction,bar.Time()); //--- Устанавливаем в образец код паттерна this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code); //--- Сортируем список всех паттернов по уникальному коду паттерна this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE); //--- ищем паттерн в списке по уникальному коду int index=this.m_list_all_patterns.Search(&this.m_pattern_instance); //--- Если в списке всех паттернов нет паттерна, равного образцу if(index==WRONG_VALUE) { //--- Создаём объект-паттерн CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar); if(pattern==NULL) continue; //--- Сортируем список всех паттернов по времени и вставляем паттерн в список по его времени this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); if(!this.m_list_all_patterns.InsertSort(pattern)) { delete pattern; continue; } //--- Добавляем тип паттерна в список типов паттернов объекта-бара bar.AddPatternType(pattern.TypePattern()); //--- В объект-паттерн добавляем указатель на бар, на котором он найден, и данные материнского бара pattern.SetPatternBar(bar); pattern.SetMotherBarData(pattern_mother_bar_data); //--- устанавливаем в объект-паттерн данные графика pattern.SetChartHeightInPixels(this.m_chart_height_px); pattern.SetChartWidthInPixels(this.m_chart_width_px); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_price_max); pattern.SetChartPriceMin(this.m_chart_price_min); //--- Если стоит флаг рисования - рисуем метку паттерна на графике if(this.m_drawing) pattern.Draw(false); //--- Получаем время предпоследнего бара в списке-коллекции (бар таймсерии с индексом 1) datetime time_prev=time_open-::PeriodSeconds(this.Timeframe()); //--- Если текущий бар в цикле - это бар с индексом 1 в таймсерии - создаёи и отправляем сообщение if(bar.Time()==time_prev) { // Здесь создание и отправка сообщения } } } //--- Сортируем список всех паттернов по времени и возвращаем общее количество паттернов в списке this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); return m_list_all_patterns.Total(); }
Метод теперь не имеет формальных параметров. При создании нового паттерна, в него сразу передаются и устанавливаются свойства графика. В самом конце блока цикла поиска паттерна сделаем заготовку для создания события нового паттерна — далее, в последующих статьях, сделаем отправку событий о появлении нового паттерна таймсерии.
Метод, перерисовывающий паттерны на графике с новым размером объекта-рисунка:
//+-------------------------------------------------------------------+ //|Перерисовывает паттерны на графике с новым размером объекта-рисунка| //+-------------------------------------------------------------------+ void CPatternControl::RedrawPatterns(const bool redraw=false) { //--- Получаем список паттернов, управляемых данным объектом управления CArrayObj *list=this.GetListPatterns(); if(list==NULL || list.Total()==0) return; //--- Сортируем полученный список по времени паттернов list.Sort(SORT_BY_PATTERN_TIME); //--- В цикле от самого позднего паттерна for(int i=list.Total()-1;i>=0;i--) { //--- получаем очередной объект-паттерн CPattern *obj=list.At(i); if(obj==NULL) continue; //--- Перерисовываем объект-рисунок паттерна на графике obj.Redraw(false); } //--- По окончании цикла, если установлен флаг, перерисовываем график if(redraw) ::ChartRedraw(this.m_chart_id); }
Просто в цикле перебираем список паттернов таймсерии и вызываем методы перерисовки каждого паттерна. Пока метод не оптимален — перерисовка осуществляется по всему списку паттернов. Хотя, можно организовать перерисовку только видимой на графике части истории. Но это позже.
Теперь доработаем классы управления паттернами. Они расположены далее в этом же файле. Найдём все вхождения строки —
const uint min_body_size
в формальных параметрах методов классов и удалим эти строки, — теперь минимальный размер искомой свечи паттерна передаётся совместно с параметрами паттернов в массиве структур MqlParam.
Просто отметим цветом все места, в которые были внесены изменения, чтобы не расписывать здесь каждую такую, однотипную для всех классов, строчку кода.
В классе управления паттерном "Пин бар":
//+------------------------------------------------------------------+ //| Класс управления паттерном "Пин бар" | //+------------------------------------------------------------------+ class CPatternControlPinBar : public CPatternControl { protected: //--- (1) Ищет паттерн, возвращает направление (или -1), //--- (2) создаёт паттерн с указанным направлением, //--- (3) создаёт и возвращает уникальный код паттерна //--- (4) возвращает список паттернов, управляемых этим объектом virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Создаёт идентификатор объекта на основе критериев поиска паттерна virtual ulong CreateObjectID(void); public: //--- Параметрический конструктор CPatternControlPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns, const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_PIN_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; this.m_ratio_body_to_candle_size=this.PatternParams[1].double_value; this.m_ratio_larger_shadow_to_candle_size=this.PatternParams[2].double_value; this.m_ratio_smaller_shadow_to_candle_size=this.PatternParams[3].double_value; this.m_ratio_candle_sizes=0; this.m_object_id=this.CreateObjectID(); } };
...
//+------------------------------------------------------------------+ //| CPatternControlPinBar::Ищет паттерн | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Получаем данные одного бара по времени CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL); //--- Если список пустой - возвращаем -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Размер тела свечи должен быть меньше, либо равен RatioBodyToCandleSizeValue() (по умолчанию 30%) размера всей свечи, //--- при этом размер тела не должен быть меньше min_body_size list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.RatioBodyToCandleSizeValue(),EQUAL_OR_LESS); list=CSelect::ByBarProperty(list,BAR_PROP_RATIO_BODY_TO_CANDLE_SIZE,this.m_min_body_size,EQUAL_OR_MORE); //--- Если список пустой - паттернов нет, возвращаем -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Определяем бычий паттерн
В классе управления паттерном "Внутренний бар":
//+------------------------------------------------------------------+ //| Класс управления паттерном "Внутренний бар" | //+------------------------------------------------------------------+ class CPatternControlInsideBar : public CPatternControl { private: //--- Проверяет и возвращает факт наличия паттерна на двух соседних барах bool CheckInsideBar(const CBar *bar1,const CBar *bar0) const { //--- Если переданы пустые объекты-бары - возвращаем false if(bar0==NULL || bar1==NULL) return false; //--- Возвращаем факт того, что бар справа полностью находится внутри размеров бара слева return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low()); } bool FindMotherBar(CArrayObj *list,MqlRates &rates) const { bool res=false; if(list==NULL) return false; //--- В цикле по списку, начиная с бара слева от определяющего for(int i=list.Total()-2;i>0;i--) { //--- Получаем указатели на два подряд идущих бара CBar *bar0=list.At(i); CBar *bar1=list.At(i-1); if(bar0==NULL || bar1==NULL) return false; //--- Если полученные бары представляют паттерн if(CheckInsideBar(bar1,bar0)) { //--- записываем в переменную MqlRates данные материнского бара и записываем в res значение true this.SetBarData(bar1,rates); res=true; } //--- Если паттерна нет - прерываем цикл else break; } //--- возвращаем результат return res; } protected: //--- (1) Ищет паттерн, возвращает направление (или -1), //--- (2) создаёт паттерн с указанным направлением, //--- (3) создаёт и возвращает уникальный код паттерна //--- (4) возвращает список паттернов, управляемых этим объектом virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Создаёт идентификатор объекта на основе критериев поиска паттерна virtual ulong CreateObjectID(void); public: //--- Параметрический конструктор CPatternControlInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns,const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_INSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; this.m_ratio_body_to_candle_size=0; this.m_ratio_larger_shadow_to_candle_size=0; this.m_ratio_smaller_shadow_to_candle_size=0; this.m_ratio_candle_sizes=0; this.m_object_id=this.CreateObjectID(); } };
...
//+------------------------------------------------------------------+ //| CPatternControlInsideBar::Ищет паттерн | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Получаем данные баров до указанного времени включительно
По аналогии с классами управления паттернами "Пин-Бар" и "Внутренний Бар" далее по тексту напишем класс управления паттерном "Внешний Бар":
//+------------------------------------------------------------------+ //| Класс управления паттерном "Внешний бар" | //+------------------------------------------------------------------+ class CPatternControlOutsideBar : public CPatternControl { private: //--- Проверяет и возвращает флаг соблюдения соотношения тела свечи к её размеру относительно заданного значения bool CheckProportions(const CBar *bar) const { return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue()); } //--- Возвращает соотношение близлежащих свечей по указанному бару double GetRatioCandles(const CBar *bar) const { //--- Если передан пустой объект - возвращаем 0 if(bar==NULL) return 0; //--- Получаем список баров со временем, меньшим, чем у переданного в метод CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS); if(list==NULL || list.Total()==0) return 0; //--- Устанавливаем списку флаг сортировки по времени баров и list.Sort(SORT_BY_BAR_TIME); //--- получаем указатель на последний бар в списке (ближний к переданному в метод) CBar *bar1=list.At(list.Total()-1); if(bar1==NULL) return 0; //--- Возвращаем соотношение размеров одного бара к другому return(bar.Size()>0 ? bar1.Size()*100.0/bar.Size() : 0.0); } //--- Проверяет и возвращает факт наличия паттерна на двух соседних барах bool CheckOutsideBar(const CBar *bar1,const CBar *bar0) const { //--- Если переданы пустые объекты-бары, или их типы по направлению не бычий и не мадвежий, либо одинаковы - возвращаем false if(bar0==NULL || bar1==NULL || bar0.TypeBody()==BAR_BODY_TYPE_NULL || bar1.TypeBody()==BAR_BODY_TYPE_NULL || bar0.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar1.TypeBody()==BAR_BODY_TYPE_CANDLE_ZERO_BODY || bar0.TypeBody()==bar1.TypeBody()) return false; //--- Рассчитываем соотношение указанных свечей и, если оно меньше заданного - возвращаем false double ratio=(bar0.Size()>0 ? bar1.Size()*100.0/bar0.Size() : 0.0); if(ratio<this.RatioCandleSizeValue()) return false; //--- Возвращаем факт того, что тело бара справа полностью перекрывает размеры тела бара слева, //--- а тени баров либо равны, либо тени бара справа перекрывают тени бара слева return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody()); } protected: //--- (1) Ищет паттерн, возвращает направление (или -1), //--- (2) создаёт паттерн с указанным направлением, //--- (3) создаёт и возвращает уникальный код паттерна //--- (4) возвращает список паттернов, управляемых этим объектом virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const; virtual CPattern *CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar); virtual ulong GetPatternCode(const ENUM_PATTERN_DIRECTION direction,const datetime time) const { //--- уникальный код паттерна = время открытия свечи + тип + статус + направление паттерна + таймфрейм + символ таймсерии return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Создаёт идентификатор объекта на основе критериев поиска паттерна virtual ulong CreateObjectID(void); public: //--- Параметрический конструктор CPatternControlOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, CArrayObj *list_series,CArrayObj *list_patterns, const MqlParam ¶m[]) : CPatternControl(symbol,timeframe,PATTERN_STATUS_PA,PATTERN_TYPE_OUTSIDE_BAR,list_series,list_patterns,param) { this.m_min_body_size=(uint)this.PatternParams[0].integer_value; // Минимальный размер свечей паттерна this.m_ratio_candle_sizes=this.PatternParams[1].double_value; // Процентное отношение размера поглощающей свечи к размеру поглощаемой this.m_ratio_body_to_candle_size=this.PatternParams[2].double_value; // Процентное отношение полного размера к размеру тела свечи this.m_object_id=this.CreateObjectID(); } };
Необходимые условия для определения паттерна: тело бара справа должно полностью перекрывать размеры тела бара слева, а тени баров либо равны, либо тени бара справа могут перекрывать тени бара слева. Такой поиск осуществляется в методе CheckOutsideBar(). Кроме того, необходимо учитывать соотношение размеров тел свечей, входящих в паттерн, относительно полного размера свечей. Эту проверку выполняет метод CheckProportions().
В конструкторе класса устанавливаем статус паттерна как "Price Action", тип паттерна как "Outside Bar" и устанавливаем все пропорции свечей паттерна из массива структур, переданного в конструктор.
Метод, создающий идентификатор объекта на основе критериев поиска паттерна:
//+------------------------------------------------------------------+ //| Создаёт идентификатор объекта на основе критериев поиска паттерна| //+------------------------------------------------------------------+ ulong CPatternControlOutsideBar::CreateObjectID(void) { ushort bodies=(ushort)this.RatioCandleSizeValue()*100; ushort body=(ushort)this.RatioBodyToCandleSizeValue()*100; ulong res=0; this.UshortToLong(bodies,0,res); return this.UshortToLong(body,1,res); }
Два критерия (процентные отношения размеров свечей и отношение тела свечи к полному размеру свечи) задаются в вещественных числах (в процентах) и не могут быть больше 100. Поэтому мы их переводим в целочисленные значения умножением на 100, а далее создаём ulong-идентификатор при помощи метода расширенного стандартного объекта библиотеки UshortToLong(), заполняющий указанные биты long-числа ushort-значениями:
//+------------------------------------------------------------------+ //| Упаковывает ushort-число в переданное long-число | //+------------------------------------------------------------------+ long CBaseObjExt::UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value) { if(to_byte>3) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_INDEX)); return 0; } return(long_value |= this.UshortToByte(ushort_value,to_byte)); } //+------------------------------------------------------------------+ //| Преобразует ushort-значение в заданный байт long-числа | //+------------------------------------------------------------------+ long CBaseObjExt::UshortToByte(const ushort value,const uchar to_byte) const { return(long)value<<(16*to_byte); }
Метод, создающий паттерн с указанным направлением:
//+------------------------------------------------------------------+ //| Создаёт паттерн с указанным направлением | //+------------------------------------------------------------------+ CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar) { //--- Если передан невалидный указатель на объект-бар - возвращаем NULL if(bar==NULL) return NULL; //--- Заполняем структуру MqlRates данными бара MqlRates rates={0}; this.SetBarData(bar,rates); //--- Создаём новый паттерн Внешний Бар CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction); if(obj==NULL) return NULL; //--- устанавливаем в свойства созданного объекта-паттерна пропорции свечи, на которой найден паттерн obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE,bar.RatioBodyToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE,bar.RatioLowerShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE,bar.RatioUpperShadowToCandleSize()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES,this.GetRatioCandles(bar)); //--- устанавливаем в свойства созданного объекта-паттерна критерии поиска свечи, на которой найден паттерн obj.SetProperty(PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION,this.RatioBodyToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioLargerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION,this.RatioSmallerShadowToCandleSizeValue()); obj.SetProperty(PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION,this.RatioCandleSizeValue()); //--- Устанавливаем объекту-паттерну идентификатор объекта управления obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID()); //--- Возвращаем указатель на созданный объект return obj; }
Метод создаёт новый объект класса паттерна "Внешний Бар", заполняет данные о его пропорциях и критериях поиска и возвращает указатель на созданный объект.
Метод поиска паттерна:
//+------------------------------------------------------------------+ //| CPatternControlOutsideBar::Ищет паттерн | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Получаем данные баров до указанного времени включительно CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS); //--- Если список пустой - возвращаем -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Сортируем список по времени открытия баров list.Sort(SORT_BY_BAR_TIME); //--- Получаем самый поздний бар из списка (определяющий бар) CBar *bar_patt=list.At(list.Total()-1); if(bar_patt==NULL) return WRONG_VALUE; //--- В цикле от следующего бара (материнский) for(int i=list.Total()-2;i>=0;i--) { //--- Получаем "материнский" бар CBar *bar_prev=list.At(i); if(bar_prev==NULL) return WRONG_VALUE; //--- Если пропорции баров не соответствуют паттерну - возвращаем -1 if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev)) return WRONG_VALUE; //--- проверяем, что полученные два бара являются паттерном. Если нет - возвращаем -1 if(!this.CheckOutsideBar(bar_prev,bar_patt)) return WRONG_VALUE; //--- Записываем данные "материнского" бара this.SetBarData(bar_prev,mother_bar_data); //--- Если на прошлом шаге паттерн найден - определяем и возвращаем его направление if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH) return PATTERN_DIRECTION_BULLISH; if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH) return PATTERN_DIRECTION_BEARISH; } //--- Паттернов не найдено - возвращаем -1 return WRONG_VALUE; }
Логика метода расписана в комментариях. В метод передаётся время открытия текущего бара. Получаем список всех баров кроме текущего (на нулевом баре паттерны не ищем). В цикле по полученному списку баров проверяем каждые близлежащие два бара на предмет их соответствия критериям паттерна. Если их соотношение представляет из себя паттерн — определяем и возвращаем направление паттерна.
Метод, возвращающий список паттернов, управляемых этим объектом:
//+------------------------------------------------------------------+ //| Возвращает список паттернов, управляемых этим объектом | //+------------------------------------------------------------------+ CArrayObj *CPatternControlOutsideBar::GetListPatterns(void) { CArrayObj *list=CSelect::ByPatternProperty(this.m_list_all_patterns,PATTERN_PROP_PERIOD,this.Timeframe(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_SYMBOL,this.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_TYPE,PATTERN_TYPE_OUTSIDE_BAR,EQUAL); return CSelect::ByPatternProperty(list,PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID(),EQUAL); }
Из общего списка всех паттернов получаем список, отфильтрованный по периоду графика, затем из полученного списка получаем список, отфильтрованный по имени символа. Далее полученный список фильтруется по типу паттерна "Внешний Бар" и полученный список фильтруется по идентификатору данного объекта управления. В итоге метод возвращает список только тех паттернов, которыми управляет данный класс.
Теперь нам нужно полностью переработать класс управления паттернами. Подробнее о нём можно почитать в этой статье.
Вместо длинных и однообразных методов для работы с каждым из типов паттернов, создадим несколько методов для работы с указанным паттерном. Далее в том же файле удалим из класса управления паттернами многочисленные однотипные методы, такие как:
... "Возвращает объект управления паттерном ..." CPatternControl *GetObjControlPattern ...XXXXX(), ... "Устанавливает флаг использования паттерна ... и создаёт объект управления если его ещё нет" void SetUsedPattern ... XXXXX(const bool flag), ... "Возвращает флаг использования паттерна ..." bool IsUsedPattern ...XXXXX(void), ... "Устанавливает флаг рисования точками паттерна ..." void SetDrawingAsDotsPattern ...XXXXX(const bool flag,const bool redraw), ... "Возвращает флаг рисования точками паттерна ..." bool IsDrawingAsDotsPattern ...XXXXX(void), ... "Ставит метки паттернов ... на графике" void DrawPattern ...XXXXX(const bool redraw=false)
В общем, таких методов очень много — на каждый паттерн по своему методу. В итоге — почти на 1300 строк кода. Удалим все строки класса управления паттернами, и заново перепишем весь класс. Теперь для работы с паттернами будет по несколько методов с возможностью выбора, с каким именно паттерном работать.
Полностью переписанный класс со всеми его методами теперь будет таким:
//+------------------------------------------------------------------+ //| Класс управления паттернами | //+------------------------------------------------------------------+ class CPatternsControl : public CBaseObjExt { private: CArrayObj m_list_controls; // Список контроллеров управления паттернами CArrayObj *m_list_series; // Указатель на список таймсерии CArrayObj *m_list_all_patterns; // Указатель на список всех паттернов //--- Данные таймсерии ENUM_TIMEFRAMES m_timeframe; // Таймфрейм таймсерии string m_symbol; // Символ таймсерии public: //--- Возвращает (1) таймфрейм, (2) символ таймсерии, (3) себя ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } string Symbol(void) const { return this.m_symbol; } CPatternsControl *GetObject(void) { return &this; } protected: //--- Создаёт объект управления указанным паттерном CPatternControl *CreateObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { switch(pattern) { case PATTERN_TYPE_HARAMI : return NULL; case PATTERN_TYPE_HARAMI_CROSS : return NULL; case PATTERN_TYPE_TWEEZER : return NULL; case PATTERN_TYPE_PIERCING_LINE : return NULL; case PATTERN_TYPE_DARK_CLOUD_COVER : return NULL; case PATTERN_TYPE_THREE_WHITE_SOLDIERS : return NULL; case PATTERN_TYPE_THREE_BLACK_CROWS : return NULL; case PATTERN_TYPE_SHOOTING_STAR : return NULL; case PATTERN_TYPE_HAMMER : return NULL; case PATTERN_TYPE_INVERTED_HAMMER : return NULL; case PATTERN_TYPE_HANGING_MAN : return NULL; case PATTERN_TYPE_DOJI : return NULL; case PATTERN_TYPE_DRAGONFLY_DOJI : return NULL; case PATTERN_TYPE_GRAVESTONE_DOJI : return NULL; case PATTERN_TYPE_MORNING_STAR : return NULL; case PATTERN_TYPE_MORNING_DOJI_STAR : return NULL; case PATTERN_TYPE_EVENING_STAR : return NULL; case PATTERN_TYPE_EVENING_DOJI_STAR : return NULL; case PATTERN_TYPE_THREE_STARS : return NULL; case PATTERN_TYPE_ABANDONED_BABY : return NULL; case PATTERN_TYPE_PIVOT_POINT_REVERSAL : return NULL; case PATTERN_TYPE_OUTSIDE_BAR : return new CPatternControlOutsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_INSIDE_BAR : return new CPatternControlInsideBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_PIN_BAR : return new CPatternControlPinBar(this.m_symbol,this.m_timeframe,this.m_list_series,this.m_list_all_patterns,param); case PATTERN_TYPE_RAILS : return NULL; //---PATTERN_TYPE_NONE default : return NULL; } } //--- Возвращает объект управления указанным паттерном CPatternControl *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { //--- В цикле по списку объектов управления int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- получаем очередной объект и, CPatternControl *obj=this.m_list_controls.At(i); //--- если это не объект управления указанным паттерном, идём к следующему if(obj==NULL || obj.TypePattern()!=pattern) continue; //--- Проверка условий поиска и возврат результата if(IsEqualMqlParamArrays(obj.PatternParams,param)) return obj; } //--- Не нашли - возвращаем NULL return NULL; } public: //--- Ищет и обновляет все активные паттерны void RefreshAll(void) { //--- В цикле по списку объектов управления паттернами int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- получаем очередной объект управления CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- если это тестер и текущие размеры графика не заданы, или не равны текущим - пробуем их получить и установить if(::MQLInfoInteger(MQL_TESTER)) { long int_value=0; double dbl_value=0; if(::ChartGetInteger(this.m_chart_id, CHART_SCALE, 0, int_value)) { if(obj.ChartScale()!=int_value) obj.SetChartScale((int)int_value); } if(::ChartGetInteger(this.m_chart_id, CHART_HEIGHT_IN_PIXELS, 0, int_value)) { if(obj.ChartHeightInPixels()!=int_value) obj.SetChartHeightInPixels((int)int_value); } if(::ChartGetInteger(this.m_chart_id, CHART_WIDTH_IN_PIXELS, 0, int_value)) { if(obj.ChartWidthInPixels()!=int_value) obj.SetChartWidthInPixels((int)int_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX, 0, dbl_value)) { if(obj.ChartPriceMax()!=dbl_value) obj.SetChartPriceMax(dbl_value); } if(::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN, 0, dbl_value)) { if(obj.ChartPriceMin()!=dbl_value) obj.SetChartPriceMin(dbl_value); } } //--- ищем и создаём новый паттерн obj.CreateAndRefreshPatternList(); } } //--- Устанавливает параметры чарта для всех объектов управления паттернами void SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { //--- В цикле по списку объектов управления паттернами int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- получаем очередной объект управления CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- Если объект получен - устанавливаем параметры чарта obj.SetChartHeightInPixels(height_px); obj.SetChartWidthInPixels(width_px); obj.SetChartScale(scale); obj.SetChartPriceMax(price_max); obj.SetChartPriceMin(price_min); } } //--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool flag) { CPatternControl *obj=NULL; //--- Получаем указатель на объект управления указанным паттерном obj=this.GetObjControlPattern(pattern,param); //--- Если указатель получен (объект существует) - устанавливаем флаг использования if(obj!=NULL) obj.SetUsed(flag); //--- Если объекта нет, а флаг передан как true else if(flag) { //--- Создаём новый объект управления паттернами obj=this.CreateObjControlPattern(pattern,param); if(obj==NULL) return; //--- Добавляем указатель на созданный объект в список if(!this.m_list_controls.Add(obj)) { delete obj; return; } //--- Устанавливаем флаг использования и параметры паттерна в объект управления obj.SetUsed(flag); obj.CreateAndRefreshPatternList(); } } //--- Возвращает флаг использования указанного паттерна bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { CPatternControl *obj=this.GetObjControlPattern(pattern,param); return(obj!=NULL ? obj.IsUsed() : false); } //--- Ставит метки указанного паттерна на графике void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.DrawPatterns(redraw); } //--- Перерисовывает объекты-рисунки указанного паттерна на графике void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.RedrawPatterns(redraw); } //--- Конструктор CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns); }; //+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CPatternsControl::CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_PATTERNS_CONTROLLERS; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_list_series=list_timeseries; this.m_list_all_patterns=list_all_patterns; } //+------------------------------------------------------------------+
Логика методов прокомментирована в коде. Хочется пояснить по выделенному блоку кода: при работе в визуальном режиме тестера обработчик OnChartEvent() практически бесполезен. А именно в нём мы отслеживаем изменение размеров графика по событию CHARTEVENT_CHART_CHANGE и записываем новые размеры графика в объект управления паттернами, откуда эти данные поступают в объекты паттернов. Но в тестере это не работает. Поэтому в выделенном блоке кода в тестере отслеживается изменение размеров графика и, при его изменении, новые данные заносятся в объекты управления паттернами.
Далее по коду в этом же файле \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh у нас написан класс таймсерии CSeriesDE. Доработаем этот класс.
Каждый раз при поиске какого-либо объекта-бара в списке таймсерии у нас создаётся новый объект при помощи оператора new, устанавливаются его параметры, объект с идентичными параметрами ищется в списке, а затем, этот новый созданный объект удаляется:
//+------------------------------------------------------------------+ //| Возвращает объект-бар по времени в таймсерии | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(); if(obj==NULL) return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; return this.m_list_series.At(index); }
Таким образом, в некоторых ситуациях получается постоянное создание-удаление объекта в цикле. Это неправильно. Лучше создать единственный объект-экземпляр для поиска и использовать его для установки параметров и использования как образец для поиска. Таким образом избавимся от постоянного пересоздания объектов.
Объявим экземпляр объекта-бара для поиска:
//+------------------------------------------------------------------+ //| Класс "Таймсерия" | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: CBar m_bar_tmp; // Объект-бар для поиска ENUM_TIMEFRAMES m_timeframe; // Таймфрейм string m_symbol; // Символ
и перепишем заново метод, возвращающий объект-бар по времени в таймсерии:
//+------------------------------------------------------------------+ //| Возвращает объект-бар по времени в таймсерии | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { this.m_bar_tmp.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(&this.m_bar_tmp); return this.m_list_series.At(index); }
Теперь, вместо создания и удаления нового объекта, мы в единственный экземпляр устанавливаем нужные параметры для поиска и ищем идентичный экземпляр в списке по образцу. Объект создан всего один раз в конструкторе класса и постоянно используется без пересоздания.
Точно так же, как в классе управления паттернами, удалим длинные списки методов для работы с каждым паттерном, и заменим их на несколько методов с выбором нужного паттерна:
//+------------------------------------------------------------------+ //| Работа с паттернами | //+------------------------------------------------------------------+ //--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool flag) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetUsedPattern(pattern,param,flag); } //--- Возвращает флаг использования указанного паттерна bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : false); } //--- Ставит метки указанного паттерна на графике void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { if(this.m_patterns_control!=NULL) this.m_patterns_control.DrawPattern(pattern,param,redraw); } //--- Перерисовывает объекты-рисунки указанного паттерна на графике void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { if(this.m_patterns_control!=NULL) this.m_patterns_control.RedrawPattern(pattern,param,redraw); } //--- Устанавливает параметры чарта для объектов управления паттернами void SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { if(this.m_patterns_control!=NULL) this.m_patterns_control.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); }
Теперь для выбора нужного паттерна достаточно указать в параметрах методов его тип, а не использовать по собственному методу для каждого из паттернов.
В классе таймсерии символа в файле D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh точно так же изменим методы для работы с паттернами.
В теле класса, в самом конце, под заголовком
//--- Конструкторы CTimeSeriesDE(CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL; this.m_list_all_patterns=list_all_patterns; } CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol); //+------------------------------------------------------------------+ //| Методы работы с паттернами | //+------------------------------------------------------------------+
написан длинный список объявлений методов для работы с паттернами. Удалим все эти объявления и заменим на объявление нескольких методов с выбором нужного паттерна:
//--- Конструкторы CTimeSeriesDE(CArrayObj *list_all_patterns) { this.m_type=OBJECT_DE_TYPE_SERIES_SYMBOL; this.m_list_all_patterns=list_all_patterns; } CTimeSeriesDE(CArrayObj *list_all_patterns,const string symbol); //+------------------------------------------------------------------+ //| Методы работы с паттернами | //+------------------------------------------------------------------+ //--- Устанавливает флаг использования указанного паттерна и создаёт объект управления если его ещё нет void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Возвращает флаг использования указанного паттерна Харами bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe); //--- Рисует метки указанного паттерна на графике void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Перерисовывает объекты-рисунки указанного паттерна на графике void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Устанавливает параметры чарта для объектов управления паттернами на указанном таймфрейме void SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px); };
За пределами тела класса, в самом конце листинга файла, под таким же заголовком написаны реализации объявленных методов. Удалим весь этот длинный список и заменим на новые методы:
//+------------------------------------------------------------------+ //| Работа с паттернами таймсерий | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Устанавливает флаг использования указанного паттерна | //| и создаёт объект управления если его ещё нет | //+------------------------------------------------------------------+ void CTimeSeriesDE::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool flag) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetUsedPattern(pattern,param,flag); } //+------------------------------------------------------------------+ //| Возвращает флаг использования указанного паттерна | //+------------------------------------------------------------------+ bool CTimeSeriesDE::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.GetSeries(timeframe); return(series!=NULL ? series.IsUsedPattern(pattern,param) : false); } //+------------------------------------------------------------------+ //| Рисует метки указанного паттерна на графике | //+------------------------------------------------------------------+ void CTimeSeriesDE::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.DrawPattern(pattern,param,redraw); } //+------------------------------------------------------------------+ //| Перерисовывает объекты-рисунки указанного паттерна на графике | //+------------------------------------------------------------------+ void CTimeSeriesDE::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.RedrawPattern(pattern,param,redraw); } //+------------------------------------------------------------------+ //| Устанавливает параметры чарта для объектов управления паттернами | //| на указанном таймфрейме | //+------------------------------------------------------------------+ void CTimeSeriesDE::SetChartPropertiesToPattCtrl(const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { CSeriesDE *series=this.GetSeries(timeframe); if(series!=NULL) series.SetChartPropertiesToPattCtrl(price_max,price_min,scale,height_px,width_px); }
В каждый метод передаётся требуемый таймфрейм графика, с паттернами которого нужно работать, и необходимые параметры, по которым выбирается объект управления паттернами. Далее получается указатель на требуемую таймсерию, и возвращается результат обращения к одноимённому методу класса таймсерии.
Сделаем доработки и в файле класса коллекции таймсерий \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
В этом классе будем получать размеры графика, их изменение, и отсылать в классы управления паттернами. Добавим новые переменные для хранения размеров графика:
//+------------------------------------------------------------------+ //| Коллекция таймсерий символов | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt { private: CListObj m_list; // Список используемых таймсерий символов CListObj m_list_all_patterns; // Список всех паттернов всех используемых таймсерий символов CChartObjCollection *m_charts; // Указатель на коллекцию чартов double m_chart_max; // Максимум графика double m_chart_min; // Минимум графика int m_chart_scale; // Масштаб графика int m_chart_wpx; // Ширина графика в пикселях int m_chart_hpx; // Высота графика в пикселях //--- Возвращает индекс таймсерии по имени символа int IndexTimeSeries(const string symbol); public:
Под заголовком —
//+------------------------------------------------------------------+ //| Работа с паттернами таймсерий | //+------------------------------------------------------------------+
так же, как и в предыдущих классах, удалим объявленные методы и впишем новые с указанием типа паттерна:
//--- Копирует в массив указанное double-свойство указанной таймсерии указанного символа //--- Независимо от направления индексации массива, копирование производится как в массив-таймсерию bool CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //+------------------------------------------------------------------+ //| Работа с паттернами таймсерий | //+------------------------------------------------------------------+ //--- Устанавливает флаг использования указанного паттерна void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Возвращает флаг использования указанного паттерна bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Рисует метки указанного паттерна на графике void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Перерисовывает объекты-рисунки указанного паттерна на графике void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Устанавливает параметры чарта для объектов управления паттернами на указанных символе и таймфрейме void SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px);
В конце тела класса объявим обработчик событий:
//--- Инициализация void OnInit(CChartObjCollection *charts) { this.m_charts=charts; } //--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Конструктор CTimeSeriesCollection(void); };
В конструкторе класса запишем размеры графика в новые переменные:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CTimeSeriesCollection::CTimeSeriesCollection(void) { this.m_type=COLLECTION_SERIES_ID; this.m_list.Clear(); this.m_list.Sort(); this.m_list.Type(COLLECTION_SERIES_ID); this.m_list_all_patterns.Clear(); this.m_list_all_patterns.Sort(); this.m_list_all_patterns.Type(COLLECTION_SERIES_PATTERNS_ID); this.m_chart_scale=(int)::ChartGetInteger(this.m_chart_id,CHART_SCALE);//-1; this.m_chart_max=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MAX); this.m_chart_min=::ChartGetDouble(this.m_chart_id, CHART_PRICE_MIN); this.m_chart_wpx=(int)::ChartGetInteger(this.m_chart_id,CHART_WIDTH_IN_PIXELS); this.m_chart_hpx=(int)::ChartGetInteger(this.m_chart_id,CHART_HEIGHT_IN_PIXELS); }
За пределами тела класса под заголовком —
//+------------------------------------------------------------------+ //| Работа с паттернами таймсерий | //+------------------------------------------------------------------+
удалим все методы для работы с каждым конкретным паттерном. Вместо удалённых методов напишем новые — с указанием типа паттерна:
//+------------------------------------------------------------------+ //| Работа с паттернами таймсерий | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Устанавливает флаг использования указанного паттерна | //| и создаёт объект управления если его ещё нет | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetUsedPattern(pattern,param,timeframe,flag); } //+------------------------------------------------------------------+ //| Возвращает флаг использования указанного паттерна | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); return(timeseries!=NULL ? timeseries.IsUsedPattern(pattern,param,timeframe) : false); } //+------------------------------------------------------------------+ //| Рисует метки указанного паттерна на графике | //+------------------------------------------------------------------+ void CTimeSeriesCollection::DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.DrawPattern(pattern,param,timeframe,redraw); } //+------------------------------------------------------------------+ //| Перерисовывает объекты-рисунки указанного паттерна на графике | //+------------------------------------------------------------------+ void CTimeSeriesCollection::RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.RedrawPattern(pattern,param,timeframe,redraw); } //+------------------------------------------------------------------+ //| Устанавливает параметры чарта для объектов управления паттернами | //| на указанных символе и таймфрейме | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetChartPropertiesToPattCtrl(const string symbol,const ENUM_TIMEFRAMES timeframe,const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries!=NULL) timeseries.SetChartPropertiesToPattCtrl(timeframe,price_max,price_min,scale,height_px,width_px); }
В каждый метод передаётся требуемый таймфрейм и символ графика, с паттернами которого нужно работать, и необходимые параметры, по которым выбирается объект управления паттернами. Далее получается указатель на требуемую таймсерию и возвращается результат обращения к одноимённому методу класса таймсерии символа.
Ниже напишем обработчик событий:
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CTimeSeriesCollection::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Получаем объект текущего графика CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID()); if(chart==NULL) return; //--- Получаем объект главного окна текущего графика CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),0); if(wnd==NULL) return; //--- Если график изменён bool res=false; if(id==CHARTEVENT_CHART_CHANGE) { //--- контролируем изменение масштаба графика int scale=(int)::ChartGetInteger(chart.ID(),CHART_SCALE); if(this.m_chart_scale!=scale) { this.m_chart_scale=scale; res=true; } //--- контролируем изменение ширины графика в пикселях int chart_wpx=(int)::ChartGetInteger(chart.ID(),CHART_WIDTH_IN_PIXELS); if(this.m_chart_wpx!=chart_wpx) { this.m_chart_wpx=chart_wpx; res=true; } //--- контролируем изменение высоты графика в пикселях int chart_hpx=(int)::ChartGetInteger(chart.ID(),CHART_HEIGHT_IN_PIXELS); if(this.m_chart_hpx!=chart_hpx) { this.m_chart_hpx=chart_hpx; res=true; } //--- контролируем изменение максимума графика double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX); if(this.m_chart_max!=chart_max) { this.m_chart_max=chart_max; res=true; } //--- контролируем изменение минимума графика double chart_min=::ChartGetDouble(chart.ID(),CHART_PRICE_MIN); if(this.m_chart_min!=chart_min) { this.m_chart_min=chart_min; res=true; } //--- Если есть хоть одно изменение if(res) { //--- Записываем в объекты управления паттернами новые значения свойств графика this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx); //--- Получаем список паттернов на текущем графике CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL); //--- Если список паттернов получен if(list!=NULL) { //--- В цикле по списку паттернов int total=list.Total(); for(int i=0;i<total;i++) { //--- получаем очередной объект-паттерн CPattern *pattern=list.At(i); if(pattern==NULL) continue; //--- Устанавливаем в объект-паттерн новые свойства графика pattern.SetChartWidthInPixels(this.m_chart_wpx); pattern.SetChartHeightInPixels(this.m_chart_hpx); pattern.SetChartScale(this.m_chart_scale); pattern.SetChartPriceMax(this.m_chart_max); pattern.SetChartPriceMin(this.m_chart_min); //--- Перерисовываем объект-рисунок паттерна с новыми размерами pattern.Redraw(false); } //--- Обновляем график ::ChartRedraw(chart.ID()); } } } }
Вся логика обработчика полностью расписана в комментариях к коду. Именно здесь, при обнаружении изменения размеров графика, новые размеры передаются в классы управления паттернами таймсерий, и паттерны перерисовываются в соответствии с новыми размерами графика.
Теперь доработаем главный класс библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh.
Здесь тоже в теле класса есть длинный список методов для работы с паттернами. Удалим только те методы, которые отвечают за рисование паттернов в виде точек. Остальные методы нужно доработать — теперь требуется отправлять параметры паттерна в методы работы с ним при помощи массива структур MqlParam. Во всех пока неиспользуемых методах будем заполнять только одно поле структуры, в котором передаётся минимальный размер свечей паттерна, например:
//--- Устанавливает флаг использования паттерна Харами и создаёт объект управления если его ещё нет void SeriesSetUsedPatternHarami(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=0; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_HARAMI,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Остальные методы для пока ещё неиспользуемых паттернов идентичны, и рассматривать их нет необходимости — они есть в прилагаемых к статье файлах.
Рассмотрим методы для работы с уже созданными в библиотеке паттернами.
Метод, устанавливающий флаг использования паттерна Внешний бар (Поглощение) и создающий объект управления если его ещё нет:
//--- Устанавливает флаг использования паттерна Внешний бар (Поглощение) и создаёт объект управления если его ещё нет void SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Флаг использования Price Action Внешний бар const double ratio_candles=70, // Процентное отношение размера поглощающей свечи к размеру поглощаемой const double ratio_body_to_shadows=80, // Процентное отношение размеров теней к размеру тела свечи const uint min_body_size=3) // Минимальный размер тела свечи { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Процентное отношение размера поглощающей свечи к размеру поглощаемой param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Процентное отношение размеров теней к размеру тела свечи param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_body_to_shadows; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Метод, устанавливающий флаг использования паттерна Внутренний бар и создающий объект управления если его ещё нет:
//--- Устанавливает флаг использования паттерна Внутренний бар и создаёт объект управления если его ещё нет void SeriesSetUsedPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag,const uint min_body_size=3) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
В этом методе заполняется только одно поле структуры, так как паттерн не имеет настраиваемых свойств, кроме общего для всех паттернов свойства минимального размера свечей.
Метод, устанавливающий флаг использования паттерна Пин бар и создающий объект управления если его ещё нет:
//--- Устанавливает флаг использования паттерна Пин бар и создаёт объект управления если его ещё нет void SeriesSetUsedPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Флаг использования Price Action Пин бар const double ratio_body=30, // Процентное отношение тела свечи к полному размеру свечи const double ratio_larger_shadow=60, // Процентное отношение размера большей тени к размеру свечи const double ratio_smaller_shadow=30, // Процентное отношение размера меньшей тени к размеру свечи const uint min_body_size=3) // Минимальный размер тела свечи { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Процентное отношение тела свечи к полному размеру свечи param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Процентное отношение размера большей тени к размеру свечи param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Процентное отношение размера меньшей тени к размеру свечи param[3].type=TYPE_DOUBLE; param[3].double_value=ratio_smaller_shadow; } this.m_time_series.SetUsedPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag); }
Рассмотрим методы для отображения паттернов на графике:
- для уже реализованных паттернов в библиотеке
- для пока ещё не созданного паттерна:
//--- Рисует метки паттернов Внешний бар (Поглощение) на графике void SeriesDrawPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_candles=70, // Процентное отношение размера поглощающей свечи к размеру поглощаемой const double ratio_body_to_shadows=80, // Процентное отношение размеров теней к размеру тела свечи const uint min_body_size=3, // Минимальный размер тела свечи const bool redraw=false) // Флаг перерисовки графика { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Процентное отношение размера поглощающей свечи к размеру поглощаемой param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Процентное отношение размеров теней к размеру тела свечи param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_body_to_shadows; } this.m_time_series.DrawPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } //--- Рисует метки паттернов Внутренний бар на графике void SeriesDrawPatternInsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint min_body_size=3,const bool redraw=false) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; } this.m_time_series.DrawPattern(PATTERN_TYPE_INSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); } //--- Рисует метки паттернов Пин бар на графике void SeriesDrawPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_body=30, // Процентное отношение тела свечи к полному размеру свечи const double ratio_larger_shadow=60, // Процентное отношение размера большей тени к размеру свечи const double ratio_smaller_shadow=30, // Процентное отношение размера меньшей тени к размеру свечи const uint min_body_size=3, // Минимальный размер тела свечи const bool redraw=false) // Флаг перерисовки графика { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Процентное отношение тела свечи к полному размеру свечи param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Процентное отношение размера большей тени к размеру свечи param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Процентное отношение размера меньшей тени к размеру свечи param[3].type=TYPE_DOUBLE; param[3].double_value=ratio_smaller_shadow; } this.m_time_series.DrawPattern(PATTERN_TYPE_PIN_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); if(redraw) ::ChartRedraw(); } //--- Рисует метки паттернов Рельсы на графике void SeriesDrawPatternRails(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false) { MqlParam param[]={}; if(::ArrayResize(param,1)==1) { param[0].type=TYPE_UINT; param[0].integer_value=0; } this.m_time_series.DrawPattern(PATTERN_TYPE_RAILS,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),redraw); }
Остальные методы для других паттернов идентичны методу для ещё не реализованного паттерна "Рельсы", и рассматривать их здесь не имеет смысла.
В самом конце тела класса объявим обработчик событий:
public: //--- Создаёт и возвращает составной магик из заданного значения магика, идентификаторов первой и второй групп и идентификатора отложенного запроса uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); //--- Обработчик событий void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Обработка событий библиотеки DoEasy void OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Работа с событиями в тестере void EventsHandling(void); };
За пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CEngine::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { this.m_graph_objects.OnChartEvent(id,lparam,dparam,sparam); this.m_time_series.OnChartEvent(id,lparam,dparam,sparam); }
Здесь сначала вызывается обработчик событий класса-коллекции графических объектов, и следом — обработчик событий коллекции таймсерий.
В обработчике событий библиотеки CEngine::OnDoEasyEvent() в блоке обработки событий таймсерий добавим блок для будущей обработки событий появления новых паттернов:
//--- Обработка событий таймсерий else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- Событие "Новый бар" if(idx==SERIES_EVENTS_NEW_BAR) { ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); CArrayObj *list=this.m_buffers.GetListBuffersWithID(); if(list!=NULL) { int total=list.Total(); for(int i=0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) continue; string symbol=sparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; if(buff.Symbol()==symbol && buff.Timeframe()==timeframe ) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) continue; int count=::fmin((int)series.AvailableUsedData(),::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated())); this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count); } } } } //--- Событие "Пропущены бары" if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam); } //--- Событие "Новый паттерн" if(idx==SERIES_EVENTS_PATTERN) { // Здесь обработка события появления нового паттерна } }
Здесь пока пусто, но, когда в дальнейшем будем делать отсылку событий паттернов, пропишем сюда обработку появления нового паттерна.
На этом доработка библиотеки завершена. Теперь проверим работу по поиску новых паттернов и обработку изменения горизонтального масштаба графика.
Тестирование
Для тестирования возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part136\ под новым именем TestDoEasy136.mq5.
Удалим из настроек флаг рисования паттернов точками:
sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput bool InpDrawPatternsAsDots= true; // Draw Patterns as dots
Добавим в настройки флаги использования паттернов и параметры для поиска паттерна "Внешний Бар":
sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_CURRENT; // Mode of used timeframes list sinput string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator) sinput ENUM_INPUT_YES_NO InpSearchPinBar = INPUT_YES; // Search for Pin Bar patterns sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput ENUM_INPUT_YES_NO InpSearchInsideBar = INPUT_YES; // Search for Inside Bar patterns sinput ENUM_INPUT_YES_NO InpSearchOutsideBar = INPUT_YES; // Search for Outside Bar patterns sinput double InpOBRatioCandles = 50.0; // Outside Bar Ratio of sizes of neighboring candles sinput double InpOBRatioBodyToCandle= 50.0; // Outside Bar Ratio Body to Candle size sinput ENUM_INPUT_YES_NO InpUseBook = INPUT_NO; // Use Depth of Market sinput ENUM_INPUT_YES_NO InpUseMqlSignals = INPUT_NO; // Use signal service sinput ENUM_INPUT_YES_NO InpUseCharts = INPUT_NO; // Use Charts control sinput ENUM_INPUT_YES_NO InpUseSounds = INPUT_YES; // Use sounds
Так как методы установки использования паттернов при установленном флаге сразу же ищут и отображают найденные паттерны, то для теста достаточно установить флаги в обработчике OnInit() для немедленного запуска поиска паттернов и их отображения на графике:
//--- Очистим список всех паттернов engine.GetListAllPatterns().Clear(); //--- Установим флаг использования паттерна Пин-Бар с параметрами, заданными в настройках engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller); //--- Установим флаг использования паттерна Внутренний бар engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchInsideBar); //--- Установим флаг использования паттерна Внешний бар engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle); //--- ChartRedraw(); return(INIT_SUCCEEDED); }
В обработчике OnChartEvent() советника добавим вызов этого обработчика для главного объекта библиотеки Engine:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Если работа в тестере - выход if(MQLInfoInteger(MQL_TESTER)) return; //--- Обработка событий мыши if(id==CHARTEVENT_OBJECT_CLICK) { //--- Обработка нажатий кнопок в панели if(StringFind(sparam,"BUTT_")>0) PressButtonEvents(sparam); } //--- Обработка событий библиотеки DoEasy if(id>CHARTEVENT_CUSTOM-1) { OnDoEasyEvent(id,lparam,dparam,sparam); } engine.OnChartEvent(id,lparam,dparam,sparam); //--- Изменение графика if(id==CHARTEVENT_CHART_CHANGE) { //--- При любом изменении графика скроем все информационные панели //... ... ...
В обработчике событий библиотеки — функции OnDoEasyEvent()
//+------------------------------------------------------------------+ //| Обработка событий библиотеки DoEasy | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Извлекаем из lparam (1) миллисекунды времени события, (2) причину, (3) источник события и (4) устанавливаем точное время события
в блоке обработки событий таймсерий добавим сообщение о пропущенных барах, если такое событие имеет место быть:
//--- Обработка событий таймсерий else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- Событие "Новый бар" if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } //--- Событие "Пропущены бары" if(idx==SERIES_EVENTS_MISSING_BARS) { Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam); } } //--- Обработка автоматических событий чартов
На данный момент это в техническом плане не даёт ничего — просто выводится в журнал количество пропущенных баров. Но зато видно, что библиотека правильно отрабатывает пропуски баров, если при работающем в терминале советнике было, например, отключение связи, или компьютер был введён в режим сна и затем выведен из него. А значит, при получении такого события, можно обновить необходимое количество баров для восстановления пропущенных данных таймсерий и паттернов. В последующем это будет реализовано.
Скомпилируем советник и запустим его, установив для поиска паттерна "Внешний Бар" такие значения:
Такие маленькие значения для пропорций свечей ставим специально, чтобы было найдено как можно большее количество паттернов.
При нормальных значениях пропорций свечей (50% и более), паттерны более правильные, но достаточно редкие.
После запуска будут найдены и отображены паттерны "Внешний Бар":
Видим, что паттерны находятся, при изменении размеров графика, размеры значков паттернов так же изменяют свои размеры.
Что дальше
В следующей статье, посвящённой ценовым формациям, мы продолжим создание различных паттернов и отправку событий об их появлении.
Все созданные файлы прикреплены к статье, и их можно скачать для самостоятельного изучения и тестирования.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования