English Русский Español 日本語 Português
preview
DoEasy. Dienstfunktionen (Teil 3): Das Muster der „Outside Bar“

DoEasy. Dienstfunktionen (Teil 3): Das Muster der „Outside Bar“

MetaTrader 5Beispiele | 22 Mai 2025, 08:34
57 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Konzept

Fahren wir fort mit dem Abschnitt der DoEasy-Bibliothek über den Umgang mit Preismustern.

Im vorangegangenen Artikel haben wir die Suche und Anzeige von Preismuster mit zwei Balken, der „Inside Bar“ erstellt. Hier werden wir eine Suche nach dem Muster „Outside Bar“ erstellen, das im Wesentlichen ein Spiegelbild des „Inside Bar“ ist.

Aber es gibt auch Unterschiede. Wenn es sich bei „Inside Bars“ um ein bidirektionales Muster handelt und der Einstieg auf beiden Seiten des Musters möglich ist, werden „Outside Bars“ in zwei Richtungen unterteilt - auf- und abwärts:

  • BUOVB (Bullish Outside Vertical Bar) - steigender, äußerer, vertikaler Balken. Der Signalbalken überdeckt den vorhergehenden vollständig, sein Schlusskurs ist höher als das Maximum des vorhergehenden Balkens. Schließen Sie einen Handel beim Durchbruch des Hochs des Signalbalkens + Filter (5-10 Punkte) ab.
  • BEOVB (Bearish Outside Vertical Bar) - fallender, äußerer, vertikaler Balken. Der Signalbalken überdeckt den vorhergehenden vollständig, sein Schlusskurs ist niedriger als das Minimum des vorhergehenden Balkens. Der Einstieg erfolgt beim Durchbrechen des Minimums des Signalbalkens - Filter (5-10 Punkte).

Bevor wir mit der Erstellung der Klasse des Musters beginnen, müssen wir einige Änderungen an den bereits vorhandenen Bibliotheksklassen vornehmen. Erstens sind nicht alle Methoden in den Musterzugriffsklassen optimal angelegt - dies war nur ein Konzepttest, bei dem alles „frontal“ durch viele Methoden implementiert wurde, jede für ihre eigene Art von Mustern. Jetzt werden wir nur eine Methode für den Zugriff auf die Arbeit mit den angegebenen Mustern erstellen. Für jede der Aktionen verwenden wir eine eigene Methode, bei der das erforderliche Muster durch eine Variable angegeben wird. Dadurch werden die Klassencodes erheblich vereinfacht und verkürzt.

Zweitens sind während der langen Zeit, in der nicht an der Bibliothek gearbeitet wurde, einige Änderungen und Ergänzungen in der MQL5-Sprache vorgenommen worden (noch sind nicht alle angekündigten Änderungen in die Sprache aufgenommen worden), und wir werden diese Änderungen in die Bibliothek aufnehmen. Ich habe auch einige Fehler behoben, die während der gesamten Zeit der Bibliothekstests gefunden wurden. Ich werde alle vorgenommenen Verbesserungen beschreiben.



Verbesserung der Bibliotheksklassen

Bei der Bestimmung einiger Muster ist die gegenseitige Beziehung zwischen den Größen benachbarter Kerzen wichtig, die an der Bildung der Musterfigur beteiligt sind. Fügen wir den Mustereigenschaften einen neuen Wert hinzu, um das Größenverhältnis zu bestimmen. In den Eigenschaften jeder Muster- und Musterverwaltungsklasse ergänzen wir eine Eigenschaft, die den Wert angibt, nach dem das Verhältnis der Kerzen gesucht wird.

In der Bibliotheksdatei \MQL5\Include\DoEasy\Defines.mqh, und zwar in der Aufzählung der Materialeigenschaften des Musters, fügen wir neue Eigenschaften hinzu und erhöhen die Gesamtzahl der Eigenschaften von 10 auf 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

Ab der Beta-Version 4540 des MetaTrader 5 Client-Terminals bietet die Enumeration ENUM_SYMBOL_SWAP_MODE den Schlüssel „SYMBOL_SWAP_MODE_CURRENCY_PROFIT“.

Wenn die Funktion SymbolInfoInteger() einen solchen Wert zurückgibt, werden die Swaps auf dem Konto in der Gewinnwährung berechnet.

Fügen wir diesen Wert zu den Bibliotheksdateien hinzu. In \MQL5\Include\DoEasy\Data.mqh geben wir die Indizes der neuen Bibliotheksmeldungen ein:

   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

und die Textnachrichten, die den neu hinzugefügten Indizes entsprechen:

   {"Свопы начисляются в деньгах в маржинальной валюте символа","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"},

Fügen wir die Beschreibung von zwei neuen Laufzeitfehlern mit den Codes 4306 und 4307 zum Array der Laufzeitfehlermeldungen hinzu:

//+------------------------------------------------------------------+
//| 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"    
   },                                                                                             
  };

In \MQL5\Include\DoEasy\Objects\Symbols\Symbol.mqh fügen wir die neuen Werte der Enumeration der Methode hinzu, die die Anzahl der Nachkommastellen in Abhängigkeit von der Swap-Berechnungsmethode zurückgibt:

//+------------------------------------------------------------------+
//| 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
     );
  }

Auch in der Methode, die die Beschreibung des Swap-Berechnungsmodells zurückgibt, geben wir die Rückgabe einer Zeichenkette mit einer Beschreibung des neuen Swap-Berechnungsmodus ein:

//+------------------------------------------------------------------+
//| 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)
     );
  }

In der MQL4-Definitionsdatei \MQL5\Include\DoEasy\ToMQL4.mqh fügen wir eine neue Eigenschaft zur Enumeration der Methoden für die Berechnung von Swaps beim Übertragen einer Position hinzu:

//+------------------------------------------------------------------+
//| 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
  };

Wir korrigieren in der Bibliotheksbasisklassendatei \MQL5\Include\DoEasy\Objects\BaseObj.mqh die Strings mit potenziellen Speicherlecks:

//+------------------------------------------------------------------+
//| 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);
  }

Hier geben die Methoden das Ergebnis des Hinzufügens des Objekts zur Liste zurück (true - erfolgreich hinzugefügt, false - Fehler). Tritt ein Fehler beim Hinzufügen des Objekts auf, das mit dem Befehl new erstellten wurde, verbleibt das Objekt irgendwo im Speicher, ohne dass der Zeiger darauf in der Liste gespeichert wird, was zu Speicherlecks führt. Beheben wir das:

//+------------------------------------------------------------------+
//| 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;
  }

Wenn nun das Hinzufügen eines Ereignisobjekts zur Liste fehlschlägt, wird das neu erstellte Objekt auf die gleiche Weise gelöscht, wie wenn das gleiche Objekt bereits in der Liste wäre, und die Methode gibt false zurück.

