Zeitreihen in der Bibliothek DoEasy (Teil 48): Mehrperioden-Multisymbol-Indikatoren mit einem Puffer in einem Unterfenster

Artyom Trishkin | 17 November, 2020

Inhalt


Konzept

Im vorigen Artikel habe ich den Standardindikator Accelerator Oscillator verwendet, um die Prinzipien und Methoden der Anzeige von Daten von Standardindikatoren hervorzuheben, die mit einem beliebigen Symbol/Zeitrahmen auf dem aktuellen Diagramm berechnet wurden. Jetzt müssen wir Methoden sammeln, die es uns ermöglichen, andere Standardindikatoren zu entwickeln. Wir sind fast alle bereit, dies zu erreichen. Im vorliegenden Artikel werde ich einen Test durchführen, um ähnliche Code-Konstruktionen in den Methoden zu bestimmen, um anschließend sich wiederholende Code-Blöcke in separate Methoden zu verschieben. Das ist das Einfachste, was wir tun müssen, um mit Single-Buffer-Standardindikatoren zu arbeiten - denjenigen, die nur einen Puffer zur Anzeige von Daten verwenden.
In den folgenden Artikeln werde ich definieren, was im Code verbessert werden kann, um ihn zu reduzieren und zu optimieren, basierend auf den Methoden, die in den aktuellen Artikeln verbessert wurden, und den Methoden der Arbeit mit Multi-Puffer-Standardindikatoren, die im nächsten Artikel entwickelt werden sollen.

Heute werde ich ein Beispiel für einen nutzerdefinierten Indikator entwickeln, der einen, in den Einstellungen ausgewählten Standardindikator in einem Unterfenster des aktuellen Charts anzeigt. Dabei handelt es sich um einen der Standardindikatoren, der einen einfach gezeichneten Puffer aufweist und seine Daten im Unterfenster des Hauptcharts anzeigt. Dazu werde ich die Bibliotheksklassen etwas verbessern müssen. Dies wird ein wichtiger vorbereitender Schritt für die Erstellung der Arbeitsmethoden für die übrigen Standardindikatoren sein.


Verbesserung der Bibliothek der Klasse

Fügen wir zunächst eine neue Bibliotheksnachricht zu \MQL5\Include\DoEasy\Datas.mqh hinzu.

Fügen wir den neuen Nachrichtenindex hinzu:

   MSG_LIB_TEXT_BUFFER_TEXT_INVALID_PROPERTY_BUFF,    // Invalid number of indicator buffers (#property indicator_buffers)
   MSG_LIB_TEXT_BUFFER_TEXT_MAX_BUFFERS_REACHED,      // Reached maximum possible number of indicator buffers
   MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ,            // No buffer object for standard indicator

   MSG_LIB_TEXT_BUFFER_TEXT_STATUS_NONE,              // No drawing

und den Nachrichtentext, der dem neu hinzugefügten Index entspricht:

   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
   {"Нет ни одного объекта-буфера для стандартного индикатора","There is no buffer object for the standard indicator"},
   
   {"Нет отрисовки","No drawing"},


Alle aktuellen Verbesserungen beziehen sich auf die Kollektionsklasse der Indikatorpuffer in \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh und natürlich auf die Klasse CEngine.

In BuffersCollection.mqh müssen wir eine einzige Methode zur Vorbereitung der Daten des berechneten Puffers aller erstellten Standardindikatoren hinzufügen, so dass die Bibliothek in der Lage ist, dies selbstständig zu tun, nachdem sie einen Befehl vom Programm erhalten hat. Dies wird den endgültigen Programmcode vereinfachen — es wird nicht mehr notwendig sein, benötigte Objekte zu suchen und zu erhalten.

Deklarieren wir die Methode im 'public' Teil der Klasse:

//--- Prepare calculated buffer data of (1) the specified standard indicator and (2) all created standard indicators
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
   bool                    PreparingDataAllBuffersStdInd(void);
//--- Clear buffer data of the specified standard indicator by the timeseries index

und schreiben Sie die Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of all created standard indicators                               |
//+------------------------------------------------------------------+
bool CBuffersCollection::PreparingDataAllBuffersStdInd(void)
  {
   CArrayObj *list=this.GetListBuffersWithID();
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
   bool res=true;
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      CBuffer *buff=list.At(i);
      if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
         continue;
      CSeriesDE *series=this.m_timeseries.GetSeries(buff.Symbol(),buff.Timeframe());
      if(series==NULL)
         continue;
      int used_data=(int)series.AvailableUsedData();
      int copied=this.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),used_data);
      if(copied<used_data)
         res &=false;
     }
   return res;
  }
//+------------------------------------------------------------------+

Jedes Pufferobjekt, das zur Berechnung des Standardkennzeichens verwendet wird, hat eine ID, die entweder automatisch entsprechend dem Standardindikatortyp (ENUM_INDICATOR) oder manuell vom Programm beim Anlegen der erforderlichen Pufferobjekte vergeben wird.

Die Liste der Pufferobjekte, deren ID ungleich -1 ist, ist das erste, was wir hier erhalten.
Wenn die Liste leer ist, wird die Meldung angezeigt, dass keine Pufferobjekte für Standardindikatoren angelegt wurden, und es wird false
zurückgegeben.
Als Nächstes holen wir in einer Schleife über alle Pufferobjekte, die einen Standardindikator-ID haben, das nächste Pufferobjekt. Jeder Standardindikator weist mindestens zwei derartige Pufferobjekte auf — ein Berechnungspuffer und einen zu zeichnenden. Wir benötigen nur Berechnungspuffer, aber wir wählen sie in der aktuellen Schleife nicht aus, da nur ein Berechnungspuffer für die weitere Behandlung in der Methode PreparingDataBufferStdInd() ausgewählt wird, die ich im vorherigen Artikel betrachtet habe (ich werde diese Methode später hier verbessern). Daher werde ich diese Auswahl hier nicht wiederholen.
Jetzt müssen wir feststellen, wie viele Balken wir von dem Symbol/der Periode existieren, für die das resultierende Pufferobjekt erstellt wurde. Wir benötigen diesen Wert, um festzustellen, wie viele Daten des Standardindikators wir in das dafür vorgesehene Pufferobjekt kopiert haben. Dazu holen wir uns die erforderliche Zeitreihe nach dem Wert für Symbol- und Zeitrahmen des Pufferobjekts und die Menge der verfügbaren Daten.
Danach rufen wir die Methode PreparingDataBufferStdInd() auf, um die erforderliche Datenmenge vom Indikator-Handle in das berechnete Pufferobjekt zu kopieren.
Wenn die Menge der kopierten Daten geringer ist als erforderlich, wird der Variablen res falsch zugewiesen. Wenn mindestens eines der vorhandenen Pufferobjekte nicht in der erforderlichen Menge kopiert wird, wird der Variablen res false zugewiesen. Und von dieser Methode wird der Variablenwert zurückgegeben. Die Methode gibt true zurück, wenn alle Daten aller berechneten Pufferobjekte erfolgreich kopiert wurden.

Bislang kopiert die Methode alle vorhandenen Daten aus dem Indikator-Handle in den Berechnungspuffer. Das Kopieren großer Datenmengen für mehr als einen Indikator bei jedem Tick ist natürlich eher unpraktisch. Da ich aber gerade die Funktionalität für die Arbeit mit Standardindikatoren schaffe, lasse ich dieses Verhalten vorerst so wie es ist. Die Logik ist im Moment wichtiger als die Geschwindigkeit. Später werde ich dafür sorgen, dass die Daten nur unter den notwendigen Bedingungen kopiert werden (erster Start, Änderungen in den Verlaufsdaten). In anderen Fällen soll nur die erforderliche Datenmenge (ein oder zwei Balken) kopiert werden.

Alle Methoden zur Erstellung von Standard-Indikator-Handles und zugehörigen Puffern habe ich bereits im vorherigen Artikel erklärt. Aber ich habe sie noch nicht implementiert (mit Ausnahme von zwei Methoden zur Erstellung der Indikatoren AC und AD). Heute werde ich die Methoden zur Erstellung der Handles der Standardindikatoren und ihren Pufferobjekten für diejenigen implementieren, die über einen einzigen Indikator-Puffer verfügen, und der Indikator zeichnet die Daten im Haupt-Unterfenster des Charts von sich aus.

Außerdem möchte ich, dass die Farbe der erstellten Indikatoren standardmäßig der Farbe der entsprechenden Standardindikatoren entspricht. Wir werden weiterhin in der Lage sein, unsere eigenen Farben für diese Pufferobjekte festzulegen. Dazu müssen wir bei der Berechnung von Puffern in der Hauptschleife einfach den Index der erforderlichen Farbe übergeben.

Betrachten wir die Änderungen, die an der Methode zum Erstellen von Puffern für den Standardindikator Accelerator Oscillator vorgenommen wurden:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period AC                              |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iAC(symbol,timeframe);
   int identifier=(id==WRONG_VALUE ? IND_AC : id);
   color array_colors[3]={clrGreen,clrRed,clrGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the histogram buffer from the zero line
      this.CreateHistogram();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetShowData(true);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AC);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accelerator Oscillator");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Da der Standardindikator Accelerator Oszillator zwei Farben zur Darstellung seiner Werte besitzt, deklarieren wir das Farbfeld mit einer Größe von 3, das sofort mit drei Farben initialisiert wird. Warum drei? Weil die Farbe mit dem Index von 0 aufsteigende Werte der Indikatorzeile anzeigt, während die Farbe mit dem Index von 1 absteigende Werte anzeigt. Aber wir brauchen noch eine weitere Farbe, die gleiche Werte von zwei benachbarten Balken der Indikatorlinie anzeigt. Im Standardindikator Accelerator Oszillator werden sie durch die Farbe der aufsteigenden Werte angezeigt. Daher benötigen wir drei Farben.
Nach dem Erstellen des gezeichneten Pufferobjekts setzen wir seine Zeichenfarben aus diesem Array.

Wenn der Zeiger auf das zuletzt erstellte Pufferobjekt erhalten wird, kann der Zeiger fälschlicherweise erhalten werden. Bisher wurde diese Situation nicht berücksichtigt, was eine potentielle Gefahr darstellte — der Zugriff mit einem ungültigen Zeiger führt zu einem Programmabsturz.
Deshalb müssen wir in diesem Fall die Methode verlassen und INVALID_HANDLE zurückgeben.

Für Standardindikatoren, die nur eine Zeichnungsfarbe haben, werde ich den Farbarray mit nur einem Element deklarieren. Das ist alles, was die Methoden zum Erstellen von Handles farbiger Standardindikatoren und ihren Pufferobjekten von den Methoden zur Erstellung monochromer Standardindikatoren unterscheidet.

Unten ist zum Beispiel die Methode zum Erstellen des Standardindikators "Average True Range" (ATR) aufgeführt:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period ATR                             |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iATR(symbol,timeframe,ma_period);
   int identifier=(id==WRONG_VALUE ? IND_ATR : id);
   color array_colors[1]={clrLightSeaGreen};
   CBuffer *buff=NULL;
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetShowData(true);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
      buff.SetColors(array_colors);
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      if(buff==NULL)
         return INVALID_HANDLE;
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_ATR);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("ATR("+symbol+","+TimeframeDescription(timeframe)+": "+(string)ma_period+")");
      buff.SetIndicatorName("Average True Range");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Heute werde ich die Methoden für Standardindikatoren mit nur einem Puffer, die in einem Unterfenster gezeichnet werden, implementieren, nämlich

Die oben aufgeführten Methoden zur Erstellung von Mehrsymbol- und Mehrperioden-Indikatoren wurden bereits erstellt. Es macht keinen Sinn, sie hier zu besprechen. Sie sind identisch mit den betrachteten Methoden und werden in den folgenden Anhängen zur Verfügung gestellt.

Sie haben wahrscheinlich bemerkt, dass die obige Liste keinen Market Facilitation Index enthält, der in einem Unterfenster gezeichnet ist und einen einzelnen zu zeichnenden Puffer aufweist. Auf den ersten Blick sind beide Bedingungen erfüllt. Um die Farbe des Histogrammstabs zu berechnen, ist jedoch noch ein weiterer Berechnungspuffer erforderlich — der Puffer Volumenindikator. Daher wird dieser Indikator als ein Multi-Puffer in einem Unterfenster besprochen.

Die Methode PreparingDataBufferStdInd() füllt das Array des Berechnungspufferobjekts mit Daten aus dem Indikator-Handle. Ich habe diese Methode bereits im vorigen Artikel betrachtet, obwohl sie damals nur das Füllen des AC-Indikator-Puffers implementierte:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
      return 0;
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Am interessantesten ist, dass exakt die gleichen Aktionen für andere Single-Buffer Indikatoren durchgeführt werden sollten. Der Operator switch kommt uns hier zu Hilfe, der verwendet wird, um die Werte des Ausdrucks mit Konstanten in allen Varianten nach case zu vergleichen und die Kontrolle an den Operator zu übergeben, der dem Ausdruckswert entspricht. Jede Variante endet entweder durch break oder return. Wenn keiner der Operatoren innerhalb des Falles gesetzt ist, wird die Kontrolle an den nächsten Fall übergeben, nachdem der Ausdruckswert in dem notwendigen Fall behandelt wurde.
Anstatt ähnliche notwendige Operationen in alle Fälle zu kopieren, die identische Operationen durchführen, ist es daher sinnvoller, einfach alle diese Fälle mit dem Rückgabe- oder Abbruchoperator zu kombinieren:

//+------------------------------------------------------------------+
//| Prepare the calculated buffer data                               |
//| of the specified standard indicator                              |
//+------------------------------------------------------------------+
int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy)
  {
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return 0;
     }
   CBufferCalculate *buffer=NULL;
   int copies=WRONG_VALUE;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return 0;
        copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy);
        return copies;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
 
      default:
        break;
     }
   return 0;
  }
//+------------------------------------------------------------------+

Machen Sie dasselbe bei der Methode zum Löschen von Pufferdaten des angegebenen Standardindikators:

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(std_ind,id);
   if(list==NULL || list.Total()==0)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      //--- Single-buffer standard indicators in a subwindow
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

und in der Methode zum Setzen des Wertes für die Pufferdaten des angegebenen Standardindikators:

//+------------------------------------------------------------------+
//| Set values for the current chart to the specified buffer         |
//| of the standard indicator by the timeseries index according to   |
//| the buffer object symbol/period                                  |
//+------------------------------------------------------------------+
bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE)
  {
//--- Get the list of buffer objects by type and ID
   CArrayObj *list=this.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ));
      return false;
     }
//--- Get the list of drawn objects with ID
   CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Get the list of calculated buffers with ID
   CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL);
   list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL);
//--- Exit if any of the lists is empty
   if(list_data.Total()==0 || list_calc.Total()==0)
      return false;
//--- Declare the necessary objects and variables
   CBuffer *buffer_data=NULL;
   CBuffer *buffer_calc=NULL;
   int index_period=0;
   int series_index_start=0;
   int num_bars=1,index=0;
   datetime time_period=0;
   double value0=EMPTY_VALUE, value1=EMPTY_VALUE;

//--- Depending on the standard indicator type
   switch((int)ind_type)
     {
      //--- Single-buffer standard indicators
      case IND_AC       :
      case IND_AD       :
      case IND_AO       :
      case IND_ATR      :
      case IND_BEARS    :
      case IND_BULLS    :
      case IND_CHAIKIN  :
      case IND_CCI      :
      case IND_DEMARKER :
      case IND_FORCE    :
      case IND_MOMENTUM :
      case IND_MFI      :
      case IND_OSMA     :
      case IND_OBV      :
      case IND_RSI      :
      case IND_STDDEV   :
      case IND_TRIX     :
      case IND_VOLUMES  :
      case IND_WPR      :

        //--- Get drawn and calculated buffer objects
        buffer_data=list_data.At(0);
        buffer_calc=list_calc.At(0);
        if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false;
        
        //--- Find the bar index corresponding to the current bar start time
        index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true);
        if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false;
        //--- Get the value by the index from the indicator buffer
        value0=buffer_calc.GetDataBufferValue(0,index_period);
        if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period())
          {
           series_index_start=series_index;
           num_bars=1;
          }
        else
          {
           //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol
           time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period);
           if(time_period==0) return false;
           //--- Get the appropriate current chart bar
           series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true);
           if(series_index_start==WRONG_VALUE) return false;
           //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data
           num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT);
           if(num_bars==0) num_bars=1;
          }
        //--- Take values to calculate colors
        value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars));
        //--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index
        //--- and set the color of the drawn buffer depending on the value0 and value1 values ratio
        for(int i=0;i<num_bars;i++)
          {
           index=series_index_start-i;
           buffer_data.SetBufferValue(0,index,value0);
           buffer_data.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2) : color_index);
          }
        return true;
      
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_BANDS :
        break;
      case IND_BWMFI :
        break;
      case IND_DEMA :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_VIDYA :
        break;
      
      default:
        break;
     }
   return false;
  }
//+------------------------------------------------------------------+

Ich habe alle drei Methoden im vorigen Artikel besprochen. Im aktuellen Artikel habe ich einfach die Fälle kombiniert, in denen derselbe Handler erstellt wird.
Ganz am Ende des Handlers ist der Rückgabe- oder Abbruchoperator erforderlich, damit die Behandlung des Umschaltwertes nicht auf andere Fälle mit einem anderen Handler übergeht.

Jetzt müssen wir die Hauptobjektklasse der CEngine-Bibliothek in \MQL5\Include\DoEasy\Engine.mqh verbessern.

Die Methoden zur Erstellung von Standardindikatoren und Pufferobjekten für diese werden in der Klasse der Pufferkollektion festgelegt (einige Methoden, die bisher noch nicht berücksichtigt wurden, geben jetzt einfach INVALID_HANDLE zurück), und wir müssen den Zugriff auf all diese Methoden für das Programm in der Klasse CEngine festlegen.

Schreiben wir in den 'public' Teil der Klasse den Aufruf all dieser Methoden:
//--- The methods of creating standard indicators and buffer objects for them
//--- Create the standard Accelerator Oscillator indicator
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);                    }
//--- Create the standard Accumulation/Distribution indicator
   bool                 BufferCreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateAD(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);     }
//--- Create the standard ADX indicator
   bool                 BufferCreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADX(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);        }
//--- Create the standard ADX Wilder indicator
   bool                 BufferCreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id)
                          { return(this.m_buffers.CreateADXWilder(symbol,timeframe,adx_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Alligator indicator
   bool                 BufferCreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                              const int jaw_period,const int jaw_shift,
                                              const int teeth_period,const int teeth_shift,
                                              const int lips_period,const int lips_shift,
                                              const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE applied_price,const int id)
                          {
                           return(this.m_buffers.CreateAlligator(symbol,timeframe,
                                                                 jaw_period,jaw_shift,
                                                                 teeth_period,teeth_shift,
                                                                 lips_period,lips_shift,
                                                                 ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Adaprive Moving Average indicator
   bool                 BufferCreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                        const int ama_period,
                                        const int fast_ema_period,
                                        const int slow_ema_period,
                                        const int ama_shift,
                                        const ENUM_APPLIED_PRICE applied_price,
                                        const int id)
                          { 
                           return(this.m_buffers.CreateAMA(symbol,timeframe,
                                                           ama_period,
                                                           fast_ema_period,slow_ema_period,
                                                           ama_shift,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Awesome Oscillator indicator
   bool                 BufferCreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAO(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard Average True Range indicator
   bool                 BufferCreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateATR(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bollinger Bands indicator
   bool                 BufferCreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                          const int bands_period,
                                          const int bands_shift,
                                          const double deviation,
                                          const ENUM_APPLIED_PRICE applied_price,
                                          const int id)
                          {
                           return(this.m_buffers.CreateBands(symbol,timeframe,
                                                             bands_period,bands_shift,
                                                             deviation,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Bears Power indicator
   bool                 BufferCreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBearsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Bulls Power indicator
   bool                 BufferCreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateBullsPower(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Chaikin Oscillator indicator
   bool                 BufferCreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          {
                           return(this.m_buffers.CreateChaikin(symbol,timeframe,
                                                                fast_ma_period,slow_ma_period,
                                                                ma_method,applied_volume,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Commodity Channel Index indicator
   bool                 BufferCreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateCCI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DEMA indicator
   bool                 BufferCreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateDEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard DeMarker indicator
   bool                 BufferCreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateDeMarker(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Envelopes indicator
   bool                 BufferCreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id)
                          {
                           return(this.m_buffers.CreateEnvelopes(symbol,timeframe,
                                                                  ma_period,ma_shift,ma_method,
                                                                  applied_price,deviation,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Force Index indicator
   bool                 BufferCreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateForce(symbol,timeframe,ma_period,ma_method,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Fractals indicator
   bool                 BufferCreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateFractals(symbol,timeframe,id)!=INVALID_HANDLE);  }
//--- Create the standard FrAMA indicator
   bool                 BufferCreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateFrAMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Gator indicator
   bool                 BufferCreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          {
                           return(this.m_buffers.CreateGator(symbol,timeframe,
                                                              jaw_period,jaw_shift,
                                                              teeth_period,teeth_shift,
                                                              lips_period,lips_shift,
                                                              ma_method,applied_price,id)!=INVALID_HANDLE);
                          }
//--- Create the standard Ichimoku indicator
   bool                 BufferCreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id)
                          { return(this.m_buffers.CreateIchimoku(symbol,timeframe,tenkan_sen,kijun_sen,senkou_span_b,id)!=INVALID_HANDLE);  }
//--- Create the standard BW MFI indicator
   bool                 BufferCreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateBWMFI(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Momentum indicator
   bool                 BufferCreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMomentum(symbol,timeframe,mom_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Money Flow Index indicator
   bool                 BufferCreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateMFI(symbol,timeframe,ma_period,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average indicator
   bool                 BufferCreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMA(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Moving Average of Oscillator indicator
   bool                 BufferCreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateOsMA(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard MACD indicator
   bool                 BufferCreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateMACD(symbol,timeframe,fast_ema_period,slow_ema_period,signal_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard On Balance Volume indicator
   bool                 BufferCreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id)
                          { return(this.m_buffers.CreateOBV(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
//--- Create the standard Parabolic SAR indicator
   bool                 BufferCreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id)
                          { return(this.m_buffers.CreateSAR(symbol,timeframe,step,maximum,id)!=INVALID_HANDLE);  }
//--- Create the standard Relative Strength Index indicator
   bool                 BufferCreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateRSI(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard RVI indicator
   bool                 BufferCreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id)
                          { return(this.m_buffers.CreateRVI(symbol,timeframe,ma_period,id)!=INVALID_HANDLE);  }
//--- Create the standard Standart Deviation indicator
   bool                 BufferCreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateStdDev(symbol,timeframe,ma_period,ma_shift,ma_method,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Stochastic indicator
   bool                 BufferCreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id)
                          { return(this.m_buffers.CreateStochastic(symbol,timeframe,Kperiod,Dperiod,slowing,ma_method,price_field,id)!=INVALID_HANDLE);  }
//--- Create the standard TEMA indicator
   bool                 BufferCreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTEMA(symbol,timeframe,ma_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Triple Exponential Average indicator
   bool                 BufferCreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateTriX(symbol,timeframe,ma_period,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard William's Percent Range indicator
   bool                 BufferCreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id)
                          { return(this.m_buffers.CreateWPR(symbol,timeframe,calc_period,id)!=INVALID_HANDLE);  }
//--- Create the standard VIDYA indicator
   bool                 BufferCreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id)
                          { return(this.m_buffers.CreateVIDYA(symbol,timeframe,cmo_period,ema_period,ma_shift,applied_price,id)!=INVALID_HANDLE);  }
//--- Create the standard Volumes indicator
   bool                 BufferCreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id)
                          { return(this.m_buffers.CreateVolumes(symbol,timeframe,applied_volume,id)!=INVALID_HANDLE);  }
                          
//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object

Die Methoden in der Kollektionsklasse geben den Typ int zurück, genauer gesagt das Handle des angelegten Standardindikators. Hier werde ich die Rückgabe des Wertes vom Typ bool implementieren. Dies vereinfacht die Arbeit mit den Methoden aus dem endgültigen Programm. Daher liefern diese Methoden das Ergebnis des Vergleichs des von der entsprechenden Methode der Kollektionsklasse der Puffer zurückgegebenen Wertes mit dem Wert INVALID_HANDLE.

Schreiben Sie im Abschnitt über die öffentliche Klasse zwei Methoden — die Methode zur Vorbereitung der berechneten Pufferdaten für alle erstellten Standardindikatoren, die das Ergebnis der gleichnamigen Methode der Kollektionsklasse der Objekte von Indikatorpuffer zurückgibt, die ich oben betrachtet habe, und deklarieren Sie die Methode, die die Beschreibung des Pufferobjekts durch den Standardindikatortyp und seine ID zurückgibt:

//--- Prepare data of the calculated buffer of all created standard indicators
   bool                 BufferPreparingDataAllBuffersStdInd(void)                         { return this.m_buffers.PreparingDataAllBuffersStdInd();    }

//--- Return the standard indicator buffer description by type and ID
   string               BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id);

//--- Display short description of all indicator buffers of the buffer collection
   void                 BuffersPrintShort(void);

Implementieren Sie die Methode außerhalb des Hauptteils der Klasse:

//+------------------------------------------------------------------+
//| Return the standard indicator buffer description                 |
//| by type and ID                                                   |
//+------------------------------------------------------------------+
string CEngine::BufferGetLabelByTypeID(const ENUM_INDICATOR ind_type,const int id)
  {
   CArrayObj *list=m_buffers.GetListBufferByTypeID(ind_type,id);
   if(list==NULL || list.Total()==0)
      return "";
   CBuffer *buff=list.At(0);
   if(buff==NULL)
      return "";
   return buff.Label();
  }
//+------------------------------------------------------------------+

Hier ist alles einfach: Wir holen uns die Liste der Pufferobjekte mit dem entsprechenden (der Methode übergebenen) Standardindikatortyp und seiner ID.
Es wird also zwei solche Pufferobjekte in der Liste geben — ein zu zeichnender und ein Berechnungspuffer. Es ist nicht notwendig, sich um das genaue Objekt zu kümmern, von dem Daten empfangen werden sollen, da beide Objekte ähnliche Daten enthalten. Daher erhalten wir den Zeiger auf das erste Pufferobjekt in der Liste und geben seine Beschreibung zurück.

Während wir die Methode OnDoEasyEvent() im Ereignisbehandlungsblock "Neuer Balken" implementieren, sollten wir die Menge der kopierten Daten leicht korrigieren:

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

Zuvor erhielten wir in der hervorgehobenen Zeichenfolge den Mindestwert aus Gesamtmenge der Daten des Pufferobjekts und berechneten Daten des Standardindikators:

int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());

Da jedoch die Anzahl der verwendeten Zeitreihendaten in der Bibliothek (und damit im Programm) auf 1000 Balken eingestellt ist, werden wir jetzt den Mindestwert aus drei Datenquellen kopieren — Gesamtzahl der Daten des Berechnungspuffers, berechnete Standardindikatordaten und die Menge der Standarddaten (1000). Normalerweise ist der Wert von 1000 immer kleiner als die beiden anderen, und dies ist beim massenhaften Kopieren von Daten vorteilhafter.

Damit sind die aktuellen Verbesserungen der Bibliotheksklassen abgeschlossen.

Test

Um das Erstellen eines Multi-Symbol-Multi-Perioden-Standardindikatoren mit einem einzelnen Puffer zu testen, die in einem Unterfenster gezeichnet werden, werde ich wie folgt vorgehen:
Ich erstelle nutzerdefinierten Indikator mit der folgenden Auswahl in den Einstellungen:

  1. das verwendete Symbol, das der Standardindikator verwendet
  2. der verwendeter Chart-Zeitrahmen, den der Standardindikator verwendet
  3. den Typ des erstellten Multi-Symbol-Multi-Perioden-Standardindikators mit einem Puffer

Die Daten des ausgewählten Standardindikators werden im Unterfenster des aktuellen Symbol/Perioden-Hauptcharts angezeigt.

Verwenden wir den Indikator aus dem vorherigen Artikel und speichern ihn in \MQL5\Indikatoren\TestDoEasy\Teil48\ als TestDoEasyPart48.mq5.

In den Indikatoreingaben fügen wir den Parameter hinzu, der es uns erlaubt, einen Standardindikator auszuwählen, der angezeigt werden soll:

//|                                             TestDoEasyPart48.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>
//--- properties
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1

//--- classes

//--- enums

//--- defines

//--- structures

//--- input variables
sinput   string               InpUsedSymbols    =  "GBPUSD";      // Used symbol (one only)
sinput   ENUM_TIMEFRAMES      InpPeriod         =  PERIOD_M30;    // Used chart period
sinput   ENUM_INDICATOR       InpIndType        =  IND_AC;        // Type standard indicator
//---
sinput   bool                 InpUseSounds      =  true;          // Use sounds
//--- indicator buffers

//--- global variables
ENUM_SYMBOLS_MODE    InpModeUsedSymbols=  SYMBOLS_MODE_DEFINES;   // Mode of used symbols list
ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;   // Mode of used timeframes list
string               InpUsedTFs;                                  // List of used timeframes
CEngine              engine;                                      // CEngine library main object
string               prefix;                                      // Prefix of graphical object names
int                  min_bars;                                    // The minimum number of bars for the indicator calculation
int                  used_symbols_mode;                           // Mode of working with symbols
string               array_used_symbols[];                        // The array for passing used symbols to the library
string               array_used_periods[];                        // The array for passing used timeframes to the library
//+------------------------------------------------------------------+

Nur Standardindikatoren mit einem Puffer für die Konstruktion, die in einem Unterfenster angezeigt werden, funktionieren mit dem nutzerdefinierten Indikator. In OnInit() werden Standardindikatoren eines geeigneten Typs in der Bibliothek erstellt. Implementieren wir die Fehlermeldung für die übrigen Indikatoren:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable
   InpUsedTFs=TimeframeDescription(InpPeriod);
//--- Initialize DoEasy library
   OnInitDoEasy();
   
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   //--- calculate the number of bars of the current period fitting in the maximum used period
   //--- Use the obtained value if it exceeds 2, otherwise use 2
   int num_bars=NumberBarsInTimeframe(InpPeriod);
   min_bars=(num_bars>2 ? num_bars : 2);

//--- Check and remove remaining indicator graphical objects
   if(IsPresentObectByPrefix(prefix))
      ObjectsDeleteAll(0,prefix);

//--- Create the button panel

//--- Check playing a standard sound using macro substitutions
   engine.PlaySoundByDescription(SND_OK);
//--- Wait for 600 milliseconds
   engine.Pause(600);
   engine.PlaySoundByDescription(SND_NEWS);

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing a selected standard indicator
   bool success=false;
   switch(InpIndType)
     {
      case IND_AC       :  success=engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_AD       :  success=engine.BufferCreateAD(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                    break;
      case IND_AO       :  success=engine.BufferCreateAO(InpUsedSymbols,InpPeriod,1);                                break;
      case IND_ATR      :  success=engine.BufferCreateATR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_BEARS    :  success=engine.BufferCreateBearsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_BULLS    :  success=engine.BufferCreateBullsPower(InpUsedSymbols,InpPeriod,13,1);                     break;
      case IND_CHAIKIN  :  success=engine.BufferCreateChaikin(InpUsedSymbols,InpPeriod,3,10,MODE_EMA,VOLUME_TICK,1); break;
      case IND_CCI      :  success=engine.BufferCreateCCI(InpUsedSymbols,InpPeriod,14,PRICE_TYPICAL,1);              break;
      case IND_DEMARKER :  success=engine.BufferCreateDeMarker(InpUsedSymbols,InpPeriod,14,1);                       break;
      case IND_FORCE    :  success=engine.BufferCreateForce(InpUsedSymbols,InpPeriod,13,MODE_SMA,VOLUME_TICK,1);     break;
      case IND_MOMENTUM :  success=engine.BufferCreateMomentum(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);           break;
      case IND_MFI      :  success=engine.BufferCreateMFI(InpUsedSymbols,InpPeriod,14,VOLUME_TICK,1);                break;
      case IND_OSMA     :  success=engine.BufferCreateOsMA(InpUsedSymbols,InpPeriod,12,26,9,PRICE_CLOSE,1);          break;
      case IND_OBV      :  success=engine.BufferCreateOBV(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);                   break;
      case IND_RSI      :  success=engine.BufferCreateRSI(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);                break;
      case IND_STDDEV   :  success=engine.BufferCreateStdDev(InpUsedSymbols,InpPeriod,20,0,MODE_SMA,PRICE_CLOSE,1);  break;
      case IND_TRIX     :  success=engine.BufferCreateTriX(InpUsedSymbols,InpPeriod,14,PRICE_CLOSE,1);               break;
      case IND_WPR      :  success=engine.BufferCreateWPR(InpUsedSymbols,InpPeriod,14,1);                            break;
      case IND_VOLUMES  :  success=engine.BufferCreateVolumes(InpUsedSymbols,InpPeriod,VOLUME_TICK,1);               break;
      default:
        break;
     }
   if(!success)
     {
      Print(TextByLanguage("Ошибка. Индикатор не создан","Error. Indicator not created"));
      return INIT_FAILED;
     }
//--- Check the number of buffers specified in the 'properties' block

Jeder der in einem Unterfenster gezeichneten Standardindikatoren hat seine eigene Anzahl von angezeigten Dezimalstellen. Außerdem zeichnen einige von ihnen auch Niveaulinien. Lassen Sie uns für jeden der verwendeten Standardindikatoren einen individuellen Satz erstellen, in dem wir die Dezimalkapazität der angezeigten Daten angeben und Ebenen definieren sollen, sofern der Standardindikator über diese verfügt:

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();
//--- Set levels where they are required and define the data decimal capacity
   int digits=(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS);
   switch(InpIndType)
     {
      case IND_AC       : digits+=2;   break;
      case IND_AD       : digits=0;    break;
      case IND_AO       : digits+=1;   break;
      case IND_ATR      :              break;
      case IND_BEARS    : digits+=1;   break;
      case IND_BULLS    : digits+=1;   break;
      case IND_CHAIKIN  : digits=0;    break;
      case IND_CCI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,100);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-100);
        digits=2;
        break;
      case IND_DEMARKER :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,0.7);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,0.3);
        digits=3;
        break;
      case IND_FORCE    : digits+=1;   break;
      case IND_MOMENTUM : digits=2;    break;
      case IND_MFI      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,20);
        break;
      case IND_OSMA     : digits+=2;   break;
      case IND_OBV      : digits=0;    break;
      case IND_RSI      :
        IndicatorSetInteger(INDICATOR_LEVELS,3);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,70);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,50);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,2,30);
        digits=2;
        break;
      case IND_STDDEV   : digits+=1;   break;
      case IND_TRIX     :              break;
      case IND_WPR      :
        IndicatorSetInteger(INDICATOR_LEVELS,2);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,0,-80);
        IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-20);
        digits=2;
        break;
      case IND_VOLUMES  : digits=0;    break;
      
      default:
        IndicatorSetInteger(INDICATOR_LEVELS,0);
        break;
     }
//--- Set the short name for the indicator and bit depth
   string label=engine.BufferGetLabelByTypeID(InpIndType,1);
   IndicatorSetString(INDICATOR_SHORTNAME,label);
   IndicatorSetInteger(INDICATOR_DIGITS,digits);
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

OnCalculate() wurde im Vergleich zum vorherigen Testindikator stark vereinfacht. Das liegt daran, dass ich die notwendigen Methoden für die Arbeit mit Standardindikatoren geschaffen habe, und alles, was wir brauchen, ist die Datenvorbereitung des Standardindikators und die berechneten Daten des Standardindikators auf dem aktuellen Chart in der Hauptschleife anzeigen:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      engine.EventsHandling();      // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
      engine.BufferPreparingDataAllBuffersStdInd();
     }
   
//--- Prepare data 
//--- Fill in calculated buffers of all created standard indicators with data
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   if(!engine.BufferPreparingDataAllBuffersStdInd())
      return 0;

//--- Calculate the indicator
//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(InpIndType,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Das ist alles, was für die Erstellung des Testindikators getan werden muss.
Der vollständige Code des Indikators ist in den unten angehängten Dateien enthalten.

Kompilieren Sie den erstellten Indikator, setzen Sie GBPUSD M5 in den Einstellungen und starten Sie den Indikator auf EURUSD M1:


Die Abbildung zeigt nicht alle möglichen Indikatoren, aber die wichtigsten werden angezeigt: Der Indikator zeichnet einen ausgewählten Standardindikator, der auf Daten basiert, die sich von den aktuellen Chart-Daten unterscheiden. Die Niveaulinien werden dort gezeichnet, wo sie benötigt werden.

Was kommt als Nächstes?

Im nächsten Artikel werde ich damit beginnen, die Methoden für die Erstellung von Standardindikatoren, die Anzeige von Daten im Hauptfenster des Charts und Indikatoren mit mehreren gezeichneten Puffern zu implementieren.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Testindikatoren angehängt, die Sie testen und herunterladen können.
Hinterlassen Sie Ihre Fragen, Kommentare und Anregungen in den Kommentaren.
Bitte bedenken Sie, dass ich hier den MQL5-Testindikator für MetaTrader 5 entwickelt habe.
Die angehängten Dateien sind nur für MetaTrader 5 bestimmt. Die aktuelle Bibliotheksversion wurde nicht mit dem MetaTrader 4 getestet.
Nachdem ich die Funktionalität für die Arbeit mit Indikatorpuffern entwickelt und getestet habe, werde ich versuchen, einige MQL5-Funktionen in MetaTrader 4 zu implementieren.

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