Grundlagen der Programmierung in MQL5: Globale Variablen des MetaTrader 5 Terminals

Dmitry Fedoseev | 5 Dezember, 2016


Inhalt

Einführung

Die globalen Variablen des Terminals (Fig. 1) sind einzigartige Merkmale von MetaTrader und der MQL-Sprache.


Fig. 1. Code-Fragment mit einer globalen Variablen

Verwechseln Sie aber nicht die globalen Variablen des Terminals mit den üblichen globalen Variablen der Programme (Fig. 2), wie sie auch in anderen Programmiersprachen existieren.


Fig. 2. Ein Teil des Codes des MovingAverage EAs der Standardbeispiele des Terminals. Die globalen Variablen des Programms sind rot umrandet

Da es kaum Vergleichbares zu den globalen Variablen des Terminals in anderen Programmiersprachen gibt, werden sie von Anfängern meistens ignoriert und vergessen. Sehr wahrscheinlich haben sie keine Vorstellung, wie und warum gerade diese sinnvoll wären, oder ihre Verwendung erscheint wegen der umständlichen Funktionsnamen zu kompliziert: GlobalVariableSet(), GlobalVariableGet(), GlobalVariableCheck(), etc.

Das wichtigste Merkmal der globalen Variablen des Terminals ist, sie behalten ihren Wert auch nachdem das Terminal geschlossen worden war. Das ist der Grund für eine schnelle, leichte und bequeme Möglichkeit, wichtige Werte zu sichern, und das ist praktisch unverzichtbar bei der Entwicklung verlässlicher EAs mit einer komplexen Interaktion zwischen Positionen und Aufträgen. Wenn Sie die globalen Variablen beherrschen, können Sie sich die Entwicklung von EAs ohne deren Verwendung nicht mehr vorstellen.

Dieser Artikel erlaubt es Ihnen, mit den hier gezeigten praktischen Beispielen alle Funktionen der globalen Variablen mit ihren Möglichkeiten und Verhaltensweisen kennen zu lernen und eine Klasse zu entwickeln, die deren Verwendung stark vereinfacht und beschleunigt.

Die Funktionen für das Arbeiten mit globalen Variablen finden Sie hier in der MQL5-Dokumentation. 

Anzeigen der globalen Variablen im Terminal

Im Terminal versuchen Sie bitte Folgendes: Hauptmenü — Extras — Globale Variablen. Das Fenster der Globalen Variablen erscheint (Fig. 3).

 
Fig. 3. Das Fenster der Globalen Variablen erscheint 

Grundsätzlich wird alles mit den globalen Variablen durch Programme abgewickelt. Dennoch ist dieses Fenster für das Testen eines EAs sehr nützlich. Sie sehen alle globalen Variablen des Terminals mit ihren Namen und deren Werten. Um eine Variable zu ändern, klicken Sie entweder auf den Namen oder den Wert. In diesem Fenster können Sie auch eine neue Variable erstellen. Dazu klicken Sie auf "Hinzufügen" ("Add") in der rechten, oberen Ecke. Mit dem Knopf "Löschen" ("Delete") können Sie globale Variablen löschen. Vor dem Löschen müssen Sie die zu löschende Variable mit einen Klick markieren, erst dann wird der Knopf aktiv. Üben Sie das Erstellen, Ändern und Löschen globaler Variablen. Beachte: Gibt es bereits global Variablen in deren Fenster, lassen Sie sie wie sie sind, es könnte sein, dass sie von einem Ihrer EAs, der auf Ihrem Konto läuft, gebraucht wird.

Eine globale Variable hat drei Attribute: Name, Wert und Zeit. Der Zeitwert zeigt den Zeitpunkt, zu dem auf diese Variable das letzte Mal zugegriffen wurde. Vier Wochen nach dem letzten Zugriff wird eine globale Variable automatisch vom Terminal gelöscht. Wenn also ein Variable einen wichtigen Wert enthält, sollte sie regelmäßig abgefragt werden, um ihr Ablaufdatum zu verschieben.  

Erstellen globaler Variablen des Terminals

Die Variable wird automatisch erzeugt, wenn ihr ein Wert zugewiesen wird. Gibt es diese Variable bereits, wird ihr Wert aktualisiert. Die Funktion GlobalVariableSet() wird für diese Aktualisierung verwendet. Sie benötigt zwei Parameter: Den Variablennamen (Typ "string") und dessen Zahl-Wert (Typ "double"). Versuchen wir das Erstellen einer Variablen. Starten Sie den MetaEditor, erstellen Sie ein Skript und schreiben Sie das Folgende in die Funktion OnStart():

GlobalVariableSet("test",1.23);

 Starten Sie das Skript und öffnen Sie das Fenster der globalen Variablen im Terminal. Das Fenster sollte nun eine neue Variable mit dem Namen "test" und dem Wert 1.23 zeigen (Fig. 4).

 
Fig. 4. Teil des Fensters der globalen Variablen mit der Variablen "test"

Der Code des Beispiels findet sich im Skript sGVTestCreate.  

Abfragen des Wertes einer globalen Variable

Aber obwohl das Skript im obigen Beispiel sich bereits beendet hat, existiert die Variable immer noch. Schauen wir uns dessen Wert an. Die Funktion GlobalVariableGet() verwenden wir, um den Wert abzufragen. Es gibt zwei Versionen dieser Funktion. Für die Erste übergeben wir der Funktion nur den Variablennamen. Die Funktion liefert einen Zahl-Wert ("double"):

double val=GlobalVariableGet("test");
Alert(val);

