Zeitreihen in der Bibliothek DoEasy (Teil 40): Bibliotheksbasierte Indikatoren - Aktualisierung der Daten in Echtzeit

5 August 2020, 14:54
Artyom Trishkin
0
153

Inhalt

Konzept

Im haben wir die Arbeit der DoEasy-Bibliothek mit Indikatoren ins Auge gefasst. Diese Art von Programmen erfordert eine etwas andere Herangehensweise an die Erstellung und Aktualisierung von Zeitreihen aufgrund einiger Merkmale einer ressourcensparenden Berechnung in Indikatoren und Einschränkungen bei der Beschaffung von Daten für die aktuelle Symbol- und Diagrammperiode in einem Indikator, der auf demselben Diagramm gestartet wurde.

Es ist uns gelungen, die korrekte Anforderung und das korrekte Laden von Verlaufsdaten zu erreichen. Jetzt müssen wir die Funktionalität für die Echtzeit-Aktualisierung aller Daten aus allen im Indikator verwendeten Zeitreihen entwickeln (wir gehen davon aus, dass der Indikator mehrperiodig ist und Daten für seinen Betrieb aus den angegebenen Chartzeiträumen erhält, in denen er gestartet wird).

Während der Konstruktion der Indikatorpufferdaten bewegten wir uns in der Schleife von dem Balken mit der angegebenen historischen Datentiefe zum aktuellen (Null-)Balken. Die einfachste Lösung ist hier, die Daten über den Schleifenindex zu nehmen — die Daten wurden bereits im Zeitreihenobjekt der Bibliothek erstellt, so dass uns nichts daran hindert, sie per Index abzurufen. Dies funktioniert jedoch nur beim Konstruieren statischer Daten. Während der Echtzeit-Datenaktualisierung in den Zeitreihenlisten stehen wir vor dem Problem der Indizierung nach Balkenzahl. Wenn ein neuer Balken zur Liste der Zeitreihen hinzugefügt wird, hat der neue Balken den Index 0, da der neue Balken zum aktuellen Null-Balken wird. Die Indizes aller vorherigen Balken in der Zeitreihenliste sollen um 1 erhöht werden. Daher müssen wir jedes Mal, wenn ein neuer Balken im Diagramm geöffnet wird, diesen neu erschienenen Balken der Zeitreihenliste hinzufügen und die Anzahl aller anderen Balken in der aktualisierten Zeitreihenliste um 1 erhöhen.

Dies ist äußerst unpraktisch. Stattdessen sollten wir mit der Zeit des Balkens in der Liste der Zeitreihen arbeiten — die Eröffnungszeit jedes Balkens in der Liste der Zeitreihen bleibt immer gleich. Die Balkenzeit ist der Startpunkt in jedem Verweis auf einen der Balken in der Zeitreihensammlung der Bibliothek. Ich werde auch die Indexierung nach Balkennummern belassen. Aber die Balkennummer können wir nicht aus den Eigenschaften des Balkens abrufen. Stattdessen soll bei der Anforderung eines Balkens durch den Zeitreihenindex die Balkenzeit durch den angeforderten Index berechnet werden, und der erforderliche Balken soll von der Zeitreihenliste durch die berechnete Balkenzeit für seine weitere Verwendung erhalten werden.

Verbesserung der Zeitreihenklassen

In \MQL5\Include\DoEasy\Defines.mqh, entfernen wir die Eigenschaft der Balkenindices aus der Enumeration der ganzzahligen Eigenschaften des Objektes:

//+------------------------------------------------------------------+
//| Bar integer properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_INTEGER
  {
   BAR_PROP_INDEX = 0,                                      // Bar index in timeseries
   BAR_PROP_TYPE,                                           // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)

Ersetzen wir sie durch die Eigenschaft "Bar time" und vermindern die Anzahl der ganzzahligen Eigenschaften des Objektes um 1 (von 14 auf 13):

//+------------------------------------------------------------------+
//| Bar integer properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BAR_PROP_INTEGER
  {
   BAR_PROP_TIME = 0,                                       // Bar period start time
   BAR_PROP_TYPE,                                           // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)
   BAR_PROP_PERIOD,                                         // Bar period (timeframe)
   BAR_PROP_SPREAD,                                         // Bar spread
   BAR_PROP_VOLUME_TICK,                                    // Bar tick volume
   BAR_PROP_VOLUME_REAL,                                    // Bar exchange volume

   BAR_PROP_TIME_DAY_OF_YEAR,                               // Bar day serial number in a year
   BAR_PROP_TIME_YEAR,                                      // A year the bar belongs to
   BAR_PROP_TIME_MONTH,                                     // A month the bar belongs to
   BAR_PROP_TIME_DAY_OF_WEEK,                               // Bar week day
   BAR_PROP_TIME_DAY,                                       // Bar day of month (number)
   BAR_PROP_TIME_HOUR,                                      // Bar hour
   BAR_PROP_TIME_MINUTE,                                    // Bar minute
  }; 
#define BAR_PROP_INTEGER_TOTAL (13)                         // Total number of integer bar properties
#define BAR_PROP_INTEGER_SKIP  (0)                          // Number of bar properties not used in sorting
//+------------------------------------------------------------------+

Dementsprechend müssen wir bei der Enumeration möglicher Sortierkriterien der Balken die Sortierung nach dem Index entfernen und durch die Sortierung nach der Zeit der Balken ersetzen:

//+------------------------------------------------------------------+
//| Possible bar sorting criteria                                    |
//+------------------------------------------------------------------+
#define FIRST_BAR_DBL_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP)
#define FIRST_BAR_STR_PROP          (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BAR_MODE
  {
//--- Sort by integer properties
   SORT_BY_BAR_TIME = 0,                                    // Sort by bar period start time
   SORT_BY_BAR_TYPE,                                        // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration)
   SORT_BY_BAR_PERIOD,                                      // Sort by bar period (timeframe)
   SORT_BY_BAR_SPREAD,                                      // Sort by bar spread
   SORT_BY_BAR_VOLUME_TICK,                                 // Sort by bar tick volume
   SORT_BY_BAR_VOLUME_REAL,                                 // Sort by bar exchange volume
   SORT_BY_BAR_TIME_DAY_OF_YEAR,                            // Sort by bar day number in a year
   SORT_BY_BAR_TIME_YEAR,                                   // Sort by a year the bar belongs to
   SORT_BY_BAR_TIME_MONTH,                                  // Sort by a month the bar belongs to
   SORT_BY_BAR_TIME_DAY_OF_WEEK,                            // Sort by a bar week day
   SORT_BY_BAR_TIME_DAY,                                    // Sort by a bar day
   SORT_BY_BAR_TIME_HOUR,                                   // Sort by a bar hour
   SORT_BY_BAR_TIME_MINUTE,                                 // Sort by a bar minute
//--- Sort by real properties
   SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP,                   // Sort by bar open price
   SORT_BY_BAR_HIGH,                                        // Sort by the highest price for the bar period
   SORT_BY_BAR_LOW,                                         // Sort by the lowest price for the bar period
   SORT_BY_BAR_CLOSE,                                       // Sort by a bar close price
   SORT_BY_BAR_CANDLE_SIZE,                                 // Sort by a candle price
   SORT_BY_BAR_CANDLE_SIZE_BODY,                            // Sort by a candle body size
   SORT_BY_BAR_CANDLE_BODY_TOP,                             // Sort by a candle body top
   SORT_BY_BAR_CANDLE_BODY_BOTTOM,                          // Sort by a candle body bottom
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP,                       // Sort by candle upper wick size
   SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN,                     // Sort by candle lower wick size
//--- Sort by string properties
   SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP,                 // Sort by a bar symbol
  };
//+------------------------------------------------------------------+

Neuerstellen der Klasse CBar in \MQL5\Include\DoEasy\Objects\Series\Bar.mqh, um mit der Balkenzeit zu arbeiten.

Zuvor setzte die Methode SetSymbolPeriod() ein bestimmtes Symbol, eine Diagrammperiode und einen Balkenindex für ein Balkenobjekt. Jetzt wird der Index durch die Balkenzeit ersetzt:

//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);

Korrigieren wir auch die Implementierung der Methode:

//+------------------------------------------------------------------+
//| Set bar symbol, timeframe and index                              |
//+------------------------------------------------------------------+
void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   this.SetProperty(BAR_PROP_TIME,time);
   this.SetProperty(BAR_PROP_SYMBOL,symbol);
   this.SetProperty(BAR_PROP_PERIOD,timeframe);
   this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
   this.m_period_description=TimeframeDescription(timeframe);
  }
//+------------------------------------------------------------------+

Anstelle des Balkenindexes erhält der erste parametrische Klassenkonstruktor nun die Balkenzeit, zu der der Konstruktor der Klasse CBar für weitere Daten aufgerufen wurde. Fügen wir die Variable hinzu, mit der die Beschreibung der Klassenmethode übergeben wird, in der die Erstellung des Balkenobjekts aufgerufen wird:

//--- Constructors
                     CBar(){;}
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source);
                     CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates);

Korrigieren wir auch die Implementierung des Konstruktors — anstelle des Indexes verwenden wir die Balkenzeit und addieren die Variable, die die Klassenmethode angibt, aus der der Konstruktor aufgerufen wurde, zu dem Text, der den Fehler beim Abrufen von Verlaufsdaten beschreibt:

//+------------------------------------------------------------------+
//| Constructor 1                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source)
  {
   this.m_type=COLLECTION_SERIES_ID;
   MqlRates rates_array[1];
   this.SetSymbolPeriod(symbol,timeframe,time);
   ::ResetLastError();
//--- If failed to get the requested data by time and write bar data to the MqlRates array,
//--- display an error message, create and fill the structure with zeros, and write it to the rates_array array
   if(::CopyRates(symbol,timeframe,time,1,rates_array)<1)
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(1)-> ",source,symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
      //--- Set the requested bar time to the structure with zero fields
      MqlRates err={0};
      err.time=time;
      rates_array[0]=err;
     }
   ::ResetLastError();
//--- If failed to set time to the time structure, display the error message
   if(!::TimeToStruct(rates_array[0].time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(1) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
     }
//--- Set the bar properties
   this.SetProperties(rates_array[0]);
  }
//+------------------------------------------------------------------+

Das Hinzufügen des Quelle zur angezeigten Fehlermeldung eines Fehlers beim Abrufen von Verlaufsdaten ermöglicht das Auffinden der Klasse und ihrer Methode, von der aus versucht wurde, ein neues Balkenobjekt zu erstellen.

Der zweite parametrische Konstruktor verwendet nun ebenfalls die Balkenzeit anstelle seines Index:

//+------------------------------------------------------------------+
//| Constructor 2                                                    |
//+------------------------------------------------------------------+
CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates)
  {
   this.m_type=COLLECTION_SERIES_ID;
   this.SetSymbolPeriod(symbol,timeframe,rates.time);
   ::ResetLastError();
//--- If failed to set time to the time structure, display the error message,
//--- create and fill the structure with zeros, set the bar properties from this structure and exit
   if(!::TimeToStruct(rates.time,this.m_dt_struct))
     {
      int err_code=::GetLastError();
      ::Print
        (
         DFUN,"(2) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(rates.time),": ",
         CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ",
         CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ",
         CMessage::Retcode(err_code)
        );
      //--- Set the requested bar time to the structure with zero fields
      MqlRates err={0};
      err.time=rates.time;
      this.SetProperties(err);
      return;
     }
//--- Set the bar properties
   this.SetProperties(rates);
  }
//+------------------------------------------------------------------+

Im Methodenblock für einen vereinfachten Zugriff auf die Eigenschaften des Balkenobjekts des 'public' Klassenabschnitts benennen wir die Methode Period() in Timeframe() um, entfernen die Methode Index() und geben die bereits entfernte Balkeneigenschaft zurück:

//+------------------------------------------------------------------+
//| Methods of simplified access to bar object properties            |
//+------------------------------------------------------------------+
//--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume,
//--- (6) bar period start time, (7) year, (8) month the bar belongs to
//--- (9) week number since the year start, (10) week number since the month start
//--- (11) day, (12) hour, (13) minute
   ENUM_BAR_BODY_TYPE TypeBody(void)                                    const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE);  }
   ENUM_TIMEFRAMES   Timeframe(void)                                    const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD);   }
   int               Spread(void)                                       const { return (int)this.GetProperty(BAR_PROP_SPREAD);               }
   long              VolumeTick(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_TICK);               }
   long              VolumeReal(void)                                   const { return this.GetProperty(BAR_PROP_VOLUME_REAL);               }
   datetime          Time(void)                                         const { return (datetime)this.GetProperty(BAR_PROP_TIME);            }
   long              Year(void)                                         const { return this.GetProperty(BAR_PROP_TIME_YEAR);                 }
   long              Month(void)                                        const { return this.GetProperty(BAR_PROP_TIME_MONTH);                }
   long              DayOfWeek(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK);          }
   long              DayOfYear(void)                                    const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR);          }
   long              Day(void)                                          const { return this.GetProperty(BAR_PROP_TIME_DAY);                  }
   long              Hour(void)                                         const { return this.GetProperty(BAR_PROP_TIME_HOUR);                 }
   long              Minute(void)                                       const { return this.GetProperty(BAR_PROP_TIME_MINUTE);               }
   long              Index(void)                                        const { return this.GetProperty(BAR_PROP_INDEX);                     }

Anstatt eine nicht existierende Balkenobjekt-Eigenschaft zurückzugeben, gibt die Methode Index() jetzt den berechneten Wert nach Balkenzeit zurück:

//--- Return bar symbol
   string            Symbol(void)                                       const { return this.GetProperty(BAR_PROP_SYMBOL);                    }
//--- Return bar index on the specified timeframe the bar time falls into
   int               Index(const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT)  const
                       { return ::iBarShift(this.Symbol(),(timeframe>PERIOD_CURRENT ? timeframe : this.Timeframe()),this.Time());            }  
//+------------------------------------------------------------------+

Die Methode gibt den Balkenindex der aktuellen Zeitreihe für den in der Methodeneingabe angegebenen Zeitrahmen zurück, der durch die Funktion iBarShift() berechnet wurde.

In der Methode, die einen kurzen Balkenobjektnamen zurückgibt, rufen wir nun die neu beschriebene Methode mit dem Standardwert PERIOD_CURRENT auf, die den Index für die Zeitreihe zurückgibt, zu der das Balkenobjekt gehört:

//+------------------------------------------------------------------+
//| Return the bar object short name                                 |
//+------------------------------------------------------------------+
string CBar::Header(void)
  {
   return
     (
      CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+
      TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.Index()+"]"
     );
  }
//+------------------------------------------------------------------+

Entfernen wir noch den Block, der die Beschreibung des Balkenindexes zurückgibt, aus der Methode, die die Beschreibung der Ganzzahleigenschaft des Balkenobjektes zurückgibt :

//+------------------------------------------------------------------+
//| Return the description of the bar integer property               |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property)
  {
   return
     (
      property==BAR_PROP_INDEX               ?  CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TYPE                ?  CMessage::Text(MSG_ORD_TYPE)+

Stattdessen setzen wir einen Codeblock, der eine Balkenzeit zurückgibt (vollständige Methodenauflistung):

//+------------------------------------------------------------------+
//| Return the description of the bar integer property               |
//+------------------------------------------------------------------+
string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property)
  {
   return
     (
      property==BAR_PROP_TIME                ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)
         )  :
      property==BAR_PROP_TYPE                ?  CMessage::Text(MSG_ORD_TYPE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.BodyTypeDescription()
         )  :
      property==BAR_PROP_PERIOD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.m_period_description
         )  :
      property==BAR_PROP_SPREAD              ?  CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_TICK         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_VOLUME_REAL         ?  CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BAR_PROP_TIME_YEAR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.Year()
         )  :
      property==BAR_PROP_TIME_MONTH          ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+MonthDescription((int)this.Month())
         )  :
      property==BAR_PROP_TIME_DAY_OF_YEAR    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.DayOfYear(),3,'0')
         )  :
      property==BAR_PROP_TIME_DAY_OF_WEEK    ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek())
         )  :
      property==BAR_PROP_TIME_DAY            ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Day(),2,'0')
         )  :
      property==BAR_PROP_TIME_HOUR           ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Hour(),2,'0')
         )  :
      property==BAR_PROP_TIME_MINUTE         ?  CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)::IntegerToString(this.Minute(),2,'0')
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Damit sind die Änderungen in der Klasse der Balkenobjekte abgeschlossen.

Wenn wir uns die Listen der Standardbibliotheksklassen genau ansehen, sehen wir zwei Dateien in MQL5\Include\Indicators\: Series.mqh und TimeSeries.mqh.
Die Bibliothek hat auch Klassendateien mit demselben Namen, was falsch ist. Lassen Sie uns unsere beiden Klassen umbenennen — fügen wir DE (DoEasy) zu ihren Namen hinzu, ebenso wie zu den Namen ihrer Dateien. Wir ändern auch die Namen, unter denen der Zugriff auf diese Dateien und Klassen erfolgt. Diese Änderungen betreffen drei Dateien: Series.mqh (jetzt umbenannt in SeriesDE.mqh und die Klasse CSeriesDE), TimeSeries.mqh (jetzt umbenannt in TimeSeriesDE.mqh und die Klasse CTimeSeriesDE) und TimeSeriesCollection.mqh (betrifft beide umbenannten Klassen). Betrachten wir all diese Dateien und ihre Klassen der Reihe nach.

Die Datei Series.mqh wird jetzt als \MQL5\Include\DoEasy\Objects\Series\\SeriesDE.mqh gespeichert und der Klassenname ändert sich ebenfalls entsprechend:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:

Dementsprechend enthält die Methode, die das Klassenobjekt zurückgibt, jetzt den neuen Klassentyp:

public:
//--- Return (1) oneself and (2) the timeseries list
   CSeriesDE        *GetObject(void)                                    { return &this;         }

Die 'public' Methode, die das Bar-Objekt nach Index wie in der Zeitreihe GetBarBySeriesIndex zurückgibt, wurde in GetBar() umbenannt. Fügen wir eine weitere Methode derselben Art hinzu um das Balkenobjekt nach seiner offenen Zeit in der Zeitreihe zurückzugeben:

//--- Return the bar object by (1) a real index in the list, (2) an index as in the timeseries, (3) time and (4) the real list size
   CBar             *GetBarByListIndex(const uint index);
   CBar             *GetBar(const uint index);
   CBar             *GetBar(const datetime time);
   int               DataTotal(void)                                       const { return this.m_list_series.Total();               }

Somit haben wir jetzt zwei überladene Methoden, um ein Balkenobjekt nach Zeit und nach Index zurückzugeben.

Implementierung der Methode zur Rückgabe eines Balkenobjekts nach seiner Eröffnungszeit:

//+------------------------------------------------------------------+
//| Return the bar object by time in the timeseries                  |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const datetime time)
  {
   CBar *obj=new CBar(this.m_symbol,this.m_timeframe,time,DFUN_ERR_LINE); 
   if(obj==NULL)
      return NULL;
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   int index=this.m_list_series.Search(obj);
   delete obj;
   CBar *bar=this.m_list_series.At(index);
   return bar;
  }
//+------------------------------------------------------------------+

Die Methode erhält die Zeit, die verwendet werden soll, um das entsprechende Balkenobjekt zu finden und zurückzugeben.
Erstellen wir ein temporäres Balkenobjekt für die aktuelle Zeitreihe mit der Zeiteigenschaft, die der an die Methode übergebenen entspricht.
Wir setzen das Flag zum Sortieren der Liste der Balkenobjekte nach der Zeit
und durchsuchen die Liste nach dem Balkenobjekt mit der Zeit-Eigenschaft, die gleich der der Methode übergebenen ist.
Die Suche ergibt den Balkenindex in der Liste oder -1, wenn die Suche erfolglos war.
Entfernen wir das temporäre Balkenobjekt und holen den benötigten Balken aus der Liste durch den erhaltenen Index. Wenn der Index kleiner als Null ist, gibt die Methode NULL zurück.
Wir geben entweder ein Balkenobjekt zurück, wenn das Objekt durch die Zeit gefunden wurde, oder das NULL
der Methode.

Implementation der Methode zur Rückgabe eines Balkenobjekts nach Index:

//+------------------------------------------------------------------+
//| Return the bar object by index as in the timeseries              |
//+------------------------------------------------------------------+
CBar *CSeriesDE::GetBar(const uint index)
  {
   datetime time=::iTime(this.m_symbol,this.m_timeframe,index);
   if(time==0)
      return NULL;
   return this.GetBar(time);
  }
//+------------------------------------------------------------------+

Die Methode erhält den benötigten Balkenindex.
Wir verwenden die Funktion , um die Balkenzeit nach dem Index
und das Ergebnis der oben betrachteten Operation der Methode GetBar() zurückzugeben, die das Balkenobjekt durch die erhaltene Zeit zurückgibt.

Im 'public' Klassenabschnitt deklarieren wir zusammen mit den Methoden, die die Eigenschaften des Hauptbalkens nach Index zurückgeben, die Methoden, die dieselben Eigenschaften nach Balkenzeit zurückgeben:

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index
   double            Open(const uint index,const bool from_series=true);
   double            High(const uint index,const bool from_series=true);
   double            Low(const uint index,const bool from_series=true);
   double            Close(const uint index,const bool from_series=true);
   datetime          Time(const uint index,const bool from_series=true);
   long              TickVolume(const uint index,const bool from_series=true);
   long              RealVolume(const uint index,const bool from_series=true);
   int               Spread(const uint index,const bool from_series=true);

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index
   double            Open(const datetime time);
   double            High(const datetime time);
   double            Low(const datetime time);
   double            Close(const datetime time);
   datetime          Time(const datetime time);
   long              TickVolume(const datetime time);
   long              RealVolume(const datetime time);
   int               Spread(const datetime time);

Die Implementierung der deklarierten Methoden wird später in Betracht gezogen.

Im gleichen 'public' Klassenabschnitt deklarieren wir die Methode, die es erlaubt, die angegebenen Zeitreihen-Objektdaten in das Array zu schreiben, das an die Methode übergeben wird:

//--- (1) Create and (2) update the timeseries list
   int               Create(const uint required=0);
   void              Refresh(SDataCalculate &data_calculate);
//--- Copy the specified double property of the timeseries to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool              CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE);

//--- Create and send the "New bar" event to the control program chart
   void              SendEvent(void);


Angenommen, wir müssen die Zeitreihendaten in einem Durchgang in den Indikatorpuffer schreiben. Das Balkenobjekt kann viele verschiedene Eigenschaften enthalten — sowohl ganzzahlige als auch reelle. Jede der reellen Eigenschaften des Balkenobjekts kann mit dieser Methode in das Array geschrieben werden. Alle Daten werden auf die gleiche Weise in das Array geschrieben wie beim Schreiben in das Zeitreihen-Array — die aktuellen Balken-Daten, die im Zeitreihen-Objekt am Ende der Liste gespeichert sind, werden in den Null-Index des Empfänger-Arrays geschrieben, d.h. das Schreiben erfolgt rückwärts.

Werfen wir einen Blick auf seine Implementierung:

//+------------------------------------------------------------------+
//| Copy the specified double property of the timeseries to the array|
//+------------------------------------------------------------------+
bool CSeriesDE::CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE)
  {
//--- Get the number of bars in the timeseries list
   int total=this.m_list_series.Total();
   if(total==0)
      return false;
//--- If a dynamic array is passed to the method and its size is not equal to that of the timeseries list,
//--- set the new size of the passed array equal to that of the timeseries list
   if(::ArrayIsDynamic(array) && ::ArraySize(array)!=total)
      if(::ArrayResize(array,total,this.m_required)==WRONG_VALUE)
         return false;
//--- In the loop from the very last timeseries list element (from the current bar)
   int n=0;
   for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--)
     {
      //--- get the next bar object by the loop index,
      CBar *bar=this.m_list_series.At(i);
      //--- calculate the index, based on which the bar property is saved to the passed array
      n=total-1-i;
      //--- write the value of the obtained bar property using the calculated index
      //--- if the bar is not received or the property is equal to zero, write the value passed to the method as "empty" to the array
      array[n]=(bar==NULL ? empty : (bar.GetProperty(property)>0 && bar.GetProperty(property)<EMPTY_VALUE ? bar.GetProperty(property) : empty));
     }
   return true;
  }
//+------------------------------------------------------------------+

