Zeitreihen in der Bibliothek DoEasy (Teil 47): Standardindikatoren für mehrere Symbole und Perioden

13 November 2020, 08:29
Artyom Trishkin
0
162

Inhalt


Konzept

Ich glaube, jeder kennt die Standardindikatoren der normalen Terminalauslieferung. Diese Indikatoren verwenden das aktuelle Symbol/Periodenchart, um Daten für dasselbe Symbol/Periode anzuzeigen.
Was ich in diesem Artikel zu implementieren beginne, ist die Möglichkeit, nutzerdefinierte Indikatoren zu erstellen, die Daten zu allen Standardindikatoren anzeigen, die für die angegebenen Symbole/Perioden auf dem aktuellen Symbol/Periodenchart berechnet wurden.

In diesem Artikel werde ich das Erstellen der benötigten Methoden zum Erstellen eines Nutzers auf der Grundlage des Standardindikators AC (Accelerator Oszillator) besprechen. Alle Methoden sollen auch für andere Standardindikatoren verwendbar sein, wenn auch mit geringfügigen Modifikationen - ich werde sie in den folgenden Artikeln implementieren.

Fügen wir neue Eigenschaften für das Pufferobjekt hinzu, um Pufferobjekte für die Arbeit mit den Daten des Standardindikators zu erstellen und zu identifizieren:

  • Identifikator mehrerer Puffer eines Indikators ermöglicht die Identifizierung und Auswahl aller Pufferobjekte, die zu einem einzigen Standardindikator gehören, unter Verwendung dieser Puffer. Ein nutzerdefinierter Indikator kann mehrere identische Standardindikatoren mit unterschiedlichen Parametern anwenden (bei der Erstellung eines komplexen benutzerdefinierten Indikators, der auf mehreren Standardindikatoren basiert). Diese Kennung ermöglicht es, jedes der angewendeten Pufferobjekte durch seine Zugehörigkeit zum Standardindikator zu definieren.
  • Handle eines Indikators unter Verwendung eines Puffers — jedes Pufferobjekt, das zur Berechnung des Standardindikators verwendet wird, muss das Handle des erstellten Standardindikators aufweisen, um mit ihm von jedem zu diesem Indikator gehörenden Pufferobjekt aus arbeiten zu können.
  • Typ eines Indikators mit einem Puffer — es wird hier der Indikatortyp aus der Enumeration ENUM_INDICATOR angegeben. Dies ermöglicht auch die Definition und Auswahl von Pufferobjekten nach ihrer Zugehörigkeit zum Standardindikatortyp.
  • Name eines Indikators, der einen Puffer verwendet — hier soll der Name eines Standardindikators gespeichert werden, der ein Pufferobjekt zur Anzeige seiner Beschreibung verwendet.

Zusätzlich zum Erstellen einer Datenbank für die Arbeit mit den Daten des Standardindikators werde ich die Objekt- und Zeitreihenklassen "neuer Balken" (new bar) leicht verbessern, um fehlende Balken der Historie zu verfolgen und das Ereignis "fehlende Balken" (Skipped bars) in das Programm zu senden.
Im Falle eines Verbindungsverlustes, der Aktivierung/Deaktivierung des Schlafmodus und anderer anormaler Ereignisse, die Zeit zur Wiederherstellung benötigen, können wir sehen, dass einige Balken in der Bibliotheksdatenbank fehlen werden, nachdem das Programm seinen Betrieb wieder aufgenommen hat. Lassen Sie uns die Methoden erstellen, die die Anzahl der fehlenden Balken verfolgen und das Ereignis "fehlende Balken" an das Programm senden, so dass Nutzer in der Lage sind, mit einer solchen Situation in ihren Programmen umzugehen.


Verbesserung der Bibliothek der Klasse

Fügen wir zunächst die Daten für die Anzeige von Nachrichten zu \MQL5\Include\DoEasy\Datas.mqh hinzu.