Wird dieser Code ausgeführt, öffnet sich ein Fenster mit 1.23. Das Beispiel findet sich im Skript sGVTestGet1 im Anhang unten.

Die zweite Version übergibt der Funktion zwei Parameter - Name und eine "double"-Variable für den Wert (die zweite Variable wird als Referenz übergeben) – die jetzt aber den Erfolg der Abfrage true oder false zurückliefert:

double val;
bool result=GlobalVariableGet("test",val);
Alert(result," ",val);

Das Fenster mit dem Inhalt "true 1.23" zeigt das Ergebnis.

Hätten wir versucht, den Wert einer nicht existierenden globalen Variable abzufragen, hätte die Funktion die Werte false und 0 zurückgegeben. Ändern wir mal den Code des Beispiels: weisen wir der Variablen 'val' bei ihrer Deklaration 1.0 zu und versuchen dann den Wert der nicht-existierenden Variablen "test2" abzufragen:

double val=1.0;
bool result=GlobalVariableGet("test2",val);
Alert(result," ",val);

Wir sehen jetzt als Ergebnis das Fenster mit "false 0.0". Das Beispiel findet sich im Skript sGVTestGet2-2 im Anhang unten.

Hätten wir die erste Version der Funktion verwendet, hätten wir auch 0.0 bei der Abfrage einer nicht existierenden Variablen erhalten. Auch den Fehler könnten wir erkennen. Wenn wir die erste Version der Funktion verwenden und das Auftreten eines Fehler überprüfen, erhalten wir die gleiche Funktionalität, die die zweiten Version bietet:

ResetLastError();
double val=GlobalVariableGet("test2");
int err=GetLastError();
Alert(val," ",err);

Im Ergebnis (das Beispiel findet sich im Skript sGVTestGet2-2 im Anhang unten) öffnet sich das Fenster mit "0.0 4501". 0.0 ist der Wert, 4501 die Fehlernummer — "Globale Variable des Client-Terminals wurde nicht gefunden". Das ist jetzt kein kritischer Fehler, eher eine Benachrichtigung. Man kann auf eine nicht existierende Variable zugreifen, wenn der Algorithmus das zulässt. Zum Beispiel beim Verfolgen des Kapitalmaximums:

GlobalVariableSet("max_equity",MathMax(GlobalVariableGet("max_equity",AccountInfoDouble(ACCOUNT_EQUITY)));

Dieser Code arbeitet korrekt, selbst wenn die Variable "max_equity" (noch) nicht existiert. Zuerst wählt die Funktion MathMax() das Maximum der Werten des aktuellen Kapitalstands und des vorher gesicherten Wertes der Variablen "max_equity". Sollte die Variable nicht existieren, erhalten wir den aktuellen Kapitalstand.

Die Namen globaler Variablen

Wie wir sehen können, ist der Name einer globalen Variable eine Zeichenkette ("string"). Es gibt keine Einschränkungen bezüglich der Buchstaben und ihrer Reihenfolge. Jeder Buchstabe, der auf der Tastatur verfügbar ist, kann verwendet werden, inklusive des Abstands (Leertaste) und Buchstaben, die in Dateinamen nicht verwendet werden dürfen. Dennoch empfehle ich die Verwendung einfacher und leicht lesbarer Namen aus normalen Buchstaben, Zahlen und dem Unterstrich, so wie üblich für Variablen.

Es gibt nur eine wesentliche Einschränkung, die es zu beachten gilt bei der Auswahl der Namen — die Länge des Namens: nicht mehr als 63 Zeichen ist.

Die Überprüfung der Existenz einer Variablen

Die Funktion GlobalVariableCheck() prüft die Existenz der Variablen. Es wird der Funktion nur ein Parameter übergeben — der Name der Variablen. Existiert die Variable liefert die Funktion true, andernfalls false. Überprüfen wir, ob die Variablen "test" und "test2" existieren:

bool check1=GlobalVariableCheck("test");
bool check2=GlobalVariableCheck("test2");
Alert(check1," ",check2);

Das Beispiel findet sich im Skript sGVTestCheck im Anhang unten. "true false" im Fenster ist das Ergebnis der Skriptbefehle — die Variable "test" existiert, "test2" hingegen nicht.

Manchmal ist die Existenzprüfung notwendig, zum Beispiel beim Verfolgen eines minimalen Kapitalstands. Wenn wir die Funktion MathMax() durch MathMin() im oberen Beispiel, in dem wir den maximalen Kapitalstand verfolgen, ersetzen, wäre das Ergebnis nicht mehr korrekt und die Variable wäre immer 0.

In diesem Fall, kann die Existenzprüfung helfen.

if(GlobalVariableCheck("min_equity")){
   GlobalVariableSet("min_equity",MathMin(GlobalVariableGet("min_equity"),AccountInfoDouble(ACCOUNT_EQUITY)));
}
else{
   GlobalVariableSet("min_equity",AccountInfoDouble(ACCOUNT_EQUITY));

Wenn die Variable existiert, wählen wir mit Hilfe der Funktion MathMin() den kleinsten Wert. Andernfalls wird der Variablen der Kapitalstand zugewiesen.

Die Zeit einer globalen Variable

Die Zeit einer globalen Variablen haben wir bereits im Bild Fig. 3 gesehen, sie kann mit der Funktion GlobalVariableTime() abgefragt werden. Es wird der Funktion nur ein Parameter übergeben — der Name der Variablen. Die Funktion liefert einen Zeit-Wert ("datetime"):

datetime result=GlobalVariableTime("test");
Alert(result);

Das Beispiel findet sich im Skript sGVTestTime im Anhang unten. Das Zeitattribut der Variablen ändert sich nur bei einem Zugriff auf diese Variable, d.h. keine weitere Funktion ändert diesen Wert, der durch die Funktionen GlobalVariableSet() und GlobalVariableGet() gesetzt wird. Sollten wir manuell den Wert der Variablen über das Fenster aller globalen Variablen ändern, wird auch ihr Zeit-Wert geändert (egal, ob wir den Namen oder den Zahl-Wert ändern).

Wie ich schon erwähnte, existiert die Variable vier Wochen ab dem letzten Zugriff und wird danach durch das Terminal automatisch gelöscht. 

Anzeige aller globalen Variablen

Manchmal brauchen wir eine globale Variable, von der wir ihren genauen Namen nicht wissen. Wir könnten uns an den Beginn erinnern, jedoch nicht an das Ende, zum Beispiel: gvar1, gvar2, etc. Um in solcher Art Variablen zu finden, müssen wir über alle existierenden globalen Variablen des Terminals iterieren und deren Namen überprüfen. Dafür benötigen wir die Funktionen GlobalVariablesTotal() und GlobalVariableName(). Die Funktion GlobalVariablesTotsl() liefert die Gesamtzahl aller globalen Variablen. Die Funktion GlobalVariableName() liefert den Namen auf Grund des Indexes. Es wird der Funktion nur ein Integer-Wert übergeben. Schauen wird zuerst auf alle Variablen und lassen uns ihre Namen und Werte im Nachrichtenfenster anzeigen:

   Alert("=== Start ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
   }

Ein Fenster mit den Namen und Werten aller Variablen öffnet sich (Fig. 5). Das Beispiel findet sich im Skript sGVTestAllNames im Anhang unten.

 
Fig. 5. Das Nachrichtenfenster mit allen globalen Variablen des Terminals

Ergänzen wir eine weitere Prüfung, um bestimmte Merkmale im Variablennamen zu erkennen. Das folgende Beispiel prüft, welche Namen mit "gvar" beginnen (Das Beispiel findet sich im Skript sGVTestAllNames2 im Anhang unten):

   Alert("=== Start ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      if(StringFind(GlobalVariableName(i),"gvar",0)==0){
         Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
      }
   }

Diese Prüfung verwendet die Funktion StringFind(). Wenn Sie Ihre Fähigkeiten im Umgang mit den Funktionen für Zeichenketten ("string") verbessern wollen, empfehle ich Ihnen den Artikel Die Grundlagen von MQL5 Strings.

Das Löschen globaler Variablen

Die Funktion GlobalVariableDel() löscht eine globale Variable und benötigt dafür nur einen Parameter — den Variablennamen. Löschen wir also mal die früher erstellte Variable "test" (das Skript sGVTestDelete ist im Anhang unten):

GlobalVariableDel("test");

Um das Ergebnis zu prüfen, verwenden Sie die Skripte sGVTestGet2-1 oder sGVTestGet2-2 oder öffnen Sie das Fenster aller globalen Variablen.

Das Entfernen einer einzelnen Variablen ist einfach, aber meistens wollen Sie mehr als eine löschen. Die Funktion GlobalVariablesDeleteAll() kann dafür verwendet werden. Es können zwei optionale Parameter der Funktion übergeben werden. Ohne übergebene Parameter löscht die Funktion alle globale Variablen. Normalerweise soll aber nur eine Gruppe von Variablen mit demselben Präfix (Namensanfang) gelöscht werden. Der erste Parameter der Funktion dient genau dieser Festlegung auf den Präfix. Experimentieren wir etwas mit dieser Funktion. Zuerst erstellen wir eine Reihe von Variablen mit unterschiedlichen Präfixen:

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);  

Der Code erstellt vier Variablen: zwei mit dem Präfix gr1_ und zwei andere mit dem Präfix _gr2. Das Beispiel findet sich im Skript sGVTestCreate4 im Anhang unten. Lassen wir das sGVTestAllNames laufen und schauen uns an, was es uns anzeigt (Fig. 6).

 
Fig. 6. Variablen, erstellt durch das Skript sGVTestCreate4

Jetzt löschen wir die Variablen, die beginnen mit gr1_ (das Skript sGVTestDeleteGroup ist im Anhang unten):

GlobalVariablesDeleteAll("gr1_");

Nach dem Ausführen des Codes schauen wir uns wieder alle Variablen an und verwenden dazu das Skript sGVTestAllNames (Fig. 7). Wieder sehen wir in der Liste alle Variablen bis auf die zwei die mit gr1_ angefangen haben.

 
Fig. 7. Die Variablen mit gr1_ am Anfang wurden gelöscht.

Der zweite Parameter der Funktion GlobalVariableDeleteAll() wird nur für das Löschen alter Variablen verwendet. Der Zeitpunkt wird in diesem, zweiten Parameter festgelegt. Ist der Zeitpunkt des letzten Zugriffs auf eine Variable kleiner als der Übergebene, wird die Variable gelöscht. Beachten Sie, nur Variablen mit einer Zugriffszeit kleiner als die festgelegte werden gelöscht, während diejenigen, deren Zeitpunkt nur kleiner oder gleich ist, in der Liste verbleiben. Die Variablen können zusätzlich auch über das Präfix ausgewählt werden. Wenn die Auswahl durch ein Präfix nicht benötigt wird, wird der erste Parameter einfach auf seinen Standard NULL gesetzt:

GlobalVariablesDeleteAll(NULL,StringToTime("2016.10.01 12:37"));

