Zeitreihen in der Bibliothek DoEasy (Teil 56): Nutzerdefiniertes Indikatorobjekt, das die Daten von Indikatorobjekten aus der Kollektion holt

Artyom Trishkin | 11 Januar, 2021

Inhaltsverzeichnis


Konzept

Fahren wir mit dem Erstellen von Funktionen zur Handhabung von Indikatoren in EAs fort, die auf Basis von Bibliotheksdaten erstellt wurden.
Wir haben bereits Standard-Indikator-Objektklassen erstellt und damit begonnen, sie in die Liste der Kollektionen aufzunehmen. Um das komplette "Set" zu haben, fehlt nur noch ein nutzerdefiniertes Indikatorobjekt. Heute werden wir ein solches Objekt erstellen. Das Konzept seiner Konstruktion wird sich von dem Konzept der Erstellung von Standard-Indikatorobjekten unterscheiden.
Da wir den ultimativen Satz von Standard-Indikatoren im Terminal haben, kann ich Standard-Indikator-Objekte auf der Grundlage von definitiv und im Voraus bekannten Daten für jeden der Indikatoren erstellen. Das tue ich, indem ich eine fest vorgegebene Parameterkombination festlege, die dem erstellten Indikator entspricht. Währenddessen kann der nutzerdefinierte Indikator einen beliebigen Parametersatz haben, der vorher nicht bekannt sein kann.
Aus diesem Grund wird sich das Konzept der Erstellung des nutzerdefinierten Indikatorobjekts von der Erstellung des Standardindikatorobjekts unterscheiden.

Wenn es für die Erstellung des Standard-Indikatorobjekts ausreicht, eine eigene Erstellungsmethode für jedes von ihnen zu erstellen, die alle notwendigen Eigenschaften in ihren Eingängen enthält, müssen wir zur gleichen Zeit, um den nutzerdefinierten Indikator mit einer vorläufig unbekannten Anzahl und Art von Parametern zu erstellen, das vorläufig ausgefüllte Array von Indikator-Eingangsstrukturen an seine Erstellungsmethode übergeben. Alle Parameter und Eigenschaften, die zur Erstellung des Indikators notwendig sind, werden von der Methode übernommen. Daher muss der Nutzer der Bibliothek leider selbständig ein solches Array von Eingabestrukturen ausfüllen, um einen nutzerdefinierten Indikator im Programm zu erstellen.

Da ich versuche, eine Bibliothek zu erstellen, um das Erstellen von Programmen zu vereinfachen, habe ich aus mehreren Varianten die folgende Variante des Aufrufs der erstellten Indikatoren in meinem Programm gewählt: Jeder erstellte Indikator wird mit einer eigenen eindeutigen ID gekennzeichnet sein. Ich werde in der Lage sein, jeden der erstellten Indikatoren durch diese ID aufzurufen. Geschickterweise erstelle ich sie, setze die IDs für sie und ich weiß genau, welche der gesetzten IDs dem einen oder anderen im Programm erstellten Indikator entspricht.

Außer der ID wird es Methoden des Indikators geben, die durch alle seine Parameter aufgerufen werden. D.h. es wird möglich sein, ein Indikatorobjekt zu erhalten, indem man dieselben Parameter angibt, mit denen dieses Indikatorobjekt erstellt wurde, und dann das erhaltene Objekt zu bearbeiten - Daten aus dem Indikator zu erhalten (Methoden für den Datenempfang werden heute erstellt) und sie in Arrays für statistische Untersuchungen zu kopieren (das wird in den folgenden Artikeln sein).


Verbesserung der Bibliothek der Klasse

Wie üblich geben wir zunächst in der Datei \MQL5\Include\DoEasy\Data.mqh eine neue Bibliotheksmeldung ein.
Hinzufügen der Indizes der neuen Nachrichten:

   MSG_LIB_TEXT_IND_TEXT_GROUP,                       // Indicator group
   MSG_LIB_TEXT_IND_TEXT_GROUP_TREND,                 // Trend indicator
   MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR,            // Oscillator
   MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES,               // Volumes
   MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS,                // Arrow indicator
   MSG_LIB_TEXT_IND_TEXT_ID,                          // Indicator ID
   
   MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE,                 // Empty value for plotting where nothing will be drawn
   MSG_LIB_TEXT_IND_TEXT_SYMBOL,                      // Indicator symbol
   MSG_LIB_TEXT_IND_TEXT_NAME,                        // Indicator name
   MSG_LIB_TEXT_IND_TEXT_SHORTNAME,                   // Indicator short name
   
   MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS,              // Indicator parameters
   MSG_LIB_TEXT_IND_TEXT_APPLIED_VOLUME,              // Volume type for calculation
   MSG_LIB_TEXT_IND_TEXT_PERIOD,                      // Averaging period
   MSG_LIB_TEXT_IND_TEXT_FAST_PERIOD,                 // Fast MA period
   MSG_LIB_TEXT_IND_TEXT_SLOW_PERIOD,                 // Slow MA period
   MSG_LIB_TEXT_IND_TEXT_SIGNAL,                      // Difference averaging period
   MSG_LIB_TEXT_IND_TEXT_TENKAN_PERIOD,               // Tenkan-sen period
   MSG_LIB_TEXT_IND_TEXT_KIJUN_PERIOD,                // Kijun-sen period
   MSG_LIB_TEXT_IND_TEXT_SPANB_PERIOD,                // Senkou Span B period
   MSG_LIB_TEXT_IND_TEXT_JAW_PERIOD,                  // Period for jaw line calculation
   MSG_LIB_TEXT_IND_TEXT_TEETH_PERIOD,                // Period for teeth line calculation
   MSG_LIB_TEXT_IND_TEXT_LIPS_PERIOD,                 // Period for lips line calculation
   MSG_LIB_TEXT_IND_TEXT_JAW_SHIFT,                   // Horizontal shift of jaws line
   MSG_LIB_TEXT_IND_TEXT_TEETH_SHIFT,                 // Horizontal shift of teeth line
   MSG_LIB_TEXT_IND_TEXT_LIPS_SHIFT,                  // Horizontal shift of lips line
   MSG_LIB_TEXT_IND_TEXT_SHIFT,                       // Horizontal shift of the indicator
   MSG_LIB_TEXT_IND_TEXT_MA_METHOD,                   // Smoothing type
   MSG_LIB_TEXT_IND_TEXT_APPLIED_PRICE,               // Price type or handle
   MSG_LIB_TEXT_IND_TEXT_STD_DEVIATION,               // Number of standard deviations
   MSG_LIB_TEXT_IND_TEXT_DEVIATION,                   // Deviation of channel borders from the central line
   MSG_LIB_TEXT_IND_TEXT_STEP,                        // Price change step — acceleration factor
   MSG_LIB_TEXT_IND_TEXT_MAXIMUM,                     // Maximum step
   MSG_LIB_TEXT_IND_TEXT_KPERIOD,                     // K-period (number of bars for calculation)
   MSG_LIB_TEXT_IND_TEXT_DPERIOD,                     // D-period (primary smoothing period)
   MSG_LIB_TEXT_IND_TEXT_SLOWING,                     // Final smoothing
   MSG_LIB_TEXT_IND_TEXT_PRICE_FIELD,                 // Stochastic calculation method
   MSG_LIB_TEXT_IND_TEXT_CMO_PERIOD,                  // Chande Momentum period
   MSG_LIB_TEXT_IND_TEXT_SMOOTHING_PERIOD,            // Smoothing factor period
   MSG_LIB_TEXT_IND_TEXT_CUSTOM_PARAM,                // Input parameter
   
//--- CIndicatorsCollection
   MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST,                // Error. Failed to add indicator object to the list
   MSG_LIB_SYS_INVALID_IND_POINTER,                   // Error. Invalid pointer to indicator object is passed
   MSG_LIB_SYS_IND_ID_EXIST,                          // Error. Indicator object with ID already exists
   
  };
//+------------------------------------------------------------------+

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

   {"Arrow indicator"},
   {"Indicator ID"},
   {"Empty value for plotting, for which there is no drawing"},
   {"Indicator symbol"},
   {"Indicator name"},
   {"Indicator shortname"},
   
   {"Indicator parameters"},
   {"Volume type for calculation"},
   {"Averaging period"},
   {"Fast MA period"},
   {"Slow MA period"},
   {"Averaging period for their difference"},
   {"Tenkan-sen period"},
   {"Kijun-sen period"},
   {"Senkou Span B period"},
   {"Period for the calculation of jaws"},
   {"Period for the calculation of teeth"},
   {"Period for the calculation of lips"},
   {"Horizontal shift of jaws"},
   {"Horizontal shift of teeth"},
   {"Horizontal shift of lips"},
   {"Horizontal shift of the indicator"},
   {"Smoothing type"},
   {"Price type or handle"},
   {"Number of standard deviations"},
   {"Deviation of boundaries from the midline"},
   {"Price increment step - acceleration factor"},
   {"Maximum value of step"},
   {,"K-period (number of bars for calculations)"},
   {"D-period (period of first smoothing)"},
   {"Final smoothing"},
   {"Stochastic calculation method"},
   {"Chande Momentum period"},
   {"Smoothing factor period"},
   {"Input parameter"},
   
   {"Error. Failed to add indicator object to list"},
   {"Error. Invalid pointer to indicator object passed"},
   {"Error. There is already exist an indicator object with ID"},
   
  };