Add new message IDs:

   MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT,          // Index of the next drawn buffer
   MSG_LIB_TEXT_BUFFER_TEXT_ID,                       // Indicator buffer ID
   MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE,               // Handle of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE,                 // Type of an indicator using a buffer
   MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME,                // Buffer (timeframe) data period
   MSG_LIB_TEXT_BUFFER_TEXT_STATUS,                   // Buffer status
   MSG_LIB_TEXT_BUFFER_TEXT_TYPE,                     // Buffer type
   MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE,                   // Active
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE,               // Arrow code
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT,              // The vertical shift of the arrows
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN,               // The number of initial bars that are not drawn and values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE,                // Graphical construction type
   MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA,                // Display construction values in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_SHIFT,                    // Indicator graphical construction shift by time axis in bars
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE,               // Line style
   MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH,               // Line width
   MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE,               // Arrow size
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM,                // Number of colors
   MSG_LIB_TEXT_BUFFER_TEXT_COLOR,                    // Drawing color
   MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE,              // Empty value for plotting where nothing will be drawn
   MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL,                   // Buffer symbol
   MSG_LIB_TEXT_BUFFER_TEXT_LABEL,                    // Name of the graphical indicator series displayed in DataWindow
   MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME,                 // Name of an indicator using a buffer
   

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

   {"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"},
   {"Идентификатор буферов индикатора","Indicator Buffer Id"},
   {"Хэндл индикатора, использующего буфер","Indicator handle that uses buffer"},
   {"Тип индикатора, использующего буфер","Indicator type that uses buffer"},
   {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"},
   {"Статус буфера","Buffer status"},
   {"Тип буфера","Buffer type"},
   {"Активен","Active"},
   {"Код стрелки","Arrow code"},
   {"Смещение стрелок по вертикали","Vertical shift of arrows"},
   {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in DataWindow"},
   {"Тип графического построения","Type of graphical construction"},
   {"Отображение значений построения в окне DataWindow","Display construction values in DataWindow"},
   {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along time axis in bars"},
   {"Стиль линии отрисовки","Drawing line style "},
   {"Толщина линии отрисовки","Thickness of drawing line"},
   {"Размер значка стрелки","Arrow icon size"},
   {"Количество цветов","Number of colors"},
   {"Цвет отрисовки","Index of buffer containing drawing color"},
   {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"},
   {"Символ буфера","Buffer Symbol"},
   {"Имя индикаторной графической серии, отображаемое в окне DataWindow","Name of indicator graphical series to display in DataWindow"},
   {"Наименование индикатора, использующего буфер","Name of indicator that uses buffer"},
   {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"},
   {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"},
   {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},


Machen wir alle notwendigen Ergänzungen für die aktuellen Aufgaben in \MQL5\Include\DoEasy\Defines.mqh.

Wir ändern im Abschnitt "Macro substitutions" den Namen der Konstante, die den Wert der Standard-Handelsversuche speichert, in eine informativere Konstante:

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
//--- Describe the function with the error line number
#define DFUN_ERR_LINE                  (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                           (__FUNCTION__+": ")        // "Function description"
#define COUNTRY_LANG                   ("Russian")                // Country language
#define END_TIME                       (D'31.12.3000 23:59:59')   // End date for account history data requests
#define TIMER_FREQUENCY                (16)                       // Minimal frequency of the library timer in milliseconds
#define TOTAL_TRADE_TRY                (5)                        // Default number of trading attempts
#define IND_COLORS_TOTAL               (64)                       // Maximum possible number of indicator buffer colors
#define IND_BUFFERS_MAX                (512)                      // Maximum possible number of indicator buffers
//--- Standard sounds

Zuvor hieß die Konstante TOTAL_TRY, was nicht aussagekräftig war. Da wir möglicherweise andere Konstanten haben, die die Anzahl der Versuche angeben, ist es informativer, die Zugehörigkeit der Versuche zu einer bestimmten Aktion (hier ist es "TRADE" — Zugehörigkeit zu Handelsversuchen) zum Konstantennamen hinzuzufügen. Es erspart uns die Notwendigkeit, den Namen der Konstanten zu ändern, wenn wir neue Konstanten für andere "Anzahl der Versuche" hinzufügen.

Hinzufügen eines neuen Ereignisses zur Enumeration der möglichen Zeitreihen-Ereignisse:

//+------------------------------------------------------------------+
//| List of possible timeseries events                               |
//+------------------------------------------------------------------+
enum ENUM_SERIES_EVENT
  {
   SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE,        // no event
   SERIES_EVENTS_NEW_BAR,                                   // "New bar" event
   SERIES_EVENTS_MISSING_BARS,                              // "Bars skipped" event
  };
#define SERIES_EVENTS_NEXT_CODE  (SERIES_EVENTS_MISSING_BARS+1)   // Code of the next event after the "Bars skipped" event
//+------------------------------------------------------------------+

Entsprechend basiert der Code des nächsten Ereignisses nun auf einer neuen Konstanten.

Das Hinzufügen neuer Eigenschaften zum Pufferobjekt habe ich bereits erwähnt. Setzen wir sie in den Enumerationen der Eigenschaften des Pufferobjekts integer und string:

//+------------------------------------------------------------------+
//| Buffer integer properties                                        |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_INTEGER
  {
   BUFFER_PROP_INDEX_PLOT = 0,                              // Plotted buffer serial number
   BUFFER_PROP_STATUS,                                      // Buffer status (by drawing style) from the ENUM_BUFFER_STATUS enumeration
   BUFFER_PROP_TYPE,                                        // Buffer type (from the ENUM_BUFFER_TYPE enumeration)
   BUFFER_PROP_TIMEFRAME,                                   // Buffer period data (timeframe)
   BUFFER_PROP_ACTIVE,                                      // Buffer usage flag
   BUFFER_PROP_DRAW_TYPE,                                   // Graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   BUFFER_PROP_ARROW_CODE,                                  // Arrow code for DRAW_ARROW style
   BUFFER_PROP_ARROW_SHIFT,                                 // The vertical shift of the arrows for DRAW_ARROW style
   BUFFER_PROP_LINE_STYLE,                                  // Line style
   BUFFER_PROP_LINE_WIDTH,                                  // Line width
   BUFFER_PROP_DRAW_BEGIN,                                  // The number of initial bars that are not drawn and values in DataWindow
   BUFFER_PROP_SHOW_DATA,                                   // Flag of displaying construction values in DataWindow
   BUFFER_PROP_SHIFT,                                       // Indicator graphical construction shift by time axis in bars
   BUFFER_PROP_COLOR_INDEXES,                               // Number of colors
   BUFFER_PROP_COLOR,                                       // Drawing color
   BUFFER_PROP_INDEX_BASE,                                  // Base data buffer index
   BUFFER_PROP_INDEX_NEXT_BASE,                             // Index of the array to be assigned as the next indicator buffer
   BUFFER_PROP_INDEX_NEXT_PLOT,                             // Index of the next drawn buffer
   BUFFER_PROP_ID,                                          // ID of multiple buffers of a single indicator
   BUFFER_PROP_IND_HANDLE,                                  // Handle of an indicator using a buffer
   BUFFER_PROP_IND_TYPE,                                    // Type of an indicator using a buffer
   BUFFER_PROP_NUM_DATAS,                                   // Number of data buffers
   BUFFER_PROP_INDEX_COLOR,                                 // Color buffer index
  }; 
#define BUFFER_PROP_INTEGER_TOTAL (23)                      // Total number of integer bar properties
#define BUFFER_PROP_INTEGER_SKIP  (2)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+
//| Buffer real properties                                           |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_DOUBLE
  {
   BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL,     // Empty value for plotting where nothing will be drawn
  }; 
#define BUFFER_PROP_DOUBLE_TOTAL  (1)                       // Total number of real buffer properties
#define BUFFER_PROP_DOUBLE_SKIP   (0)                       // Number of buffer properties not used in sorting
//+------------------------------------------------------------------+
//| Buffer string properties                                         |
//+------------------------------------------------------------------+
enum ENUM_BUFFER_PROP_STRING
  {
   BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Buffer symbol
   BUFFER_PROP_LABEL,                                       // Name of the graphical indicator series displayed in DataWindow
   BUFFER_PROP_IND_NAME,                                    // Name of an indicator using a buffer
  };
#define BUFFER_PROP_STRING_TOTAL  (3)                       // Total number of string buffer properties
//+------------------------------------------------------------------+

Erhöhen Sie die Gesamtzahl der ganzzahligen Eigenschaften von 20 auf 23, sowie die Anzahl der String-Eigenschaften von 2 auf 3.

Da wir neue Eigenschaften hinzugefügt haben, müssen wir auch die Möglichkeit hinzufügen, nach diesen Eigenschaften zu sortieren und auszuwählen.
Fügen wir noch neue Sortiertypen für Pufferobjekte zur Enumeration der möglichen Sortierkriterien hinzu:

//+------------------------------------------------------------------+
//| Possible buffer sorting criteria                                 |
//+------------------------------------------------------------------+
#define FIRST_BUFFER_DBL_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP)
#define FIRST_BUFFER_STR_PROP          (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP)
enum ENUM_SORT_BUFFER_MODE
  {
//--- Sort by integer properties
   SORT_BY_BUFFER_INDEX_PLOT = 0,                           // Sort by the plotted buffer serial number
   SORT_BY_BUFFER_STATUS,                                   // Sort by buffer drawing style (status) from the ENUM_BUFFER_STATUS enumeration
   SORT_BY_BUFFER_TYPE,                                     // Sort by buffer type (from the ENUM_BUFFER_TYPE enumeration)
   SORT_BY_BUFFER_TIMEFRAME,                                // Sort by the buffer data period (timeframe)
   SORT_BY_BUFFER_ACTIVE,                                   // Sort by the buffer usage flag
   SORT_BY_BUFFER_DRAW_TYPE,                                // Sort by graphical construction type (from the ENUM_DRAW_TYPE enumeration)
   SORT_BY_BUFFER_ARROW_CODE,                               // Sort by the arrow code for DRAW_ARROW style
   SORT_BY_BUFFER_ARROW_SHIFT,                              // Sort by the vertical shift of the arrows for DRAW_ARROW style
   SORT_BY_BUFFER_LINE_STYLE,                               // Sort by the line style
   SORT_BY_BUFFER_LINE_WIDTH,                               // Sort by the line width
   SORT_BY_BUFFER_DRAW_BEGIN,                               // Sort by the number of initial bars that are not drawn and values in DataWindow
   SORT_BY_BUFFER_SHOW_DATA,                                // Sort by the flag of displaying construction values in DataWindow
   SORT_BY_BUFFER_SHIFT,                                    // Sort by the indicator graphical construction shift by time axis in bars
   SORT_BY_BUFFER_COLOR_INDEXES,                            // Sort by a number of attempts
   SORT_BY_BUFFER_COLOR,                                    // Sort by the drawing color
   SORT_BY_BUFFER_INDEX_BASE,                               // Sort by the basic data buffer index
   SORT_BY_BUFFER_INDEX_NEXT_BASE,                          // Sort by the index of the array to be assigned as the next indicator buffer
   SORT_BY_BUFFER_INDEX_NEXT_PLOT,                          // Sort by the index of the next drawn buffer
   SORT_BY_BUFFER_ID,                                       // Sort by ID of multiple buffers of a single indicator
   SORT_BY_BUFFER_IND_HANDLE,                               // Sort by handle of an indicator using a buffer
   SORT_BY_BUFFER_IND_TYPE,                                 // Sort by type of an indicator using a buffer
//--- Sort by real properties
   SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP,      // Sort by the empty value for plotting where nothing will be drawn
//--- Sort by string properties
   SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP,           // Sort by the buffer symbol
   SORT_BY_BUFFER_LABEL,                                    // Sort by the name of the graphical indicator series displayed in DataWindow
   SORT_BY_BUFFER_IND_NAME,                                 // Sort by name of an indicator using a buffer
  };
//+------------------------------------------------------------------+


Um fehlende Balken (zum Beispiel nach einem Verbindungsverlust) zu erkennen, müssen wir die Objektklasse "neuer Balken" in \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh leicht verbessern. Alles, was wir tun müssen, ist die Anzahl der Balken zwischen den beiden Ereignissen "neuer Balken" zuzählen. Ein Wert von mehr als 1 zeigt an, dass historische Balken übersprungen oder auf dem Server überhaupt nicht vorhanden sind (diese Situation wurde noch nicht besprochen).

Fügen Sie im 'pivate' Teil der Klasse vier neue Klassenvariablen hinzu, um die Zeit des vorhergehenden Ereignisse "neuer Balken" für ein manuelles und ein automatischesZeitmanagement zu speichern, sowie für die Speicherung der Anzahl der Sekunden und Balken zwischen den beiden Ereignissen "neuer Balken".

//+------------------------------------------------------------------+
//| "New bar" object class                                           |
//+------------------------------------------------------------------+
class CNewBarObj : public CBaseObj
  {
private:
   string            m_symbol;                                    // Symbol
   ENUM_TIMEFRAMES   m_timeframe;                                 // Timeframe
   datetime          m_new_bar_time;                              // New bar time for auto time management
   datetime          m_prev_time;                                 // Previous time for auto time management
   datetime          m_new_bar_time_manual;                       // New bar time for manual time management
   datetime          m_prev_time_manual;                          // Previous time for manual time management
   datetime          m_prev_new_bar_time;                         // Previous new bar time for auto time management
   datetime          m_prev_new_bar_time_manual;                  // Previous new bar time for manual time management
   long              m_seconds_between;                           // Number of seconds between two "New bar" events
   int               m_bars_between;                              // Number of bars between two "New bar" events
//--- Return the current bar data
   datetime          GetLastBarDate(const datetime time);
public:

Im 'public' Teil der Klasse geben wir den Methoden zum Setzen und Zurückgeben des Objekt-Zeitrahmens (Periode wurde bereits früher verwendet, es ist jedoch informativer, Zeitrahmen für die Speicherung eines Zeitrahmens zu verwenden) neue Namen und fügen neue Methoden zum Zurückgeben von Werten neu deklarierter Variablen hinzu:

public:
//--- Set (1) symbol and (2) timeframe
   void              SetSymbol(const string symbol)               { this.m_symbol=(symbol==NULL || symbol==""   ? ::Symbol() : symbol);                     }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe){ this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); }
//--- Save the new bar time during the manual time management
   void              SaveNewBarTime(const datetime time)          { this.m_prev_time_manual=this.GetLastBarDate(time);                                      }
//--- Return (1) symbol and (2) timeframe
   string            Symbol(void)                           const { return this.m_symbol;             }
   ENUM_TIMEFRAMES   Timeframe(void)                        const { return this.m_timeframe;          }
//--- Return (1) new bar time, (2) previous new bar time, number of (3) seconds, (4) number of bars between the two last events
   datetime          TimeNewBar(void)                       const { return this.m_new_bar_time;       }
   datetime          TimePrevNewBar(void)                   const { return this.m_prev_new_bar_time;  }
   long              SecondsBetweenNewBars(void)            const { return this.m_seconds_between;    }
   int               BarsBetweenNewBars(void)               const { return this.m_bars_between;       }
//--- Return the new bar opening flag during the time (1) auto, (2) manual management
   bool              IsNewBar(const datetime time);
   bool              IsNewBarManual(const datetime time);
//--- Constructors
                     CNewBarObj(void) : m_symbol(::Symbol()),
                                        m_timeframe((ENUM_TIMEFRAMES)::Period()),
                                        m_prev_time(0),m_new_bar_time(0),
                                        m_prev_time_manual(0),m_new_bar_time_manual(0) {}
                     CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe);
  };
//+------------------------------------------------------------------+

In der Initialisierungsliste des paramterischen Klassenkonstruktors setzen Sie Initialisierungswerte für die Anzahl der Sekunden und Balken, während andere neue Variablen im Hauptteil des Konstruktors mit Null initialisiert werden:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),
                                                                              m_timeframe(timeframe),
                                                                              m_seconds_between(0),
                                                                              m_bars_between(0)
  {
   this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0;
  }
//+------------------------------------------------------------------+

In der Methode, die das Flag zur Eröffnung eines neuen Balken während der automatischen Zeitverwaltung zurückgibt, speichern Sie die Zeit des vorherigen neuen Balken, wenn sich ein neuer Balken gebildet hat, und berechnen Sie die Anzahl der Sekunden und Balken zwischen den beiden Ergebnissen "neuer Balken":

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBar(const datetime time)
  {
//--- Get the current bar time
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- If the previous and current time are equal to zero, this is the first launch
   if(this.m_prev_time+this.m_new_bar_time==0)
     {
      //--- set the new bar opening time,
      //--- set the previous bar time as the current one and return 'false'
      this.m_new_bar_time=this.m_prev_time=tm;
      return false;
     }
//--- If the previous time is less than the current bar open time, this is a new bar
   if(this.m_prev_time>0 && this.m_prev_time<tm)
     {
      this.m_prev_new_bar_time=this.m_prev_time;
      this.m_seconds_between=tm-m_prev_time;
      this.m_bars_between=int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe));
      //--- set the new bar opening time,
      //--- set the previous time as the current one and return 'true'
      this.m_new_bar_time=this.m_prev_time=tm;
      return true;
     }
//--- in other cases, return 'false'
   return false;
  }
//+------------------------------------------------------------------+

Bei der Methode, die im Falle der manuellen Verwaltung das Flag "neuer Balken" des Balkens zurückgibt, ist es nicht notwendig, die Daten zu berechnen. Die Daten der fehlenden Balken werden immer automatisch berechnet. In dieser Methode speichern wir jedoch die Zeit des vorherigen "neuer Balken" im Falle der manuellen Verwaltung und beheben den Fehler der Zuweisung der neuen Zeit des Balkens (vorher wurde die Zeit in der Variablen für die automatische Zeitverwaltung gespeichert):

//+------------------------------------------------------------------+
//| Return the new bar opening flag during the manual management     |
//+------------------------------------------------------------------+
bool CNewBarObj::IsNewBarManual(const datetime time)
  {
//--- Get the current bar time
   datetime tm=this.GetLastBarDate(time);
   if(tm<=0)
      return false;
//--- If the previous and current time are equal to zero, this is the first launch
   if(this.m_prev_time_manual+this.m_new_bar_time_manual==0)
     {
      //--- set the new bar opening time,
      //--- set the previous bar time as the current one and return 'false'
      this.m_new_bar_time_manual=this.m_prev_time_manual=tm;
      return false;
     }
//--- If the previous time is less than the current bar open time, this is a new bar
   if(this.m_prev_time_manual>0 && this.m_prev_time_manual<tm)
     {
      this.m_prev_new_bar_time_manual=this.m_prev_time_manual;
      //--- set the new bar opening time and return 'true'
      //--- Save the previous time as the current one from the program using the SaveNewBarTime() method
      //--- Till the previous time is forcibly set as the current one from the program,
      //--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar.
      this.m_new_bar_time_manual=tm;
      return true;
     }
//--- in other cases, return 'false'
   return false;
  }
//+------------------------------------------------------------------+


Wir können oft die Einträge der Bibliothek über Fehler beim Empfang historischer Balken im Terminal-Journal sehen. Dies geschieht, weil die Bibliothek die gesamte Historie anzeigt, auch wenn ein bestimmtes Symbol keine historischen Daten zu einem bestimmten Symbol enthält. Der entsprechende Eintrag wird angezeigt, und das System geht zum nächsten historischen Balken über. Dies geschieht für die Möglichkeit, Bibliotheksmethoden bei der Arbeit mit Zeitreihen zu debuggen. Ich werde diese Einträge entfernen, wenn es definitiv nicht notwendig ist, die Fehler bei der Beschaffung historischer Daten anzuzeigen. Um dies zu tun, sollte \MQL5\Include\DoEasy\Objects\Series\Bar.mqh der Objektklasse Balken noch einen anderen Konstruktor ohne Parameter erhalten:

//+------------------------------------------------------------------+
//| Bar class                                                        |
//+------------------------------------------------------------------+
class CBar : public CBaseObj
  {
private:
   MqlDateTime       m_dt_struct;                                 // Date structure
   int               m_digits;                                    // Symbol's digits value
   string            m_period_description;                        // Timeframe string description
   long              m_long_prop[BAR_PROP_INTEGER_TOTAL];         // Integer properties
   double            m_double_prop[BAR_PROP_DOUBLE_TOTAL];        // Real properties
   string            m_string_prop[BAR_PROP_STRING_TOTAL];        // String properties

//--- Return the index of the array the bar's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_BAR_PROP_DOUBLE property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL;                        }
   int               IndexProp(ENUM_BAR_PROP_STRING property)     const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL;  }

//--- Return the bar type (bullish/bearish/zero)
   ENUM_BAR_BODY_TYPE BodyType(void)                              const;
//--- Calculate and return the size of (1) candle, (2) candle body,
//--- (3) upper, (4) lower candle wick,
//--- (5) candle body top and (6) bottom
   double            CandleSize(void)                             const { return(this.High()-this.Low());                                    }
   double            BodySize(void)                               const { return(this.BodyHigh()-this.BodyLow());                            }
   double            ShadowUpSize(void)                           const { return(this.High()-this.BodyHigh());                               }
   double            ShadowDownSize(void)                         const { return(this.BodyLow()-this.Low());                                 }
   double            BodyHigh(void)                               const { return ::fmax(this.Close(),this.Open());                           }
   double            BodyLow(void)                                const { return ::fmin(this.Close(),this.Open());                           }

//--- Return the (1) year and (2) month the bar belongs to, (3) week day,
//--- (4) bar serial number in a year, (5) day, (6) hour, (7) minute,
   int               TimeYear(void)                               const { return this.m_dt_struct.year;                                      }
   int               TimeMonth(void)                              const { return this.m_dt_struct.mon;                                       }
   int               TimeDayOfWeek(void)                          const { return this.m_dt_struct.day_of_week;                               }
   int               TimeDayOfYear(void)                          const { return this.m_dt_struct.day_of_year;                               }
   int               TimeDay(void)                                const { return this.m_dt_struct.day;                                       }
   int               TimeHour(void)                               const { return this.m_dt_struct.hour;                                      }
   int               TimeMinute(void)                             const { return this.m_dt_struct.min;                                       }

public:
//--- Set bar's (1) integer, (2) real and (3) string properties
   void              SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                              }
   void              SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;            }
   void              SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;            }
//--- Return (1) integer, (2) real and (3) string bar properties from the properties array
   long              GetProperty(ENUM_BAR_PROP_INTEGER property)  const { return this.m_long_prop[property];                                 }
   double            GetProperty(ENUM_BAR_PROP_DOUBLE property)   const { return this.m_double_prop[this.IndexProp(property)];               }
   string            GetProperty(ENUM_BAR_PROP_STRING property)   const { return this.m_string_prop[this.IndexProp(property)];               }

//--- Return the flag of the bar supporting the property
   virtual bool      SupportProperty(ENUM_BAR_PROP_INTEGER property)    { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_DOUBLE property)     { return true; }
   virtual bool      SupportProperty(ENUM_BAR_PROP_STRING property)     { return true; }
//--- Return itself
   CBar             *GetObject(void)                                    { return &this;}
//--- Set (1) bar symbol, timeframe and time, (2) bar object parameters
   void              SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time);
   void              SetProperties(const MqlRates &rates);

//--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- Compare CBar objects by all properties (to search for equal bar objects)
   bool              IsEqual(CBar* compared_bar) const;
//--- 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);
                     
//+------------------------------------------------------------------+ 

Bei der Erstellung von Zeitreihenlisten nach Symbolen verwenden wir den Konstruktor zur Erstellung eines neuen Balkenobjekts, das zu der angegebenen Symbolzeitreihe gehört. Zuvor haben parametrische Konstruktoren versucht, die erforderlichen neu erstellten Balkenobjektdaten aus der Historie selbstständig abzurufen, und der Debugging-Eintrag wurde im Fehlerfall beim Abrufen der Historie aus dem Konstruktor an das Journal gesendet. Ein einfacher Konstruktor ohne Parameter erzeugt ein leeres Balkenobjekt, das Sie nach erfolgreicher Erstellung mit Daten füllen müssen. Dies geschieht in den Methoden der Klasse CSeriesDE.

Lassen Sie uns die Änderungen besprechen, die an der Klassenauflistung in \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh vorgenommen werden müssen.

Fügen Sie im 'public' Teil der Klasse die Methode hinzu, die den Zeiger auf das Objekt der Klasse "neuer Balken" zurückgibt, das zu der Klasse Timeseries gehört:

//+------------------------------------------------------------------+
//| Timeseries class                                                 |
//+------------------------------------------------------------------+
class CSeriesDE : public CBaseObj
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;                                       // Timeframe
   string            m_symbol;                                          // Symbol
   string            m_period_description;                              // Timeframe string description
   datetime          m_firstdate;                                       // The very first date by a period symbol at the moment
   datetime          m_lastbar_date;                                    // Time of opening the last bar by period symbol
   uint              m_amount;                                          // Amount of applied timeseries data
   uint              m_required;                                        // Required amount of applied timeseries data
   uint              m_bars;                                            // Number of bars in history by symbol and timeframe
   bool              m_sync;                                            // Synchronized data flag
   CArrayObj         m_list_series;                                     // Timeseries list
   CNewBarObj        m_new_bar_obj;                                     // "New bar" object
//--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol
   void              SetServerDate(void)
                       {
                        this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE);
                        this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE);
                       }

public:
//--- Return (1) itself, (2) timeseries list, (3) timeseries "New bar" object
   CSeriesDE        *GetObject(void)                                    { return &this;               }
   CArrayObj        *GetList(void)                                      { return &m_list_series;      }
   CNewBarObj       *GetNewBarObj(void)                                 { return &this.m_new_bar_obj; }

//--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition

Da wir jetzt zwei Zeitreihen-Ereignisse haben ("neuer Balken" und "fehlende Bar"), sollte die Methode zum Erstellen und Senden des Zeitreihen-Ereignisses an das Kontrollprogramm-Chart verbessert werden. Fügen Sie in der Methodendeklaration den Eingabeparameter hinzu, in dem wir das zu erstellende und zu sendende Zeitreihen-Ereignis übergeben werden:

//--- Create and send the timeseries event to the control program chart
   void              SendEvent(ENUM_SERIES_EVENT event);

Verbessern Sie die Methode, die sich außerhalb des Klassenkörpers befindet:

//+------------------------------------------------------------------+
//| Create and send the timeseries event                             |
//| to the control program chart                                     |
//+------------------------------------------------------------------+
void CSeriesDE::SendEvent(ENUM_SERIES_EVENT event)
  {
   if(event==SERIES_EVENTS_NEW_BAR)
     {
      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());
     }
   else if(event==SERIES_EVENTS_MISSING_BARS)
     {
      ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_MISSING_BARS,this.m_new_bar_obj.BarsBetweenNewBars(),this.Timeframe(),this.Symbol());
     }
  }
//+------------------------------------------------------------------+

Hier erzeugen wir, je nach dem Wert, der der Methode übergeben wird, das notwendige Ereignis und senden es an das Kontrollprogramm des Charts. Wenn das Ereignis "fehlende Balken" erzeugt wird, übergeben wir die Anzahl der fehlenden Balken in lparam Wert der Funktion EventChartCustom().

Um die unnötigen Fehler beim Empfang von Verlaufsdaten im Journal zu beseitigen, müssen wir die Methode entwickeln, das Balkenobjekt nach Zeit in der Zeitreihe zurückzugeben:

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

Da wir jetzt den Konstruktor ohne Parameter in der Klasse CBar haben, werden wir die Erstellung eines neuen Balkenobjekts mit Hilfe des Konstruktors verwenden, um nach dem erforderlichen Balken zu suchen.

Hier erstellen wir einfach ein temporäres leeres Balkenobjekt, sowie das gewünschte Symbol, den Zeitrahmen und die Balkenzeit.
Der Rest ist einfach: Wir sortieren die Liste der Balkenobjekte nach Zeit und suchen in der Liste der Balkenobjekte nach dem Objekt, dessen Daten mit denen übereinstimmen, die wir für das erstellte temporäre Balkenobjekt festgelegt haben.
Die Methode Search() gibt den erhaltenen Objektindex in der Liste zurück, während die Methode den Zeiger auf das Objekt per Index zurückgibt. Wenn kein Objekt gefunden wird, hat der Index den Wert -1, während At() den Wert NULL zurückgibt.

Neue Balken-Ereignisse sowie Ereignisse von fehlenden Balken werden jetzt in den Methoden zur Aktualisierung aller vorhandenen Zeitreihen der CTimeSeriesDE-Klasse in \MQL5\Include\DoEasy\Objects\Series\\TimeSeriesDE.mqh erkannt.

Lassen Sie uns zwei Methoden zur Aktualisierung von Zeitreihen verbessern, indem wir Codeblöcke zur Definition von "fehlenden Balken" Ereignissen 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);
   datetime time=
     (
      this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
      data_calculate.rates.time : 
      series_obj.LastBarDate()
     );
//--- If the timeseries object features the New bar event
   if(series_obj.IsNewBar(time))
     {
      //--- send the "New bar" event to the control program chart
      series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
      //--- 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,time,series_obj.Timeframe(),series_obj.Symbol()))
         this.m_is_event=true;
         
      //--- Check skipped bars
      int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
      if(missing>1)
        {
         //--- send the "Bars skipped" event to the control program chart
         series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
         //--- add the "Bars skipped" event to the list of timeseries events
         this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
        }
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update all timeseries lists                                      |
//+------------------------------------------------------------------+
void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate)
  {
//--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal
//--- and the timeseries event flag, and clear the list of all timeseries events
   bool upd=false;
   this.m_is_event=false;
   this.m_list_events.Clear();
//--- In the loop by the list of all used timeseries,
   int total=this.m_list_series.Total();
   for(int i=0;i<total;i++) 
     {
      //--- get the next timeseries object by the loop index
      CSeriesDE *series_obj=this.m_list_series.At(i);
      if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0)
         continue;
      //--- update the timeseries list
      series_obj.Refresh(data_calculate);
      datetime time=
        (
         this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? 
         data_calculate.rates.time : 
         series_obj.LastBarDate()
        );
      //--- If the timeseries object features the New bar event
      if(series_obj.IsNewBar(time))
        {
         //--- send the "New bar" event to the control program chart,
         series_obj.SendEvent(SERIES_EVENTS_NEW_BAR);
         //--- set the flag indicating the necessity to set the first date in history on the server and in the terminal
         upd=true;
         //--- 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,time,series_obj.Timeframe(),series_obj.Symbol()))
            this.m_is_event=true;
            
         //--- Check skipped bars
         int missing=series_obj.GetNewBarObj().BarsBetweenNewBars();
         if(missing>1)
           {
            //--- send the "Bars skipped" event to the control program chart
            series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS);
            //--- add the "Bars skipped" event to the list of timeseries events
            this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol());
           }
        }
     }
//--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled,
//--- set the values of the first date in history on the server and in the terminal
   if(upd)
      this.SetTerminalServerDate();
  }
//+------------------------------------------------------------------+

Bei der Definition des Ereignisses "Neuer Balken" rufen wir die zuvor geänderte Methode zur Erzeugung eines neuen Zeitreihen-Ereignisses auf, an das wir das Ereignis "Neuer Balken" übergeben. Wenn die Balken fehlen, erzeugen wir das entsprechende Ereignis ebenfalls.

Im 'public' Teil der Sammelklasse aller Zeitreihenobjekte der CTimeSeriesCollection in \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh, Deklaration der Methode zur Neuerstellung aller Zeitreihen hinzufügen:

//--- (1) Create, (2) re-create a specified timeseries of a specified symbol, (3) re-create all timeseries
   bool                    CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0);
   bool                    ReCreateSeriesAll(const int rates_total=0,const uint required=0);
//--- Return (1) an empty, (2) partially filled timeseries

