
Meistern Sie MQL5 vom Anfänger bis zum Profi (Teil IV): Über Arrays, Funktionen und globale Terminalvariablen
Einführung
Dieser Artikel ist eine Fortsetzung der Serie für Anfänger. In früheren Artikeln haben wir die Methoden zur Beschreibung der in unserem Programm gespeicherten Daten ausführlich besprochen. An dieser Stelle sollte der Leser Folgendes wissen:
- Daten können in Variablen oder Konstanten gespeichert werden.
- Die Sprache MQL5 ist eine stark typisierte Sprache, was bedeutet, dass jedes Datenelement in einem Programm seinen eigenen Typ hat, den der Compiler verwendet, um den Speicher richtig zuzuordnen und logische Fehler zu vermeiden.
- Datentypen können einfach (grundlegend) und komplex (nutzerdefiniert) sein.
- Um Daten in einem MQL5-Programm zu verwenden, müssen Sie mindestens eine Funktion deklarieren.
- Alle Codeblöcke können in separate Dateien verschoben werden und diese Dateien können dann mit der Präprozessordirektive #include in das Projekt eingefügt werden.
In diesem Artikel werde ich drei globale Themen behandeln:
- Datenarrays, die den Hauptteil der Daten innerhalb des Programms vervollständigen.
- Globale Terminalvariablen, die den Austausch einfacher Daten zwischen verschiedenen MQL5-Programmen ermöglichen.
- Einige der Merkmale von Funktionen und ihre Interaktionen mit Variablen.
Grundlegende Informationen zu Arrays
Ein Array ist eine Variable, die eine Folge von Daten des gleichen Typs enthält.
Um ein Array zu beschreiben, müssen Sie seinen Typ und Variablennamen beschreiben und dann eckige Klammern schreiben. Die Anzahl der Elemente einer gegebenen Sequenz kann in eckigen Klammern angegeben werden.
int myArray[2]; // Describes an array of integers containing two elements
Beispiel 1. Beschreibung eines statischen Arrays.
Wir beschreiben Sequenzen ziemlich oft in MQL5-Programmen. Hierzu gehören historische Preise, Öffnungszeiten der Kerzen, Volumina und mehr. Immer wenn Datensätze vorhanden sind, können Arrays eine gute Wahl sein.
Die Nummerierung der Elemente innerhalb eines Arrays in MQL5 beginnt immer bei 0. Daher ist die Nummer des letzten Elements im Array immer gleich der Anzahl seiner Elemente minus eins (lastElement = Größe - 1).
Um auf ein Array-Element zuzugreifen, geben Sie einfach den Index dieses Elements in eckigen Klammern an:
// Fill the array with values: myArray[0] = 3; myArray[1] = 315; // Output the last element of this array to the log: Print(myArray[1]); // 315
Beispiel 2. Verwenden von Array-Elementen.
Natürlich kann jedes Array bei der Deklaration wie eine Struktur mit geschweiften Klammern initialisiert werden:
double anotherArray[2] = {4.0, 5.2}; // A two-element array is initialized with two values Print( DoubleToString(anotherArray[0],2) ); // Output 4.00
Beispiel 3. Array-Initialisierung während der Beschreibung.
Mehrdimensionale Arrays
Ein Array kann andere Arrays in sich speichern. Solche verschachtelten Arrays werden als „mehrdimensional“ bezeichnet.
Ein einfaches visuelles Beispiel für mehrdimensionale Arrays können Buchseiten sein. Zeichen werden in einer Zeile zusammengefasst, was die erste Dimension darstellt, Zeilen werden in Absätzen zusammengefasst und bilden die zweite Dimension, und eine Seite ist eine Reihe von Absätzen, also die dritte Dimension.
Abbildung 1. Zeichen werden in einer Zeile gesammelt – einem eindimensionalen Array.
Abbildung 2. Zeilen werden in Absätzen zusammengefasst – einem zweidimensionalen Array.
Abbildung 3. Absätze werden zu Seiten zusammengefasst – einem dreidimensionalen Array.
Um solche Arrays in MQL5 zu beschreiben, fügen Sie einfach für jede neue Dimension eckige Klammern hinzu. Die Klammern für „äußere“ Container werden links von den „inneren“ Containern platziert. Beispielsweise könnten die in den Abbildungen 1 bis 3 gezeigten Arrays wie folgt beschrieben und verwendet werden:
char stringArray[21]; // One-dimensional array char paragraphArray[2][22]; // Two-dimensional array char pageArray[3][2][22]; // Three-dimensional array // Filling a two-dimensional array paragraphArray[0][0]='T'; paragraphArray[0][1]='h'; // … paragraphArray[1][20]='n'; paragraphArray[1][21]='.'; // Access to an arbitrary element of a two-dimensional array Print(CharToString(paragraphArray[1][3])); // Will print "a" (why?)
Beispiel 4. Beschreibung mehrdimensionaler Arrays für Abbildungen 1–3.
Die Gesamtzahl der Dimensionen in einem Array sollte 4 nicht überschreiten. Die maximale Anzahl von Elementen in jeder Dimension beträgt 2147483647.
Die Initialisierung mehrdimensionaler Arrays ist genauso einfach wie die eindimensionaler Arrays. Die geschweiften Klammern listen einfach die Elemente jedes Arrays auf:
int arrayToInitialize [2][5] = { {1,2,3,4,5}, {6,7,8,9,10} }
Beispiel 5. Initialisierung mehrdimensionaler Arrays.
Dynamische Arrays
Nicht bei allen Arrays lässt sich auf Anhieb sagen, wie viele Elemente sie haben werden. Beispielsweise ändern sich Arrays, die den Terminalverlauf oder Handelslisten enthalten, im Laufe der Zeit. Daher können Sie in MQL5 zusätzlich zu den in den vorherigen Abschnitten beschriebenen statischen Arrays auch dynamische Arrays erstellen, d. h. solche, deren Anzahl der Elemente sich während der Ausführung des Programms ändern kann. Solche Arrays werden genauso beschrieben wie statische, nur die Anzahl der Elemente wird nicht in eckigen Klammern angegeben:
int dinamicArray [];
Beispiel 6. Beschreibung dynamischer Arrays
Ein auf diese Weise deklariertes neues Array enthält keine Elemente, seine Länge beträgt 0 und daher kann nicht auf seine Elemente zugegriffen werden. Wenn das Programm dies versucht, tritt ein kritischer Fehler auf und das Programm wird beendet. Daher ist es vor der Arbeit mit einem solchen Array erforderlich, seine Größe mithilfe der speziellen integrierten Funktion ArrayResize festzulegen:
ArrayResize(dinamicArray, 1); // The first parameter is the array and the second is the new size ArrayResize(dinamicArray,1, 100); // The third parameter is the reserved (excess) size
Beispiel 7. Ändern der Größe eines dynamischen Arrays
In der Sprachdokumentation können Sie sehen, dass die Funktion bis zu drei Parameter annehmen kann. Der dritte Parameter hat jedoch einen Standardwert, sodass Sie ihn weglassen können, wie ich es in der ersten Zeile meines Beispiels getan habe.
Der erste Parameter dieser Funktion ist notwendigerweise das Array, das wir ändern. Die zweite ist die neue Größe des Arrays. Ich denke nicht, dass es hiermit irgendwelche Probleme geben wird. Der dritte Parameter ist die „reservierte Größe“.
Eine reservierte Größe wird verwendet, wenn wir die endgültige Größe unseres Arrays kennen. Beispielsweise können gemäß den Bedingungen unseres Problems nicht mehr als 100 Werte im Array vorhanden sein, aber wie viele genau, ist unbekannt. Dann können Sie den Parameter reserve_size in dieser Funktion verwenden und ihn auf 100 setzen, wie in Beispiel 7 in der zweiten Zeile. In diesem Fall reserviert die Funktion eine überschüssige Speichergröße für 100 Elemente, die tatsächliche Array-Größe bleibt jedoch die im zweiten Parameter angegebene (1 Element).
Warum solche Komplikationen? Warum nicht bei Bedarf einfach jeweils ein Element hinzufügen?
Die einfache Antwort besteht darin, unser Programm zu beschleunigen.
Das Schreiben einer ausführlicheren Antwort könnte viel Zeit in Anspruch nehmen. Kurz gesagt geht es darum, dass unser Programm jedes Mal, wenn wir die Funktion ArrayResize ohne den dritten Parameter verwenden, das Betriebssystem um zusätzlichen Speicher anfordert. Diese Speicherzuweisung ist, aus Sicht des Prozessors, ein ziemlich langwieriger Vorgang, und es spielt keine Rolle, ob der Speicher nur für ein Element oder für mehrere gleichzeitig benötigt wird. Je seltener unser Programm dies tun muss, desto besser. Das heißt, es ist besser, viel Platz auf einmal zu reservieren und ihn dann zu füllen, als wenig Platz zuzuweisen und ihn dann zu erweitern. Allerdings muss auch hier berücksichtigt werden, dass der Arbeitsspeicher eine begrenzte Ressource ist und daher immer eine Balance zwischen Arbeitsgeschwindigkeit und Datengröße gefunden werden muss.
Wenn Sie die Beschränkungen Ihrer Arrays kennen, ist es besser, diese dem Programm explizit mitzuteilen, indem Sie statische Arrays deklarieren oder mit dem dritten Parameter in der Funktion ArrayResize Speicher reservieren. Wenn Sie es nicht wissen: Das Array ist auf jeden Fall dynamisch und der dritte Parameter in der Funktion ArrayResize muss nicht unbedingt angegeben werden, obwohl dies möglich ist. Denn wenn die tatsächliche Größe des Arrays größer als die reservierte Größe ist, weist MQL5 einfach den tatsächlich benötigten Speicher zu.
Nachdem die Größe des Arrays wie in Beispiel 7 geändert wurde, können Sie die darin enthaltenen Daten ändern:
dinamicArray[0] = 3; // Now our array contains exactly one element (see example 7), its index is 0
Beispiel 8. Verwenden eines geänderten Arrays
Wenn wir mit dynamischen Arrays arbeiten, besteht die Aufgabe meistens darin, Daten am Ende dieses Arrays hinzuzufügen und nicht in der Mitte etwas zu ändern (obwohl dies auch vorkommt). Da das Programm zu keinem Zeitpunkt weiß, wie viele Elemente das Array im jeweiligen Moment enthält, ist eine spezielle Funktion erforderlich, um dies herauszufinden. Es heißt ArraySize. Die Funktion verwendet einen Parameter – ein Array – und gibt einen ganzzahligen Wert für die Anzahl der Elemente in diesem Array zurück. Und sobald wir die genaue Größe des dynamischen Arrays kennen (das diese Funktion zurückgibt), ist das Hinzufügen eines Elements ganz einfach:
int size, // Number of elements in the array lastElemet; // Index of the last element char stringArray[]; // Our example dynamic array. // Immediately description its size is 0 (array cannot contain elements) // add an element to the end. size = ArraySize(stringArray); // Find the current size of the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `H`; // Write the value // Now add one more element. The sequence of actions is absolutely the same. size = ArraySize(stringArray); // Find the current size if the array size++; // Array size should increase by 1 ArrayResize(stringArray, size, 2); // Resize the array. In our example, the array will have no more than two elements. lastElemet = size — 1; // Numbering starts from 0, so the number of the last element is 1 less than the size of the array stringArray[lastElement] = `i`; // Write the value // Note that when adding the second element in this way, only on line changes: // the one that writes the actual value to a specific cell. // // This means that the solution can be written in a shorter form. For example, by creating a separate custom function for it.
Beispiel 9. Hinzufügen eines Elements am Ende eines dynamischen Arrays.
Die Funktionen ArraySize und ArrayResize werden bei der Arbeit mit dynamischen Arrays ständig verwendet, normalerweise in Kombination wie in Beispiel 9. Weitere Funktionen, die weniger häufig verwendet werden, aber nicht weniger nützlich sind, finden Sie in der Dokumentation.
Und zum Abschluss dieses Abschnitts möchte ich darauf hinweisen, dass die Sprache MQL5 auch mehrdimensionale dynamische Arrays unterstützt, es darf jedoch nur der erste Index undefiniert bleiben.
int a [][12]; // It's ok // int b [][]; // Compilation error: only the first index can be dynamic
Beispiel 10. Mehrdimensionales dynamisches Array.
Wenn Sie wirklich mehrere Indizes dynamisch gestalten müssen, können Sie eine Struktur erstellen, deren einziges Feld ein dynamisches Array ist, und dann ein Array aus solchen Strukturen erstellen.
struct DinArray // Structure containing a dynamic array { int a []; }; DinArray dinamicTwoDimensions []; // Dynamic array of structures ArrayResize( dinamicTwoDimensions, 1 ); // Set size of the outer dimension ArrayResize( dinamicTwoDimensions[0].a, 1 ); // Set size of the internal dimension dinamicTwoDimensions[0].a[0] = 12; // Use cell to write data
Beispiel 11. Array mit zwei dynamischen Indizes.
Es gibt andere Methoden, um dieses Problem zu lösen. Sie können beispielsweise Ihre eigene Klasse erstellen oder eine verwenden, die bereits in der Standardbibliothek vorhanden ist. Das Thema der Arbeit mit Klassen werde ich jedoch zukünftigen Artikeln überlassen.
Zeitreihen-Arrays
Eröffnungs-, Schluss-, Höchst- und Tiefstpreise, Tick- und Realvolumina, Spreads, Startzeit der Kerzen und Indikatorwerte für jede Kerze in MQL5 werden als Serien oder Zeitreihen bezeichnet.
Ein MQL5-Programmierer hat keinen direkten Zugriff auf diese Zeitreihen, aber die Sprache bietet die Möglichkeit, diese Daten mithilfe eines Satzes spezieller vordefinierter Funktionen (aufgeführt in Tabelle 1) in beliebige Variablen innerhalb unseres Programms zu kopieren.
Wenn wir beispielsweise Schlusskurse benötigen, müssen wir zunächst unser eigenes Array erstellen, in dem diese Kurse gespeichert werden, dann die Funktion CopyClose aufrufen und ihr das erstellte Array als letzten Parameter übergeben. Die Funktion kopiert die Standard-Zeitreihe in unsere Variable, und dann können diese Daten auf die übliche Weise verwendet werden: mit Indizes in eckigen Klammern.
Operationen mit Zeitreihen unterscheiden sich jedoch etwas von anderen Arrays. Dies ist mittlerweile eine traditionell festgelegte Form.
Im Speicher werden die Zeitreihen in der gleichen Reihenfolge wie alle anderen Daten abgelegt: vom ältesten zum neuesten. Die Funktionen für Reihendatenoperationen aus Tabelle 1 nummerieren Elemente in Zeitreihen jedoch in umgekehrter Reihenfolge, also von rechts nach links. Bei allen diesen Funktionen ist die Nullkerze die ganz rechte, die aktuelle und die noch nicht abgeschlossene Kerze. Aber „normale“ Arrays wissen davon nichts, und deshalb wird diese Kerze für sie die letzte sein. Das ist irgendwie verwirrend …
Versuchen wir, dies anhand der folgenden Bilder zu verstehen.
Abbildung 4. Richtung der Indizierung in regulären Arrays (grüner Pfeil) und in Zeitreihen (blauer Pfeil).
Abbildung 5. Kopieren von Serien in reguläre Arrays.
Abbildung 4 zeigt den Unterschied in der Indizierungsrichtung von Zeitreihen und regulären Arrays.
Abbildung 5 visualisiert, wie Zeitreihendaten beispielsweise mithilfe der Funktion CopyRates oder einer ähnlichen Funktion in reguläre Arrays kopiert werden können (siehe Tabelle 1). Die physische Reihenfolge der Elemente im Speicher ist für reguläre Arrays und Zeitreihen dieselbe, aber die Indizierung ändert sich, und das erste Element in der Zeitreihe wird nach dem Kopieren das letzte im regulären Array.
Manchmal kann es umständlich sein, beim Programmieren ständig diese Nuancen im Hinterkopf zu behalten. Es gibt zwei Möglichkeiten, diese Unannehmlichkeiten zu bekämpfen:
- Mit der Standardfunktion ArraySetAsSeries können Sie die Indizierungsrichtung für jedes dynamische Array ändern. Sie benötigt zwei Parameter: das Array selbst und die Angabe, ob es sich um eine Zeitreihe handelt (true/false). Wenn Ihr Algorithmus das Kopieren von Daten beinhaltet, das immer mit der letzten Kerze beginnt, können Sie das Ziel-Array meistens als Reihe zuweisen. Dann verwenden die Standardfunktionen und Ihr Algorithmus zum Arbeiten dieselben Indizes.
- Wenn Ihr Algorithmus das Kopieren kleiner Datenfragmente von einer beliebigen Stelle im Chart beinhaltet, insbesondere wenn deren Anzahl bei jedem Schritt des Algorithmus genau bekannt ist (wir nehmen beispielsweise die Schlusskurse von drei Balken: dem ersten geschlossenen, also mit dem Serienindex 1, und den beiden folgenden mit den Serienindizes 2 und 3), dann ist es am besten, ihn so zu akzeptieren, wie er ist. Es ist besser, zu akzeptieren, dass die Indizierung in unterschiedliche Richtungen erfolgt und beim Programmieren einfach vorsichtiger zu sein. Eine mögliche Lösung besteht darin, eine separate Funktion zum Überprüfen der erforderlichen Werte zu erstellen und zu versuchen, sie in beliebigen Ausdrücken zu verwenden.
Im folgenden Beispiel habe ich versucht, all das oben Genannte mit Code zu veranschaulichen.
datetime lastBarTime; // We'll try to write the last candle's time into this variable datetime lastTimeValues[]; // The array will store the time of the last two candles. // It's dynamic so that it can be made into a time series to test indices // Get the start time of the current candlestick using the iTime function lastBarTime = iTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0 // Current candlestick ); Print("Start time of the 0 bar is ", lastBarTime); // Get the start time of the last two candlesticks using the CopyTime function CopyTime ( Symbol(), // Use the current symbol PERIOD_CURRENT, // For the current timeframe 0, // Start with position 0 2, // Take two values lastTimeValues // Write them to array lastTimeValues ("regular") array ); Print("No series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array to log. The separator between elements is a semicolon ArraySetAsSeries(lastTimeValues,true); // Convert the array into a time series Print("Series"); ArrayPrint(lastTimeValues,_Digits,"; "); // Print the entire array again. Note the order of the data /* Script output: 2024.08.01 09:43:27.000 PrintArraySeries (EURUSD,H4) Start time of the 0 bar is 2024.08.01 08:00:00 2024.08.01 09:43:27.051 PrintArraySeries (EURUSD,H4) No series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 04:00:00; 2024.08.01 08:00:00 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) Series 2024.08.01 09:43:27.061 PrintArraySeries (EURUSD,H4) 2024.08.01 08:00:00; 2024.08.01 04:00:00 */
Beispiel 12. Testfunktionen für die Arbeit mit Zeitreihen.
Tabelle 1. Liste der Funktionen für den Zugriff auf Zeitreihen. Bei all diesen Funktionen beginnt die Indizierung der Elemente rechts, bei der letzten (unfertigen) Kerze.
Funktion | Aktion |
---|---|
CopyBuffer | Abrufen der Daten des angegebenen Indikatorpuffers in ein Array |
CopyRates | Abrufen von historischen Daten für das angegebene Symbol und den angegebenen Zeitrahmen in einem Array von MqlRates-Strukturen |
CopySeries | Abrufen von mehreren synchronisierten Zeitreihen für das angegebene Symbol/den angegebenen Zeitrahmen in der angegebenen Menge. Am Ende wird die Liste aller zu füllenden Arrays übergeben, deren Reihenfolge den Feldern der MqlRates-Struktur entsprechen muss. |
CopyTime | Abrufen der historischen Daten der Eröffnungszeiten der Balken für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyOpen | Abrufen der historischen Daten der Eröffnungskurse der Balken für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyHigh | Abrufen der historischen Daten der Höchstpreise der Balken für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyLow | Abrufen der historischen Daten der Tiefstpreise der Balken für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyClose | Abrufen der historischen Daten der Schlusskurse der Balken für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyTickVolume | Abrufen der historischen Daten der Tick-Volumina für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyRealVolume | Abrufen der historischen Daten der Handelsvolumina für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopySpread | Abrufen der historischen Daten der Spreads für das entsprechende Symbol und den entsprechenden Zeitrahmen in einem Array |
CopyTicks | Abrufen der Ticks im Format von MqlTick in einem Array |
CopyTicksRange | Erstellen eines Arrays mit Ticks im angegebenen Datumsbereich |
iBarShift | Gibt den Index des Balkens in der Reihe zurück, der die angegebene Zeit enthält |
iClose | Gibt den Schlusskurs des Balkens (angegeben durch den Parameter „shift“) im entsprechenden Chart zurück. |
iHigh | Gibt den Höchstpreis des Balkens (angezeigt durch den Parameter „shift“) im entsprechenden Chart zurück. |
iHighest | Gibt den Index des höchsten im entsprechenden Chart gefundenen Wertes zurück (Verschiebung relativ zum aktuellen Balken) |
iLow | Gibt den Tiefstpreis des Balkens (angezeigt durch den Parameter „shift“) im entsprechenden Chart zurück. |
iLowest | Gibt den Index des kleinsten gefundenen Wertes im entsprechenden Chart zurück (Verschiebung relativ zum aktuellen Balken) |
iOpen | Gibt den Eröffnungspreis des Balkens (angegeben durch den Parameter „shift“) im entsprechenden Chart zurück. |
iTime | Gibt den Öffnungszeitpunkt des Balkens (angegeben durch den Parameter „shift“) im entsprechenden Chart zurück. |
iTickVolume | Gibt das Tick-Volumen des Balkens (angegeben durch den Parameter „shift“) im entsprechenden Chart zurück. |
iRealVolume | Gibt das reale Volumen des Balkens (angegeben durch den Parameter „shift“) im entsprechenden Chart zurück. |
iSpread | Gibt den Spread-Wert für den durch den Parameter „shift“ angegebenen Balken im entsprechenden Chart zurück. |
Funktionen erstellen (im Detail)
Jede Funktion in einem MQL5-Programm wird mithilfe derselben Vorlage erstellt, die wir im ersten Artikel der Serie kurz besprochen haben:
ResultType Function_Name(TypeOfParameter1 nameOfParameter1, TypeOfParameter2 nameOfParameter2 …) { // Description of the result variable and other local variables ResultType result; // … //--- // Main actions are performed here //--- return resut; }
Beispiel 13. Vorlage für eine Funktionsbeschreibung.
ResultType (und TypeOfParameter) stellen jeden zulässigen Datentyp dar. Dies kann ein int, ein double, ein Klassen- oder Enumerationsname oder irgendetwas anderes sein, das Sie kennen.
Es können auch keine Parameter oder kein explizites Ergebnis der Funktionsoperation vorhanden sein. Dann wird anstelle des Ergebnistyps bzw. anstelle der Parameterliste innerhalb der Klammern das Wort ‚void‘ eingefügt. Es steht für ein leeres Ergebnis.
Wenn der ResultType void ist, müssen offensichtlich keine Daten zurückgegeben werden. Dementsprechend muss die letzte Zeile in den geschweiften Klammern (return result) nicht angegeben werden und es ist auch nicht erforderlich, die Ergebnisvariable zu beschreiben.
Hier sind noch ein paar einfache Regeln:
- Funktionsname und Parameternamen müssen den Konventionen für die Bezeichnung entsprechen.
- Der Operator return gibt nur einen Wert zurück. Mehr kann er nicht zurückgeben. Es gibt jedoch Workarounds, auf die wir später noch näher eingehen.
- Eine Funktion kann nicht innerhalb einer anderen Funktion beschrieben werden, sondern nur außerhalb aller Funktionen.
- Sie können mehrere Funktionen mit demselben Namen, aber mit unterschiedlicher Anzahl (oder unterschiedlichen Typen) von Parametern und/oder unterschiedlichen Typen des Rückgabewerts beschreiben. Stellen Sie einfach sicher, dass Sie genau verstehen, welche Funktion in einem bestimmten Fall verwendet werden soll. Wenn Sie diese Unterschiede erkennen und jemandem erklären können, der mit Ihrem Code nicht vertraut ist, dann kann der Compiler dies auch.
Hier einige Beispiele, die veranschaulichen, wie Funktionen beschrieben werden können:
//+------------------------------------------------------------------+ //| Example 1 | //| Comments are often used to describe what a function does, | //| what date it needs and why. For example, like this: | | //| | //| | //| The function returns difference between two integers. | //| | //| Parameters: | //| int a is a minuend | //| int b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ int diff(int a, int b) { // The action is very simple, we do not create a variable for the result. return (a-b); } //+------------------------------------------------------------------+ //| Example 1a | //| The function returns the difference between two real numbers. | //| | //| Function name is as in the previous example, but parameter type | //| differs | //| | //| Parameters: | //| double a is a minuendе | //| double b is a subtrahend | //| Return value: | //| difference between a and b | //+------------------------------------------------------------------+ double diff(double a, double b) { return (a-b); } //+------------------------------------------------------------------+ //| Example 2 | //| Illustrates the use of "void". | //| Calls (uses) the diff function | //+------------------------------------------------------------------+ void test() { // You can do whatever you want. // For example, use the function from Example 1. Print(diff(3,4)); // the result is -1 // Since when calling the diff function, integer parameters were // passed in parentheses, the result is also int. // Now let's try to call the same function with double precision parameters Print(diff(3.0,4.0)); // the result is -1.0 // Since the function is declared as "void", the "return" statement is not needed } //+------------------------------------------------------------------+ //| Example 3 | //| The function has no parameters to process. We could use | //| empty parentheses as in the previous example or explicitly use | //| the word "void" | //| Return value: | //| string nameForReturn is some name , always the same | | //+------------------------------------------------------------------+ string name(void) { string nameForReturn="Needed Name"; return nameForReturn; }
Beispiel 14. Beispiele für Funktionsbeschreibungen nach Vorlage.
Es ist wichtig zu verstehen, dass die Arbeit mit einer Funktion aus zwei Phasen besteht: Beschreibung und Verwendung. Wenn wir eine Funktion beschreiben, tut diese Funktion noch nichts. Es handelt sich lediglich um einen formalen Aktionsalgorithmus. Beispielsweise könnte die Diff-Funktion aus Beispiel 14 in Worten wie folgt beschrieben werden:
- Nimm zwei ganze Zahlen (beliebig, im Voraus unbekannt).
- Bezeichne sie innerhalb des Algorithmus einen von ihnen als a und den anderen als b.
- Subtrahiere b von a.
- Gib das Ergebnis der Berechnung (beliebig, im Vorfeld unbekannt) wird dem Anrufer zurück (dem Aufrufpunkt).
Dort, wo von „einem beliebigen, im Voraus unbekannten“ Wert die Rede ist, kann dieser Ausdruck durch einen „formalen Parameter“ ersetzt werden. Funktionen werden erstellt, um bestimmte Aktionen mit beliebigen Daten formal auszuführen.
Als „formal“ werden Parameter bezeichnet, die bei der Beschreibung von Funktionen verwendet werden.
In Beispiel 14 enthält die Diff-Funktion zwei formale Parameter, die anderen haben keine. Im Allgemeinen kann eine Funktion viele formale Parameter haben (bis zu 63).
Um aber ein bestimmtes Ergebnis zu erhalten, muss die Funktion aufgerufen (also verwendet) werden. Siehe den Funktionstest in Beispiel 14, wo die Funktionen „Print“ und „diff“ aufgerufen wurden. Dabei verwendet die Funktion ganz konkrete Werte, die im Moment des Aufrufs aktuell sind: Inhalte von Variablen oder Konstanten, Literale (wie in meinem Beispiel), Ergebnisse anderer Funktionen.
Als „aktuell“ werden die Parameter bezeichnet, die wir der Funktion zum Aufrufzeitpunkt übergeben.
Um eine Funktion aufzurufen, müssen Sie ihren Namen angeben und die aktuellen Parameter in Klammern auflisten. Die aktuellen Parameter sollten hinsichtlich Art und Menge den Formalparametern entsprechen.
In Beispiel 14 verwendet die Funktion „Test“ genau zwei Ganzzahlen oder genau zwei „double“-Werte, um die Funktion „diff“ aufzurufen. Wenn ich einen Fehler machte und versuchte, einen oder drei Parameter zu schreiben, erhielt ich einen Kompilierungsfehler.
Gültigkeitsbereich von Variablen
Bei der Deklaration von Variablen muss darauf geachtet werden, wo genau diese deklariert werden.
-
Wenn die Variable innerhalb einer Funktion deklariert wird (einschließlich der formalen Parameter dieser Funktion), sehen andere Funktionen diese Variable nicht (und können sie daher nicht verwenden). Normalerweise wird eine solche Variable in dem Moment „geboren“, in dem die Funktion aufgerufen wird, und „stirbt“, wenn die Funktion ihre Arbeit beendet. Eine solche Variable wird als lokal bezeichnet.
Generell können wir sagen, dass der Gültigkeitsbereich einer Variablen durch die gesamte „Entität“ in unserem Code bestimmt wird. Wenn beispielsweise eine Variable in geschweiften Klammern deklariert wird, ist sie nur innerhalb der Klammern sichtbar, die den Block bilden, nicht jedoch außerhalb dieses Blocks. Formale Parameter einer Funktion gehören zur „Entität“ der Funktion und sind daher nur innerhalb dieser Funktion sichtbar. Und so weiter. Die Lebensdauer einer solchen Variable entspricht der Lebensdauer der „Entität“, zu der sie gehört. Beispielsweise wird eine innerhalb einer Funktion deklarierte Variable erstellt, wenn diese Funktion aufgerufen wird, und zerstört, wenn die Funktion beendet wird.
void OnStart() { //--- Local variable inside a function is visible to all blocks of that function, but not beyond it int myString = "This is local string"; // Curly braces describe a block inside a function { int k=4; // Block local variable - visible only inside curly braces Print(k); // It's ok Print (myString); } // Print(k); // Compilation error. The variable k does not exist outside the curly brace block. }
Beispiel 15. Lokale Variable innerhalb eines Blocks mit geschweiften Klammern
- Wenn eine Variable außerhalb der Beschreibung einer Funktion deklariert wird, kann sie von allen Funktionen unserer Anwendung verwendet werden. In diesem Fall entspricht die Lebensdauer einer solchen Variablen der Lebensdauer des Programms. Eine solche Variable wird als global bezeichnet.
int globalVariable = 345; void OnStart() { //--- Print (globalVariable); // It's ok }
Beispiel 16. Eine globale Variable ist für alle Funktionen in unserem Programm sichtbar.
- Eine lokale Variable kann den gleichen Namen wie eine globale Variable haben, in diesem Fall verbirgt die lokale Variable jedoch die globale Variable für die angegebene Funktion.
int globalVariable=5; void OnStart() { int globalVariable=10; // The variable is described according to all the rules, including the type. // If the type were not declared, this expression would change the global variable //--- Print(globalVariable); // The result is 10 - that is, the value of the local variable Print(::globalVariable); // The result is 5. To print the value of a global variable, not the local one, // we use two colons before the name }
Beispiel 17. Übereinstimmende Namen lokaler und globaler Variablen. Die lokale Variable verbirgt die globale.
Statische Variablen
Es gibt einen Sonderfall in der Beschreibung lokaler Variablen.
Wie oben erwähnt, verlieren lokale Variablen ihre Werte, nachdem die Funktion sich beendet hat. Normalerweise ist dies genau das erwartete Verhalten. Es gibt jedoch Situationen, in denen der Wert einer lokalen Variablen bewahrt werden muss, auch nachdem die Funktion ihre Ausführung abgeschlossen hat.
Beispielsweise ist es manchmal erforderlich, die Aufrufe einer Funktion zu zählen. Eine weitere, für Händler häufiger anfallende Aufgabe ist die Organisation einer Funktion zur Überprüfung des Beginns einer neuen Kerze. Dazu muss bei jedem Tick der aktuelle Zeitwert abgerufen und mit dem zuvor bekannten Wert verglichen werden. Sie können natürlich für jeden dieser Zähler eine globale Variable erstellen. Aber jede globale Variable erhöht die Wahrscheinlichkeit eines Fehlers, da diese Zähler von einer Funktion verwendet werden, während andere Funktionen sie nicht ändern oder sogar sehen sollten.
In solchen Fällen, in denen eine lokale Variable genauso lange bestehen bleiben soll wie eine globale, werden statische Variablen verwendet. Sie werden genau wie gewöhnliche beschrieben, lediglich das Wort „statisch“ wird vor der Beschreibung hinzugefügt. Diese Verwendung wird in der Funktion HowManyCalls im folgenden Beispiel gezeigt:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- HowManyCalls(); HowManyCalls(); HowManyCalls(); } //+------------------------------------------------------------------+ //| The function counts the number of requests | //+------------------------------------------------------------------+ void HowManyCalls() { //--- Variable description. The variable is local, but its lifetime is long. static int counter=0; // Since 'static' keyword is used, the variable is initialized only // before he first function call // (more precisely, before the OnInit function call) //--- Main actions counter++; // During program execution, the value will be stored till the end //--- Operation result Print( IntegerToString(counter)+" calls"); } // Script output: // 1 calls // 2 calls // 3 calls
Beispiel 18. Verwenden einer statischen Variablen.
Das Beispiel enthält zwei Funktionen: HowManyCalls, das eine statische Variable verwendet, um die Anzahl der Aufrufe zu zählen und das Ergebnis in das Protokoll druckt, und OnStart, das HowManyCalls dreimal hintereinander aufruft.
Übergeben von Funktionsparameter per Wert und per Referenz
Standardmäßig verwendet eine Funktion nur Datenkopien, die ihr als Parameter übergeben werden (Programmierer sagen, dass in diesem Fall die Daten „by value“ (als Werte) übertragen werden). Selbst wenn also etwas in einen Variablenparameter innerhalb der Funktion geschrieben wird, passiert nichts mit den Quelldaten.
Wenn wir möchten, dass Quelldaten innerhalb der Funktion geändert werden, muss der änderbare Formalparameter mit einem speziellen Symbol & (Et-Zeichen) gekennzeichnet werden. Diese Methode zum Beschreiben von Parametern wird als Übergabe per Referenz bezeichnet.
Um zu veranschaulichen, wie eine Funktion externe Daten ändern kann, erstellen wir eine neue Skriptdatei mit dem folgenden Code:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart(void) { //--- Declare and initialize two local variables int first = 3; int second = 77; //--- Print their values BEFORE all changes Print("Before swap: first = " + first + " second = " + second); //--- Use the Swap function, which takes data by reference Swap(first,second); //--- See what happened Print("After swap: first = " + first + " second = " + second); //--- //--- Apply the CheckLocal function to the received data //--- This function takes parameters by value CheckLocal(first,second); //--- Print the result again Print("After CheckLocal: first = " + first + " second = " + second); } //+------------------------------------------------------------------+ //| Swaps the values of two integer variables | //| Data is passed by reference, so the originals will be modified | //+------------------------------------------------------------------+ void Swap(int &a, int& b) // It can be done in any way, both positions are correct { int temp; //--- temp = a; a = b; b = temp; } //+------------------------------------------------------------------+ //| Takes parameters by value, that is why changes happen | //| only locally | //+------------------------------------------------------------------+ void CheckLocal(int a, int b) { a = 5; b = 10; } // Script output: // Before swap: first = 3 second = 77 // After swap: first = 77 second = 3 // After CheckLocal: first = 77 second = 3
Beispiel 19. Parameter per Referenz übergeben.
Dieser Code definiert drei Kurzfunktionen: OnStart, Swap und CheckLocal.
CheckLocal nimmt Daten als Wert und arbeitet daher mit Kopien, Swap nimmt zwei Parameter als Referenz und arbeitet daher mit den Quelldaten. Die Funktion OnStart deklariert zwei lokale Variablen, druckt dann den Wert dieser Variablen aus, ruft die Funktionen Swap und CheckLocal auf und zeigt die Ergebnisse der Interaktion an, indem sie den Wert ihrer lokalen Variablen nach jeder Interaktion in das Protokoll druckt. Ich mache Sie noch einmal darauf aufmerksam, dass die Swap-Funktion die ihr übergebenen Daten verändert hat, CheckLocal dies jedoch nicht konnte.
Es ist wichtig zu beachten, dass alle Variablen komplexer Typen (wie Enumerationen, Strukturen, Objekte usw. sowie etwaige Arrays) immer als Referenz übergeben werden müssen.
Beim Versuch, solche Variablen als Wert zu übergeben, generiert der Compiler einen Fehler.
Und ich liste noch einmal kurz die Grundregeln des Zusammenwirkens von Variablen und Funktionen auf:
- Globale Variablen in der Sprache MQL5 können direkt innerhalb jeder Funktion verwendet werden, einschließlich der Änderung ihrer Werte.
- Auf lokale Variablen kann nur innerhalb des Blocks zugegriffen werden, in dem sie deklariert sind.
- Wenn ein formaler Parameter die Datenübergabe „als Wert“ beschreibt, kann die Funktion die Originaldaten nicht ändern, auch wenn sie intern den Wert der Parametervariable ändert. Allerdings kann sich bei „per Referenz“ weitergegebenen Daten die ursprüngliche Position ändern.
- Wenn eine globale und eine lokale Variable denselben Namen haben, hat die lokale Variable Vorrang (mit anderen Worten: die lokale Variable überschreibt die globale).
- Die Lebensdauer globaler Variablen entspricht der Lebensdauer des Programms und lokaler Variablen entspricht der Lebensdauer des Blocks, in dem sie beschrieben werden.
Standardwerte für formale Funktionsparameter
Formalen Parametern können Standardwerte zugewiesen werden.
Wenn wir beispielsweise eine Protokollierungsfunktion erstellen, müssen wir möglicherweise die Nachrichten der Funktion von allen anderen Terminalnachrichten unterscheiden. Am einfachsten geht das, indem Sie der Originalnachricht am Anfang ein Präfix und am Ende ein Suffix hinzufügen. Die Zeichenkette selbst muss immer angegeben werden, da sonst die Bedeutung der Funktion verloren geht. Die „Ergänzungen“ können Standard sein oder geändert werden.
Der einfachste Code zur Veranschaulichung dieser Idee ist unten angegeben:
//+------------------------------------------------------------------+ //| Add a prefix and suffix to a sting | //+------------------------------------------------------------------+ string MakeMessage( string mainString, string prefix="=== ", string suffix=" ===" ) { return (prefix + mainString + suffix); }
Beispiel 20. Beschreibung einer Funktion mit standardmäßigen formalen Parametern
Beim Aufruf dieser Funktion können einer oder beide Parameter mit Standardwerten weggelassen werden. Wenn diese Parameter nicht explizit angegeben werden, verwendet die Funktion die in der Beschreibung angegebenen Werte. Zum Beispiel:
Print ( MakeMessage("My first string") ); // Default prefix and suffix Print ( MakeMessage("My second string", "~~ ") ); // Prefix changed, suffix remains unchanged Print ( MakeMessage("My third string", "~~ ", " ~~") ); // Both actual parameter have been changed // Script output: // === My first string === // ~~ My first string === // ~~ My first string ~~
Beispiel 21. Tatsächliche Parameter mit Standardwerten können weggelassen werden
Parameter mit Standardwerten können nur aufeinander folgen und müssen nach allen anderen Parametern beschrieben werden, die keine solchen Werte haben.
So lässt man eine Funktion mehrere Ergebnisse zurückgeben
Wie oben erwähnt, kann der Operator „return“ nur ein Ergebnis zurückgeben. Darüber hinaus kann die Funktion keine Arrays zurückgeben. Aber was, wenn Sie wirklich mehr Rückgabewerte benötigen? Was wäre beispielsweise, wenn Sie mit einer Funktion sowohl den Schlusszeitpunkt als auch den Preis einer Kerze berechnen oder eine Liste der verfügbaren Instrumente abrufen müssten? Versuchen wir zunächst, selbst eine Lösung zu finden und vergleichen wir diese dann mit den unten stehenden Angaben.
Um das im Titel genannte Problem zu lösen, können wir eine der folgenden Methoden verwenden:
- Wir erstellen einen komplexen Datentyp (z. B. eine Struktur) und geben eine Variable dieses Typs zurück.
- Verwenden wir die Parameterübergabe per Referenz. Dies hilft uns natürlich nicht dabei, diese Daten mithilfe von „return“ zurückzugeben, ermöglicht Ihnen jedoch, beliebige Werte aufzuschreiben und sie dann zu verwenden.
- Wir verwenden globale Variablen (nicht empfohlen). Diese Methode ähnelt der vorherigen, ist aber möglicherweise gefährlicher für den Code. Es ist besser, globale Variablen auf ein Minimum zu beschränken und nur dort einzusetzen, wo es absolut unmöglich ist, auf sie zu verzichten. Aber wenn es wirklich nötig ist, können Sie es versuchen.
Globale Variablenmodifikatoren: „input“ und „extern“
Auch bei der Verwendung globaler Variablen gibt es „Sonderfälle“. Dazu gehören:
- Beschreiben von Programm-Eingabeparametern mit dem Modifikator „input“
- Verwenden des Modifikators „extern“
Jeder Eingabeparameter eines in MQL5 geschriebenen Programms wird als globale Variable (außerhalb aller Funktionen) beschrieben und durch das Schlüsselwort „input“ bezeichnet, das am Anfang der Beschreibung steht.
input string smart = "The smartest decision"; // The window will contain this description
Beispiel 22. Beschreibung der Eingabeparameter
Normalerweise wird in der linken Spalte des Eigenschaftenfensters der Variablenname angezeigt. Wenn jedoch dieselbe Zeile, in der diese Variable beschrieben wird, einen Kommentar enthält, wie in Beispiel 22, wird dieser Kommentar anstelle des Variablennamens angezeigt.
In in MQL5 geschriebenen Programmen kann auf Variablen, die als input gekennzeichnet sind, nur gelesen werden, während man nichts in sie schreiben kann. Die Werte dieser Variablen können nur in der Beschreibung (im Code) oder im Dialogfeld der Programmeigenschaften festgelegt werden.
Wenn Sie einen Expert Advisor oder Indikator erstellen, können Sie die Werte solcher Variablen normalerweise mit einem Strategietester optimieren. Wenn Sie jedoch einige Parameter von der Optimierung ausschließen möchten, müssen Sie am Anfang des Wortes „input“ den Buchstaben „s“ oder den Modifikator „static“ hinzufügen:
input double price =1.0456; // optimize sinput int points =15; // NOT optimize static input int unoptimizedVariable =100; // NOT optimize
Beispiel 23. Verwenden des Modifikators „sinput“, um eine Variable von der Optimierung im Tester auszuschließen
Wenn der Nutzer Werte aus einer Liste im Eingabefeld auswählen soll, müssen Sie für jedes dieser Felder eine Enumeration hinzufügen. Inline-Kommentare für Enumerationselemente funktionieren auch, sodass Sie anstelle von Namen wie POINT_PRICE_CLOSE „Point Close Price“ in jeder menschlichen Sprache anzeigen können. Leider gibt es keine einfache Möglichkeit, die Textsprache für den Feldnamen (Kommentare) auszuwählen. Für jede verwendete Sprache müssen Sie eine separate Datei kompilieren. Aus diesem Grund bevorzugen die meisten erfahrenen Programmierer die Verwendung der universellen (englischen) Sprache.
Parameter können visuell gruppiert werden, um ihre Verwendung zu vereinfachen. Um einen Gruppennamen anzugeben, wird eine spezielle Beschreibung verwendet:
input group "Group Name"
Beispiel 24. Parametergruppenkopfzeile
Hier ist ein vollständiges Beispiel, das alle diese Möglichkeiten veranschaulicht:
#property script_show_inputs // Enumeration. Allows you to create a list box. enum ENUM_DIRECTION { // All inline comments next to lines describing parameters, // will be displayed instead of the names in the parameters window DIRECTION_UP = 1, // Up DIRECTION_DN = -1, // Down DIRECTION_FL = 0 // Unknown }; input group "Will be optimized" input int onlyExampleName = 10; input ENUM_DIRECTION direction = DIRECTION_FL; // Possible directions list input group "Will not be optimized" sinput string something = "Something good"; static input double doNotOptimizedMagickVariable = 1.618; // Some magic //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- } //+------------------------------------------------------------------+
Beispiel 25 . Verschiedene Möglichkeiten zur Beschreibung der Eingabeparameter
Abbildung 6. Dialogfeld „Parameter“. Gruppen (Eingabegruppen) werden farblich hervorgehoben. Grüne Pfeile zeigen die Werte von Kommentaren an, die anstelle von Variablennamen eingesetzt werden.
Abbildung 6 zeigt den Optionsdialog, der aus dem Code in Beispiel 25 generiert wurde. Auf verschiedenen Computern kann es etwas anders aussehen, aber in jedem Fall werden die Gruppenüberschriften hervorgehoben (ich habe sie im Bild blau hervorgehoben). Sie können auch sehen, dass Parameter ohne Inline-Kommentare Variablennamen verwenden. Wenn Kommentare vorhanden sind, verwendet der Compiler diese anstelle von Variablennamen, wie in meinen Zellen, die durch die grünen Pfeile angezeigt werden. Vergleichen Sie den Code aus Beispiel 25 mit dem Bild. Ich hoffe, es hilft Ihnen, alles zu verstehen.
Und noch etwas. Nicht alle Anfänger bemerken die Symbole auf der linken Seite des Datentyps jedes Parameters. Beispielsweise hat der Parameter „Possible directions list“ in der Abbildung den Datentyp Enumeration und sein Symbol () lässt darauf schließen, dass es sich um eine Liste handelt. Für dieses Feld können nur Daten aus einer eingeschränkten Auswahl ausgewählt werden. Auch die restlichen Icons sind selbsterklärend.
Der Name eines Parameters sollte nicht länger als 63 Zeichen sein (was viel ist, da echte Namen normalerweise viel kürzer sind).
Die Länge des Zeichenfolgenparameters darf 254 Zeichen nicht überschreiten. Zudem gilt: Je länger der Parametername, desto kürzer ist der Inhalt, da die Parameter im Speicher als eine durchgehende Zeile abgelegt werden.
Es ist wichtig, diese Einschränkung zu beachten, insbesondere wenn Sie die Adresse einer für das Programm wichtigen Webseite angeben. Manchmal sind Adressen wirklich lang. Wenn dies bei Ihnen der Fall ist, versuchen Sie, die Adresse auf eine andere Weise zu übergeben. Codieren Sie sie zum Beispiel als globale Variable, aber nicht als Parameter. Natürlich gibt es bessere Lösungen, wie etwa die Verwendung von Dateien oder das „Zusammenfügen“ einer Adresse aus mehreren Fragmenten. Denken Sie dabei jedoch an die Begrenzung auf 254 Zeichen für Parameterwerte.
Der zweite Sonderfall sind „externe“ Variablen.
Wenn Entwickler ein großes Programm schreiben, das auf mehrere Dateien aufgeteilt ist, kommt es vor, dass eine globale Variable in einer Datei beschrieben wird und das Programm von anderen Dateien aus darauf zugreifen muss. Und wir möchten keine Dateien mit der Direktive #include einschließen. MetaEditor nimmt jede Datei separat wahr und kann daher in diesem Fall nicht helfen.
Am häufigsten tritt diese Situation bei der Verwendung von Eingabeparametern auf (die im vorherigen Unterabschnitt beschrieben werden).
Hier kann das Schlüsselwort „extern“ verwendet werden.
extern bool testComplete;
Beispiel 26. Beschreibung der externen Variablen
Solche Variablen dürfen in dieser Datei nicht initialisiert werden, und beim Kompilieren wird die Speicheradresse dieser Variablen höchstwahrscheinlich durch eine „echte“ globale Variable mit demselben Namen ersetzt, sofern der Compiler eine solche finden kann. Funktionen können jedoch frei auf diese „formalen“ Daten zugreifen und sie auch ändern, und die IDE hat keine Probleme mit der automatischen Ersetzung.
Globale Terminalvariablen
Sowohl die in den vorherigen Abschnitten beschriebenen lokalen als auch globalen Variablen sind nur für das aktuelle Programm zugänglich. Alle anderen Programme können diese Daten nicht verwenden. Es gibt jedoch Situationen, in denen Programme Daten untereinander austauschen müssen oder sichergestellt werden muss, dass die Werte von Variablen auch nach dem Ausschalten des Terminals gespeichert bleiben.
Ein Beispiel für einen Datenaustausch kann ein sehr einfacher Indikator sein, bei dem Sie den zum Öffnen einer Position erforderlichen Geldbetrag in der Einzahlungswährung ausgeben müssen. Es scheint, als sei alles einfach. Nachdem wir das Inhaltsverzeichnis der Hilfe durchsucht haben, stellen wir fest, dass MQL5 über eine spezielle Funktion OrderCalcMargin verfügt, die den erforderlichen Betrag berechnet. Wir versuchen, es anzuwenden, und ... werden enttäuscht. Dies liegt daran, dass Sie in Indikatoren keine Handelsfunktionen verwenden können. Dies ist physikalisch auf Compilerebene verboten. OrderCalcMargin ist eine Handelsfunktion.
Daher müssen wir Workarounds finden. Eine Möglichkeit besteht darin, ein Skript oder einen Dienst zu schreiben, der die erforderlichen Beträge berechnet und diese Beträge dann in Terminalvariablen schreibt. Und dann wird unser Indikator diese Daten lesen, nicht berechnen. Dieser Trick ist möglich, weil Skripte und Dienste im Gegensatz zu Indikatoren zum Handeln berechtigt sind (siehe Tabelle im ersten Artikel der Serie).
Schauen wir uns an, wie ein solcher Datenaustausch umgesetzt werden könnte. Lassen Sie uns zunächst mit dem Assistenten eine Skriptdatei erstellen. Nennen wir diese Datei „CalculateMargin.mq5“.
Für den Zugriff auf Terminalvariablen gibt es eine ganze Reihe vordefinierter Funktionen, deren Namen mit dem Präfix GlobalVariable beginnen.
Um den Indikatoren die erforderlichen Daten zur Verfügung zu stellen, erstellen wir mithilfe dieser und der Funktion OrderCalcMargin ein neues Skript:
//+------------------------------------------------------------------+ //| CalculateMargin.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property script_show_inputs //--- Script input parameters input double requiredAmount = 1; // Number of lots //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Description of local variables string symbolName = Symbol(); // Name of the current symbol string terminalVariableName; // Terminal variable name double marginBuy, marginSell; // Margin values (buy and sell) double currentPrice = iClose(symbolName,PERIOD_CURRENT,0); // Current price to calculate margin bool okCalcBuy, okCalcSell; // Indication of success when calculating margin up or down //--- Main operations // Calculate Buy margin okCalcBuy = OrderCalcMargin( ORDER_TYPE_BUY, // Order type symbolName, // Symbol name requiredAmount, // Required volume in lots currentPrice, // Order open price marginBuy // Result (by reference) ); // Calculate Sell margin okCalcSell = OrderCalcMargin( ORDER_TYPE_SELL, // Sometimes different amounts are needed for opening up and down symbolName, requiredAmount, currentPrice, marginSell ); //--- Operation result // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Write the data. If the global terminal variable does not exist, it will be created. GlobalVariableSet ( terminalVariableName, // Where to write marginBuy // What to write ); // Now we create another name - for the Sell details terminalVariableName = symbolName + "SellAmount"; // Write data for Sell. If there was no variable with the name stored in terminalVariableName, // create one GlobalVariableSet(terminalVariableName,marginSell); } //+------------------------------------------------------------------+
Beispiel 31. Skript zur Berechnung der für den Kauf oder Verkauf von 1 Lot erforderlichen Mittel in der Einzahlungswährung und zum Speichern dieser Daten in den globalen Variablen des Terminals
Hier haben wir die Standardfunktion GlobalVariableSet verwendet, um Daten in Terminalvariablen zu schreiben. Ich denke, im gegebenen Beispiel ist die Verwendung dieser Funktionen offensichtlich. Zusätzlicher Hinweis: Die Namenslänge für die globale Terminalvariable darf 63 Zeichen nicht überschreiten.
Wenn Sie dieses Skript auf einem beliebigen Chart ausführen, werden Sie nicht sofort offensichtliche Ergebnisse sehen. Sie können jedoch mit der Taste <F3> oder durch Auswahl von „Extras -> Globale Variablen“ im Terminalmenü sehen, was passiert ist.
Abbildung 7. Menü „Terminalvariablen“.
Nach Auswahl dieses Menüpunkts erscheint ein Fenster mit einer Liste aller Terminalvariablen:
Abbildung 8. Fenster mit einer Liste globaler Terminalvariablen
In Abbildung 8 können Sie sehen, dass ich das Skript nur für das EURUSD-Paar ausgeführt habe, sodass nur zwei Variablen sichtbar sind: die Beträge für Kauf und Verkauf, die in diesem Fall gleich sind.
Lassen Sie uns nun einen Indikator erstellen, der diese Daten verwendet, und gleichzeitig sehen wir, wie Standardfunktionen die oben besprochenen Prinzipien der Arbeit mit Variablen verwenden.
Nennen wir diese Datei „GlobalVars.mq5“. Die wichtigsten Operationen dieses Indikators werden innerhalb der Funktion OnInit ausgeführt, die einmal unmittelbar nach dem Programmstart ausgeführt wird. Wir fügen auch die Funktion OnDeinit hinzu, die Kommentare entfernt, wenn wir den Indikator aus dem Chart entfernen. Die für jeden Indikator obligatorische und bei jedem Tick ausgeführte Funktion OnCalculate ist in diesem Indikator ebenfalls vorhanden, wird jedoch nicht verwendet.
//+------------------------------------------------------------------+ //| GlobalVars.mq5 | //| Oleg Fedorov (aka certain) | //| mailto:coder.fedorov@gmail.com | //+------------------------------------------------------------------+ #property copyright "Oleg Fedorov (aka certain)" #property link "mailto:coder.fedorov@gmail.com" #property version "1.00" #property indicator_chart_window //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Description of local variables string symbolName = Symbol(); // Symbol name string terminalVariableName; // Name of global terminal value double buyMarginValue, sellMarginValue; // Buy and Sell value bool okCalcBuy; // Indication that everything is OK when calling one of the variants of the GlobalVariableGet function //--- Main operations // Create a terminal variable name for Buy details terminalVariableName = symbolName + "BuyAmount"; // Use the first method to get the value of a global variable. // To get the result, the parameter is passed by reference okCalcBuy = GlobalVariableGet(terminalVariableName, buyMarginValue); // Change the name of the terminal variable - for Sell details terminalVariableName = symbolName + "SellAmount"; // Second way to get the result: return value sellMarginValue = GlobalVariableGet(terminalVariableName); //--- Output the result as a comment on the chart Comment( "Buy margin is " + DoubleToString(buyMarginValue) // Buy margin value, the second parameter // of the DoubleToString function is omitted +"\n" // Line break +"Sell margin is " + DoubleToString(sellMarginValue,2) // Margin value for sale, indicated the number of // decimal places ); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| The function will be called when the program terminates. | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Очищаем комментарии Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function (not used here) | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+
Beispiel 32. Verwenden globaler Terminalvariablen im Indikator.
In diesem Demoindikator müssen wir die Terminalvariablen einmal lesen und den Vorgang abschließen. Daher wird der Hauptcode in der OnInit-Funktion platziert. Das Beispiel mag umfangreich und beängstigend erscheinen, ist aber tatsächlich sehr einfach zu lesen, insbesondere weil es sich größtenteils um Kommentare handelt. Lassen Sie mich noch einmal in Worten beschreiben, was in der OnInit-Funktion passiert:
- Im ersten Block dieser Funktion deklarieren wir alle Variablen, die wir später benötigen.
- Anschließend erstellen wir einen Namen für die globale Terminalvariable.
- Wir lesen den Wert der gewünschten globalen Terminalvariable in die entsprechende lokale Variable ein.
- Anschließend generieren wir den Namen der zweiten Variable und lesen deren Wert in der nächsten Zeile.
- Und die letzte Aktion besteht darin, eine Nachricht für den Nutzer in Form eines Kommentars in der oberen linken Ecke auszugeben (siehe Abbildung 9).
Bitte beachten Sie, dass die Funktion GlobalVariableGet über zwei Aufrufmöglichkeiten verfügt: einmal mit dem Rückgabewert und einmal mit per Referenz übergebenen Parameter, während die Funktion DoubleToString über einen Parameter mit einem Standardwert verfügt. Wenn Sie den Beispieltext im Editor eingeben, um die Funktionalität des Codes zu überprüfen, anstatt ihn über die Zwischenablage zu kopieren, werden Sie von MetaEditor auf diese Nuancen hingewiesen.
Abbildung 9. Ergebnis der Indikatoroperation
Da ich zum Generieren der Ausgabe unterschiedliche Aufrufmethoden für die Funktion DoubleToString verwendet habe, sehen die Kommentare in der oberen und unteren Zeile etwas unterschiedlich aus. Beim Formatieren einer Nachricht für die oberste Zeile habe ich den zweiten Parameter der Funktion DoubleToString weggelassen. Dieser Parameter muss die Anzahl der Zeichen nach dem Dezimalpunkt angeben. Standardmäßig sind das 8. Für die unterste Zeile habe ich diesen Wert explizit angegeben und das Programm angewiesen, zwei Zeichen auszugeben.
Bitte beachten Sie, dass der Indikator auf dem Chart gestartet werden muss, auf dem das Skript angewendet wurde, und erst nach dem Skript – um sicherzustellen, dass die globalen Variablen des Terminals während der Ausführung vorhanden sind. Andernfalls tritt beim Ausführen des Indikators ein Fehler auf und die Kommentare werden nicht angezeigt.
So wird die Funktion GlobalVariableSet zum Schreiben von Terminalvariablen und GlobalVariableGet zum Lesen dieser verwendet. Diese Funktionen werden von Programmierern am häufigsten verwendet, aber auch die anderen sind nützlich. Ich empfehle daher, zumindest die Liste in der Sprachdokumentation zu lesen (Link am Anfang des Abschnitts).
Schlussfolgerung
Lassen Sie uns die Liste der heute behandelten Themen noch einmal durchgehen. Sollte Ihnen ein Punkt der Liste unklar bleiben, kehren Sie bitte zur entsprechenden Stelle im Artikel zurück und lesen Sie ihn noch einmal durch, da dieses Material die Grundlage für die weitere Arbeit bildet (na ja, vielleicht mit Ausnahme der globalen Terminalvariablen, auf die kann man oft verzichten - aber ihr Verständnis bereitet normalerweise keine Schwierigkeiten). In diesem Artikel haben wir also über Folgendes gesprochen:
- Reihen:
- Können statisch und dynamisch sein.
- Können eindimensional und mehrdimensional sein.
- Statische Arrays können mit Literalen (in geschweiften Klammern) initialisiert werden.
- Um mit dynamischen Arrays zu arbeiten, müssen Sie Standardfunktionen zum Ändern der Größe und zum Ermitteln der aktuellen Größe verwenden
- Variable in Bezug auf Funktionen können sein
- Lokal (kurzlebig, außer statisch); der statische Modifikator kann ihnen hinzugefügt werden
- Global (langlebig); Sie können ihnen externe und Eingabemodifikatoren hinzufügen
- Funktionsparameter können übergeben werden
- als Referenz
- als Wert
- Es gibt globale Terminalvariablen. Im Gegensatz zu einfachen globalen Programmvariablen können sie zum Datenaustausch zwischen verschiedenen Programmen verwendet werden. Für deren Nutzung steht ein spezieller Funktionsumfang zur Verfügung.
Wenn Sie sich all dies merken und keiner der Punkte auf der Liste Sie verwirrt, können Sie nicht länger als Neuling betrachtet werden: Sie verfügen bereits über eine gute Grundlage, auf der Sie aufbauen können. Jetzt müssen Sie nur noch herausfinden, wie die grundlegenden Operatoren verwendet werden und welche Möglichkeiten die Sprache MQL5 beim Schreiben von Indikatoren und Experten im Verhältnis zu anderen Sprachen bietet – und schon können Sie mit dem Schreiben nützlicher Programme beginnen. Um jedoch das „professionelle“ Niveau zu erreichen, müssen Sie ein Dutzend weiterer Themen verstehen, einschließlich der objektorientierten Programmierung. Aber alle diese Themen basieren auf der einen oder anderen Weise auf den Grundlagen, die bereits zur Hälfte fertig sind.
Und möge die Sprachdokumentation mit Ihnen sein …
Frühere Artikel der Serie:
- MQL5 meistern vom Anfänger bis zum Profi (Teil I): Erste Schritte der Programmierung
- MQL5 meistern vom Anfänger bis zum Profi (Teil II): Grundlegende Datentypen und Verwendung von Variablen
- MQL5 meistern vom Anfänger bis zum Profi (Teil IIII): Komplexe Datentypen und Include-Dateien
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15357





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.