//+---------------------------------------------------------------------+


Beim Erstellen eines Standard-Indikator-Objekts, setzen wir sofort in seinem Konstruktor die Gruppe, die dem Indikator-Typ entspricht: Trend, Pfeil, Oszillator oder Volumen-Indikator. Da der Typ des erstellten Indikators im Voraus bekannt ist, kann ich das für Standardindikatoren tun. Für den nutzerdefinierten Indikator muss man seinen Typ (die Gruppe, zu der der Indikator gehört) nicht im Voraus kennen und kann ihn im Konstruktor der Objektklasse einstellen. Deshalb werde ich eine weitere Indikatorgruppe "any" erstellen, die automatisch für ein neu erstelltes Objekt gesetzt wird. Dem Kunden wird eine Funktion zur Verfügung gestellt, mit der er die nutzerdefinierte Indikator-Zugehörigkeitsgruppe einstellen kann, nachdem sein Objekt erstellt wurde.

In der Datei \MQL5\Include\DoEasy\Defines.mqh in der Enumeration der Indikatorgruppen wird eine neue Konstante eingefügt:

//+------------------------------------------------------------------+
//| Indicator group                                                  |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_GROUP
  {
   INDICATOR_GROUP_TREND,                                   // Trend indicator
   INDICATOR_GROUP_OSCILLATOR,                              // Oscillator
   INDICATOR_GROUP_VOLUMES,                                 // Volumes
   INDICATOR_GROUP_ARROWS,                                  // Arrow indicator
   INDICATOR_GROUP_ANY,                                     // Any indicator
  };
//+------------------------------------------------------------------+

Da wir uns entschieden haben, die erstellte Indikatoren über eindeutige IDs aufzurufen, sollten wir eine neue Eigenschaft zur Enumeration der Indikator-Ganzzahl-Eigenschaften hinzufügen:

//+------------------------------------------------------------------+
//| Indicator integer properties                                     |
//+------------------------------------------------------------------+
enum ENUM_INDICATOR_PROP_INTEGER
  {
   INDICATOR_PROP_STATUS = 0,                               // Indicator status (from enumeration ENUM_INDICATOR_STATUS)
   INDICATOR_PROP_TYPE,                                     // Indicator type (from enumeration ENUM_INDICATOR)
   INDICATOR_PROP_TIMEFRAME,                                // Indicator timeframe
   INDICATOR_PROP_HANDLE,                                   // Indicator handle
   INDICATOR_PROP_GROUP,                                    // Indicator group
   INDICATOR_PROP_ID,                                       // Indicator ID
  }; 
#define INDICATOR_PROP_INTEGER_TOTAL (6)                    // Total number of indicator integer properties
#define INDICATOR_PROP_INTEGER_SKIP  (0)                    // Number of indicator properties not used in sorting
//+------------------------------------------------------------------+

und wir erhöhen dem entsprechend die Anzahl der Indikator-Ganzzahl-Eigenschaften von 5 auf 6.

Um einen Indikator in der Liste nach seiner ID suchen zu können fügen wir ein neues Sortierkriterium für Indikatoren nach IDs hinzu:

//+------------------------------------------------------------------+
//| Possible indicator sorting criteria                              |
//+------------------------------------------------------------------+
#define FIRST_INDICATOR_DBL_PROP          (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP)
#define FIRST_INDICATOR_STR_PROP          (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP+INDICATOR_PROP_DOUBLE_TOTAL-INDICATOR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_INDICATOR_MODE
  {
//--- Sort by integer properties
   SORT_BY_INDICATOR_INDEX_STATUS = 0,                      // Sort by indicator status
   SORT_BY_INDICATOR_TYPE,                                  // Sort by indicator type
   SORT_BY_INDICATOR_TIMEFRAME,                             // Sort by indicator timeframe
   SORT_BY_INDICATOR_HANDLE,                                // Sort by indicator handle
   SORT_BY_INDICATOR_GROUP,                                 // Sort by indicator group
   SORT_BY_INDICATOR_ID,                                    // Sort by indicator ID
//--- Sort by real properties
   SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,// Sort by the empty value for plotting where nothing will be drawn
//--- Sort by string properties
   SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP,     // Sort by indicator symbol
   SORT_BY_INDICATOR_NAME,                                  // Sort by indicator name
   SORT_BY_INDICATOR_SHORTNAME,                             // Sort by indicator short name
  };
//+------------------------------------------------------------------+


Da die neuen Eigenschaften für das Indikator-Objekt hinzugefügt wurden, verbessern wir nun die abstrakte Indikator-Objektklasse in \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh etwas.

Schreiben wir im 'public' Teil der Klasse zwei Methoden zum Setzen und Abfragen der Eigenschaft "Indikator-ID":

//--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name, (5) indicator ID
   void              SetGroup(const ENUM_INDICATOR_GROUP group)      { this.SetProperty(INDICATOR_PROP_GROUP,group);                         }
   void              SetEmptyValue(const double value)               { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value);                   }
   void              SetName(const string name)                      { this.SetProperty(INDICATOR_PROP_NAME,name);                           }
   void              SetShortName(const string shortname)            { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname);                 }
   void              SetID(const int id)                             { this.SetProperty(INDICATOR_PROP_ID,id);                               }
   
//--- Return indicator’s (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID, 
//--- (7) empty value of buffers, (8) name, (9) short name, (10) symbol
   ENUM_INDICATOR_STATUS Status(void)                          const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);}
   ENUM_INDICATOR_GROUP  Group(void)                           const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP);  }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME);   }
   ENUM_INDICATOR    TypeIndicator(void)                       const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE);         }
   int               Handle(void)                              const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE);                  }
   int               ID(void)                                  const { return (int)this.GetProperty(INDICATOR_PROP_ID);                      }
   double            EmptyValue(void)                          const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE);                  }
   string            Name(void)                                const { return this.GetProperty(INDICATOR_PROP_NAME);                         }
   string            ShortName(void)                           const { return this.GetProperty(INDICATOR_PROP_SHORTNAME);                    }
   string            Symbol(void)                              const { return this.GetProperty(INDICATOR_PROP_SYMBOL);                       }

In Standard-Indikatorobjekten könnten wir Methoden zur Anzeige der Beschreibung jedes Indikatorparameters hinzufügen, da wir definitiv wissen, welcher Parameter eines bestimmten Indikators beschrieben wird. Um die Beschreibung von nutzerdefinierten Indikatorparametern anzuzeigen, können wir den Zweck jedes Parameters eines Indikators, der vorher nicht bekannt ist, nicht definitiv kennen. Daher zeigen wir einfach die Beschreibungen jedes nachfolgenden Parameters aus dem Parameter-Array des MqlParam-Indikators an.
Im 'public' Abschnitt der abstrakten Indikator-Objektklasse deklarieren wir eine Methode zum Anzeigen der Beschreibung der Parameter der MqlParam-Struktur und zwei weitere Methoden: zum Abrufen von Daten aus dem Indikator-Objekt nach Index und Zeit des Balkens:

//--- Return description of (1) type, (2) status, (3) group, (4) timeframe, (5) empty value of indicator, (6) parameter of m_mql_param array
   string            GetTypeDescription(void)                  const { return m_ind_type_description;                                        }
   string            GetStatusDescription(void)                const;
   string            GetGroupDescription(void)                 const;
   string            GetTimeframeDescription(void)             const;
   string            GetEmptyValueDescription(void)            const;
   string            GetMqlParamDescription(const int index)   const;
   
//--- Display the description of indicator object properties in the journal (full_prop=true - all properties, false - supported ones only)
   void              Print(const bool full_prop=false);
//--- Display (1) a short description, (2) description of indicator object parameters in the journal (implementation in the descendants)
   virtual void      PrintShort(void) {;}
   virtual void      PrintParameters(void) {;}

//--- Return data of specified buffer from specified bar (1) by index, (2) by bar time
   double            GetDataBuffer(const int buffer_num,const int index);
   double            GetDataBuffer(const int buffer_num,const datetime time);
  };
//+------------------------------------------------------------------+

Implementieren wir diese Methoden außerhalb des Klassenkörpers:

Hier die Methode, die die Beschreibung der Parameter von MqlParam-Strukturen Array zurückgibt:

//+------------------------------------------------------------------+
//| Return the description of parameter of m_mql_param array         |
//+------------------------------------------------------------------+
string CIndicatorDE::GetMqlParamDescription(const int index) const
  {
   return "["+(string)index+"] "+MqlParameterDescription(this.m_mql_param[index]);
  }
//+------------------------------------------------------------------+

Der Methode, übergeben wir den Parameter-Index als Array und erstellen dann eine Zeichenkette aus Array-Index und der Parameter-Beschreibung in Übereinstimmung mit den Daten in der Struktur durch den angegebenen Array-Index gespeichert. Im Folgenden werde ich die Funktion MqlParameterDescription() schreiben, um die Beschreibung der Daten der MqlParam-Struktur zurückzugeben.

Methoden, die Indikatordaten nach Index und Taktzeit zurückgeben:

//+------------------------------------------------------------------+
//| Return data of specified buffer from specified bar by index      |
//+------------------------------------------------------------------+
double CIndicatorDE::GetDataBuffer(const int buffer_num,const int index)
  {
   double array[1]={EMPTY_VALUE};
   int copied=::CopyBuffer(this.Handle(),buffer_num,index,1,array);
   return(copied==1 ? array[0] : this.EmptyValue());
  }
//+------------------------------------------------------------------+
//| Return data of specified buffer from specified bar by time       |
//+------------------------------------------------------------------+
double CIndicatorDE::GetDataBuffer(const int buffer_num,const datetime time)
  {
   double array[1]={EMPTY_VALUE};
   int copied=::CopyBuffer(this.Handle(),buffer_num,time,1,array);
   return(copied==1 ? array[0] : this.EmptyValue());
  }
//+------------------------------------------------------------------+

Die Methode erhält Index oder Taktzeit, welche Daten vom Indikator empfangen werden müssen. Mit der Funktion CopyBuffer() wird ein Balken per Index oder Zeit abgefragt und das erhaltene Ergebnis zurückgegeben, geschrieben in das Array. Wenn aus irgendeinem Grund keine Daten empfangen werden konnten, geben die Methoden 'leeren' Werte für das Indikatorobjekt zurück.

Setzen wir im Klassenkonstruktor eine Standard-Indikator-ID (-1):

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CIndicatorDE::CIndicatorDE(ENUM_INDICATOR ind_type,
                           string symbol,
                           ENUM_TIMEFRAMES timeframe,
                           ENUM_INDICATOR_STATUS status,
                           ENUM_INDICATOR_GROUP group,
                           string name,
                           string shortname,
                           MqlParam &mql_params[])
  {
//--- Set collection ID for the object
   this.m_type=COLLECTION_INDICATORS_ID;
//--- Write description of indicator type
   this.m_ind_type_description=IndicatorTypeDescription(ind_type);
//--- If parameter array size passed to constructor is more than zero
//--- fill in the array of object parameters with data from the array passed to constructor
   int count=::ArrayResize(this.m_mql_param,::ArraySize(mql_params));
   for(int i=0;i<count;i++)
     {
      this.m_mql_param[i].type         = mql_params[i].type;
      this.m_mql_param[i].double_value = mql_params[i].double_value;
      this.m_mql_param[i].integer_value= mql_params[i].integer_value;
      this.m_mql_param[i].string_value = mql_params[i].string_value;
     }
//--- Create indicator handle
   int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,this.m_mql_param);
   
//--- Save integer properties
   this.m_long_prop[INDICATOR_PROP_STATUS]                     = status;
   this.m_long_prop[INDICATOR_PROP_TYPE]                       = ind_type;
   this.m_long_prop[INDICATOR_PROP_GROUP]                      = group;
   this.m_long_prop[INDICATOR_PROP_TIMEFRAME]                  = timeframe;
   this.m_long_prop[INDICATOR_PROP_HANDLE]                     = handle;
   this.m_long_prop[INDICATOR_PROP_ID]                         = WRONG_VALUE;
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE; 
//--- Save string properties
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SYMBOL)]   = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_NAME)]     = name;
   this.m_string_prop[this.IndexProp(INDICATOR_PROP_SHORTNAME)]= shortname;
  }
//+------------------------------------------------------------------+

Da jedes Indikator-Handle eindeutig ist, ebenso wie seine ID, die wir nach eigenem Ermessen festlegen, müssen beim Vergleich der beiden Indikatoren für die Identität durch ihre Parameter die beiden oben genannten Parameter übersprungen werden. Andernfalls wird jeder Indikator als eindeutig identifiziert und wir können die Parameter der beiden Indikatoren für die Identität nicht korrekt vergleichen.
Um dies zu vermeiden, überspringen wir die Eigenschaften "Handle" und "ID" in der Methode des Vergleichs der beiden Indikatorobjekte:

//+------------------------------------------------------------------+
//| Compare CIndicatorDE objects with each other by all properties   |
//+------------------------------------------------------------------+
bool CIndicatorDE::IsEqual(CIndicatorDE *compared_obj) const
  {
   if(!IsEqualMqlParamArrays(compared_obj.m_mql_param))
      return false;
   int beg=0, end=INDICATOR_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_INTEGER prop=(ENUM_INDICATOR_PROP_INTEGER)i;
      if(prop==INDICATOR_PROP_HANDLE || prop==INDICATOR_PROP_ID) continue;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=INDICATOR_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_DOUBLE prop=(ENUM_INDICATOR_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   beg=end; end+=INDICATOR_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_INDICATOR_PROP_STRING prop=(ENUM_INDICATOR_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

Hinzufügen der Anzeige der Beschreibung der Indikatoreigenschaft "ID" in der Methode, die die Beschreibung der Integer-Eigenschaft des Indikators zurückgibt:

//+------------------------------------------------------------------+
//| Return description of indicator's integer property               |
//+------------------------------------------------------------------+
string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return
     (
      property==INDICATOR_PROP_STATUS     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==INDICATOR_PROP_TYPE       ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeDescription()
         )  :
      property==INDICATOR_PROP_GROUP      ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetGroupDescription()
         )  :
      property==INDICATOR_PROP_ID         ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==INDICATOR_PROP_TIMEFRAME  ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==INDICATOR_PROP_HANDLE     ?  CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Fügen wir nun in der Service-Funktionsdatei \MQL5\Include\DoEasy\Services\DELib.mqh eine Funktion zur Anzeige der MqlParam-Strukturbeschreibung hinzu:

//+------------------------------------------------------------------+
//| Return the description of parameter MqlParam array               |
//+------------------------------------------------------------------+
string MqlParameterDescription(const MqlParam &mql_param)
  {
   int type=mql_param.type;
   string res=CMessage::Text(MSG_ORD_TYPE)+" "+typename(type)+": ";
   //--- type of parameter string
   if(type==TYPE_STRING)
      res+=mql_param.string_value;
   //--- type of parameter datetime
   else if(type==TYPE_DATETIME)
      res+=TimeToString(mql_param.integer_value,TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   //--- type of parameter color
   else if(type==TYPE_COLOR)
      res+=ColorToString((color)mql_param.integer_value,true);
   //--- type of parameter bool
   else if(type==TYPE_BOOL)
      res+=(string)(bool)mql_param.integer_value;
   //--- integer types
   else if(type>TYPE_BOOL && type<TYPE_FLOAT)
      res+=(string)mql_param.integer_value;
   //--- real types
   else
      res+=DoubleToString(mql_param.double_value,8);
   return res;
  }
//+------------------------------------------------------------------+

Die Struktur MqlParam enthält mehrere Felder. Eines davon enthält den Datentyp, der in der Struktur gespeichert ist. Anhand dieses Datentyps kann ich erkennen, aus welchem Feld der Struktur die Daten empfangen werden und welche Funktion der Datenstring-Darstellung für die Datenanzeige im Journal verwendet werden soll.

Datentyp holen und mit der Bildung der Zeichenkette beginnen, bestehend aus "Type" +" "+ Datentyp als Zeichenkette + ": "".
Und weiter wird, je nach Datentyp, zum bereits erstellten String die Beschreibung des Wertes, der im Strukturfeld gespeichert ist, zum entsprechenden Typ hinzugefügt, unter Verwendung von Standardfunktionen für die Darstellung des Strings des gewünschten Datentyps.

Am Ende wird die Parameterbeschreibung des nutzerdefinierten Indikators mit vier Parametern bei Verwendung der Methode der Anzeige von MqlParam-Strukturparametern der abstrakten Indikatorobjektklasse und der oben analysierten Servicefunktion im Terminaljournal wie folgt aussehen:

--- Indicator parameters --- 
- [1] Type int: 13
- [2] Type int: 0
- [3] Type int: 0
- [4] Type int: 2

Da dem Indikator-Objekt eine neue Eigenschaft hinzugefügt wird - seine ID, zu allen Klassen aller Indikator-Objekte, deren Dateien sich im Ordner \MQL5\Include\DoEasy\Objects\Indicators\Standart\ befinden, machen Sie einen kleinen Zusatz zu den Methoden der Anzeige des kurzen Indikator-Namens. Wir fügen einfach den ID-Wert zum Kurznamen hinzu:

//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndAC::PrintShort(void)
  {
   string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]");
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id);
  }
//+------------------------------------------------------------------+

Hier erstellen wir die ID-Beschreibung. Wohingegen, wenn der Wert der ID größer als -1 ist, wird die ID angezeigt, andernfalls, wenn die ID fehlt (ihr Wert ist -1), wird sie nicht in der Beschreibung angezeigt (nur eine schließende eckige Klammer). Und weiter, die erhaltene Zeichenkette in die Kurzbeschreibung des Indikators einfügen.

Solche Verbesserungen sind bereits in allen Indikatorobjektklassen eingetragen.


Nutzerdefiniertes Indikatorobjekt

Fügen wir nun eine nutzerdefinierte Indikatorobjektklasse hinzu. Legen Sie sie in den Standardindikator-Ordner der Bibliothek \MQL5\Include\DoEasy\Objects\Indicators\Standart\. Ich mache das nur, weil die Terminalindikatorliste auch nutzerdefinierte Indikatoren enthält. Das bedeutet, dass er auch zur Terminalindikatorliste gehört.

Die gesamte Klasse im Allgemeinen:

//+------------------------------------------------------------------+
//|                                                    IndCustom.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\\IndicatorDE.mqh"
//+------------------------------------------------------------------+
//| Custom indicator                                                 |
//+------------------------------------------------------------------+
class CIndCustom : public CIndicatorDE 
  {
private:

public:
   //--- Constructor
                     CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,MqlParam &mql_param[]) :
                        CIndicatorDE(IND_CUSTOM,symbol,timeframe,
                                     INDICATOR_STATUS_CUSTOM,
                                     INDICATOR_GROUP_ANY,
                                     mql_param[0].string_value,
                                     mql_param[0].string_value+"("+(symbol==NULL || symbol=="" ? ::Symbol() : symbol)+
                                                               ","+TimeframeDescription(timeframe)+")",mql_param) {}
   //--- Supported indicator properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_INDICATOR_PROP_INTEGER property);
   
//--- Display (1) a short description, (2) description of indicator object parameters in the journal
   virtual void      PrintShort(void);
   virtual void      PrintParameters(void);
  };