Tatsächlich ist das Löschen von Variablen nur in seltenen und außergewöhnlichen Fällen notwendig, ich möchte mich daher nicht länger mit diesem Thema beschäftigen.

Die Funktion GlobalVariablesFlush

Beim Schließen des Terminals werden die globalen Variablen automatisch in einer Datei gespeichert, aus der sie beim nächste Start des Terminals wieder gelesen werden. Wir müssen nicht alle Details im Einzelnen kennen (Dateiname, Datenformat, etc.), um die globalen Variablen zu verwenden.

Im Falle, dass das Terminal sich in eine Art Notfall selbst beendet, könnten die globalen Variablen verloren gehen. Die Funktion GlobalVariableFlush() hilft, genau dies zu verhindern. Diese Funktion erzwingt die sofortige Sicherung der globalen Variablen. Nachdem die Werte durch die Funktion GlobalVariableSet() gesetzt wurden oder Variablen gelöscht wurden, rufen Sie ganz einfach die Funktion GlobalVariableFlush() auf. Diese Funktion wird ohne Parameter aufgerufen: 

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);   
   
   GlobalVariablesFlush();

Das Beispiel findet sich im Skript sGVTestFlush im Anhang unten. 

Es wäre schön, die Arbeitsweise der Funktion GlobalVariableFlush() näher erläutern zu können, aber unglücklicherweise gelang es mir nicht, die globalen Variablen während eines Notfall-Endes des Terminals verschwinden zu lassen. Das Terminal wurde über das Prozess-Fenster des Task-Managers beendet. Vielleicht verschwinden die globalen Variablen im Falles eines Rechnerabsturzes. Ein erwarteter Rechnerabsturz passiert allerdings heutzutage eher selten, da die meisten Nutzer über Laptops verfügen, während die Standrechner normalerweise über eine unterbrechungsfreie Stromversorgung verfügen. Wenn ein Terminal auf einem "dedicated server" läuft, ist der Schutz vor einer Unterbrechung der Stromversorgung noch besser. Daher speichern globale Variablen ihre Daten sehr zuverlässig, auch ohne die Verwendung der Funktion GlobalVariableFlush().    

Temporäre Variablen, die Funktion GlobalVariableTemp

Die Funktion GlobalVariableTemp() erstellt eine temporäre globale Variable (sie existiert bis das Terminal geschlossen wird). In den Jahren, in denen ich EAs mit MQL5 entwickelte, habe ich noch nie eine solche Variable benötigt. Darüber hinaus widerspricht eine temporäre globale Variable eigentlich ihrem Grundkonzept — eine langfristige Datensicherung unabhängig vom (Neu-)Start des Terminals. Aber da sie nun einmal existiert, sollten wir ihr etwas Aufmerksamkeit erweisen, falls sie doch jemand benötigt.

Für den Aufruf wird dieser Funktion ein einziger Parameter — der Variablenname — übergeben. Existiert unter diesem Namen noch keine Variable, wird eine mit dem Startwert 0 angelegt. Danach kann ihr mit der Funktion GlobalVariableSet() ein Wert zugewiesen werden, so dass sie in herkömmlicher Weise verwendet werden kann. Existiert die Variable bereits (vorher erstellt mit der Funktion GlobalVariableSet()), wird diese Variable aber nicht in eine temporäre umgewandelt:

   GlobalVariableSet("test",1.2); // Setze den Wert der Variable, um sicher zu sein sie existiert
   GlobalVariableTemp("temp"); // erstelle eine temporäre Variable
   Alert("temp variable value right after creation - ",GlobalVariableGet("temp"));
   GlobalVariableSet("temp",3.4); // Setze den Wert der temporären Variablen
   GlobalVariableTemp("test"); // Versuch die die Variable "test" in eine temporäre zu ändern

Das Beispiel findet sich im Skript sGVTestTemp im Anhang unten. Nach dem Starten des Skripts öffnen wir das Fenster aller globalen Variablen. Es sollte die Variable "temp" mit dem Wert 3.4 und "test" mit 1.2 zu sehen sein. Wir schließen jetzt das Fenster und das Terminal und starten dann das Terminal und das Fenster. Die Variable "test" wurde gesichert, aber "temp" ist verschwunden.

Bedingtes Ändern einer Variablen, die Funktion GlobalVariableSetOnCondition

Jetzt ist es an der Zeit sich der letzten und, meiner Meinung nach, interessantesten Funktion zuzuwenden: GlobalVariableSetOnCondition(). Der Funktion werden drei Parameter übergeben: Name, neuere Wert und Testwert. Ist der Wert der Variablen gleich dem Testwert, wird der neue Wert gesichert und die Funktion liefert ein true, ansonsten ein false (das passiert auch, wenn die Variable nicht existiert).

Bezüglich ihrer Arbeitsweise entspricht diese Funktion dem folgenden Code:

   double check_value=1;
   double value=2;

   if(GlobalVariableGet("test")==check_value){
      GlobalVariableSet("test",value);
      return(true);
   }
   else {
      return(false);
   }

Wenn der Wert der globalen Variablen "test" gleich dem Testwert check_value ist, wird der Wert von value zugewiesen und true wird zurückgeliefert, ansonsten — false. Die Variable check_value hat einen Standardwert von 1, so dass, falls die Variable "test" nicht existiert, ebenfalls ein false zurückgegeben wird.

Das Hauptziel der Funktion GlobalVariableSetOnCondition() ist, eine geordnetes Verhalten von einander unabhängiger EAs zu ermöglichen. Da die heutigen Betriebssysteme das Multi-Tasking unterstützen und jeder EA daher im eigenen Thread läuft, kann nicht mehr garantiert werden, dass alle Aufgabe aller EAs hübsch der Reihe nach ausgeführt werden.

