Andere Klassen in der Bibliothek DoEasy (Teil 69): Kollektionsklasse der Chart-Objekte

Artyom Trishkin | 21 Mai, 2021

Inhalt


Konzept

Ich habe bereits die Funktionalität für die Arbeit mit Chart-Objekten vorbereitet. Jetzt haben wir das Chart-Objekt mit den Listen der Chart-Fenster-Objekte, die wiederum die Listen der an sie angehängten Indikatoren enthalten. Es ist an der Zeit, dies in einer einzigen Chart-Objekt-Sammlung zu kombinieren, die einen bequemen Zugriff auf jeden im Terminal geöffneten Chart ermöglicht. Die Kollektion wird es uns ermöglichen, die Chart-Objekt-Listen nach beliebigen Eigenschaften zu suchen, zu sortieren und anzufordern und einen ausgewählten Chart oder mehrere Charts bequem zu handhaben.


Verbesserung der Bibliothek der Klasse

Fügen wir zunächst neue Nachrichten zur Bibliothek hinzu.

In \MQL5\Include\DoEasy\Data.mqh fügen wir die neue Nachrichtenindizes hinzu:

   MSG_CHART_OBJ_CHART_WINDOW,                        // Main chart window
   MSG_CHART_OBJ_CHART_SUBWINDOW,                     // Chart subwindow
   MSG_CHART_OBJ_CHART_SUBWINDOWS_NUM,                // Subwindows
   MSG_CHART_OBJ_INDICATORS_MW_NAME_LIST,             // Indicators in the main chart window
   MSG_CHART_OBJ_INDICATORS_SW_NAME_LIST,             // Indicators in the chart window
   MSG_CHART_OBJ_INDICATOR,                           // Indicator
   MSG_CHART_OBJ_INDICATORS_TOTAL,                    // Indicators
   MSG_CHART_OBJ_WINDOW_N,                            // Window
   MSG_CHART_OBJ_INDICATORS_NONE,                     // No indicators
  
//--- CChartObjCollection
   MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,        // Chart collection
   MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,  // Failed to create a new chart object
   MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,         // Failed to add a chart object to the collection
  
  };
//+------------------------------------------------------------------+

und die Nachrichtentexte entsprechend den neu hinzugefügten Indizes:

   {"Главное окно графика","Main chart window"},
   {"Подокно графика","Chart subwindow"},
   {"Подокон","Subwindows"},
   {"Индикаторы в главном окне графика","Indicators in the main chart window"},
   {"Индикаторы в окне графика","Indicators in the chart window"},
   {"Индикатор","Indicator"},
   {"Индикаторов","Indicators total"},
   {"Окно","Window"},
   {"Отсутствуют","No indicators"},
   
//--- CChartObjCollection
   {"Коллекция чартов","Chart collection"},
   {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
   {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
   
  };
//+---------------------------------------------------------------------+

Die Kollektion der Chart-Objekte sollte Änderungen in der Anzahl der geöffneten Charts sowie Änderungen in der Anzahl der geöffneten Fenster in jedem Chart verfolgen, um ihre Listen der Charts und der darin geöffneten Fenster umgehend zu aktualisieren. Einige Änderungen können mit OnChartEvent() nachverfolgt werden. Allerdings gibt die Funktion nach den Tests hauptsächlich an, dass der Chart bestimmte Änderungen erfahren hat — Ereignis einer Änderung auf dem Chart (CHARTEVENT_CHART_CHANGE), d.h. hier gibt es keine Einzelheiten. Deshalb werde ich mit dem Timer arbeiten und die Änderungen in der Anzahl der geöffneten Charts und der Fenster damit verfolgen. Andere Änderungen, die im Chart auftreten können, können entweder über das oben beschriebene OnChartEvent() verfolgt werden oder, indem das Chart-Objekt und das Fensterobjekt des Charts vom Bibliotheksobjekt CBaseObjExt abgeleitet werden, das wiederum ein Nachkomme des Basisobjekts aller CBaseObj-Bibliotheksobjekte ist und seinen Nachkommen eine zusätzliche Ereignisfunktionalität bietet. Dies ist notwendig, falls wir später eine solche Funktionalität für die Arbeit mit Charts benötigen.

Charts werden hauptsächlich im halbautomatischen Modus gehandhabt. Um also Änderungen in der Anzahl der geöffneten Charts und Fenster zu definieren, reicht es aus, zweimal pro Sekunde die aktuelle Anzahl der Charts und Fenster zu überprüfen und mit der vorherigen Anzahl zu vergleichen. Wenn es keine Änderungen gab, dann muss nichts getan werden. Wenn es Änderungen in der Anzahl der Fenster und Charts gibt, werden wir die Daten in unserer Kollektion aktualisieren.

Um die Chart-Objekte im Timer arbeiten zu lassen, benötigt die Kollektion der Chart-Objekt weiter Timer-Zähler. Jede Kollektion, die im Timer arbeitet, verfügt über einen eigenen Timer-Zähler, der es ermöglicht, die angegebene Aktualisierungsfrequenz der Kollektion zu verfolgen. Abgesehen von den Zählerparametern müssen wir die neue Kollektions-ID hinzufügen, da jede Objektsammlung ihre eigene ID hat, mit der wir die Zugehörigkeit einer bestimmten Objektliste zu einer Sammlung definieren können.

Im Abschnitt "Makro-Ersetzungen" von \MQL5\Include\DoEasy\Defines.mqh setzen wir die Parameter des Timer-Zählers der Chart-Objekt-Kollektion und die ID der Chart-Objekt-Kollektionsliste:

//+------------------------------------------------------------------+
//| 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
#define SND_ALERT                      "alert.wav"
#define SND_ALERT2                     "alert2.wav"
#define SND_CONNECT                    "connect.wav"
#define SND_DISCONNECT                 "disconnect.wav"
#define SND_EMAIL                      "email.wav"
#define SND_EXPERT                     "expert.wav"
#define SND_NEWS                       "news.wav"
#define SND_OK                         "ok.wav"
#define SND_REQUEST                    "request.wav"
#define SND_STOPS                      "stops.wav"
#define SND_TICK                       "tick.wav"
#define SND_TIMEOUT                    "timeout.wav"
#define SND_WAIT                       "wait.wav"
//--- Parameters of the orders and deals collection timer
#define COLLECTION_ORD_PAUSE           (250)                      // Orders and deals collection timer pause in milliseconds
#define COLLECTION_ORD_COUNTER_STEP    (16)                       // Increment of the orders and deals collection timer counter
#define COLLECTION_ORD_COUNTER_ID      (1)                        // Orders and deals collection timer counter ID
//--- Parameters of the account collection timer
#define COLLECTION_ACC_PAUSE           (1000)                     // Account collection timer pause in milliseconds
#define COLLECTION_ACC_COUNTER_STEP    (16)                       // Account timer counter increment
#define COLLECTION_ACC_COUNTER_ID      (2)                        // Account timer counter ID
//--- Symbol collection timer 1 parameters
#define COLLECTION_SYM_PAUSE1          (100)                      // Pause of the symbol collection timer 1 in milliseconds (for scanning market watch symbols)
#define COLLECTION_SYM_COUNTER_STEP1   (16)                       // Increment of the symbol timer 1 counter
#define COLLECTION_SYM_COUNTER_ID1     (3)                        // Symbol timer 1 counter ID
//--- Symbol collection timer 2 parameters
#define COLLECTION_SYM_PAUSE2          (300)                      // Pause of the symbol collection timer 2 in milliseconds (for events of the market watch symbol list)
#define COLLECTION_SYM_COUNTER_STEP2   (16)                       // Increment of the symbol timer 2 counter
#define COLLECTION_SYM_COUNTER_ID2     (4)                        // Symbol timer 2 counter ID
//--- Trading class timer parameters
#define COLLECTION_REQ_PAUSE           (300)                      // Trading class timer pause in milliseconds
#define COLLECTION_REQ_COUNTER_STEP    (16)                       // Trading class timer counter increment
#define COLLECTION_REQ_COUNTER_ID      (5)                        // Trading class timer counter ID
//--- Parameters of the timeseries collection timer
#define COLLECTION_TS_PAUSE            (64)                       // Timeseries collection timer pause in milliseconds
#define COLLECTION_TS_COUNTER_STEP     (16)                       // Account timer counter increment
#define COLLECTION_TS_COUNTER_ID       (6)                        // Timeseries timer counter ID
//--- Parameters of the timer of indicator data timeseries collection
#define COLLECTION_IND_TS_PAUSE        (64)                       // Pause of the timer of indicator data timeseries collection in milliseconds
#define COLLECTION_IND_TS_COUNTER_STEP (16)                       // Increment of indicator data timeseries timer counter
#define COLLECTION_IND_TS_COUNTER_ID   (7)                        // ID of indicator data timeseries timer counter
//--- Parameters of the tick series collection timer
#define COLLECTION_TICKS_PAUSE         (64)                       // Tick series collection timer pause in milliseconds
#define COLLECTION_TICKS_COUNTER_STEP  (16)                       // Tick series timer counter increment step
#define COLLECTION_TICKS_COUNTER_ID    (8)                        // Tick series timer counter ID
//--- Parameters of the chart collection timer
#define COLLECTION_CHARTS_PAUSE        (500)                      // Chart collection timer pause in milliseconds
#define COLLECTION_CHARTS_COUNTER_STEP (16)                       // Chart timer counter increment
#define COLLECTION_CHARTS_COUNTER_ID   (9)                        // Chart timer counter ID
//--- Collection list IDs
#define COLLECTION_HISTORY_ID          (0x777A)                   // Historical collection list ID
#define COLLECTION_MARKET_ID           (0x777B)                   // Market collection list ID
#define COLLECTION_EVENTS_ID           (0x777C)                   // Event collection list ID
#define COLLECTION_ACCOUNT_ID          (0x777D)                   // Account collection list ID
#define COLLECTION_SYMBOLS_ID          (0x777E)                   // Symbol collection list ID
#define COLLECTION_SERIES_ID           (0x777F)                   // Timeseries collection list ID
#define COLLECTION_BUFFERS_ID          (0x7780)                   // Indicator buffer collection list ID
#define COLLECTION_INDICATORS_ID       (0x7781)                   // Indicator collection list ID
#define COLLECTION_INDICATORS_DATA_ID  (0x7782)                   // Indicator data collection list ID
#define COLLECTION_TICKSERIES_ID       (0x7783)                   // Tick series collection list ID
#define COLLECTION_MBOOKSERIES_ID      (0x7784)                   // DOM series collection list ID
#define COLLECTION_MQL5_SIGNALS_ID     (0x7785)                   // MQL5 signals collection list ID
#define COLLECTION_CHARTS_ID           (0x7786)                   // Chart collection list ID
//--- Data parameters for file operations

In der Enumeration der ganzzahligen Chart-Eigenschaften entfernen wir die Konstante CHART_PROP_WINDOW_IS_VISIBLE, da ich keine praktische Verwendung dieser Eigenschaft für ein Objekt gefunden habe. Damit wird die Anzahl der Integer-Eigenschaften um 1 reduziert (von 67 auf 66):

#define CHART_PROP_INTEGER_TOTAL (66)                 // Total number of integer properties

Legen wir noch die Sortierkriterien für Chart-Objekte anhand ihrer Eigenschaften fest, indem wir die Objektsortiereigenschaften hinzufügen:

//+------------------------------------------------------------------+
//| Possible chart sorting criteria                                  |
//+------------------------------------------------------------------+
#define FIRST_CHART_DBL_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP)
#define FIRST_CHART_STR_PROP  (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP+CHART_PROP_DOUBLE_TOTAL-CHART_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CHART_MODE
  {
//--- Sort by integer properties
   SORT_BY_CHART_ID = 0,                              // Sort by chart ID
   SORT_BY_CHART_TIMEFRAME,                           // Sort by chart timeframe
   SORT_BY_CHART_SHOW,                                // Sort by the price chart drawing attribute
   SORT_BY_CHART_IS_OBJECT,                           // Sort by chart object (OBJ_CHART) identification attribute

Entfernen wir außerdem die Konstante SORT_BY_CHART_WINDOW_IS_VISIBLE aus der Konstantenliste der Enumeration, da ich diese Eigenschaft im Objekt nicht verwenden werde.

Alle Objekte, deren Kollektionen in der Bibliothek vorhanden sind, verfügen über eigene Listen, die nach verschiedenen Objekteigenschaften sortiert werden können. Die Kollektionsliste des Chart-Objekts wird auch die Sortiermöglichkeit für die Suche und Auswahl von Objekten mit den gewünschten Eigenschaftswerten haben. Für jedes solche Objekt erstellen wir unsere eigenen Sortiermethoden, die in \MQL5\Include\DoEasy\Services\Select.mqh festgelegt sind.

Fügen wir der Klassendatei CSelect neue Methoden hinzu, um die Suche und Sortierung von Chart-Objekten zu organisieren.

Binden wir die Klassendatei der Chart-Objekte in die Klassendatei von CSelect ein:

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\Event.mqh"
#include "..\Objects\Accounts\Account.mqh"
#include "..\Objects\Symbols\Symbol.mqh"
#include "..\Objects\PendRequest\PendRequest.mqh"
#include "..\Objects\Series\SeriesDE.mqh"
#include "..\Objects\Indicators\Buffer.mqh"
#include "..\Objects\Indicators\IndicatorDE.mqh"
#include "..\Objects\Indicators\DataInd.mqh"
#include "..\Objects\Ticks\DataTick.mqh"
#include "..\Objects\Book\MarketBookOrd.mqh"
#include "..\Objects\MQLSignalBase\MQLSignal.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
//+------------------------------------------------------------------+

Deklarieren wir die neuen Methoden am Ende des Codes des Klassenhauptteils:

//+------------------------------------------------------------------+
//| Methods of working with chart data                               |
//+------------------------------------------------------------------+
   //--- Return the list of charts with one of (1) integer, (2) real and (3) string properties meeting a specified criterion
   static CArrayObj *ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode);
   static CArrayObj *ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode);
   //--- Return the chart index with the maximum value of the (1) integer, (2) real and (3) string properties
   static int        FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_INTEGER property);
   static int        FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_DOUBLE property);
   static int        FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_STRING property);
   //--- Return the chart index with the minimum value of the (1) integer, (2) real and (3) string properties
   static int        FindChartMin(CArrayObj *list_source,ENUM_CHART_PROP_INTEGER property);
   static int        FindChartMin(CArrayObj *list_source,ENUM_CHART_PROP_DOUBLE property);
   static int        FindChartMin(CArrayObj *list_source,ENUM_CHART_PROP_STRING property);
//---
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Methods of working with chart data                               |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Return the list of charts with one integer                       |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   int total=list_source.Total();
   for(int i=0; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      long obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of charts with one real                          |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CChartObj *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      double obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the list of charts with one string                        |
//| property meeting the specified criterion                         |
//+------------------------------------------------------------------+
CArrayObj *CSelect::ByChartProperty(CArrayObj *list_source,ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode)
  {
   if(list_source==NULL) return NULL;
   CArrayObj *list=new CArrayObj();
   if(list==NULL) return NULL;
   list.FreeMode(false);
   ListStorage.Add(list);
   for(int i=0; i<list_source.Total(); i++)
     {
      CChartObj *obj=list_source.At(i);
      if(!obj.SupportProperty(property)) continue;
      string obj_prop=obj.GetProperty(property);
      if(CompareValues(obj_prop,value,mode)) list.Add(obj);
     }
   return list;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_INTEGER property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CChartObj *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      long obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_DOUBLE property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CChartObj *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      double obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the maximum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindChartMax(CArrayObj *list_source,ENUM_CHART_PROP_STRING property)
  {
   if(list_source==NULL) return WRONG_VALUE;
   int index=0;
   CChartObj *max_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      max_obj=list_source.At(index);
      string obj2_prop=max_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum integer property value                          |
//+------------------------------------------------------------------+
int CSelect::FindChartMin(CArrayObj* list_source,ENUM_CHART_PROP_INTEGER property)
  {
   int index=0;
   CChartObj *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      long obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      long obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum real property value                             |
//+------------------------------------------------------------------+
int CSelect::FindChartMin(CArrayObj* list_source,ENUM_CHART_PROP_DOUBLE property)
  {
   int index=0;
   CChartObj *min_obj=NULL;
   int total=list_source.Total();
   if(total== 0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      double obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      double obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+
//| Return the chart index in the list                               |
//| with the minimum string property value                           |
//+------------------------------------------------------------------+
int CSelect::FindChartMin(CArrayObj* list_source,ENUM_CHART_PROP_STRING property)
  {
   int index=0;
   CChartObj *min_obj=NULL;
   int total=list_source.Total();
   if(total==0) return WRONG_VALUE;
   for(int i=1; i<total; i++)
     {
      CChartObj *obj=list_source.At(i);
      string obj1_prop=obj.GetProperty(property);
      min_obj=list_source.At(index);
      string obj2_prop=min_obj.GetProperty(property);
      if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i;
     }
   return index;
  }
//+------------------------------------------------------------------+

Ich habe diese Methoden detailliert im Abschnitt "Einrichten der Suche" des Artikels 3 besprochen.

Wenn sich die Anzahl der an den Chart angehängten Fenster ändert, ändern sich die Fensterindizes. Daher benötigen wir manchmal den neuen Fensterindex. Schreiben wir im öffentlichen Abschnitt der Klasse in der Datei \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh des Chart-Fenster-Objekts die neue Methode zum Setzen des Fensterindexes:

//+------------------------------------------------------------------+
//| Chart window indicator object class                              |
//+------------------------------------------------------------------+
class CWndInd : public CObject
  {
private:
   long              m_chart_id;                         // Chart ID
   string            m_name;                             // Indicator short name
   int               m_index;                            // Window index on the chart
   int               m_handle;                           // Indicator handle
public:
//--- Return itself
   CWndInd          *GetObject(void)                     { return &this;         }
//--- Return (1) indicator name, (2) window index and (3) indicator handle
   string            Name(void)                    const { return this.m_name;   }
   int               Index(void)                   const { return this.m_index;  }
   int               Handle(void)                  const { return this.m_handle; }
//--- Set the index of the on-chart window
   void              SetIndex(const int index)           { this.m_index=index;   }
   
//--- Display the description of object properties in the journal (dash=true - hyphen before the description, false - description only)
   void              Print(const bool dash=false)        { ::Print((dash ? "- " : "")+this.Header());                      }
//--- Return the object short name
   string            Header(void)                  const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); }
   
//--- Compare CWndInd objects with each other by the specified property
   virtual int       Compare(const CObject *node,const int mode=0) const;
   
//--- Constructors
                     CWndInd(void);
                     CWndInd(const int handle,const string name,const int index) : m_handle(handle),m_name(name),m_index(index) {}
  };
//+------------------------------------------------------------------+

Lassen Sie uns die Chart-Objekt-Klasse in \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh leicht verbessern.

Wir entfernen die Deklaration der Methode SetVisible() aus der Liste der privaten Methoden zum Setzen der Objekteigenschaften, um die von der Methode gesetzte Eigenschaft loswerden zu können, da sie hier nicht hilfreich ist:

//--- The methods of setting property values
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetVisible(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);

Jenseits des Klassenhauptteils gibt es die Implementierung, die ebenfalls entfernt wird:

//+-------------------------------------------------------------------+
//|  Set the property                                                 |
//| "The total number of chart windows including indicator subwindows"|
//+-------------------------------------------------------------------+
void CChartObj::SetWindowsTotal(void)
  {
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));
  }
//+------------------------------------------------------------------+
//| Set the "Subwindow visibility" property                          |
//+------------------------------------------------------------------+
void CChartObj::SetVisible(void)
  {
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));
  }
//+---------------------------------------------------------------------+
//| Set the property "The number of the first visible bar on the chart" |
//+---------------------------------------------------------------------+
void CChartObj::SetFirstVisibleBars(void)
  {
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));
  }
//+------------------------------------------------------------------+

Die Methode hat einfach die Sichtbarkeitseigenschaft des Hauptfensters des Charts auf die bereits entfernte Integer-Eigenschaft gelegt. Da wir sie nicht verwenden werden, sollten wir alle zugehörigen Methoden loswerden. In jedem Fall kann der Wert der Fenstereigenschaft weiterhin direkt aus der Umgebung und nicht aus den Eigenschaften des Chart-Fenster-Objekts bezogen werden.

Wir deklarieren im privaten Abschnitt der Klasse die Methode zum Erstellen der Liste der an das Chart angehängten Fenster:

//--- The methods of setting property values
   bool              SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false);
   bool              SetScale(const string source,const int scale,const bool redraw=false);
   bool              SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false);
   void              SetVisibleBars(void);
   void              SetWindowsTotal(void);
   void              SetFirstVisibleBars(void);
   void              SetWidthInBars(void);
   void              SetWidthInPixels(void);
   void              SetMaximizedFlag(void);
   void              SetMinimizedFlag(void);
   void              SetExpertName(void);
   void              SetScriptName(void);
   
//--- Create the list of chart windows
   void              CreateWindowsList(void);

public:

Deklarieren wir im öffentlichen Abschnitt der Klasse die Methode zum Aktualisieren der Eigenschaften des Chart-Objekts und seiner Unterfenster:

//--- Update the chart object and its list of indicator windows
   void              Refresh(void);
   
//--- Constructors
                     CChartObj(){;}
                     CChartObj(const long chart_id);


Beim Arrangieren der Arbeit mit der Methode, die die Gesamtzahl der Chart-Fenster zurückgibt, stieß ich auf die Tatsache, dass ich die Daten in den Objekteigenschaften sofort nach der Abfrage einstellen muss. Daher entschied ich mich, die Datenanforderung und ihre Aufnahme in die Objekteigenschaft zu kombinieren.

In der Methode WindowsTotal() fügen wir zunächst die Anforderung und das Setzen der Anzahl der Chart-Fenster in der Methode SetWindowsTotal() hinzu. Als Nächstes implementieren wir die Rückgabe des neu gewonnenen Wertes, der in den Objekteigenschaften gespeichert wird. Entfernen wir die Implementierung der Methode Visible():

//--- Return the number of bars on a chart that are available for display
   int               VisibleBars(void)                               const { return (int)this.GetProperty(CHART_PROP_VISIBLE_BARS);       }

//--- Return the total number of chart windows including indicator subwindows
   int               WindowsTotal(void) 
                       { 
                        this.SetWindowsTotal();
                        return (int)this.GetProperty(CHART_PROP_WINDOWS_TOTAL);
                       }

//--- Return the window visibility
   bool              Visible(void)                                   const { return (bool)this.GetProperty(CHART_PROP_WINDOW_IS_VISIBLE); }

Fügen wir noch eine weitere Methode hinzu, die das Flag zurückgibt, das anzeigt, dass das Chart-Objekt das Hauptobjekt ist — das Chart, an das das bibliotheksbasierte Programm angehängt ist:

//--- Emulate a tick (chart updates - similar to the terminal Refresh command)
   void              EmulateTick(void)                                     { ::ChartSetSymbolPeriod(this.ID(),this.Symbol(),this.Timeframe());}

//--- Return the flag indicating that the chart object belongs to the program chart
   bool              IsMainChart(void)                               const { return(this.m_chart_id==CBaseObj::GetMainChartID());            }
//--- Return the chart window specified by index
   CChartWnd        *GetWindowByIndex(const int index)               const { return this.m_list_wnd.At(index);                               }

Das Basisobjekt CBaseObj aller Bibliotheksobjekte enthält die Variable m_chart_id_main, in der die ID des Charts gespeichert wird, mit dem das Programm gestartet wird. Im Konstruktor erhält die Variable den von der Funktion ChartID() zurückgegebenen Wert. Die aktuelle ID des Charts wird mit der Methode GetMainChartID() der Klasse CBaseObj zurückgegeben, die den in der Variablen m_chart_id_main gesetzten Wert zurückgibt. Wir geben also das Flag zurück, das angibt, dass die aktuelle ID des Charts mit der ID des Programmhauptfensters übereinstimmt. Wenn es eine Übereinstimmung gibt, gibt die Methode true zurück, sonst false.

Aus dem parametrischen Konstruktor entfernen wir die Zeile, durch die der aktuelle Sichtbarkeitswert des Chart-Fensters festgelegt ist:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Set chart ID to the base object
   CBaseObj::SetChartID(chart_id);
//--- Set integer properties
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Chart ID
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Chart timeframe
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Price chart drawing attribute
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Chart object identification attribute
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Show chart above all others
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Access to the context menu using the right click
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Access the Crosshair tool by pressing the middle mouse button
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Scroll the chart horizontally using the left mouse button
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Send messages about mouse wheel events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Send messages about mouse button click and movement events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Send messages about the graphical object creation event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Send messages about the graphical object destruction event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Type of the chart (candlesticks, bars or line)
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Price chart in the foreground
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Mode of shift of the price chart from the right border
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // The mode of automatic shift to the right border of the chart
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Allow managing the chart using a keyboard
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Scale
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Fixed scale mode
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // 1:1 scale mode
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Mode for specifying the scale in points per bar
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Display a symbol ticker in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Display OHLC values in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Display Bid value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Display Ask value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Display Last value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Display vertical separators between adjacent periods
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Display the chart grid
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Display volumes on the chart
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Display text descriptions of the objects
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Number of bars on a chart that are available for display
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // The total number of chart windows including indicator subwindows
   this.SetProperty(CHART_PROP_WINDOW_IS_VISIBLE,::ChartGetInteger(this.ID(),CHART_WINDOW_IS_VISIBLE,0));// Window visibility
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Chart window handle
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Number of the first visible bar on the chart
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Chart width in bars
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Chart width in pixels
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Chart height in pixels
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Chart background color
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Color of axes, scale and OHLC line
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Grid color
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Color of volumes and position opening levels
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Color for the up bar, shadows and body borders of bullish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Color for the down bar, shadows and body borders of bearish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Color of the chart line and the Doji candlesticks
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Color of the bullish candle body
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Color of the bearish candle body
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Bid price line color
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Ask price line color
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Color of the last performed deal's price line (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Color of stop order levels (Stop Loss and Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Enable the ability to drag trading levels on a chart using mouse
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Display the time scale on the chart
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Display the price scale on the chart
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Display the quick trading panel on the chart
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Chart window maximized
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Chart window minimized
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Chart window docked
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Left coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Upper coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Right coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Bottom coordinate of the undocked chart window relative to the virtual screen
//--- Set real properties
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Shift size of the zero bar from the right border in %
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Chart fixed position from the left border in %
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Fixed chart maximum
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Fixed chart minimum
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Scale in points per bar
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Chart minimum
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Chart maximum
//--- Set string properties
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Comment text on the chart
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // name of an EA launched on the chart
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // name of a script launched on the chart
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Chart symbol
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);

Anstelle der Schleife mit den zum Chart gehörenden Fenster-Objekten,

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   int total=this.WindowsTotal();
   for(int i=0;i<total;i++)
     {
      CChartWnd *wnd=new CChartWnd(m_chart_id,i);
      if(wnd==NULL)
         continue;
      m_list_wnd.Sort();
      if(!m_list_wnd.Add(wnd))
         delete wnd;
     }

fügen wir den Aufruf der Methode hinzu, in der die Liste der zum Chart gehörenden Fenster erstellt wird:

   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.CreateWindowsList();

Als Ergebnis sieht der parametrische Konstruktor jetzt wie folgt aus:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CChartObj::CChartObj(const long chart_id)
  {
//--- Set chart ID to the base object
   CBaseObj::SetChartID(chart_id);
//--- Set integer properties
   this.SetProperty(CHART_PROP_ID,chart_id);                                                             // Chart ID
   this.SetProperty(CHART_PROP_TIMEFRAME,::ChartPeriod(this.ID()));                                      // Chart timeframe
   this.SetProperty(CHART_PROP_SHOW,::ChartGetInteger(this.ID(),CHART_SHOW));                            // Price chart drawing attribute
   this.SetProperty(CHART_PROP_IS_OBJECT,::ChartGetInteger(this.ID(),CHART_IS_OBJECT));                  // Chart object identification attribute
   this.SetProperty(CHART_PROP_BRING_TO_TOP,false);                                                      // Show chart above all others
   this.SetProperty(CHART_PROP_CONTEXT_MENU,::ChartGetInteger(this.ID(),CHART_CONTEXT_MENU));            // Access to the context menu using the right click
   this.SetProperty(CHART_PROP_CROSSHAIR_TOOL,::ChartGetInteger(this.ID(),CHART_CROSSHAIR_TOOL));        // Access the Crosshair tool by pressing the middle mouse button
   this.SetProperty(CHART_PROP_MOUSE_SCROLL,::ChartGetInteger(this.ID(),CHART_MOUSE_SCROLL));            // Scroll the chart horizontally using the left mouse button
   this.SetProperty(CHART_PROP_EVENT_MOUSE_WHEEL,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_WHEEL));  // Send messages about mouse wheel events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_MOUSE_MOVE,::ChartGetInteger(this.ID(),CHART_EVENT_MOUSE_MOVE));    // Send messages about mouse button click and movement events to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_CREATE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_CREATE)); // Send messages about the graphical object creation event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_EVENT_OBJECT_DELETE,::ChartGetInteger(this.ID(),CHART_EVENT_OBJECT_DELETE)); // Send messages about the graphical object destruction event to all MQL5 programs on a chart
   this.SetProperty(CHART_PROP_MODE,::ChartGetInteger(this.ID(),CHART_MODE));                            // Type of the chart (candlesticks, bars or line)
   this.SetProperty(CHART_PROP_FOREGROUND,::ChartGetInteger(this.ID(),CHART_FOREGROUND));                // Price chart in the foreground
   this.SetProperty(CHART_PROP_SHIFT,::ChartGetInteger(this.ID(),CHART_SHIFT));                          // Mode of shift of the price chart from the right border
   this.SetProperty(CHART_PROP_AUTOSCROLL,::ChartGetInteger(this.ID(),CHART_AUTOSCROLL));                // The mode of automatic shift to the right border of the chart
   this.SetProperty(CHART_PROP_KEYBOARD_CONTROL,::ChartGetInteger(this.ID(),CHART_KEYBOARD_CONTROL));    // Allow managing the chart using a keyboard
   this.SetProperty(CHART_PROP_QUICK_NAVIGATION,::ChartGetInteger(this.ID(),CHART_QUICK_NAVIGATION));    // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
   this.SetProperty(CHART_PROP_SCALE,::ChartGetInteger(this.ID(),CHART_SCALE));                          // Scale
   this.SetProperty(CHART_PROP_SCALEFIX,::ChartGetInteger(this.ID(),CHART_SCALEFIX));                    // Fixed scale mode
   this.SetProperty(CHART_PROP_SCALEFIX_11,::ChartGetInteger(this.ID(),CHART_SCALEFIX_11));              // 1:1 scale mode
   this.SetProperty(CHART_PROP_SCALE_PT_PER_BAR,::ChartGetInteger(this.ID(),CHART_SCALE_PT_PER_BAR));    // Mode for specifying the scale in points per bar
   this.SetProperty(CHART_PROP_SHOW_TICKER,::ChartGetInteger(this.ID(),CHART_SHOW_TICKER));              // Display a symbol ticker in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_OHLC,::ChartGetInteger(this.ID(),CHART_SHOW_OHLC));                  // Display OHLC values in the upper left corner
   this.SetProperty(CHART_PROP_SHOW_BID_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_BID_LINE));          // Display Bid value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_ASK_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_ASK_LINE));          // Display Ask value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_LAST_LINE,::ChartGetInteger(this.ID(),CHART_SHOW_LAST_LINE));        // Display Last value as a horizontal line on the chart
   this.SetProperty(CHART_PROP_SHOW_PERIOD_SEP,::ChartGetInteger(this.ID(),CHART_SHOW_PERIOD_SEP));      // Display vertical separators between adjacent periods
   this.SetProperty(CHART_PROP_SHOW_GRID,::ChartGetInteger(this.ID(),CHART_SHOW_GRID));                  // Display the chart grid
   this.SetProperty(CHART_PROP_SHOW_VOLUMES,::ChartGetInteger(this.ID(),CHART_SHOW_VOLUMES));            // Display volumes on the chart
   this.SetProperty(CHART_PROP_SHOW_OBJECT_DESCR,::ChartGetInteger(this.ID(),CHART_SHOW_OBJECT_DESCR));  // Display text descriptions of the objects
   this.SetProperty(CHART_PROP_VISIBLE_BARS,::ChartGetInteger(this.ID(),CHART_VISIBLE_BARS));            // Number of bars on a chart that are available for display
   this.SetProperty(CHART_PROP_WINDOWS_TOTAL,::ChartGetInteger(this.ID(),CHART_WINDOWS_TOTAL));          // The total number of chart windows including indicator subwindows
   this.SetProperty(CHART_PROP_WINDOW_HANDLE,::ChartGetInteger(this.ID(),CHART_WINDOW_HANDLE));          // Chart window handle
   this.SetProperty(CHART_PROP_WINDOW_YDISTANCE,::ChartGetInteger(this.ID(),CHART_WINDOW_YDISTANCE,0));  // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.SetProperty(CHART_PROP_FIRST_VISIBLE_BAR,::ChartGetInteger(this.ID(),CHART_FIRST_VISIBLE_BAR));  // Number of the first visible bar on the chart
   this.SetProperty(CHART_PROP_WIDTH_IN_BARS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_BARS));          // Chart width in bars
   this.SetProperty(CHART_PROP_WIDTH_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_WIDTH_IN_PIXELS));      // Chart width in pixels
   this.SetProperty(CHART_PROP_HEIGHT_IN_PIXELS,::ChartGetInteger(this.ID(),CHART_HEIGHT_IN_PIXELS,0));  // Chart height in pixels
   this.SetProperty(CHART_PROP_COLOR_BACKGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_BACKGROUND));    // Chart background color
   this.SetProperty(CHART_PROP_COLOR_FOREGROUND,::ChartGetInteger(this.ID(),CHART_COLOR_FOREGROUND));    // Color of axes, scale and OHLC line
   this.SetProperty(CHART_PROP_COLOR_GRID,::ChartGetInteger(this.ID(),CHART_COLOR_GRID));                // Grid color
   this.SetProperty(CHART_PROP_COLOR_VOLUME,::ChartGetInteger(this.ID(),CHART_COLOR_VOLUME));            // Color of volumes and position opening levels
   this.SetProperty(CHART_PROP_COLOR_CHART_UP,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_UP));        // Color for the up bar, shadows and body borders of bullish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_DOWN,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_DOWN));    // Color for the down bar, shadows and body borders of bearish candlesticks
   this.SetProperty(CHART_PROP_COLOR_CHART_LINE,::ChartGetInteger(this.ID(),CHART_COLOR_CHART_LINE));    // Color of the chart line and the Doji candlesticks
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BULL,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BULL));  // Color of the bullish candle body
   this.SetProperty(CHART_PROP_COLOR_CANDLE_BEAR,::ChartGetInteger(this.ID(),CHART_COLOR_CANDLE_BEAR));  // Color of the bearish candle body
   this.SetProperty(CHART_PROP_COLOR_BID,::ChartGetInteger(this.ID(),CHART_COLOR_BID));                  // Bid price line color
   this.SetProperty(CHART_PROP_COLOR_ASK,::ChartGetInteger(this.ID(),CHART_COLOR_ASK));                  // Ask price line color
   this.SetProperty(CHART_PROP_COLOR_LAST,::ChartGetInteger(this.ID(),CHART_COLOR_LAST));                // Color of the last performed deal's price line (Last)
   this.SetProperty(CHART_PROP_COLOR_STOP_LEVEL,::ChartGetInteger(this.ID(),CHART_COLOR_STOP_LEVEL));    // Color of stop order levels (Stop Loss and Take Profit)
   this.SetProperty(CHART_PROP_SHOW_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_SHOW_TRADE_LEVELS));  // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders)
   this.SetProperty(CHART_PROP_DRAG_TRADE_LEVELS,::ChartGetInteger(this.ID(),CHART_DRAG_TRADE_LEVELS));  // Enable the ability to drag trading levels on a chart using mouse
   this.SetProperty(CHART_PROP_SHOW_DATE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_DATE_SCALE));      // Display the time scale on the chart
   this.SetProperty(CHART_PROP_SHOW_PRICE_SCALE,::ChartGetInteger(this.ID(),CHART_SHOW_PRICE_SCALE));    // Display the price scale on the chart
   this.SetProperty(CHART_PROP_SHOW_ONE_CLICK,::ChartGetInteger(this.ID(),CHART_SHOW_ONE_CLICK));        // Display the quick trading panel on the chart
   this.SetProperty(CHART_PROP_IS_MAXIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MAXIMIZED));            // Chart window maximized
   this.SetProperty(CHART_PROP_IS_MINIMIZED,::ChartGetInteger(this.ID(),CHART_IS_MINIMIZED));            // Chart window minimized
   this.SetProperty(CHART_PROP_IS_DOCKED,::ChartGetInteger(this.ID(),CHART_IS_DOCKED));                  // Chart window docked
   this.SetProperty(CHART_PROP_FLOAT_LEFT,::ChartGetInteger(this.ID(),CHART_FLOAT_LEFT));                // Left coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_TOP,::ChartGetInteger(this.ID(),CHART_FLOAT_TOP));                  // Upper coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_RIGHT,::ChartGetInteger(this.ID(),CHART_FLOAT_RIGHT));              // Right coordinate of the undocked chart window relative to the virtual screen
   this.SetProperty(CHART_PROP_FLOAT_BOTTOM,::ChartGetInteger(this.ID(),CHART_FLOAT_BOTTOM));            // Bottom coordinate of the undocked chart window relative to the virtual screen
//--- Set real properties
   this.SetProperty(CHART_PROP_SHIFT_SIZE,::ChartGetDouble(this.ID(),CHART_SHIFT_SIZE));                 // Shift size of the zero bar from the right border in %
   this.SetProperty(CHART_PROP_FIXED_POSITION,::ChartGetDouble(this.ID(),CHART_FIXED_POSITION));         // Chart fixed position from the left border in %
   this.SetProperty(CHART_PROP_FIXED_MAX,::ChartGetDouble(this.ID(),CHART_FIXED_MAX));                   // Fixed chart maximum
   this.SetProperty(CHART_PROP_FIXED_MIN,::ChartGetDouble(this.ID(),CHART_FIXED_MIN));                   // Fixed chart minimum
   this.SetProperty(CHART_PROP_POINTS_PER_BAR,::ChartGetDouble(this.ID(),CHART_POINTS_PER_BAR));         // Scale in points per bar
   this.SetProperty(CHART_PROP_PRICE_MIN,::ChartGetDouble(this.ID(),CHART_PRICE_MIN));                   // Chart minimum
   this.SetProperty(CHART_PROP_PRICE_MAX,::ChartGetDouble(this.ID(),CHART_PRICE_MAX));                   // Chart maximum
//--- Set string properties
   this.SetProperty(CHART_PROP_COMMENT,::ChartGetString(this.ID(),CHART_COMMENT));                       // Comment text on the chart
   this.SetProperty(CHART_PROP_EXPERT_NAME,::ChartGetString(this.ID(),CHART_EXPERT_NAME));               // name of an EA launched on the chart
   this.SetProperty(CHART_PROP_SCRIPT_NAME,::ChartGetString(this.ID(),CHART_SCRIPT_NAME));               // name of a script launched on the chart
   this.SetProperty(CHART_PROP_SYMBOL,::ChartSymbol(this.ID()));                                         // Chart symbol
   
   this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS);
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Wir entfernen den Code-Block aus der Methode GetPropertyDescription(), die die Beschreibung einer Integer-Eigenschaft eines Objekts zurückgibt,, in dem die Beschreibung des Parameters für die Sichtbarkeit des Fensters erstellt wird, da ich diese Eigenschaft aus dem Objekt entfernt habe:

      property==CHART_PROP_WINDOWS_TOTAL  ?  CMessage::Text(MSG_CHART_OBJ_WINDOWS_TOTAL)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CHART_PROP_WINDOW_IS_VISIBLE  ?  CMessage::Text(MSG_CHART_OBJ_WINDOW_IS_VISIBLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
         )  :
      property==CHART_PROP_WINDOW_HANDLE  ?  CMessage::Text(MSG_CHART_OBJ_WINDOW_HANDLE)+
         (!this.SupportProperty(property) ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

In den Methoden, die die Daten aller Indikatoren in allen Chart-Fenstern und die Eigenschaften aller Chart-Fenster im Journal anzeigen, ersetzen wir die Schleifengröße durch die Anzahl der Chart-Fenster-Objekte in den Listen:

//+-------------------------------------------------------------------+
//| Display data of all indicators of all chart windows in the journal|
//+-------------------------------------------------------------------+
void CChartObj::PrintWndIndicators(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.PrintIndicators(true);
     }
  }
//+------------------------------------------------------------------+
//| Display the properties of all chart windows in the journal       |
//+------------------------------------------------------------------+
void CChartObj::PrintWndParameters(void)
  {
   for(int i=0;i<this.m_list_wnd.Total();i++)
     {
      CChartWnd *wnd=m_list_wnd.At(i);
      if(wnd==NULL)
         continue;
      wnd.PrintParameters(true);
     }
  }
//+------------------------------------------------------------------+

Zuvor wurden die Schleifen bis zu dem von der Methode WindowsTotal() zurückgegebenen Wert gezählt, was nicht ganz korrekt ist. Die Methode gibt den tatsächlichen Wert aus dem Chart zurück, während die Listen entweder mehr oder weniger Objekte im Vergleich zu ihrer tatsächlichen Anzahl enthalten können. Dies geschieht beim Ändern ihrer Anzahl im Terminal Chart. Daher ist die Schleife, die auf der falschen Anzahl von Objekten in der Liste basiert, fehlerbehaftet. Genau das habe ich hier behoben.

Implementieren wir die Methode, die das Chart-Objekt und die Liste seiner Fenster aktualisiert:

//+------------------------------------------------------------------+
//| Update the chart object and its window list                      |
//+------------------------------------------------------------------+
void CChartObj::Refresh(void)
  {
   int change=this.WindowsTotal()-this.m_list_wnd.Total();
   if(change==0)
      return;
   this.CreateWindowsList();
  }
//+------------------------------------------------------------------+

Hier zählen wir die Differenz zwischen der tatsächlichen Anzahl der Chart-Fenster und der Anzahl der Fenster-Objekte in der Liste. Wenn es keine Differenz gibt, gab es keine Änderungen — wir verlassen die Methode, andernfalls erstellen wir erneut die vollständige Liste aller zum Chart-Objekt gehörenden Objekte der Chart-Fenster. Das ist viel einfacher, als nach Unterschieden zwischen Objekten oder fehlenden Chart-Objekten zu suchen, um überflüssige zu entfernen oder neue in die Liste aufzunehmen.

Implementieren wir die Methode, die die Liste der zum Chart-Objekt gehörenden Objekte der Chart-Fenster erstellt:

//+------------------------------------------------------------------+
//| Create the list of chart windows                                 |
//+------------------------------------------------------------------+
void CChartObj::CreateWindowsList(void)
  {
   this.m_list_wnd.Clear();
   int total=this.WindowsTotal();
   for(int i=0;i<total;i++)
     {
      CChartWnd *wnd=new CChartWnd(m_chart_id,i);
      if(wnd==NULL)
         continue;
      this.m_list_wnd.Sort();
      if(!m_list_wnd.Add(wnd))
         delete wnd;
     }
  }
//+------------------------------------------------------------------+

Hier leeren wir die Liste der Fenster-Objekte, erhalten die Gesamtzahl der Chart-Fenster aus seinen Terminal-Parametern und, in der Schleife durch die erhaltene Anzahl von Fenstern, erzeugen ein neues Chart-Fenster-Objekt und hinzufügen es zur Liste. Wenn das Hinzufügen des Objekts zur Liste fehlgeschlagen ist, entfernen wir es, um Speicherlecks zu vermeiden.

Damit ist die Verbesserung der Bibliotheksklassen abgeschlossen.

Beginnen wir mit der Erstellung der Kollektionsklasse der Chart-Objekte.


Die Kollektionsklasse der Chart-Objekte

Ähnlich wie beim Chart-Objekt, wo wir die Änderungen in der Anzahl der Chart-Fenster überprüfen, um die Aktualisierung ihrer Anzahl im Objekt zu aktivieren, werden wir in der Chart-Objekt-Kollektion die Änderungen in der Anzahl der geöffneten Charts überprüfen, um den Neuaufbau der Kollektionsliste der Chart-Objekte zu starten.

Zuerst überprüfen wir die Anzahl der Fenster der vorhandenen Chart-Objekte. Als Nächstes wird die Änderung der Anzahl der geöffneten Charts geprüft. Wenn das Prüfergebnis ungleich Null ist, wird die Kollektion der Chart-Objekte neu erstellt.

Erstellen wir im Bibliotheksordner \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh die neue Datei der Klasse CChartObjCollection. Die Klasse sollte von der Klasse des Basisobjekts aller Bibliotheksobjekte CBaseObj abgeleitet sein. Die Chart-Objekt-Datei sollte in die Datei der Chart-Objekt-Kollektionsklasse eingebunden werden:

//+------------------------------------------------------------------+
//|                                           ChartObjCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Chart\ChartObj.mqh"
//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
  }

Im privaten Teil der Klasse deklarieren wir die CListObj-Liste, in der Chart-Objekte gespeichert werden, die Variable zur Abfrage der bisherigen Anzahl der geöffneten Charts im Terminal und Hilfsmethoden zur Gestaltung des Klassenbetriebs:

//+------------------------------------------------------------------+
//| MQL5 signal object collection                                    |
//+------------------------------------------------------------------+
class CChartObjCollection : public CBaseObj
  {
private:
   CListObj                m_list;                                   // List of chart objects
   int                     m_charts_total_prev;                      // Previous number of charts in the terminal
   //--- Return the number of charts in the terminal
   int                     ChartsTotal(void) const;
   //--- Return the flag indicating the existence of (1) a chart object and (2) a chart
   bool                    IsPresentChartObj(const long chart_id);
   bool                    IsPresentChart(const long chart_id);
   //--- Create a new chart object and add it to the list
   bool                    CreateNewChartObj(const long chart_id,const string source);
   //--- Find the missing chart object, create it and add it to the collection list
   bool                    FindAndCreateMissingChartObj(void);
   //--- Find a chart object not present in the terminal and remove it from the list
   void                    FindAndDeleteExcessChartObj(void);
public:
  }

Richten wir die Standardmethoden im öffentlichen Bereich der Klasse ein:

public:
//--- Return (1) itself and (2) chart object collection list
   CChartObjCollection    *GetObject(void)                                 { return &this;                  }
   CArrayObj              *GetList(void)                                   { return &this.m_list;           }
   //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion
   CArrayObj              *GetList(ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
   CArrayObj              *GetList(ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode);  }
//--- Return the number of chart objects in the list
   int                     DataTotal(void)                           const { return this.m_list.Total();    }
//--- Display (1) the complete and (2) short collection description in the journal
   void                    Print(void);
   void                    PrintShort(void);
//--- Constructor
                           CChartObjCollection();

und die weiteren Methoden:

//--- Return the pointer to the chart object (1) by ID and (2) by an index in the list
   CChartObj              *GetChart(const long id);
   CChartObj              *GetChart(const int index)                       { return this.m_list.At(index);  }
//--- Return the list of chart objects by (1) symbol and (2) timeframe
   CArrayObj              *GetChartsList(const string symbol)              { return this.GetList(CHART_PROP_SYMBOL,symbol,EQUAL);         }
   CArrayObj              *GetChartsList(const ENUM_TIMEFRAMES timeframe)  { return this.GetList(CHART_PROP_TIMEFRAME,timeframe,EQUAL);   }
//--- Return the chart ID with the program
   long                    GetMainChartID(void)                      const { return CBaseObj::GetMainChartID();   }
//--- Create the collection list of chart objects
   bool                    CreateCollection(void);
//--- Update (1) the chart object collection list and (2) the specified chart object
   void                    Refresh(void);
   void                    Refresh(const long chart_id);

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

Werfen wir einen Blick auf die Implementierung einiger Klassenmethoden.

Im Klassenkonstruktor leeren wir die Liste der Kollektions-Objekte, setzen das Flag "Sortierte Liste" für die Liste, weisen der Chart-Objekt-Kollektions-ID der Liste zu und speichern die Anzahl der aktuell geöffneten Charts im Terminal:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CChartObjCollection::CChartObjCollection()
  {
   this.m_list.Clear();
   this.m_list.Sort();
   this.m_list.Type(COLLECTION_CHARTS_ID);
   this.m_charts_total_prev=this.ChartsTotal();
  }
//+------------------------------------------------------------------+

MQL bietet keine Funktion, die die Anzahl der offenen Charts zurückgibt. Außerdem können wir nicht durch das vorgefertigte Array der offenen Charts in einer Schleife gehen und jeden nachfolgenden Chart durch den Schleifenindex erhalten. Es gibt jedoch eine Funktion, die die ID des allerersten Charts zurückgibt ChartFirst() und die Funktion, die die ID des nächsten Charts nach dem angegebenen zurückgibt ChartNext(). So sind wir in der Lage, eine Schleife durch alle geöffneten Charts zu erstellen, die jeden nachfolgenden Chart nacheinander auf Basis der ID des vorherigen Charts erhält. Die Info zu ChartNext() in der Hilfe enthält ein Beispiel für das Anlegen einer solchen Schleife:

//--- variables for chart identifiers
   long currChart,prevChart=ChartFirst();
   int i=0,limit=100;
   Print("ChartFirst = ",ChartSymbol(prevChart)," ID = ",prevChart);
   while(i<limit)// we probably have no more than 100 open charts
     {
      currChart=ChartNext(prevChart); // get new chart on the basis of the previous one
      if(currChart<0) break;          // the end of chart list is reached
      Print(i,ChartSymbol(currChart)," ID =",currChart);
      prevChart=currChart;// memorize identifier of the current chart for ChartNext()
      i++;// do not forget to increase the counter
     }

Basierend auf einer solchen Schleife werde ich benutzerdefinierte Methoden erstellen, die das Arbeiten mit offenen Charts des Client-Terminals erfordern.

Die Methode, die die Kollektionsliste der Chart-Objekte erstellt:

//+------------------------------------------------------------------+
//| Create the collection list of chart objects                      |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateCollection(void)
  {
   //--- Clear the list and set the flag of sorting by the chart ID
   m_list.Clear();
   m_list.Sort(SORT_BY_CHART_ID);
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- Create the first chart object and add it to the list
   if(!this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- Create the chart object based on the current chart ID in the loop and add it to the list
      if(!this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   //--- Filled in the list successfully
   return true;
  }
//+------------------------------------------------------------------+

Die gesamte Methodenlogik ist hier in den Kommentaren zum Code beschrieben. Kurz gesagt, zuerst löschen wir die Kollektionsliste, indem wir zuvor hinzugefügte Objekte entfernen. Dann wird in der obigen Schleife jedes nachfolgende geöffnete Chart abgerufen und seine ID verwendet, um ein neues Chart-Objekt zu erstellen und es der Kollektionsliste hinzuzufügen.

Die Methode zur Aktualisierung der Kollektionsliste der Chart-Objekte:

//+------------------------------------------------------------------+
//| Update the collection list of chart objects                      |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(void)
  {
   //--- Get the number of open charts in the terminal and
   int charts_total=this.ChartsTotal();
   //--- calculate the difference between the number of open charts in the terminal
   //--- and chart objects in the collection list. These values are displayed in the chart comment
   int change=charts_total-this.m_list.Total();
   Comment(DFUN,", list total=",DataTotal(),", charts total=",charts_total,", change=",change);
   //--- If there are no changes, leave
   if(change==0)
      return;
   //--- If a chart is added in the terminal
   if(change>0)
     {
      //--- Find the missing chart object, create and add it to the collection list
      this.FindAndCreateMissingChartObj();
      //--- Get the current chart and return to it since 
      //--- adding a new chart switches the focus to it
      CChartObj *chart=this.GetChart(GetMainChartID());
      if(chart!=NULL)
         chart.SetBringToTopON(true);
     }
   //--- If a chart is removed from the terminal
   else if(change<0)
    {
     //--- Find an extra chart object in the collection list and remove it from the list
     this.FindAndDeleteExcessChartObj();
    }
   //--- In the loop by the number of chart objects in the list,
   for(int i=0;i<this.m_list.Total();i++)
     {
      //--- get the next chart object and
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      //--- update it
      chart.Refresh();
     }
  }
//+------------------------------------------------------------------+

Die gesamte Methodenlogik ist in den Kommentaren zum Code beschrieben. Kurz gesagt, zuerst wird die Änderung der Anzahl der offenen Charts im Client-Terminal überprüft. Wenn ein Chart hinzugefügt wurde, rufen wir die Methode FindAndCreateMissingChartObj() auf, in der ein fehlendes Chart-Objekt gesucht, erstellt und zur Kollektionsliste hinzugefügt wird. Danach wird der Fokus auf das aktuelle Chart mit dem Programm umgeschaltet (da das Hinzufügen eines neuen Charts im Terminal den Fokus auf dieses schaltet). Wenn das Chart aus dem Client-Terminal entfernt wird, wird die Methode aufgerufen, die nach dem zusätzlichen Chart-Objekt sucht und es aus der Liste entfernt. Schließlich werden alle Chart-Objekte aktualisiert — die Methode Refresh() des Chart-Objekts prüft Änderungen in der Anzahl der an das Chart angehängten Fenster und ändert deren Anzahl in der eigenen Fensterliste, wenn einige Änderungen festgestellt wurden.

Die Methode zum Aktualisieren des Chart-Objekts, das durch die ID:

//+------------------------------------------------------------------+
//| Update the specified chart object                                |
//+------------------------------------------------------------------+
void CChartObjCollection::Refresh(const long chart_id)
  {
   CChartObj *chart=this.GetChart(chart_id);
   if(chart==NULL)
      return;
   chart.Refresh();
  }
//+------------------------------------------------------------------+

Hier holen wir das Chart-Objekt aus der Kollektionsliste nach ID und aktualisieren es.

Die Methode, die die vollständige Kollektionsliste an die Zeitschrift zurückgibt:

//+------------------------------------------------------------------+
//| Display complete collection description to the journal           |
//+------------------------------------------------------------------+
void CChartObjCollection::Print(void)
  {
   //--- Display the header in the journal and print all chart objects in full
   ::Print(CMessage::Text(MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      chart.Print();
     }
  }
//+------------------------------------------------------------------+

Hier wird zuerst die Kopfzeile gedruckt, dann, in der Schleife nach der Gesamtzahl der Objekte in der Kollektionsliste, das nächste Chart-Objekt geholt und seine vollständige Beschreibung angezeigt.

Die Methode, die die kurze Kollektionsliste in das Journal schreibt:

//+------------------------------------------------------------------+
//| Display the short collection description in the journal          |
//+------------------------------------------------------------------+
void CChartObjCollection::PrintShort(void)
  {
   //--- Display the header in the journal and print short descriptions of chart objects
   ::Print(CMessage::Text(MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION),":");
   for(int i=0;i<this.m_list.Total();i++)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      chart.PrintShort(true);
     }
  }
//+------------------------------------------------------------------+

Die Logik der Methode ist identisch mit der oben betrachteten, außer dass das Journal Kurzbeschreibungen von Chart-Objekten anzeigt.

Die Methode gibt die Anzahl der Charts im Terminal zurück:

//+------------------------------------------------------------------+
//| Return the number of charts in the terminal                      |
//+------------------------------------------------------------------+
int CChartObjCollection::ChartsTotal(void) const
  {
   //--- Declare the variables and get the first chart ID
   long currChart,prevChart=::ChartFirst(); 
   int res=1; // We definitely have one chart - the current one
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(res<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      currChart=::ChartNext(prevChart);
      //--- When reaching the end of the chart list, complete the loop
      if(currChart<0) break;
      prevChart=currChart;
      res++;
     }
   //--- Return the obtained loop counter
   return res;
  }
//+------------------------------------------------------------------+

Die Logik der Methode wird in den Code-Kommentaren beschrieben. Kurz gesagt, wir haben mindestens einen offenen Chart — den, auf dem das Programm läuft. Daher beginnt der Schleifenzähler bei eins. Als Nächstes wird in der Schleife jedes nachfolgende Chart basierend auf dem vorherigen empfangen, während der Zähler erhöht wird. Als Ergebnis wird nach dem Ende der Schleife der in der Schleife erhaltene Zählerwert zurückgegeben.

Die Methode erstellt ein neues Chart-Objekt und fügt es der Kollektionsliste hinzu:

//+------------------------------------------------------------------+
//| Create a new chart object and add it to the list                 |
//+------------------------------------------------------------------+
bool CChartObjCollection::CreateNewChartObj(const long chart_id,const string source)
  {
   ::ResetLastError();
   CChartObj *chart_obj=new CChartObj(chart_id);
   if(chart_obj==NULL)
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,true);
      return false;
     }
   this.m_list.Sort(SORT_BY_CHART_ID);
   if(!this.m_list.InsertSort(chart_obj))
     {
      CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,true);
      delete chart_obj;
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Hier erhält die Methode die ID (chart_id) des Charts, dessen Chart-Objekt erzeugt werden soll, und den Namen (source) der Methode, aus der die Methode aufgerufen wird. Erzeugen wir ein neues Chart-Objekt. Wenn die Erstellung fehlgeschlagen ist, zeigen wir die entsprechende Meldung an und geben false zurück. Wenn das Objekt erfolgreich erstellt wurde, setzen wir das Flag der nach Chart-IDs sortierten Liste auf die Kollektionsliste und versuchen, das Objekt der sortierten Liste hinzuzufügen. Wenn das Hinzufügen des Objekts zur Liste fehlgeschlagen ist, geben wir die entsprechende Meldung aus, entfernen das neu erstellte Objekt und geben false zurück. Im Erfolgsfall geben wir true zurück.

Die Methode gibt den Zeiger auf das Chart-Objekt per Chart-ID zurück:

//+------------------------------------------------------------------+
//| Return the pointer to the chart object by ID                     |
//+------------------------------------------------------------------+
CChartObj *CChartObjCollection::GetChart(const long id)
  {
   CArrayObj *list=CSelect::ByChartProperty(GetList(),CHART_PROP_ID,id,EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

Hier erhalten wir die Liste der Chart-Objekte, die die Eigenschaft "Chart-ID" gleich dem an die Methode übergebenen Wert haben (die Liste kann ein einzelnes Chart-Objekt enthalten, da die Chart-IDs eindeutig sind). Wenn das Objekt in der neuen Liste mit einem einzelnen Element erhalten wurde, geben wir es zurück. In allen anderen Fällen wird NULL zurückgegeben — das Objekt wurde nicht erhalten.

Die Methode gibt das Flag zurück, das die Existenz eines Chart-Objekts anzeigt:

//+------------------------------------------------------------------+
//| Return the flag indicating the existence of a chart object       |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChartObj(const long chart_id)
  {
   return(this.GetChart(chart_id)!=NULL);
  }
//+------------------------------------------------------------------+

Hier wird, wenn es möglich ist, ein Objekt mit einer angegebenen ID aus der Kollektionsliste zu erhalten (das Anfrageergebnis ist nicht NULL), true zurückgegeben. Andernfalls wird false zurückgegeben.

Die Methode gibt das Flag zurück, das die Existenz eines Charts im Client-Terminal anzeigt:

//+------------------------------------------------------------------+
//| Return the flag indicating the existence of a chart              |
//+------------------------------------------------------------------+
bool CChartObjCollection::IsPresentChart(const long chart_id)
  {
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst();
   //--- If the IDs match, return 'true'
   if(prev_chart==chart_id)
      return true;
   int i=0; 
   //--- In the loop by the total number of terminal charts (not more than 100)
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- If the IDs match, return 'true'
      if(curr_chart==chart_id)
         return true;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   return false;
  }
//+------------------------------------------------------------------+

Hier in der Schleife über die Terminal-Charts, die auf Basis der vorherigen Chart-ID erhalten wurden, prüfen wir, ob die ID des aktuell ausgewählten Charts in der Schleife mit dem an die Methode übergebenen Wert übereinstimmt. Wenn die Werte übereinstimmen, ist der Chart mit einer solchen ID vorhanden, und es wird true zurückgegeben.
Nach dem Ende der Schleife wird
false zurückgegeben — das Chart mit der angegebenen ID wurde nicht gefunden.

Die Methode, die nach einem fehlenden Chart-Objekt sucht, sowie dieses erstellt und der Kollektionsliste hinzufügt:

//+------------------------------------------------------------------+
//| Find a missing chart object,                                     |
//| create it and add to the collection list                         |
//+------------------------------------------------------------------+
bool CChartObjCollection::FindAndCreateMissingChartObj(void)
  {
   //--- Declare the variables and get the first chart ID
   long curr_chart,prev_chart=::ChartFirst(); 
   int i=0; 
   //--- If the first chart object is not in the list, attempt to create and add it to the list
   if(!this.IsPresentChartObj(prev_chart) && !this.CreateNewChartObj(prev_chart,DFUN))
      return false;
   //--- In the loop by the total number of terminal charts (not more than 100), look for the rest of the charts
   while(i<CHARTS_MAX)
     { 
      //--- based on the previous one, get the new chart
      curr_chart=::ChartNext(prev_chart);
      //--- When reaching the end of the chart list, complete the loop
      if(curr_chart<0) break;
      //--- If the object is not in the list, attempt to create and add it to the list
      if(!this.IsPresentChartObj(curr_chart) && !this.CreateNewChartObj(curr_chart,DFUN))
         return false;
      //--- remember the current chart ID for ChartNext() and increase the loop counter
      prev_chart=curr_chart;
      i++;
     }
   return true;
  }
//+------------------------------------------------------------------+

Auch hier verwenden wir die Schleife über die Charts basierend auf der vorherigen Chart-ID. Erst stellen wir das Vorhandensein/Fehlen des Chart-Objekts mit dem Programm in der Kollektionsliste fest, dann suchen wir nach fehlenden Objekten der im Terminal vorhandenen Charts durch alle verbleibenden Charts in der Schleife, um diese der Kollektionsliste hinzufügen.

Die Methode sucht und entfernt ein Chart-Objekt, das im Terminal fehlt, aber in der Kollektionsliste noch vorhanden ist:

//+-----------------------------------------------------------------------------+
//|Find a chart object not present in the terminal and remove it from the list  |
//+-----------------------------------------------------------------------------+
void CChartObjCollection::FindAndDeleteExcessChartObj(void)
  {
   for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--)
     {
      CChartObj *chart=this.m_list.At(i);
      if(chart==NULL)
         continue;
      if(!this.IsPresentChart(chart.ID()))
        {
         m_list.Delete(i);
        }
     }
  }
//+------------------------------------------------------------------+

Hier, in einer umgekehrten Schleife durch alle Chart-Objekte in der Kollektionsliste, holen wir uns ein anderes Chart-Objekt. Wenn das Client-Terminal aktuell über kein Chart mit einer ID verfügt, die mit der des Chart-Objekts übereinstimmt, entfernen wir das Objekt aus der Kollektionsliste.

Damit ist die Erstellung der ersten Version der Kollektionsklasse für Chart-Objekte abgeschlossen.

Nun müssen wir die Datei der erstellten Chart-Objekt-Kollektionsklasse in die Hauptklasse der Bibliothek CEngine einbinden und die Methoden für den Zugriff auf die Methoden der Chart-Objekt-Kollektionsklasse erstellen, damit wir von unseren Programmen aus mit ihnen arbeiten können.

In \MQL5\Include\DoEasy\Engine.mqh der Klasse CEngine binden wir die Datei einer neu erstellten Chart-Objekt Kollektionsklasse ein:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Services\TimerCounter.mqh"
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Collections\AccountsCollection.mqh"
#include "Collections\SymbolsCollection.mqh"
#include "Collections\ResourceCollection.mqh"
#include "Collections\TimeSeriesCollection.mqh"
#include "Collections\BuffersCollection.mqh"
#include "Collections\IndicatorsCollection.mqh"
#include "Collections\TickSeriesCollection.mqh"
#include "Collections\BookSeriesCollection.mqh"
#include "Collections\MQLSignalsCollection.mqh"
#include "Collections\ChartObjCollection.mqh"
#include "TradingControl.mqh"
//+------------------------------------------------------------------+

Wir deklarieren im privaten Abschnitt der Klasse das Objekt der Kollektionsklasse Chart:

//+------------------------------------------------------------------+
//| Library basis class                                              |
//+------------------------------------------------------------------+
class CEngine
  {
private:
   CHistoryCollection   m_history;                       // Collection of historical orders and deals
   CMarketCollection    m_market;                        // Collection of market orders and deals
   CEventsCollection    m_events;                        // Event collection
   CAccountsCollection  m_accounts;                      // Account collection
   CSymbolsCollection   m_symbols;                       // Symbol collection
   CTimeSeriesCollection m_time_series;                  // Timeseries collection
   CBuffersCollection   m_buffers;                       // Collection of indicator buffers
   CIndicatorsCollection m_indicators;                   // Indicator collection
   CTickSeriesCollection m_tick_series;                  // Collection of tick series
   CMBookSeriesCollection m_book_series;                 // Collection of DOM series
   CMQLSignalsCollection m_signals_mql5;                 // Collection of MQL5.com Signals service signals
   CChartObjCollection  m_charts;                        // Chart collection
   CResourceCollection  m_resource;                      // Resource list
   CTradingControl      m_trading;                       // Trading management object
   CPause               m_pause;                         // Pause object
   CArrayObj            m_list_counters;                 // List of timer counters

Platzieren wir im öffentlichen Klassenteil die Methoden für den Zugriff auf die Kollektionsklasse der Chart-Objekte:

//--- Display (1) the complete, (2) short collection description in the journal and (3) parameters of the signal copying settings
   void                 SignalsMQL5Print(void)                                         { m_signals_mql5.Print();                             }
   void                 SignalsMQL5PrintShort(const bool list=false,const bool paid=true,const bool free=true)
                                                                                       { m_signals_mql5.PrintShort(list,paid,free);          }
   void                 SignalsMQL5CurrentSubscriptionParameters(void)                 { this.m_signals_mql5.CurrentSubscriptionParameters();}
   
//--- Current the chart collection
   bool                 ChartCreateCollection(void)                                    { return this.m_charts.CreateCollection();            }
//--- Return (1) the chart collection and (2) the list of charts from the chart collection
   CChartObjCollection *GetChartObjCollection(void)                                    { return &this.m_charts;                              }
   CArrayObj           *GetListCharts(void)                                            { return this.m_charts.GetList();                     }

//--- Return (1) the specified chart object and (2) the chart object with the program
   CChartObj           *ChartGetChartObj(const long chart_id)                          { return this.m_charts.GetChart(chart_id);            }
   CChartObj           *ChartGetMainChart(void)                                        { return this.m_charts.GetChart(this.m_charts.GetMainChartID());}

//--- Update (1) the chart specified by ID and (2) all charts
   void                 ChartRefresh(const long chart_id)                              { this.m_charts.Refresh(chart_id);                    }
   void                 ChartsRefreshAll(void)                                         { this.m_charts.Refresh();                            }

//--- Return the list of chart objects by (1) symbol and (2) timeframe
   CArrayObj           *ChartGetChartsList(const string symbol)                        { return this.m_charts.GetChartsList(symbol);         }
   CArrayObj           *ChartGetChartsList(const ENUM_TIMEFRAMES timeframe)            { return this.m_charts.GetChartsList(timeframe);      }
   
//--- Return (1) the buffer collection and (2) the buffer list from the collection 

Alle neu aktualisierten Methoden geben einfach das Ergebnis des Aufrufs der entsprechenden Methoden der Chart-Objekt Kollektion zurück, die ich oben gerade betrachtet habe. Alle diese Methoden werden in unseren bibliotheksbasierten Programmen sichtbar und verwendbar sein.

Fügen wir im Klassenkonstruktor die Erstellung eines neuen Zählers für die Chart-Objekt Kollektionsklasse hinzu:

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name=::MQLInfoString(MQL_PROGRAM_NAME);
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE);
   this.CreateCounter(COLLECTION_CHARTS_COUNTER_ID,COLLECTION_CHARTS_COUNTER_STEP,COLLECTION_CHARTS_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+

Wir fügen in der Klasse Timer den Codeblock für die Arbeit mit der Kollektion der Chart-Objekte ein:

//+------------------------------------------------------------------+
//| CEngine timer                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- If this is not a tester, work with collection events by timer
   if(!this.IsTester())
     {
   //--- Timer of the collections of historical orders and deals, as well as of market orders and positions
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- If unpaused, work with the order, deal and position collections events
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Account collection timer
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- If unpaused, work with the account collection events
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Timer 1 of the symbol collection (updating symbol quote data in the collection)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- If the pause is over, update quote data of all symbols in the collection
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- If the pause is over
         if(cnt4.IsTimeDone())
           {
            //--- update data and work with events of all symbols in the collection
            this.SymbolEventsControl();
            //--- When working with the market watch list, check the market watch window events
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Trading class timer
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- If unpaused, work with the list of pending requests
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Timeseries collection timer
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- If the pause is over, work with the timeseries list (update all except the current one)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Timer of timeseries collection of indicator buffer data
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Tick series collection timer
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- If the pause is over, work with the tick series list (update all except the current one)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
   //--- Chart collection timer
      index=this.CounterIndex(COLLECTION_CHARTS_COUNTER_ID);
      CTimerCounter* cnt9=this.m_list_counters.At(index);
      if(cnt9!=NULL)
        {
         //--- If unpaused, work with the chart list
         if(cnt9.IsTimeDone())
            this.ChartsRefreshAll();
        }
     }
//--- If this is a tester, work with collection events by tick
   else
     {
      //--- work with events of collections of orders, deals and positions by tick
      this.TradeEventsControl();
      //--- work with events of collections of accounts by tick
      this.AccountEventsControl();
      //--- update quote data of all collection symbols by tick
      this.m_symbols.RefreshRates();
      //--- work with events of all symbols in the collection by tick
      this.SymbolEventsControl();
      //--- work with the list of pending orders by tick
      this.m_trading.OnTimer();
      //--- work with the timeseries list by tick
      this.SeriesRefresh(data_calculate);
      //--- work with the timeseries list of indicator buffers by tick
      this.IndicatorSeriesRefreshAll();
      //--- work with the list of tick series by tick
      this.TickSeriesRefreshAll();
     }
  }
//+------------------------------------------------------------------+

Sobald der Zähler des Timers für die Kollektion eine halbe Sekunde zählt, wird die Methode zum Aktualisieren aller Chart-Objekte in ihren Kollektionen aufgerufen. Auf diese Weise habe ich eine automatisierte Verfolgung der Änderungen in der Anzahl der Charts im Terminal und der Fenster, die mit jedem geöffneten Chart verbunden sind.

Testen Sie die Arbeit der heute erstellten Klasse.


Test

Um den Test durchzuführen, verwenden wir den EA aus dem vorigen Artikel und speichern ihn in \MQL5\Experts\TestDoEasy\Part69\ als TestDoEasyPart69.mq5.

Tatsächlich ändere ich nicht viel an der Logik des bisherigen Expert Advisors — ich zeige kurze Beschreibungen aller Chart-Objekte an, deren Charts im Terminal geöffnet sind, während die vollständige Beschreibung für den Haupt-Chart angezeigt wird. Außerdem bietet die Methode zur Aktualisierung der Kollektionsliste der Kollektionsklasse der Chart-Objekte die Anzeige einer Chart-Meldung, die über die Anzahl der Chart-Objekte in der Kollektion, die Anzahl der entsprechenden offenen Charts und die Differenz dieser Zahlen informiert. Ich werde diesen Debugging-Kommentar später entfernen. Im aktuellen Artikel werde ich ihn verwenden, um zu prüfen, ob die Chart-Objekt Kollektionsklasse korrekt funktioniert.

Entfernen wir zunächst die Einbindung der Chart-Objekt-Klassendatei aus dem EA-Code:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart68.mq5 |
//|                        Copyright 2021, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
#include <DoEasy\Objects\Chart\ChartObj.mqh>
//--- enums

Jetzt ist die Kollektionsklasse der Chart-Objekte mit dem Hauptobjekt der Bibliothek verbunden und alle Chart-Klassen und ihre Kollektionen sind von ihr aus verfügbar.

Fügen wir im Eingabeblock die Variable ein, die das Flag angibt, das die Notwendigkeit der Verwendung der Chart-Objekt Kollektionsklasse im Programm anzeigt:

//--- input variables
input    ushort            InpMagic             =  123;  // Magic number
input    double            InpLots              =  0.1;  // Lots
input    uint              InpStopLoss          =  150;  // StopLoss in points
input    uint              InpTakeProfit        =  150;  // TakeProfit in points
input    uint              InpDistance          =  50;   // Pending orders distance (points)
input    uint              InpDistanceSL        =  50;   // StopLimit orders distance (points)
input    uint              InpDistancePReq      =  50;   // Distance for Pending Request's activate (points)
input    uint              InpBarsDelayPReq     =  5;    // Bars delay for Pending Request's activate (current timeframe)
input    uint              InpSlippage          =  5;    // Slippage in points
input    uint              InpSpreadMultiplier  =  1;    // Spread multiplier for adjusting stop-orders by StopLevel
input    uchar             InpTotalAttempts     =  5;    // Number of trading attempts
sinput   double            InpWithdrawal        =  10;   // Withdrawal funds (in tester)

sinput   uint              InpButtShiftX        =  0;    // Buttons X shift 
sinput   uint              InpButtShiftY        =  10;   // Buttons Y shift 

input    uint              InpTrailingStop      =  50;   // Trailing Stop (points)
input    uint              InpTrailingStep      =  20;   // Trailing Step (points)
input    uint              InpTrailingStart     =  0;    // Trailing Start (points)
input    uint              InpStopLossModify    =  20;   // StopLoss for modification (points)
input    uint              InpTakeProfitModify  =  60;   // TakeProfit for modification (points)

sinput   ENUM_SYMBOLS_MODE InpModeUsedSymbols   =  SYMBOLS_MODE_CURRENT;            // Mode of used symbols list
sinput   string            InpUsedSymbols       =  "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY";  // List of used symbols (comma - separator)
sinput   ENUM_TIMEFRAMES_MODE InpModeUsedTFs    =  TIMEFRAMES_MODE_LIST;            // Mode of used timeframes list
sinput   string            InpUsedTFs           =  "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator)

sinput   ENUM_INPUT_YES_NO InpUseBook           =  INPUT_YES;                       // Use Depth of Market
sinput   ENUM_INPUT_YES_NO InpUseMqlSignals     =  INPUT_YES;                       // Use signal service
sinput   ENUM_INPUT_YES_NO InpUseCharts         =  INPUT_YES;                       // Use Charts control
sinput   ENUM_INPUT_YES_NO InpUseSounds         =  INPUT_YES;                       // Use sounds

//--- global variables

Jetzt entfernen wir aus dem OnTick() des EA den Codeblock, in dem ich die Liste der Chart-Objekte erstellt und ihre Beschreibungen im Journal angezeigt habe:

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

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

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
   
//--- If it is the first launch
   static bool done=false;
   if(!done)
     {
      //--- Create the list object for storing chart objects
      CArrayObj *list=new CArrayObj();
      if(list==NULL)
         return;
      //--- Declare the variables and get the first chart ID
      long currChart,prevChart=ChartFirst(); 
      int i=0; 
      //--- Create the chart object and add it to the list
      CChartObj *chart_first=new CChartObj(prevChart);
      list.Add(chart_first);
      //--- In the loop by the total number of terminal charts (not more than 100)
      while(i<CHARTS_MAX)
        { 
         //--- based on the previous one, get the new chart
         currChart=ChartNext(prevChart);
         //--- When reaching the end of the chart list, complete the loop
         if(currChart<0) break;
         //--- Create the chart object based on the current chart ID in the loop and add it to the list
         CChartObj *chart=new CChartObj(currChart);
         list.Add(chart);
         //--- remember the current chart ID for ChartNext() and increase the loop counter
         prevChart=currChart;
         i++;
        }
      Print("");
      //--- From the filled list in the loop, receive the next chart object and display its short description
      int total=list.Total();
      for(int j=0;j<total;j++)
        {
         CChartObj *chart_obj=list.At(j);
         if(chart_obj!=NULL)
            chart_obj.PrintShort();
        }
      Print("");
      //--- Display the full description of the current chart: in the loop by all objects of the created list
      for(int j=0;j<total;j++)
        {
         //--- get the next chart object and
         CChartObj *chart_obj=list.At(j);
         //--- if its symbol matches the current chart symbol, display its full description in the journal
         if(chart_obj!=NULL && chart_obj.Symbol()==Symbol())
            chart_obj.Print();
        }
      //--- Destroy the list of chart objects
      delete list;
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Nun wird all dies mit den folgenden Code-Zeilen gemacht:

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

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

//--- If the trailing flag is set
   if(trailing_on)
     {
      TrailingPositions();          // Trailing positions
      TrailingOrders();             // Trailing pending orders
     }
   
//--- If it is the first launch
   static bool done=false;
   if(!done)
     {
      //--- Get the chart with the EA from the collection and display its full description
      CChartObj *chart_main=engine.ChartGetMainChart();
      Print("");
      chart_main.Print();
      done=true;
     }
//---
  }
//+------------------------------------------------------------------+

Hier sehen wir nur die vollständige Beschreibung eines Charts, auf dem ein EA gestartet wird. Die kurzen Beschreibungen aller Chart-Objekte innerhalb der Kollektion sollen im folgenden Codeblock der Funktion OnInitDoEasy() angezeigt werden:

//--- If working with signals is not enabled or failed to create the signal collection, 
//--- disable copying deals by subscription
   else
      engine.SignalsMQL5CurrentSetSubscriptionEnableOFF();

//--- Create the chart collection
//--- If working with charts and the chart collection is created
   if(InpUseCharts && engine.ChartCreateCollection())
     {
      //--- Check created chart objects - display the short collection description in the journal
      engine.GetChartObjCollection().PrintShort();
     }

//--- Create resource text files

In OnBookEvent() des EA, machen wir die Anzeige der Debugging-Daten aus der Kollektionsklasse Market Depth auf dem Chart zu Kommentaren, da wir diese Daten jetzt nicht benötigen (im Moment ist es aufgrund des gelegentlichen seltsamen Terminal-Verhaltens bei der Arbeit mit der Market Depth vorhanden — der EA reagiert manchmal für eine recht lange Zeit nicht, und diese Informationen, die oft ihre Werte ändern, erlauben uns, seine Arbeit zu sehen). Außerdem würde der Kommentar verhindern, dass wir die von der Kollektionsklasse des Chart-Objekts erkannten Änderungen sehen können.

//+------------------------------------------------------------------+
//| OnBookEvent function                                             |
//+------------------------------------------------------------------+
void OnBookEvent(const string& symbol)
  {
   static bool first=true;
   //--- Leave if failed to update the symbol snapshot series
   if(!engine.OnBookEvent(symbol))
      return;
   //--- Work by the current symbol
   if(symbol==Symbol())
     {
      //--- Get the DOM snapshot series of the current symbol
      CMBookSeries *book_series=engine.GetMBookSeries(symbol);
      if(book_series==NULL)
         return;
      //--- Get the last DOM snapshot object from the DOM snapshot series object
      CMBookSnapshot *book=book_series.GetLastMBook();
      if(book==NULL)
         return;
      //--- Get the very first and last DOM order objects from the DOM snapshot object
      CMarketBookOrd *ord_0=book.GetMBookByListIndex(0);
      CMarketBookOrd *ord_N=book.GetMBookByListIndex(book.DataTotal()-1);
      if(ord_0==NULL || ord_N==NULL) return;
      //--- Display the time of the current DOM snapshot in the chart comment,
      //--- the maximum number of displayed orders in DOM for a symbol,
      //--- the obtained number of orders in the current DOM snapshot,
      //--- the total number of DOM snapshots set in the series list and
      //--- the highest and lowest orders of the current DOM snapshot
      //Comment
      //  (
      //   DFUN,book.Symbol(),": ",TimeMSCtoString(book.Time()),
      //   //", symbol book size=",sym.TicksBookdepth(),
      //   ", last book data total: ",book.DataTotal(),
      //   ", series books total: ",book_series.DataTotal(),
      //   "\nMax: ",ord_N.Header(),"\nMin: ",ord_0.Header()
      //  );
      //--- Display the first DOM snapshot in the journal
      if(first)
        {
         //--- series description
         book_series.Print();
         //--- snapshot description
         book.Print();
         first=false;
        }
     }
  }
//+------------------------------------------------------------------+

Kompilieren Sie den EA. Öffnen Sie vier beliebige Charts im Terminal (starten Sie den EA auf dem ersten) und schaffen Sie eine Umgebung, die derjenigen während des Tests im vorherigen Artikel entspricht:

Fügen wir den Fraktal-Indikator in den Chart mit dem EA ein und wir fügen ein weiteres Indikatorfenster hinzu, z.B. DeMarker, das einen anderen Indikator enthält, z.B. AMA, der auf DeMarker-Daten basiert.
Platzieren Sie das Stochastik-Fenster auf den zweiten Chart...

Nach dem Start des EA werden Daten zu den angelegten Chart-Objekten Kollektion Objekte und der Chart mit dem Programm im Journal angezeigt:

Chart collection:
- Main chart window EURUSD H4 ID: 131733844391938630, HWND: 920114, Subwindows: 1
- Main chart window GBPUSD H4 ID: 131733844391938633, HWND: 592798, Subwindows: No
- Main chart window AUDUSD H1 ID: 131733844391938634, HWND: 527424, Subwindows: 2
- Main chart window USDRUB H4 ID: 131733844391938635, HWND: 920468, Subwindows: No
Subscribed to Depth of Market EURUSD
Library initialization time: 00:00:05.922
 
============= The beginning of the parameter list (Main chart window EURUSD H4) =============
Chart ID: 131733844391938630
Timeframe: H4
Drawing attributes of a price chart: Yes
Object "Chart": No
Chart on top of other charts: No
Accessing the context menu by pressing the right mouse button: Yes
Accessing the "Crosshair tool" by pressing the middle mouse button: Yes
Scrolling the chart horizontally using the left mouse button: Yes
Sending messages about mouse wheel events to all mql5 programs on a chart: No
Send notifications of mouse move and mouse click events to all mql5 programs on a chart: No
Send a notification of an event of new object creation to all mql5-programs on a chart: No
Send a notification of an event of object deletion to all mql5-programs on a chart: No
Chart type: Display as Japanese candlesticks
Price chart in the foreground: No
Price chart indent from the right border: Yes
Automatic moving to the right border of the chart: Yes
Managing the chart using a keyboard: Yes
Allowed to intercept Space and Enter key presses on the chart to activate the quick navigation bar: Yes
Scale: 2
Fixed scale mode: No
Scale 1:1 mode: No
Scale to be specified in points per bar: No
Display a symbol ticker in the upper left corner: Yes
Display OHLC values in the upper left corner: Yes
Display Bid values as a horizontal line in a chart: Yes
Display Ask values as a horizontal line in a chart: Yes
Display Last values as a horizontal line in a chart: No
Display vertical separators between adjacent periods: No
Display grid in the chart: No
Display volume in the chart: Trade volumes
Display textual descriptions of objects: Yes
The number of bars on the chart that can be displayed: 137
The total number of chart windows, including indicator subwindows: 2
Chart window handle: 920114
Number of the first visible bar in the chart: 136
Chart width in bars: 168
Chart width in pixels: 670
 Main chart window:
 - Chart height in pixels: 293
 Chart subwindow 1:
 - The distance between the upper frame of the indicator subwindow and the upper frame of the main chart window: 295
 - Chart height in pixels: 21
Chart background color: clrWhite
Color of axes, scales and OHLC line: clrBlack
Grid color: clrSilver
Color of volumes and position opening levels: clrGreen
Color for the up bar, shadows and body borders of bull candlesticks: clrBlack
Color for the down bar, shadows and body borders of bear candlesticks: clrBlack
Line chart color and color of "Doji" Japanese candlesticks: clrBlack
Body color of a bull candlestick: clrWhite
Body color of a bear candlestick: clrBlack
Bid price level color: clrLightSkyBlue
Ask price level color: clrCoral
Line color of the last executed deal price (Last): clrSilver
Color of stop order levels (Stop Loss and Take Profit): clrOrangeRed
Displaying trade levels in the chart (levels of open positions, Stop Loss, Take Profit and pending orders): Yes
Permission to drag trading levels on a chart with a mouse: Yes
Showing the time scale on a chart: Yes
Showing the price scale on a chart: Yes
Showing the "One click trading" panel on a chart: No
Chart window is maximized: Yes
Chart window is minimized: No
The chart window is docked: Yes
The left coordinate of the undocked chart window relative to the virtual screen: 2028
The top coordinate of the undocked chart window relative to the virtual screen: 100
The right coordinate of the undocked chart window relative to the virtual screen: 2654
The bottom coordinate of the undocked chart window relative to the virtual screen: 329
------
The size of the zero bar indent from the right border in percents: 18.93
Chart fixed position from the left border in percent value: 0.00
Fixed  chart maximum: 1.21320
Fixed  chart minimum : 1.16950
Scale in points per bar: 1.00
Chart minimum: 1.16950
Chart maximum: 1.21320
------
Text of a comment in a chart: ""
The name of the Expert Advisor running on the chart: "TestDoEasyPart69"
The name of the script running on the chart: ""
Indicators in the main chart window:
- Indicator Fractals
Indicators in the chart window 1:
- Indicator DeM(14)
- Indicator AMA(14,2,30)
Symbol: "EURUSD"
============= End of the parameter list (Main chart window EURUSD H4) =============

Fügen Sie zunächst mehrere Charts hinzu und entfernen Sie diese anschließend. Die Chart-Kommentare spiegeln die laufenden Änderungen in der Anzahl der Chart-Objekte innerhalb der Kollektion, die Anzahl der im Terminal geöffneten Charts und die Differenz zwischen der Anzahl der Charts und Objekte wider:



Was kommt als Nächstes?

Im nächsten Artikel werde ich die Funktionalität der Chart-Objekt-Kollektion weiter ausbauen.

Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit der Test-EA-Datei für MQL5 zum Testen und Herunterladen angehängt.
Ihre Fragen und Vorschläge schreiben Sie bitte in den Kommentarteil.

Zurück zum Inhalt

*Frühere Artikel dieser Serie:

Andere Klassen in der Bibliothek DoEasy (Teil 67): Objektklasse der Charts
Andere Klassen in der Bibliothek DoEasy (Teil 68): Die Chartfenster-Objektklasse und die Indikator-Objektklassen im Chartfenster