//+------------------------------------------------------------------+
//| Return 'true' if indicator supports a passed                     |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if indicator supports a passed                     |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CIndCustom::SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Display a short description of indicator object in the journal   |
//+------------------------------------------------------------------+
void CIndCustom::PrintShort(void)
  {
   string id=(this.ID()>WRONG_VALUE ? ", id #"+(string)this.ID()+"]" : "]");
   ::Print(GetStatusDescription()," ",this.Name()," ",this.Symbol()," ",TimeframeDescription(this.Timeframe())," [handle ",this.Handle(),id);
  }
//+------------------------------------------------------------------+
//| Display parameter description of indicator object in the journal |
//+------------------------------------------------------------------+
void CIndCustom::PrintParameters(void)
  {
   ::Print(" --- ",CMessage::Text(MSG_LIB_TEXT_IND_TEXT_IND_PARAMETERS)," --- ");
   
   int total=::ArraySize(this.m_mql_param);
   for(int i=1;i<total;i++)
     {
      ::Print(" - ",this.GetMqlParamDescription(i));
     }
  }
//+------------------------------------------------------------------+

Wir kennen alle Methoden bereits durch die Standard-Indikatorobjektklassen. Aber im Gegensatz zu ihnen bekommen wir hier zum Klassenkonstruktor die Parameter des Indikators, die im vorläufig erstellten Array der MqlParam-Eingangsstrukturen erstellt werden, aber nicht in den Eingängen. Und an den geschlossenen Konstruktor der abstrakten Indikator-Objektklasse übergeben wir den Status "custom indicator", die Gruppe “any indicator”, und als Name übergeben wir das allererste Element des Parameter-Arrays, das bei der Erstellung des nutzerdefinierten Indikators obligatorisch den Typ TYPE_STRING hat und der Wert des Feldes string_value enthält den Namen des nutzerdefinierten Indikators. Auf die gleiche Weise erstellen wir den Kurznamen des Indikators, aber zusammen mit der Beschreibung von Symbol und Zeitrahmen. Weiterhin können der Indikatorname und der Kurzname mit den Methoden der übergeordneten Klasse SetName() und SetShortName() geändert werden. Aber der Indikator-Name enthält bereits den Pfad zu ihm. Daher kann (zumindest in dieser Phase der Bibliotheksentwicklung) der Name eines bereits erstellten nutzerdefinierten Indikators nicht geändert werden.

In der Methode, die die Beschreibung der Parameter des Indikatorobjekts im Journal anzeigt, gehen wir wie folgt vor: zuerst die Anzeige der Kopfzeile, dann in der Schleife durch das Indikatorparameter-Array die Anzeige jedes folgenden Parameters unter Verwendung der zuvor analysierten Methoden (insbesondere der Methode der übergeordneten Klasse GetMqlParamDescription()).

Alle Indikatorobjekte werden in der Kollektion der Klasse CIndicatorsCollection in \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh gespeichert.

Beim Hinzufügen des Indikators zur Liste der Kollektionen muss zusätzlich die Eindeutigkeit seiner ID geprüft werden. Und wir fügen die Sichtbarkeit der nutzerdefinierten Indikatorenklasse hinzu, damit die Kollektionen der Indikatoren mit ihnen umgehen können.

Wir fügen die Datei der nutzerdefinierten Indikatorklasse in die Klasse der Indikatorobjekte der Kollektion ein:

//+------------------------------------------------------------------+
//|                                         IndicatorsCollection.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Indicators\Standart\IndAC.mqh"
#include "..\Objects\Indicators\Standart\IndAD.mqh"
#include "..\Objects\Indicators\Standart\IndADX.mqh"
#include "..\Objects\Indicators\Standart\IndADXW.mqh"
#include "..\Objects\Indicators\Standart\IndAlligator.mqh"
#include "..\Objects\Indicators\Standart\IndAMA.mqh"
#include "..\Objects\Indicators\Standart\IndAO.mqh"
#include "..\Objects\Indicators\Standart\IndATR.mqh"
#include "..\Objects\Indicators\Standart\IndBands.mqh"
#include "..\Objects\Indicators\Standart\IndBears.mqh"
#include "..\Objects\Indicators\Standart\IndBulls.mqh"
#include "..\Objects\Indicators\Standart\IndBWMFI.mqh"
#include "..\Objects\Indicators\Standart\IndCCI.mqh"
#include "..\Objects\Indicators\Standart\IndChaikin.mqh"
#include "..\Objects\Indicators\Standart\IndCustom.mqh"
#include "..\Objects\Indicators\Standart\IndDEMA.mqh"
#include "..\Objects\Indicators\Standart\IndDeMarker.mqh"
#include "..\Objects\Indicators\Standart\IndEnvelopes.mqh"
#include "..\Objects\Indicators\Standart\IndForce.mqh"
#include "..\Objects\Indicators\Standart\IndFractals.mqh"
#include "..\Objects\Indicators\Standart\IndFRAMA.mqh"
#include "..\Objects\Indicators\Standart\IndGator.mqh"
#include "..\Objects\Indicators\Standart\IndIchimoku.mqh"
#include "..\Objects\Indicators\Standart\IndMA.mqh"
#include "..\Objects\Indicators\Standart\IndMACD.mqh"
#include "..\Objects\Indicators\Standart\IndMFI.mqh"
#include "..\Objects\Indicators\Standart\IndMomentum.mqh"
#include "..\Objects\Indicators\Standart\IndOBV.mqh"
#include "..\Objects\Indicators\Standart\IndOsMA.mqh"
#include "..\Objects\Indicators\Standart\IndRSI.mqh"
#include "..\Objects\Indicators\Standart\IndRVI.mqh"
#include "..\Objects\Indicators\Standart\IndSAR.mqh"
#include "..\Objects\Indicators\Standart\IndStDev.mqh"
#include "..\Objects\Indicators\Standart\IndStoch.mqh"
#include "..\Objects\Indicators\Standart\IndTEMA.mqh"
#include "..\Objects\Indicators\Standart\IndTRIX.mqh"
#include "..\Objects\Indicators\Standart\IndVIDYA.mqh"
#include "..\Objects\Indicators\Standart\IndVolumes.mqh"
#include "..\Objects\Indicators\Standart\IndWPR.mqh"
//+------------------------------------------------------------------+

Im 'private' Klassenabschnitt deklarieren wir eine Methode zum Hinzufügen von Indikatorobjekten zur Kollektion und eine Methode, die die Verfügbarkeit von Indikatorobjekten mit eingestellter ID in der Liste prüft:

//+------------------------------------------------------------------+
//| Indicator collection                                             |
//+------------------------------------------------------------------+
class CIndicatorsCollection : public CObject
  {
private:
   CListObj                m_list;                       // Indicator object list
   MqlParam                m_mql_param[];                // Array of indicator parameters

//--- (1) Create, (2) add to collection list a new indicator object and set an ID for it
   CIndicatorDE           *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT);
   int                     AddIndicatorToList(CIndicatorDE *indicator,const int id);
//--- Return the indicator index in the list
   int                     Index(CIndicatorDE *compared_obj);
//--- Check presence of indicator object with specified id in the list
   bool                    CheckID(const int id);

public:

Im Abschnitt 'public' der Klasse deklarieren wir eine Methode, die den Zeiger auf das nutzerdefinierte Indikatorobjekt durch seine Gruppe und die im Array MqlParam spezifizierten Parameter zurückgibt (im Gegensatz zu den Standard-Indikatoren können die Parameter nur durch ihre Übergabe in einem solchen Array angegeben werden):

   CIndicatorDE           *GetIndCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price);
   CIndicatorDE           *GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam &param[]);
   CIndicatorDE           *GetIndDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price);

Fügen wir im Deklarationsblock der Methoden zur Erstellung von Indikatoren erstens zu den Parametern jeder Methode Angabe der Indikator-ID und zweitens die Deklaration der Methode zur Erstellung eines nutzerdefinierten Indikators hinzu. Ich werde nicht die vollständige Deklarationsliste dieser Methoden bereitstellen. Stattdessen werde ich nur drei Methoden bereitstellen (alle Methoden sind bereits verbessert und Sie können sie in den angehängten Dateien finden):

   int                     CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       const int ma_period=14,
                                       const ENUM_APPLIED_PRICE applied_price=PRICE_TYPICAL);
   int                     CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       ENUM_INDICATOR_GROUP group,
                                       MqlParam &mql_param[]);
   int                     CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                       const int ma_period=14,
                                       const int ma_shift=0,
                                       const ENUM_APPLIED_PRICE applied_price=PRICE_CLOSE);

In allen Methoden wurde nach der Eingabe von Symbol und Zeitrahmen die obligatorische Angabe der Indikator-ID bei dessen Erstellung hinzugefügt.
In der Methode der Erstellung des nutzerdefinierten Indikators wird zusätzlich die Indikatorgruppe angegeben und das Array der Indikatorparameter, die vorher erstellt und gefüllt wurden, übergeben. Der nutzerdefinierte Indikator wird auf Basis dieser Parameter erstellt.

Am Ende des Klassenkörpers deklarieren wir die Methode zum Setzen der ID für den angegebenen Indikator und die Methode, die das Indikatorobjekt mit der angegebenen ID zurückgibt:

//--- Set ID for the specified indicator
   void                    SetID(CIndicatorDE *indicator,const int id);
   
//--- Return indicator object by its ID
   CIndicatorDE           *GetIndByID(const uint id);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(void);

//--- Constructor
                           CIndicatorsCollection();

  };
//+------------------------------------------------------------------+

Analysieren wir nun alle deklarierten Methoden.

In der privaten Methode der Erstellung eines neuen Indikatorobjekts Erstellung eines neuen Objekts - nutzerdefinierter Indikator:

//+------------------------------------------------------------------+
//| Create new indicator object                                      |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   string symbol=(symbol_name==NULL || symbol_name=="" ? ::Symbol() : symbol_name);
   ENUM_TIMEFRAMES timeframe=(period==PERIOD_CURRENT ? ::Period() : period);
   CIndicatorDE *indicator=NULL;
   switch(ind_type)
     {
      case IND_AC          : indicator=new CIndAC(symbol,timeframe,mql_param);         break;
      case IND_AD          : indicator=new CIndAD(symbol,timeframe,mql_param);         break;
      case IND_ADX         : indicator=new CIndADX(symbol,timeframe,mql_param);        break;
      case IND_ADXW        : indicator=new CIndADXW(symbol,timeframe,mql_param);       break;
      case IND_ALLIGATOR   : indicator=new CIndAlligator(symbol,timeframe,mql_param);  break;
      case IND_AMA         : indicator=new CIndAMA(symbol,timeframe,mql_param);        break;
      case IND_AO          : indicator=new CIndAO(symbol,timeframe,mql_param);         break;
      case IND_ATR         : indicator=new CIndATR(symbol,timeframe,mql_param);        break;
      case IND_BANDS       : indicator=new CIndBands(symbol,timeframe,mql_param);      break;
      case IND_BEARS       : indicator=new CIndBears(symbol,timeframe,mql_param);      break;
      case IND_BULLS       : indicator=new CIndBulls(symbol,timeframe,mql_param);      break;
      case IND_BWMFI       : indicator=new CIndBWMFI(symbol,timeframe,mql_param);      break;
      case IND_CCI         : indicator=new CIndCCI(symbol,timeframe,mql_param);        break;
      case IND_CHAIKIN     : indicator=new CIndCHO(symbol,timeframe,mql_param);        break;
      case IND_DEMA        : indicator=new CIndDEMA(symbol,timeframe,mql_param);       break;
      case IND_DEMARKER    : indicator=new CIndDeMarker(symbol,timeframe,mql_param);   break;
      case IND_ENVELOPES   : indicator=new CIndEnvelopes(symbol,timeframe,mql_param);  break;
      case IND_FORCE       : indicator=new CIndForce(symbol,timeframe,mql_param);      break;
      case IND_FRACTALS    : indicator=new CIndFractals(symbol,timeframe,mql_param);   break;
      case IND_FRAMA       : indicator=new CIndFRAMA(symbol,timeframe,mql_param);      break;
      case IND_GATOR       : indicator=new CIndGator(symbol,timeframe,mql_param);      break;
      case IND_ICHIMOKU    : indicator=new CIndIchimoku(symbol,timeframe,mql_param);   break;
      case IND_MA          : indicator=new CIndMA(symbol,timeframe,mql_param);         break;
      case IND_MACD        : indicator=new CIndMACD(symbol,timeframe,mql_param);       break;
      case IND_MFI         : indicator=new CIndMFI(symbol,timeframe,mql_param);        break;
      case IND_MOMENTUM    : indicator=new CIndMomentum(symbol,timeframe,mql_param);   break;
      case IND_OBV         : indicator=new CIndOBV(symbol,timeframe,mql_param);        break;
      case IND_OSMA        : indicator=new CIndOsMA(symbol,timeframe,mql_param);       break;
      case IND_RSI         : indicator=new CIndRSI(symbol,timeframe,mql_param);        break;
      case IND_RVI         : indicator=new CIndRVI(symbol,timeframe,mql_param);        break;
      case IND_SAR         : indicator=new CIndSAR(symbol,timeframe,mql_param);        break;
      case IND_STDDEV      : indicator=new CIndStDev(symbol,timeframe,mql_param);      break;
      case IND_STOCHASTIC  : indicator=new CIndStoch(symbol,timeframe,mql_param);      break;
      case IND_TEMA        : indicator=new CIndTEMA(symbol,timeframe,mql_param);       break;
      case IND_TRIX        : indicator=new CIndTRIX(symbol,timeframe,mql_param);       break;
      case IND_VIDYA       : indicator=new CIndVIDYA(symbol,timeframe,mql_param);      break;
      case IND_VOLUMES     : indicator=new CIndVolumes(symbol,timeframe,mql_param);    break;
      case IND_WPR         : indicator=new CIndWPR(symbol,timeframe,mql_param);        break;
      case IND_CUSTOM      : indicator=new CIndCustom(symbol,timeframe,mql_param);     break;
      default: break;
     }
   return indicator;
  }
//+------------------------------------------------------------------+

Ändern wir die Methode zum Hinzufügen eines neuen Indikatorobjekts zur Kollektion:

//+------------------------------------------------------------------+
//| Add a new indicator object to collection list                    |
//+------------------------------------------------------------------+
int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id)
  {
//--- If invalid indicator is passed to the object - return INVALID_HANDLE
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- If such indicator is already in the list
   int index=this.Index(indicator);
   if(index!=WRONG_VALUE)
     {
      //--- Remove the earlier created object, get indicator object from the list and return indicator handle
      delete indicator;
      indicator=this.m_list.At(index);
     }
//--- If indicator object is not in the list yet
   else
     {
      //--- If failed to add indicator object to the list - display a corresponding message,
      //--- remove object and return INVALID_HANDLE
      if(!this.m_list.Add(indicator))
        {
         ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST));
         delete indicator;
         return INVALID_HANDLE;
        }
     }
//--- If indicator is successfully added to the list or is already there...
//--- If indicator with specified ID (not -1) is not in the list - set ID
   if(id>WRONG_VALUE && !this.CheckID(id))
      indicator.SetID(id);
