
DoEasy.服务函数(第 3 部分):外包线形态
目录
概念
让我们继续 DoEasy 库中有关处理价格形态的部分。
在上一篇文章中,我们创建了两个柱的孕线价格行为形态的搜索和显示。在这里,我们将创建外包线(Outside Bar)形态的搜索,它本质上是孕线(Inside Bar)的镜像。
但也存在差异。如果孕线是双向形态,并且可以在形态的任一侧入场,则外包线分为两个方向 —— 看涨和看跌:
- BUOVB (Bullish Outside Vertical Bar)—— 看涨外部垂直线。信号柱完全覆盖前一个信号柱,其收盘价高于前一个信号柱的最高价。在突破信号柱的最高价 + 过滤器(5-10点)时进行交易。
- BEOVB (Bearish Outside Vertical Bar)——看跌外部垂直线。 信号柱完全覆盖前一个信号柱,其收盘价低于前一个信号柱的最低价。突破信号柱的最低价 - 过滤器(5-10 点)后进场交易。
在开始制作形态类之前,我们需要对已经存在的库类进行一些修改。首先,形态访问类中的所有方法都不是经过优化创建的 —— 这只是一次概念测试,其中所有内容都是通过多种方法“正面”实现的,每种方法都针对其自己的形态类型。现在我们将只使用一种方法来访问指定形态。对于每个动作,我们都使用其自己的方法,其中所需的形态将由变量指示。这将大大简化和缩短类代码。
其次,在库没有工作的很长一段时间里,MQL5 语言发生了一些更改和添加(并非所有宣布的更改都已添加到语言中),我们将把这些更改添加到库中。我还修复了整个库测试期间发现的一些错误。我将描述所做的所有改进。
改进库类
在确定某些形态时,参与形成形态图形的相邻烛形的尺寸之间的相互关系非常重要。让我们向形态属性添加一个新值来确定尺寸比率。在每个形态和形态管理类的属性中,添加一个属性,该属性将指示搜索烛形比率的值。
在 \MQL5\Include\DoEasy\Defines.mqh 库文件中,即在形态真实属性的枚举中,添加新属性并将真实属性的总数从 10 增加到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
从 MetaTrader 5 客户端测试版 4540 开始, ENUM_SYMBOL_SWAP_MODE 枚举具有 SYMBOL_SWAP_MODE_CURRENCY_PROFIT。
如果 SymbolInfoInteger() 函数返回这样的值,则账户上的库存费将以盈利货币计算。
让我们将这个值添加到库文件中。 在 \MQL5\Include\DoEasy\Data.mqh 中,输入新库消息的索引:
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
以及与新添加的索引对应的测试消息:
{"Свопы начисляются в деньгах в маржинальной валюте символа","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"},
将代码为 4306 和 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" }, };
在 \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh 中,添加处理新枚举值的方法,该方法根据库存费计算方法返回小数位数:
//+------------------------------------------------------------------+ //| 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 ); }
在返回库存费计算模型描述的方法中,输入返回一个描述新库存费计算模式的字符串:
//+------------------------------------------------------------------+ //| 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) ); }
在 MQL4 定义文件 \MQL5\Include\DoEasy\ToMQL4.mqh 中,向转移仓位时计算库存费的方法枚举添加新属性:
//+------------------------------------------------------------------+ //| 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 };
在 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 库基础对象类文件中,修复可能存在内存泄漏的字符串:
//+------------------------------------------------------------------+ //| 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); }
这里的方法返回将对象添加到列表的结果( true 表示添加成功, false 表示错误)。如果在添加由 new 运算符时,对象会保留在内存中的某个位置,而不会将指向它的指针存储在列表中,这将导致内存泄漏。让我们修复这个问题:
//+------------------------------------------------------------------+ //| 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; }
现在,如果将事件对象添加到列表失败,则新创建的对象将以与列表中已有相同对象相同的方式被删除,并且该方法将返回 false 。
在 \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh 中,计算点数时,需要对所得结果进行四舍五入,否则,对于小于 1 但接近于 1 的值,在将实数转换为整数时,我们会得到零点数:
//+------------------------------------------------------------------+ //| 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; }
在 \MQL5\Include\DoEasy\Objects\Events\Event.mqh 中,将长整型值作为枚举值返回时出现错误:
//--- 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(); }
只要长整型订单号的值不超过 INT_MAX 值(枚举数据类型 — int ),订单号就能正确返回。但一旦订单号超过 INT_MAX ,就会发生溢出并返回负数。现在一切都已修复:
//--- 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(); }
返回订单系统事件状态名称的枚举缺少修改状态,并且在某些情况下交易事件描述显示状态为“Unknown”(未知)。通过添加字符串来修复:
//+------------------------------------------------------------------+ //| 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) ); }
让我们修复交易对象类中的一个遗漏:填充交易量的策略(来自 ENUM_ORDER_TYPE_FILLING 枚举)被错误地传递给了开仓方法。
让我们对 MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh 中的交易方法实施必要的改进。 我将在建仓方法中将交易量填充策略的设置添加到填充交易请求结构的块中:
//+------------------------------------------------------------------+ //| 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 }
在类中,当使用订单允许的值初始化库时,填充策略值最初设置为 m_type_filling 变量(CEngine::TradingSetCorrectTypeFilling 方法)。如果将填充策略的负值传递给打开方法,则将使用在库初始化期间设置的 m_type_filling 变量的值。如果我们需要指定不同类型的交易量填充,那么应该在 type_filling 方法参数中传递它,并使用传递的值。
此前,没有添加字符串。如果要设置另一个(非默认)策略,则填充策略始终为返回( ORDER_FILLING_RETURN ),因为 MqlTradeRequest 结构的 type_filling 字段未填充且始终为零值。现在这个问题已经修复了。
让我们修复该类的其他方法中的类似缺陷,这些方法以某种方式需要交易量填充策略:
//+------------------------------------------------------------------+ //| 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 }
现在让我们修复 \MQL5\Include\DoEasy\Trading.mqh 中的库交易类中的类似遗漏。
在 OpenPosition() 方法中,交易请求结构根据传递给方法的值进行填充:
//--- 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());
并且在调用交易对象品种的建仓方法时,其参数没有指定交易请求结构中设置的值,而是传递给建仓方法的值:
//--- 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); //... ... ...
让我们修复这个问题:
//--- 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);
为了跟踪所有打开的图表上发生的事件,而不仅仅是基于库的程序所连接的图表上的事件,在每个打开(或已经打开)的图表上都放置了一个指标,用于跟踪图表事件并将其发送给程序。
如果客户端首先连接到一个服务器,打开了各种工具的图表,然后将终端连接到另一个服务器上,而该服务器上没有已经打开图表的工具,那么程序将无法将这些指标放置在这些图表上,日志中将包含错误条目,指示创建指标时出错,而不是服务器上没有打开的图表交易品种。为了修复这些模糊的日志条目,我们需要在创建指标之前检查服务器上是否存在该交易品种。
在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 中,即在创建事件控制指标的方法中,添加对服务器上是否存在交易品种的检查:
//+------------------------------------------------------------------+ //| 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; }
现在,如果服务器上没有交易品种,则会在日志中显示有关此消息,并且该方法返回 false 。
在基础图形对象的 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 文件中,即在返回图形元素类型描述的方法中,添加一个缺失的类型 — 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" ); }
我们还将稍微修改形态类别 —— 它们的组织和结构。基础抽象形态类位于 \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh 中,然后从基类继承的形态类都写入其中。
我们将所有形态类别分离到各自的文件中。现在可以将形态图标显示为图表上的常规点,这些常规点是使用在一个柱形上具有价格和时间坐标的标准趋势线对象绘制的。我决定放弃这个特性来简化代码。所有图标都将绘制在位图图形对象上。形态图是根据组成形态的柱形的尺寸创建的。要在更改图表的水平比例时更改图形对象的大小,我们需要输入一个存储比例值的变量。用于存储剩余图表尺寸的变量已经包含在基本形态对象中。调整图表大小时,新值将适合所有已找到形态创建的对象,并且它们将根据新尺寸重新绘制。
在 \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ 库文件中,创建两个新文件 PatternPinBar.mqh 和 PatternInsideBar.mqh 。在那里,我们使用剪切和粘贴来放置 Pin Bar(锤子线) 和 Inside Bar (孕线)形态的类(直接在基础抽象模式类文件中设置)。接下来,我们将对它们进行修改,但现在我们将继续编辑抽象形态类。
从类的受保护部分中,删除 m_draw_dots 标志变量,该变量指示使用点绘制模式图标的方式,并声明变量以存储图表宽度(以像素为单位):
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:
计算位图对象的宽度和高度的方法
//--- Calculate the bitmap object (1) width and (2) height int GetBitmapWidth(void); int GetBitmapHeight(void);
将它们重命名为正确的名称并将它们声明为虚拟方法:
//--- Calculate the bitmap object (1) width and (2) height virtual int CalculatetBitmapWidth(void); virtual int CalculatetBitmapHeight(void);
但是,Get 的意思是“接收”,而不是“计算”。虚拟方法将允许继承的类使用它们自己的形态宽度和高度计算,具体取决于形态的类型和绘制方法。
在类的公有部分,删除 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);
并声明 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);
Redraw() 方法使用新的尺寸重新绘制位图对象。由于每种形态类型可以有自己的位图类型,因此该方法被声明为虚拟的,在这里它只是返回 true 。在继承的类中,该方法将被重写以重新绘制为给定形态绘制的位图。
在公有部分中,我们还将设置用于设置和返回图表宽度(以像素为单位)的方法:
//--- 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; }
更改图表宽度时,形态管理类会将新的图表尺寸逐一写入所有形态对象,这样在每个创建的形态对象中,您就不会获得图表属性,而是在更改时只获得一次新尺寸,然后将其写入所有创建的形态对象。
在类构造函数的最后,删除不再需要的用于初始化的变量的字符串:
//--- 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; }
在返回形态真实属性描述的方法中,添加两个代码块,用于显示两个新形态属性的描述:
//+------------------------------------------------------------------+ //| 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) ) : "" ); }
在图表上显示信息面板的方法中,不再需要获取图表属性,因为在创建形态对象或调整图表大小时,它们已经在形态对象中设置。
从方法中删除用于接收图表属性的字符串:
//+------------------------------------------------------------------+ //| 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
现在使用以下创建对象时预设的属性来代替之前获取的图表属性:
//+------------------------------------------------------------------+ //| 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); }
从绘制、显示、隐藏图案图标的方法中,删除与绘制带点形态图标相关的字符串:
//+------------------------------------------------------------------+ //| 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); }
现在这些方法看起来更简单了:
//+------------------------------------------------------------------+ //| 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); }
接下来,我们将细化从抽象形态文件转移代码的形态类。
打开 \MQL5\Include\DoEasy\Objects\Series\Patterns\Patterns\PatternBar.mqh 锤子线形态类文件并进行更改。
以前,该形态的图标仅使用标准图形对象绘制为点。现在我们需要添加在位图图形对象上绘制点的方法。
让我们将新方法的声明添加到类主体中:
//+------------------------------------------------------------------+ //| 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); };
CalculatetBitmapWidth() 和 CalculatetBitmapHeight() 虚拟方法始终返回严格指定的图像尺寸 20x40 像素,因为对于仅在一个柱上绘制的此形态,无需计算图像的高度或宽度 - 它必须始终是相同的大小。位图的锚点设置在对象的中心,并且这些点始终在画布上绘制在位图的上半部分或下半部分,具体取决于形态的方向。对于看涨形态,点绘制在位图的下半部分,而对于看跌形态,点绘制在位图的上半部分。这使得无论垂直尺度和图表周期如何,形态点始终显示在距蜡烛影线相同的距离处,非常方便实用。
在创建 CreateInfoPanelView() 信息面板外观的方法实现中,删除用于获取图表属性的字符串:
//--- 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());
现在,这些属性在创建对象时是预设的,或者在调整图表大小时更新。因此,现在我们使用包含图表宽度和高度的变量的值:
//--- 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());
让我们实现绘制形态图标的方法。
在图表上绘制形态图标的方法:
//+------------------------------------------------------------------+ //| 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; }
如果还没有物理位图对象,我们将创建它,然后将其显示在图表上。
创建位图对象的方法:
//+------------------------------------------------------------------+ //| 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; }
该方法的逻辑在代码中进行了注释。设置对象坐标时要考虑形态方向。如果形态看涨,则坐标为形态柱的最低价,如果形态看跌,则坐标为最高价。
创建位图对象外观的方法:
//+------------------------------------------------------------------+ //| 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); }
首先,用完全透明的颜色清除画布。然后我们确定绘制点的局部坐标。对于看涨形态,Y 坐标将是形态高度的一半加上 6 个像素(形态中心下方 6 个像素)。对于看跌形态,从位图中心坐标中减去 6 个像素。X 坐标将是位图宽度的一半。绘制圆的半径设置为 2,这使得该点在任何图表时间范围内都通常可见。
现在让我们对 \MQL5\Include\DoEasy\Objects\Series\Patterns\Patterns\ PatternInsideBar.mqh 孕线形态文件实施类似的改进。
声明重绘形态图标的虚方法:
//+------------------------------------------------------------------+ //| PatternInsideBar.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict // 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); };
在类主体外部实现使用新尺寸重新绘制形态图标的方法:
//+------------------------------------------------------------------+ //| 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; }
该方法的逻辑在代码中进行了注释 - 这里的一切都非常简单:我们计算画布的新尺寸,更改其尺寸,并根据新尺寸在画布上绘制新的位图。
在创建信息面板外观的方法中,计算柱形形态大小时出现错误:
//--- 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();
这种计算有权存在,但只有当模式的相邻柱形不被周末分隔时才有效。如果模式的左侧和右侧柱形之间有周末,则它们会被添加到柱形数量中,这样我们得到的值将不是 2,而是 4。
让我们修正一下计算:
//--- 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();
现在,我们将始终能够获得形态的两个柱形之间的正确柱形数量。
由于图表宽度和高度的值现在已提前在变量中设置,因此删除接收图表属性:
//--- 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());
并调整面板坐标的计算以使用变量中的预设值:
//--- 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());
删除图表上绘制形态图标方法中绘制点的代码块:
//+------------------------------------------------------------------+ //| 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); }
现在该方法看起来更简单了:
//+------------------------------------------------------------------+ //| 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; }
所有其他改进都将在创建新的“外包线”形态类后实施。
“外包线”形态类
在 \MQL5\Include\DoEasy\Objects\Series\Patterns\库文件夹中,创建 CPatternOutsideBar 类的新文件 PatternOutsideBar.mqh 。
该类应该从形态对象基类继承,而其文件应该包含在创建的形态类文件中:
//+------------------------------------------------------------------+ //| 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 { }
声明形态对象类的标准方法:
//+------------------------------------------------------------------+ //| "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); };
所有这些方法对于所有形态都是相同的。然而,它们的实现在不同形态之间略有不同。让我们看一下每个方法。
在类构造函数中,定义形态名称、形态中包含的烛形数量以及等于形态烛形数量的相邻连续形态数量:
//+------------------------------------------------------------------+ //| 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); }
创建信息面板外观的方法:
//+------------------------------------------------------------------+ //| 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); }
该方法的逻辑在代码中进行了注释。这里绘制了一个面板,其中红色代表看跌形态,蓝色代表看涨形态。在顶部,绘制了一个带有 (i) 符号的图标,并写出了形态的名称及其特征 - 形态的两个柱形的比例。底部部分通过为形态搜索给出的值和找到形态的时间描述模式的方向。
在图表上绘制形态图标的方法:
//+------------------------------------------------------------------+ //| 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; }
使用新尺寸在图表上重新绘制形态图标的方法:
//+------------------------------------------------------------------+ //| 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; }
只需更改画布大小并以新尺寸重新绘制新的形态位图。
创建位图对象的方法:
//+------------------------------------------------------------------+ //| 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; }
如果图形对象之前已经创建,那么我们只需保留该方法。否则,我们将获得放置图形对象的价格和时间,计算其宽度和高度并创建一个新对象。然后我们将锚点设置在对象的中心并在其上绘制其外观。图形对象中心点所处的价格是根据形态中最大烛形的中心来计算的。
创建位图对象外观的方法:
//+------------------------------------------------------------------+ //| 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); }
这里首先使用完全透明的颜色擦除位图。然后根据图表比例计算绘制区域起点的局部 X 坐标。使用计算出的初始 X 坐标在图形对象的整个高度上绘制一个填充的矩形,并在顶部使用相同的坐标和相同的大小绘制一个矩形框。
计算绘图对象高度的方法:
//+------------------------------------------------------------------+ //| 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; }
在这里,我们得到图表的价格范围 —— 从图表上的最高价格到图表上的最低价格,以及形态的价格范围,从定义烛形的高点到定义烛形的低点。然后,我们计算一个范围与另一个范围的像素比率,并返回生成的位图对象高度,为其添加 8 个像素 —— 顶部 4 个,底部 4 个。
外部柱形形态类已准备就绪。现在我们需要去掉时间序列类中的许多相同方法,每个方法都为自己的形态做同样的工作。我们为形态上的每个动作创建一个方法,其中我们将指示所需形态的类型。
我们将使用 MqlParam 结构将形态中包含的烛形关系所需的所有参数传输到形态类。这将允许我们将完全不同的参数传递给不同形态的不同类,而不仅仅是那些由所有类相同的形式变量严格定义的参数。
目前,我们不会改变这些变量的名称,它们表示形态烛形的各种比率。但如果有必要,我们会将它们重命名为“不露面的”变量,例如 “param1”,“param2” 等。在类构造函数中,如有必要,我们将为每个这样的变量分配所需的形态参数。现在,我们将使用已经命名的变量,这些变量对于所有形态都是相同的。
在位于 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh 的柱形对象类中,将 AddPattern() 方法名称替换为 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)
不过,此方法将形态类型添加到柱形对象,而不是指向形态的指针。因此,将方法名称更改为更正确的名称是更合乎逻辑的。
为了能够从列表中选择和接收所需的形态,在 CSelect 类的 \MQL5\Include\DoEasy\Services\Select.mqh 文件中,包含所有形态文件:
//+------------------------------------------------------------------+ //| 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"
这将使形态类在时间序列类中可用于处理模式。
我们需要比较 MqlParam 结构数组是否相等。我们在 \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; } //+------------------------------------------------------------------+
形态管理类位于 \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh 时间序列类文件中。在抽象形态管理类中,删除不再需要的变量并添加新的变量:
//+------------------------------------------------------------------+ //| 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
之前,在形态搜索方法中,我们传递变量中的最小烛形尺寸来搜索形态:
virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const { return WRONG_VALUE; }
现在我们将通过 MqlParam 变量传输此大小,因此现在此变量已从所有形态管理类中的所有形态搜索方法中删除:
//--- (1) 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; }
从类的公有部分中,删除设置和获取点形态绘制属性的方法:
//--- (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; }
声明形态参数数组,添加处理新变量的方法,从 CreateAndRefreshPatternList() 虚拟方法中删除传递 min_body_size 参数,而在参数构造函数中,添加通过 MqlParam 结构数组传递形态属性数组。声明一个新方法来重新绘制图表上所有现有的形态:
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[]); };
在类的参数构造函数中,获取图表缺少的属性,并从传递给构造函数的数组中填充形态参数:
//+------------------------------------------------------------------+ //| 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; } }
在搜索形态并将找到的形态添加到所有形态列表中的方法中进行必要的修复:
//+------------------------------------------------------------------+ //| 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(); }
该方法现在还没有正式参数。当创建新形态时,图表属性会立即传输并设置到其中。在形态搜索循环块的最后,插入一个空白以创建新的形态事件。此外,在接下来的文章中,我们将发送有关新时间序列形态出现的事件。
使用位图对象的新尺寸重新绘制图表上的形态的方法:
//+-------------------------------------------------------------------+ //|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); }
我们只需循环遍历时间序列形态列表并调用每个形态的重绘方法。虽然该方法还不是最佳的,但会在整个形态列表中执行重新绘制。尽管如此,也可以安排仅重新绘制图表上可见的历史记录部分。我稍后会讲到它。
现在让我们细化形态管理类。它们位于同一文件的更深处。让我们找到字符串的所有出现 -
const uint min_body_size
在类方法的形式参数中并将其删除。现在,所需形态烛形的最小尺寸与 MqlParam 结构数组中的形态参数一起传递。
只需用颜色标记所有发生更改的地方,以免在这里描述每个这样的代码字符串,这对于所有类都是相同的。
在 Pin Bar(锤子线)形态管理类中:
//+------------------------------------------------------------------+ //| 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
在 Inside Bar(孕线)形态管理类中:
//+------------------------------------------------------------------+ //| 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
与 Pin Bar 和 Inside Bar管理类类似,我们将实现用于管理 Outside Bar (外包线)形态的类:
//+------------------------------------------------------------------+ //| 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(); } };
定义该形态的必要条件是:右侧柱形的主体必须完全覆盖左侧柱形的主体尺寸,并且柱形的影线相等,或者右侧柱形的影线可以覆盖左侧柱形的影线。此搜索在 CheckOutsideBar() 方法中执行。此外,还需要考虑形态中包含的烛形主体的尺寸与烛形总尺寸的比例。此检查由 CheckProportions() 方法执行。
在类构造函数中,我们将形态状态设置为“Price Action”,将模式类型设置为“Outside Bar”,并从传递给构造函数的结构数组中设置所有形态烛形比例。
根据形态搜索条件创建对象 ID 的方法:
//+------------------------------------------------------------------+ //| 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); }
两个标准(烛形尺寸的百分比和烛形主体与完整烛形尺寸的比率)以实数(百分比)指定,并且不能超过 100。因此,我们通过乘以 100 将它们转换为整数值,然后使用 UshortToLong() 扩展标准对象库方法创建一个 ulong ID,并用 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); }
以指定方向创建形态的方法:
//+------------------------------------------------------------------+ //| 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; }
该方法创建“Outside Bar”形态类的新对象,填充有关其比例和搜索条件的数据,并返回指向所创建对象的指针。
形态搜索方法:
//+------------------------------------------------------------------+ //| 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; }
方法的逻辑在注释中有所描述。当前柱形的开启时间会传递给该方法。获取除当前柱形之外的所有柱形列表(我们不在零柱上查找形态)。在基于结果柱形列表的循环中,每隔两个附近的柱形检查它们是否符合形态标准。如果它们的比例是一种形态,则确定并返回形态方向。
返回对象管理的形态列表的方法:
//+------------------------------------------------------------------+ //| 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); }
我们从所有形态的总体列表中获得按图表周期排序的列表,然后从结果列表中获得按交易品种名称排序的列表。接下来,按外包线形态类型对结果列表进行排序,并按控制对象 ID 对结果列表进行排序。因此,该方法仅返回由该类管理的形态的列表。
现在我们需要彻底重新设计形态管理类。您可以在此处阅读更多相关信息。
我们将创建几种方法来处理指定的形态,而不是创建冗长而单调的方法来处理每种形态类型。然后,在同一个文件中,我们将从形态管理类中删除许多类似的方法,例如:
... "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)
一般来说,有很多这样的方法 —— 每种形态都有自己的方法。结果是 ——几乎有 1300 个代码字符串。让我们删除形态管理类的所有字符串并重写整个类。现在将有几种处理形态的方法,并能够选择使用哪种形态。
完全重写的类及其所有方法现在看起来像这样:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
方法的逻辑在代码中进行了注释。我想对突出显示的代码块做一些澄清:当在测试器的可视模式下工作时, OnChartEvent() 几乎没有用。然而,我们在那里跟踪有关 CHARTEVENT_CHART_CHANGE 事件的图表尺寸变化,并将新的图表尺寸写入形态管理对象,然后这些数据将传送到形态对象。但它在测试器中不起作用。因此,专用测试器代码块会监控图表尺寸的变化,当其发生变化时,新数据就会输入到形态控制对象中。
在同一文件 \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh 的代码中,我们有 CSeriesDE 时间序列类。让我们最终确定它。
每次我们在时间序列列表中搜索任何柱形对象时,我们都会借助 new 运算符得到一个新对象。设置它的参数,在列表中搜索具有相同参数的对象,然后删除这个新创建的对象:
//+------------------------------------------------------------------+ //| 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); }
因此,在某些情况下,我们会在循环中不断创建和删除对象。这种行为是不正确的。最好创建一个用于搜索的单个实例对象并使用它来设置参数并用作搜索的形态。这样我们就可以摆脱不断地重新创建对象。
声明用于搜索的柱形对象的实例:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: CBar m_bar_tmp; // Bar object for search ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol
并重写按时间序列中的时间返回柱形对象的方法:
//+------------------------------------------------------------------+ //| 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); }
现在,我们不需要创建和删除新对象,而是在单个实例中设置所需的搜索参数,并按形态在列表中搜索相同的实例。该对象仅在类构造函数中创建一次,并且会持续使用而无需重新创建。
就像在形态管理类中一样,我们将删除用于处理每个模式的长方法列表,并用选择所需形态的几个方法替换它们:
//+------------------------------------------------------------------+ //| 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); }
现在,要选择所需的形态,只需在方法参数中指定其类型,而不是对每个形态使用自定义方法。
在 \MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh 中的交易品种时间序列类中,以相同方式更改处理形态的方法。
在类的主体中,在最后,在标题下
//--- 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 | //+------------------------------------------------------------------+
有一长串用于处理形态的方法声明。让我们用选择所需形态来声明几种方法来替换所有这些声明:
//--- 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); };
在类主体之外,在文件列表的最末尾,在同一个标题下,写入了声明方法的实现。让我们删除整个长列表并用新方法替换它:
//+------------------------------------------------------------------+ //| 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); }
每种方法都会接收要处理其模式的图表所需的时间范围,以及用于选择形态控制对象的必要参数。接下来,获取所需时间序列的指针,并返回调用同名时间序列类方法的结果。
让我们对时间序列集合类文件 \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh 做一些改进。
在这个类中,我们将接收图表尺寸及其变化,并将它们发送到形态管理类。让我们添加新变量来存储图表尺寸:
//+------------------------------------------------------------------+ //| 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:
在标题下 ——
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
就像前面的类一样,删除声明的方法并输入指示形态类型的新方法:
//--- 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);
在类主体的末尾,声明事件处理程序:
//--- 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); };
在类构造函数中,将图表尺寸写入新变量:
//+------------------------------------------------------------------+ //| 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); }
在类主体之外,标题下 -
//+------------------------------------------------------------------+ //| Handling timeseries patterns | //+------------------------------------------------------------------+
删除处理每个特定形态的所有方法。设置新的方法来代替已删除的方法 - 指示形态类型:
//+------------------------------------------------------------------+ //| 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); }
每种方法都会接收需要处理形态的图表所需的时间范围和交易品种,以及用于选择形态控制对象的必要参数。接下来,获取所需时间序列的指针,并返回调用同名交易品种时间序列类方法的结果。
让我们实现事件处理程序:
//+------------------------------------------------------------------+ //| 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()); } } } }
处理程序的整个逻辑在代码的注释中有完整的描述。当检测到图表尺寸发生变化时,新的尺寸将传递给时间序列形态管理类,并根据新的图表尺寸重新绘制模式。
现在我们来修改 \MQL5\Include\DoEasy\Engine.mqh 中 CEngine 库的主类。
在这里,类主体中也有一长串用于处理形态的方法。我们只删除那些负责以点的形式绘制形态的方法。其余方法需要改进 - 现在需要将形态参数发送到使用 MqlParam 结构数组处理它的方法。在所有尚未使用的方法中,仅填充一个结构字段,其中发送形态烛形的最小尺寸,例如:
//--- 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); }
对于尚未使用的形态的其余方法是相同的,无需考虑它们 - 您可以在文章附带的文件中找到它们。
让我们研究一下处理库中已创建的形态的方法。
设置使用外包线(吞噬)形态的标志的方法,如果控制对象尚不存在,则创建一个控制对象:
//--- 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 Outside 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); }
设置使用孕线形态的标志并创建控制对象(如果尚不存在)的方法:
//--- 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); }
在此方法中,仅填充结构的一个字段,因为该形态没有可自定义的属性,除了最小烛形尺寸的属性,这是所有模式共有的。
设置使用锤子线形态的标志并创建控制对象(如果尚不存在)的方法:
//--- 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); }
让我们看一下在图表上显示形态的方法:
- 对于库中已经实现的形态
- 对于尚未创建的形态:
//--- 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); }
其他形态的其余方法与尚未实现的 Rails 形态的方法相同,这里没有必要考虑它们。
在类主体的最后,声明事件处理程序:
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); };
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| 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); }
这里,首先调用图形对象集合类的事件处理程序,然后调用时间序列集合的事件处理程序。
在 CEngine::OnDoEasyEvent() 库的事件处理程序中,即在时间序列事件处理块中,添加用于将来处理新形态事件的块:
//--- 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 } }
这里暂时是空的,但是当我们将来发送形态事件时,我们会在这里处理新形态的出现。
这样就完成了库的修改。现在让我们检查新形态的搜索以及图表水平刻度变化的处理。
测试
为了执行测试,我们使用上一篇文章中的 EA ,并将其保存到新的 \MQL5\Experts\TestDoEasy\ Part136\ 文件夹中,文件名为 TestDoEasy136.mq5 。
从设置中删除使用点绘制形态的标志:
sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput bool InpDrawPatternsAsDots= true; // Draw Patterns as dots
将使用形态的标志添加到搜索“外包线”形态的设置和参数中:
sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_CURRENT; // Mode of used timeframes list sinput string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator) sinput ENUM_INPUT_YES_NO InpSearchPinBar = INPUT_YES; // Search for Pin Bar patterns sinput double InpPinBarRatioBody = 30.0; // Pin Bar Ratio Body to Candle size sinput double InpPinBarRatioLarger = 60.0; // Pin Bar Ratio Larger shadow to Candle size sinput double InpPinBarRatioSmaller= 30.0; // Pin Bar Ratio Smaller shadow to Candle size sinput ENUM_INPUT_YES_NO InpSearchInsideBar = INPUT_YES; // Search for Inside Bar patterns sinput ENUM_INPUT_YES_NO InpSearchOutsideBar = INPUT_YES; // Search for Outside Bar patterns sinput double InpOBRatioCandles = 50.0; // Outside Bar Ratio of sizes of neighboring candles sinput double InpOBRatioBodyToCandle= 50.0; // Outside Bar Ratio Body to Candle size sinput ENUM_INPUT_YES_NO InpUseBook = INPUT_NO; // Use Depth of Market sinput ENUM_INPUT_YES_NO InpUseMqlSignals = INPUT_NO; // Use signal service sinput ENUM_INPUT_YES_NO InpUseCharts = INPUT_NO; // Use Charts control sinput ENUM_INPUT_YES_NO InpUseSounds = INPUT_YES; // Use sounds
由于设置形态使用的方法在设置标志时会立即搜索并显示找到的形态,因此对于测试来说,只需在 OnInit() 处理程序中设置标志,立即开始搜索形态并将其显示在图表上:
//--- 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); }
在 OnChartEvent() EA 处理程序中,为引擎库的主对象添加调用此处理程序:
//+------------------------------------------------------------------+ //| 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 //... ... ...
在库事件处理程序中 — 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
在处理时间序列事件的块中,如果发生此类事件,则添加有关错过柱的消息:
//--- 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
目前,这在技术上没有提供任何内容 - 它只是显示日志中错过的柱数。但很明显,如果在 EA 在终端中运行时连接断开,或者计算机进入睡眠模式然后又恢复,则库可以正确处理跳过柱形。这意味着当收到这样的事件时,可以更新所需数量的柱形以恢复缺失的时间序列和模式数据。此项工作将会在未来实施。
让我们编译并启动 EA,设置以下值来搜索外包线形态:
我们特意将蜡烛比例设置得如此小,以便找到尽可能多的形态。
当蜡烛比例为正常值(50%或更多)时,形态更为正确,但相当罕见。
启动后,将找到并显示外包线形态:
我们看到已经找到了形态。调整图表大小时,形态图标的大小也会发生变化。
接下来做什么?
在下一篇有关价格形成的文章中,我们将继续创建不同的形态并在它们出现时发送事件。
所有创建的文件都附在文章之后,可下载用于自学和测试。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14710
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


