English Русский Español Deutsch 日本語 Português
preview
DoEasy.服务函数(第 3 部分):外包线形态

DoEasy.服务函数(第 3 部分):外包线形态

MetaTrader 5示例 |
257 0
Artyom Trishkin
Artyom Trishkin

目录


概念

让我们继续 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.mqhPatternInsideBar.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 &param[]);
  };

在类的参数构造函数中,获取图表缺少的属性,并从传递给构造函数的数组中填充形态参数

//+------------------------------------------------------------------+
//| 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 &param[]) :
  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 &param[]) :
                        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 &param[]) :
                        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 &param[]) :
                        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 &param[])
                       {
                        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 &param[])
                       {
                        //--- 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 &param[],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 &param[])
                       {
                        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 &param[],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 &param[],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 &param[],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 &param[])
                       {
                        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 &param[],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 &param[],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 &param[],const ENUM_TIMEFRAMES timeframe,const bool flag);
//--- Return the flag of using the specified Harami pattern
   bool              IsUsedPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],const ENUM_TIMEFRAMES timeframe);
//--- Draw marks of the specified pattern on the chart
   void              DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],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 &param[],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 &param[],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 &param[],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 &param[],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 &param[],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 &param[],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 &param[],const string symbol,const ENUM_TIMEFRAMES timeframe);
//--- Draw marks of the specified pattern on the chart
   void                    DrawPattern(const ENUM_PATTERN_TYPE pattern,MqlParam &param[],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 &param[],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 &param[],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 &param[],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 &param[],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 &param[],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

附加的文件 |
MQL5.zip (4958.58 KB)
价格行为分析工具包开发(第二部分):分析注释脚本 价格行为分析工具包开发(第二部分):分析注释脚本
秉承我们简化价格行为分析的核心理念,我们很高兴推出又一款可显著提升市场分析能力、助力您做出精准决策的工具。该工具可展示关键技术指标(如前一日价格、重要支撑阻力位、成交量),并在图表上自动生成可视化标记。
在外汇数据分析中使用关联规则 在外汇数据分析中使用关联规则
如何将超市零售分析中的预测规则应用于真实的外汇市场?购买饼干、牛奶和面包与证券交易所的交易有何关联?本文讨论了一种基于关联规则的算法交易的创新方法。
数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易 数据科学和机器学习(第 31 部分):利用 CatBoost AI 模型进行交易
CatBoost AI 模型最近在机器学习社区中广受欢迎,因为它们的预测准确性、效率、及针对分散和困难数据集的健壮性。在本文中,我们将详细讨论如何实现这些类型的模型,进而尝试进击外汇市场。
交易中的神经网络:具有相对编码的变换器 交易中的神经网络:具有相对编码的变换器
自我监督学习是分析大量无标签数据的有效方法。通过令模型适应金融市场的特定特征来提供效率,这有助于提升传统方法的有效性。本文讲述了一种替代的注意力机制,它参考输入之间的相对依赖关系。