Wie wir sehen können, wird der Empfänger-Array-Index so berechnet, dass der allerletzte Wert aus dem Quell-Array in die Nullzelle des Empfänger-Arrays fällt. Daher wird unsere Zeitreihenliste (angeforderte Balken-Eigenschaft) in das Array (z.B. den Indikatorpuffer) in der Reihenfolge der Balkennummerierung in einem Symboldiagramm geschrieben, während die Balkenobjekte in der Zeitreihenliste in umgekehrter Reihenfolge angeordnet sind — der Balken mit der jüngsten Zeit (der aktuelle Balken) befindet sich am Ende der Liste. Auf diese Weise können wir die Eigenschaften aller Balken aus der Zeitreihenliste schnell in den Indikatorpuffer kopieren, wenn der Zeitrahmen der kopierten Zeitreihen mit dem Diagrammzeitrahmen übereinstimmt, für den wir die Zeitreihen mit der Methode in den Puffer kopieren.

Setzen Sie in beiden Klassenkonstruktoren das Flag für die Sortierung der Zeitreihenliste nach Balkenzeit:

//+------------------------------------------------------------------+
//| Constructor 1 (current symbol and period timeseries)             |
//+------------------------------------------------------------------+
CSeriesDE::CSeriesDE(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period());
   this.m_period_description=TimeframeDescription(this.m_timeframe);
  }
//+------------------------------------------------------------------+
//| Constructor 2 (specified symbol and period timeseries)           |
//+------------------------------------------------------------------+
CSeriesDE::CSeriesDE(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false)
  {
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   this.SetSymbolPeriod(symbol,timeframe);
   this.m_sync=this.SetRequiredUsedData(required,0);
   this.m_period_description=TimeframeDescription(this.m_timeframe);
  }
//+------------------------------------------------------------------+

In der Methode zur Erstellung der Liste der Zeitreihen ersetzen wir die Sortierung nach Index durch eine Sortierung nach Zeit und ergänzen den Text, der im Falle von Fehlern bei der Erstellung von Balkenobjekten angezeigt wird und die beim Eintragen in die Liste der Zeitreihen aufgetreten sind:

//+------------------------------------------------------------------+
//| Create the timeseries list                                       |
//+------------------------------------------------------------------+
int CSeriesDE::Create(const uint required=0)
  {
//--- If the required history depth is not set for the list yet,
//--- display the appropriate message and return zero,
   if(this.m_amount==0)
     {
      ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA));
      return 0;
     }
//--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, 
//--- while being lower than the available bar number,
//--- set the new value of the required history depth for the list
   else if(required>0 && this.m_amount!=required && required<this.m_bars)
     {
      //--- If failed to set a new value, return zero
      if(!this.SetRequiredUsedData(required,0))
         return 0;
     }
//--- For the rates[] array we are to receive historical data to,
//--- set the flag of direction like in the timeseries,
//--- clear the bar object list and set the flag of sorting by bar index
   MqlRates rates[];
   ::ArraySetAsSeries(rates,true);
   this.m_list_series.Clear();
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
   ::ResetLastError();
//--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount,
//--- if failed to get data, display the appropriate message and return zero
   int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,(uint)this.m_amount,rates),err=ERR_SUCCESS;
   if(copied<1)
     {
      err=::GetLastError();
      ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ",
                   CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
      return 0;
     }
//--- Historical data is received in the rates[] array
//--- In the rates[] array loop,
   for(int i=0; i<copied; i++)
     {
      //--- create a new bar object out of the current MqlRates structure by the loop index
      ::ResetLastError();
      CBar* bar=new CBar(this.m_symbol,this.m_timeframe,rates[i]);
      if(bar==NULL)
        {
         ::Print
           (
            DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ)," ",this.Header()," ",::TimeToString(rates[i].time),". ",
            CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())
           );
         continue;
        }
      //--- If failed to add bar object to the list,
      //--- display the appropriate message with the error description in the journal
      if(!this.m_list_series.Add(bar))
        {
         err=::GetLastError();
         ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header()," ",::TimeToString(rates[i].time),". ",
                      CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err));
        }
     }
//--- Return the size of the created bar object list
   return this.m_list_series.Total();
  }
//+------------------------------------------------------------------+

Die Methode zur Aktualisierung der Listen- und Zeitreihendaten wurde ebenfalls leicht aktualisiert:

//+------------------------------------------------------------------+
//| Update timeseries list and data                                  |
//+------------------------------------------------------------------+
void CSeriesDE::Refresh(SDataCalculate &data_calculate)
  {
//--- If the timeseries is not used, exit
   if(!this.m_available)
      return;
   MqlRates rates[1];
//--- Set the flag of sorting the list of bars by time
   this.m_list_series.Sort(SORT_BY_BAR_TIME);
//--- If a new bar is present on a symbol and period,
   if(this.IsNewBarManual(data_calculate.rates.time))
     {
      //--- create a new bar object and add it to the end of the list
      CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,this.m_new_bar_obj.TimeNewBar(),DFUN_ERR_LINE);
      if(new_bar==NULL)
         return;
      if(!this.m_list_series.InsertSort(new_bar))
        {
         delete new_bar;
         return;
        }
      //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol 
      this.SetServerDate();
      //--- if the timeseries exceeds the requested number of bars, remove the earliest bar
      if(this.m_list_series.Total()>(int)this.m_required)
         this.m_list_series.Delete(0);
      //--- save the new bar time as the previous one for the subsequent new bar check
      this.SaveNewBarTime(data_calculate.rates.time);
     }
     
//--- Get the bar index with the maximum time (zero bar) and bar object from the list by the obtained index
   int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
   CBar *bar=this.m_list_series.At(index);
   if(bar==NULL)
      return;
//--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe,
//--- copy price parameters (passed to the method from the outside) to the bar price structure
   int copied=1;
   if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==(ENUM_TIMEFRAMES)::Period())
     {
      rates[0].time=data_calculate.rates.time;
      rates[0].open=data_calculate.rates.open;
      rates[0].high=data_calculate.rates.high;
      rates[0].low=data_calculate.rates.low;
      rates[0].close=data_calculate.rates.close;
      rates[0].tick_volume=data_calculate.rates.tick_volume;
      rates[0].real_volume=data_calculate.rates.real_volume;
      rates[0].spread=data_calculate.rates.spread;
     }
//--- otherwise, get data to the bar price structure from the environment
   else
      copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates);
//--- If the prices are obtained, set the new properties from the price structure for the bar object
   if(copied==1)
      bar.SetProperties(rates[0]);
  }
//+------------------------------------------------------------------+

Hier wird jetzt auch die Listensortierung nach der Zeit gesetzt. Bei der Erstellung eines neuen Balkenobjekts, übergeben wir die Balkenzeit des Objekts "New bar" an den Klassenkonstruktor, da wir den neuen Balken nur bei der Definition der Tatsache des Eröffnens eines neuen Balkens zur Liste hinzufügen, während das Objekt "New bar" bereits die Balkenzeit enthält. Übergeben wir diese an den Konstruktor. Außerdem geben wir die Beschreibung der Methode, bei der ein neues Balkenobjekt erzeugt wird, an den Konstruktor weiter. Wenn es nicht gelungen ist, ein neues Balkenobjekt zu erstellen, wird die Nachricht vom Konstruktor mit der Methode CSeriesDE::Refresh und dem Code-String, von dem aus der Konstruktor der Klasse CBar aufgerufen wurde, an das Journal gesendet.
Um den neuesten (aktuellen) Balken aus der Zeitreihenliste zu erhalten, suchen wir ihn anhand der größten Zeit aller Balkenobjekte in der Zeitreihenliste. Dazu suchen wir zunächst mit der Methode FindBarMax() der Klasse CSelect den Index des Balkenobjekts mit der maximalen Zeit. Unter Verwendung des erhaltenen Index nehmen wir den allerletzten Balken aus der Liste. Dieser Balken wird der aktuelle sein. Wenn wir aus irgendeinem Grund nicht in der Lage sind, den aktuellen Balkenindex zu erhalten, ist der Indexwert -1. , erhalten wir im Falle eines negativen Index NULL. Wenn er tatsächlich Null ist, beenden wir einfach die Aktualisierungsmethode.

Die Methoden zur Rückgabe der Eigenschaften von Hauptleistenobjekten nach Zeit:

//+------------------------------------------------------------------+
//| Return bar's Open by time                                        |
//+------------------------------------------------------------------+
double CSeriesDE::Open(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Open() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's High by time                                        |
//+------------------------------------------------------------------+
double CSeriesDE::High(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.High() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's Low by time                                         |
//+------------------------------------------------------------------+
double CSeriesDE::Low(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Low() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar's Close by time                                       |
//+------------------------------------------------------------------+
double CSeriesDE::Close(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Close() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar time by time                                          |
//+------------------------------------------------------------------+
datetime CSeriesDE::Time(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return bar tick volume by time                                   |
//+------------------------------------------------------------------+
long CSeriesDE::TickVolume(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar real volume by time                                   |
//+------------------------------------------------------------------+
long CSeriesDE::RealVolume(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return bar spread by time                                        |
//+------------------------------------------------------------------+
int CSeriesDE::Spread(const datetime time)
  {
   CBar *bar=this.GetBar(time);
   return(bar!=NULL ? bar.Spread() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+

Sie funktionieren alle auf die gleiche Weise:
Geholt wird das Balkenobjekt aus der Zeitreihenliste nach der Zeit
und zurückgeliefert wird der Wert der entsprechenden Eigenschaft unter Berücksichtigung eines Fehlers beim Empfang des Balkenobjekts.

Die Methode zum Erstellen und Senden des Ereignisses "New bar" auf dem Chart des Kontrollprogramms wurde ebenfalls verbessert, da es notwendig ist, das aktuelle Balkenobjekt mit der Zeit zu erhalten:

//+------------------------------------------------------------------+
//| Create and send the "New bar" event                              |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CSeriesDE::SendEvent(void)
  {
   int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME);
   CBar *bar=this.m_list_series.At(index);
   if(bar==NULL)
      return;
   ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol());
  }
//+------------------------------------------------------------------+

Wie bei der Methode Refresh() erhalten wir hier das aktuelle Balkenobjekt aus der Zeitreihenliste, und die Balkenzeit wird an den Parameter lparam übergeben, wenn ein kundenspezifisches Ereignis an die Kontrollprogramm auf dem Chart gesendet wird.

Damit ist die Zeitreihenklasse abgeschlossen. Nun wollen wir die Klasse aller Zeitreihen eines einzelnen Symbols verbessern.

Wie bereits erwähnt, kann die Klasse CTimeSerirs einen Konflikt mit der gleichnamigen Klasse der Standardbibliothek verursachen. Daher habe ich sie in CTimeSerirsDE umbenannt. Innerhalb des Codes der Klasse habe ich alle Instanzen der Zeichenkette CTimeSerirs durch CTimeSerirsDE und CSerirs durch CSerirsDE ersetzt. Anstatt sich in eine detaillierte Beschreibung zu vertiefen, betrachten wir das folgende kurze Beispiel:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "SeriesDE.mqh"
#include "..\Ticks\NewTickObj.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries class                                          |
//+------------------------------------------------------------------+
class CTimeSeriesDE : public CBaseObjExt
  {
private:

Im 'public' Teil der Klasse deklarieren wir die Methode zum Kopieren der angegebenen reellen Eigenschaft der Balken der angegebenen Zeitreihe in das übergebene Array:

//--- Copy the specified double property of the specified timeseries to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool              CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe,
                                          const ENUM_BAR_PROP_DOUBLE property,
                                          double &array[],
                                          const double empty=EMPTY_VALUE);

//--- Compare CTimeSeriesDE objects (by symbol)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Display (1) description and (2) short symbol timeseries description in the journal
   void              Print(const bool created=true);
   void              PrintShort(const bool created=true);
   
//--- Constructors
                     CTimeSeriesDE(void){;}
                     CTimeSeriesDE(const string symbol);
  };
//+------------------------------------------------------------------+

Wir haben diese Methode oben bei der Verbesserung der Klasse CSeriesDE berücksichtigt. Implementieren wir die Methode:

//+------------------------------------------------------------------+
//| Copy the specified double property of the specified timeseries   |
//+------------------------------------------------------------------+
bool CTimeSeriesDE::CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe,
                                         const ENUM_BAR_PROP_DOUBLE property,
                                         double &array[],
                                         const double empty=EMPTY_VALUE)
  {
   CSeriesDE *series=this.GetSeries(timeframe);
   if(series==NULL)
      return false;
   return series.CopyToBufferAsSeries(property,array,empty);
  }
//+------------------------------------------------------------------+

Hier ist alles ganz einfach: Zuerst wird die benötigte Zeitreihe des angegebenen Zeitrahmens geholt, dann dann wird das Ergebnis des Methodenaufrufs aus dem erhaltenen Zeitreihenobjekt zurückgegeben.

Implementieren wir in der Liste aller Symbolzeitreihen der Methode, die den Zeitreihenindex zurückgibt, die Überprüfung des für die Suche angegebenen Zeitrahmens:

//+------------------------------------------------------------------+
//| Return the timeframe index in the list                           |
//+------------------------------------------------------------------+
int CTimeSeriesDE::IndexTimeframe(const ENUM_TIMEFRAMES timeframe)
  {
   const CSeriesDE *obj=new CSeriesDE(this.m_symbol,(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe));
   if(obj==NULL)
      return WRONG_VALUE;
   this.m_list_series.Sort();
   int index=this.m_list_series.Search(obj);
   delete obj;
   return index;
  }
//+------------------------------------------------------------------+

Wenn wir ein temporäres Objekt für eine Suche anlegen, prüfen wir den angegebenen Zeitraum, und wenn dieser CURRENT_PERIOD ist, verwenden wir den aktuellen Zeitraum für die Suche.

In der Methode verwenden wir zur Aktualisierung der angegebenen Zeitreihenliste die neue Eröffnungszeit des Balkens aus der Struktur data_calculate als Parameterwert lparam, wenn wir ein neues Ereignis zur Ereignisliste hinzufügen:

//+------------------------------------------------------------------+
//| Update a specified timeseries list                               |
//+------------------------------------------------------------------+
void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the timeseries event flag and clear the list of all timeseries events
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the timeseries from the list by its timeframe
   CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe));
   if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable())
      return;
//--- Update the timeseries list
   series_obj.Refresh(data_calculate);
//--- If the timeseries object features the New bar event
   if(series_obj.IsNewBar(data_calculate.rates.time))
     {
      //--- send the "New bar" event to the control program chart
      series_obj.SendEvent();
      //--- set the values of the first date in history on the server and in the terminal
      this.SetTerminalServerDate();
      //--- add the "New bar" event to the list of timeseries events
      //--- in case of successful addition, set the event flag for the timeseries
      if(this.EventAdd(SERIES_EVENTS_NEW_BAR,series_obj.Time(data_calculate.rates.time),series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
     }
  }
//+------------------------------------------------------------------+

Hiermit wird die Klasse CTimeSeriesDE vervollständigt. Wechseln wir zur Klasse CTimeSeriesCollection des Kollektionsobjekts von Objekten aller Zeitreihen aller Symbole.

Derzeit haben wir zwei umbenannte Klassen: CSeriesDE und CTimeSerirsDE. Innerhalb des Codes der Klasse CTimeSeriesCollection ersetzen wir alle Instanzen der Zeichenfolge CTimeSerirs durch CTimeSerirsDE und CSerirs durch CSerirsDE.
Anstatt sich in eine detaillierte Beschreibung zu vertiefen, betrachten wir das folgende kurze Beispiel:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Objects\Series\TimeSeriesDE.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
//+------------------------------------------------------------------+
//| Symbol timeseries collection                                     |
//+------------------------------------------------------------------+
class CTimeSeriesCollection : public CBaseObjExt
  {
private:
   CListObj                m_list;                    // List of applied symbol timeseries
//--- Return the timeseries index by symbol name
   int                     IndexTimeSeries(const string symbol);
public:
//--- Return (1) oneself and (2) the timeseries list
   CTimeSeriesCollection  *GetObject(void)            { return &this;         }
   CArrayObj              *GetList(void)              { return &this.m_list;  }
//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period
   CTimeSeriesDE          *GetTimeseries(const string symbol);
   CSeriesDE              *GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe);

//--- Create the symbol timeseries list collection

Wir deklarieren im 'public' Teil der Klasse drei neue Methoden:
Die Methode, die das Balkenobjekt der angegebenen Zeitreihe des angegebenen Symbols nach der Eröffnungszeit des Balkens zurückgibt und zwei Methoden, die das Balkenobjekt einer einzelnen Zeitreihe zurückgeben, die der Öffnungszeit des Balkens in einer anderen Zeitreihe entspricht nach Balkenindex und Balkenzeit:

//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time
//--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true);
   CBar                   *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
   CBar                   *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                             const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);

Wir deklarieren außerdem noch zwei weitere Methoden im 'public' Bereich: die Methode zum Aktualisieren aller Zeitreihen des angegebenen Symbols und die Methode zum Kopieren der angegebenen reellen Eigenschaft der angegebenen Zeitreihe des angegebenen Symbols in das Array:

//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols
   void                    Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate);
   void                    Refresh(const string symbol,SDataCalculate &data_calculate);
   void                    Refresh(SDataCalculate &data_calculate);

//--- Get events from the timeseries object and add them to the list
   bool                    SetEvents(CTimeSeriesDE *timeseries);

//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(const bool created=true);
   void                    PrintShort(const bool created=true);
   
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                    CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                const ENUM_BAR_PROP_DOUBLE property,
                                                double &array[],
                                                const double empty=EMPTY_VALUE);
//--- Constructor
                           CTimeSeriesCollection();
  };
//+------------------------------------------------------------------+

Implementieren der Methode, die das Balkenobjekt der angegebenen Zeitreihe des angegebenen Symbols der angegebenen Position nach Zeit zurückgibt:

//+------------------------------------------------------------------+
//| Return the bar object of the specified timeseries                |
//| of the specified symbol of the specified position by time        |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time)
  {
   CSeriesDE *series=this.GetSeries(symbol,timeframe);
   if(series==NULL)
      return NULL;
   return series.GetBar(bar_time);
  }
//+------------------------------------------------------------------+

Die Methode übergibt Symbol und Zeitrahmen der Zeitreihe, von der wir einen Balken mit der angegebenen Eröffnungszeit erhalten sollten.

Abrufen des Zeitreihen-Objekts mit dem angegebenen Symbol und Zeitrahmen und Rückgabe des aus der erhaltenen Zeitreihe entnommene Balken-Objekt wegen der Balkenzeit.
Wenn wir den Balken nicht erhalten haben, geben wir NULL zurück.

Implementation der Methode, die das Balkenobjekt der ersten Zeitreihe durch einen Index zurückgibt, der der Eröffnungszeit des Balkens der zweiten Zeitreihe entspricht:

//+------------------------------------------------------------------+
//| Return the bar object of the first timeseries by index           |
//| corresponding to the bar open time on the second timeseries      |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                               const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
  {
   CBar *bar_first=this.GetBar(symbol_first,timeframe_first,index);
   if(bar_first==NULL)
      return NULL;
   CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time());
   return bar_second;
  }
//+------------------------------------------------------------------+

Die Methode erhält Symbol und Zeitrahmen des ersten Charts, den Balkenindex des ersten Charts, sowie Symbol und Periode des zweiten Charts.

Abrufen des ersten Balkenobjekts aus der Zeitreihe des ersten Symbol-Periode nach dem angegebenen Index, Abrufen und Rückgabe des zweiten Balkenobjekts der zweiten Symbol-Periode gemäß dem Zeitpunkt des ersten erhaltenen Balkens.

Die Methode ermöglicht den Erhalt einer durch den Index spezifizierten Balkenposition auf der spezifizierten ersten Chartsymbolperiode, die mit der Balkenposition auf dem zweiten spezifizierten Diagrammperiodensymbol durch die Eröffnungszeit übereinstimmt.
Wie nutzt uns das? Zum Beispiel sind wir in der Lage, schnell alle Н1 Balken auf dem M15-Chart zu markieren.
Wir übergeben einfach das aktuelle Symbol, die М15 Chartperiode, die Balkenposition nach seinem Index auf dem Chart (z.B. Index der Indikatorberechnungsschleife), das aktuelle Symbol und die Н1-Periode an die Methode. Die Methode gibt das Balkenobjekt aus dem aktuellen Symboldiagramm und Н1 Periode zurück, dessen Öffnungszeit den Zeitpunkt der Öffnung des ersten angegebenen Balkens einschließt.

Implementation der Methode, die das Balkenobjekt der ersten Zeitreihe mit der Zeit zurückgibt, die der Balkenöffnungszeit der zweiten Zeitreihe entspricht:

//+------------------------------------------------------------------+
//| Return the bar object of the first timeseries by time            |
//| corresponding to the bar open time on the second timeseries      |
//+------------------------------------------------------------------+
CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time,
                                                               const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
  {
   CBar *bar_first=this.GetBar(symbol_first,timeframe_first,first_bar_time);
   if(bar_first==NULL)
      return NULL;
   CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time());
   return bar_second;
  }
//+------------------------------------------------------------------+

Die Methode ähnelt der Methode, bei der das Balkenobjekt nach Index empfangen wird, die gerade oben beschrieben worden ist. Hier setzt das System anstelle des Balkenindex in der Zeitreihe den Zeitpunkt seiner Eröffnung in die angegebene erste Zeitreihe.

Wie Sie vielleicht bemerkt haben, erhalten beide Methoden die Perioden und Symbole beider Charts. Dies bedeutet, dass die Methoden in der Lage sind, das Balkenobjekt von jedem Periodensymbol zurückzuholen, das dem Balkenobjekt des ersten Periodensymbols mit seiner angegebenen Position in der Zeitreihe entspricht. Auf diese Weise können wir zwei Balken aus jedem beliebigen Periodensymbol leicht abgleichen, um sie anhand einer der Eigenschaften des Balkenobjekts zu vergleichen.

Fügen wir die Prüfung auf ein "nicht natives Symbol" zu der Methode zur Aktualisierung der angegebenen Zeitreihe des angegebenen Symbols hinzu:

//+------------------------------------------------------------------+
//| Update the specified timeseries of the specified symbol          |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
  {
//--- Reset the flag of an event in the timeseries collection and clear the event list
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the object of all symbol timeseries by a symbol name
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries==NULL)
      return;
//--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit
   if(symbol!=::Symbol() && !timeseries.IsNewTick())
      return;
//--- Update the required object timeseries of all symbol timeseries
   timeseries.Refresh(timeframe,data_calculate);
//--- If the timeseries has the enabled event flag,
//--- get events from symbol timeseries, write them to the collection event list
//--- and set the event flag in the collection
   if(timeseries.IsEvent())
      this.m_is_event=this.SetEvents(timeseries);
  }
//+------------------------------------------------------------------+

Warum brauchen wir das? Wir aktualisieren alle Zeitreihen, die nicht zum aktuellen Periodensymbol gehören, im Timer der Bibliothek. Zeitreihen, die zu dem Symbol gehören, an dem das Programm gestartet wird, sollten über den Ereignisbehandler des Programms aktualisiert werden. Um das Ereignis eines neue Ticks für das aktuelle Symbol im Timer zu vermeiden (die Zeitreihe des aktuellen Symbols wird ohnehin durch Tick aktualisiert), prüfen wir, ob das Zeitreihensymbol mit dem aktuellen übereinstimmt, und prüfen das Zeitreihenereignis "new tick" nur, wenn die Zeitreihe nicht zum aktuellen Symbol gehört.

Implementierung der Methode zur Aktualisierung aller Zeitreihen des angegebenen Symbols:

//+------------------------------------------------------------------+
//| Update all timeseries of the specified symbol                    |
//+------------------------------------------------------------------+
void CTimeSeriesCollection::Refresh(const string symbol,SDataCalculate &data_calculate)
  {
//--- Reset the flag of an event in the timeseries collection and clear the event list
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- Get the object of all symbol timeseries by a symbol name
   CTimeSeriesDE *timeseries=this.GetTimeseries(symbol);
   if(timeseries==NULL)
      return;
//--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit
   if(symbol!=::Symbol() && !timeseries.IsNewTick())
      return;
//--- Update all object timeseries of all symbol timeseries
   timeseries.RefreshAll(data_calculate);
//--- If the timeseries has the enabled event flag,
//--- get events from symbol timeseries, write them to the collection event list
//--- and set the event flag in the collection
   if(timeseries.IsEvent())
      this.m_is_event=this.SetEvents(timeseries);
  }
//+------------------------------------------------------------------+

Jede Zeile der Methode wird in den Code-Kommentaren beschrieben, daher hoffe ich, dass hier alles klar ist.

Implementation der Methode, die die angegebenen reellen Daten des Balkens des angegebenen Zeitreihenobjekts in das an die Methode übergebene Array schreibt:

//+------------------------------------------------------------------+
//| Copy the specified double property to the array                  |
//| for a specified timeseries of a specified symbol                 |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                                 const ENUM_BAR_PROP_DOUBLE property,
                                                 double &array[],
                                                 const double empty=EMPTY_VALUE)
  {
   CSeriesDE *series=this.GetSeries(symbol,timeframe);
   if(series==NULL)
      return false;
   return series.CopyToBufferAsSeries(property,array,empty);
  }
//+------------------------------------------------------------------+

Wir haben die Methodenoperation von oben bei der Verbesserung der CSeriesDE-Klasse berücksichtigt.
Hier rufen wir einfach durch die angegebenen Symbol und Periode das gewünschte Zeitreihenobjekt und geben das Ergebnis des Aufrufs der Gleichnamens-Methode der erhaltenen Zeitreihe zurück.

Damit ist die Arbeit an der Kollektionsklasse der Zeitreihen abgeschlossen.

Jetzt müssen wir den Zugriff auf neu erstellte Methoden von bibliotheksbasierten Programmen aus ermöglichen. Ein solcher Zugriff wird durch das Hauptobjekt der Bibliothek CEngine ermöglicht.

Wir öffnen \MQL5\Include\DoEasy\Engine.mqh und ersetzen alle Instanzen der Zeichenkette CSerirs in CSerirsDE und CTimeSerirs in CTimeSerirsDE.

Im 'private' Teil der Klasse deklarieren wir die Klassenvariable zum Speichern des Programmnamens:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_time_series;                  // Timeseries collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CPause               m_pause;                         // Pause object
   CArrayObj            m_list_counters;                 // List of timer counters
   int                  m_global_error;                  // Global error code
   bool                 m_first_start;                   // First launch flag
   bool                 m_is_hedge;                      // Hedge account flag
   bool                 m_is_tester;                     // Flag of working in the tester
   bool                 m_is_market_trade_event;         // Account trading event flag
   bool                 m_is_history_trade_event;        // Account history trading event flag
   bool                 m_is_account_event;              // Account change event flag
   bool                 m_is_symbol_event;               // Symbol change event flag
   ENUM_TRADE_EVENT     m_last_trade_event;              // Last account trading event
   int                  m_last_account_event;            // Last event in the account properties
   int                  m_last_symbol_event;             // Last event in the symbol properties
   ENUM_PROGRAM_TYPE    m_program;                       // Program type
   string               m_name;                          // Program name

Im Klassenkonstruktor weisen wir den Programmnamen der Variablen zu:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
...

Im 'public' Teil der Klasse fügen wir hinzu: die Methode, die das Balkenobjekt der angegebenen Zeitreihe des angegebenen Symbols der angegebenen Position durch Balkenzeit,
zwei Methoden, die das Balkenobjekt der ersten Zeitreihe entsprechend der Eröffnungszeit des Balkens der zweiten Zeitreihe durch Index und Zeit,
die Methode, die alle Zeitreihen des angegebenen Symbols aktualisiert,
die Methoden, die die Basiseigenschaften der Balken gemäß der Zeit zurückgeben,
die Methode zum Kopieren der spezifizierten reellen Eigenschaft der spezifizierten Zeitreihe des spezifizierten Symbols in das Array und
die Methode, die den Namen des bibliotheksbasierten Programms zurückgibt
.

//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time
   CBar                *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true)
                          { return this.m_time_series.GetBar(symbol,timeframe,index,from_series);                 }
   CBar                *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
                          { return this.m_time_series.GetBar(symbol,timeframe,time);                              }
//--- Return the bar object of the first timeseries corresponding to the bar open time on the second timeseries (1) by index, (2) by time
   CBar                *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index,
                                                                const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
                          { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,index,symbol_second,timeframe_second); }
   
   CBar                *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime time,
                                                                const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT)
                          { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,time,symbol_second,timeframe_second); }

//--- Return the flag of opening a new bar of the specified timeseries of the specified symbol
   bool                 SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0)
                          { return this.m_time_series.IsNewBar(symbol,timeframe,time);                            }

//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols
   void                 SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,timeframe,data_calculate);                          }
   void                 SeriesRefresh(const string symbol,SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(symbol,data_calculate);                                    }
   void                 SeriesRefresh(SDataCalculate &data_calculate)
                          { this.m_time_series.Refresh(data_calculate);                                           }

//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period
   CTimeSeriesDE       *SeriesGetTimeseries(const string symbol)
                          { return this.m_time_series.GetTimeseries(symbol);                                      }
   CSeriesDE           *SeriesGetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe)
                          { return this.m_time_series.GetSeries(symbol,timeframe);                                }
//--- Return (1) an empty, (2) partially filled timeseries
   CSeriesDE           *SeriesGetSeriesEmpty(void)       { return this.m_time_series.GetSeriesEmpty();            }
   CSeriesDE           *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted();      }

//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume,
//--- (7) RealVolume, (8) Spread of the bar, specified by index, of the specified symbol of the specified timeframe
   double               SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   double               SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   datetime             SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   long                 SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   int                  SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index);
   
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume,
//--- (7) RealVolume, (8) Spread of the bar, specified by time, of the specified symbol of the specified timeframe
   double               SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   double               SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   datetime             SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   long                 SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   long                 SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   int                  SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   
//--- Copy the specified double property of the specified timeseries of the specified symbol to the array
//--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array
   bool                 SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property,
                                                   double &array[],const double empty=EMPTY_VALUE)
                          { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}

...

//--- Return the program name
   string               Name(void)                                const { return this.m_name;                                 }

Alle Methoden, deren Implementierung im Klassenkörper festgelegt ist, geben das Ergebnis des Aufrufs von gleichnamigen Methoden der oben betrachteten Kollektion der Zeitreihen TimeSeriesCollection zurück.

Implementation der Methoden, die Basiseigenschaften der Balken gemäß der Zeit zurückgeben:

//+------------------------------------------------------------------+
//| Return Open of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Open() : 0);
  }
//+------------------------------------------------------------------+
//| Return High of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.High() : 0);
  }
//+------------------------------------------------------------------+
//| Return Low of the specified bar by time                          |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Low() : 0);
  }
//+------------------------------------------------------------------+
//| Return Close of the specified bar by time                        |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Close() : 0);
  }
//+------------------------------------------------------------------+
//| Return Time of the specified bar by time                         |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Time() : 0);
  }
//+------------------------------------------------------------------+
//| Return TickVolume of the specified bar by time                   |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return RealVolume of the specified bar by time                   |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE);
  }
//+------------------------------------------------------------------+
//| Return Spread of the specified bar by time                       |
//| of the specified symbol of the specified timeframe               |
//+------------------------------------------------------------------+
int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time)
  {
   CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time);
   return(bar!=NULL ? bar.Spread() : INT_MIN);
  }
//+------------------------------------------------------------------+

Hier ist alles einfach:
Wir holen uns das Balkenobjekt aus der Kollektionsklasse der Zeitreihen mit der Methode GetBar() unter Angabe der Zeitreihe Symbol und Periode und dem Zeitpunkt des Öffnens des angeforderten Balkens in der Zeitreihe, und geben den Wert der entsprechenden Eigenschaft des erhaltenen Balkens zurück, unter Berücksichtigung eines Fehlers beim Empfang des Balkens aus der Zeitreihe.

Hinzufügen der Aktualisierung aller Zeitreihen des aktuellen Symbols zur Ereignisbehandlung eines NewTick des aktuellen Symbols:

//+------------------------------------------------------------------+
//| NewTick event handler                                            |
//+------------------------------------------------------------------+
void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If this is not a EA, exit
   if(this.m_program!=PROGRAM_EXPERT)
      return;
//--- Re-create empty timeseries and update the current symbol timeseries
   this.SeriesSync(data_calculate,required);
   this.SeriesRefresh(NULL,data_calculate);
//--- end
  }
//+------------------------------------------------------------------+

Dies ermöglicht die Aktualisierung aller verwendeten Zeitreihen des aktuellen Symbols in den EAs unmittelbar nach dem Synchronisierungsversuch, so dass wir nicht auf die Aktualisierung der Zeitreihe des aktuellen Symbols im Timer der Bibliothek warten müssen, da dies manchmal eine Desynchronisierung der Daten verursacht, wenn die Datenaktualisierung im Zeitgeber aufgerufen wird, nachdem ein neuer Tick auf dem aktuellen Symbol eingetroffen ist.

Fügen wir die Aktualisierung aller Zeitreihen des aktuellen Symbols nach der Synchronisierung aller Zeitreihen zur Ereignisbehandlung von Calculate des aktuellen Symbols hinzu:

//+------------------------------------------------------------------+
//| Calculate event handler                                          |
//+------------------------------------------------------------------+
int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If this is not an indicator, exit
   if(this.m_program!=PROGRAM_INDICATOR)
      return data_calculate.rates_total;
//--- Re-create empty timeseries
//--- If at least one of the timeseries is not synchronized, return zero
   if(!this.SeriesSync(data_calculate,required))
     {
      return 0;
     }
//--- Update the timeseries of the current symbol and return rates_total
   this.SeriesRefresh(NULL,data_calculate);
   return data_calculate.rates_total;
  }
//+------------------------------------------------------------------+

Hier sind die Unterschiede zu OnTick() — die Methode gibt Null zurück, bis alle verwendeten Zeitreihen des aktuellen Symbols synchronisiert sind, was wiederum OnCalculate() des Indikators über die Notwendigkeit einer vollständigen Neuberechnung der historischen Daten informiert.

Dementsprechend sollte die Methode zur Synchronisierung der Daten aller Zeitreihen nun boolesche Werte zurückgeben:

//+------------------------------------------------------------------+
//| Synchronize timeseries data with the server                      |
//+------------------------------------------------------------------+
bool CEngine::SeriesSync(SDataCalculate &data_calculate,const uint required=0)
  {
//--- If the timeseries data is not calculated, try re-creating the timeseries
//--- Get the pointer to the empty timeseries
   CSeriesDE *series=this.SeriesGetSeriesEmpty();
//--- If there is an empty timeseries
   if(series!=NULL)
     {
      //--- Display the empty timeseries data as a chart comment and try synchronizing the timeseries with the server data
      ::Comment(series.Header(),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_WAIT_FOR_SYNC));
      ::ChartRedraw(::ChartID());
      //--- if the data has been synchronized
      if(series.SyncData(required,data_calculate.rates_total))
        {
         //--- if managed to re-create the timeseries
         if(this.m_time_series.ReCreateSeries(series.Symbol(),series.Timeframe(),data_calculate.rates_total))
           {
            //--- display the chart comment and the journal entry with the re-created timeseries data
            ::Comment(series.Header(),": OK");
            ::ChartRedraw(::ChartID());
            Print(series.Header()," ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED_OK),":");
            series.PrintShort();
            return true;
           }
        }
      //--- Data is not yet synchronized or failed to re-create the timeseries
      return false;
     }
//--- There are no empty timeseries - all is synchronized, delete all comments
   else
     {
      ::Comment("");
      ::ChartRedraw(::ChartID());
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

Damit ist die Klasse CEngine vorerst abgeschlossen.

Nun wollen wir überprüfen, wie all dies mit den Indikatoren funktioniert. Da wir mehrere verschiedene Zeitreihen in einem einzigen Indikator verwenden und wir in der Lage sind, Daten eines einzelnen Balkens zu erhalten, die den Daten eines anderen Balkens entsprechen, wobei die Zeit innerhalb der Grenzen des ersten Balkens aus anderen Zeitreihen liegt, ist das erste, was uns in den Sinn kommt, die Erstellung eines Indikators, der OHLC-Linien von Balken aus anderen Zeitrahmen im aktuellen Diagramm anzeigt.

Erstellen und Testen eines Mehrperiodenindikators

Um den Test durchzuführen, verwenden wir den Indikator, den wir im vorherigen Artikel entwickelt haben und speichern ihn in \MQL5\Indikatoren\TestDoEasy\Teil40\ als TestDoEasyPart40.mq5.

Wir können 21 Zeitreihen nach der Anzahl der standardmäßig verfügbaren Diagrammperioden verwenden. Die Einstellungen enthalten den Standardsatz der verwendeten Zeitreihen, während das Diagramm die Schaltflächen anzeigen soll, die den in den Einstellungen gewählten verwendeten Zeitreihen entsprechen. Um übermäßigen Code für die Indikatorpuffer zu vermeiden, weisen Sie die Puffer einfach mit Hilfe des Strukturarrays jeder im Terminal vorhandenen Chartperiode zu.
Die Sichtbarkeit der Pufferlinien auf dem Diagramm und seinen Daten im Indikator-Datenfenster wird durch Aktivieren/Deaktivieren der entsprechenden Schaltfläche aktiviert/deaktiviert. Jedem Zeitrahmen sind zwei Puffer (gezeichnet und berechnet) zuzuordnen. Der berechnete Puffer ermöglicht die Speicherung von Zwischendaten der entsprechenden Zeitreihe. In der aktuellen Implementierung wird der berechnete Puffer jedoch nicht verwendet. Um nicht alle 42 Puffer (21 gezeichnete und 21 berechnete) zu schreiben, werden wir die Struktur erstellen, die die Parameter für jeden der Zeitreihen speichern soll:

  • Das durch den gezeichneten Indikatorpuffer zugewiesene Array
  • Das durch den berechneten Indikatorpuffer zugewiesene Array
  • Die Puffer-ID (Zeitrahmen der Zeitreihe, deren Daten durch den Puffer angezeigt werden sollen)
  • Der Index des Indikatorpuffers bezogen auf das gezeichnete Puffer-Array
  • Der Index des Indikatorpuffers bezogen auf das berechnete Puffer-Array
  • Das Flag für die Verwendung des Puffers im Indikator (Taste gedrückt/nicht gedrückt)
  • Das Flag für die Anzeige des Puffers im Indikator vor der Aktivierung/Deaktivierung der Pufferanzeige durch die Chart-Taste

Über die Indikatoreinstellungen können Sie entscheiden, ob jeder der Zeitrahmen verwendet werden soll und dementsprechend, welche der Zeitreihen ausgewählt wird. Die Chart-Buttons, die entsprechend der ausgewählten Zeitreihe gezeichnet werden, ermöglichen es, die Anzeige der entsprechenden Indikatorpuffer im Chart zu aktivieren/deaktivieren. Das Flag für die Anzeige des Puffers im Indikator, bis seine Anzeige durch die Schaltfläche aktiviert/deaktiviert wird, ermöglicht es uns zu entscheiden, ob die Pufferdaten im Chart nur dann entfernt oder angezeigt werden sollen, wenn die entsprechende Schaltfläche gedrückt wird.

Stellen wir jetzt alle Parameter jedes Indikatorpuffers ein (wir hätten ihn programmatisch einstellen können, aber die aktuelle Methode ist schneller):

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart40.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.comusers/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.comusers/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_chart_window
#property indicator_buffers 43
#property indicator_plots   21
//--- plot M1
#property indicator_label1  " M1"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGray
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot M2
#property indicator_label2  " M2"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot M3
#property indicator_label3  " M3"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//--- plot M4
#property indicator_label4  " M4"
#property indicator_type4  DRAW_LINE
#property indicator_color4  clrGray
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- plot M5
#property indicator_label5  " M5"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrGray
#property indicator_style5  STYLE_SOLID
#property indicator_width5  1
//--- plot M6
#property indicator_label6  " M6"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrGray
#property indicator_style6  STYLE_SOLID
#property indicator_width6  1
//--- plot M10
#property indicator_label7  " M10"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrGray
#property indicator_style7  STYLE_SOLID
#property indicator_width7  1
//--- plot M12
#property indicator_label8  " M12"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrGray
#property indicator_style8  STYLE_SOLID
#property indicator_width8  1
//--- plot M15
#property indicator_label9  " M15"
#property indicator_type9   DRAW_LINE
#property indicator_color9  clrGray
#property indicator_style9  STYLE_SOLID
#property indicator_width9  1
//--- plot M20
#property indicator_label10 " M20"
#property indicator_type10  DRAW_LINE
#property indicator_color10 clrGray
#property indicator_style10 STYLE_SOLID
#property indicator_width10 1
//--- plot M30
#property indicator_label11 " M30"
#property indicator_type11  DRAW_LINE
#property indicator_color11 clrGray
#property indicator_style11 STYLE_SOLID
#property indicator_width11 1
//--- plot H1
#property indicator_label12 " H1"
#property indicator_type12  DRAW_LINE
#property indicator_color12 clrGray
#property indicator_style12 STYLE_SOLID
#property indicator_width12 1
//--- plot H2
#property indicator_label13 " H2"
#property indicator_type13  DRAW_LINE
#property indicator_color13 clrGray
#property indicator_style13 STYLE_SOLID
#property indicator_width13 1
//--- plot H3
#property indicator_label14 " H3"
#property indicator_type14  DRAW_LINE
#property indicator_color14 clrGray
#property indicator_style14 STYLE_SOLID
#property indicator_width14 1
//--- plot H4
#property indicator_label15 " H4"
#property indicator_type15  DRAW_LINE
#property indicator_color15 clrGray
#property indicator_style15 STYLE_SOLID
#property indicator_width15 1
//--- plot H6
#property indicator_label16 " H6"
#property indicator_type16  DRAW_LINE
#property indicator_color16 clrGray
#property indicator_style16 STYLE_SOLID
#property indicator_width16 1
//--- plot H8
#property indicator_label17 " H8"
#property indicator_type17  DRAW_LINE
#property indicator_color17 clrGray
#property indicator_style17 STYLE_SOLID
#property indicator_width17 1
//--- plot H12
#property indicator_label18 " H12"
#property indicator_type18  DRAW_LINE
#property indicator_color18 clrGray
#property indicator_style18 STYLE_SOLID
#property indicator_width18 1
//--- plot D1
#property indicator_label19 " D1"
#property indicator_type19  DRAW_LINE
#property indicator_color19 clrGray
#property indicator_style19 STYLE_SOLID
#property indicator_width19 1
//--- plot W1
#property indicator_label20 " W1"
#property indicator_type20  DRAW_LINE
#property indicator_color20 clrGray
#property indicator_style20 STYLE_SOLID
#property indicator_width20 1
//--- plot MN1
#property indicator_label21 " MN1"
#property indicator_type21  DRAW_LINE
#property indicator_color21 clrGray
#property indicator_style21 STYLE_SOLID
#property indicator_width21 1

//--- classes

Wie wir sehen können, ist die Gesamtzahl der Puffer auf 43 gesetzt, während die Anzahl der gezeichneten Puffer auf 21 ist. Da ich beschlossen habe, zu jedem der gezeichneten Puffer einen berechneten Puffer hinzuzufügen, ist das Ergebnis 21+21=42. Woher der zusätzliche Puffer? Wir benötigen ihn, um Daten aus dem Array time[] von OnCalculate() zu speichern. Da einige Funktionen die Balkenzeit nach Index benötigen, während das Array time[] nur innerhalb des Sichtbarkeitsbereichs von OnCalculate() existiert, ist die einfachste Lösung, um Zeitdaten für jeden Balken des aktuellen Zeitrahmens zu haben, darin, das Array time[] in einem der indikatorberechneten Puffer zu kopieren. Aus diesem Grund habe ich einen weiteren Puffer eingerichtet.

Der Indikator bietet die Möglichkeit, Preise für vier Balken anzuzeigen: Open, High, Low und Close. Das Balkenobjekt hat mehr reelle Eigenschaften:

  • Eröffnungspreis des Balkens (Open)
  • Höchster Preis des Balkens (High)
  • Niedrigster Preis des Balkens (Low)
  • Schlusskurs des Balkens (Close)
  • Kerzengröße
  • Körpergröße der Kerze
  • Hoch des Kerzenkörpers
  • Tief des Kerzenkörpers
  • Oberer Preis des Dochts der Kerze
  • Unterer Preis des Dochts der Kerze

Daher können wir den Wert der Enumeration (ENUM_BAR_PROP_DOUBLE) nicht in den Einstellungen verwenden. Erstellen wir daher eine weitere Enumeration erstellen, die die erforderlichen Eigenschaften aufweist, die mit den Eigenschaften der Enumeration der reellen Eigenschaften von ENUM_BAR_PROP_DOUBLE des Balkenobjekts übereinstimmen, die in den Einstellungen für die Darstellung ausgewählt werden können, und die Makroersetzung mit der Gesamtmenge der verfügbaren Diagrammperioden festlegen:

//--- classes

//--- enums
enum ENUM_BAR_PRICE
  {
   BAR_PRICE_OPEN    =  BAR_PROP_OPEN,    // Bar Open
   BAR_PRICE_HIGH    =  BAR_PROP_HIGH,    // Bar High
   BAR_PRICE_LOW     =  BAR_PROP_LOW,     // Bar Low
   BAR_PRICE_CLOSE   =  BAR_PROP_CLOSE,   // Bar Close
  };
//--- defines
#define PERIODS_TOTAL   (21)              // Total amount of available chart periods
//--- structures

Erstellen wir nun die Datenstruktur eines gezeichneten und eines berechneten Puffers, die für eine einzelne Zeitreihe (Chartperiode) zugewiesen wurden:

//--- structures
struct SDataBuffer
  {
private:
   int               m_buff_id;           // Buffer ID (timeframe)
   int               m_buff_data_index;   // The index of the indicator buffer related to the Data[] array
   int               m_buff_tmp_index;    // The index of the indicator buffer related to the Temp[] array
   bool              m_used;              // The flag of using the buffer in the indicator
   bool              m_show_data;         // The flag of displaying the buffer on the chart before enabling/disabling its display
public:
   double            Data[];              // The array assigned as INDICATOR_DATA by the indicator buffer
   double            Temp[];              // The array assigned as INDICATOR_CALCULATIONS by the indicator buffer
//--- Set indices for the drawn and calculated buffers assigned to the timeframe
   void              SetIndex(const int index)
                       {
                        this.m_buff_data_index=index;
                        this.m_buff_tmp_index=index+PERIODS_TOTAL;
                       }
//--- Methods of setting and returning values of the private structure members
   void              SetID(const int id)              { this.m_buff_id=id;             }
   void              SetUsed(const bool flag)         { this.m_used=flag;              }
   void              SetShowData(const bool flag)     { this.m_show_data=flag;         }
   int               IndexDataBuffer(void)      const { return this.m_buff_data_index; }
   int               IndexTempBuffer(void)      const { return this.m_buff_tmp_index;  }
   int               ID(void)                   const { return this.m_buff_id;         }
   bool              IsUsed(void)               const { return this.m_used;            }
   bool              GetShowDataFlag(void)      const { return this.m_show_data;       }
   void              Print(void);
  };
//--- Display structure data to the journal
void SDataBuffer::Print(void)
  {
   ::Print
     (
      "Buffer[",this.IndexDataBuffer(),"], ID: ",(string)this.ID(),
      " (",TimeframeDescription((ENUM_TIMEFRAMES)this.ID()),
      "), temp buffer index: ",(string)this.IndexTempBuffer(),
      ", used: ",this.IsUsed()
     );
  }
//--- input variables

Die Struktur soll alle Daten für die Arbeit mit einem einzigen Zeitrahmen speichern. Jedem der verwendeten Indikatorzeitrahmen ist eine eigene Struktur zuzuordnen. Das Array der entsprechenden Strukturen ist dafür die optimale Lösung. Legen wir es in dem Block zur Definition der Indikatorpuffer an.

Schreiben wir die Indikatoreingaben:

//--- input variables
/*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   ENUM_BAR_PRICE       InpBarPrice       =  BAR_PRICE_OPEN;                  // Applied bar price
sinput   bool                 InpShowBarTimes   =  false;                           // Show bar time comments
sinput   uint                 InpControlBar     =  1;                               // Control bar
sinput   uint                 InpButtShiftX     =  0;    // Buttons X shift 
sinput   uint                 InpButtShiftY     =  10;   // Buttons Y shift 
sinput   bool                 InpUseSounds      =  true; // Use sounds
//--- indicator buffers

Hier ist alles ähnlich wie bei den Test EAs und den Indikatoren, die ich für jeden Artikel zur Verfügung stelle. Da ich die Arbeit mit einem einzelnen Symbol testen werde, auskommentieren Sie die sinput Modifikatoren in den Symboleinstellungen , die die Variable eine Indikatoreingaben klassifizieren (sinput Modifikator von Variablen sind für die Parameteroptimierung deaktiviert). Daher können diese Parameter in den Einstellungen nicht ausgewählt werden, während der Wert SYMBOLS_MODE_CURRENT der Variable InpModeUsedSymbols zugewiesen wird — wobei nur mit dem aktuellen Symbol gearbeitet wird.
Die Variable InpShowBarTimes erlaubt das Ein-/Ausblenden von Kommentaren auf dem Chart — die Darstellung des Balkens in der aktuellen Chartperiode, der mit dem Balken mit der gleichen Zeit in den Diagrammen der getesteten Zeitreihen übereinstimmt. Die Variable InpControlBar wird verwendet, um den Index des Balkens festzulegen, dessen Wert über die Chartkommentare verfolgt werden kann.

Schließlich schreiben wir die Indikatorpuffer und globalen Variablen:

//--- indicator buffers
SDataBuffer    Buffers[PERIODS_TOTAL];          // Array of the indicator buffer data structures assigned to the timeseries
double         BufferTime[];                    // The calculated buffer for storing and passing data from the time[] array
//--- global variables
CEngine        engine;                          // CEngine library main object
string         prefix;                          // Prefix of graphical object names
bool           testing;                         // Flag of working in the tester
int            used_symbols_mode;               // Mode of working with symbols
string         array_used_symbols[];            // Array of used symbols
string         array_used_periods[];            // Array of used timeframes
//+------------------------------------------------------------------+

Wie Sie sehen, habe ich das oben beschriebene Array der Strukturen als Indikatorpuffer-Definition eingestellt. Bei der Initialisierung eines Indikators werden wir den Strukturarrays Daten zuweisen und die Strukturarrays an die Indikatorpuffer binden. Hier wird der berechnete Puffer definiert zur Speicherung und Weitergabe der Zeit an die Indikatorfunktionen.
Die globalen Variablen des Indikators werden kommentiert und sind, wie ich glaube, recht verständlich.

In OnInit() des Indikators erstellen wir zunächst das Panel mit den Schaltflächen entsprechend den in den Einstellungen gewählten Zeitrahmen. Dann weisen wir alle Indikatorpuffer zu und stellen alle Indikatorpufferparameter in den Strukturen ein, die sich im Array der Indikatorpufferstrukturen befinden:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set indicator global variables
   prefix=engine.Name()+"_";
   testing=engine.IsTester();
   ZeroMemory(rates_data);
   
//--- Initialize DoEasy library
   OnInitDoEasy();

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

//--- Create the button panel
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;

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

   //--- In the loop by the total number of available timeframes,
   for(int i=0;i<PERIODS_TOTAL;i++)
     {
      //--- get the next timeframe
      ENUM_TIMEFRAMES timeframe=TimeframeByEnumIndex(uchar(i+1));
      //--- Bind the drawn indicator buffer by the buffer index equal to the loop index with the structure Data[] array
      SetIndexBuffer(i,Buffers[i].Data);
      //--- set "the empty value" for the Data[] buffer, 
      //--- set the name of the graphical series displayed in the data window for the Data[] buffer
      //--- set the direction of indexing the Data[] drawn buffer as in the timeseries
      PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetString(i,PLOT_LABEL,"Buffer "+TimeframeDescription(timeframe));
      ArraySetAsSeries(Buffers[i].Data,true);
      //--- Setting the drawn buffer according to the button status
      bool state=false;
      //--- Set the name of the button correspondign to the buffer with the loop index and its timeframe
      string name=prefix+"BUTT_"+TimeframeDescription(timeframe);
      //--- If not in the tester, while the chart features the button with the specified name,
      if(!engine.IsTester() && ObjectFind(ChartID(),name)==0)
        {
         //--- set the name of the terminal global variable for storing the button status
         string name_gv=(string)ChartID()+"_"+name;
         //--- if no global variable with such a name is found, create it set to 'false',
         if(!GlobalVariableCheck(name_gv))
            GlobalVariableSet(name_gv,false);
         //--- get the button status from the terminal global variable
         state=GlobalVariableGet(name_gv);
        }
      //--- Set the values for all structure fields
      Buffers[i].SetID(timeframe);
      Buffers[i].SetIndex(i);
      Buffers[i].SetUsed(state);
      Buffers[i].SetShowData(state);
      //--- Set the button status
      ButtonState(name,state);
      //--- Depending on the button status, specify whether the buffer data should be displayed should be displayed in the data window
      PlotIndexSetInteger(i,PLOT_SHOW_DATA,state);
      //--- Bind the calculated indicator buffer by the buffer index from IndexTempBuffer() with the Temp[] array of the structure
      SetIndexBuffer(Buffers[i].IndexTempBuffer(),Buffers[i].Temp,INDICATOR_CALCULATIONS);
      //--- set the direction of indexing the Temp[] calculated buffer as in the timeseries
      ArraySetAsSeries(Buffers[i].Temp,true);
     }
   //--- Bind the calculated indicator buffer by the PERIODS_TOTAL*2 buffer index with the BufferTime[] array of the indicator
   SetIndexBuffer(PERIODS_TOTAL*2,BufferTime,INDICATOR_CALCULATIONS);
   //--- set the direction of indexing the BufferTime[] calculated buffer as in the timeseries
   ArraySetAsSeries(BufferTime,true);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

Hier habe ich alle Zeilen der Schleife kommentiert, bei der die Indikatorpuffer durch das Strukturarray an den Schleifenindex gebunden sind und die übrigen Parameter für jede in jeder Zelle des Strukturarrays gespeicherte Struktur festgelegt sind. Wenn Sie Fragen haben, können Sie diese gerne in den Kommentaren stellen.

Die Schaltfläche funktioniert:

//+------------------------------------------------------------------+
//| Create the buttons panel                                         |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=20,const int shift_y=0)
  {
   int total=ArraySize(array_used_periods);
   uint w=30,h=20,x=InpButtShiftX+1, y=InpButtShiftY+h+1;
   //--- In the loop by the amount of used timeframes
   for(int i=0;i<total;i++)
     {
      //--- create the name of the next button
      string butt_name=prefix+"BUTT_"+array_used_periods[i];
      //--- create a new button with the offset by ((button width + 1) * loop index)
      if(!ButtonCreate(butt_name,x+(w+1)*i,y,w,h,array_used_periods[i],clrGray))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),array_used_periods[i]);
         return false;
        }
     }   
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| Create the button                                                |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Set the terminal's global variable value                         |
//+------------------------------------------------------------------+
bool SetGlobalVariable(const string gv_name,const double value)
  {
//--- If the variable name length exceeds 63 symbols, return 'false'
   if(StringLen(gv_name)>63)
      return false;
   return(GlobalVariableSet(gv_name,value)>0);
  }
//+------------------------------------------------------------------+
//| Return the button status                                         |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| Return the button status by the timeframe name                   |
//+------------------------------------------------------------------+
bool ButtonState(const ENUM_TIMEFRAMES timeframe)
  {
   string name=prefix+"BUTT_"+TimeframeDescription(timeframe);
   return ButtonState(name);
  }
//+------------------------------------------------------------------+
//| Set the button status                                            |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
   if(state)
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240');
   else
      ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240');
  }
//+------------------------------------------------------------------+
//| Track the buttons' status                                        |
//+------------------------------------------------------------------+
void PressButtonsControl(void)
  {
   int total=ObjectsTotal(0,0);
   for(int i=0;i<total;i++)
     {
      string obj_name=ObjectName(0,i);
      if(StringFind(obj_name,prefix+"BUTT_")<0)
         continue;
      PressButtonEvents(obj_name);
     }
  }
//+------------------------------------------------------------------+
//| Handle pressing the buttons                                      |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
//--- Convert button name into its string ID
   string button=StringSubstr(button_name,StringLen(prefix));
//--- Create the button name for the terminal's global variable
   string name_gv=(string)ChartID()+"_"+prefix+button;
//--- Get the button status (pressed/released). If not in the tester,
//--- write the status to the button global variable (1 or 0)
   bool state=ButtonState(button_name);
   if(!engine.IsTester())
      SetGlobalVariable(name_gv,state);
//--- Get the timeframe from the button string ID and
//--- the drawn buffer index by timeframe
   ENUM_TIMEFRAMES timeframe=TimeframeByDescription(StringSubstr(button,5));
   int buffer_index=IndexBuffer(timeframe);
//--- Set the button color depending on its status, 
//--- write its status to the buffer structure depending on the button status (used/not used)
//--- initialize the buffer corresponding to the button timeframe by the buffer index received earlier
   ButtonState(button_name,state);
   Buffers[buffer_index].SetUsed(state);
   if(Buffers[buffer_index].GetShowDataFlag()!=state)
     {
      InitBuffer(buffer_index);
      BufferFill(buffer_index);
      Buffers[buffer_index].SetShowData(state);
     }

//--- Here you can add additional handling of button pressing:
//--- If the button is pressed
   if(state)
     {
      //--- If M1 button is pressed
      if(button=="BUTT_M1")
        {
         
        }
      //--- If button M2 is pressed
      else if(button=="BUTT_M2")
        {
         
        }
      //---
      // Remaining buttons ...
      //---
     }
   //--- Not pressed
   else 
     {
      //--- M1 button
      if(button=="BUTT_M1")
        {
         
        }
      //--- M2 button
      if(button=="BUTT_M2")
        {
         
        }
      //---
      // Remaining buttons ...
      //---
     }
//--- re-draw the chart
   ChartRedraw();
  }
//+------------------------------------------------------------------+

Alle diese Funktionen sind recht einfach und überschaubar, außerdem sind einige ihrer Zeilen kommentiert.

Lassen Sie uns einen Blick auf OnCalculate() des Indikators werfen:

//+------------------------------------------------------------------+
//| 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
   CopyData(rates_data,rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Handle the Calculate event in the library
   engine.OnCalculate(rates_data);

//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the timer
      PressButtonsControl();        // Button pressing control
      EventsHandling();             // Working with events
     }

//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Set OnCalculate arrays as timeseries
   ArraySetAsSeries(open,true);
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(low,true);
   ArraySetAsSeries(close,true);
   ArraySetAsSeries(time,true);
   ArraySetAsSeries(tick_volume,true);
   ArraySetAsSeries(volume,true);
   ArraySetAsSeries(spread,true);

//--- Setting buffer arrays as timeseries

//--- Check for the minimum number of bars for calculation
   if(rates_total<2 || Point()==0) return 0;
   
//--- Display reference data on bar open time
   if(InpShowBarTimes)
     {
      string txt="";
      int total=ArraySize(array_used_periods);
      //--- In the loop by the amount of used timeframes
      for(int i=0;i<total;i++)
        {
         //--- get the next timeframe, buffer index and timeseries object by timeframe
         ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[i]);
         int buffer_index=IndexBuffer(timeframe);
         CSeriesDE *series=engine.SeriesGetSeries(NULL,timeframe);
         //--- If failed to get the timeseries or the buffer is not used (the button is released), move on to the next one
         if(series==NULL || !Buffers[buffer_index].IsUsed())
            continue;
         //--- Get the reference bar from the timeseries list
         CBar *bar=series.GetBar(InpControlBar);
         if(bar==NULL)
            continue;
         //--- Collect data for the comment text
         string t1=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         string t2=TimeframeDescription(bar.Timeframe());
         string t3=(string)InpControlBar;
         string t4=TimeToString(bar.Time());
         string t5=(string)bar.Index((ENUM_TIMEFRAMES)Period());
         //--- Set the comment text depending on the terminal language
         string tn=TextByLanguage
           (
            "Бар на "+t1+", соответствующий бару "+t2+"["+t3+"] со временеи открытия "+t4+", расположен на баре "+t5,
            "The bar on "+t1+", corresponding to the "+t2+"["+t3+"] bar since the opening time of "+t4+", is located on bar "+t5
           );
         txt+=tn+"\n";
        }
      //--- Display the comment on the chart
      Comment(txt);
     }

//--- Check and calculate the number of calculated bars
   int limit=rates_total-prev_calculated;

//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      InitBuffersAll();
     }
//--- Prepare data

//--- Calculate the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      BufferTime[i]=(double)time[i];
      CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Wenn der Parameter "Show bar time comments" ("Anzeige der Kommentare", die Variable InpShowBarTimes) auf true gesetzt ist, zeigt der Codeblock Daten auf dem Balken des aktuellen Diagramms an, der in der Variablen InpControlBar ("ControlBar") angegeben ist, und zeigt damit an, dass er mit dem Balken in den Zeitrahmen aller verwendeten Zeitreihen übereinstimmt.

Wenn der berechnete Wert von limit Eins überschreitet (was bedeutet, dass die gesamte Historie aufgrund von Änderungen in der Historie neu gezeichnet werden muss), setzen wir limit gleich dem Beginn der Historie im aktuellen Diagramm und rufen die Funktion zur Initialisierung aller Indikatorpuffer auf.

Der Indikator wird ab limit (unter normalen Bedingungen ist er gleich 1 (neuer Balken) oder Null — berechnen des aktuellen Balkens) herunter bis Null berechnet.
In der Hauptschleife zur Indikatorberechnung füllen wir den berechneten Zeitpuffer aus dem Array time[] (wir benötigen den Zeitpuffer für andere Indikatorfunktionen, die die Zeit per Index erhalten, wenn das Array time[] nicht verfügbar ist) und rufen die Funktion der Berechnung eines einzelnen Balkens für alle verwendeten Indikatorpuffer auf.

Die Funktion der Initialisierung der Indikatorpuffer:

//+------------------------------------------------------------------+
//| Initialize the timeseries and the appropriate buffers by index   |
//+------------------------------------------------------------------+
bool InitBuffer(const int buffer_index)
  {
//--- Leave if the wrong index is passed
   if(buffer_index==WRONG_VALUE)
      return false;
Initialize the variables using the "Not rendered" drawing style and disable the display in the data window
   int draw_type=DRAW_NONE;
   bool show_data=false;
//--- If the buffer is used (button pressed)
//--- Set the "Line" drawing style for variables and enable display in the data window
   if(Buffers[buffer_index].IsUsed())
     {
      draw_type=DRAW_LINE;
      show_data=true;
     }
//--- Set the drawing style and display in the data window for the buffer by its index
   PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_DRAW_TYPE,draw_type);
   PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_SHOW_DATA,show_data);
//--- Initialize the calculated buffer using zero, while the drawn one is initialized using the "empty" value 
   ArrayInitialize(Buffers[buffer_index].Temp,0);
   ArrayInitialize(Buffers[buffer_index].Data,EMPTY_VALUE);
   return true;
  }
//+------------------------------------------------------------------+
//|Initialize the timeseries and the appropriate buffers by timeframe|
//+------------------------------------------------------------------+
bool InitBuffer(const ENUM_TIMEFRAMES timeframe)
  {
   return InitBuffer(IndexBuffer(timeframe));
  }
//+------------------------------------------------------------------+
//| Initialize all timeseries and the appropriate buffers            |
//+------------------------------------------------------------------+
void InitBuffersAll(void)
  {
//--- Initialize the next buffer in the loop by the total number of chart periods
   for(int i=0;i<PERIODS_TOTAL;i++)
      if(!InitBuffer(i))
         continue;
  }
//+------------------------------------------------------------------+

Die Funktion der Berechnung eines einzelnen, spezifizierten Balkens aller verwendeten Indikatorpuffer (für die die Taste gedrückt wird):

//+------------------------------------------------------------------+
//| Calculating a single bar of all active buffers                   |
//+------------------------------------------------------------------+
void CalculateSeries(const ENUM_BAR_PROP_DOUBLE property,const int index,const datetime time)
  {
//--- Get the next buffer in the loop by the total number of chart periods
   for(int i=0;i<PERIODS_TOTAL;i++)
     {
      //--- if the buffer is not used (the button is released), move on to the next one
      if(!Buffers[i].IsUsed())
         continue;
      //--- get the timeseries object by the buffer timeframe
      CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[i].ID());
      //--- if the timeseries is not received
      //--- or the bar index passed to the function is beyond the total number of bars in the timeseries, move on to the next buffer
      if(series==NULL || index>series.GetList().Total()-1)
         continue;
      //--- get the bar object from the timeseries corresponding to the one passed to the bar time function on the current chart
      CBar *bar=engine.SeriesGetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,time,NULL,series.Timeframe());
      if(bar==NULL)
         continue;
      //--- get the specified property from the obtained bar and
      //--- call the function of writing the value to the buffer by i index
      double value=bar.GetProperty(property);
      SetBufferData(i,value,index,bar);
     }
  }
//+------------------------------------------------------------------+

Die Funktion des Schreibens der Eigenschaften des Balkenobjekts in den Indikatorpuffer durch mehrere Balkenindizes im aktuellen Diagramm:

//+------------------------------------------------------------------+
//| Write data on a single bar to the specified buffer               |
//+------------------------------------------------------------------+
void SetBufferData(const int buffer_index,const double value,const int index,const CBar *bar)
  {
//--- Get the bar index by its time falling within the time limits on the current chart
   int n=iBarShift(NULL,PERIOD_CURRENT,bar.Time());
//--- If the passed index on the current chart (index) is less than the calculated time of bar start on another timeframe
   if(index<n)
      //--- in the loop from the n bar on the current chart to zero
      while(n>WRONG_VALUE && !IsStopped())
        {
         //--- fill in the n index buffer with the 'value' passed to the function (0 - EMPTY_VALUE)
         //--- and decrease the n value
         Buffers[buffer_index].Data[n]=(value>0 ? value : EMPTY_VALUE);
         n--;
        }
//--- If the passed index on the current chart (index) is not less than the calculated time of bar start on another timeframe
//--- Set 'value' for the buffer by the 'index' passed to the function (0 - EMPTY_VALUE)
   else
      Buffers[buffer_index].Data[index]=(value>0 ? value : EMPTY_VALUE);
  }
//+------------------------------------------------------------------+

Für die korrekte Anzeige von Balkendaten aus einem anderen Zeitrahmen im aktuellen Diagramm suchen Sie den Beginn der angegebenen Kerzen- (Balken-) Periode im aktuellen Chart und füllen alle Pufferindizes im aktuellen Chart mit dem Wert des Balkens einer anderen Periode aus. Dies ist die Aufgabe der Funktion.

Wenn Sie die Schaltfläche zur Aktivierung des Zeitrahmens drücken, müssen wir entweder den entsprechenden angezeigten Puffer mit einem leeren Wert füllen (wenn die Schaltfläche losgelassen wird) oder alle Daten des durch die Schaltfläche angezeigten Puffers vollständig neu berechnen (wenn die Schaltfläche gedrückt wird). Die Initialisierungsfunktion der Puffer löscht die Daten, während die folgende Funktion den Puffer mit den angegebenen Zeitreihendaten füllt:

//+------------------------------------------------------------------+
//| Fill in the entire buffer with historical data                   |
//+------------------------------------------------------------------+
void BufferFill(const int buffer_index)
  {
//--- Leave if the wrong index is passed
   if(buffer_index==WRONG_VALUE)
      return;
//--- Leave if the buffer is not used (the button is released)
   if(!Buffers[buffer_index].IsUsed())
      return;
//--- Get the timeseries object by the buffer timeframe
   CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[buffer_index].ID());
   if(series==NULL)
      return;
//--- If the buffer belongs to the current chart, copy the bar data from the timeseries to the buffer
   if(Buffers[buffer_index].ID()==Period())
      series.CopyToBufferAsSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,Buffers[buffer_index].Data,EMPTY_VALUE);
//--- Otherwise, calculate each next timeseries bar and write it to the buffer in the loop by the number of the current chart bars
   else 
      for(int i=rates_data.rates_total-1;i>WRONG_VALUE && !IsStopped();i--)
         CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,(datetime)BufferTime[i]);
  }
//+------------------------------------------------------------------+

Der vollständige Indikatorcode ist in den unten angehängten Dateien enthalten.

Bitte beachten Sie, dass dieser Testindikator in MQL5 entwickelt wurde. Er funktioniert auch mit MQL4, aber nicht auf normale Weise — beim Drücken der entsprechenden Schaltfläche wird die aktuelle Chartperiode nicht angezeigt. Er wird nur angezeigt, wenn ein weiterer Zeitrahmen aktiviert wird. Bei der Einstellung von nicht standardmäßigen Chartperioden in den Einstellungen von MetaTrader 4 wartet der Indikator endlos auf ihre Synchronisierung.
Auch werden einige Daten im Terminaldatenfenster falsch angezeigt — alle Indikatorpuffer werden angezeigt (einschließlich der berechneten), was natürlich ist, da nicht alle MQL5-Funktionen in MQL4 funktionieren und durch ihre MQL4-Gegenstücke ersetzt werden müssten.
Darüber hinaus kann der Indikator auch Änderungen der historischen Daten in MetaTrader 5 falsch handhaben, da der Indikator zu Testzwecken gemacht wird, nämlich um den Betrieb im Mehrperiodenmodus zu überprüfen. Alle festgestellten Fehler sollen in den folgenden Artikeln schrittweise behoben werden. Wenn alle Unzulänglichkeiten in MetaTrader 5 beseitigt sind, soll die Bibliothek um die Indikatoren von MetaTrader 4 angepasst werden.

Kompilieren Sie den Indikator und starten Sie ihn auf dem Chart:


Wie wir sehen können, zeigt der Datenpuffer von М5 auf М15 die Schlusskurse der М5 Balken in einem Drittel der aktuellen Chartkerzen an, was verständlich ist, da ein einziger М15 Balken drei М5 Balken enthält und der Schlusskurs des М5 Balkens auf dem М15 Balken angezeigt wird.

Starten Sie den Indikator im Tester mit dem aktivierten Parameter zur Anzeige der Zeitreihendaten der aktuellen Chartperiode:



Was kommt als Nächstes?

Im nächsten Artikel werden wir unsere Arbeit zur Behandlung von Bibliothekszeitreihenobjekten in Indikatoren fortsetzen.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie testen und herunterladen können.
Schreiben Sie Ihre Fragen und Vorschläge in den Kommentaren.

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



Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/7771

Beigefügte Dateien |
MQL5.zip (3700.41 KB)
MQL4.zip (3700.4 KB)
Kontinuierliche Walk-Forward-Optimierung (Teil 6): Logikteil und die Struktur des Auto-Optimizers Kontinuierliche Walk-Forward-Optimierung (Teil 6): Logikteil und die Struktur des Auto-Optimizers

Wir haben bereits früher die Schaffung einer automatischen Walk-Forward-Optimierung in Betracht gezogen. Dieses Mal werden wir zur internen Struktur des Auto-Optimizers übergehen. Der Artikel wird für all diejenigen nützlich sein, die mit dem erstellten Projekt weiterarbeiten und es modifizieren möchten, sowie für diejenigen, die die Programmlogik verstehen möchten. Der aktuelle Artikel enthält UML-Diagramme, die die interne Struktur des Projekts und die Beziehungen zwischen den Objekten darstellen. Er beschreibt auch den Prozess des Optimierungsstarts, enthält jedoch keine Beschreibung des Implementierungsprozesses des Optimizers.

MQL als Darstellungsmittel für graphische Schnittstellen von MQL-Programmen. Teil 2 MQL als Darstellungsmittel für graphische Schnittstellen von MQL-Programmen. Teil 2

In diesem Beitrag wird die neue Konzeption zur Beschreibung der Fenster-Schnittstelle von MQL-Programmen anhand der Strukturen von MQL weiter überprüft. Die automatische Erstellung einer GUI auf der Grundlage des MQL-Markups bietet zusätzliche Funktionalität für die Zwischenspeicherung und dynamische Generierung der Elemente und die Steuerung der Stile und neuen Schemata für die Verarbeitung der Ereignisse. Beigefügt ist eine erweiterte Version der Standardbibliothek von Steuerelementen.

Entwicklung eines plattformübergreifenden Grid-EAs: Testen eines Mehrwährungs-EA Entwicklung eines plattformübergreifenden Grid-EAs: Testen eines Mehrwährungs-EA

Die Märkte brachen innerhalb eines Monats um mehr als 30% ein. Dies scheint der beste Zeitpunkt für die Prüfung von Expertenberatern mit Grid- und Martingal-Basis zu sein. Dieser Artikel ist eine ungeplante Fortsetzung der Serie "Entwicklung eines plattformübergreifenden Grid-EAs". Der aktuelle Markt bietet eine Gelegenheit, einen Stresstest für den Grid-EA zu arrangieren. Lassen Sie uns also diese Gelegenheit nutzen und unseren Expert Advisor testen.

Kontinuierliche Walk-Forward-Optimierung (Teil 7): Einbinden des logischen Teils des Auto-Optimizer mit Grafiken und Steuerung Kontinuierliche Walk-Forward-Optimierung (Teil 7): Einbinden des logischen Teils des Auto-Optimizer mit Grafiken und Steuerung

Dieser Artikel beschreibt die Verbindung des grafischen Teils des Auto-Optimizers mit seinem logischen Teil. Er betrachtet den Prozess des Optimierungsstarts, von einem Tastenklick bis zur Aufgabenumleitung zum Optimierungsmanager.