In \MT5\MQL5\Include\DoEasy\Objects\Orders\Order.mqh muss bei der Berechnung der Anzahl der Punkte eine Rundung des erhaltenen Ergebnisses hinzugefügt werden, da andernfalls bei Werten, die kleiner als 1 sind, aber nahe bei 1 liegen, bei der Umwandlung einer reellen Zahl in eine ganze Zahl eine Anzahl von Punkten von Null entsteht:

//+------------------------------------------------------------------+
//| 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;
  }

In \MQL5\Include\DoEasy\Objects\Events\Event.mqh gab es einen Fehler bei der Rückgabe eines Wertes vom Typ long als Wert einer Enumeration:

//--- 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();                                        }

Während der Wert des Tickets eines Auftrags vom Typ long den Wert INT_MAX (aus der Enumeration des Datentyps int) nicht überschritt, wurden der Wert des Tickets korrekt zurückgegeben. Sobald der Ticketwert jedoch INT_MAX überschritt, kam es zum Überlauf und es wurde eine negative Zahl zurückgegeben. Jetzt ist alles geregelt:

//--- 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();                                        }

In der Enumeration, die den Namen des Ereignisstatus des Auftragssystems zurückgibt, fehlte der Änderungsstatus, und in einigen Fällen wurde der Status in der Beschreibung des Handelsereignisses als „Unbekannt“ angezeigt. Behoben durch das Hinzufügen der Zeichenkette:

//+------------------------------------------------------------------+
//| 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)
     );
  }

Beheben wir ein Versäumnis in der Klasse des Handelsobjekts: Die Politik des Füllens des Volumens (aus der Enumeration ENUM_ORDER_TYPE_FILLING ) wurde fälschlicherweise an die Positionseröffnungsmethode übergeben.

Implementieren wir die notwendigen Verbesserungen an den Handelsmethoden in MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh. Ich werde den Block zum Füllen der Handelsanforderungsstruktur in der Methode zur Eröffnung einer Position um die Einstellung der Volumenfüllungspolitik ergänzen:

//+------------------------------------------------------------------+
//| 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 
  }

In der Klasse wird der Wert für die Befüllungsrichtlinie zunächst auf die Variable m_type_filling gesetzt, wenn die Bibliothek mit den für Aufträge zulässigen Werten initialisiert wird (die Methode CEngine::TradingSetCorrectTypeFilling). Wird der Methode open ein negativer Wert für die Füllungsrichtlinie übergeben, wird der Wert aus der Variablen m_type_filling, die bei der Initialisierung der Bibliothek gesetzt wurde, verwendet. Wenn eine andere Art der Volumenfüllung angegeben werden soll, muss diese im Methodenparameter type_filling übergeben werden, und der übergebene Wert wird verwendet.

Zuvor gab es keine zusätzliche Zeichenfolge. Wenn eine andere (nicht standardmäßige) Richtlinie eingestellt werden sollte, war die Befüllungsrichtlinie immer Return (ORDER_FILLING_RETURN), da das Feld type_filling der Struktur MqlTradeRequest nicht gefüllt wurde und immer einen Nullwert hatte. Das Problem wurde jetzt behoben.

Beheben wir ähnliche Unzulänglichkeiten in anderen Methoden der Klasse, in denen das Volumen Füllung Politik ist in der einen oder anderen Weise benötigt:

//+------------------------------------------------------------------+
//| 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 
  }

Nun wollen wir ähnliche Auslassungen in der Handelsklasse der Bibliothek in \MQL5\Include\DoEasy\Trading.mqh beheben.

In der Methode OpenPosition() wird die Handelsanforderungsstruktur mit Werten gefüllt, die auf den an die Methode übergebenen Werten basieren:

//--- 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());


und beim Aufruf der Methode zur Positionseröffnung eines Symbolhandelsobjekts geben seine Parameter nicht die Werte an, die in der Struktur des Handelsauftrags festgelegt sind, sondern diejenigen, die an die Positionseröffnungsmethode übergeben wurden:

//--- 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);
      //... ... ...

Bringen wir das in Ordnung:

      //--- 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);

Um Ereignisse auf allen geöffneten Charts zu verfolgen und nicht nur auf demjenigen, mit dem das auf der Bibliothek basierende Programm verbunden ist, wird ein Indikator auf jedem geöffneten (oder bereits geöffneten) Chart platziert, der Chart-Ereignisse verfolgt und an das Programm sendet.

Wenn das Client-Terminal zunächst mit einem Server verbunden war und die Charts verschiedener Instrumente geöffnet wurden und das Terminal dann mit einem anderen Server verbunden wurde, auf dem es keine Instrumente gibt, deren Charts bereits geöffnet sind, kann das Programm diese Indikatoren nicht auf solchen Charts platzieren, und das Journal enthält Fehlereinträge, die auf einen Fehler bei der Erstellung des Indikators hinweisen und nicht auf das Fehlen von Symbolen für offene Charts auf dem Server. Um diese zweideutigen Protokolleinträge zu beheben, müssen wir eine Prüfung auf das Vorhandensein des Symbols auf dem Server hinzufügen, bevor wir den Indikator erstellen.

Fügen wir in \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, und zwar in der Methode zur Erstellung des Ereignissteuerungsindikators, eine Prüfung auf das Vorhandensein eines Symbols auf dem Server hinzu:

//+------------------------------------------------------------------+
//| 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;
  }

Wenn nun kein Symbol auf dem Server vorhanden ist, wird im Journal eine entsprechende Meldung angezeigt, und die Methode gibt false zurück.

In der Datei \MQL5\Include\DoEasy\Objects\Graph\GBaseObjGBaseObj.mqh des grafischen Basisobjekts, und zwar in der Methode, die die Beschreibung des Typs des grafischen Elements zurückgibt, fügen wir den fehlenden Typ für ein grafisches Objekt hinzu - 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"
     );
  }  

Wir werden auch die Musterklassen leicht überarbeiten - ihre Organisation und Struktur. Die abstrakte Basismusterklasse befindet sich in \MQL5\Include\DoEasy\Objects\Series\Patterns\Pattern.mqh, und dann werden die von der Basisklasse geerbten Musterklassen in diese Klasse geschrieben.

Lassen Sie uns alle Musterklassen in eigene Dateien aufteilen. Jetzt ist es möglich, die Mustersymbole als reguläre Punkte im Chart anzuzeigen, die mit Hilfe von standardmäßigen Trendlinienobjekten mit Preis- und Zeitkoordinaten auf einem Balken gezeichnet werden. Ich habe beschlossen, diese Funktion aufzugeben, um den Code zu vereinfachen. Alle Symbole werden als grafische Objekte, als Bitmap gezeichnet. Zeichnungen von Mustern werden auf der Grundlage der Größen der Balken, aus denen das Muster besteht, erstellt. Um die Größe von Zeichenobjekten zu ändern, wenn der horizontale Maßstab des Charts geändert wird, müssen wir eine Variable eingeben, die den Maßstabswert speichert. Die Variablen für die Speicherung der übrigen Chartgrößen sind bereits im Grundmusterobjekt enthalten. Wenn Sie die Größe des Charts ändern, werden die neuen Werte in alle erstellten Objekte der gefundenen Muster passen, und sie werden entsprechend der neuen Größen neu gezeichnet werden.

