
DoEasy. Funciones de servicio (Parte 3): Patrón "Barra exterior"
Contenido
- Concepto
- Mejorando las clases de la biblioteca
- Clase de patrón "Barra exterior"
- Simulación
- ¿Qué es lo próximo?
Concepto
Continuamos la sección de la biblioteca DoEasy sobre el trabajo con patrones de precios.
En el último artículo, implementamos la búsqueda y visualización de patrones de doble barra "Barra interior" de Price Action. Hoy implementaremos la búsqueda para el patrón "Barra exterior", que supone esencialmente una imagen especular de la barra interior.
Sin embargo, existen ciertas diferencias. Si la barra interior es un patrón bidireccional, y la entrada se puede hacer en cualquier lado del patrón, entonces el patrón de la barra exterior se dividirá en dos direcciones: alcista y bajista:
- BUOVB (Bullish Outside Vertical Bar) - barra vertical exterior alcista. La barra de señal se solapa completamente con la barra anterior y su precio de cierre es superior al máximo de la barra anterior. La entrada se realiza en la ruptura del máximo de la barra de señal + filtro (5-10 pips).
- BEOVB (Bearish Outside Vertical Bar) - Barra vertical exterior bajista. La barra de señal se solapa completamente con la barra anterior y su precio de cierre es inferior al mínimo de la barra anterior. La entrada se realiza en la ruptura de la barra de señal mínima + filtro (5-10 pips).
Antes de empezar a crear una clase de patrón, necesitaremos realizar mejoras en las clases ya listas de la biblioteca. En primer lugar, no todos los métodos de las clases de acceso a patrones están creados de forma óptima: esto era solo una prueba del concepto en la que todo se implementó "directamente" con múltiples métodos, cada uno para un tipo diferente de patrón. Ahora crearemos un solo método para acceder al trabajo con los patrones especificados. Para cada una de las acciones habrá un método propio donde el patrón requerido será especificado por una variable. Esto simplificará y reducirá enormemente los códigos de clase.
Y en segundo lugar, durante el largo tiempo que no hemos trabajado con la biblioteca, han aparecido algunos cambios y adiciones en el lenguaje MQL5 (todavía no se han añadido al lenguaje todos los cambios anunciados), así que añadiremos estos cambios a la biblioteca. Asimismo, hemos trabajado sobre los errores encontrados durante todo el tiempo de prueba de la biblioteca, por lo que describiremos todas las mejoras realizadas.
Mejorando las clases de la biblioteca
A la hora de determinar algunos patrones, resulta importante la relación mutua de los tamaños de las velas vecinas que intervienen en la formación de la figura del patrón. Para determinar la relación de tamaño, añadiremos un nuevo valor a las propiedades del patrón. Y también en las propiedades de cada patrón y clase de gestión de patrones, añadiremos una propiedad que especifique el valor por el que se busca la relación de velas.
En el archivo de la biblioteca \MQL5\Include\DoEasy\Defines.mqh, añadiremos nuevas propiedades a la lista de propiedades reales del patrón, y aumentaremos el número total de propiedades reales de 10 a 12:
//+------------------------------------------------------------------+ //| Pattern real properties | //+------------------------------------------------------------------+ enum ENUM_PATTERN_PROP_DOUBLE { //--- bar data PATTERN_PROP_BAR_PRICE_OPEN = PATTERN_PROP_INTEGER_TOTAL,// Pattern defining bar Open price PATTERN_PROP_BAR_PRICE_HIGH, // Pattern defining bar High price PATTERN_PROP_BAR_PRICE_LOW, // Pattern defining bar Low price PATTERN_PROP_BAR_PRICE_CLOSE, // Pattern defining bar Close price PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE, // Percentage ratio of the candle body to the full size of the candle PATTERN_PROP_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the upper shadow size to the candle size PATTERN_PROP_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the lower shadow size to the candle size PATTERN_PROP_RATIO_CANDLE_SIZES, // Ratio of pattern candle sizes PATTERN_PROP_RATIO_BODY_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the candle body to the full candle size in % PATTERN_PROP_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the maximum shadow to the candle size in % PATTERN_PROP_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRITERION, // Defined criterion of the ratio of the minimum shadow to the candle size in % PATTERN_PROP_RATIO_CANDLE_SIZES_CRITERION, // Defined criterion for the ratio of pattern candle sizes }; #define PATTERN_PROP_DOUBLE_TOTAL (12) // Total number of real pattern properties #define PATTERN_PROP_DOUBLE_SKIP (0) // Number of pattern properties not used in sorting
A partir de la versión beta 4540 del terminal de cliente MetaTrader 5, a la enumeración ENUM_SYMBOL_SWAP_MODE se le ha añadido el valor SYMBOL_SWAP_MODE_CURRENCY_PROFIT.
Si la función SymbolInfoInteger() retorna un valor de este tipo, significará que los swaps de la cuenta se acumulan en dinero en la divisa de cálculo del beneficio.
Vamos a añadir este valor a los archivos de la biblioteca. En el archivo \MQL5\Include\DoEasy\Data.mqh escribiremos los índices de los nuevos mensajes de la biblioteca:
MSG_SYM_SWAP_MODE_CURRENCY_MARGIN, // Swaps charged in money in symbol margin currency MSG_SYM_SWAP_MODE_CURRENCY_DEPOSIT, // Swaps charged in money in client deposit currency MSG_SYM_SWAP_MODE_CURRENCY_PROFIT, // Swaps charged in money in profit calculation currency MSG_SYM_SWAP_MODE_INTEREST_CURRENT, // Swaps charged as specified annual interest from symbol price at calculation of swap MSG_SYM_SWAP_MODE_INTEREST_OPEN, // Swaps charged as specified annual interest from position open price
...
MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE, // Percentage ratio of the candle body to the full size of the candle MSG_LIB_TEXT_PATTERN_RATIO_UPPER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the upper shadow size to the candle size MSG_LIB_TEXT_PATTERN_RATIO_LOWER_SHADOW_TO_CANDLE_SIZE, // Percentage ratio of the lower shadow size to the candle size MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES, // Ratio of pattern candle sizes MSG_LIB_TEXT_PATTERN_RATIO_BODY_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the candle body to the full candle size in % MSG_LIB_TEXT_PATTERN_RATIO_LARGER_SHADOW_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the maximum shadow to the candle size in % MSG_LIB_TEXT_PATTERN_RATIO_SMALLER_SHADOW_TO_CANDLE_SIZE_CRIT, // Defined criterion of the ratio of the minimum shadow to the candle size in % MSG_LIB_TEXT_PATTERN_RATIO_CANDLE_SIZES_CRITERION, // Defined criterion for the ratio of pattern candle sizes MSG_LIB_TEXT_PATTERN_NAME, // Name
y los mensajes de prueba correspondientes a los nuevos índices añadidos:
{"Свопы начисляются в деньгах в маржинальной валюте символа","Swaps charged in money in symbol margin currency"}, {"Свопы начисляются в деньгах в валюте депозита клиента","Swaps 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 charged as specified annual interest from 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"},
En el array de mensajes de error de ejecución, añadiremos una descripción de los dos nuevos errores de ejecución con los códigos 4306 y 4307:
//+------------------------------------------------------------------+ //| Array of runtime error messages (4301 - 4307) | //| (MarketInfo) | //| (1) in user's country language | //| (2) in the international language | //+------------------------------------------------------------------+ string messages_runtime_market[][TOTAL_LANG]= { {"Неизвестный символ","Unknown symbol"}, // 4301 {"Символ не выбран в MarketWatch","Symbol not selected in MarketWatch"}, // 4302 {"Ошибочный идентификатор свойства символа","Wrong identifier of symbol property"}, // 4303 {"Время последнего тика неизвестно (тиков не было)","Time of the last tick 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" }, };
En el archivo \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh en el método que retorna el número de decimales dependiendo del método de cálculo del swap, escribiremos el procesamiento del nuevo valor de enumeración:
//+------------------------------------------------------------------+ //| Return the number of decimal places | //| depending on the swap calculation method | //+------------------------------------------------------------------+ 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 ); }
En el método que retorna la descripción del modelo de cálculo del swap, escribiremos el retorno de una línea que describirá el nuevo modo de cálculo del swap:
//+------------------------------------------------------------------+ //| Return the description of a swap calculation model | //+------------------------------------------------------------------+ 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) ); }
En el archivo de definiciones MQL4 \MQL5\Include\DoEasy\ToMQL4.mqh, añadiremos una nueva propiedad a la lista de métodos de cálculo de swap al transferir una posición:
//+------------------------------------------------------------------+ //| Swap charging methods during a rollover | //+------------------------------------------------------------------+ enum ENUM_SYMBOL_SWAP_MODE { SYMBOL_SWAP_MODE_POINTS, // (MQL5 - 1, MQL4 - 0) Swaps are charged in points SYMBOL_SWAP_MODE_CURRENCY_SYMBOL, // (MQL5 - 2, MQL4 - 1) Swaps are charged in money in symbol base currency SYMBOL_SWAP_MODE_INTEREST_OPEN, // (MQL5 - 6, MQL4 - 2) Swaps are charged as the specified annual interest from the open price of position SYMBOL_SWAP_MODE_CURRENCY_MARGIN, // (MQL5 - 3, MQL4 - 3) Swaps are charged in money in margin currency of the symbol SYMBOL_SWAP_MODE_DISABLED, // (MQL5 - 0, MQL4 - N) No swaps SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT, // Swaps are charged in money, in client deposit currency SYMBOL_SWAP_MODE_INTEREST_CURRENT, // Swaps are charged as the specified annual interest from the instrument price at calculation of swap SYMBOL_SWAP_MODE_REOPEN_CURRENT, // Swaps are charged by reopening positions by the close price SYMBOL_SWAP_MODE_REOPEN_BID, // Swaps are charged by reopening positions by the current Bid price SYMBOL_SWAP_MODE_CURRENCY_PROFIT // Swaps charged in money in profit calculation currency };
En el archivo de clase de objeto básico de la biblioteca \MQL5\Include\DoEasy\Objects\BaseObj.mqh, corregiremos las líneas con posibles fugas de memoria:
//+------------------------------------------------------------------+ //| Add the event object to the list | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Add the object base event to the list | //+------------------------------------------------------------------+ 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); }
Los métodos retornarán el resultado de la adición de un objeto a la lista (true en caso de éxito y false en caso de error). Si se produce un error de adición de un objeto creado mediante el operador new, este objeto permanecerá en algún lugar de la memoria sin guardarse el puntero a él en la lista, lo cual provocará fugas de memoria. Vamos a arreglar esto:
//+------------------------------------------------------------------+ //| Add the event object to the list | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+ //| Add the object base event to the list | //+------------------------------------------------------------------+ 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; }
Ahora, cuando falle la adición de un objeto de evento a la lista, el objeto recién creado se borrará de la misma forma que ya ocurría con el propio objeto en la lista, y el método retornará false.
En el archivo MT5/MQL5/Include/DoEasy/Objects/Orders/Order.mqh, al calcular el número de puntos, deberemos añadir el redondeo del resultado obtenido, de lo contrario, si los valores son inferiores a 1 pero próximos a uno, al convertir un número real a entero, obtendremos un número de puntos igual a cero:
//+------------------------------------------------------------------+ //| Get order profit in points | //+------------------------------------------------------------------+ 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; }
En el archivo \MQL5\Include\DoEasy\Objects\Events\Event.mqh, se producía inicialmente un error al retornar un valor long como valor de enumeración:
//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket, //--- (3) current position order type, (4) current position order ticket, //--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction 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(); }
Mientras el valor de un ticket de orden de tipo long no superaba el valor INT_MAX (el tipo de datos para la enumeración es int), los tickets de las órdenes se retornaban correctamente. Pero en cuanto el valor del ticket superaba INT_MAX, se producía un desbordamiento y se retornaba un número negativo. Ahora lo hemos corregido:
//--- When changing position direction, return (1) previous position order type, (2) previous position order ticket, //--- (3) current position order type, (4) current position order ticket, //--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction 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(); }
En la enumeración que retorna el nombre del estado del evento del sistema de órdenes faltaba el estado de modificación y, en algunos casos, la descripción del evento comercial indicaba un estado "Desconocido". Lo hemos corregido añadiendo una línea:
//+------------------------------------------------------------------+ //| Return the event status name | //+------------------------------------------------------------------+ 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) ); }
En la clase del objeto comercial, corregiremos otra omisión: la política de rellenado de volumen (de la enumeración ENUM_ORDER_TYPE_FILLING) se transmitía incorrectamente al método de apertura de posición.
También realizaremos las mejoras necesarias en los métodos comerciales en el archivo MQL5/Include/DoEasy/Objects/Trade/TradeObj.mqh. En el método de apertura de una posición en el bloque de rellenado de la estructura de la solicitud comercial , añadiremos la configuración de la política de rellenado de volumen:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ 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(); //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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; } //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure 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); //--- Return the result of sending a request to the server #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 }
En esta clase, el valor de la política de rellenado se establecerá en la variable m_type_filling inicialmente al inicializarse la biblioteca con los valores permitidos para las órdenes (método CEngine::TradingSetCorrectTypeFilling). Si transmitimos un valor negativo para la política de rellenado al método open, se utilizará el valor de la variable m_type_filling establecido en la inicialización de la biblioteca. Si debemos especificar un tipo diferente de rellenado de volumen, deberemos transmitirlo en el parámetro del método type_filling, y se utilizará el valor pasado.
Antes no había ninguna línea añadida, y la política de rellenado, si había que especificar una política diferente a la predeterminada, siempre tenía un valor Return (ORDER_FILLING_RETURN), porque el campo type_filling de la estructura MqlTradeRequest no se rellenaba y siempre tenía un valor nulo. También hemos arreglado esto.
Corregiremos deficiencias similares en otros métodos de la clase donde la política de rellenado de volumen es necesaria de una forma u otra:
//+------------------------------------------------------------------+ //| Close a position | //+------------------------------------------------------------------+ 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(); //--- If the position selection failed. Write the error code and error description, send the message to the log and return '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; } //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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; } //--- Get a position type and an order type inverse of the position type ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure 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; //--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Return the result of sending a request to the server #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 } //+------------------------------------------------------------------+ //| Close a position partially | //+------------------------------------------------------------------+ 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(); //--- If the position selection failed. Write the error code and error description, send the message to the log and return '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; } //--- If failed to get the current prices, write the error code and description, send the message to the journal and return '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; } //--- Get a position type and an order type inverse of the position type ENUM_POSITION_TYPE position_type=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); ENUM_ORDER_TYPE type=OrderTypeOppositeByPositionType(position_type); //--- Get a position volume double position_volume=::PositionGetDouble(POSITION_VOLUME); //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure 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; //--- In case of a hedging account, write the ticket of a closed position to the structure if(this.IsHedge()) this.m_request.position=::PositionGetInteger(POSITION_TICKET); //--- Return the result of sending a request to the server #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 }
Ahora vamos a corregir omisiones similares en la clase comercial de la biblioteca en el archivo \MQL5\Include\DoEasy\Trading.mqh.
En el método OpenPosition() de apertura de una posición, la estructura de la solicitud de operación se rellenará con valores basados en los valores pasados al método:
//--- Write the volume, deviation, comment and filling type to the request structure 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());
y al llamar al método de apertura de una posición del objeto comercial de un símbolo, sus parámetros no contendrán los valores establecidos en la estructura de la solicitud comercial, sino los transmitidos al método de apertura de una posición:
//--- In the loop by the number of attempts for(int i=0;i<this.m_total_try;i++) { //--- Send a request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation,type_filling); //... ... ...
Vamos a corregir esto:
//--- Send a request 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);
Para seguir los eventos que se producen en todos los gráficos abiertos, y no solo en aquel al que está vinculado el programa basado en la biblioteca, en cada gráfico abierto (o ya abierto) se colocará un indicador que siga los eventos del gráfico y los envíe al programa.
Si el terminal del cliente estaba primero conectado a un servidor, y se abrían gráficos de diferentes instrumentos, y luego el terminal se conectaba a otro servidor donde no había instrumentos cuyos gráficos ya estuvieran abiertos, el programa no podía colocar estos indicadores en dichos gráficos, y obteníamos registros de error en el log indicando el error de creación del indicador, pero no la ausencia de los símbolos de los gráficos abiertos en el servidor. Para solucionar estas entradas de registro ambiguas, deberemos añadir la comprobación de la presencia del símbolo en el servidor antes de crear el indicador.
En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh en el método que crea el indicador de control de eventos, añadiremos una comprobación de la presencia del símbolo en el servidor:
//+------------------------------------------------------------------+ //| CChartObjectsControl: Create the event control indicator | //+------------------------------------------------------------------+ bool CChartObjectsControl::CreateEventControlInd(const long chart_id_main) { //--- If the symbol is not on the server, return '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; } //--- Create the indicator 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; }
Ahora, si el símbolo no está presente en el servidor, se registrará un mensaje al respecto y el método retornará false.
En el archivo de objetos gráficos básico de la biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, en el método que retorna la descripción del tipo de elemento gráfico, añadiremos el tipo que falta: el objeto gráfico "Bitmap":
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ 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) : //--- Containers 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) : //--- Standard controls 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) : //--- Auxiliary control objects 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" ); }
Las clases de patrones (su organización y estructura) también se retocarán un poco. La clase del patrón abstracto básico se encuentra en el archivo \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, y las clases de patrón heredadas de la clase básica estarán escritas en él.
Vamos a separar todas las clases de patrones en sus propios archivos. Ahora es posible mostrar iconos de patrones como puntos regulares en el gráfico que se dibujan mediante objetos de "Línea de tendencia" estándar con coordenadas de precio y tiempo en una barra. Hemos decidido renunciar a esta posibilidad para simplificar el código. Todos los iconos se dibujarán en objetos gráficos "Dibujo". Los dibujos del patrón se crean según los tamaños de las barras incluidas en el patrón. Para modificar el tamaño de los objetos de dibujo al cambiar la escala horizontal del gráfico, deberemos introducir una variable que almacene el valor de la escala. Las variables para almacenar el resto de las dimensiones del gráfico ya están presentes como parte del objeto patrón básico. Al cambiar el tamaño del gráfico, los nuevos valores se ajustarán a todos los objetos de patrón encontrados creados y se redibujarán para alcanzar las nuevas dimensiones.
En la carpeta de la biblioteca \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ crearemos dos nuevos archivos PatternPinBar.mqh y PatternInsideBar.mqh, en ellos colocaremos las clases de los patrones "Barra Pin" y "Barra Interior" escritas directamente en el archivo de clases del patrón abstracto básico usando el método Cut and Paste. Realizaremos cambios en ellos más adelante, pero por ahora, vamos a seguir editando la clase de patrón abstracto.
De la sección protegida de la clase, eliminaremos la variable de bandera m_draw_dots, que especifica el método de dibujar iconos de patrones con puntos, y declararemos una variable para almacenar la anchura del gráfico en píxeles:
protected: CForm *m_form; // Pointer to form object CGCnvBitmap *m_bitmap; // Pointer to the bitmap object int m_digits; // Symbol's digits value ulong m_symbol_code; // Symbol as a number (sum of name symbol codes) string m_name_graph_obj; // Name of the graphical object displaying the pattern double m_price; // Price level the graphical object is placed at color m_color_bullish; // Color of a graphical object set to the bullish pattern icon color m_color_bearish; // Color of a graphical object set to the bearish pattern icon color m_color_bidirect; // Color of a graphical object set to the bidirectional pattern icon color m_color; // Graphical object color color m_color_panel_bullish; // Bullish pattern panel color color m_color_panel_bearish; // Bearish pattern panel color color m_color_panel_bidirect; // Bidirectional pattern panel color int m_bars_formation; // Number of bars in the formation (nested pattern) bool m_draw_dots; // Draw on the chart with dots int m_chart_scale; // Chart scale int m_chart_height_px; // Height of the chart in pixels int m_chart_width_px; // Height of the chart in pixels double m_chart_price_max; // Chart maximum double m_chart_price_min; // Chart minimum public:
Métodos que calculan la anchura y la altura de un objeto de dibujo
//--- Calculate the bitmap object (1) width and (2) height int GetBitmapWidth(void); int GetBitmapHeight(void);
renombramos con los nombres correctos y los declaramos como virtuales:
//--- Calculate the bitmap object (1) width and (2) height virtual int CalculatetBitmapWidth(void); virtual int CalculatetBitmapHeight(void);
Y es que Get significa "Obtener", no "Calcular". Los métodos virtuales permitirán a las clases heredadas usar sus propios cálculos de anchura y altura del patrón, dependiendo del tipo de patrón y de cómo se dibuje.
En la sección pública de la clase, eliminaremos el método SetDrawAsDots():
public: //--- Remove a graphical object bool DeleteGraphObj(bool redraw=false); //--- Set graphical object display colors and pattern display color void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Set the flag for drawing pattern labels as dots void SetDrawAsDots(const bool flag) { this.m_draw_dots=flag; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel 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; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components 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); //--- Draw the pattern icon on the chart virtual void Draw(const bool redraw); //--- (1) Display, (2) hide the pattern icon on the chart void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Display and (2) hide the info panel on the chart void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
y declararemos el método virtual Redraw():
public: //--- Remove a graphical object bool DeleteGraphObj(bool redraw=false); //--- Set graphical object display colors and pattern display color void SetColors(const color color_bullish,const color color_bearish,const color color_bidirect,const bool redraw=false); //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel 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; } //--- Set the background color for the (1) bullish, (2) bearish and (3) bidirectional pattern panel by setting the values of the RGB color components 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) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw) { return true; } //--- (1) Display, (2) hide the pattern icon on the chart void Show(const bool redraw=false); void Hide(const bool redraw=false); //--- (1) Display and (2) hide the info panel on the chart void ShowInfoPanel(const int x,const int y,const bool redraw=true); void HideInfoPanel(void);
El método Redraw() redibujará el objeto de dibujo con las nuevas dimensiones. Y puesto que cada tipo de patrón puede tener diferentes tipos de patrón, el método se declarará virtual, y simplemente retornará true aquí. En las clases heredadas, el método se redefinirá para redibujar exactamente lo que se dibuja para este patrón.
Allí, en la sección pública, escribiremos los métodos para establecer y devolver la anchura del gráfico en píxeles:
//--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low 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; } //--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low 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; }
Al cambiar la anchura del gráfico, la clase de gestión de patrones escribirá las nuevas dimensiones del gráfico en todos los objetos de patrón uno a uno, de forma que cada uno de los objetos de patrón creados no obtendrá las propiedades del gráfico, sino que obtendrá las nuevas dimensiones solo una vez cuando se cambien, y entonces se escribirán en todos los objetos de patrón creados.
En el constructor de la clase, al final del constructor, eliminaremos la línea con la inicialización de la variable que ya no es necesaria:
//--- Set base colors of the pattern information panels 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; }
En el método que retorna la descripción de la propiedad real del patrón, añadiremos dos bloques de código para dar salida a las descripciones de las dos nuevas propiedades del patrón:
//+------------------------------------------------------------------+ //| Return the description of the pattern real property | //+------------------------------------------------------------------+ 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) ) : "" ); }
En el método que muestra el panel de información en el gráfico, ahora no será necesario obtener las propiedades del gráfico, puesto que ya están prescritas en el objeto patrón cuando se crea, o cuando se redimensiona el gráfico.
Luego eliminaremos del método las filas para la obtención de las propiedades del gráfico:
//+------------------------------------------------------------------+ //| Display the info panel on the chart | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- If there is no panel object yet, create it if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Get the chart width and height 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); //--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart
Ahora, en lugar de las propiedades del gráfico obtenidas anteriormente, utilizaremos estas propiedades preestablecidas cuando se creó el objeto:
//+------------------------------------------------------------------+ //| Display the info panel on the chart | //+------------------------------------------------------------------+ void CPattern::ShowInfoPanel(const int x,const int y,const bool redraw=true) { //--- If there is no panel object yet, create it if(this.m_form==NULL) if(!this.CreateInfoPanel()) return; //--- Calculate the X and Y coordinates of the panel so that it does not go beyond the chart 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); //--- Set the calculated coordinates and display the panel if(this.m_form.SetCoordX(cx) && this.m_form.SetCoordY(cy)) this.m_form.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); }
Del método de dibujado, representación y ocultamiento de iconos del patrón eliminaremos las líneas relacionadas con el dibujado de iconos de patrón con puntos:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Draw(const bool redraw) { //--- If the graphical object has not yet been created, create it 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); //--- Otherwise - display else this.Show(redraw); } //+------------------------------------------------------------------+ //| Display the pattern icon on the chart | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Hide the pattern icon on the chart | //+------------------------------------------------------------------+ 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); }
Ahora estos métodos parecen más sencillos:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPattern::Draw(const bool redraw) { this.Show(redraw); return true; } //+------------------------------------------------------------------+ //| Display the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Show(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Show(); if(redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| Hide the pattern icon on the chart | //+------------------------------------------------------------------+ void CPattern::Hide(const bool redraw=false) { if(this.m_bitmap==NULL) return; this.m_bitmap.Hide(); if(redraw) ::ChartRedraw(this.m_chart_id); }
A continuación, finalizaremos las clases de patrones cuyos códigos se han trasladado desde el archivo de patrones abstractos.
Luego abriremos el archivo de clase de patrón "Barra Pin" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh e introduciremos los cambios.
Antes, los iconos de este patrón se dibujaban solo con puntos utilizando objetos gráficos estándar. Ahora necesitaremos añadir métodos para dibujar puntos en el objeto gráfico "Bitmap".
Después añadiremos la declaración de los nuevos métodos al cuerpo de la clase:
//+------------------------------------------------------------------+ //| 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 // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| Pin Bar pattern class | //+------------------------------------------------------------------+ class CPatternPinBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Calculate the bitmap object (1) width and (2) height virtual int CalculatetBitmapWidth(void) { return(20); } virtual int CalculatetBitmapHeight(void) { return(40); } public: //--- Return the flag of the pattern supporting the specified property 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; } //--- Return description of the pattern (1) status and (2) type 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); } //--- Draw the pattern icon on the chart virtual bool Draw(const bool redraw); //--- Constructor CPatternPinBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Los métodos virtuales CalculatetBitmapWidth() y CalculatetBitmapHeight() siempre retornan un tamaño estrictamente definido de 20x40 píxeles porque para este patrón, dibujado en una sola barra, no resulta necesario calcular ni la altura ni la anchura del patrón: siempre debe tener el mismo tamaño. El punto de anclaje del dibujo se fija en el centro del objeto, y los puntos siempre se dibujan en el lienzo en la mitad superior o inferior del dibujo, dependiendo de la dirección del patrón. Para un patrón alcista, el punto se dibujará en la mitad inferior del patrón, mientras que para un patrón bajista, el punto se dibujará en la mitad superior del patrón. Esto permitirá mostrar los puntos del patrón siempre a la misma distancia de la sombra de la vela, independientemente de la escala vertical y del periodo del gráfico, lo cual resulta bastante cómodo y práctico.
En la implementación del método que crea la apariencia del panel informativo CreateInfoPanelView(), eliminaremos las líneas para obtener las propiedades del gráfico:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart 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());
Estas propiedades se preestablecen ahora al crear el objeto, o se actualizan al cambiar el tamaño del gráfico. Así que ahora utilizaremos los valores de las variables en las que se escriben la anchura y la altura del gráfico:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart 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());
Luego escribiremos la implementación de los métodos para dibujar el icono del patrón.
Método que dibuja un icono de patrón en un gráfico:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternPinBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Si aún no existe un objeto de dibujo físico, lo crearemos y lo mostraremos en el gráfico.
Método que crea un objeto de dibujo:
//+------------------------------------------------------------------+ //| Create the bitmap object | //+------------------------------------------------------------------+ bool CPatternPinBar::CreateBitmap(void) { //--- If the bitmap object has already been created earlier, return 'true' if(this.m_bitmap!=NULL) return true; //--- Calculate the object coordinates and dimensions datetime time=this.MotherBarTime(); double price=(this.Direction()==PATTERN_DIRECTION_BULLISH ? this.MotherBarLow() : this.MotherBarHigh()); int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Create the Bitmap object 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; //--- Set the object origin to its center and remove the tooltip ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Draw the bitmap object appearance this.CreateBitmapView(); return true; }
La lógica del método se comenta con todo detalle en el código. Al fijar las coordenadas del objeto en el gráfico, tendremos en cuenta la dirección del patrón. Si el patrón es alcista, la coordenada será el precio de la barra Low del patrón, si es bajista, la coordenada será el precio de la barra High.
Método que crea la apariencia del objeto de dibujo:
//+------------------------------------------------------------------+ //| Create the bitmap object appearance | //+------------------------------------------------------------------+ 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); }
En primer lugar, limpiaremos el lienzo con un color totalmente transparente. A continuación, definiremos las coordenadas locales del punto que deseamos dibujar. Para un patrón alcista, la coordenada Y será la mitad de la altura del patrón más 6 píxeles (6 píxeles por debajo del centro del patrón). Para un patrón bajista, restaremos 6 píxeles a la coordenada del centro del dibujo. La coordenada X será la mitad de la anchura del dibujo. El radio del círculo dibujado se establecerá igual a 2, lo cual hace un punto normalmente visible en cualquier marco temporal del gráfico.
Ahora realizaremos modificaciones similares en el archivo de patrones "Barra Interior" \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh.
Además declararemos un método virtual para redibujar el icono del patrón:
//+------------------------------------------------------------------+ //| 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 // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| "Inside Bar" pattern class | //+------------------------------------------------------------------+ class CPatternInsideBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); public: //--- Return the flag of the pattern supporting the specified property 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; } //--- Return description of the pattern (1) status and (2) type 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) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Constructor CPatternInsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Fuera del cuerpo de la clase, escribiremos una implementación del método que redibuja el icono del patrón con nuevas dimensiones:
//+------------------------------------------------------------------+ //| Redraw the pattern icon on the chart with a new size | //+------------------------------------------------------------------+ bool CPatternInsideBar::Redraw(const bool redraw) { //--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) return CPatternInsideBar::Draw(redraw); //--- Calculate the new object dimensions int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- If canvas resizing failed, return 'false' if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); //--- display and return 'true' this.Show(redraw); return true; }
La lógica del método está comentada en el código. Aquí todo es muy simple aquí: calculamos las nuevas dimensiones del lienzo, cambiamos sus dimensiones y dibujamos una nueva imagen en el lienzo de acuerdo con las nuevas dimensiones.
En el método que crea la apariencia del panel de información, antes se cometía un error al calcular el tamaño del patrón en barras:
//--- Create strings to describe the pattern, its parameters and search criteria string name=::StringFormat("Inside Bar (%lu bars)",int(this.Time()-this.MotherBarTime())/::PeriodSeconds(this.Timeframe())+1); string param=this.DirectDescription();
Este cálculo tiene su razón de ser, pero solo si las barras adyacentes del patrón no están separadas por fines de semana. Si hay fines de semana entre las barras izquierda y derecha del patrón, se sumarán al número de barras, y en lugar de 2 obtendremos el valor 4.
Vamos a corregir el cálculo:
//--- Create strings to describe the pattern, its parameters and search criteria string name=::StringFormat("Inside Bar (%lu bars)",::Bars(this.Symbol(),this.Timeframe(),this.MotherBarTime(),this.Time())); string param=this.DirectDescription();
Ahora siempre se retornará el número correcto de barras entre los tiempos de los dos barras del patrón.
Y como ahora los valores de anchura y altura del gráfico están preestablecidos en las variables, eliminaremos la obtención de las propiedades del gráfico en el mismo método:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart 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());
y corregiremos el cálculo de las coordenadas del panel para utilizar los valores preestablecidos de las variables:
//--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart 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());
En el método para dibujar el icono del patrón en el gráfico, eliminaremos el bloque de código para dibujar con puntos:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ void CPatternInsideBar::Draw(const bool redraw) { //--- If the flag for drawing with dots is set, call the parent class method and leave if(this.m_draw_dots) { CPattern::Draw(redraw); return; } //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return; } //--- display this.Show(redraw); }
Ahora el método parece más sencillo:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternInsideBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Todas las demás modificaciones se realizarán después de crear la clase del nuevo patrón "Barra exterior"
Clase de patrón "Barra exterior"
Ahora crearemos el nuevo archivo PatternOutsideBar.mqh de la clase CPatternOutsideBar en la carpeta de la biblioteca \MQL5\Include\DoEasy\Objects\Series\Patterns\.
La clase deberá heredarse de la clase básica del objeto de patrón, mientras que su archivo deberá adjuntarse al archivo de la clase patrón creada:
//+------------------------------------------------------------------+ //| 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 // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Pattern.mqh" //+------------------------------------------------------------------+ //| "Outside bar" pattern class | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { }
Vamos a declarar los métodos estándar para las clases de objetos de patrón:
//+------------------------------------------------------------------+ //| "Outside bar" pattern class | //+------------------------------------------------------------------+ class CPatternOutsideBar : public CPattern { protected: //--- Create the (1) image object, the appearance of the (2) info panel and (3) the bitmap object virtual bool CreateBitmap(void); virtual void CreateInfoPanelView(void); void CreateBitmapView(void); //--- Calculate the bitmap object height virtual int CalculatetBitmapHeight(void); public: //--- Return the flag of the pattern supporting the specified property 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; } //--- Return description of the pattern (1) status and (2) type 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) Draw and (2) resize the pattern icon on the chart virtual bool Draw(const bool redraw); virtual bool Redraw(const bool redraw); //--- Constructor CPatternOutsideBar(const uint id,const string symbol,const ENUM_TIMEFRAMES timeframe,MqlRates &rates,const ENUM_PATTERN_DIRECTION direct); };
Todos estos métodos serán iguales para todos los patrones. Solo su aplicación diferirá ligeramente de un modelo a otro. Analicemos cada método.
En el constructor de la clase, definiremos el nombre del patrón, el número de velas incluidas en el patrón y un número de patrones consecutivos adyacentes igual al número de velas del patrón:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ 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); }
Método que crea la apariencia del panel de información:
//+------------------------------------------------------------------+ //| Create the info panel appearance | //+------------------------------------------------------------------+ void CPatternOutsideBar::CreateInfoPanelView(void) { //--- If the form object is not created, leave if(this.m_form==NULL) return; //--- Change the color tone for bullish and bearish patterns: the bullish ones will have a blue tint, while the bearish ones will have a red tint 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; //--- Declare the array for the initial and final colors of the gradient fill color clr[2]={}; //--- Depending on the direction of the pattern, change the lightness of the corresponding colors - the initial one is a little darker, the final one is a little lighter 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; } //--- Set the background and form frame colors 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); //--- Create strings to describe the pattern, its parameters and search criteria 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)); //--- Set the coordinates of the panel and calculate its width and height depending on the size of the texts placed on the panel 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()))); //--- Set the width and height of the panel according to the calculated values this.m_form.SetWidth(w); this.m_form.SetHeight(h); //--- Depending on the chart size and coordinates, we calculate the coordinates of the panel so that it does not go beyond the chart 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); //--- Fill the background with a gradient color this.m_form.Erase(clr,200,true,false); //--- Draw the panel frame, an icon with (i), draw the header text with the proportions of a candle and separate the header with a horizontal line 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); //--- Under the horizontal line, enter the pattern description with its search criteria and the date of the pattern-defining bar 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); //--- Update the panel while redrawing the chart this.m_form.Update(true); }
La lógica del método se comenta con todo detalle en el código. Aquí dibujaremos un panel de rellenado de gradiente con un matiz rojizo para el patrón bajista y un matiz azul para el patrón alcista. En la parte superior dibujaremos un icono con el signo (i) y escribiremos el nombre del patrón con sus características: la relación de dos barras del patrón. La parte inferior describirá la dirección del patrón con los valores establecidos para la búsqueda del patrón y el tiempo del patrón encontrado.
Método que dibuja un icono de patrón en un gráfico:
//+------------------------------------------------------------------+ //| Draw the pattern icon on the chart | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Draw(const bool redraw) { //--- If the bitmap object has not yet been created, create it if(this.m_bitmap==NULL) { if(!this.CreateBitmap()) return false; } //--- display this.Show(redraw); return true; }
Método que redibuja el icono del patrón en el gráfico con un nuevo tamaño:
//+------------------------------------------------------------------+ //| Redraw the pattern icon on the chart with a new size | //+------------------------------------------------------------------+ bool CPatternOutsideBar::Redraw(const bool redraw) { //--- If a drawing object has not yet been created, create and display it in the Draw() method if(this.m_bitmap==NULL) return CPatternOutsideBar::Draw(redraw); //--- Calculate the new object dimensions int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- If canvas resizing failed, return 'false' if(!this.m_bitmap.SetWidth(w) || !this.m_bitmap.SetHeight(h)) return false; //--- Draw the new bitmap object appearance with new dimensions this.CreateBitmapView(); //--- display and return 'true' this.Show(redraw); return true; }
Simplemente redimensionaremos el lienzo y redibujaremos el nuevo patrón en el nuevo tamaño.
Método que crea un objeto de dibujo:
//+------------------------------------------------------------------+ //| Create the bitmap object | //+------------------------------------------------------------------+ bool CPatternOutsideBar::CreateBitmap(void) { //--- If the bitmap object has already been created earlier, return 'true' if(this.m_bitmap!=NULL) return true; //--- Calculate the object coordinates and dimensions datetime time=this.MotherBarTime(); double price=(this.BarPriceHigh()+this.BarPriceLow())/2; int w=this.CalculatetBitmapWidth(); int h=this.CalculatetBitmapHeight(); //--- Create the Bitmap object 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; //--- Set the object origin to its center and remove the tooltip ::ObjectSetInteger(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_ANCHOR,ANCHOR_CENTER); ::ObjectSetString(this.GetChartID(),this.m_bitmap.NameObj(),OBJPROP_TOOLTIP,"\n"); //--- Draw the bitmap object appearance this.CreateBitmapView(); return true; }
Si el objeto gráfico ha sido creado previamente, simplemente saldremos del método. En caso contrario, obtendremos el precio y la hora en la que se colocará el objeto gráfico, calcularemos su anchura y altura y crearemos un nuevo objeto. A continuación, situaremos el punto de anclaje en el centro del objeto y dibujaremos su apariencia sobre él. El precio en el que se situará el punto central del objeto gráfico se calculará mediante el centro de la vela más grande del patrón.
Método que crea la apariencia de un objeto de dibujo:
//+------------------------------------------------------------------+ //| Create the bitmap object appearance | //+------------------------------------------------------------------+ 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); }
En este caso, primero se borrará el dibujo con un color totalmente transparente. A continuación se calculará la coordenada X local del inicio de la zona a dibujar, en función de la escala del gráfico. Luego se dibujará un rectángulo relleno de color a toda la altura del objeto gráfico con la coordenada inicial X calculada, y encima se dibujará un marco rectangular, en las mismas coordenadas y con el mismo tamaño.
Método que calcula la altura de un objeto de dibujo:
//+------------------------------------------------------------------+ //| Calculate the bitmap object height | //+------------------------------------------------------------------+ int CPatternOutsideBar::CalculatetBitmapHeight(void) { //--- Calculate the chart price range and pattern price range double chart_price_range=this.m_chart_price_max-this.m_chart_price_min; double patt_price_range=this.BarPriceHigh()-this.BarPriceLow(); //--- Using the calculated price ranges, calculate and return the height of the bitmap object return (int)ceil(patt_price_range*this.m_chart_height_px/chart_price_range)+8; }
Aquí obtendremos el rango de precios del gráfico, desde el precio máximo del gráfico hasta el precio mínimo del gráfico, y el rango de precios del patrón, desde el High de la vela definitoria hasta el Low de la vela definitoria. Luego calcularemos la relación de un rango a otro en píxeles y retornaremos el valor obtenido de la altura del objeto de dibujo añadiéndole 8 píxeles: cuatro píxeles en la parte superior e inferior.
La clase de patrón "Barra Exterior" está lista. Ahora deberemos deshacernos de muchos métodos idénticos en las clases de series temporales, pues cada uno de ellos realiza el mismo trabajo para su propio patrón. Solo crearemos un método para cada acción sobre el patrón, en el que especificaremos el tipo de patrón que queremos.
Transmitiremos todos los parámetros necesarios de las relaciones de las velas incluidas en el patrón a las clases del patrón utilizando la estructura MqlParam. Esto permitirá transmitir a diferentes clases de diferentes patrones completamente diferentes parámetros entre sí, y no solo puramente definidos por las variables formales que son los mismos para todas las clases.
Por ahora, no cambiaremos los nombres de estas variables, que indican las distintas proporciones de las velas de patrón. Pero de ser necesario, las renombraremos en variables "impersonales" como "param1", "param2", etc. Y en los constructores de clase asignaremos el parámetro de patrón requerido a cada una de dichas variables. Pero solo si fuera necesario. De momento, prescindiremos de las variables ya mencionadas, que son las mismas para todos los patrones.
En la clase del objeto de barra ubicada en el archivo \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, cambiaremos el nombre del método AddPattern() por AddPatternType():
//--- Return itself CBar *GetObject(void) { return &this;} //--- Set (1) bar symbol, timeframe and time, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetProperties(const MqlRates &rates); //--- Add the pattern type on bar void AddPatternType(const ENUM_PATTERN_TYPE pattern_type){ this.m_long_prop[BAR_PROP_PATTERNS_TYPE] |=pattern_type; } //--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property)
Aún así, este método añadirá un tipo de patrón al objeto de barra, no un puntero al patrón. Por ello, resultará más lógico cambiar el nombre del método por otro más correcto.
Para poder seleccionar y obtener los patrones requeridos de las listas de patrones, en el archivo \MQL5\Include\DoEasy\Services\Select.mqh de la clase CSelect, deberemos conectar todos los archivos de patrones:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #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"
Esto hará que las clases de patrones estén disponibles en las clases de series temporales en las que se organiza el trabajo con patrones.
Asimismo, tendremos que comparar los arrays de estructuras MqlParam para comprobar la igualdad. Además, escribiremos las funciones para comparar los campos de estructuras y el array de estructuras en el archivo \MQL5\Include\DoEasy\Services\DELib.mqh:
//+------------------------------------------------------------------+ //| Compare MqlParam structures with each other | //+------------------------------------------------------------------+ bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) { if(struct1.type!=struct2.type) return false; switch(struct1.type) { //--- integer types 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); //--- real types case TYPE_FLOAT : case TYPE_DOUBLE : return(NormalizeDouble(struct1.double_value-struct2.double_value,DBL_DIG)==0); //--- string type case TYPE_STRING : return(struct1.string_value==struct2.string_value); default : return false; } } //+------------------------------------------------------------------+ //| Compare array of MqlParam structures with each other | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
Las clases de control de patrones se encuentran en el archivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh de la clase de series temporales. En la clase de control de patrón abstracto, eliminaremos el patrón ahora innecesario y añadiremos las nuevas variables:
//+------------------------------------------------------------------+ //| Abstract pattern control class | //+------------------------------------------------------------------+ class CPatternControl : public CBaseObjExt { private: ENUM_TIMEFRAMES m_timeframe; // Pattern timeseries chart period string m_symbol; // Pattern timeseries symbol double m_point; // Symbol Point bool m_used; // Pattern use flag bool m_drawing; // Flag for drawing the pattern icon on the chart bool m_draw_dots; // Flag for drawing the pattern icon on the chart with dots //--- Handled pattern ENUM_PATTERN_TYPE m_type_pattern; // Pattern type protected: //--- Candle proportions double m_ratio_body_to_candle_size; // Percentage ratio of the candle body to the full size of the candle double m_ratio_larger_shadow_to_candle_size; // Percentage ratio of the size of the larger shadow to the size of the candle double m_ratio_smaller_shadow_to_candle_size; // Percentage ratio of the size of the smaller shadow to the size of the candle double m_ratio_candle_sizes; // Percentage of candle sizes uint m_min_body_size; // The minimum size of the candlestick body ulong m_object_id; // Unique object code based on pattern search criteria //--- List views CArrayObj *m_list_series; // Pointer to the timeseries list CArrayObj *m_list_all_patterns; // Pointer to the list of all patterns CPattern m_pattern_instance; // Pattern object for searching by property //--- Graph ulong m_symbol_code; // Chart symbol name as a number int m_chart_scale; // Chart scale int m_chart_height_px; // Height of the chart in pixels int m_chart_width_px; // Height of the chart in pixels double m_chart_price_max; // Chart maximum double m_chart_price_min; // Chart minimum
Antes, transmitíamos en una variable al método de búsqueda de patrones el tamaño mínimo de vela para buscar los patrones:
virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const { return WRONG_VALUE; }
Ahora este tamaño se transmitirá mediante la variable MqlParam, por lo que ahora esta variable se eliminará de todos los métodos de búsqueda de patrones en todas las clases de gestión de patrones:
//--- (1) Search for a pattern, return direction (or -1 if no pattern is found), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code, //--- (4) return the list of patterns managed by the object 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; }
De la sección pública de la clase , eliminaremos los métodos para establecer y recuperar las propiedades de dibujo del patrón de puntos:
//--- (1) Set and (2) return the flag for drawing pattern icons as dots
void SetDrawingAsDots(const bool flag,const bool redraw);
bool IsDrawingAsDots(void) const { return this.m_draw_dots; }
Declaremos un array de parámetros de patrón, añadiremos los métodos para trabajar con nuevas variables, eliminaremos el paso del parámetro min_body_size del método virtual CreateAndRefreshPatternList(), y en el constructor paramétrico añadiremos el paso del array de propiedades de patrón a través del array de estructuras MqlParam. Luego declararemos un nuevo método para redibujar todos los patrones disponibles en el gráfico:
public: MqlParam PatternParams[]; // Array of pattern parameters //--- Return itself CPatternControl *GetObject(void) { return &this; } //--- (1) Set and (2) return the pattern usage flag void SetUsed(const bool flag) { this.m_used=flag; } bool IsUsed(void) const { return this.m_used; } //--- (1) Set and (2) return the pattern drawing flag void SetDrawing(const bool flag) { this.m_drawing=flag; } bool IsDrawing(void) const { return this.m_drawing; } //--- Set the necessary percentage ratio of the candle body to the full size of the candle, //--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body 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; } //--- Return the necessary percentage ratio of the candle body to the full size of the candle, //--- size of the (2) upper and (3) lower shadow to the candle size, (4) sizes of candles, (5) minimum size of the candle body 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; } //--- Return object ID based on pattern search criteria virtual ulong ObjectID(void) const { return this.m_object_id; } //--- Return pattern (1) type, (2) timeframe, (3) symbol, (4) symbol Point, (5) symbol code 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; } //--- Set the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low 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; } //--- Return the (1) chart scale, (2) height, (3) width in pixels, (4) high, (5) low 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; } //--- Compare CPatternControl objects by all possible properties virtual int Compare(const CObject *node,const int mode=0) const; //--- Search for patterns and add found ones to the list of all patterns virtual int CreateAndRefreshPatternList(void); //--- Display patterns on the chart void DrawPatterns(const bool redraw=false); //--- Redraw patterns on the chart with a new size void RedrawPatterns(const bool redraw=false); //--- Protected parametric constructor 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[]); };
En el constructor paramétrico de la clase, obtendremos las propiedades que faltan del gráfico y rellenaremos los parámetros del patrón a partir del array transmitido al constructor:
//+------------------------------------------------------------------+ //| CPatternControl::Protected parametric constructor | //+------------------------------------------------------------------+ 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); //--- fill in the array of parameters with data from the array passed to constructor 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; } }
En el método que busca patrones y añade los encontrados a la lista de todos los patrones, realizaremos algunas modificaciones:
//+------------------------------------------------------------------+ //| CPatternControl::Search for patterns and add | //| found ones to the list of all patterns | //+------------------------------------------------------------------+ int CPatternControl::CreateAndRefreshPatternList(void) { //--- If not used, leave if(!this.m_used) return 0; //--- Reset the timeseries event flag and clear the list of all timeseries pattern events this.m_is_event=false; this.m_list_events.Clear(); //--- Get the opening date of the last (current) bar datetime time_open=0; if(!::SeriesInfoInteger(this.Symbol(),this.Timeframe(),SERIES_LASTBAR_DATE,time_open)) return 0; //--- Get a list of all bars in the timeseries except the current one CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,time_open,LESS); if(list==NULL || list.Total()==0) return 0; //--- "Mother" bar data structure MqlRates pattern_mother_bar_data={}; //--- Sort the resulting list by bar opening time list.Sort(SORT_BY_BAR_TIME); //--- In a loop from the latest bar, for(int i=list.Total()-1;i>=0;i--) { //--- get the next bar object from the list CBar *bar=list.At(i); if(bar==NULL) continue; //--- look for a pattern relative to the received bar ENUM_PATTERN_DIRECTION direction=this.FindPattern(bar.Time(),pattern_mother_bar_data); //--- If there is no pattern, go to the next bar if(direction==WRONG_VALUE) continue; //--- Pattern found on the current bar of the loop //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol ulong code=this.GetPatternCode(direction,bar.Time()); //--- Set the pattern code to the sample this.m_pattern_instance.SetProperty(PATTERN_PROP_CODE,code); //--- Sort the list of all patterns by the unique pattern code this.m_list_all_patterns.Sort(SORT_BY_PATTERN_CODE); //--- search for a pattern in the list using a unique code int index=this.m_list_all_patterns.Search(&this.m_pattern_instance); //--- If there is no pattern equal to the sample in the list of all patterns if(index==WRONG_VALUE) { //--- Create the pattern object CPattern *pattern=this.CreatePattern(direction,this.m_list_all_patterns.Total(),bar); if(pattern==NULL) continue; //--- Sort the list of all patterns by time and insert the pattern into the list by its time this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); if(!this.m_list_all_patterns.InsertSort(pattern)) { delete pattern; continue; } //--- Add the pattern type to the list of pattern types of the bar object bar.AddPatternType(pattern.TypePattern()); //--- Add the pointer to the bar the pattern object is found on, together with the mother bar data pattern.SetPatternBar(bar); pattern.SetMotherBarData(pattern_mother_bar_data); //--- set the chart data to the pattern object 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 the drawing flag is set, draw the pattern label on the chart if(this.m_drawing) pattern.Draw(false); //--- Get the time of the penultimate bar in the collection list (timeseries bar with index 1) datetime time_prev=time_open-::PeriodSeconds(this.Timeframe()); //--- If the current bar in the loop is the bar with index 1 in the timeseries, create and send a message if(bar.Time()==time_prev) { // Here is where the message is created and sent } } } //--- Sort the list of all patterns by time and return the total number of patterns in the list this.m_list_all_patterns.Sort(SORT_BY_PATTERN_TIME); return m_list_all_patterns.Total(); }
Ahora el método no tendrá parámetros formales. Al crear un nuevo patrón, las propiedades del gráfico se transferirán a él y se establecerán inmediatamente. Al final del bloque del ciclo de búsqueda de patrones, haremos una plantilla para crear un nuevo evento de patrón, luego, en artículos posteriores, implementaremos el envío de eventos sobre la aparición de un nuevo patrón de serie temporal.
Método que redibuja los patrones en un gráfico con un nuevo tamaño del objeto de dibujo:
//+-------------------------------------------------------------------+ //|Redraw patterns on a chart with a new size of the bitmap object | //+-------------------------------------------------------------------+ void CPatternControl::RedrawPatterns(const bool redraw=false) { //--- Get a list of patterns controlled by the control object CArrayObj *list=this.GetListPatterns(); if(list==NULL || list.Total()==0) return; //--- Sort the obtained list by pattern time list.Sort(SORT_BY_PATTERN_TIME); //--- In a loop from the latest pattern, for(int i=list.Total()-1;i>=0;i--) { //--- get the next pattern object CPattern *obj=list.At(i); if(obj==NULL) continue; //--- Redraw the pattern bitmap object on the chart obj.Redraw(false); } //--- At the end of the cycle, redraw the chart if the flag is set if(redraw) ::ChartRedraw(this.m_chart_id); }
Simplemente iteraremos la lista de patrones de series temporales y llamaremos a los métodos de redibujado para cada patrón. Hasta ahora, el método no era óptimo: el redibujado se realizaba según toda la lista de patrones. No obstante, podemos organizar el redibujado solo de la parte de la historia visible en el gráfico. Pero eso será para más adelante.
Ahora vamos a finalizar las clases de control de patrones. Se encuentran más abajo en el mismo archivo. Ahora buscaremos todas las apariciones de la línea
const uint min_body_size
en los parámetros formales de los métodos de la clase y eliminaremos estas líneas; ahora el tamaño mínimo de la vela del patrón buscada se transmitirá junto con los parámetros del patrón en el array de estructuras MqlParam.
Marcaremos en color todos los lugares donde se han hecho cambios para que no tengamos que describir aquí cada línea de código, que será la misma para todas las clases.
En la clase de control del patrón "Barra Pin":
//+------------------------------------------------------------------+ //| Pin Bar pattern control class | //+------------------------------------------------------------------+ class CPatternControlPinBar : public CPatternControl { protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object 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 { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_PIN_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor 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::Search for the pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlPinBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get data for one bar by time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL); //--- If the list is empty, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- he size of the candle body should be less than or equal to RatioBodyToCandleSizeValue() (default 30%) of the entire candle size, //--- in this case, the body size should not be less than 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); //--- If the list is empty - there are no patterns, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Define the bullish pattern
En la clase de control de patrones "Barra interior".
//+------------------------------------------------------------------+ //| Inside Bar pattern control class | //+------------------------------------------------------------------+ class CPatternControlInsideBar : public CPatternControl { private: //--- Check and return the presence of a pattern on two adjacent bars bool CheckInsideBar(const CBar *bar1,const CBar *bar0) const { //--- If empty bar objects are passed, return 'false' if(bar0==NULL || bar1==NULL) return false; //--- Return the fact that the bar on the right is completely within the dimensions of the bar on the left return(bar0.High()<bar1.High() && bar0.Low()>bar1.Low()); } bool FindMotherBar(CArrayObj *list,MqlRates &rates) const { bool res=false; if(list==NULL) return false; //--- In a loop through the list, starting from the bar to the left of the base one for(int i=list.Total()-2;i>0;i--) { //--- Get the pointers to two consecutive bars CBar *bar0=list.At(i); CBar *bar1=list.At(i-1); if(bar0==NULL || bar1==NULL) return false; //--- If the obtained bars represent a pattern if(CheckInsideBar(bar1,bar0)) { //--- set mother bar data to the MqlRates variable and set 'res' to 'true' this.SetBarData(bar1,rates); res=true; } //--- If there is no pattern, interrupt the loop else break; } //--- return the result return res; } protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object 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 { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_INSIDE_BAR+PATTERN_STATUS_PA+PATTERN_DIRECTION_BOTH+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor 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::Search for pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlInsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get bar data up to and including the specified time
Por analogía con las clases de control de patrones "Barra Pin" y "Barra Interior", escribiremos más adelante la clase de control de patrones "Barra Exterior":
//+------------------------------------------------------------------+ //| Outside Bar pattern management class | //+------------------------------------------------------------------+ class CPatternControlOutsideBar : public CPatternControl { private: //--- Check and return the flag of compliance with the ratio of the candle body to its size relative to the specified value bool CheckProportions(const CBar *bar) const { return(bar.RatioBodyToCandleSize()>=this.RatioBodyToCandleSizeValue()); } //--- Return the ratio of nearby candles for the specified bar double GetRatioCandles(const CBar *bar) const { //--- If an empty object is passed, return 0 if(bar==NULL) return 0; //--- Get a list of bars with a time less than that passed to the method CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,bar.Time(),LESS); if(list==NULL || list.Total()==0) return 0; //--- Set the flag of sorting by bar time for the list and list.Sort(SORT_BY_BAR_TIME); //--- get the pointer to the last bar in the list (closest to the one passed to the method) CBar *bar1=list.At(list.Total()-1); if(bar1==NULL) return 0; //--- Return the ratio of the sizes of one bar to another return(bar.Size()>0 ? bar1.Size()*100.0/bar.Size() : 0.0); } //--- Check and return the presence of a pattern on two adjacent bars bool CheckOutsideBar(const CBar *bar1,const CBar *bar0) const { //--- If empty bar objects are passed, or their types in direction are neither bullish nor bearish, or are the same - return '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; //--- Calculate the ratio of the specified candles and, if it is less than the specified one, return 'false' double ratio=(bar0.Size()>0 ? bar1.Size()*100.0/bar0.Size() : 0.0); if(ratio<this.RatioCandleSizeValue()) return false; //--- Return the fact that the bar body on the right completely covers the dimensions of the bar body on the left, //--- and the shadows of the bars are either equal, or the shadows of the bar on the right overlap the shadows of the bar on the left return(bar1.High()<=bar0.High() && bar1.Low()>=bar0.Low() && bar1.TopBody()<bar0.TopBody() && bar1.BottomBody()>bar0.BottomBody()); } protected: //--- (1) Search for a pattern, return direction (or -1), //--- (2) create a pattern with a specified direction, //--- (3) create and return a unique pattern code //--- (4) return the list of patterns managed by the object 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 { //--- unique pattern code = candle opening time + type + status + pattern direction + timeframe + timeseries symbol return(time+PATTERN_TYPE_OUTSIDE_BAR+PATTERN_STATUS_PA+direction+this.Timeframe()+this.m_symbol_code); } virtual CArrayObj*GetListPatterns(void); //--- Create object ID based on pattern search criteria virtual ulong CreateObjectID(void); public: //--- Parametric constructor 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; // Minimum size of pattern candles this.m_ratio_candle_sizes=this.PatternParams[1].double_value; // Percentage of the size of the absorbing candle to the size of the absorbed one this.m_ratio_body_to_candle_size=this.PatternParams[2].double_value; // Percentage of full size to candle body size this.m_object_id=this.CreateObjectID(); } };
Condiciones necesarias para la definición del patrón: el cuerpo de la barra de la derecha deberá solaparse completamente con las dimensiones del cuerpo de la barra de la izquierda, al tiempo que las sombras de las barras serán iguales o las sombras de la barra de la derecha podrán solaparse con las sombras de la barra de la izquierda. Dicha búsqueda se realizará en el método CheckOutsideBar(). Además, deberemos considerar la relación entre el tamaño de los cuerpos de las velas incluidos en el patrón, en relación con el tamaño completo de las velas. Esta comprobación la realizará el método CheckProportions().
En el constructor de la clase estableceremos el estado del patrón como "Price Action", el tipo de patrón como "Outside Bar" y estableceremos todas las proporciones de las velas del patrón desde el array de estructuras pasadas al constructor.
Método que crea un identificador de objeto basado en los criterios de búsqueda del patrón:
//+------------------------------------------------------------------+ //| Create object ID based on pattern search criteria | //+------------------------------------------------------------------+ 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); }
Los dos criterios (el porcentaje del tamaño de vela y la relación entre el cuerpo de la vela y el tamaño de la vela completa) se indicarán en números (porcentajes) reales y no podrán ser superiores a 100. Así que los convertiremos en valores enteros multiplicándolos por 100, y luego crearemos un identificador ulong utilizando el método del objeto estándar extendido de la biblioteca UshortToLong(), que rellenará los bits especificados del número long con valores ushort:
//+------------------------------------------------------------------+ //| Pack a 'ushort' number to a passed 'long' number | //+------------------------------------------------------------------+ 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)); } //+------------------------------------------------------------------+ //| Convert a 'ushort' value to a specified 'long' number byte | //+------------------------------------------------------------------+ long CBaseObjExt::UshortToByte(const ushort value,const uchar to_byte) const { return(long)value<<(16*to_byte); }
Método que crea un patrón con una dirección especificada:
//+------------------------------------------------------------------+ //| Create a pattern with a specified direction | //+------------------------------------------------------------------+ CPattern *CPatternControlOutsideBar::CreatePattern(const ENUM_PATTERN_DIRECTION direction,const uint id,CBar *bar) { //--- If invalid indicator is passed to the bar object, return NULL if(bar==NULL) return NULL; //--- Fill the MqlRates structure with bar data MqlRates rates={0}; this.SetBarData(bar,rates); //--- Create a new Outside Bar pattern CPatternOutsideBar *obj=new CPatternOutsideBar(id,this.Symbol(),this.Timeframe(),rates,direction); if(obj==NULL) return NULL; //--- set the proportions of the candle the pattern was found on to the properties of the created pattern object 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)); //--- set the search criteria of the candle the pattern was found on to the properties of the created pattern object 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()); //--- Set the control object ID to the pattern object obj.SetProperty(PATTERN_PROP_CTRL_OBJ_ID,this.ObjectID()); //--- Return the pointer to a created object return obj; }
El método creará un nuevo objeto de la clase de patrón "Barra exterior", rellenará los datos sobre sus proporciones y criterios de búsqueda y retornará el puntero al objeto creado.
Método para encontrar un patrón:
//+------------------------------------------------------------------+ //| CPatternControlOutsideBar::Search for pattern | //+------------------------------------------------------------------+ ENUM_PATTERN_DIRECTION CPatternControlOutsideBar::FindPattern(const datetime series_bar_time,MqlRates &mother_bar_data) const { //--- Get bar data up to and including the specified time CArrayObj *list=CSelect::ByBarProperty(this.m_list_series,BAR_PROP_TIME,series_bar_time,EQUAL_OR_LESS); //--- If the list is empty, return -1 if(list==NULL || list.Total()==0) return WRONG_VALUE; //--- Sort the list by bar opening time list.Sort(SORT_BY_BAR_TIME); //--- Get the latest bar from the list (base bar) CBar *bar_patt=list.At(list.Total()-1); if(bar_patt==NULL) return WRONG_VALUE; //--- In the loop from the next bar (mother), for(int i=list.Total()-2;i>=0;i--) { //--- Get the "mother" bar CBar *bar_prev=list.At(i); if(bar_prev==NULL) return WRONG_VALUE; //--- If the proportions of the bars do not match the pattern, return -1 if(!this.CheckProportions(bar_patt) || !this.CheckProportions(bar_prev)) return WRONG_VALUE; //--- check that the resulting two bars are a pattern. If not, return -1 if(!this.CheckOutsideBar(bar_prev,bar_patt)) return WRONG_VALUE; //--- Set the "mother" bar data this.SetBarData(bar_prev,mother_bar_data); //--- If the pattern is found at the previous step, determine and return its direction if(bar_patt.TypeBody()==BAR_BODY_TYPE_BULLISH) return PATTERN_DIRECTION_BULLISH; if(bar_patt.TypeBody()==BAR_BODY_TYPE_BEARISH) return PATTERN_DIRECTION_BEARISH; } //--- No patterns found - return -1 return WRONG_VALUE; }
La lógica del método está descrita en los comentarios. La hora actual de apertura de la barra se transmite al método. Obtenemos una lista de todas las barras salvo la actual (no buscamos patrones en la barra cero). En el ciclo sobre la lista de barras obtenida, comprobamos cada dos barras próximas si cumplen los criterios del patrón. Si su proporción representa un patrón, determinaremos y retornaremos la dirección del patrón.
Método que retorna una lista de patrones gestionados por este objeto:
//+------------------------------------------------------------------+ //| Returns the list of patterns managed by the object | //+------------------------------------------------------------------+ 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); }
De la lista general de todos los patrones obtendremos una lista filtrada según el periodo del gráfico; luego obtendremos de la lista resultante una lista filtrada según el nombre del símbolo. La lista resultante se filtrará según el tipo de patrón de "Barra exterior" y la lista resultante se filtrará según el ID de este objeto de control. Como resultado, el método retornará una lista de solo aquellos patrones que son gestionados por la clase dada.
Ahora tendremos que rehacer por completo la clase de gestión de patrones. Podrá leer más al respecto en este artículo.
En lugar de métodos largos y monótonos para tratar cada tipo de patrón, crearemos varios métodos para tratar el patrón especificado. A continuación, en el mismo archivo, eliminaremos los numerosos métodos de tipo único de la clase de gestión de patrones, tales como:
... "Return the ... pattern control object" CPatternControl *GetObjControlPattern ...XXXXX(), ... "Set the flag for using the ... pattern and create a control object if it does not already exist" void SetUsedPattern ... XXXXX(const bool flag), ... "Return the flag of using the ... pattern" bool IsUsedPattern ...XXXXX(void), ... "Set the flag for drawing the ... pattern with dots" void SetDrawingAsDotsPattern ...XXXXX(const bool flag,const bool redraw), ... "Return the flag for drawing the ... pattern with dots" bool IsDrawingAsDotsPattern ...XXXXX(void), ... "Set ... pattern labels on the chart" void DrawPattern ...XXXXX(const bool redraw=false)
En general, existen muchos métodos de este tipo: para cada patrón hay un método distinto. Eso suma casi 1 300 líneas de código. Luego eliminaremos todas las líneas de la clase de gestión de patrones y reescribiremos la clase completa. Ahora habrá varios métodos para trabajar con patrones con la posibilidad de elegir con qué patrón trabajar.
La clase completamente reescrita con todos sus métodos será ahora así:
//+------------------------------------------------------------------+ //| Pattern control class | //+------------------------------------------------------------------+ class CPatternsControl : public CBaseObjExt { private: CArrayObj m_list_controls; // List of pattern management controllers CArrayObj *m_list_series; // Pointer to the timeseries list CArrayObj *m_list_all_patterns; // Pointer to the list of all patterns //--- Timeseries data ENUM_TIMEFRAMES m_timeframe; // Timeseries timeframe string m_symbol; // Timeseries symbol public: //--- Return (1) timeframe, (2) timeseries symbol, (3) itself ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } string Symbol(void) const { return this.m_symbol; } CPatternsControl *GetObject(void) { return &this; } protected: //--- Create an object for managing a specified pattern 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; } } //--- Return an object for managing a specified pattern CPatternControl *GetObjControlPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { //--- In a loop through the list of control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next object CPatternControl *obj=this.m_list_controls.At(i); //--- if this is not a pattern control object, go to the next one if(obj==NULL || obj.TypePattern()!=pattern) continue; //--- Check search conditions and return the result if(IsEqualMqlParamArrays(obj.PatternParams,param)) return obj; } //--- Not found - return NULL return NULL; } public: //--- Search and update all active patterns void RefreshAll(void) { //--- In a loop through the list of pattern control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next control object CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- if this is a tester and the current chart sizes are not set, or are not equal to the current ones - we try to get and set them 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); } } //--- search and create a new pattern obj.CreateAndRefreshPatternList(); } } //--- Set chart parameters for all pattern management objects void SetChartPropertiesToPattCtrl(const double price_max,const double price_min,const int scale,const int height_px,const int width_px) { //--- In a loop through the list of pattern control objects, int total=this.m_list_controls.Total(); for(int i=0;i<total;i++) { //--- get the next control object CPatternControl *obj=this.m_list_controls.At(i); if(obj==NULL) continue; //--- If the object is received, set the chart parameters obj.SetChartHeightInPixels(height_px); obj.SetChartWidthInPixels(width_px); obj.SetChartScale(scale); obj.SetChartPriceMax(price_max); obj.SetChartPriceMin(price_min); } } //--- Set the flag for using the specified pattern and create a control object if it does not already exist void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool flag) { CPatternControl *obj=NULL; //--- Get the pointer to the object for managing the specified pattern obj=this.GetObjControlPattern(pattern,param); //--- If the pointer is received (the object exists), set the use flag if(obj!=NULL) obj.SetUsed(flag); //--- If there is no object and the flag is passed as 'true' else if(flag) { //--- Create a new pattern management object obj=this.CreateObjControlPattern(pattern,param); if(obj==NULL) return; //--- Add pointer to the created object to the list if(!this.m_list_controls.Add(obj)) { delete obj; return; } //--- Set the usage flag and pattern parameters to the control object obj.SetUsed(flag); obj.CreateAndRefreshPatternList(); } } //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { CPatternControl *obj=this.GetObjControlPattern(pattern,param); return(obj!=NULL ? obj.IsUsed() : false); } //--- Places marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.DrawPatterns(redraw); } //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const bool redraw=false) { CPatternControl *obj=GetObjControlPattern(pattern,param); if(obj!=NULL) obj.RedrawPatterns(redraw); } //--- Constructor CPatternsControl(const string symbol,const ENUM_TIMEFRAMES timeframe,CArrayObj *list_timeseries,CArrayObj *list_all_patterns); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ 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; } //+------------------------------------------------------------------+
La lógica de los métodos se comentará en el código. Me gustaría aclarar el bloque de código resaltado: al trabajar en el modo visual del simulador, el manejador OnChartEvent() resulta prácticamente inútil. Es decir, en él realizaremos un seguimiento del cambio de los tamaños de los gráficos mediante el evento CHARTEVENT_CHART_CHANGE y registraremos los nuevos tamaños de los gráficos en el objeto de gestión de patrones desde donde estos datos se introducen en los objetos de patrón. Pero esto no funciona en el simulador. Por lo tanto, supervisaremos en un bloque de código específico del simulador los cambios en el tamaño del gráfico y, si cambian, introduciremos nuevos datos en los objetos de gestión de patrones.
Entonces, según el código en el mismo archivo \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh, tendremos la clase de series temporales CSeriesDE. Vamos a perfeccionar esta clase.
Cada vez que busquemos un objeto de barra en la lista de series temporales, se creará un nuevo objeto con la ayuda del operador new, se establecerán sus parámetros, se buscará en la lista el objeto con parámetros idénticos, y a continuación, se borrará este objeto recién creado:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ 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); }
Así, en algunas situaciones, nos encontraremos con la creación-eliminación constante de un objeto en un ciclo. Y eso no está bien. Lo mejor será crear un único objeto de instancia para la búsqueda y utilizarlo para establecer parámetros y utilizarlo como patrón para la búsqueda. De este modo nos libraremos de la constante recreación de objetos.
Vamos a declarar una instancia del objeto de barra para la búsqueda:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: CBar m_bar_tmp; // Bar object for search ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol
y reescribir el método que retorna el objeto de barra según el tiempo en la serie temporal:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ 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); }
Ahora, en lugar de crear y eliminar un nuevo objeto, estableceremos los parámetros de búsqueda deseados en una única instancia y buscaremos una instancia idéntica en la lista basándonos en el patrón. El objeto se creará una sola vez en el constructor de la clase y se utilizará continuamente sin volver a crearlo.
Al igual que en la clase de gestión de patrones, eliminaremos las largas listas de métodos para manejar cada patrón, y las sustituiremos por unos pocos métodos con una selección del patrón deseado:
//+------------------------------------------------------------------+ //| Working with patterns | //+------------------------------------------------------------------+ //--- Set the flag for using the specified pattern and create a control object if it does not already exist 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); } //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[]) { return(this.m_patterns_control!=NULL ? this.m_patterns_control.IsUsedPattern(pattern,param) : false); } //--- Places marks of the specified pattern on the chart 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); } //--- Redraw the bitmap objects of the specified pattern on the chart 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); } //--- Sets chart parameters for pattern management objects 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); }
Ahora, para seleccionar el patrón deseado, bastará con especificar su tipo en los parámetros del método, en lugar de utilizar un método diferente para cada uno de los patrones.
En la clase de la serie temporal del símbolo en el archivo D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh, cambiaremos los métodos para trabajar con los patrones de la misma manera.
En el cuerpo de la clase, al final, bajo el encabezado
//--- Constructors 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); //+------------------------------------------------------------------+ //| Methods for handling patterns | //+------------------------------------------------------------------+
Se escribirá una larga lista de declaraciones de métodos para manejar patrones. Eliminaremos todas estas declaraciones y las sustituiremos por una declaración de método múltiple con la elección del patrón deseado:
//--- Constructors 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); //+------------------------------------------------------------------+ //| Methods for handling patterns | //+------------------------------------------------------------------+ //--- Set the flag for using the specified pattern and create a control object if it does not already exist void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Return the flag of using the specified Harami pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe); //--- Draw marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Set chart parameters for pattern management objects on the specified timeframe 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); };
Fuera del cuerpo de la clase, al final del listado del archivo, las implementaciones de los métodos declarados se escribirán bajo el mismo encabezado. Eliminaremos toda esta larga lista y la sustituiremos por los nuevos métodos:
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Set the flag of using the specified pattern | //| and create a control object if it does not exist yet | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Return the flag of using the specified pattern | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Draw marks of the specified pattern on the chart | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Redraw the bitmap objects of the specified pattern on the chart | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Set chart parameters for pattern management objects | //| on the specified timeframe | //+------------------------------------------------------------------+ 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); }
Cada método obtendrá el marco temporal requerido del gráfico con cuyos patrones vamos a trabajar, y los parámetros requeridos mediante los cuales se seleccionará el objeto de gestión de patrones. A continuación, se obtendrá el puntero a la serie temporal requerida y se retornará el resultado de la llamada al método homónimo de la clase de series temporales.
Vamos a realizar algunas mejoras en el archivo de clase de colección de series temporales \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh.
En esta clase obtendremos las dimensiones del gráfico, sus cambios, y los enviaremos a las clases de control de patrones. Luego añadiremos las nuevas variables para almacenar las dimensiones del gráfico:
//+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt { private: CListObj m_list; // List of applied symbol timeseries CListObj m_list_all_patterns; // List of all patterns of all used symbol timeseries CChartObjCollection *m_charts; // Pointer to the chart collection double m_chart_max; // Chart maximum double m_chart_min; // Chart minimum int m_chart_scale; // Chart scale int m_chart_wpx; // Chart width in pixels int m_chart_hpx; // Chart height in pixels //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public:
Bajo el encabezado
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
al igual que en las clases anteriores, eliminaremos los métodos declarados e insertaremos otros nuevos especificando el tipo de patrón:
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //--- Set the flag of using the specified pattern void SetUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag); //--- Return the flag of using the specified pattern bool IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Draw marks of the specified pattern on the chart void DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Redraw the bitmap objects of the specified pattern on the chart void RedrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam ¶m[],const string symbol,const ENUM_TIMEFRAMES timeframe,const bool redraw=false); //--- Set chart parameters for pattern management objects on the specified symbol and timeframe 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);
Al final del cuerpo de la clase , declararemos un manejador de eventos:
//--- Initialization void OnInit(CChartObjCollection *charts) { this.m_charts=charts; } //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CTimeSeriesCollection(void); };
En el constructor de la clase , escribiremos las dimensiones del gráfico en las nuevas variables:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ 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); }
Fuera del cuerpo de la clase bajo, bajo el encabezado
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
eliminaremos todos los métodos para trabajar con cada patrón específico. En lugar de los métodos eliminados, escribiremos otros nuevos, especificando el tipo de patrón:
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Set the flag of using the specified pattern | //| and create a control object if it does not exist yet | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Return the flag of using the specified pattern | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Draw marks of the specified pattern on the chart | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Redraw the bitmap objects of the specified pattern on the chart | //+------------------------------------------------------------------+ 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); } //+------------------------------------------------------------------+ //| Set chart parameters for pattern management objects | //| on the specified symbol and timeframe | //+------------------------------------------------------------------+ 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); }
Cada método obtendrá el marco temporal requerido y el símbolo del gráfico con cuyos patrones vamos a trabajar, así como los parámetros requeridos mediante los cuales se seleccionará el objeto de gestión de patrones. A continuación, se obtendrá el puntero a la serie temporal requerida y se retornará el resultado de la llamada al método homónimo de la clase de series temporales del símbolo.
Luego escribiremos un manejador de eventos:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CTimeSeriesCollection::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the current chart object CChartObj *chart=this.m_charts.GetChart(this.m_charts.GetMainChartID()); if(chart==NULL) return; //--- Get the main window object of the current chart CChartWnd *wnd=this.m_charts.GetChartWindow(chart.ID(),0); if(wnd==NULL) return; //--- If the chart is changed bool res=false; if(id==CHARTEVENT_CHART_CHANGE) { //--- control the change in the chart scale int scale=(int)::ChartGetInteger(chart.ID(),CHART_SCALE); if(this.m_chart_scale!=scale) { this.m_chart_scale=scale; res=true; } //--- control the change in the chart width in pixels 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; } //--- control the change in the chart height in pixels 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; } //--- control the change in the chart maximum double chart_max=::ChartGetDouble(chart.ID(),CHART_PRICE_MAX); if(this.m_chart_max!=chart_max) { this.m_chart_max=chart_max; res=true; } //--- control the change in the chart minimum 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 there is at least one change if(res) { //--- Write new values of the chart properties to the pattern management objects this.SetChartPropertiesToPattCtrl(chart.Symbol(),chart.Timeframe(),chart_max,chart_min,scale,chart_hpx,chart_wpx); //--- Get a list of patterns on the current chart CArrayObj *list=CSelect::ByPatternProperty(this.GetListAllPatterns(),PATTERN_PROP_SYMBOL,chart.Symbol(),EQUAL); list=CSelect::ByPatternProperty(list,PATTERN_PROP_PERIOD,chart.Timeframe(),EQUAL); //--- If the list of patterns is received if(list!=NULL) { //--- In a loop by the list of patterns, int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next pattern object CPattern *pattern=list.At(i); if(pattern==NULL) continue; //--- set the new chart data to the pattern object 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); //--- Redraw the pattern bitmap object with new dimensions pattern.Redraw(false); } //--- Update the chart ::ChartRedraw(chart.ID()); } } } }
La lógica completa del manejador se describe con detalle en los comentarios del código. Aquí es donde, al detectarse un cambio en las dimensiones del gráfico, se transmitirán las nuevas dimensiones a las clases de control de patrones de series temporales, y los patrones se redibujarán de acuerdo con las nuevas dimensiones del gráfico.
Ahora finalizaremos la clase principal de la biblioteca CEngine en el archivo \MQL5\Include\DoEasy\Engine.mqh.
Aquí también, el cuerpo de la clase tendrá una larga lista de métodos para manejar patrones. Eliminaremos solo los métodos responsables de dibujar patrones como puntos. El resto de los métodos necesitarán mejoras: ahora se requiere enviar los parámetros del patrón a los métodos para trabajar con él usando el array de estructuras MqlParam. En todos los métodos no utilizados rellenaremos solo un campo de la estructura en el que se transmitirá, por ejemplo, el tamaño mínimo de las velas del patrón:
//--- Set the flag for using the Harami pattern and create a control object if it does not already exist 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); }
El resto de los métodos para los patrones aún no utilizados serán idénticos, y no tendremos que analizarlos: están disponibles en los archivos adjuntos al artículo.
Vamos a considerar ahora los métodos para trabajar con patrones ya creados en la biblioteca.
Método que establece la bandera para utilizar el patrón "Barra Exterior" (Absorción) y crea un objeto de control si aún no existe:
//--- Set the flag for using the Pattern Outside and create a control object if it does not already exist void SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Price Action External Bar usage flag const double ratio_candles=70, // Percentage ratio of the size of the engulfing color to the size of the engulfed one const double ratio_body_to_shadows=80, // Percentage ratio of shadow sizes to candle body size const uint min_body_size=3) // Minimum candle body size { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Percentage ratio of shadow sizes to candle body size 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); }
Método que establece la bandera de uso del patrón "Barra Interior" y crea un objeto de control si aún no existe:
//--- Set the flag for using the Pattern Inside and create a control object if it does not already exist 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); }
En este método, solo se rellenará un campo de la estructura, ya que el patrón no tiene propiedades personalizables, salvo la propiedad del tamaño mínimo de la vela, que es común para todos los patrones.
Método que establece la bandera de uso del patrón "Barra Pin" y crear un objeto de control si aún no existe:
//--- Set the flag for using the Pin Bar pattern and create a control object if it does not already exist void SeriesSetUsedPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const bool flag, // Price Action Pin Bar usage flag const double ratio_body=30, // Percentage ratio of the candle body to the full size of the candle const double ratio_larger_shadow=60, // Percentage ratio of the size of the larger shadow to the size of the candle const double ratio_smaller_shadow=30, // Percentage ratio of the size of the smaller shadow to the size of the candle const uint min_body_size=3) // Minimum candle body size { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the candle body to the full size of the candle param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Percentage ratio of the size of the larger shadow to the size of the candle param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Percentage ratio of the size of the smaller shadow to the size of the candle 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); }
Veamos los métodos para mostrar los patrones en un gráfico:
- para los patrones ya implementados en la biblioteca
- para los patrones que aún no se han creado:
//--- Draw Pattern Outside labels on the chart void SeriesDrawPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_candles=70, // Percentage ratio of the size of the engulfing color to the size of the engulfed one const double ratio_body_to_shadows=80, // Percentage ratio of shadow sizes to candle body size const uint min_body_size=3, // Minimum candle body size const bool redraw=false) // Chart redraw flag { MqlParam param[]={}; if(::ArrayResize(param,3)==3) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_candles; //--- Percentage ratio of shadow sizes to candle body size 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); } //--- Draw Inside Bar pattern labels on the chart 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); } //--- Draw Pin Bar pattern labels on the chart void SeriesDrawPatternPinBar(const string symbol,const ENUM_TIMEFRAMES timeframe, const double ratio_body=30, // Percentage ratio of the candle body to the full size of the candle const double ratio_larger_shadow=60, // Percentage ratio of the size of the larger shadow to the size of the candle const double ratio_smaller_shadow=30, // Percentage ratio of the size of the smaller shadow to the size of the candle const uint min_body_size=3, // Minimum candle body size const bool redraw=false) // Chart redraw flag { MqlParam param[]={}; if(::ArrayResize(param,4)==4) { param[0].type=TYPE_UINT; param[0].integer_value=min_body_size; //--- Percentage ratio of the candle body to the full size of the candle param[1].type=TYPE_DOUBLE; param[1].double_value=ratio_body; //--- Percentage ratio of the size of the larger shadow to the size of the candle param[2].type=TYPE_DOUBLE; param[2].double_value=ratio_larger_shadow; //--- Percentage ratio of the size of the smaller shadow to the size of the candle 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(); } //--- Draw Rails pattern labels on the chart 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); }
Los demás métodos para otros patrones serán idénticos al método para el patrón "Raíles" aún por realizar, y no tendrá sentido analizarlos aquí.
Al final del cuerpo de la clase, declararemos un manejador de eventos:
public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); //--- Event handler void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Handling DoEasy library events void OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Working with events in the tester void EventsHandling(void); };
Luego escribiremos su implementación fuera del cuerpo de la misma:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ 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); }
Aquí, primero se llamará al manejador de eventos de la colección de la clase de colección de objetos gráficos, seguido del manejador de eventos de la colección de series temporales.
En el manejador de eventos de la biblioteca CEngine::OnDoEasyEvent(), en el bloque de procesamiento de eventos de series temporales, añadiremos un bloque para el futuro procesamiento de eventos de aparición de nuevos patrones:
//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event 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); } } } } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam); } //--- "New pattern" event if(idx==SERIES_EVENTS_PATTERN) { // New pattern event is handled here } }
Este lugar está vacío por ahora, pero cuando enviemos eventos de patrones en el futuro, escribiremos aquí el procesamiento de la aparición de un nuevo patrón.
Con esto daremos por completa la puesta a punto de la biblioteca. Ahora vamos a comprobar el trabajo de búsqueda de nuevos patrones y el procesamiento del cambio de la escala horizontal del gráfico.
Simulación
Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part136\ con el nuevo nombre TestDoEasy136.mq5
Luego borraremos de la configuración la bandera para dibujar patrones con puntos:
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
Después añadiremos a la configuración las banderas para el uso de patrones y los parámetros para la búsqueda del patrón "Barra exterior":
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
Como los métodos para habilitar el uso de patrones al establecerse la bandera buscan y muestran inmediatamente los patrones encontrados, para la prueba bastará con establecer las banderas en el manejador OnInit() para iniciar directamente la búsqueda de patrones y mostrarlos en el gráfico:
//--- Clear the list of all patterns engine.GetListAllPatterns().Clear(); //--- Set the flag of using the Pin Bar pattern with the parameters specified in the settings engine.SeriesSetUsedPatternPinBar(NULL,PERIOD_CURRENT,(bool)InpSearchPinBar,InpPinBarRatioBody,InpPinBarRatioLarger,InpPinBarRatioSmaller); //--- Set the flag of using the Inside Bar pattern engine.SeriesSetUsedPatternInsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchInsideBar); //--- Set the flag of using the Outside Bar pattern engine.SeriesSetUsedPatternOutsideBar(NULL,PERIOD_CURRENT,(bool)InpSearchOutsideBar,InpOBRatioCandles,InpOBRatioBodyToCandle); //--- ChartRedraw(); return(INIT_SUCCEEDED); }
En el manejador OnChartEvent() del asesor experto, añadiremos la llamada a este manejador para el objeto principal de la biblioteca Engine:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- Handling mouse events if(id==CHARTEVENT_OBJECT_CLICK) { //--- Handle pressing the buttons in the panel if(StringFind(sparam,"BUTT_")>0) PressButtonEvents(sparam); } //--- Handling DoEasy library events if(id>CHARTEVENT_CUSTOM-1) { OnDoEasyEvent(id,lparam,dparam,sparam); } engine.OnChartEvent(id,lparam,dparam,sparam); //--- Chart change if(id==CHARTEVENT_CHART_CHANGE) { //--- Whenever the chart changes, hide all information panels //... ... ...
En el controlador de eventos de la biblioteca, las funciones OnDoEasyEvent()
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
en el bloque de procesamiento de eventos de series temporales añadiremos un mensaje sobre barras perdidas, si se produce un evento de este tipo:
//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { Print(TextByLanguage("Пропущены бары на ","Missing bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",lparam); } } //--- Handle chart auto events
Por el momento no realizará nada técnicamente: solo registrará el número de barras perdidas. Sin embargo, podemos ver que la biblioteca funciona correctamente para las barras que faltan, si, por ejemplo, ha habido una desconexión cuando el asesor experto estaba trabajando en el terminal, o el ordenador ha entrado en modo de suspensión y luego ha salido de él. Y esto significa que cuando recibamos un evento de este tipo, podremos actualizar el número de barras necesario para recuperar los datos de las series temporales y los patrones perdidos. Esta medida se aplicará más adelante.
Vamos a compilar el asesor experto y a ejecutarlo estableciendo los siguientes valores para la búsqueda del patrón "Barra Exterior":
Hemos fijado valores pequeños para las proporciones de las velas a propósito, para encontrar el mayor número posible de patrones.
Con proporciones de velas normales (50% o más), los patrones serán más correctos, pero bastante raros.
Después del inicio, se encontrarán y mostrarán los patrones de la "Barra Exterior":
Podemos ver que los patrones se localizan, cuando el tamaño del gráfico cambia, los tamaños de los iconos de los patrones también cambian sus tamaños.
¿Qué es lo próximo?
En el próximo artículo dedicado a las formaciones de precios, seguiremos creando diferentes patrones y enviando eventos sobre la aparición de los mismos.
Todos los archivos creados se adjuntan al artículo y pueden descargarse para que el lector aprenda y realice pruebas por sí mismo.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14710
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso