Synchronisierung mehrerer Charts eines Symbols auf verschiedenen Zeitrahmen

Dmitriy Gizlyk | 8 Juni, 2018


Einleitung

Seit Elders Zeiten treffen Händler Handelsentscheidungen basierend auf der Analyse von Charts auf verschiedenen Zeitrahmen. Ich glaube jeder kennt die Situation, wenn Objekte, die globale Trends darstellen, auf die Charts höherer Zeitrahmen eingezeichnet werden, und danach wird das Verhalten des Preises neben diesen Objekten auf kleineren Timeframes analysiert. Während solcher Analyse können die früher erstellten Objekte korrigiert werden. Die Werkzeuge des MetaTrader 5 erlauben es, dies auf einem Chart zu implementieren, indem man den Zeitrahmen wechselt und die eingezeichneten Objekte speichert. Aber was tun, wenn man den Preis gleichzeitig auf mehreren Charts überwachen muss?

Als Lösung kann man in solchen Situationen Templates verwenden. Aber dann muss man nach jeder Änderung mindestens eines Objekts das Template neu speichern und es an jeden Chart neu anwenden. In diesem Artikel schlage ich vor, diesen Vorgang zu automatisieren und die Funktion der Synchronisierung von Charts einem Indikator zuzuweisen.


1. Aufgabenstellung

Die Hauptaufgabe unseres Indikators besteht darin, offene Charts in MetaTrader 5 zu synchronisieren. Dabei muss das Programm die benötigten Charts nach Symbol bestimmen. Das Programm muss auch ständig den Status aller grafischen Objekte auf allen benötigten Charts verfolgen. Jede Änderung des Objekts muss auf den anderen Charts wiederholt werden.

Man kann den Status der grafischen Objekte auf zwei Wege verfolgen. Der erste Weg: eine Periodizität bestimmen und alle eingezeichneten Objekte zyklisch überprüfen und synchronisieren. Der Vorteil dieses Ansatzes — wir brauchen nur eine Programminstanz auf einem der Charts. Allerdings gibt es zwei Probleme:

  • Verzögerung der Synchronisierung, die mit der Periodizität der Aktualisierung verbunden ist;
  • es ist schwierig festzustellen, welcher Status des Objekts als letzter gilt.

Auf den ersten Blick können beide Probleme einfach gelöst werden, indem die Häufigkeit der Synchronisierung erhöht wird und die zuletzt synchronisierte Information über Objekte in den Variablen des Programms oder in einer Datei auf der Festplatte gespeichert wird. Aber mit der Erhöhung der Anzahl von Objekten auf den Charts verlängert sich auch die Zeit der Ausführung jeder Schleife und das Volumen der zu speichernden Daten. Dabei ist es zu beachten, dass im Gegensatz zu Expert Advisors und Skripts werden Indikatoren in einem gemeinsamen Thread des MetaTrader 5 gestartet. Deswegen kann eine zu hohe Belastung auf den Indikator zu Verzögerungen bei der Ausführung anderer Indikatoren und des Terminals im Ganzen führen.

Die zweite Herangehensweise: dem Terminal die Überwachung der Änderungen der Objekte zuweisen und die Synchronisierung der Objekte nach Events des Terminals starten, indem man sie in der Funktion OnChartEvent verarbeitet. Dieser Ansatz erlaubt es dem Programm, nach der Erstellung oder Änderung eines Objekts sofort zu reagieren und auf diese Weise die Verzögerung maximal zu reduzieren. Deswegen müssen wir weder Informationen über alle synchronisierten Objekte speichern, noch deren Status auf allen Charts kontrollieren. Dies reduziert wesentlich die Belastung des Programms.

Es scheint, als ob uns die zweite Variante ideal passt. Aber hier gibt es auch ein kleines Problem: die Funktion OnChartEvent wird nur durch die Ereignisse des Charts aufgerufen, auf dem das Programm läuft. Das hätte uns auch nicht angehalten, hätten wir den Master-Chart festgelegt, auf welchen wir alle Änderungen durchführen. In diesem Fall hätte uns eine Instanz des Indikators gereicht. Wir wollen uns aber nicht auf einen Chart beschränken. Wir müssen die Instanzen des Indikators auf jedem Chart starten. Man kann das manuell tun, oder den Vorgang mithilfe der Funktion ChartIndicatorAdd automatisieren.

Für die Implementierung des Programms habe ich mich für die zweite Variante entschieden. So kann die Arbeit des Indikators in zwei Blöcke aufgeteilt werden.

  1. Beim Start des Indikators werden offene Charts nach Symbol gefiltert. Es wird überprüft, ob der Indikator auf den offenen Charts des entsprechenden Symbols vorhanden ist. Es werden Klone aller Objekte des aktuellen Charts auf den ausgewählten Charts erstellt.
  2. Ablaufdiagramm des Initialisierungprozesses.

  3. Verarbeitung von Chart-Events. Wenn das Ereignis der Erstellung oder Änderung eines grafischen Objekts eintrifft, liest das Programm die Informationen über das geänderte Objekt vom Chart ab und fügt diese Daten allen Charts aus der zuvor erstellten Liste hinzu.

Prozess der Verarbeitung von Ereignissen.

Während das Programm läuft, kann der Nutzer Charts öffnen und schließen. Damit die Chartliste immer aktuell bleibt, schlage ich vor, den ersten Vorgang mit einer bestimmen Häufigkeit nach Timer zu starten.


2. Organisieren der Arbeit mit Charts

Zunächst einmal müssen die Klonen des Indikators auf den Charts erstellt werden. Um diese Aufgabe zu bewältigen, erstellen wir die Klasse CCloneIndy. In ihren Variablen speichern wir den Namen des Symbols, des Indikators und den Pfad für den Aufruf des Indikators. Die Klasse hat nur eine public-Funktion für die Auswahl der benötigten Charts - SearchCharts. Der Funktion wird der Identifikator des ersten Charts übergeben, und sie wird das Array der ausgewählten Charts zurückgeben.

class CCloneIndy
  {
private:
   string            s_Symbol;
   string            s_IndyName;
   string            s_IndyPath;

public:
                     CCloneIndy();
                    ~CCloneIndy();
   bool              SearchCharts(long chart,long &charts[]);

protected:
   bool              AddChartToArray(const long chart,long &charts[]);
   bool              AddIndicator(const long master_chart,const long slave_chart);
  };

Bei der Initialisierung der Klasse speichern wir die Ausgangsdaten in den Variablen und geben einen kurzen Namen für den Indikator ein. Den Namen brauchen wir, um Parameter zu erhalten, die für den Start der Instanzen des Indikators benötigt werden.

CCloneIndy::CCloneIndy()
  {
   s_Symbol=_Symbol;
   s_IndyName=MQLInfoString(MQL_PROGRAM_NAME);
   s_IndyPath=MQLInfoString(MQL_PROGRAM_PATH);
   int pos=StringFind(s_IndyPath,"\\Indicators\\",0);
   if(pos>=0)
     {
      pos+=12;
      s_IndyPath=StringSubstr(s_IndyPath,pos);
     }
   IndicatorSetString(INDICATOR_SHORTNAME,s_IndyName);
  }

2.1. Funktion zur Auswahl von Charts nach Symbol

Gehen wir ausführlich auf die Funktion zur Auswahl der benötigten Charts ein. Zunächst überprüfen wir den Identifikator des Master-Charts, der in den Parametern der Funktion übergeben wurde. Wenn er ungültig ist, gibt die Funktion false zurück. Wenn der Identifikator nicht vorgegeben wurde, weisen wir dem Parameter den Identifikator des aktuellen Charts zu. Danach prüfen wir, ob der Name des gespeicherten Symbols mit dem Symbol des Master-Charts übereinstimmt. Wenn die Symbole nicht übereinstimmen, schreiben wir den Symbolnamen für das Filtern von Charts neu.

bool CCloneIndy::SearchCharts(long chart,long &charts[])
  {
   switch((int)chart)
     {
      case -1:
        return false;
        break;
      case 0:
        chart=ChartID();
        break;
      default:
        if(s_Symbol!=ChartSymbol(chart))
           s_Symbol=ChartSymbol(chart);
        break;
     }

Beim nächsten Schritt iterieren wir über alle offenen Charts. Wenn der Identifikator des Charts gleich dem Identifikator des Master-Charts ist, wechseln wir zum nächsten.

   long check_chart=ChartFirst();
   while(check_chart!=-1)
     {
      if(check_chart==chart)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Danach überprüfen wir, ob das Symbol des zu analysierenden Charts dem zu suchenden entspricht. Wenn das Symbol die Suchbedingungen nicht erfüllt, wechseln wir zum nächsten Chart.

      if(ChartSymbol(check_chart)!=s_Symbol)
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Danach überprüfen wir, ob unser Indikator auf dem Chart vorhanden ist. Wenn ja, speichern wir den Identifikator des Charts im Array und wechseln zum nächsten.

      int handl=ChartIndicatorGet(check_chart,0,s_IndyName);
      if(handl!=INVALID_HANDLE)
        {
         AddChartToArray(check_chart,charts);
         check_chart=ChartNext(check_chart);
         continue;
        }

Wenn der Indikator nicht auf dem zu prüfenden Chart vorhanden ist, starten wir die Aufruffunktion, indem wir die Identifikatoren des Master-Charts und des zu prüfenden Charts angeben. Wenn fehlgeschlagen, wechseln wir zum nächsten Chart, und versuchen den Indikator zu diesem Chart beim nächsten Aufruf der Funktion hinzuzufügen.

      if(!AddIndicator(chart,check_chart))
        {
         check_chart=ChartNext(check_chart);
         continue;
        }

Wenn der Indikator erfolgreich zum Chart hinzugefügt wurde, fügen wir den Identifikator dieses Charts zum Array hinzu. Dabei wird die Funktion zur Erstellung von "Klonen" für alle Objekte des Master-Charts auf dem überprüfenden Chart gestartet.

      AddChartToArray(check_chart, charts);
      check_chart=ChartNext(check_chart);
     }
//---
   return true;
  }

Nach dem Ende der Schleife verlassen wir die Funktion und geben true zurück.

2.2. Aufruffunktion des Indikators

Gehen wir auf die Funktion der Bindung des Indikators an einen Chart ein. In den Parametern werden ihr die Identifikatoren des Master-Charts und des Empfänger-Charts übergeben. Ihre Stichhaltigkeit wird am Anfang der Funktion überprüft: die Identifikatoren müssen gültig und nicht gleich sein.

bool CCloneIndy::AddIndicator(const long master_chart,const long slave_chart)
  {
   if(master_chart<0 || slave_chart<=0 || master_chart==slave_chart)
      return false;

Danach fragen wir den Handle des Indikators auf dem Master-Chart ab. Wenn er ungültig ist, gibt die Funktion false zurück.

   int master_handle=ChartIndicatorGet(master_chart,0,s_IndyName);
   if(master_handle==INVALID_HANDLE)
      return false;

Wenn der Handle gültig ist, erhalten wir die Parameter für den Aufruf des gleichen Indikators auf einem neuen Chart. Wenn es beim Abfragen der Parameter ein Fehler auftritt, wird die Funktion mit dem Ergebnis false verlassen.

   MqlParam params[];
   ENUM_INDICATOR type;
   if(IndicatorParameters(master_handle,type,params)<0)
      return false;

Beim nächsten Schritt schreiben wir den bei der Initialisierung gespeicherten Pfad für den Aufruf des Indikators in die erste Zelle des Arrays und in die zweite — den Identifikator des Charts, auf welchem der Indikator ausgeführt wird. Stellen wir die Timeframe des Empfänger-Charts fest und rufen den Indikator auf. Wenn der Aufruf des Indikators fehlgeschlagen ist, wird die Funktion verlassen, es wird false zurückgegeben.

   params[0].string_value=s_IndyPath;
   params[1].integer_value=slave_chart;
   ENUM_TIMEFRAMES Timeframe=ChartPeriod(slave_chart);
   int slave_handle=IndicatorCreate(s_Symbol,Timeframe,type,ArraySize(params),params);
   if(slave_handle<0)
      return false;

Am Ende der Funktion fügen wir dem Empfänger-Chart den Indikator nach dem erhaltenen Handle hinzu.

   return ChartIndicatorAdd(slave_chart,0,slave_handle);
  }

Wahrscheinlich fragt sich der Leser, warum man dem Empfänger-Chart den Indikator nicht nach Handle vom Master-Chart hinzufügen kann. Die Antwort ist ganz einfach: dafür müssen der Indikator und der Chart nach Symbol und Timeframe übereinstimmen. Und wenn wir von unseren Aufgaben ausgehen, unterscheiden sich die Zeitrahmen der Charts voneinander.

Der Quellcode der Klasse ist im Anhang zu finden.

3. Klassen für die Arbeit mit grafischen Objekten

Der nächste Prozess des Programms besteht in der Verarbeitung der Ereignisse und der Übertragung von Daten über grafische Objekte auf andere Charts. Bevor wir anfangen, den Code zu schreiben, müssen wir die Technologie der Datenübertragung zwischen Charts wählen.

Dank den Mitteln der MQL5 können Programme eines Charts Objekte auf einem anderen Chart erstellen und ändern, indem man den Identifikator des Charts in den Funktionen für die Arbeit mit den grafischen Objekten angibt. Das passt für die Arbeit mit einer kleinen Anzahl von Charts und grafischer Objekte.

Es gibt aber auch eine andere Variante. Früher haben wir uns entschieden, für das Verfolgen von Änderungen der Objekte auf dem Chart Ereignisse zu verwenden. Wir haben bereits einen Code für das Hinzufügen von Instanzen des Indikators zu allen benötigten Charts geschrieben. Warum nutzen wir nicht das Ereignis-Modell auch für die Übertragung von Daten über sich ändernde Objekte zwischen den Indikatoren auf verschiedenen Charts? Die Arbeit mit Objekten wird dem Indikator auf dem Chart zugewiesen. Dieser Ansatz erlaubt es uns, die Arbeit mit Objekten unter allen Indikatoren zu verteilen und ein asynchrones Modell zu erstellen.

Klingt schön, aber wie bekannt, erhält die Funktion OnChartEvent nur vier Parameter:

  • Event-ID;
  • Parameter des Events vom Typ long;
  • Parameter des Events vom Typ double;
  • Parameter des Events vom Typ string.

Wie können diese vier Parameter die ganzen Informationen über das Objekt aufnehmen? Wir übergeben einfach den Event-Identifikator, und die ganzen Informationen über das Objekt schreiben wir in den Parameter vom Typ string. Um Informationen über das Objekt in einer Variablen vom Typ string zu sammeln nutzen wir das Wissen aus dem Artikel "Verwendung von Cloud-Speichern für den Datenaustausch zwischen Terminals".

Erstellen wir die Klasse CCloneObjects, die Daten über grafische Objekte sammeln und diese in den Chart ausgeben wird.

class CCloneObjects
  {
private:
   string            HLineToString(long chart, string name, int part);
   string            VLineToString(long chart, string name, int part);
   string            TrendToString(long chart, string name, int part);
   string            RectangleToString(long chart, string name, int part);
   bool              CopySettingsToObject(long chart,string name,string &settings[]);

public:
                     CCloneObjects();
                    ~CCloneObjects();
//---
   string            CreateMessage(long chart, string name, int part);
   bool              DrawObjects(long chart, string message);
  };

Die Funktion dieser Klasse wurde ausführlich hier beschrieben: ich glaube, es ergibt keinen Sinn diese Beschreibung zu wiederholen. Aber bitte beachten Sie: bei der Erzeugung eines benutzerdefinierten Ereignisses durch die Funktion EventChartCustom ist die Länge des sparam-Parameters auf 63 Symbole begrenzt. Deswegen werden wir die Nachricht bei der Übertragung der Objektdaten in zwei Teile teilen. Dafür wurde der Funktion für die Erstellung der Nachricht ein Parameter für die Angabe der benötigten Datenmenge hinzugefügt. Als Beispiel ist unten der Code der Funktion für das Sammeln der Informationen über die Trendlinie angeführt.

string CCloneObjects::TrendToString(long chart,string name, int part)
  {
   string result = NULL;
   if(ObjectFind(chart,name)!=0)
      return result;
   
   switch(part)
     {
      case 0:
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=0="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,0),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=0="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,0))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_DOUBLE)+"="+IntegerToString(OBJPROP_PRICE)+"=1="+DoubleToString(ObjectGetDouble(chart,name,OBJPROP_PRICE,1),5)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_TIME)+"=1="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_TIME,1))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_LEFT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_LEFT))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_RAY_RIGHT)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_RAY_RIGHT))+"|";
        break;
      default:
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_COLOR)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_COLOR))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_WIDTH)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_WIDTH))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_STYLE)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_STYLE))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_INTEGER)+"="+IntegerToString(OBJPROP_BACK)+"="+IntegerToString(ObjectGetInteger(chart,name,OBJPROP_BACK))+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TEXT)+"="+ObjectGetString(chart,name,OBJPROP_TEXT)+"|";
        result+=IntegerToString(ENUM_SET_TYPE_STRING)+"="+IntegerToString(OBJPROP_TOOLTIP)+"="+ObjectGetString(chart,name,OBJPROP_TOOLTIP);
        break;
     }
   return result;
  }

Der Code aller Funktionen kann in den dem Artikel beigefügten Dateien studiert werden.


4. Zusammenstellung des Indikators

Wir haben alles vorbereitet. Nun stellen wir unseren Indikator für das Verfolgen und Klonen grafischer Objekte zusammen. Unser Indikator hat nur einen Parameter — den Identifikator des Charts.

sinput long    Chart =  0;

Beim Start des Indikators muss dieses Parameter immer gleich Null sein. Ich höre schon Frage des Lesers: wozu den Parameter ausgeben, der sich nie ändert?

Sein Wert wird sich beim Aufruf des Indikators aus dem Programm für das Hinzufügen zu den anderen Charts ändern.

Die Funktion ChartID gibt den Identifikator des Charts zurück, von welchem der Indikator aufgerufen wurde, und nicht des Charts, zu welchem er hinzugefügt wurde. Das hängt mit den Besonderheiten der Verarbeitung von Indikatoren in MetaTrader 5 zusammen. Wenn einer und derselbe Indikator nach einem Symbol und Zeitrahmen mehrmals aufgerufen wird, wird er nur einmal gestartet — beim ersten Aufruf. Bei den weiteren Zugriffen, inklusive von den anderen Charts, wird der bereits gestartete Indikator aufgerufen. Und der Indikator läuft auf seinem Chart und gibt Informationen über ihn zurück. Auf diese Weise werden beim Aufruf von Indikatorinstanzen in der Klasse CCloneIndy neue Kopien des Indikators laufen und Informationen über den Chart zurückgeben, auf welchem die erste Instanz gestartet wurde. Um dies zu vermeiden, müssen wir für jede Indikatorinstanz explizit einen Chart angeben, auf welchem er laufen wird.

Schauen wir uns den Code des Indikators genauer an. Im Block der globalen Variablen deklarieren wir:

  • Instanzen der früher erstellten Klassen,
  • Variable für das Speichern des Identifikators des Arbeitscharts
  • und ein Array für das Speichern der Identifikatoren des Charts, auf welchen die Klone der Objekte erstellt werden.
CCloneIndy    *CloneIndy;
CCloneObjects *CloneObjects;
long           l_Chart;
long           ar_Charts[];

In der Funktion OnInit initialisieren wir die Instanzen der Klassen für die Arbeit mit Charts und Objekten.

int OnInit()
  {
//--- indicator Create classes
   CloneIndy   =  new   CCloneIndy();
   if(CheckPointer(CloneIndy)==POINTER_INVALID)
      return INIT_FAILED;
   CloneObjects=  new CCloneObjects();
   if(CheckPointer(CloneObjects)==POINTER_INVALID)
      return INIT_FAILED;

Initialisieren wir den Identifikator des Arbeitscharts.

   l_Chart=(Chart>0 ? Chart : ChartID());

Suchen wir nach offenen Charts nach Symbol. Dabei werden die Indikatorinstanzen allen gefundenen Charts hinzugefügt wenn nötig.

   CloneIndy.SearchCharts(l_Chart,ar_Charts);

Am Ende der Funktion initialisieren wir den Timer mit einem Intervall von 10 Sekunden. Die einzige Aufgabe des Timers ist die Aktualisierung der Liste der Charts für das Klonen von Objekten.

   EventSetTimer(10);
//---
   return(INIT_SUCCEEDED);
  }

In der Funktion OnCalculate werden keine Operationen ausgeführt. Wie bereits oben erwähnt basiert unser Indikator auf einem Event-Modell. Daraus folgt, dass die ganze Funktionalität des Indikators in der Funktion OnChartEvent konzentriert ist. Am Anfang der Funktion deklarieren wir lokale Hilfsvariablen.

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   string message1=NULL;
   string message2=NULL;
   int total=0;

Weiter erstellen wir eine Verzweigung auf dem switch Operator je nach dem eingehenden Event.

Der erste Block wird Informationen über die Erstellung oder Änderung eines Objekts sammeln und den anderen Charts übergeben. Er wird nach den Ereignissen der Erstellung, Änderung oder Bewegung des Objekts auf dem Chart aufgerufen. Wenn diese Events eintreffen, erstellt der Indikator zwei Nachrichten mit den Daten über den Status des Objekts und dann startet die Schleife für das Senden der Nachrichten an alle Charts mit den Indentifikatoren aus unserem Array.

   switch(id)
     {
      case CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_OBJECT_DRAG:
        message1=CloneObjects.CreateMessage(l_Chart,sparam,0);
        message2=CloneObjects.CreateMessage(l_Chart,sparam,1);
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
          {
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message1);
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,message2);
          }
        break;

Der nächste Block wird beim Löschen des Objekts vom Chart gestartet. In diesem Fall muss keine separate Nachricht vorbereitet werden, denn für das Löschen des Objekts reicht sein Name, und den haben wir in der Variablen sparam. Deswegen wird die Schleife für das Senden der Nachrichten an die anderen Charts direkt gestartet.

      case CHARTEVENT_OBJECT_DELETE:
        total=ArraySize(ar_Charts);
        for(int i=0;i<total;i++)
           EventChartCustom(ar_Charts[i],(ushort)id,0,0,sparam);
        break;

Die nächsten zwei Blöcke dienen zum Verarbeiten der Nachrichten, die von den anderen Charts erhalten wurden. Wenn die Informationen über die Erstellung oder Änderung des Objekts eingehen, rufen wir die Funktion auf, die das Objekt auf den Chart ausgibt. In den Parametern der Funktion übergeben wir den Identifikator des Charts und die erhaltene Nachricht.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CHANGE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_CREATE:
      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DRAG:
        CloneObjects.DrawObjects(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;

Wenn Informationen über das Löschen des Objekts eingehen, rufen wir die Funktion für das Löschen des gleichen Objekts auf dem Arbeitschart auf.

      case CHARTEVENT_CUSTOM + CHARTEVENT_OBJECT_DELETE:
        ObjectDelete(l_Chart,sparam);
        ChartRedraw(l_Chart);
        break;
     }
  }

Der Code des Indikators und der verwendeten Klassen kann in den dem Artikel beigefügten Dateien studiert werden.


Fazit

Im Artikel wurde eine Methode zur Erstellung eines Indikators vorgeschlagen, der grafische Objekte automatisch zwischen den Charts des Terminals in Echtzeit kopiert. Dabei wurde der Mechanismus eines gegenseitigen Datenaustauschs zwischen den offenen Charts im Terminal implementiert. Dieser Ansatz beschränkt nicht die Anzahl der zu synchronisierenden Charts. Gleichzeitig kann der Nutzer grafische Objekte auf jedem der zu synchronisierenden Charts erstellen, ändern und löschen. Die Arbeit des Indikators ist im Video dargestellt:


Links

  1. Verwendung von Cloud-Speichern für den Datenaustausch zwischen Terminals

Die im Artikel verwendeten Programme:

#
 Name
Typ 
Beschreibung 
1 ChartObjectsClone.mq5  Indikator  Indikator des Datenaustauschs zwischen Charts
2 CloneIndy.mqh  Klassenbibliothek  Klasse für die Auswahl von Charts nach Symbol
3 CloneObjects.mqh  Klassenbibliothek  Klasse für die Arbeit mit grafischen Objekten
4 ChartObjectsClone.mqproj    Datei mit der Beschreibung des Projekts