Wir erstellen in der Bibliotheksdatei \MT5\MQL5\Include\DoEasy\Objects\Series\Patterns\ zwei neue Dateien PatternPinBar.mqh und PatternInsideBar.mqh. Dort platzieren wir die Klassen der Muster „Pin Bar“ und „Inside Bar“ (die direkt in der abstrakten Basismusterklassendatei festgelegt wurden) mit Hilfe von Ausschneiden und Einfügen. Als Nächstes werden wir Änderungen an ihnen vornehmen, aber zunächst werden wir mit der Bearbeitung der abstrakten Musterklasse fortfahren.

Aus dem geschützten Abschnitt der Klasse entfernen wir die Flag-Variable m_draw_dots, die angibt, wie die Mustersymbole mit Punkten gezeichnet werden, und deklarieren die Variable zum Speichern der Chartbreite in Pixeln:

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:

Die Methoden zur Berechnung der Breite und Höhe eines Bitmap-Objekts

//--- Calculate the bitmap object (1) width and (2) height
   int               GetBitmapWidth(void);
   int               GetBitmapHeight(void);

und benennen wir sie in die richtigen Namen um und deklarieren sie als virtuell:

//--- Calculate the bitmap object (1) width and (2) height
   virtual int       CalculatetBitmapWidth(void);
   virtual int       CalculatetBitmapHeight(void);

„Get“ bedeutet jedoch „Abrufen“, nicht „Berechnen“. Virtuelle Methoden ermöglichen es geerbten Klassen, ihre eigenen Berechnungen der Breite und Höhe des Musters zu verwenden, abhängig von der Art des Musters und der Methode, es zu zeichnen.

Im öffentlichen Abschnitt der Klasse entfernen wird die Methode 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);

und deklarieren die virtuelle Methode 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);

Mit der Methode Redraw() wird das Bitmap-Objekt mit den neuen Abmessungen neu gezeichnet. Da jeder Mustertyp seine eigenen Arten von Bitmaps haben kann, wird die Methode als virtuell deklariert und gibt hier einfach true zurück. In geerbten Klassen wird die Methode überschrieben, um genau die Bitmap neu zu zeichnen, die für das angegebene Muster gezeichnet wird.

Dort, im öffentlichen Abschnitt, werden wir auch die Methoden zum Setzen und Zurückgeben der Chartbreite in Pixeln festlegen:

//--- 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; }

Wenn die Breite des Charts geändert wird, schreibt die Musterverwaltungsklasse die neuen Chartgrößen nacheinander in alle Musterobjekte - sodass Sie in jedem der erstellten Musterobjekte nicht die Eigenschaften des Charts erhalten, sondern die neuen Größen nur einmal erhalten, wenn sie sich ändern, und sie dann in alle erstellten Musterobjekte schreiben.

Ganz am Ende des Klassenkonstruktors entfernen wir die Zeichenfolge für die Initialisierung einer nicht mehr benötigten Variablen:

//--- 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;
  }

Wir fügen in der Methode, die die Beschreibung der realen Eigenschaft des Musters zurückgibt, zwei Codeblöcke für die Anzeige der Beschreibungen von zwei neuen Mustereigenschaften hinzu:

//+------------------------------------------------------------------+
//| 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)
         )  :
      ""
     );
  }

In der Methode, die das Infopanel auf dem Chart anzeigt, müssen die Charteigenschaften nicht mehr abgerufen werden, da sie bereits im Musterobjekt festgelegt sind, wenn es erstellt wird oder wenn die Größe des Charts geändert wird.

Entfernen wir die Zeichenketten für den Empfang von Charteigenschaften aus der Methode:

//+------------------------------------------------------------------+
//| 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

Die folgenden, bei der Erstellung des Objekts voreingestellten Eigenschaften werden nun anstelle der zuvor erhaltenen Charteigenschaften verwendet:

//+------------------------------------------------------------------+
//| 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);
  }

Die Strings, die sich auf das Zeichnen der Mustersymbole mit Punkten beziehen entfernen wir, aus der Methode zum Zeichnen, Anzeigen und Ausblenden von Mustersymbolen:

//+------------------------------------------------------------------+
//| 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);
  }

Jetzt sehen diese Methoden einfacher aus:

//+------------------------------------------------------------------+
//| 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);
  }

Als Nächstes werden wir die Musterklassen verfeinern, deren Codes aus der abstrakten Musterdatei übernommen wurden.

Wir öffnen die Datei \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternPinBar.mqh der Klasse für das Muster der „Pin Bars“ und nehmen die Änderungen vor.

Zuvor wurden die Symbole dieses Musters nur als Punkte mit Hilfe von Standard-Grafikobjekten gezeichnet. Nun müssen wir Methoden zum Zeichnen von Punkten auf dem grafischen Bitmap-Objekt hinzufügen.

Fügen wir dem Hauptteil der Klasse die Deklaration der neuen Methoden hinzu:

//+------------------------------------------------------------------+
//|                                                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);
  };

Die virtuellen Methoden CalculatetBitmapWidth() und CalculatetBitmapHeight() geben immer die streng spezifizierten Abmessungen des Bildes 20x40 Pixel zurück, da für dieses Muster, das nur auf einem Balken gezeichnet wird, weder die Höhe noch die Breite des Bildes berechnet werden muss - es muss immer die gleiche Größe haben. Der Ankerpunkt der Bitmap wird in der Mitte des Objekts gesetzt, und die Punkte werden immer in der oberen oder unteren Hälfte der Bitmap auf die Leinwand gezeichnet, je nach Richtung des Musters. Bei einem Aufwärts-Muster wird der Punkt in der unteren Hälfte der Bitmap gezeichnet, bei einem Abwärts-Muster in der oberen Hälfte. Auf diese Weise können die Musterpunkte unabhängig von der vertikalen Skala und der Chartperiode immer im gleichen Abstand zum Docht der Kerzen angezeigt werden, was sehr bequem und praktisch ist.

In der Implementierung der Methode, die das Erscheinungsbild des Info-Panels CreateInfoPanelView() erstellt, entfernen wir die Zeichenketten zum Abrufen der Charteigenschaften:

//--- 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());

Jetzt werden diese Eigenschaften bei der Erstellung eines Objekts voreingestellt oder aktualisiert, wenn die Größe des Charts geändert wird. Daher verwenden wir jetzt die Werte aus den Variablen, die die Breite und Höhe des Charts enthalten:

//--- 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());

Implementieren wir nun die Methoden zum Zeichnen des Mustersymbols.

Die Methode, die das Mustersymbol auf dem Chart zeichnet:

//+------------------------------------------------------------------+
//| 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;
  }

Wenn es noch kein physisches Bitmap-Objekt gibt, erstellen wir es und zeigen es dann im Chart an.

Die Methode, die das Bitmap-Objekt erstellt:

//+------------------------------------------------------------------+
//| 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;
  }

Die Logik der Methode ist im Code kommentiert. Berücksichtigen wir noch bei der Festlegung der Objektkoordinaten die Musterrichtung. Ist das Muster aufwärts gerichtet, ist die Koordinate das Tief des Balkens mit dem Muster, ist es abwärts, ist die Koordinate das Hoch.

Die Methode zur Erstellung für das Aussehen des Bitmap-Objekts:

//+------------------------------------------------------------------+
//| 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);
  }

Leeren wir zunächst den Leinwand mit einer vollständig transparenten Farbe. Dann bestimmen wir die lokalen Koordinaten des zu zeichnenden Punktes. Bei einem Aufwärts-Muster ist die Y-Koordinate die halbe Höhe des Musters plus 6 Pixel (6 Pixel unterhalb der Mitte des Musters). Für das Abwärts-Muster ziehen Sie 6 Pixel von den Koordinaten der Bitmap-Mitte ab. Die X-Koordinate entspricht der halben Breite der Bitmap. Der Radius des gezeichneten Kreises ist auf 2 eingestellt, sodass der Punkt normalerweise auf jedem Chart-Zeitrahmen sichtbar ist.

Nun wollen wir ähnliche Verbesserungen an der Datei \MQL5\Include\DoEasy\Objects\Series\Patterns\PatternInsideBar.mqh mit dem Muster der „Inside Bar“ vornehmen.

Deklarieren wir die virtuelle Methode zum erneuten Zeichnen des Mustersymbols:

//+------------------------------------------------------------------+
//|                                             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);
  };

Die Methode zum Neuzeichnen des Mustersymbols mit neuen Größen implementieren wir außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| 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;
  }

Die Logik der Methode ist im Code kommentiert - hier ist alles sehr einfach: Wir berechnen die neuen Abmessungen der Leinwand, ändern ihre Abmessungen und zeichnen eine neue Bitmap auf der Leinwand entsprechend den neuen Abmessungen.

In der Methode, mit der das Erscheinungsbild des Infofelds erstellt wird, gab es einen Fehler bei der Berechnung der Mustergröße in Balken:

//--- 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();

Diese Berechnung hat ihre Berechtigung, aber nur, wenn benachbarte Balken des Musters nicht durch Wochenenden getrennt sind. Wenn zwischen dem linken und dem rechten Balken des Musters Wochenenden liegen, werden diese zur Anzahl der Balken addiert, und statt 2 erhalten wir den Wert 4.

Lassen Sie uns die Berechnung korrigieren:

//--- 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();

Jetzt erhalten wir immer die richtige Anzahl von Balken zwischen zwei Balken des Musters.

Da die Werte für die Chartbreite und -höhe jetzt im Voraus in Variablen festgelegt werden, entfernen wir den Empfang der Charteigenschaften:

//--- 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());

und passen die Berechnung der Tafelkoordinaten so an, dass voreingestellte Werte aus den Variablen verwendet werden:

//--- 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());

Wir entfernen den Codeblock zum Zeichnen von Punkten in der Methode zum Zeichnen eines Mustersymbols in einem Chart:

//+------------------------------------------------------------------+
//| 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);
  }

Jetzt sieht die Methode einfacher aus:

//+------------------------------------------------------------------+
//| 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;
  }

Alle anderen Verbesserungen werden nach der Erstellung der Klasse des neuen Musters „Outside Bar“ implementiert.


Musterklasse „Outside Bar“

Im Bibliotheksordner \MQL5\Include\DoEasy\Objects\Series\Patterns\ erstellen wir die neue Datei PatternOutsideBar.mqh mit der Klasse CPatternOutsideBar.

Die Klasse sollte von der Basisklasse des Musterobjekts geerbt werden, und ihre Datei sollte in die erstellte Musterklassendatei aufgenommen werden:

//+------------------------------------------------------------------+
//|                                            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
  {
  }

Deklarieren wir die Standardmethoden für Musterobjektklassen:

//+------------------------------------------------------------------+
//| "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);
  };

Alle diese Methoden sind für alle Muster gleich. Ihre Umsetzung unterscheidet sich jedoch leicht von Muster zu Muster. Schauen wir uns die einzelnen Methoden an.

Im Konstruktor der Klasse definieren wir den Namen des Musters, die Anzahl der Kerzen, die in der Form enthalten sind, und die Anzahl der benachbarten, aufeinanderfolgenden Muster, die der Anzahl der Kerzen des Musters entspricht:

//+------------------------------------------------------------------+
//| 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);
  }

Die Methode, die das Erscheinungsbild des Info-Panels erzeugt:

//+------------------------------------------------------------------+
//| 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);
  }

Die Logik der Methode ist im Code kommentiert. Hier wird ein Feld mit einer Verlaufsfüllung gezeichnet, die bei einem Abwärts-Muster rot und bei einem Aufwärts-Muster blau gefärbt ist. Oben wird ein Symbol mit dem Zeichen i gezeichnet und der Name des Musters mit seinen Merkmalen geschrieben - das Verhältnis von zwei Balken des Musters. Der untere Teil beschreibt die Richtung des Musters mit den angegebenen Werten für die Mustersuche und die Zeit des gefundenen Musters.

Die Methode, die das Mustersymbol auf dem Chart zeichnet:

//+------------------------------------------------------------------+
//| 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;
  }

Die Methode, mit der das Mustersymbol auf der Karte mit einer neuen Größe neu gezeichnet wird:

//+------------------------------------------------------------------+
//| 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;
  }

Wir ändern einfach die Größe der Leinwand und zeichnen ein neues Muster-Bitmap in der neuen Größe.

Die Methode, die das Bitmap-Objekt erstellt:

//+------------------------------------------------------------------+
//| 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;
  }

Wenn das grafische Objekt bereits zuvor erstellt wurde, wird die Methode einfach verlassen. Andernfalls erhalten wir den Preis und die Uhrzeit, zu der das grafische Objekt platziert werden soll, berechnen seine Breite und Höhe und erstellen ein neues Objekt. Dann setzen wir den Ankerpunkt in die Mitte des Objekts und zeichnen sein Aussehen darauf. Der Kurs, auf dem der zentrale Punkt des grafischen Objekts stehen wird, wird auf der Grundlage des Mittelpunkts der größten Kerze des Musters berechnet.

Die Methode, die das Aussehen eines Bitmap-Objekts erzeugt:

//+------------------------------------------------------------------+
//| 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);
  }

Hier wird die Bitmap zunächst mit einer vollständig transparenten Farbe gelöscht. Dann wird die lokale X-Koordinate des Beginns des gemalten Bereichs berechnet, abhängig vom Maßstab der Karte. Ein gefülltes Rechteck wird über die gesamte Höhe des grafischen Objekts mit der berechneten anfänglichen X-Koordinate gezeichnet, und darüber wird ein rechteckiger Rahmen mit den gleichen Koordinaten und der gleichen Größe gezeichnet.

Die Methode zur Berechnung der Höhe eines Zeichnungsobjekts:

//+------------------------------------------------------------------+
//| 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;
  }

Hier erhalten wir die Preisspanne des Charts - vom Hoch auf dem Chart bis zum Tief auf dem Chart - und die Preisspanne des Musters - vom Hoch der definierenden Kerze bis zum Tief der definierenden Kerze. Dann berechnen wir das Verhältnis eines Bereichs zu einem anderen in Pixeln und geben die resultierende Höhe des Bitmap-Objekts zurück, indem wir 8 Pixel dazu addieren - vier oben und vier unten.

Die Musterklasse „Outside Bar“ ist fertig. Jetzt müssen wir eine Menge identischer Methoden in den Zeitreihenklassen loswerden, von denen jede die gleiche Aufgabe für ihr eigenes Muster erfüllt. Wir machen nur eine Methode für jede Aktion auf dem Muster, in der wir den Typ des gewünschten Musters angeben.

Wir werden alle erforderlichen Parameter der im Muster enthaltenen Beziehungen der Kerzen mit Hilfe der Struktur MqlParam an die Musterklassen übergeben. Dadurch können wir völlig unterschiedliche Parameter an verschiedene Klassen mit unterschiedlichen Mustern übergeben, und nicht nur solche, die streng durch formale Variablen definiert sind, die für alle Klassen gleich sind.

Wir werden die Namen dieser Variablen, die die verschiedenen Verhältnisse der Musterkerzen angeben, vorerst nicht ändern. Aber wenn nötig, werden wir sie in „gesichtslose“ Variablen umbenennen, wie „param1“, „param2“ usw. In den Klassenkonstruktoren werden wir jeder dieser Variablen gegebenenfalls den erforderlichen Musterparameter zuweisen. Für den Moment werden wir uns mit den bereits benannten Variablen begnügen, die für alle Muster gleich sind.

Ersetzen wir in der Objektklasse der Balken in \MQL5\Include\DoEasy\Objects\Series\Bar.mqh den Methodennamen AddPattern() durch 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)

Dennoch fügt diese Methode dem Balkenobjekt den Mustertyp hinzu und nicht einen Zeiger auf das Muster. Daher ist es logischer, den Namen der Methode in einen korrekteren Namen zu ändern.

Um die gewünschten Muster aus den Listen auswählen und empfangen zu können, binden wir in der Datei \MQL5\Include\DoEasy\Services\Select.mqh der Klasse CSelect alle Musterdateien ein:

//+------------------------------------------------------------------+
//|                                                       Select.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/de/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/de/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"

Dadurch werden Musterklassen in Zeitreihenklassen für die Handhabung von Mustern verfügbar.

Wir müssen Arrays der MqlParam-Strukturen auf Gleichheit vergleichen. Wir schreiben Funktionen zum Vergleich von Strukturfeldern und Strukturarrays in \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;
  }
//+------------------------------------------------------------------+

Die Klassen für die Musterverwaltung befinden sich in der Klassendatei \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh timeseries. Entfernen wir in der abstrakten Musterverwaltungsklasse die nicht mehr benötigten Variablen und fügen neue hinzu:

//+------------------------------------------------------------------+
//| 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

Zuvor wurde bei der Mustersuche die minimale Kerzengröße der Variable für die Suche nach einem Muster zugewiesen:

   virtual ENUM_PATTERN_DIRECTION FindPattern(const datetime series_bar_time,const uint min_body_size,MqlRates &mother_bar_data) const
                                    { return WRONG_VALUE;   }

Diese Größe wird nun über die Variable MqlParam übertragen, sodass diese Variable nun in allen Mustermanagementklassen aus allen Mustersuchmethoden entfernt wird:

//--- (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;                                        }

Entfernen wir aus dem öffentlichen Abschnitt der Klasse die Methoden zum Setzen und Abrufen von Eigenschaften der Punktmusterzeichnung:

//--- (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;                            }

Deklarieren wir das Array der Muster-Parameter, fügen die Methoden für die Behandlung neuer Variablen hinzu, entfernen in der virtuellen Methode CreateAndRefreshPatternList() die Übergabe des Parameters min_body_size, und fügen im parametrischen Konstruktor die Übergabe des Arrays der Pattern-Eigenschaften über das Array der MqlParam-Struktur hinzu. Wir deklarieren eine neue Methode, um alle vorhandenen Muster im Chart neu zu zeichnen:

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[]);
  };

Im parametrischen Konstruktor der Klasse ermittel wir die fehlenden Eigenschaften des Charts und füllen die Musterparameter aus dem an den Konstruktor übergebenen Array aus:

//+------------------------------------------------------------------+
//| 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;
     }
  }

Nehmen wir die erforderlichen Korrekturen in der Methode vor, die nach Mustern sucht und die gefundenen Muster zur Liste aller Muster hinzufügt:

//+------------------------------------------------------------------+
//| 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();
  }

Die Methode hat nun keine formalen Parameter mehr. Bei der Erstellung eines neuen Musters werden die Charteigenschaften sofort übernommen und in das Muster gesetzt. Ganz am Ende des Blocks der Mustersuchschleife fügen wir ein Leerzeichen für die Erzeugung eines neuen Musterereignisses ein. Außerdem werden wir in den folgenden Artikeln Ereignisse über das Auftreten eines neuen Zeitreihenmusters übermitteln.

Die Methode, mit der Muster auf einem Chart mit einer neuen Größe des Bitmap-Objekts neu gezeichnet werden:

//+-------------------------------------------------------------------+
//|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);
  }

Wir gehen einfach in einer Schleife durch die Liste der Zeitreihenmuster und rufen die Redraw-Methoden für jedes Muster auf. Die Methode ist zwar noch nicht optimal, aber das Neuzeichnen erfolgt über die gesamte Liste der Muster. Es ist jedoch möglich, nur den auf dem Chart sichtbaren Teil des Verlaufs neu zu zeichnen. Ich werde später darauf zurückkommen.

Nun wollen wir die Klassen für die Musterverwaltung verfeinern. Sie befinden sich weiter in derselben Datei. Suchen wir alle Vorkommen der Zeichenkette

const uint min_body_size

in den formalen Parametern der Klassenmethoden und löschen sie. Nun wird die Mindestgröße der gewünschten Musterkerze zusammen mit den Musterparametern im Array der Struktur MqlParam übergeben.

Markieren wir einfach alle Stellen, an denen Änderungen vorgenommen wurden, farbig, um hier nicht jeden einzelnen Code-String, der für alle Klassen gleich ist, zu beschreiben.

In der Klasse für die Verwaltung des Musters der „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

In der Musterverwaltungsklasse der „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

Ähnlich wie bei den Verwaltungsklassen der „Pin Bar“ und der „Inside Bar“ werden wir die Klasse für die Verwaltung des Musters „Outside Bar“ implementieren:

//+------------------------------------------------------------------+
//| 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();
                       }
  };

Die notwendigen Bedingungen für die Definition des Musters sind: Der Körper des Balkens auf der rechten Seite muss die Abmessungen des Körpers des Balkens auf der linken Seite vollständig überdecken, und die Schatten der Balken sind entweder gleich groß, oder die Schatten des Balkens auf der rechten Seite können die Schatten des Balkens auf der linken Seite überdecken. Diese Suche wird mit der Methode CheckOutsideBar() durchgeführt. Außerdem muss das Verhältnis zwischen den Größen der im Muster enthaltenen Kerzenkörper und der Gesamtgröße der Kerzen berücksichtigt werden. Diese Prüfung wird mit der Methode CheckProportions() durchgeführt.

Im Konstruktor der Klasse wird der Status des Musters auf „Price Action“ und der Typ des Musters auf „Outside Bar“ festgelegt und alle Kerzenproportionen des Musters aus dem Array der Strukturen, die an den Konstruktor übergeben werden, festgelegt.

Die Methode, die eine Objekt-ID auf der Grundlage von Mustersuchkriterien erstellt:

//+------------------------------------------------------------------+
//| 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);
  }

Zwei Kriterien (prozentuale Verhältnisse der Kerzengrößen und das Verhältnis des Kerzenkörpers zur vollen Kerzengröße) werden in realen Zahlen (in Prozent) angegeben und können 100 nicht überschreiten. Daher wandeln wir sie in Ganzzahlwerte um, indem wir sie mit 100 multiplizieren, und erstellen dann mit der erweiterten Standard-Objektbibliotheksmethode UshortToLong() eine Ullong-ID, indem wir die angegebenen Bits der Long-Nummer mit Ushort-Werten füllen:

//+------------------------------------------------------------------+
//| 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);
  }

Die Methode, die das Muster mit der angegebenen Richtung erstellt:

//+------------------------------------------------------------------+
//| 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;
  }

Die Methode erstellt ein neues Objekt der Musterklasse „Outside Bar“, füllt Daten über seine Proportionen und Suchkriterien aus und gibt den Zeiger auf das erstellte Objekt zurück.

Methode der Mustersuche:

//+------------------------------------------------------------------+
//| 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;
  }

Die Logik der Methode ist in den Kommentaren beschrieben. Die Eröffnungszeit des aktuellen Balkens wird an die Methode übergeben. Sie holt die Liste aller Balken außer dem aktuellen Balken (wir suchen nicht nach Mustern auf dem Null-Balken). In der Schleife, die auf der resultierenden Liste der Balken basiert, werden alle zwei benachbarten Balken daraufhin überprüft, ob sie die Kriterien des Musters erfüllen. Wenn ihr Verhältnis ein Muster ist, bestimmen wir die Richtung des Musters und geben es zurück.

Die Methode gibt eine Liste von Mustern zurück, die von dem Objekt verwaltet werden:

//+------------------------------------------------------------------+
//| 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);
  }

Aus der allgemeinen Liste aller Muster erhalten wir eine Liste, die nach der Chartperiode sortiert ist, und aus der resultierenden Liste erhalten wir eine Liste, die nach dem Symbolnamen sortiert ist. Als Nächstes wird die resultierende Liste nach dem Mustertyp „Outside Bar“ und die resultierende Liste nach der ID des Steuerungsobjekts sortiert. Als Ergebnis liefert die Methode eine Liste mit nur den Mustern, die von dieser Klasse verwaltet werden.

Nun müssen wir die Klasse der Musterverwaltung komplett überarbeiten. Mehr darüber können Sie hier lesen.

Anstelle langer und eintöniger Methoden für die Behandlung der einzelnen Mustertypen werden wir mehrere Methoden für die Behandlung des angegebenen Musters erstellen. Dann werden wir in derselben Datei zahlreiche ähnliche Methoden aus der Mustermanagementklasse entfernen, wie z. B.:

... "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)

Im Allgemeinen gibt es eine Vielzahl solcher Methoden - jedes Muster hat seine eigene Methode. Das Ergebnis: fast 1300 Code-Zeilen. Entfernen wir alle Zeichenketten aus der Musterverwaltungsklasse und schreiben wir die gesamte Klasse neu. Jetzt gibt es mehrere Methoden für die Handhabung von Mustern, mit der Möglichkeit zu wählen, mit welchem Muster man arbeiten möchte.

Die komplett neu geschriebene Klasse mit all ihren Methoden sieht nun wie folgt aus:

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+

Die Logik der Methoden ist im Code kommentiert. Ich möchte einige Klarstellungen bezüglich des hervorgehobenen Codeblocks machen: Wenn man im visuellen Modus des Testers arbeitet, ist OnChartEvent() fast nutzlos. Dort verfolgen wir jedoch die Änderung der Chartgröße in Bezug auf das Ereignis CHARTEVENT_CHART_CHANGE und schreiben die neuen Chartgrößen in das Musterverwaltungsobjekt, von wo aus diese Daten an die Musterobjekte weitergeleitet werden. Aber im Tester funktioniert das nicht. Daher werden Änderungen in der Größe der Karte im speziellen Tester-Codeblock überwacht, und wenn sie sich ändert, werden neue Daten in die Mustersteuerungsobjekte eingegeben.

Weiter im Code in derselben Datei \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh befindet sich die Zeitreihenklasse CSeriesDE. Bringen wir es zu Ende.

Jedes Mal, wenn wir nach einem Balkenobjekt in der Zeitreihenliste suchen, haben wir ein neues Objekt mit Hilfe des Operators new. Seine Parameter werden eingestellt, ein Objekt mit identischen Parametern wird in der Liste gesucht , und dann wird dieses neu erstellte Objekt gelöscht:

//+------------------------------------------------------------------+
//| 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);
  }

So kommt es in manchen Situationen zu einer ständigen Erstellen und Löschen eines Objekts in einer Schleife. Dieses Verhalten ist falsch. Es ist besser, ein einzelnes Instanzobjekt für die Suche zu erstellen und es zum Festlegen von Parametern und als Muster für die Suche zu verwenden. Auf diese Weise werden wir das ständige Neuerstelen von Objekten los.

Deklarieren wir eine Instanz des Balken-Objekts für die Suche:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   CBar              m_bar_tmp;                                         // Bar object for search
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   string            m_symbol;                                          // Symbol

und schreiben die Methode um, die das Balkenobjekt nach Zeit in der Zeitreihe zurückgibt:

//+------------------------------------------------------------------+
//| 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);
  }

Anstatt ein neues Objekt zu erstellen und zu löschen, legen wir die erforderlichen Suchparameter in einer einzigen Instanz fest und suchen nach einem Muster nach einer identischen Instanz in der Liste. Das Objekt wird nur einmal im Klassenkonstruktor erstellt und wird kontinuierlich verwendet, ohne neu erstellt zu werden.

Genau wie in der Musterverwaltungsklasse werden wir die langen Listen von Methoden für die Behandlung jedes Musters entfernen und sie durch einige wenige Methoden ersetzen, die das gewünschte Muster auswählen:

//+------------------------------------------------------------------+
//| 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);
                       }

Um das gewünschte Muster auszuwählen, genügt es nun, seinen Typ in den Methodenparametern anzugeben, anstatt für jedes Muster eine eigene Methode zu verwenden.

Wir ändern in der Klasse der Zeitreihen des Symbols, in D:\MetaQuotes\MT5\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh, die Methoden für die Behandlung von Mustern auf die gleiche Weise.

Im Hauptteil der Klasse, ganz am Ende, unter dem Header

//--- 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                                    |
//+------------------------------------------------------------------+

gibt es eine lange Liste von Methodendeklarationen zur Behandlung von Mustern. Ersetzen wir alle diese Deklarationen durch die Deklaration mehrerer Methoden mit einer Auswahl des gewünschten Musters:

//--- 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);
  };

Außerhalb des Klassenkörpers, ganz am Ende der Datei, unter derselben Kopfzeile, werden die Implementierungen der deklarierten Methoden geschrieben. Streichen wir die gesamte lange Liste und ersetzen sie durch neue Methoden:

//+------------------------------------------------------------------+
//| 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);
  }

Jede Methode erhält den gewünschten Zeitrahmen des Charts, dessen Muster verarbeitet werden sollen, sowie die erforderlichen Parameter zur Auswahl des Mustersteuerungsobjekts. Anschließend wird ein Zeiger auf die gewünschte Zeitreihe ermittelt und das Ergebnis des Aufrufs der gleichnamigen Methode der Klasse der Zeitreihe zurückgegeben.

Nehmen wir einige Verbesserungen in der Datei \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh für die Zeitreihensammlung vor.

In dieser Klasse erhalten wir die Chartgrößen und ihre Änderungen und senden sie an die Klassen der Musterverwaltung. Fügen wir neue Variablen hinzu, um die Chartgrößen zu speichern:

//+------------------------------------------------------------------+
//| 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:

Unter der Überschrift

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+

genau wie in den vorherigen Klassen entfernen wir die deklarierten Methoden und geben neue Methoden mit Angabe des Mustertyps ein:

//--- 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);

Am Ende des Hauptteils der Klasse deklarieren wir die Ereignisbehandlung:

//--- 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);
  };

Im Klassenkonstruktor schreiben wir die Größen des Charts in die neuen Variablen:

//+------------------------------------------------------------------+
//| 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);
  }

Außerhalb des Hauptteils der Klasse, unter dem Kopf

//+------------------------------------------------------------------+
//| Handling timeseries patterns                                     |
//+------------------------------------------------------------------+

entfernen wir alle Methoden für die Behandlung jedes spezifischen Musters. Anstelle der entfernten Methoden legen wir neue Methoden fest, die den Mustertyp angeben:

//+------------------------------------------------------------------+
//| 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);
  }

Jede Methode erhält den gewünschten Zeitrahmen und das Symbol des Charts, dessen Muster verarbeitet werden sollen, sowie die erforderlichen Parameter zur Auswahl des Musterkontrollobjekts. Anschließend wird ein Zeiger auf die benötigte Zeitreihe abgerufen und das Ergebnis des Aufrufs der gleichnamigen Methode der Klasse der Zeitreihe des Symbol zurückgegeben.

Implementieren wir nun die Ereignisbehandlung:

//+------------------------------------------------------------------+
//| 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());
           }
        }  
     }
  }

Die gesamte Logik der Ereignisbehandlung ist in den Kommentaren zum Code ausführlich beschrieben. Wenn hier eine Änderung der Chartgröße festgestellt wird, werden die neuen Größen an die Zeitreihenmuster-Verwaltungsklassen weitergegeben und die Muster entsprechend der neuen Chartgröße neu gezeichnet.

Ändern wir nun die Hauptklasse der CEngine-Bibliothek in \MQL5\Include\DoEasy\Engine.mqh.

Auch hier gibt es im Klassenkörper eine lange Liste von Methoden zur Behandlung von Mustern. Wir wollen nur die Methoden entfernen, die für das Zeichnen von Mustern in Form von Punkten zuständig sind. Die verbleibenden Methoden müssen verbessert werden - jetzt ist es notwendig, Musterparameter an die Methoden zu senden, die sie mit Hilfe des Strukturarrays MqlParam verarbeiten. Bei allen Methoden, die noch nicht verwendet werden, füllen wir nur ein Strukturfeld, in dem z.B. die Mindestgröße der Musterkerzen gesendet wird:

//--- 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);
                          }

Die übrigen Methoden für die noch nicht verwendeten Muster sind identisch und müssen nicht berücksichtigt werden - Sie finden sie in den Dateien im Anhang des Artikels.

Betrachten wir die Methoden zur Behandlung der bereits in der Bibliothek erstellten Muster.

Die Methode, die das Flag für die Verwendung des Musters Externer Balken (Engulfing) setzt und ein Kontrollobjekt erstellt, falls dieses noch nicht existiert:

//--- Set the flag for using the Pattern Outside and create a control object if it does not already exist
   void                 SeriesSetUsedPatternOutsideBar(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                       const bool   flag,                       // Price Action External Bar usage flag
                                                       const double ratio_candles=70,           // Percentage ratio of the size of the engulfing color to the size of the engulfed one
                                                       const double ratio_body_to_shadows=80,   // Percentage ratio of shadow sizes to candle body size
                                                       const uint   min_body_size=3)            // Minimum candle body size
                          {
                           MqlParam param[]={};
                           if(::ArrayResize(param,3)==3)
                             {
                              param[0].type=TYPE_UINT;
                              param[0].integer_value=min_body_size;
                              //--- Percentage ratio of the size of the engulfing color to the size of the engulfed one
                              param[1].type=TYPE_DOUBLE;
                              param[1].double_value=ratio_candles;
                              //--- Percentage ratio of shadow sizes to candle body size
                              param[2].type=TYPE_DOUBLE;
                              param[2].double_value=ratio_body_to_shadows;
                             }
                           this.m_time_series.SetUsedPattern(PATTERN_TYPE_OUTSIDE_BAR,param,CorrectSymbol(symbol),CorrectTimeframe(timeframe),flag);
                          }

Die Methode setzt das Flag für die Verwendung des Inside Bar-Musters und die Erstellung das Kontrollobjekt, falls es noch nicht existiert:

//--- 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);
                          }

Bei dieser Methode wird nur ein Feld der Struktur ausgefüllt, da das Muster keine anpassbaren Eigenschaften hat, mit Ausnahme der Eigenschaft der minimalen Kerzengröße, die allen Mustern gemeinsam ist.

Die Methode setzt das Flag für die Verwendung des Pin Bar-Musters und die Erstellung das Kontrollobjekt, wenn es noch nicht existiert:

//--- 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);
                          }

Betrachten wir nun die Methoden zur Darstellung von Mustern in einem Chart:

  • für bereits implementierte Muster in der Bibliothek
  • für ein Muster, das noch nicht erstellt worden ist:

//--- 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);
                          }

Die übrigen Methoden für andere Muster sind identisch mit der Methode für das noch nicht implementierte Rails-Muster, und es ist nicht sinnvoll, sie hier zu betrachten.

Ganz am Ende des Hauptteil der Klasse deklarieren wir die Ereignisbehandlung:

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);

  };

Nun schreiben wir die Implementierung außerhalb des Hauptteils der Klasse:

//+------------------------------------------------------------------+
//| 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);
  }

Hier wird zuerst die Ereignisbehandlung der Klasse der grafischen Objektsammlung aufgerufen, gefolgt von der Ereignisbehandlung der Zeitreihensammlung.

In der Ereignisbehandlung der Bibliothek CEngine::OnDoEasyEvent(), d. h. im Block für die Behandlung von Zeitreihenereignissen, fügen wir den Block für die künftige Behandlung von Ereignissen mit neuen Mustern hinzu:

//--- 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
        }
     }

Noch steht hier nichts, aber wenn wir in Zukunft Musterereignisse senden, werden wir das Auftreten eines neuen Musters hier behandeln.

Damit ist die Änderung der Bibliothek abgeschlossen. Überprüfen wir nun die Suche nach neuen Mustern und den Umgang mit Änderungen in der horizontalen Skala des Charts.


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn im neuen Ordner \MQL5\Experts\TestDoEasy\Part136\ als TestDoEasy136.mq5.

Wir entfernen das Flag für das Zeichnen von Mustern mit Punkten aus den Einstellungen:

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

Die Flags für die Verwendung von Mustern fügen wir zu den Einstellungen hinzu, wie auch die Parametern für die Suche nach dem Muster „Outside Bar“:

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

Da die Methoden zum Setzen der Verwendung von Mustern sofort nach den gefundenen Mustern suchen und diese anzeigen, wenn das Flag gesetzt ist, reicht es für den Test aus, die Flags in OnInit() zu setzen, um sofort mit der Suche nach Mustern und deren Anzeige im Chart zu beginnen:

//--- 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);
  }

In dem EA OnChartEvent() fügen wir den Aufruf dieser Ereignisbehandlung für das Hauptobjekt der Engine-Bibliothek hinzu:

//+------------------------------------------------------------------+
//| 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
      //... ... ...

In der Ereignisbehandlung der Bibliothek - die Funktion 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

im Block für die Behandlung von Zeitreihenereignissen fügen wir eine Meldung über verpasste Balken hinzu, wenn ein solches Ereignis eintritt:

//--- 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

Im Moment bietet diese Funktion keine technischen Vorteile - sie zeigt lediglich die Anzahl der fehlenden Balken im Journal an. Aber es ist klar, dass die Bibliothek korrekt mit Balkensprüngen umgeht, wenn z. B. die Verbindung getrennt wurde, während der EA im Terminal lief, oder der Computer in den Ruhezustand versetzt und dann wieder aus diesem herausgeholt wurde. Das bedeutet, dass beim Eintreffen eines solchen Ereignisses die erforderliche Anzahl von Balken aktualisiert werden kann, um die fehlenden Zeitreihen und Musterdaten wiederherzustellen. Dies wird in Zukunft umgesetzt werden.

Kompilieren wir den EA und starten ihn, indem wir die folgenden Werte für die Suche nach dem Outside Bar-Muster einstellen:

Wir setzen bewusst so kleine Werte für die Kerzenanteile, damit möglichst viele Muster gefunden werden.
Bei normalen Werten von Kerzenanteilen (50 % oder mehr) sind die Muster korrekter, aber recht selten.

Nach dem Start werden die Muster „Outside Bar“ gefunden und angezeigt:

Wir sehen, dass die Muster gefunden werden. Wenn Sie die Größe des Charts ändern, ändern sich auch die Größen der Mustersymbole.


Was kommt als Nächstes?

Im nächsten Artikel über Kursformationen werden wir damit fortfahren, verschiedene Muster zu erstellen und Ereignisse zu senden, wenn sie auftreten.

Alle erstellten Dateien sind dem Artikel beigefügt und können zum Selbststudium und für Tests heruntergeladen werden.

Zurück zum Inhalt

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/14710

Beigefügte Dateien |
MQL5.zip (4958.58 KB)
Entwicklung eines Replay-Systems (Teil 68): Das richtige Bestimmen der Zeit (I) Entwicklung eines Replay-Systems (Teil 68): Das richtige Bestimmen der Zeit (I)
Heute werden wir weiter daran arbeiten, dass der Mauszeiger uns anzeigt, wie viel Zeit in Zeiten geringer Liquidität noch auf einem Balken verbleibt. Obwohl es auf den ersten Blick einfach erscheint, ist diese Aufgabe in Wirklichkeit viel schwieriger. Dabei gibt es einige Hindernisse, die wir überwinden müssen. Daher ist es wichtig, dass Sie den ersten Teil dieser Teilserie gut verstehen, damit Sie die folgenden Teile verstehen können.
Entwicklung eines Replay-Systems (Teil 67): Verfeinerung des Kontrollindikators Entwicklung eines Replay-Systems (Teil 67): Verfeinerung des Kontrollindikators
In diesem Artikel werden wir uns ansehen, was mit ein wenig Code-Verfeinerung erreicht werden kann. Diese Verfeinerung zielt darauf ab, unseren Code zu vereinfachen, mehr Gebrauch von MQL5-Bibliotheksaufrufen zu machen und ihn vor allem viel stabiler, sicherer und einfacher in anderen Projekten zu verwenden, die wir in Zukunft entwickeln werden.
Von der Grundstufe bis zur Mittelstufe: Arrays und Zeichenketten (III) Von der Grundstufe bis zur Mittelstufe: Arrays und Zeichenketten (III)
Dieser Artikel behandelt zwei Aspekte. Erstens, wie die Standardbibliothek binäre Werte in andere Darstellungen wie oktal, dezimal und hexadezimal konvertieren kann. Zweitens werden wir darüber sprechen, wie wir die Breite unseres Passworts auf der Grundlage der geheimen Phrase bestimmen können, indem wir das bereits erworbene Wissen nutzen.
Vorhersage von Wechselkursen mit klassischen Methoden des maschinellen Lernens: Logit- und Probit-Modelle Vorhersage von Wechselkursen mit klassischen Methoden des maschinellen Lernens: Logit- und Probit-Modelle
In diesem Artikel wird der Versuch unternommen, einen Handels-EA zur Vorhersage von Wechselkursen zu erstellen. Der Algorithmus basiert auf klassischen Klassifikationsmodellen - logistische und Probit-Regression. Das Kriterium des Wahrscheinlichkeitsquotienten wird als Filter für Handelssignale verwendet.
OSZAR »