//--- Return handle of a new indicator added to the list
   return indicator.Handle();
  }
//+------------------------------------------------------------------+

Jetzt wird auch die Indikator-ID der Methode übergeben. Wenn der Indikator mit dieser ID noch nicht in der Kollektion enthalten ist, wird die angegebene ID für das Kennzeichenobjekt gesetzt. Andernfalls wird die Indikator-ID standardmäßig auf -1 gesetzt.

Nun sind alle Methoden zur Erzeugung von Indikatorobjekten kürzer geworden.
Lassen Sie uns dies am Beispiel der Erstellung von AC- und Alligator-Indikatorobjekten analysieren:

//+------------------------------------------------------------------+
//| Create a new indicator object Accelerator Oscillator             |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
  {
//--- AC indicator possesses no parameters - resize the array of parameter structures
   ::ArrayResize(this.m_mql_param,0);
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Create new indicator object Alligator                            |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                           const int jaw_period=13,
                                           const int jaw_shift=8,
                                           const int teeth_period=8,
                                           const int teeth_shift=5,
                                           const int lips_period=5,
                                           const int lips_shift=3,
                                           const ENUM_MA_METHOD ma_method=MODE_SMMA,
                                           const ENUM_APPLIED_PRICE applied_price=PRICE_MEDIAN)
  {
//--- Add required indicator parameters to the array of parameter structures
   ::ArrayResize(this.m_mql_param,8);
   this.m_mql_param[0].type=TYPE_INT;
   this.m_mql_param[0].integer_value=jaw_period;
   this.m_mql_param[1].type=TYPE_INT;
   this.m_mql_param[1].integer_value=jaw_shift;
   this.m_mql_param[2].type=TYPE_INT;
   this.m_mql_param[2].integer_value=teeth_period;
   this.m_mql_param[3].type=TYPE_INT;
   this.m_mql_param[3].integer_value=teeth_shift;
   this.m_mql_param[4].type=TYPE_INT;
   this.m_mql_param[4].integer_value=lips_period;
   this.m_mql_param[5].type=TYPE_INT;
   this.m_mql_param[5].integer_value=lips_shift;
   this.m_mql_param[6].type=TYPE_INT;
   this.m_mql_param[6].integer_value=ma_method;
   this.m_mql_param[7].type=TYPE_INT;
   this.m_mql_param[7].integer_value=applied_price;
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_ALLIGATOR,this.m_mql_param,symbol,timeframe);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+

Jetzt reicht es aus, die Eingangsstruktur auszufüllen, einen Indikator zu erstellen und die Methode für dessen Hinzufügen zur Kollektion aufzurufen. Solche Änderungen wurden in allen Methoden der Erstellung von Indikatorobjekten durchgeführt. Ich werde sie nicht analysieren, mit Ausnahme der nutzerdefinierten Methode zur Erstellung des Indikators:

//+------------------------------------------------------------------+
//| Create a new object - custom indicator                           |
//| and place it to the collection list                              |
//+------------------------------------------------------------------+
int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,
                                        ENUM_INDICATOR_GROUP group,
                                        MqlParam &mql_param[])
  {
//--- Create indicator object
   CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe);
   if(indicator==NULL)
      return INVALID_HANDLE;
//--- Set a group for indicator object
   indicator.SetGroup(group);
//--- Return indicator handle received as a result of adding the object to collection list
   return this.AddIndicatorToList(indicator,id);
  }
//+------------------------------------------------------------------+

Hier ist es ein wenig anders. Hier erhält die Erstellungsmethode außer der ID auch die Indikatorgruppe. Und alle Parameter des erstellten Indikators werden auf einmal innerhalb des Arrays von MqlParam-Parametern übergeben, aus dem Grund, dass wir die Parameter des erstellten nutzerdefinierten Indikators vorher nicht kennen können.

Zu absolut allen Methoden der Standard-Indikator-Erstellung wurden Standard-Standardwerte für jeden Eingabeparameter hinzugefügt. Um also einen Standardindikator mit Standardparametern zu erstellen, reicht es aus, das Symbol, den Zeitrahmen und die ID anzugeben.

Implementierung der Methode, die den Zeiger auf das Objekt - den nutzerdefinierten Indikator - zurückgibt:

//+------------------------------------------------------------------+
//| Return pointer to object- custom indicator                       |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,ENUM_INDICATOR_GROUP group,MqlParam &param[])
  {
   CIndicatorDE *tmp=new CIndCustom(symbol,timeframe,param);
   if(tmp==NULL)
      return NULL;
   tmp.SetGroup(group);
   int index=this.Index(tmp);
   delete tmp;
   return(index>WRONG_VALUE ? this.m_list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

Hier erzeugen wir ein temporäres Indikatorobjekt, um das gleiche Objekt in der Liste der Kollektionen zu suchen, setzen eine Gruppe dafür und rufen den Index des gefundenen Objekts in der Liste der Kollektionen ab. Wir entfernen dann das temporäre Objekt und geben entweder einen Zeiger auf das Objekt mit dem gefundenen Index der Liste zurück, oder NULL, wenn ein solches Objekt nicht in der Liste gefunden wird.

Die Methode, die das Vorhandensein des Indikatorobjekts mit der angegebenen ID in der Liste prüft:

//+------------------------------------------------------------------+
//| Check presence of indicator object with specified id in the list |
//+------------------------------------------------------------------+
bool CIndicatorsCollection::CheckID(const int id)
  {
   CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL);
   return(list!=NULL && list.Total()!=0);
  }
//+------------------------------------------------------------------+

Das holt die Liste der Indikatorobjekte mit der angegebenen ID und gibt das Flag zurück, das prüft, ob die Liste gültig und nicht leer ist (die Liste muss ein Objekt enthalten).

Die Methode, die die ID für den angegebenen Indikator setzt:

//+------------------------------------------------------------------+
//| Set ID for the specified indicator                               |
//+------------------------------------------------------------------+
void CIndicatorsCollection::SetID(CIndicatorDE *indicator,const int id)
  {
   if(indicator==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_IND_POINTER));
      return;
     }
   if(id>WRONG_VALUE)
     {
      if(CheckID(id))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_IND_ID_EXIST)," #",(string)id);
         return;
        }
     }
   indicator.SetID(id);
  }
//+------------------------------------------------------------------+

Die Methode erhält einen Zeiger auf ein Indikatorobjekt, auf das die über den Parameter an die Methode übergebene ID gesetzt werden muss.

Wenn ein ungültiger Zeiger übergeben wird, erfolgt eine Benachrichtigung und die Methode wird verlassen.
Ist der ID-Wert größer als -1, wird das Vorhandensein des Indikatorobjekts mit der gleichen ID geprüft und wenn es vorhanden ist - wird informiert und die Methode verlassen.
Wenn alle Prüfungen erfolgreich waren, wird die ID für das Objekt gesetzt. Falls der an die Methode übergebene ID-Wert kleiner als Null ist, wird eine solche ID für das Objekt ohne jegliche Prüfung gesetzt, und eine solche ID des Indikatorobjekts bedeutet dessen Abwesenheit.

Die Methode, die das Indikatorobjekt mit der angegebenen ID zurückgibt:

//+------------------------------------------------------------------+
//| Return indicator object by its ID                                |
//+------------------------------------------------------------------+
CIndicatorDE *CIndicatorsCollection::GetIndByID(const uint id)
  {
   CArrayObj *list=CSelect::ByIndicatorProperty(this.GetList(),INDICATOR_PROP_ID,id,EQUAL);
   return(list==NULL || list.Total()==0 ? NULL : list.At(list.Total()-1));
  }
//+------------------------------------------------------------------+

Hier erhalten wir die Liste der Indikator-Objekte mit der angegebenen ID und geben entweder NULL (wenn die Liste nicht erhalten werden konnte oder leer ist) oder einen Zeiger auf das Objekt mit der erforderlichen ID zurück. Da es nur ein Objekt mit der angegebenen ID geben kann, ist es egal, welcher Index in der empfangenen Liste angegeben wird: der erste oder der letzte. Hier wird der Letzte angegeben.

Beim Testen der Indikatorerstellung im EA zeigte sich ein Problem: Beim Wechsel des Zeitrahmens werden die gleichen zusätzlichen Indikatoren erstellt, aber mit einem anderen Zeitrahmen. Und es ist wahr: Indikatoren mit den gleichen Eingaben, aber berechnet auf verschiedenen Zeitrahmen sind zwei verschiedene Indikatoren. Um dieses Problem einfach zu vermeiden, genügt es, die Liste der erstellten Indikatoren bei der Deinitialisierung des Programms zu löschen. Zu diesem Zweck wird im Hauptobjekt der CEngine-Bibliothek in der Datei \MQL5\Include\DoEasy\Engine.mqh ein neues OnDeinit() deklariert:

//--- (1) Timer, event handler (2) NewTick, (3) Calculate, Deinit
   void                 OnTimer(SDataCalculate &data_calculate);
   void                 OnTick(SDataCalculate &data_calculate,const uint required=0);
   int                  OnCalculate(SDataCalculate &data_calculate,const uint required=0);
   void                 OnDeinit(void);

Dessen Implementierung geschieht außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Deinitialize library                                             |
//+------------------------------------------------------------------+
void CEngine::OnDeinit(void)
  {
   this.m_indicators.GetList().Clear();
  }
//+------------------------------------------------------------------+

Diese Klassenmethode wird von OnDeinit() des Programms aufgerufen. Was wir hier haben, ist der Aufruf der Methode zum Löschen der Kollektionsliste im Kollektionsobjekt des Indikators.

Tests

Verwenden wir den EA aus dem vorherigen Artikel, um das Erstellen verschiedener Indikatoren und das Abrufen von Daten aus den erstellten Indikatorobjekten zu testen.

Wir speichern ihn in einem neuen Ordner \MQL5\Experts\TestDoEasy\Part56\ unter einem neuen Namen TestDoEasyPart56.mq5.

Wir erstellen im EA zwei nutzerdefinierte Indikatoren Moving Average, aber mit unterschiedlichen Parametern (wir nehmen die Indikatoren im Indikator-Beispielordner aus der Terminal-Standardauslieferung \MQL5\Indicators\Examples\) und erstellen zwei Standardindikatoren, Adaptive Moving Average, ebenfalls mit unterschiedlichen Eingabeparameter.

Im globalen Bereich setzen wir Makro-Ersetzungen, um den Aufruf der Indikatoren durch ihre ID zu vereinfachen und deklarieren zwei Arrays von Parametern für die Erstellung von zwei nutzerdefinierten Indikatoren:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart56.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- enums
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_SET_STOP_LOSS,
   BUTT_SET_TAKE_PROFIT,
   BUTT_PROFIT_WITHDRAWAL,
   BUTT_TRAILING_ALL
  };
#define TOTAL_BUTT   (20)
#define MA1          (1)
#define MA2          (2)
#define AMA1         (3)
#define AMA2         (4)
//--- structures
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)
sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 
input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)
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_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)
sinput   bool              InpUseSounds         =  true; // Use sounds
//--- global variables
CEngine        engine;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ushort         magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           distance_pending_request;
uint           bars_delay_pending_request;
uint           slippage;
bool           trailing_on;
bool           pressed_pending_buy;
bool           pressed_pending_buy_limit;
bool           pressed_pending_buy_stop;
bool           pressed_pending_buy_stoplimit;
bool           pressed_pending_close_buy;
bool           pressed_pending_close_buy2;
bool           pressed_pending_close_buy_by_sell;
bool           pressed_pending_sell;
bool           pressed_pending_sell_limit;
bool           pressed_pending_sell_stop;
bool           pressed_pending_sell_stoplimit;
bool           pressed_pending_close_sell;
bool           pressed_pending_close_sell2;
bool           pressed_pending_close_sell_by_buy;
bool           pressed_pending_delete_all;
bool           pressed_pending_close_all;
bool           pressed_pending_sl;
bool           pressed_pending_tp;
double         trailing_stop;
double         trailing_step;
uint           trailing_start;
uint           stoploss_to_modify;
uint           takeprofit_to_modify;
int            used_symbols_mode;
string         array_used_symbols[];
string         array_used_periods[];
bool           testing;
uchar          group1;
uchar          group2;
double         g_point;
int            g_digits;
//--- Arrays of custom indicator parameters
MqlParam       param_ma1[];
MqlParam       param_ma2[];
//+------------------------------------------------------------------+

Im Wesentlichen handelt es sich bei den deklarierten Makro-Substitutionen um die Beschreibung von numerischen Werten von Indikatoren. Der Aufruf der Indikator-ID über den Namen ist einfacher als der Aufruf über den Wert.

In OnInit() des EAs erzeugen wir alle vier Indikatoren und schreiben ins Journal die Daten aller erzeugten Indikatoren:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Calling the function displays the list of enumeration constants in the journal,
//--- (the list is set in strings 22 and 25 of  DELib.mqh file) for checking the constants validity
   //EnumNumbersTest();

//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   testing=engine.IsTester();
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
   trailing_stop=InpTrailingStop*Point();
   trailing_step=InpTrailingStep*Point();
   trailing_start=InpTrailingStart;
   stoploss_to_modify=InpStopLossModify;
   takeprofit_to_modify=InpTakeProfitModify;
   distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq);
   bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq);
   g_point=SymbolInfoDouble(NULL,SYMBOL_POINT);
   g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS);
//--- Initialize random group numbers
   group1=0;
   group2=0;
   srand(GetTickCount());
   
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- Create indicators
   ArrayResize(param_ma1,4);
   //--- Name of indicator 1
   param_ma1[0].type=TYPE_STRING;
   param_ma1[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Calculation period
   param_ma1[1].type=TYPE_INT;
   param_ma1[1].integer_value=13;
   //--- Horizontal shift
   param_ma1[2].type=TYPE_INT;
   param_ma1[2].integer_value=0;
   //--- Smoothing method
   param_ma1[3].type=TYPE_INT;
   param_ma1[3].integer_value=MODE_SMA;
   //--- Create indicator 1
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,INDICATOR_GROUP_TREND,param_ma1);
   
   ArrayResize(param_ma2,5);
   //--- Name of indicator 2
   param_ma2[0].type=TYPE_STRING;
   param_ma2[0].string_value="Examples\\Custom Moving Average.ex5";
   //--- Calculation period
   param_ma2[1].type=TYPE_INT;
   param_ma2[1].integer_value=13;
   //--- Horizontal shift
   param_ma2[2].type=TYPE_INT;
   param_ma2[2].integer_value=0;
   //--- Smoothing method
   param_ma2[3].type=TYPE_INT;
   param_ma2[3].integer_value=MODE_SMA;
   //--- Calculation price
   param_ma2[4].type=TYPE_INT;
   param_ma2[4].integer_value=PRICE_OPEN;
   //--- Create indicator 2
   engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,INDICATOR_GROUP_TREND,param_ma2);
   
   //--- Create indicator 3
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA1);
   //--- Create indicator 4
   engine.GetIndicatorsCollection().CreateAMA(NULL,PERIOD_CURRENT,AMA2,14);
   
   //--- Display descriptions of created indicators
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();
   
//--- Check and remove remaining EA graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- Set trailing activation button status
   ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on);
//--- Reset states of the buttons for working using pending requests
   for(int i=0;i<14;i++)
     {
      ButtonState(butt_data[i].name+"_PRICE",false);
      ButtonState(butt_data[i].name+"_TIME",false);
     }

//--- Check playing a standard sound by macro substitution and a custom sound by description
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(TextByLanguage("The sound of a falling coin 2"));

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Wir legen den Berechnungspreis PRICE_OPEN für den zweiten nutzerdefinierten Indikator MA fest. Wenn der Berechnungspreis nicht eindeutig angegeben wird, wird standardmäßig (im ersten MA-Indikator) der Preis PRICE_CLOSE zur Berechnung des Indikators verwendet.

Beim Erstellen von AMA-Indikatoren erhält der erste von ihnen den Berechnungszeitraum 9 (standardmäßig eingestellt). Der zweite erhält explizit den Wert von 14.
Alle vier erstellten Indikatoren besitzen also unterschiedliche Eingabewerte für ihre Parameter.

In OnDeinit() des EAa wird OnDeinit() der Bibliothek aufgerufen:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove EA graphical objects by an object name prefix
   ObjectsDeleteAll(0,prefix);
   Comment("");
//--- Deinitialize library
   engine.OnDeinit();
  }
//+------------------------------------------------------------------+

Dadurch wird die Liste der Kollektion von Indikatoren während der Deinitialisierung des EA beim Umschalten der Zeitrahmen gelöscht. Dies schließt die Notwendigkeit aus, unnötige zusätzliche Indikator-Objekte zu erstellen.

In OnTick() des EA erfolgt der Zugriff auf jedes der erstellten Indikator-Objekte und es werden die Daten des aktuellen Balkens jedes Indikators als Kommentar auf dem Chart angezeigt:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Handle the NewTick event in the library
   engine.OnTick(rates_data);

//--- If work in tester
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer(rates_data);   // Work in timer
      PressButtonsControl();        // Button press control
      engine.EventsHandling();      // Work with events
     }