Wenn Sie etwas Erfahrung im Umgang mit mt4 haben, erinnern Sie sich sicher noch an die hoch ausgelasteten Handelsausführungen. Jetzt, können mehrere EAs ihre Handelsaufträge zugleich an den Server senden und die werden ausgeführt, anders als früher, als nur jeweils ein EA seinen Auftrag zu einem vorgegebenen Zeitpunkt senden konnte. Liefen mehrere EAs auf dem Terminal, traten häufiger Fehler ("trade server is busy") bei der Auftragsausführung auf, besonders bei "Marketorders". Beim Öffnen oder Schließen von Positionen waren solche Fehler nur ein Ärgernis, da gute EAs ihre Versuche eine Position zu öffnen oder schließen einfach wiederholten. Außerdem war es wohl ziemlich selten, dass ausgerechnet zum selben Zeitpunkt zwei verschiedene EAs ihre Aufträge erteilen wollten. Wenn mehrere EAs jedoch ihren Stopp kontinuierlich nachziehen sollen (als Teil der EAs, nicht des Terminals), dann konnte immer nur jeweils einer seinen Stopp ändern und das konnte schon zu Problem werden. Unabhängig davon, dass es jetzt keine solche Probleme mehr gibt, könnte es dennoch die Notwendigkeit für nacheinander abzuarbeitende Aufgaben einiger EAs geben. 

Die beschriebenen globalen Variablen können genau diesen korrekten Ablauf der Aufgaben sicherstellen. Zu Beginn der Funktion OnTick() weisen wir einen Wert der Variablen zu, so dass andere EAs erkennen können, es wird bereits gerechnet, und sie müssen entweder den Ablauf in ihrer Funktion OnTick() unterbrechen oder in eine Warteschleife eintreten. Nachdem nun der EA alles Notwendige erledigt hat, weisen wir check_value der Variablen zu, und jetzt kann ein anderer EA seine Funktion OnTick() abarbeiten, etc.

Eigentlich sollte der obige Code diese Aufgabe erledigen, aber wir können nicht garantieren, dass nach der Zeile:

if(GlobalVariableGet("test")==check_value){

sofort die folgende Zeile ausgeführt wird: 

GlobalVariableSet("test",value);

Ein anderer EA könnte sich dazwischen schalten und nach der Abfrage von check_value zu rechnen beginnen. Nachdem er seine Aufgaben teilweise erledigt hat, könnte der erste EA seine Aufgaben fortführen. So könnte es dazu kommen, dass trotzdem beide EAs zugleich arbeiten. Die Funktion GlobalVariableSetOnCondition() löst genau dies Problem. Wie in der Dokumentation beschrieben, "bietet die Funktion einen unteilbaren Zugang ("atomic access") zu der globalen Variablen". "Atomic" meint hier unteilbar. Daher kann sich kein anderes Programm während der Variablenabfrage dazwischen schalten und ihr einen neuen Wert zuweisen.

Der einzige Nachteil der Funktion ist, dass sie selber keine Variable erstellt, wenn sie nicht existiert. Das heißt, wir müssen eine zusätzlich Prüfung vornehmen (vorzugsweise während der Initialisierung) und die Variable gegebenenfalls erstellen. 

Gut, schreiben wir zwei EAs, die dieses Experiment durchführen. Beide EAs sind komplett identisch. Das Fenster mit "EA1 start" erscheint bei Beginn der Funktion OnTick() gefolgt von eine Pause von 3 Sekunden.(die Funktion Sleep()). Am Ende erscheint "EA1 end":

void OnTick(){
   Alert("EA1 start");   
   Sleep(3000);   
   Alert("EA1 end");
}

Der zweite EA ist ganz ähnlich, es sind nur die Nachrichten anders: "EA2 start" und "EA2 end". Diese EAs sind angehängt als eGVTestEA1 und eGVTestEA2. Öffnen Sie jetzt zwei identische Charts im Terminal und starten sie jeweils einen der EAs auf ihnen. Das Fenster zeigt, dass die EAs ihre Berechnungen gleichzeitig starten und beenden (Abb. 8).


Fig. 8. EA Nachrichten aus OnTick() über Start und End

Verwenden wir jetzt die Funktion GlobalVariableSetOnCondition() für ein geordnetes Verhalten beider EAs. Die zu ergänzenden Änderungen sind gleich für beide EAs, wir schreiben daher den Code in eine mit "include" zu ladende Datei. Die Datei nennen wir GVTestMutex.mqh (im Anhang unten).

Betrachten wir die Funktionen in der Datei GVTestMutex.mqh. Während der Initialisierung des EA prüfen wir, ob die globale Variable existiert, und erstellen sie, falls notwendig (die Funktion Mutex_Init()). Der Funktion wird ein einziger Parameter — der Variablennamen — übergeben:

void Init(string name){
   if(!GlobalVariableCheck(name)){
      GlobalVariableSet(name,0);
   }
}

Die zweite Funktion (Mutex_Check()) dient der Verifikation. Eine Schleife, die auf die Freigabe der globalen Variable wartet, leistet das. Sobald die Variable freigegeben wurde, returniert die Funktion true, und der EA kann mit seinen Berechnungen in der Funktion OnTick() fortfahren. Wird die Variable in der vorgegebenen Zeit nicht freigegeben, retourniert die Funktion false. In diesem Falls sollte die Funktion OnTick() unterbrochen werden:

bool Mutex_Check(string name,int timeout){   
   datetime end_time=TimeLocal()+timeout; // maximale Wartezeit
   while(TimeLocal()<end_time){ // Warteschleife
      if(IsStopped()){
         return(false); // Falls der EA vom Chart entfernt wurde
      }
      if(GlobalVariableSetOnCondition(name,1,0)){ 
         return(true);
      }
      Sleep(1); // kurze Pause
   }   
   return(false); // keine Freigabe innerhalb der vorgegeben Zeit
}

Der Name der globalen Variablen und die maximale Wartezeit in Sekunden wird der Funktion übergeben.

Die dritte Funktion ist Mutex_Release(). Sie setzt den Wert der globalen Variablen auf 0 (freigegeben), so dass andere EAs jetzt weiter rechnen können:

void Mutex_Release(string name){
   GlobalVariableSet(name,0);
}

Machen Sie eine Kopie von einem EA, laden Sie die Datei und fügen Sie den Funktionsaufruf ein. Der Name der Variablen ist "mutex_test". Rufen wir die Funktion Mutex_Check() mit einer Toleranzzeit von 30 Sekunden auf. Der ganze EA ist unten aufgeführt:

#include <GVTestMutex.mqh>

int OnInit(){
   Mutex_Init("mutex_test");
   return(INIT_SUCCEEDED);
}

void OnTick(){

   if(!Mutex_Check("mutex_test",30)){
      return;
   }

   Alert("EA1 start");
   Sleep(3000);
   Alert("EA1 end");

   Mutex_Release("mutex_test");

}

Erstellen wir eine Kopie des EA und ändern noch den Text der angezeigten Nachricht. Die beigefügten EAs heißen eGVTestEA1-2 und eGVTestEA2-2. Starten wir nun beide EAs auf jeweils identischen Charts, um zu sehen, ob sie sich jetzt selbst koordinieren können (Fig. 9).

 
Fig. 9. Beide EAs arbeiten abwechselnd

Anmerkung zum Parameter Toleranzzeit: Setzen Sie die Zeit so, dass sie länger ist, als die Arbeitszeit aller EAs in der Gruppe. Es könnte ja sein, dass ein EA während OnTick() vom Chart entfernt wurde und so die Funktion Mutex_Release() nicht mehr ausgeführt worden war. In diesem Fall würde kein einziger EA mehr seine Freigabe erhalten. Daher sollten wir für das Ablaufen der Toleranzzeit eine globale Variable auf 0 setzen oder einen anderen Weg finden, um sich aus dieser Falle zu befreien. Das hängt aber von der spezifischen Aufgabe ab. Manchmal sind gleichzeitige Abläufe mehrerer EAs akzeptable, manchmal müssen sie aber der Reihe nach agieren.

Klasse für ein einfaches Arbeiten mit globalen Variablen

Achten Sie auf die folgenden Punkte, durch die die Arbeit mit globalen Variablen bequemer wird.

  1. Eindeutige Variablennamen sind für jeden EA notwendig.
  2. Die Namen der Variablen für den Tester sollten sich von denen, die für ein Konto verwendet werden, unterscheiden.
  3. Wird mit einem EA im Tester gearbeitet, sollte er alle Variablen nach dem Ende seines Testlaufes löschen.
  4. Um bequemer mit Anruffunktionen globaler Variablen zu arbeiten, verwenden Sie kurze Funktionsnamen.
Verwenden Sie globale Variablen in einem EA, ergeben sich zwei Verwendungsweisen: allgemein oder auftragsgebunden. Die Allgemeinen werden für die Sicherung EA bezogener Daten verwendet: zum Beispiel der Zeitpunkt eines Anlasses, der maximale Profit einer Gruppe von Positionen, etc. Auftragsgebundene Variable enthalten zusätzliche Daten von Einzelaufträgen (oder Positionen): zum Beispiel der Auftragsindex einer Entwicklungsreihe von Lotgrößen, Eröffnungspreis eines Auftrages, etc. Jeder Auftrag hat seinen eigenen, eindeutigen Index — "ticket". Daher müssen wir nur einen Namen aus dem Ticket des Auftrages und einem aussagekräftigen Textteil bilden (zum Beispiel "Index", "Preis"). Das Ticket eines Auftrages ist vom Typ "ulong" mit einer Maximallänge von 20 Zeichen, während die Maximallänge für die Namen der Variablen 63 Zeichen umfasst, so dass wir immer noch 43 Zeichen zur Namensbildung haben.

Die Situation ist aber etwas komplizierter bei der Bildung normaler Namen von Variablen. Schätzen wir einmal grob die mögliche Länge von Variablennamen. Das erste Attribut, das wir zur Trennung der Variablen verschiedener EAs verwenden können, ist der Name des EAs selbst, sagen wir, 20 Zeichen. Ein und derselbe EA kann mit mehreren Symbolen arbeiten. Das heißt, das zweite, eindeutige Attribut ist das Symbol (4 weitere Zeichen). EAs, die mit einem einzigen Symbol arbeiten haben andere Auftrags-IDs — "magic numbers" vom Typ "ulong" (Maximal — 20 Zeichen). Es ist im Terminal möglich, zwischen verschiedenen Konten zu wechseln. Eine Kontonummer ist vom Typ "long" (Maximallänge — 19 Zeichen). Insgesamt erhalten wir also 63 Zeichen, die insgesamt erlaubte Länge des Variablennamens, aber das ist ja nur die Länge des Präfix!

Das bedeutet, wir müssen etwas opfern. Richten wir uns nach ein paar Regeln: ein Terminal arbeitet nur auf einem Konto. Gibt es mehrere Konten, starten sie für jedes Konto eine eigene Terminal-Instanz. So können wir die Kontonummer einsparen und unser Präfix um 20 Zeichen auf 43 reduzieren. Noch eine Regel: Wir verwenden keine "magic numbers" vomn Typ "ulong". Schlussendlich geben wir unseren EAs sinnvollerweise kürzere Namen. Der Name einer globalen Variable, derart gebildet aus dem Namen des EAs, des Symbols und der "magic number", kann als ausreichend angesehen werden. Vielleicht gibt es bequemere Wege der Benennung, aber bleiben wir in diesem Artikel bei diesem Vorgehen.

Beginnen wir mit dem Schreiben einer Klasse. Die Klasse heißt CGlobalVariables, der Dateiname ist CGlobalVariables.mqh, beide sind im Anhang unten. Wir deklarieren zwei Variablen für die Präfixes in der Sektion 'private': die erste — für allgemeine Variablen, die zweite — für die Auftragsvariablen:

class CGlobalVariables{
   private:
      string m_common_prefix; // Präfix der allgemeinen Variablen
      string m_order_prefix; // Präfix der Auftragsvariablen
   public:
      // constructor
      void CGlobalVariables(){}
      // destructor
      void ~CGlobalVariables(){}
}

Erstellen wir jetzt die Methode Init() in der Sektion 'public'. Die Methode sollte während der Initialisierung des EAs aufgerufen werden. Die beiden Parameter — Symbol und "magic number" — werden übergeben. Die Präfixes werden in dieser Methode gebildet. Die Präfixe der Auftragsvariablen sind einfach, da sie nur der Unterscheidung von Variablen dienen, die der EA entweder auf dem Konto oder im Tester verwendet. Daher beginnen die Auftragsvariable für das Konto mit "order_", die für den Tester hingegen mit "tester_order_". Nur ein "t_" kann dem Präfix der allgemeinen Variablen für den Tester hinzugefügt werden (obwohl sie eindeutig sind, sollten wir sparsam mit den Zeichen bleiben). Alte globale Variable sollten auch während der Initialisierung im Tester gelöscht werden. Natürlich sollten sie auch bei der Deinitialisierung gelöscht werden, aber es kann sein, dass sie trotzdem übrig bleiben. Erstellen wir also erst einmal die Methode DeleteAll() und rufen sie auf. Ich empfehle, die Methode in der Sektion 'private' zu platzieren. Den Code ergänzen wir später. Die Methode Init() ist unten dargestellt:

void Init(string symbol,int magic){
   m_order_prefix="order_";
   m_common_prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+symbol+"_"+IntegerToString(magic)+"_";
   if(MQLInfoInteger(MQL_TESTER)){
      m_order_prefix="tester_"+m_order_prefix;
      m_common_prefix="t_"+m_common_prefix;
      DeleteAll();
   }         
}

Ergänzen wir jetzt die Methode, die das Präfix der allgemeinen Variablen zurückgibt, da es unter Umständen bei der Arbeit mit globalen Variablen helfen könnte:

string Prefix(){
   return(m_common_prefix);
} 

Ergänzen wir die grundlegenden Methoden: zum Prüfen, Setzen, Werte Abfragen und Löschen einzelner Variablen. Da wir zwei Präfixe haben, sollten wir zwei Methoden mit Überladen verwenden (beide Methoden haben denselben Namen, aber unterschiedliche Parameter). Nur ein Parameter — der Variablenname — wird einer Gruppe von Methoden übergeben. Das sind die Methoden für die allgemeinen Variablen. Die Ticketnummer und ein Variablenname werden der anderen Gruppe der Methoden übergeben. Das sind die Methoden für die Auftragsvariablen:

// für allgemeine Variablen
bool Check(string name){
   return(GlobalVariableCheck(m_common_prefix+name));
}
void Set(string name,double value){
   GlobalVariableSet(m_common_prefix+name,value);      
}      
double Get(string name){
   return(GlobalVariableGet(m_common_prefix+name));
} 
void Delete(string name){
   GlobalVariableDel(m_common_prefix+name); 
}
// für die Auftragsvariablen
bool Check(ulong ticket,string name){
   return(GlobalVariableCheck(m_order_prefix+IntegerToString(ticket)+"_"+name));
}
void Set(ulong ticket,string name,double value){
   GlobalVariableSet(m_order_prefix+IntegerToString(ticket)+"_"+name,value);      
}      
double Get(ulong ticket,string name){
   return(GlobalVariableGet(m_order_prefix+IntegerToString(ticket)+"_"+name));
} 
void Delete(ulong ticket,string name){
   GlobalVariableDel(m_order_prefix+IntegerToString(ticket)+"_"+name); 
} 

Gehen wir zurück zur Methode DeleteAll() und schreiben den Code für das Löschen der Variablen-Präfixe:

GlobalVariablesDeleteAll(m_common_prefix);
GlobalVariablesDeleteAll(m_order_prefix);  

Das Löschen kann im Tester nach dem Testende durchgeführt werden, rufen wir daher die Methode in Deinit() auf, damit sie im Zuge der Deinitialisierung ausgeführt wird:

 void Deinit(){
    if(MQLInfoInteger(MQL_TESTER)){
        DeleteByPrefix();
    }
 }

Um die Zuverlässigkeit der globalen Variablen zu erhöhen, sollten wir auch die Funktion GlobalVariablesFlush() verwenden. Fügen wir noch eine weitere Methode mit dieser Funktion hinzu. Es ist viel einfacher diese Methode aufzurufen, als den langen Namen der der Funktion (in Erfüllung der Regel 4):

void Flush(){
   GlobalVariablesFlush();
}

Manchmal müssen allgemeine Variablen in Gruppen durch zusätzliche Präfixe gekennzeichnet werden, um sie dann auch als Gruppe insgesamt wieder durch den EA zu löschen. Ergänzen wir einfach eine weitere Methode DeletByPrefix():

void DeleteByPrefix(string prefix){
   GlobalVariablesDeleteAll(m_common_prefix+prefix);
}

Im Ergebnis haben wir jetzt eine Klasse mit ausreichender Funktionalität, um 95% der Aufgaben zu erledigen, die eine Verwendung globaler Variablen erfordert.

Um diese Klasse zu verwenden laden Sie die Datei wie folgt in Ihren EA:

#include <CGlobalVariables.mqh>

Erstellen Sie ein Objekt:

CGlobalVariables gv;

Rufen Sie während der Initialisierung des EAs die Methode Init() auf, und übergeben Sie den Symbolnamen und die "magic number":

gv.Init(Symbol(),123);

Rufen Sie die Methode Deinit() während der Deinitialisierung auf, um die Variablen des Testers zu löschen:

gv.Deinit();

Jetzt können wir bei der Entwicklung eines EAs einfach die Methoden Check(), Set(), Get() und Delete() verwenden, in dem wir ihnen einen eindeutigen Teil eines Variablennamens übergeben:

   gv.Set("name1",123.456);
   double val=gv.Get("name1");

Als Ergebnis dieser Operationen des EAs erscheint die Variable mit dem Namen eGVTestClass_GBPJPY_123_name1 in der Liste der globalen Variablen (Fig. 10).

 
Fig. 10. Teil des Fensters der globalen Variablen mit der Variablen erstellt mit Hilfe der Klasse CGlobalVariables

Die Länge des Variablennamens ist 29 Zeichen, d.h. wir sind relativ frei bei der Auswahl eines Variablennamens. Für die Auftragsvariablen müssen wir die Ticketnummer des Auftrages übergeben, aber wir müssen nicht extra den Zahlenwert in eine Zeichenkette umwandeln oder einen komplette Namen erstellen, das vereinfacht sehr stark die Verwendung der globalen Variablen. Ein Anwendungsbeispiel dieser Klasse ist mit dem eGVTestClass EA unten angehängt.

Es ist auch möglich die Klassen etwas abzuändern, um ihre Verwendung weiter zu vereinfachen. Nun bleibt nur noch die Verbesserung des "constructors" und des "destructors" der Klasse. Rufen wir die Methode Init() im "contructor" mit den entsprechenden Parametern auf und die Methode Deinit() im "destructor":

void CGlobalVariables(string symbol="",int magic=0){
   Init(symbol,magic);
}
// destructor
void ~CGlobalVariables(){
   Deinit();
}

Jetzt ist es nicht mehr nötig, die die Methoden Init() und Deinit() selbst aufzurufen. Statt dessen müssen wir nur das Symbol und die "magic number" beim Erstellen der Klasse angeben:

CGlobalVariables gv(Symbol(),123);

Schlussfolgerung

In diesem Artikel haben wir uns alle Funktionen für das Arbeiten mit globalen Variablen erarbeitet inklusive der Funktion GlobalVariableSetOnCondition(). Wir habe uns eine Klasse erstellt, die bei der Erstellung eines EAs die Verwendung der globalen Variablen stark vereinfacht. Natürlich umfasst die Kasse nicht alle erdenklichen Möglichkeiten, die die globale Variablen bieten, aber die wichtigsten und die am häufigsten benötigten. Sie können natürlich noch weiter verbessern oder Ihre eigene Version entwickeln, wenn das notwendig ist. 

Anlagen

  • sGVTestCreate — erstellt die Variable.
  • sGVTestGet1 — erste Methode einen Wert abzufragen.
  • sGVTestGet2  — zweite Methode einen Wert abzufragen.
  • sGVTestGet1-2 — erste Methode, den Wert einer nicht existierenden Variablen abzufragen.
  • sGVTestGet2-2 — zweite Methode, den Wert einer nicht existierenden Variablen abzufragen.
  • sGVTestCheck — Prüfen der Existenz eine Variablen.
  • sGVTestTime — Abfrage des Zeitwertes der Variablen.
  • sGVTestAllNames — Abfrage einer Liste aller Variablennamen.
  • sGVTestAllNames2 — Abfrage einer Liste aller Variablennamen mit einem angegebenen Präfix.
  • sGVTestDelete — Löschen einer Variablen.
  • sGVTestCreate4 — Erstellen von vier Variablen (zwei Gruppen mit je zwei Variablen).
  • sGVTestDeleteGroup — Löschen einer Gruppe von Variablen.
  • sGVTestFlush — Erzwungene Sicherung der Variablen.
  • sGVTestTemp — Erstellen einer temporären Variablen.
  • eGVTestEA1, eGVTestEA2 — Demonstration eines gleichzeitigen Operierens zweier EAs.
  • GVTestMutex.mqh — Funktionen für eine Mutex-Entwicklung.
  • eGVTestEA1-2, eGVTestEA1-2 — Demonstration von zwei EAs, die abwechselnd arbeiten.
  • CGlobalVariables.mqh — Klasse CGlobalVariables zum Arbeiten mit globalen Variablen.
  • eGVTestClass — EA, der die Verwendung der Klasse CGlobalVariables demonstriert.