Schreiben wir seine Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Re-create all timeseries                                         |
//+------------------------------------------------------------------+
bool CTimeSeriesCollection::ReCreateSeriesAll(const int rates_total=0,const uint required=0)
  {
//--- In the loop by all symbol timeseries objects in the collection,
   int total=this.m_list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next symbol timeseries object
      CTimeSeriesDE *timeseries=this.m_list.At(i);
      if(timeseries==NULL)
         continue;
      //--- Get the list of all symbol timeseries
      CArrayObj *list=timeseries.GetListSeries();
      if(list==NULL)
         continue;
      //--- In a loop by all symbol timeseries
      int total_series=list.Total();
      for(int j=0;j<total_series;j++)
        {
         //--- Get the next timeseries
         CSeriesDE *series=list.At(j);
         if(series==NULL)
            continue;
         //--- check timeseries synchronization and re-create it
         if(!series.SyncData(required,rates_total))
            return false;
         if(series.Create(required)==0)
            return false;
        }
     }
   return true;
  }
//+------------------------------------------------------------------+

Die Methode erstellt einfach alle verfügbaren Zeitreihen in der Kollektion neu. Bisher wird diese Methode nirgendwo verwendet, aber sie kann in der Zukunft nützlich sein, wenn es notwendig ist, die vorhandenen Zeitreihenkollektionen neu zu erstellen. Sie kann zum Beispiel erforderlich sein, wenn eine große Anzahl von Balken fehlen sollten und das Programm viele Symbole/Perioden verwendet. In diesem Fall ist es viel einfacher, alle Zeitseriensammlungen durch Aufruf einer Methode neu zu erstellen, als die Anzahl der fehlende Balken in jeder Zeitserie zu definieren und jeden Balken einzeln neu zu erstellen. Außerdem geschieht dies nur bei der Wiederherstellung der Verbindung zum Server oder bei einem neuen Balken.

Ich habe alle vorbereitenden Schritte abgeschlossen und die Handhabung von Zeitserien und Balken leicht verbessert. Es ist an der Zeit, mit der Erstellung von Methoden für die Arbeit mit Standardindikatoren zu beginnen.


Methoden für die Arbeit mit den Standardindikatoren

Verbessern wir zunächst die abstrakte Klasse des Pufferobjekts in \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh.

Im 'public' Teil der Klasse fügen wir Einstellungsmethoden hinzu und geben vier neue Pufferobjekteigenschaften zurück:

//--- Set (1) the arrow code, (2) vertical shift of arrows, (3) symbol, (4) timeframe, (5) buffer activity flag
//--- (6) drawing type, (7) number of initial bars without drawing, (8) flag of displaying construction values in DataWindow,
//--- (9) shift of the indicator graphical construction along the time axis, (10) line style, (11) line width,
//--- (12) total number of colors, (13) one drawing color, (14) color of drawing in the specified color index,
//--- (15) drawing colors from the color array, (16) empty value, (17) name of the graphical series displayed in DataWindow
   virtual void      SetArrowCode(const uchar code)                  { return;                                                               }
   virtual void      SetArrowShift(const int shift)                  { return;                                                               }
   void              SetSymbol(const string symbol)                  { this.SetProperty(BUFFER_PROP_SYMBOL,symbol);                          }
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)   { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe);                    }
   void              SetActive(const bool flag)                      { this.SetProperty(BUFFER_PROP_ACTIVE,flag);                            }
   void              SetDrawType(const ENUM_DRAW_TYPE draw_type);
   void              SetDrawBegin(const int value);
   void              SetShowData(const bool flag);
   void              SetShift(const int shift);
   void              SetStyle(const ENUM_LINE_STYLE style);
   void              SetWidth(const int width);
   void              SetColorNumbers(const int number);
   void              SetColor(const color colour);
   void              SetColor(const color colour,const uchar index);
   void              SetColors(const color &array_colors[]);
   void              SetEmptyValue(const double value);
   virtual void      SetLabel(const string label);
   void              SetID(const int id)                             { this.SetProperty(BUFFER_PROP_ID,id);                                  }
   void              SetIndicatorHandle(const int handle)            { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle);                      }
   void              SetIndicatorType(const ENUM_INDICATOR type)     { this.SetProperty(BUFFER_PROP_IND_TYPE,type);                          }
   void              SetIndicatorName(const string name)             { this.SetProperty(BUFFER_PROP_IND_NAME,name);                          }
   
//--- Return (1) the serial number of the drawn buffer, (2) bound array index, (3) color buffer index,
//--- (4) index of the first free bound array, (5) index of the next drawn buffer, (6) buffer data period, (7) buffer status,
//--- (8) buffer type, (9) buffer usage flag, (10) arrow code, (11) arrow shift for DRAW_ARROW style,
//--- (12) number of initial bars that are not drawn and values in DataWindow, (13) graphical construction type,
//--- (14) flag of displaying construction values in DataWindow, (15) indicator graphical construction shift along the time axis,
//--- (16) drawing line style, (17) drawing line width, (18) number of colors, (19) drawing color, number of buffers for construction
//--- (20) set empty value, (21) buffer symbol, (22) name of the indicator graphical series displayed in DataWindow
   int               IndexPlot(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT);                 }
   int               IndexBase(void)                           const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE);                 }
   int               IndexColor(void)                          const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR);                }
   int               IndexNextBaseBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE);            }
   int               IndexNextPlotBuffer(void)                 const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT);            }
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME);      }
   ENUM_BUFFER_STATUS Status(void)                             const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS);      }
   ENUM_BUFFER_TYPE  TypeBuffer(void)                          const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE);          }
   bool              IsActive(void)                            const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE);                    }
   uchar             ArrowCode(void)                           const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE);               }
   int               ArrowShift(void)                          const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT);                }
   int               DrawBegin(void)                           const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN);                 }
   ENUM_DRAW_TYPE    DrawType(void)                            const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE);       }
   bool              IsShowData(void)                          const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA);                 }
   int               Shift(void)                               const { return (int)this.GetProperty(BUFFER_PROP_SHIFT);                      }
   ENUM_LINE_STYLE   LineStyle(void)                           const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE);     }
   int               LineWidth(void)                           const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH);                 }
   int               ColorsTotal(void)                         const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES);              }
   color             Color(void)                               const { return (color)this.GetProperty(BUFFER_PROP_COLOR);                    }
   int               BuffersTotal(void)                        const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS);                  }
   double            EmptyValue(void)                          const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE);                     }
   string            Symbol(void)                              const { return this.GetProperty(BUFFER_PROP_SYMBOL);                          }
   string            Label(void)                               const { return this.GetProperty(BUFFER_PROP_LABEL);                           }
   int               ID(void)                                  const { return (int)this.GetProperty(BUFFER_PROP_ID);                         }
   int               IndicatorHandle(void)                     const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE);                 }
   ENUM_INDICATOR    IndicatorType(void)                       const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE);        }
   string            IndicatorName(void)                       const { return this.GetProperty(BUFFER_PROP_IND_NAME);                        }
   int               IndicatorBarsCalculated(void)             const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}

Wir setzen im Klassenkonstruktor die Standardwerte auf die neuen Eigenschaften :

//+------------------------------------------------------------------+
//| Closed parametric constructor                                    |
//+------------------------------------------------------------------+
CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status,
                 ENUM_BUFFER_TYPE buffer_type,
                 const uint index_plot,
                 const uint index_base_array,
                 const int num_datas,
                 const uchar total_arrays,
                 const int width,
                 const string label)
  {
   this.m_type=COLLECTION_BUFFERS_ID;
   this.m_act_state_trigger=true;
   this.m_total_arrays=total_arrays;
//--- Save integer properties
   this.m_long_prop[BUFFER_PROP_STATUS]                        = buffer_status;
   this.m_long_prop[BUFFER_PROP_TYPE]                          = buffer_type;
   this.m_long_prop[BUFFER_PROP_ID]                            = WRONG_VALUE;
   this.m_long_prop[BUFFER_PROP_IND_HANDLE]                    = INVALID_HANDLE;
   this.m_long_prop[BUFFER_PROP_IND_TYPE]                      = WRONG_VALUE;
   ENUM_DRAW_TYPE type=
     (
      !this.TypeBuffer() || !this.Status() ? DRAW_NONE      : 
      this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING   : 
      ENUM_DRAW_TYPE(this.Status()+8)
     );
   this.m_long_prop[BUFFER_PROP_DRAW_TYPE]                     = type;
   this.m_long_prop[BUFFER_PROP_TIMEFRAME]                     = PERIOD_CURRENT;
   this.m_long_prop[BUFFER_PROP_ACTIVE]                        = true;
   this.m_long_prop[BUFFER_PROP_ARROW_CODE]                    = 0x9F;
   this.m_long_prop[BUFFER_PROP_ARROW_SHIFT]                   = 0;
   this.m_long_prop[BUFFER_PROP_DRAW_BEGIN]                    = 0;
   this.m_long_prop[BUFFER_PROP_SHOW_DATA]                     = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false);
   this.m_long_prop[BUFFER_PROP_SHIFT]                         = 0;
   this.m_long_prop[BUFFER_PROP_LINE_STYLE]                    = STYLE_SOLID;
   this.m_long_prop[BUFFER_PROP_LINE_WIDTH]                    = width;
   this.m_long_prop[BUFFER_PROP_COLOR_INDEXES]                 = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0);
   this.m_long_prop[BUFFER_PROP_COLOR]                         = clrRed;
   this.m_long_prop[BUFFER_PROP_NUM_DATAS]                     = num_datas;
   this.m_long_prop[BUFFER_PROP_INDEX_PLOT]                    = index_plot;
   this.m_long_prop[BUFFER_PROP_INDEX_BASE]                    = index_base_array;
   this.m_long_prop[BUFFER_PROP_INDEX_COLOR]                   = this.GetProperty(BUFFER_PROP_INDEX_BASE)+
                                                                   (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0);
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE]               = index_base_array+this.m_total_arrays;
   this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT]               = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot);
   
//--- Save real properties
   this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0);
//--- Save string properties
   this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)]      = ::Symbol();
   this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)]       = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL);
   this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)]    = NULL;

//--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string

Solche Werte dieser neuen Eigenschaften werden zu Pufferobjekten gehören, die nicht mit Standardindikatoren arbeiten. Wenn wir ein Pufferobjekt anlegen, das zu einem Standardindikator gehört, werden diese Parameter von der Bibliothek zum Zeitpunkt seiner Erstellung (die später implementiert werden soll) ausgefüllt.

Fügen wir die anzeigende Einstellungen für neue ganzzahlige Eigenschaften zur Methode hinzu, die die Beschreibung der ganzzahligen Puffereigenschaften zurückgibt:

//+------------------------------------------------------------------+
//| Return description of a buffer's integer property                |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property)
  {
   return
     (
      property==BUFFER_PROP_INDEX_PLOT    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_STATUS        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetStatusDescription()
         )  :
      property==BUFFER_PROP_TYPE          ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTypeBufferDescription()
         )  :
      property==BUFFER_PROP_TIMEFRAME     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetTimeframeDescription()
         )  :
      property==BUFFER_PROP_ACTIVE        ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetActiveDescription()
         )  :
      property==BUFFER_PROP_DRAW_TYPE     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetDrawTypeDescription()
         )  :
      property==BUFFER_PROP_ARROW_CODE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ARROW_SHIFT   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+
         (!this.SupportProperty(property)    ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_LINE_STYLE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetLineStyleDescription()
         )  :
      property==BUFFER_PROP_LINE_WIDTH    ?  
         (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) :
          CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_DRAW_BEGIN    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_SHOW_DATA     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetShowDataDescription()
         )  :
      property==BUFFER_PROP_SHIFT         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR_INDEXES ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_COLOR   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_BASE    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_BASE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_INDEX_NEXT_PLOT ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_ID ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_HANDLE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_IND_TYPE ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_NUM_DATAS     ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==BUFFER_PROP_COLOR         ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.GetColorsDescription()
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Fügen wir die anzeigende Einstellungen für eine neue String-Eigenschaft zur Methode hinzu, die die Beschreibung der Puffer-String-Eigenschaft zurückgibt:

//+------------------------------------------------------------------+
//| Return description of a buffer's string property                 |
//+------------------------------------------------------------------+
string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property)
  {
   return
     (
      property==BUFFER_PROP_SYMBOL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.Symbol()
         )  :
      property==BUFFER_PROP_LABEL    ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"")
         )  :
      property==BUFFER_PROP_IND_NAME   ?  CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"")
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Lassen Sie uns Änderungen in den Methoden zum Setzen eines leeren Wertes und des grafischen Reihennamens vornehmen. Zuvor wurden diese Werte für den berechneten Puffer nicht gesetzt. Machen wir es so, dass die Werte nur auf die Eigenschaften des Pufferobjekts im Falle eines berechneten Puffers gesetzt werden.
Im Falle eines zu zeichnenden Puffers,
sollten die Werte sowohl des Objekt als auch der Puffereigenschaften gesetzt werden:

//+------------------------------------------------------------------+
//| Set the "empty" value for construction                           |
//| without drawing                                                  |
//+------------------------------------------------------------------+
void CBuffer::SetEmptyValue(const double value)
  {
   this.SetProperty(BUFFER_PROP_EMPTY_VALUE,value);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,value);
  }
//+------------------------------------------------------------------+
//| Set the indicator graphical series name                          |
//+------------------------------------------------------------------+
void CBuffer::SetLabel(const string label)
  {
   this.SetProperty(BUFFER_PROP_LABEL,label);
   if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE)
      ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,label);
  }
//+------------------------------------------------------------------+

Fügen wir die Kontrolle für Indexwert kleiner als Null zu den Methoden hinzu, die Werte durch den Zeitreihenindex zurückgeben:

//+------------------------------------------------------------------+
//| Return the value from the specified timeseries index             |
//| of the specified data buffer array                               |
//+------------------------------------------------------------------+
double CBuffer::GetDataBufferValue(const uint buffer_index,const int series_index) const
  {
   int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index);
   int data_total=this.GetDataTotal(correct_buff_index);
   if(data_total==0 || series_index<0)
      return this.EmptyValue();
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return this.DataBuffer[correct_buff_index].Array[data_index];
  }
//+------------------------------------------------------------------+
//| Return the color index value from the specified timeseries index |
//| of the specified color buffer array                              |
//+------------------------------------------------------------------+
int CBuffer::GetColorBufferValueIndex(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return WRONG_VALUE;
   int data_index=((int)series_index<data_total ? (int)series_index : data_total-1);
   return(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]);
  }
//+------------------------------------------------------------------+
//| Return the color value from the specified timeseries index       |
//| of the specified color buffer array                              |
//+------------------------------------------------------------------+
color CBuffer::GetColorBufferValueColor(const int series_index) const
  {
   int data_total=this.GetDataTotal(0);
   if(data_total==0 || series_index<0)
      return clrNONE;
   int color_index=this.GetColorBufferValueIndex(series_index);
   return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE);
  }
//+------------------------------------------------------------------+

Wenn also ein falscher Index an die Methode übergeben wird, wird die Methode verlassen und gleichzeitig ein "leerer" Wert zurückgegeben, der für jede der Methoden unterschiedlich ist.

Verbessern wir nun die berechnete Pufferobjektklasse in \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh.

Die Methoden, die das Flag der Unterstützung der Eigenschaften double und string durch das Pufferobjekt zurückgeben, gaben zuvor false zurück — d.h. der Berechnungspuffer unterstützt die Eigenschaften dieses Typs nicht. Erlauben wir ihm, jede dieser Eigenschaften zu unterstützen. In der Methode, die das Flag der Unterstützung ganzzahliger Eigenschaften durch das Objekt zurückgibt, fügen wir neue ganzzahlige Eigenschaften hinzu, um sie mit Hilfe des berechneten Pufferobjekts zu unterstützen:

//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property)
  {
   if(
      property==BUFFER_PROP_INDEX_PLOT       || 
      property==BUFFER_PROP_STATUS           ||  
      property==BUFFER_PROP_TYPE             || 
      property==BUFFER_PROP_INDEX_BASE       || 
      property==BUFFER_PROP_ID               || 
      property==BUFFER_PROP_IND_HANDLE       || 
      property==BUFFER_PROP_IND_TYPE         || 
      property==BUFFER_PROP_INDEX_NEXT_BASE
     ) return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| Return 'true' if a buffer supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property)
  {
   return true;
  }
//+------------------------------------------------------------------+

Das Objekt, das den Berechnungspuffer aller Eigenschaften double und string unterstützt, ist meist eine schnelle temporäre Lösung für die Erstellung von Methoden zur Arbeit mit Puffern, die mit Standardindikatoren arbeiten. Später werde ich einige von ihnen aus der Liste der unterstützten Eigenschaften entfernen.

Die gesamte Handhabung von Indikatorpuffern für Standardindikatoren ist in der Kollektionsklasse CBuffersCollection von Indikatorpuffern in \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh eingerichtet.

Heute werde ich einen Mehrsymbol-Mehrperioden-Indikatorpuffer des Standardindikators AC (Accelerator Oscillator) erstellen und pflegen. In den folgenden Artikeln werde ich die Möglichkeit hinzufügen, auf der Grundlage der getesteten Funktionalität andere Standardindikatoren zu erstellen und mit ihnen zu arbeiten.

Alle Pufferobjekte, die mit Standardindikatoren arbeiten, erhalten die ID, die es uns ermöglicht, die erforderlichen Puffer zu finden und mit ihnen zu arbeiten.

Im öffentlichen Klassenabschnitt deklarieren wir die Methode, die die Liste der Pufferobjekte mit einer solchen ID zurückgibt:

//+------------------------------------------------------------------+
//| Collection of indicator buffers                                  |
//+------------------------------------------------------------------+
class CBuffersCollection : public CObject
  {
private:
   CListObj                m_list;                       // Buffer object list
   CTimeSeriesCollection  *m_timeseries;                 // Pointer to the timeseries collection object
   
//--- Return the index of the (1) last, (2) next drawn and (3) basic buffer
   int                     GetIndexLastPlot(void);
   int                     GetIndexNextPlot(void);
   int                     GetIndexNextBase(void);
//--- Create a new buffer object and place it to the collection list
   bool                    CreateBuffer(ENUM_BUFFER_STATUS status);
//--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars
   int                     GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period);

public:
//--- Return (1) itself, (2) timeseries list, (3) indicator buffer list (featuring the ID of belonging to an indicator)
   CBuffersCollection     *GetObject(void)               { return &this;                                       }
   CArrayObj              *GetList(void)                 { return &this.m_list;                                }
   CArrayObj              *GetListBuffersWithID(void);

Schreiben wir seine Implementierung außerhalb des Klassenkörpers:

//+------------------------------------------------------------------+
//| Return the list of indicator buffers                             |
//| (featuring the ID of belonging to an indicator)                  |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Hier ist alles einfach: Mit der Klasse CSelect erhalten wir die Liste der Pufferobjekte mit dem ID-Wert ungleich -1 und geben den Zeiger auf die erhaltene Liste zurück.

Wenn wir die Liste erfolgreich erhalten haben, enthält sie alle Pufferobjekte mit einer ID ungleich -1. Das bedeutet, dass die Liste alle erstellten Pufferobjekte für die Arbeit mit Standardindikatoren enthält, einschließlich berechneter und gezeichneter Objekte für jeden Standardindikatortyp.
Um nach Pufferobjekten zu suchen, die zu einem bestimmten Kennzeichen gehören, sollte die Liste zusätzlich nach dem Typ des Standardindikators, der ID und dem Puffertyp sortiert werden.

Fügen wir die Deklarationen von Methoden zur Erstellung von Pufferobjekten, die Standardkennzeichen behandeln, im 'public' Teil der Klasse ein:

//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                    CreateArrow(void)             { return this.CreateBuffer(BUFFER_STATUS_ARROW);      }
   bool                    CreateLine(void)              { return this.CreateBuffer(BUFFER_STATUS_LINE);       }
   bool                    CreateSection(void)           { return this.CreateBuffer(BUFFER_STATUS_SECTION);    }
   bool                    CreateHistogram(void)         { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM);  }
   bool                    CreateHistogram2(void)        { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); }
   bool                    CreateZigZag(void)            { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG);     }
   bool                    CreateFilling(void)           { return this.CreateBuffer(BUFFER_STATUS_FILLING);    }
   bool                    CreateBars(void)              { return this.CreateBuffer(BUFFER_STATUS_BARS);       }
   bool                    CreateCandles(void)           { return this.CreateBuffer(BUFFER_STATUS_CANDLES);    }
   bool                    CreateCalculate(void)         { return this.CreateBuffer(BUFFER_STATUS_NONE);       }

//--- Create a multi-symbol multi-period indicator
   int                     CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);
   int                     CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     CreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE);
   int                     CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ama_period,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const int ama_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int bands_period,
                                       const int bands_shift,
                                       const double deviation,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ma_period,
                                       const int slow_ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const double deviation,
                                       const int id=WRONG_VALUE);
   int                     CreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE);
   int                     CreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int jaw_period,
                                       const int jaw_shift,
                                       const int teeth_period,
                                       const int teeth_shift,
                                       const int lips_period,
                                       const int lips_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int tenkan_sen,
                                       const int kijun_sen,
                                       const int senkou_span_b,
                                       const int id=WRONG_VALUE);
   int                     CreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int mom_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int fast_ema_period,
                                       const int slow_ema_period,
                                       const int signal_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const ENUM_APPLIED_VOLUME applied_volume,
                                       const int id=WRONG_VALUE);
   int                     CreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const double step,
                                       const double maximum,
                                       const int id=WRONG_VALUE);
   int                     CreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE);
   int                     CreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int Kperiod,
                                       const int Dperiod,
                                       const int slowing,
                                       const ENUM_MA_METHOD ma_method,
                                       const ENUM_STO_PRICE price_field,
                                       const int id=WRONG_VALUE);
   int                     CreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int ma_period,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id=WRONG_VALUE);
   int                     CreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                       const int cmo_period,
                                       const int ema_period,
                                       const int ma_shift,
                                       const ENUM_APPLIED_PRICE applied_price,
                                       const int id=WRONG_VALUE);
   int                     CreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);

Jeder spezifische Standardindikatortyp muss seine eigene Methode zur Erstellung eines geeigneten Indikators und der erforderlichen Pufferobjekte verwenden.

Als Beispiel werde ich hier die Handhabung des AC-Indikators implementieren. Lassen Sie uns die Methode zur Erstellung des AC-Indikators und seiner Puffer über den Klassenkörper hinaus schreiben:

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

Wie Sie sehen können, ist hier alles ganz einfach. Wenn -1 als ID übergeben wird, entspricht die ID dem Wert einer Konstanten des Standardindikatortyps. Wenn der Indikator erfolgreich erstellt wurde (sein Handle ist ungleich INVALID_HANDLE), erstellen wir das Pufferobjekt vom Zeichnungstyp "Histogramm von Nulllinie" und verwenden die Methode GetLastCreateBuffer(), die den Zeiger auf den zuletzt erstellten Puffer zurückgibt (die Methode ist später zu besprechen), um den Zeiger auf das Histogramm-Pufferobjekt zu erhalten und die notwendigen Parameter für seine Identifizierung als Puffer für Zeichnungsdaten des Standardindikators AC einzustellen.
Als Nächstes müssen Sie dasselbe auch für den berechneten Puffer tun. Wir schreiben in den Berechnungspuffer die Indikatordaten des AC, die wir beim Zugriff auf sein Handle erhalten haben. Das Handle des erzeugten Indikators wird in den Eigenschaften des Pufferobjekts gesetzt. Dies gilt sowohl für den zu zeichnenden als auch für den Berechnungspuffer, d.h. wir können jedes dieser Pufferobjekte erhalten, auf den Indikator über das in den Objekten gesetzte Handle zugreifen und mit dem Indikator arbeiten.

Fügen wir die Implementierung der Methode zur Erstellung des AD mit den erforderlichen Pufferobjekten hinzu, um die Unterschiede bei der Implementierung der Methoden für Standardindikatoren verschiedener Typen zu erkennen:

//+------------------------------------------------------------------+
//| Create multi-symbol multi-period AD                              |
//+------------------------------------------------------------------+
int CBuffersCollection::CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE)
  {
//--- Create the indicator handle and set the default ID
   int handle=::iAD(symbol,timeframe,applied_volume);
   int identifier=(id==WRONG_VALUE ? IND_AD : id);
   if(handle!=INVALID_HANDLE)
     {
      //--- Create the line buffer
      this.CreateLine();
      //--- Get the last created (drawn) buffer object and set all the necessary parameters to it
      CBuffer *buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetShowData(true);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
      
      //--- Create a calculated buffer storing standard indicator data
      this.CreateCalculate();
      //--- Get the last created (calculated) buffer object and set all the necessary parameters to it
      buff=this.GetLastCreateBuffer();
      buff.SetSymbol(symbol);
      buff.SetTimeframe(timeframe);
      buff.SetID(identifier);
      buff.SetIndicatorHandle(handle);
      buff.SetIndicatorType(IND_AD);
      buff.SetEmptyValue(EMPTY_VALUE);
      buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")");
      buff.SetIndicatorName("Accumulation/Distribution");
     }
   return handle;
  }
//+------------------------------------------------------------------+

Die Unterschiede sind gering und hängen hauptsächlich mit dem gezeichneten Puffertyp, dem Standardindikatortyp, dem Namen der graphischen Reihe und dem Indikatornamen zusammen. Bei anderen Typen von Standardindikatoren gibt es eine unterschiedliche Anzahl von zu zeichnenden und Berechnungspufferobjekten (falls erforderlich) für die Handhabung des Standardindikators.

Deklaration der übrigen Methoden hinzufügen direkt nach der Deklaration der Methoden zur Erstellung von Standardindikatoren:

//--- Prepare calculated buffer data of the specified standard indicator
   int                     PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy);
//--- Clear buffer data of the specified standard indicator by the timeseries index
   void                    ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index);
//--- Set the values for the current chart to the specified standard indicator buffer by the timeseries index according to the buffer object period/symbol
   bool                    SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE);

//--- Return the buffer (1) by the graphical series name, (2) by timeframe,
//--- (3) by Plot index, (4) by object index in the collection list, (5) the last created one,
//--- list of buffers (6) by ID, (7) by standard indicator type, (8) by type and ID
   CBuffer                *GetBufferByLabel(const string plot_label);
   CBuffer                *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe);
   CBuffer                *GetBufferByPlot(const int plot_index);
   CBuffer                *GetBufferByListIndex(const int index_list);
   CBuffer                *GetLastCreateBuffer(void);
   CArrayObj              *GetListBufferByID(const int id);
   CArrayObj              *GetListBufferByIndType(const ENUM_INDICATOR indicator_type);
   CArrayObj              *GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id);

Alle deklarierten Methoden werden in den Kommentaren erklärt. Lassen Sie uns ihre Implementierungen außerhalb des Hauptteils der Klasse besprechen.

Die Methode, die das zuletzt erzeugte Pufferobjekt zurückgibt:

//+------------------------------------------------------------------+
//| Return the last created buffer                                   |
//+------------------------------------------------------------------+
CBuffer *CBuffersCollection::GetLastCreateBuffer(void)
  {
   return this.m_list.At(this.m_list.Total()-1);
  }
//+------------------------------------------------------------------+

Die Methode gibt einfach den Zeiger auf das Pufferobjekt zurück, das das letzte in der Liste der Pufferobjekte ist.

Die Methode gibt die Liste der Pufferobjekte nach ID zurück:

//+------------------------------------------------------------------+
//| Return the list of buffers by ID                                 |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByID(const int id)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Wir holen uns die Liste der Pufferobjekte mit der ID, die gleich der der Methode übergeben ist.
Und geben den Zeiger auf die von der Methode erhaltene Liste zurück.

Die Methode, die die Liste der Pufferobjekte nach dem Typ des Standardindikators zurückgibt:

//+------------------------------------------------------------------+
//| Return the list of buffers by the standard indicator type        |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByIndType(const ENUM_INDICATOR indicator_type)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_IND_TYPE,indicator_type,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Wir holen uns die Liste der Pufferobjekte mit dem Typ des Standardindikators gleich dem der Methode übergebenen.
Und geben den Zeiger auf die von der Methode erhaltene Liste zurück.

Die Methode, die die Liste der Pufferobjekte nach dem Typ des Standardindikators zurückgibt:

//+------------------------------------------------------------------+
//| Return the list of buffers by type and ID                        |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id)
  {
   CArrayObj *list=this.GetListBufferByIndType(indicator_type);
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_ID,id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Wir holen uns zunächst die Liste der Pufferobjekte mit dem angegebenen Typ des Standardindikators in ihren Eigenschaften. Danach sortieren wir die erhaltene Liste nach Pufferobjekten, deren Eigenschaften die angegebene IDaufweisen.
Der Zeiger auf die Ergebnisliste wird von der Methode zurückgegeben.

Die Methode, die die Liste der Pufferobjekte zurückgibt, die zu irgendeinem Standardindikator gehören:

//+------------------------------------------------------------------+
//| Return the list of indicator buffers                             |
//| (featuring the ID of belonging to an indicator)                  |
//+------------------------------------------------------------------+
CArrayObj *CBuffersCollection::GetListBuffersWithID(void)
  {
   CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL);
   return list;
  }
//+------------------------------------------------------------------+

Wir holen uns aus der Kollektionsliste der Pufferobjekte die Liste der Objekte, dessen ID-Eigenschaft ungleich -1 ist.
Und geben den Zeiger auf die von der Methode erhaltene Liste zurück.

Die Hauptobjektklasse der Bibliothek CEngine ist die Verbindung zwischen dem Programm und der Bibliothek.
Lassen Sie uns die notwendigen Verbesserungen in der Klassendatei \MQL5\Include\DoEasy\Engine.mqh vornehmen.

Hinzufügen der Methode zur Neuerstellung aller Zeitreihen zum 'public' Teil der Klasse:

//--- Re-create (1) the specified timeseries of the specified symbol, (2) all collection timeseries
   bool                 SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required);      }
   bool                 SeriesReCreateAll(const int rates_total=0,const uint required=0)
                          { return this.m_time_series.ReCreateSeriesAll(rates_total,required);                    }

Die Methode gibt einfach das Ergebnis der gleichnamigen Methode der Zeitreihenkollektion zurück, die ich oben hinzugefügt habe.

Fügen wir im 'public' Teil der Klasse die Methode hinzu, die die Anzahl der Balken der angegebenen Zeitreihe zurückgibt:

//--- 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 the umber of bars of the timeseries of the specified symbol/period
   int                  SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe);

Implementation der Methode außerhalb des Klassenhauptteils:

//+---------------------------------------------------------------------------+
//| Return the umber of bars of the timeseries of the specified symbol/period |
//+---------------------------------------------------------------------------+
int CEngine::SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe)
  {
   CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
   if(series==NULL)
      return WRONG_VALUE;
   return (int)series.Bars();
  }
//+------------------------------------------------------------------+

Bestimmen wir die angegebene Zeitreihe aus der Kollektionsklasse der Zeitreihen und geben die Anzahl der Zeitreihen-Balken zurück.


Zuvor hatte ich die Methode, die den zuletzt erstellten Puffer zurückgibt:

//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastBuffer(void);

und deren Umsetzung:

//+------------------------------------------------------------------+
//| Return the last indicator buffer                                 |
//| in the indicator buffer collection list                          |
//+------------------------------------------------------------------+
CBuffer *CEngine::GetLastBuffer(void)
  {
   CArrayObj *list=this.GetListBuffers();
   if(list==NULL)
      return NULL;
   return list.At(list.Total()-1);
  }
//+------------------------------------------------------------------+

Entfernen wir die Methodenimplementierung aus dem Code der Klasse und ersetzen ihre Deklaration durch die neue Methode:

//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list
   CBuffer             *GetBufferByLabel(const string plot_label)                      { return this.m_buffers.GetBufferByLabel(plot_label); }
   CBuffer             *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe)          { return this.m_buffers.GetBufferByTimeframe(timeframe);}
   CBuffer             *GetBufferByPlot(const int plot_index)                          { return this.m_buffers.GetBufferByPlot(plot_index);  }
   CBuffer             *GetBufferByListIndex(const int index_list)                     { return this.m_buffers.GetBufferByListIndex(index_list);}
   CBuffer             *GetLastCreateBuffer(void)                                      { return this.m_buffers.GetLastCreateBuffer();        }

Die Methode gibt das Ergebnis der oben besprochenen Methode der oben besprochenen gleichnamigen Kollektionsklasse der Puffer zurück.

Fügen wir im 'public' Teil der Klasse eine Methode zum Erstellen des Standardindikators AC und den Puffern für seine Arbeit hinzu:

//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", 
//--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels",
//--- (8) "Display as bars", (9) "Display as candles", calculated buffer
   bool                 BufferCreateArrow(void)                                        { return this.m_buffers.CreateArrow();                }
   bool                 BufferCreateLine(void)                                         { return this.m_buffers.CreateLine();                 }
   bool                 BufferCreateSection(void)                                      { return this.m_buffers.CreateSection();              }
   bool                 BufferCreateHistogram(void)                                    { return this.m_buffers.CreateHistogram();            }
   bool                 BufferCreateHistogram2(void)                                   { return this.m_buffers.CreateHistogram2();           }
   bool                 BufferCreateZigZag(void)                                       { return this.m_buffers.CreateZigZag();               }
   bool                 BufferCreateFilling(void)                                      { return this.m_buffers.CreateFilling();              }
   bool                 BufferCreateBars(void)                                         { return this.m_buffers.CreateBars();                 }
   bool                 BufferCreateCandles(void)                                      { return this.m_buffers.CreateCandles();              }
   bool                 BufferCreateCalculate(void)                                    { return this.m_buffers.CreateCalculate();            }

//--- The methods of creating standard indicators and buffer objects for them
   bool                 BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id)
                          { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE);  }

//--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object

Die Methode gibt das Ergebnis der gleichnamigen Erstellungsmethode des AC aus der Kollektionsklasse der Puffer des Indikators zurück, die ich oben besprochen habe. Die Methoden zur Erstellung anderer Standardindikatoren sollen in den folgenden Artikeln hinzugefügt werden.

Implementierung der Methode zur Vorbereitung berechneter Pufferdaten für den Standardindikator (bisher nur für AC):

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

Wir holen uns die Liste der Pufferobjekte nach Indikatortyp und ID, lassen nur die Berechnungspuffer in der erhaltenen Liste, holen uns den allerersten (und nur bei AC) Berechnungspuffer aus der Liste, tragen in den Berechnungspuffer die angegebenen Daten ein und geben die Anzahl der erfolgreich kopierten Daten aus dem Indikator-Handle in den Berechnungspuffer zurück.

Implementation der Methode zum Löschen der Daten des Berechnungspuffers für den Standardindikator durch den angegebenen Index (bisher nur für AC):

//+------------------------------------------------------------------+
//| Clear buffer data of the specified standard indicator            |
//| by the timeseries index                                          |
//+------------------------------------------------------------------+
void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index)
  {
   CArrayObj *list=this.GetListBufferByID(id);
   if(list==NULL)
      return;
   list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL);
   if(list.Total()==0)
      return;
   CBuffer *buffer=NULL;
   switch((int)std_ind)
     {
      case IND_AC :
        buffer=list.At(0);
        if(buffer==NULL) return;
        buffer.SetBufferValue(0,series_index,buffer.EmptyValue());
        break;
      
      case IND_AD :
        break;
      case IND_ADX :
        break;
      case IND_ADXW :
        break;
      case IND_ALLIGATOR :
        break;
      case IND_AMA :
        break;
      case IND_AO :
        break;
      case IND_ATR :
        break;
      case IND_BANDS :
        break;
      case IND_BEARS :
        break;
      case IND_BULLS :
        break;
      case IND_BWMFI :
        break;
      case IND_CCI :
        break;
      case IND_CHAIKIN :
        break;
      case IND_DEMA :
        break;
      case IND_DEMARKER :
        break;
      case IND_ENVELOPES :
        break;
      case IND_FORCE :
        break;
      case IND_FRACTALS :
        break;
      case IND_FRAMA :
        break;
      case IND_GATOR :
        break;
      case IND_ICHIMOKU :
        break;
      case IND_MA :
        break;
      case IND_MACD :
        break;
      case IND_MFI :
        break;
      case IND_MOMENTUM :
        break;
      case IND_OBV :
        break;
      case IND_OSMA :
        break;
      case IND_RSI :
        break;
      case IND_RVI :
        break;
      case IND_SAR :
        break;
      case IND_STDDEV :
        break;
      case IND_STOCHASTIC :
        break;
      case IND_TEMA :
        break;
      case IND_TRIX :
        break;
      case IND_VIDYA :
        break;
      case IND_VOLUMES :
        break;
      case IND_WPR :
        break;
      
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

Die Methode funktioniert ähnlich wie die Methode zur Datenaufbereitung. Anstatt jedoch Daten aus dem Indikator-Handle in den Berechnungspuffer zu kopieren, wird hier der leere Wert angegeben, der für das Pufferobjekt durch den angegebenen Index in den gezeichneten Puffer gesetzt wird.

Implementation der Methode zum Füllen des zu zeichnenden Puffers auf dem aktuellen Chart mit Standardindikatordaten von einem beliebigen Symbol/Zeitrahmen (bisher nur für AC):

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

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

Die gesamte Methodenlogik im Zusammenhang mit der Datenberechnung für AC wird in Kommentaren ausführlich beschrieben.


Ganz am Ende des Hauptteils der Klasse deklarieren wir zwei Methoden zur Behandlung von Ereignissen in der Bibliothek:

public:
//--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID
   uint                 SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0);

//--- Handling DoEasy library events
void                    OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
//--- Working with events in the tester
void                    EventsHandling(void);

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

Zuvor habe ich die Funktionen mit den gleichen Namen wie die neu deklarierten Methoden verwendet, um Bibliotheksereignisse in Nutzerprogrammen zu behandeln. Wir haben diese Funktionen ohne Änderungen von einem Programm zum anderen weitergegeben. Dies deutet darauf hin, dass diese Handler der Bibliothek übergeben werden können, während wir im Programm einfach die Flags der aufgetretenen Ereignisse empfangen können (der Empfang der Flags sowie der Ereignis-Flags selbst und die Fähigkeit, Ereignisse in benutzerdefinierten Nutzern zu behandeln, sollen später implementiert werden).

Ich habe diese Funktionen bereits vom Testindikator in die Auflistung der Klasse CEngine verschoben und benutze sie zur Implementierung der oben erklärten Methoden:

//+------------------------------------------------------------------+
//| Handling DoEasy library events                                   |
//+------------------------------------------------------------------+
void CEngine::OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   int idx=id-CHARTEVENT_CUSTOM;
//--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time
   ushort msc=this.EventMSC(lparam);
   ushort reason=this.EventReason(lparam);
   ushort source=this.EventSource(lparam);
   long time=::TimeCurrent()*1000+msc;
   
//--- Handling symbol events
   if(source==COLLECTION_SYMBOLS_ID)
     {
      CSymbol *symbol=this.GetSymbolObjByName(sparam);
      if(symbol==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits());
      //--- Event text description
      string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=::DoubleToString(dparam,digits);
      
      //--- Check event reasons and display its description in the journal
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     }   
     
//--- Handling account events
   else if(source==COLLECTION_ACCOUNT_ID)
     {
      CAccount *account=this.GetAccountCurrent();
      if(account==NULL)
         return;
      //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol
      int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits());
      //--- Event text description
      string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx));
      //--- Property change text value
      string value=::DoubleToString(dparam,digits);
      
      //--- Checking event reasons and handling the increase of funds by a specified value,
      
      //--- Display an event in the journal
      if(reason==BASE_EVENT_REASON_INC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_DEC)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_MORE_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_LESS_THEN)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
      if(reason==BASE_EVENT_REASON_EQUALS)
        {
         ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits));
        }
     } 
     
//--- Handling market watch window events
   else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE)
     {
      //--- Market Watch window event
      string descr=this.GetMWEventDescription((ENUM_MW_EVENT)idx);
      string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam);
      Print(TimeMSCtoString(lparam)," ",descr,name);
     }
     
//--- Handling timeseries events
   else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE)
     {
      //--- "New bar" event
      if(idx==SERIES_EVENTS_NEW_BAR)
        {
         ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam));
         CArrayObj *list=this.m_buffers.GetListBuffersWithID();
         if(list!=NULL)
           {
            int total=list.Total();
            for(int i=0;i<total;i++)
              {
               CBuffer *buff=list.At(i);
               if(buff==NULL)
                  continue;
               string symbol=sparam;
               ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam;
               if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
                  continue;
               if(buff.Symbol()==symbol && buff.Timeframe()==timeframe )
                 {
                  CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe);
                  if(series==NULL)
                     continue;
                  int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated());
                  this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count);
                 }
              }
           }
        }
      //--- "Bars skipped" event
      if(idx==SERIES_EVENTS_MISSING_BARS)
        {
         ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam);
        }
     }
     
//--- Handling trading events
   else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE)
     {
      //--- Get the list of trading events
      CArrayObj *list=this.GetListAllOrdersEvents();
      if(list==NULL)
         return;
      //--- get the event index shift relative to the end of the list
      //--- in the tester, the shift is passed by the lparam parameter to the event handler
      //--- outside the tester, events are sent one by one and handled in OnChartEvent()
      int shift=(this.IsTester() ? (int)lparam : 0);
      CEvent *event=list.At(list.Total()-1-shift);
      if(event==NULL)
      return;
      //--- Accrue the credit
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional charges
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Correction
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Enumerate bonuses
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Additional commissions
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Daily agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Monthly agent commission
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Interest rate
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled buy deal
      if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Canceled sell deal
      if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Dividend operations
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Accrual of franked dividend
      if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Tax charges
      if(event.TypeEvent()==TRADE_EVENT_TAX)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Replenishing account balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Withdrawing funds from balance
      if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      
      //--- Pending order placed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order removed
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Pending order partially activated by price
      if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position opened partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by partial market order execution (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position reversal by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by a new deal (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial execution of a market order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by activating a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Added volume to a position by partial activation of a pending order (netting)
      if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position partially closed by an opposite one
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by StopLoss
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Position closed partially by TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- StopLimit order activation
      if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and StopLoss price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order, StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss and TakeProfit price
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing order's TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position's StopLoss and TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position StopLoss
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
      //--- Changing position TakeProfit
      if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP)
        {
         ::Print(DFUN,event.TypeEventDescription());
        }
     }
  }
//+------------------------------------------------------------------+
//| Working with events in the tester                                |
//+------------------------------------------------------------------+
void CEngine::EventsHandling(void)
  {
//--- If a trading event is present
   if(this.IsTradeEvent())
     {
      //--- Number of trading events occurred simultaneously
      int total=this.GetTradeEventsTotal();
      for(int i=0;i<total;i++)
        {
         //--- Get the next event from the list of simultaneously occurred events by index
         CEventBaseObj *event=this.GetTradeEventByIndex(i);
         if(event==NULL)
            continue;
         long   lparam=i;
         double dparam=event.DParam();
         string sparam=event.SParam();
         this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
        }
     }
//--- If there is an account event
   if(this.IsAccountsEvent())
     {
      //--- Get the list of all account events occurred simultaneously
      CArrayObj* list=this.GetListAccountEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- If there is a symbol collection event
   if(this.IsSymbolsEvent())
     {
      //--- Get the list of all symbol events occurred simultaneously
      CArrayObj* list=this.GetListSymbolsEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
//--- If there is a timeseries collection event
   if(this.IsSeriesEvent())
     {
      //--- Get the list of all timeseries events occurred simultaneously
      CArrayObj* list=this.GetListSeriesEvents();
      if(list!=NULL)
        {
         //--- Get the next event in a loop
         int total=list.Total();
         for(int i=0;i<total;i++)
           {
            //--- take an event from the list
            CEventBaseObj *event=list.At(i);
            if(event==NULL)
               continue;
            //--- Send an event to the event handler
            long lparam=event.LParam();
            double dparam=event.DParam();
            string sparam=event.SParam();
            this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Ich habe diese Funktionen (bei denen es sich jetzt um Methoden der Klasse CEngine handelt) bereits in den anfänglichen Bibliotheksbeschreibungsartikeln bei der Entwicklung von Test-EAs besprochen. Die Methodenauflistung zeigt deutlich, dass fast jedes Ereignis von einem Journaleintrag begleitet wird. Dementsprechend ist es möglich, die Liste der Ereignis-Flags im globalen Sichtbarkeitsbereich zu erstellen und einfach die erforderlichen Flags zu setzen. In nutzerdefinierten Programmen ist es einfacher, Handler für jedes der aktivierten Flags zu implementieren. Ich werde das später tun.

Daher brauchen wir diese Handler nicht mehr in jedem Nutzerprogramm anzugeben.

Die Klasse verfügt über die Berechnung in der Ereignisbehandlung, der aus dem Indikator aufgerufen wird. Wenn der von der Ereignisbehandlung zurückgegebene Wert gleich Null ist, bedeutet dies, dass noch nicht alle im Indikator verwendeten Zeitreihen konstruiert wurden. Der Indikator sollte OnCalculate() mit dem Rückgabecode 0 verlassen, d.h. auf den nächsten Tick warten und anzeigen, dass noch keine Daten berechnet wurden.

Da ich die Behandlung von Standardindikatoren hinzufüge, ist es notwendig, sicherzustellen, dass der erstellte Indikator berechnet wurde.
Um die Menge der berechneten Daten zu ermitteln, können wir die Funktion BarsCalculated() verwenden, die die bereits durch den Indikator berechnete Datenmenge zurückgibt. Wenn die Daten noch nicht berechnet wurden, gibt die Funktion -1 zurück.

Fügen Sie die Prüfung auf erfolgreiche Berechnung aller erstellten Standardindikatoren in der Puffersammlung zu der Methode hinzu, die das Ereignis 'calculate' behandelt:

//+------------------------------------------------------------------+
//| 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 0;
//--- 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 (not in the tester) and
//--- return either 0 (in case there are empty timeseries), or rates_total
   if(!this.IsTester())
      this.SeriesRefresh(NULL,data_calculate);
   int res=(this.SeriesGetSeriesEmpty()==NULL ? data_calculate.rates_total : 0);
   
//--- Check the amount of calculated standard indicator data
   CArrayObj *list=m_buffers.GetListBuffersWithID();
   if(list!=NULL)
     {
      //--- In a loop by the number of buffers having an ID
      int total=list.Total();
      for(int i=0;i<total;i++)
        {
         //--- get the next calculated buffer using the standard indicator
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorHandle()==INVALID_HANDLE)
            continue;
         //--- if the indicator data is not calculated yet, return zero
         if(buff.IndicatorBarsCalculated()==WRONG_VALUE)
            return 0;
        }
     }
   return res;
  }
//+------------------------------------------------------------------+

Die Logik der Behandlung der erzeugten Indikatorendaten ist im Code der Methoden beschrieben.

Als letzten Schliff bei der Überarbeitung der Bibliothek im aktuellen Artikel werde ich die aktuelle Chart-Periode in die Liste der verwendeten Zeitrahmen aufnehmen.
Die Datei der Bibliotheksservicefunktionen E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Services\DELib.mqh enthält die Funktion, die die Liste der benutzten Zeitrahmen vorbereitet. Wenn die aktuelle Chart-Periode in den Programmeinstellungen nicht angegeben ist, erstellt die Bibliothek ihre Zeitreihen nicht. Aber wir benötigen die Zeitreihen ständig für unsere Arbeit.

Lassen Sie uns die Funktion CreateUsedTimeframesArray() verbessern, indem wir den Codeblock zur Angabe der aktuellen Chart-Periode in der Liste der verwendeten Zeitrahmen hinzufügen:

//+------------------------------------------------------------------+
//| Prepare the array of timeframes for the timeseries collection    |
//+------------------------------------------------------------------+
bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[])
  {
//--- If working with the current chart period, fill the array with the current timeframe description string
   if(mode_used_periods==TIMEFRAMES_MODE_CURRENT)
     {
      ArrayResize(used_periods_array,1,21);
      used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
      return true;
     }
//--- If working with a predefined set of chart periods (from the defined_used_periods string)
   else if(mode_used_periods==TIMEFRAMES_MODE_LIST)
     {
      //--- Set comma as a separator (defined in the Datas.mqh file, page 11)
      string separator=INPUT_SEPARATOR;
      //--- Fill in the array of parameters from the string with predefined timeframes
      int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array);
      //--- if nothing is found, display the appropriate message (working with the current period is selected automatically)
      if(n<1)
        {
         int err_code=GetLastError();
         string err=
           (n==0  ?  
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) :
            DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code)
           );
         Print(err);
         //--- Set the current period to the array
         ArrayResize(used_periods_array,1,21);
         used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
         return false;
        }
     }
//--- If working with the full list of timeframes, fill in the array with strings describing all timeframes
   else
     {
      ArrayResize(used_periods_array,21,21);
      for(int i=0;i<21;i++)
         used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1)));
     }

//--- Add the current chart timeframe to the list of used periods
   bool f=false;
   for(int i=0;i<ArraySize(used_periods_array);i++)
     {
      if(used_periods_array[i]==TimeframeDescription((ENUM_TIMEFRAMES)Period()))
        {
         f=true;
         break;
        }
     }
   //--- If the list of used periods features no timeframe of the current chart
   if(!f)
     {
      //--- Increase the array of used periods by 1 and add the current chart period to it
      ArrayResize(used_periods_array,ArraySize(used_periods_array)+1);
      used_periods_array[ArraySize(used_periods_array)-1]=TimeframeDescription((ENUM_TIMEFRAMES)Period());
     }
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+

Damit sind die Verbesserungen der Bibliotheksklassen abgeschlossen.

Es ist an der Zeit, die Entwicklung des Standardindikators AC für mehrere Symbole und Zeitrahmen zu testen.

Test

Um den Test durchzuführen, werde ich den Indikator aus dem vorhergehenden Artikel verwenden und ihn in \MQL5\Indikatoren\TestDoEasy\Part47\ als TestDoEasyPart47.mq5 speichern.

Wir müssen in den Indikatoreinstellungen angeben, welches Symbol und welchen Zeitrahmen wir bei der Berechnung des Standardindikators AcceleratorOscillator verwenden wollen. Der Indikator soll diese Daten im aktuellen Chart-Unterfenster anzeigen.

Die Kopfzeilen des Indikators soll wie folgt aussehen:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart47.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- properties
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   1

//--- classes

//--- enums

//--- defines

//--- structures

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

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

Geben Sie nur ein Symbol und einen Zeitrahmen des Symbols an, die zur Berechnung des AC-Indikators verwendet werden sollen.

In OnInit() erstellen Sie den Standardindikator AC mit den in den Indikatoreingaben angegebenen Parametern, seiner ID (gleich 1) und den Puffern für die Arbeit damit:

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

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

//--- Create the button panel

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

//--- indicator buffers mapping
//--- Create all the necessary buffer objects for constructing AO
   engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1);

//--- Check the number of buffers specified in the 'properties' block
   if(engine.BuffersPropertyPlotsTotal()!=indicator_plots)
      Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal());
   if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers)
      Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal());
      
//--- Create the color array and set non-default colors to all buffers within the collection
   color array_colors[]={clrGreen,clrRed,clrGray};
   engine.BuffersSetColors(array_colors);

//--- Display short descriptions of created indicator buffers
   engine.BuffersPrintShort();
//--- Set the short name for the indicator and bit depth
   IndicatorSetString(INDICATOR_SHORTNAME,"AC("+InpUsedSymbols+","+TimeframeDescription(InpPeriod)+")");
   IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+2);
//--- Successful
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

In OnCalculate() müssen wir zunächst die Daten für den Berechnungspuffer des Indikators AC vorbereiten. Als Nächstes tragen wir in der Hauptindikatorschleife die Daten für den zu zeichnenden Puffer im aktuellen Chart mit Daten aus dem Berechnungspuffer des AC:

//+------------------------------------------------------------------+
//| OnCalculate code block for working with the library:             |
//+------------------------------------------------------------------+
//--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays
   CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread);

//--- Check for the minimum number of bars for calculation
   if(rates_total<min_bars || Point()==0) return 0;
//--- Handle the Calculate event in the library
//--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick
   if(engine.0)
      return 0;
   
//--- If working in the tester
   if(MQLInfoInteger(MQL_TESTER)) 
     {
      engine.OnTimer(rates_data);   // Working in the library timer
      engine.EventsHandling();      // Working with library events
     }
//+------------------------------------------------------------------+
//| OnCalculate code block for working with the indicator:           |
//+------------------------------------------------------------------+
//--- Check and calculate the number of calculated bars
//--- If limit = 0, there are no new bars - calculate the current one
//--- If limit = 1, a new bar has appeared - calculate the first and the current ones
//--- limit > 1 means the first launch or changes in history - the full recalculation of all data
   int limit=rates_total-prev_calculated;
   
//--- Recalculate the entire history
   if(limit>1)
     {
      limit=rates_total-1;
      engine.BuffersInitPlots();
      engine.BuffersInitCalculates();
     }
   
//--- Prepare data 
   int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod);
   int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total));
   
//--- Fill in the calculated buffer with AO data
   CArrayObj *list=engine.GetBuffersCollection().GetListBuffersWithID();
   if(list!=NULL)
     {
      for(int i=0;i<list.Total();i++)
        {
         CBuffer *buff=list.At(i);
         if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE)
            continue;
         CSeriesDE *series=engine.SeriesGetSeries(buff.Symbol(),buff.Timeframe());
         if(series==NULL)
            return 0;
         ulong used_data=series.AvailableUsedData();
         int copied=engine.GetBuffersCollection().PreparingDataBufferStdInd(IND_AC,1,(int)used_data);
         if(copied<(int)used_data)
            return 0;
        }
     }

//--- Calculate the indicator
   CBar *bar=NULL;         // Bar object for defining the candle direction
   uchar color_index=0;    // Color index to be set for the buffer depending on the candle direction
   
//--- Main calculation loop of the indicator
   for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--)
     {
      engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Dies ist alles, was wir brauchen, um den Standardindikator AC auf dem aktuellen Chart zu berechnen und anzuzeigen, der auf einem beliebigen Symbol/Zeitrahmen berechnet wurde.

Der Datenvorbereitungsblock für den Standardindikator wird verbessert werden (er ist in der gegenwärtigen Implementierung nicht optimal, da er nur zur Überprüfung des Konzepts entwickelt wurde) und wird in nachfolgenden Artikeln in die Bibliothek verschoben.

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

Kompilieren Sie den Indikator und starten Sie ihn auf EURUSD M1, nachdem Sie GBPUSD M5 in den Indikatoreinstellungen festgelegt haben, was bedeutet, dass AC-Indikatordaten (berechnet auf GBPUSD M5) auf dem aktuellen EURUSD-Minuten-Chart angezeigt werden:


GBPUSD M5 mit dem Standardindikator AC ist ebenfalls zum Vergleich geöffnet.

Was kommt als Nächstes?

Im nächsten Artikel werde ich die Entwicklung von Multisymbol- und Mehrperioden-Standardindikatoren 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.
Hinterlassen Sie Ihre Fragen, Kommentare und Anregungen in den Kommentaren.
Bitte bedenken Sie, dass ich hier den MQL5-Testindikator für MetaTrader 5 entwickelt habe.
Die angehängten Dateien sind nur für MetaTrader 5 bestimmt. Die aktuelle Bibliotheksversion wurde nicht mit dem MetaTrader 4 getestet.
Nachdem ich die Funktionalität für die Arbeit mit Indikatorpuffern entwickelt und getestet habe, werde ich versuchen, einige MQL5-Funktionen in MetaTrader 4 zu implementieren.

Zurück zum Inhalt

Frühere Artikel dieser Serie:

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

Zeitreihen in der Bibliothek DoEasy (Teil 40): Bibliotheksbasierte Indikatoren - Aktualisierung der Daten in Echtzeit
Zeitreihen in der Bibliothek DoEasy (Teil 41): Beispiel eines Multi-Symbol- und Multi-Zeitrahmen-Indikators
Zeitreihen in der Bibliothek DoEasy (Teil 42): Abstrakte Objektklasse der Indikatorpuffer
Zeitreihen in der Bibliothek DoEasy (Teil 43): Klassen der Objekte von Indikatorpuffern
Zeitreihen in der Bibliothek DoEasy (Teil 44): Kollektionsklasse der Objekte von Indikatorpuffern
<Zeitreihen in der Bibliothek DoEasy (Teil 45): Puffer für Mehrperiodenindikator

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

Beigefügte Dateien |
MQL5.zip (3744.83 KB)
Über Methoden zum Erkennen überkaufter/überverkaufter Zonen. Teil I Über Methoden zum Erkennen überkaufter/überverkaufter Zonen. Teil I

Überkaufte/überverkaufte Zonen kennzeichnen einen bestimmten Zustand des Marktes, der sich durch schwächere Veränderungen der Wertpapierpreise von anderen unterscheidet. Diese nachteilige Veränderung der Dynamik ist in der letzten Phase der Entwicklung von Trends jeglicher Größenordnung am stärksten ausgeprägt. Da der Gewinn beim Handel direkt von der Fähigkeit abhängt, eine möglichst große Trendamplitude abzudecken, ist die Genauigkeit der Erkennung solcher Zonen eine Schlüsselaufgabe beim Handel mit irgendwelchen Wertpapieren.

Wahrscheinlichkeitstheorie und mathematische Statistik mit Beispielen (Teil I): Grundlagen und elementare Theorie Wahrscheinlichkeitstheorie und mathematische Statistik mit Beispielen (Teil I): Grundlagen und elementare Theorie

Beim Handel geht es immer darum, im Angesicht von Unsicherheit Entscheidungen zu treffen. Das bedeutet, dass die Ergebnisse der Entscheidungen zu dem Zeitpunkt, zu dem diese Entscheidungen getroffen werden, nicht ganz eindeutig sind. Daraus ergibt sich die Bedeutung theoretischer Ansätze für die Konstruktion mathematischer Modelle, die es uns ermöglichen, solche Fälle sinnvoll zu beschreiben.

Ein System von Sprachbenachrichtigungen für Ereignisse und Signale im Handel Ein System von Sprachbenachrichtigungen für Ereignisse und Signale im Handel

Heutzutage spielen Sprachassistenten eine herausragende Rolle im menschlichen Leben, da wir häufig Navigatoren, Sprachsuche und Übersetzer einsetzen. In diesem Artikel werde ich versuchen, ein einfaches und nutzerfreundliches System von Sprachbenachrichtigungen für verschiedene Handelsereignisse, Marktzustände oder durch Handelssignale erzeugte Signale zu entwickeln.

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

Der Artikel betrachtet ein Beispiel für die Erstellung von Mehrsymbol- und Mehrperioden-Standardindikatoren unter Verwendung eines einzigen Indikator-Puffers für die Konstruktion und die Darstellung im Indikator-Unterfenster. Ich werde die Bibliotheksklassen auf die Arbeit mit Standardindikatoren vorbereiten, die im Hauptfenster des Programms arbeiten und mehr als einen Puffer für die Anzeige ihrer Daten haben.