//--- Get custom indicator objects
   CIndicatorDE *ma1=engine.GetIndicatorsCollection().GetIndByID(MA1);
   CIndicatorDE *ma2=engine.GetIndicatorsCollection().GetIndByID(MA2);
   CIndicatorDE *ama1=engine.GetIndicatorsCollection().GetIndByID(AMA1);
   CIndicatorDE *ama2=engine.GetIndicatorsCollection().GetIndByID(AMA2);
   Comment
     (
      "ma1=",DoubleToString(ma1.GetDataBuffer(0,0),6),
      ", ma2=",DoubleToString(ma2.GetDataBuffer(0,0),6),
      "\nama1=",DoubleToString(ama1.GetDataBuffer(0,0),6),
      ", ama2=",DoubleToString(ama2.GetDataBuffer(0,0),6)
     );
   
   
//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing of pending orders
     }
  }
//+------------------------------------------------------------------+

Im vorigen Artikel haben wir temporär Indikatorobjekte in der Bibliotheksinitialisierungsfunktion OnInitDoEasy() erzeugt. Entfernen wir dieser Zeichenketten aus der Funktion:

//--- Create timeseries of all symbols used
   engine.SeriesCreateAll(array_used_periods);
//--- Check created timeseries - display descriptions of all created timeseries in the journal
//--- (true - only created ones, false - created and declared ones)
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
   //engine.GetTimeSeriesCollection().Print(true);      // Full descriptions

//--- Create indicators
   engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),9,2,30,0,PRICE_CLOSE);
   engine.GetIndicatorsCollection().CreateAMA(Symbol(),Period(),10,3,32,5,PRICE_CLOSE);
   engine.GetIndicatorsCollection().Print();
   engine.GetIndicatorsCollection().PrintShort();

//--- Create resource text files

Kompilieren Sie den EA und starten Sie ihn auf dem Chart, der in den Einstellungen vorläufig so eingestellt ist, dass er nur das aktuelle Symbol und den aktuellen Zeitrahmen verwendet.
Im Journal werden Beschreibungen der Parameter aller erstellten Indikatoren angezeigt:

--- Initialize "DoEasy" library ---
Work with the current symbol only: "EURUSD"
Work with the current timeframe only: H1
EURUSD symbol timeseries: 
- "EURUSD" H1 timeseries: Requested: 1000, Actually: 1000, Created: 1000, On the server: 6284
Library initialize time: 00:00:00.141
============= Parameter list start: “Custom indicator" =============
Indicator status: Custom indicator
Indicator type: CUSTOM
Indicator timeframe: H1
Indicator handle: 10
Indicator group: Trend indicator
Indicator ID: 1
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Examples\Custom Moving Average.ex5"
Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Indicator parameters --- 
 - [1] Type int: 13
 - [2] Type int: 0
 - [3] Type int: 0
================== Parameter list end: "Custom indicator" ==================
 
============= Parameter list start: “Custom indicator" =============
Indicator status: Custom indicator
Indicator type: CUSTOM
Indicator timeframe: H1
Indicator handle: 11
Indicator group: Trend indicator
Indicator ID: 2
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Examples\Custom Moving Average.ex5"
Indicator short name: "Examples\Custom Moving Average.ex5(EURUSD,H1)"
 --- Indicator parameters --- 
 - [1] Type int: 13
 - [2] Type int: 0
 - [3] Type int: 0
 - [4] Type int: 2
================== Parameter list end: "Custom indicator" ==================
 
============= Parameter list start: "Standard indicator" =============
Indicator status: Standard indicator
Indicator type: AMA
Indicator timeframe: H1
Indicator handle: 12
Indicator group: Trend indicator
Indicator ID: 3
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator short name: "AMA(EURUSD,H1)"
 --- Indicator parameters --- 
 - Averaging period: 9
 - Fast MA period: 2
 - Slow MA period: 30
 - Horizontal shift of the indicator: 0
 - Price type or handle: CLOSE
================== Parameter list end: "Standard indicator" ==================
 
============= Parameter list start: "Standard indicator" =============
Indicator status: Standard indicator
Indicator type: AMA
Indicator timeframe: H1
Indicator handle: 13
Indicator group: Trend indicator
Indicator ID: 4
------
Empty value for plotting where nothing will be drawn: EMPTY_VALUE
------
Indicator symbol: EURUSD
Indicator name: "Adaptive Moving Average"
Indicator short name: "AMA(EURUSD,H1)"
 --- Indicator parameters --- 
 - Averaging period: 14
 - Fast MA period: 2
 - Slow MA period: 30
 - Horizontal shift of the indicator: 0
 - Price type or handle: CLOSE
================== Parameter list end: "Standard indicator" ==================

Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 10, id #1]
Custom indicator Examples\Custom Moving Average.ex5 EURUSD H1 [handle 11, id #2]
Standard indicator Adaptive Moving Average EURUSD H1 [handle 12, id #3]
Standard indicator Adaptive Moving Average EURUSD H1 [handle 13, id #4]

Das Symbolchart zeigt Daten aus den Puffern aller erstellten Indikatoren an:

Zum Chart können notwendige Indikatoren hinzugefügt werden, die von den Parametern her den im EA erstellten Indikatoren entsprechen. Sie können die Übereinstimmung dieser Indikatoren im Kommentar im Chart und im Datenfenster überprüfen - sie werden übereinstimmen.

Was kommt als Nächstes?

Im nächsten Artikel werde ich die Funktionsweisen zur Handhabung von Indikatoren in EAs weiter ausbauen. In den nächsten Artikeln plane ich, die Bindung der Indikator-Daten an die Balken der Zeitreihenklasse zu erstellen und die Daten aus den Indikatoren für verschiedene statistische Untersuchungen zu erhalten.

Alle Dateien der aktuellen Bibliotheksversion sind unten angehängt, zusammen mit der Test-EA-Datei für MQL5. Sie können sie herunterladen und alles testen.
Bitte beachten Sie, dass sich die Kollektionsklasse der Indikatoren derzeit noch in der Entwicklung befindet, daher wird dringend davon abgeraten, sie in Ihren Programmen zu verwenden.
Hinterlassen Sie Ihre Kommentare, Fragen und Anregungen im Kommentarteil dieses Artikels.

Zurück zum Inhalt

Frühere Artikel dieser Serie:

Zeitreihen in der Bibliothek DoEasy (Teil 35): das Balkenobjekt und die Liste der Zeitreihen eines Symbols
Zeitreihen in der Bibliothek DoEasy (Teil 36): Objekt der Zeitreihe für alle verwendeten Symbolzeitrahmen
Zeitreihen in der Bibliothek DoEasy (Teil 37): Kollektion von Zeitreihen - Datenbank der Zeitreihen nach Symbolen und Zeitrahmen
Zeitreihen in der Bibliothek DoEasy (Teil 38): Kollektion von Zeitreihen - Aktualisierungen in Echtzeit und Datenzugriff aus dem Programm

Zeitreihen in der Bibliothek DoEasy (Teil 40): Bibliotheksbasierte Indikatoren - Aktualisierung der Daten in Echtzeit
Zeitreihen in der Bibliothek DoEasy (Teil 41): Beispiel eines Multi-Symbol- und Multi-Zeitrahmen-Indikators
Zeitreihen in der Bibliothek DoEasy (Teil 42): Abstrakte Objektklasse der Indikatorpuffer
Zeitreihen in der Bibliothek DoEasy (Teil 43): Klassen der Objekte von Indikatorpuffern
Zeitreihen in der Bibliothek DoEasy (Teil 44): Kollektionsklasse der Objekte von Indikatorpuffern
Zeitreihen in der Bibliothek DoEasy (Teil 45): Puffer für Mehrperiodenindikator
Zeitreihen in der Bibliothek DoEasy (Teil 46): Mehrperioden-Multisymbol-Indikatorpuffer
Zeitreihen in der Bibliothek DoEasy (Teil 47): Standardindikatoren für mehrere Symbole und Perioden
Zeitreihen in der Bibliothek DoEasy (Teil 48): Mehrperioden-Multisymbol-Indikatoren mit einem Puffer in einem Unterfenster
Zeitreihen in der Bibliothek DoEasy (Teil 49): Standardindikatoren mit mehreren Puffern für mehrere Symbole und Perioden
Zeitreihen in der Bibliothek DoEasy (Teil 50): Verschieben der Standardindikatoren für mehrere Symbole und Perioden
Zeitreihen in der Bibliothek DoEasy (Teil 51): Zusammengesetzte Standardindikatoren für mehrere Symbole und Perioden
Zeitreihen in der Bibliothek DoEasy (Teil 52): Plattformübergreifende Eigenschaft für Standardindikatoren mit einem Puffer für mehrere Symbole und Perioden
Zeitreihen in der Bibliothek DoEasy (Teil 53): Abstrakte Basisklasse der Indikatoren
Zeitreihen in der Bibliothek DoEasy (Teil 54): Abgeleitete Klassen des abstrakten Basisindikators
Zeitreihen in der Bibliothek DoEasy (Teil 55): Die Kollektionsklasse der Indikatoren