
MQL5 beherrschen, vom Anfänger zum Profi (Teil V): Grundlegende Operatoren für die Ablaufkontrolle
Einführung
In diesem Artikel unserer Serie werden wir uns darauf konzentrieren, einem Programm beizubringen, wie man Entscheidungen trifft. Um dies zu erreichen, werden wir einen Indikator entwickeln. Dieser Indikator zeigt entweder „inside bars“ oder „outside bars“ an.
- „Outside bar“ ist eine Kerze, die den Bereich der vorherigen Kerze in beide Richtungen durchbricht.
- „Inside bar“ ist ein Kerze, die nach dem Schließen innerhalb des Bereichs der vorherigen Kerze bleibt.
- Wir markieren jede geschlossene Kerze. Wenn sich eine Kerze noch in der Entstehung befindet, wird sie nicht markiert.
- Die Markierungen werden nur auf „lange“ Kerzen angewandt: Bei einer „outside bar“ wird die Markierung auf der aktuellen Kerze platziert, während sie bei einer „inside bar“ auf der vorherigen Kerzen platziert wird. Diese Entscheidung ist subjektiv. Wenn Sie den Indikator also nützlich finden, aber mit dieser Entscheidung nicht einverstanden sind, können Sie die entsprechenden Teile des Codes anpassen.
- Lassen Sie uns unseren Indikator universell einsetzen. Er kann beide Optionen anzeigen, und der Nutzer kann anhand von Eingabeparametern entscheiden, welche Option er wählen möchte.
Um diesen Indikator zu entwickeln, müssen wir die in diesem Artikel besprochenen Operatoren für die Ablaufkontrolle verwenden. Da der Indikator relativ einfach ist, werden wir hauptsächlich mit bedingten Anweisungen und Schleifen arbeiten - alles, was für diese Aufgabe notwendig ist. Als zusätzliche Übung können Sie jedoch erwägen, den Indikator neu zu schreiben:
- Verschiedene Arten von Schleifen;
- Der switch-case-Operator anstelle von einigen (oder allen) bedingten Anweisungen.
Bisher waren unsere Programme ziemlich beschränkt und führten Befehle auf streng lineare Weise ohne Entscheidungsmöglichkeiten aus. Nach der Lektüre dieses Artikels werden Sie in der Lage sein, anspruchsvollere Programme zu erstellen.
Zu Beginn werden wir einen detaillierten Blick auf boolesche (logische) Ausdrücke werfen, da sie die Grundlage für das Verständnis des restlichen Materials bilden.
Detaillierte Übersicht über logische Ausdrücke
Zur Erinnerung: Der logische Datentypen (bool) kennt nur zwei mögliche Werte: wahr (true) oder falsch (false).
Jedes Mal, wenn ein logischer Operator verwendet wird, fragt das Programm im Wesentlichen seine aktuellen Daten ab: “Ist es wahr, dass (Bedingung)?“ (Ist es wahr, dass (prev_calculated == rates_total)? Wenn ja, dann sollte ich die aktuelle Funktion verlassen! Ansonsten muss ich die Berechnungen fortsetzen...).
In MQL5 steht die Zahl 0 für falsch, während alle anderen Zahlen in booleschen Ausdrücken, ob positiv oder negativ, als wahr behandelt werden.
Zeichenketten (Typ: string) können jedoch nicht in einen bool‘schen Wert umgewandelt werden. Jeder Versuch, dies zu tun, führt zu einem Compilerfehler. Zeichenketten können jedoch verglichen werden.
Logische Daten ergeben sich in der Regel aus Vergleichsausdrücken. Der einfachste Typ eines logischen Ausdrucks ist eine Vergleichsanweisung. In MQL5 werden die folgenden Vergleichsoperatoren verwendet (ähnlich der mathematischen Standardnotation): größer als (>), kleiner als (<), gleich (==), nicht gleich (!=), größer oder gleich (>=), kleiner oder gleich (<=). Der Ausdruck (a==b) wird beispielsweise nur dann als wahr bewertet, wenn beide Elemente gleich sind. Das ist, glaube ich, klar.
Beim Vergleich von Zeichenketten wird die Zeichenkette mit den Zeichen, die in der Tabelle der Zeichen höhere ASCII-Werte haben, als größer angesehen. Zum Beispiel: ("A" > "1") ist wahr, während ("Y" < "A") falsch ist. Wenn eine Zeichenkette mehrere Zeichen enthält, wird der Vergleich zeichenweise von links nach rechts durchgeführt, bis ein Unterschied gefunden wird. Eine leere Zeichenfolge ("") wird als kleiner als jede andere Zeichenfolge betrachtet. Wenn zwei Zeichenfolgen identische Anfangszeichen haben, aber eine kürzer ist, wird die kürzere Zeichenfolge als kleiner angesehen.
Es gibt drei weitere logische Operationen:
- Logische Negation oder Umkehrung (auch als logisches „NICHT“ bezeichnet), die durch ein Ausrufezeichen „!“ angezeigt wird;
verwandelt einen logischen Wert in sein Gegenteil. - Die logische Verknüpfung (logisches „UND“) wird durch zwei kaufmännische Und-Symbole (&&) dargestellt;
ein Ausdruck mit diesem Operator ist nur dann wahr, wenn beide Teile des Ausdrucks - sowohl links als auch rechts - wahr sind. - Die logische Disjunktion (logisches „ODER“) wird durch zwei vertikale Linien (||) gekennzeichnet;
ein Ausdruck mit diesem Operator ist wahr, wenn mindestens einer seiner Teile wahr ist.
Tabelle 1. Logisches NICHT (!) | Tabelle 2. Logisches UND (&&) | Tabelle 3. Logisches ODER (||) |
![]() | ![]() | ![]() |
Versuchen Sie, das Ergebnis der einzelnen logischen Ausdrücke zu bestimmen, um Ihr Verständnis zu vertiefen. In den Kommentaren werden Hinweise gegeben, aber ich empfehle, die Übungen zu machen, ohne sie vorher anzuschauen. Verwenden Sie die Kommentare nur, um Ihre Antworten zu überprüfen.
int a = 3; int b = 5; bool e = ( a*b ) > ( a+b+b ); // true Print ( (a>b) ); // false Print ( (a<b) || ((a/b) > (3/2)) ); // true Print ( !a ); // false Print ( ! (!e) ); // true Print ( (b>a) && ((a<=3) || (b<=3)) ); // true Print ( "Trust" > "Training" ); // true Print ( a==b ); // false Print ( b=a ); // 3 (!!!) (therefore, for any logical operator this is always true) // In the last example, a very common and hard-to-detect mistake has been made. // Instead of a comparison, an assignment was used, which results in a value of type int! // Fortunately, the compiler will usually warn about this substitution.
Beispiel 1. Beispiele für die Verwendung logischer Operatoren
Wenn ein Ausdruck mehrere Operatoren ohne Klammern enthält, werden sie im Allgemeinen von links nach rechts ausgewertet (außer bei Zuweisungen). Die Reihenfolge der Ausführung folgt diesen Regeln:
- Alle arithmetischen Operatoren werden zuerst ausgeführt.
- *, /, %
- +, -
- Es folgen die Vergleichsoperatoren (==, !=, >, <, >=, <=).
- Danach folgen die übrigen logischen Operatoren, die in der Reihenfolge ihrer Auflistung ausgeführt werden:
- Inversion (!);
- Konjunktion (&&);
- Disjunktion (||);
- Der Zuweisungsoperator (=) wird zuletzt ausgeführt.
Wenn Sie sich über die Ausführungsreihenfolge in Ihrem Code unsicher sind, verwenden Sie Klammern, wie in Beispiel 1 gezeigt. Klammern sorgen für Klarheit, indem sie Operationen in Klammern dazu zwingen, zuerst ausgeführt zu werden. Außerdem wird die Lesbarkeit des Codes durch die richtige Einrückung verbessert.
Die if-Anweisung
Die if-Anweisung, auch bekannt als bedingte Anweisung, Verzweigungsanweisung oder Entscheidungsstruktur, hilft dem Programm, Entscheidungen zu treffen.
Die Syntax ist einfach:
if (condition) action_if_condition_is_true; else action_in_all_other_cases; // The "else" branch is optional
Beispiel 2. Vorlage für eine bedingte Anweisung.
Warum nennt man es „Verzweigung“? Wenn man sich das vorstellt, sieht die Struktur wie ein Baumzweig aus, der den Programmfluss aufteilt:
Abbildung 1. Verzweigungsoperator
Wenn Sie Ihre Vorstellungskraft einsetzen, können Sie sehen, wie sich der „Hauptstamm“ der Ausführung in einzelne Zweige aufspaltet. In älteren Flussdiagrammen ähneln ganze Algorithmen oft Büschen. Nehmen wir zum Beispiel an, Sie wollen ein Buch lesen, aber Ihre Schreibtischlampe funktioniert nicht. Versuchen Sie zunächst, die Lampe einzuschalten - vielleicht funktioniert es ja? Wenn dies nicht der Fall ist, prüfen Sie, ob die Glühbirne in der Fassung sitzt - vielleicht hat sie jemand entfernt? Wenn die Glühbirne vorhanden ist, sich die Lampe aber trotzdem nicht einschalten lässt, prüfen Sie, ob sie durchgebrannt ist. Wenn sie durchgebrannt ist, versuchen Sie, sie zu ersetzen. Wenn keiner dieser Schritte funktioniert, versuchen Sie es mit einer anderen Lampe. Dieser logische Prozess kann in Form eines Entscheidungsbaums dargestellt werden:
Abbildung 2. Illustration von Zweigen
In diesem Flussdiagramm beginnt die Ausführung mit dem grünen Punkt „Start“ am unteren Rand. Meiner Meinung nach zeigt es deutlich, wie die Verzweigung an jedem Auswahlpunkt erfolgt. Jeder blau markierte Entscheidungspunkt stellt einen Verzweigungspfad dar.
Beachten Sie, dass „action_if_condition_is_true" (Aktion_wenn_Bedingung_zutrifft) und „action_in_all_other_cases“ (Aktion_in_allen_anderen_Fällen) in der MQL5-Sprache eine einzige Anweisung sein können, aber diese Anweisung kann bei Bedarf zusammengesetzt sein.
Lassen Sie es mich erklären. In MQL5 können Aktionen in geschweifte Klammern eingeschlossen werden (oder auch nicht). Die geschweiften Klammern fungieren als separate „Einheiten“ und grenzen separate Codeblöcke ab. Alle innerhalb eines Blocks deklarierten Variablen sind außerhalb des Blocks nicht zugänglich. In einigen Fällen sind geschweifte Klammern obligatorisch, häufig sind sie jedoch optional. Der Inhalt innerhalb der geschweiften Klammern wird vom Rest des Programms als ein einziges Ganzes wahrgenommen. So sind beispielsweise Variablen, die innerhalb eines Blocks deklariert werden, für alle Anweisungen außerhalb des Blocks nicht sichtbar. Unter zusammengesetzten Anweisungen verstehe ich solche „Blöcke“ in geschweiften Klammern. Darin können sich beliebig viele Aktionen befinden, aber if-Befehle werden so lange ausgeführt, bis sie auf die geschweifte Klammer treffen, die den Hauptblock abschließt. Wenn die geschweiften Klammern weggelassen werden, wird nur die erste Anweisung nach if ausgeführt, während die nachfolgenden Anweisungen bedingungslos ausgeführt werden.
Häufig werden Befehle, die einer Anweisung (in unserem Fall if oder else) untergeordnet sind, als „Anweisungshauptteil“ (statement body) bezeichnet.
In der Vorlage aus Beispiel 2 ist der Hauptteil der if-Anweisung „action_if_condition_is_true“ (Aktion_wenn_Bedingung_zutrifft) und der Hauptteil der else-Anweisung ist „action_in_all_other_cases“ (Aktion_in_allen_anderen_Fällen).
Hier ein praktisches Beispiel. Dieser Code prüft auf der Grundlage von Beispiel 16, ob eine neue Kerze im Indikator vorhanden ist. Die Logik vergleicht die Anzahl der zuvor berechneten Kerzen (prev_calculated) mit der Gesamtanzahl der Kerzen (rates_total):
if (prev_calculated == rates_total) // _If_ they are equal, { // _then_ do nothing, wait for a new candlestick Comment("Nothing to do"); // Display a relevant message in a comment return(rates_total); // Since nothing needs to be done, exit the function // and inform the terminal that all bars have been calculated (return rates_total) } // A new candle has arrived. Execute necessary actions Comment(""); // Clear comments Print("I can start to do anything"); // Log a message // Since the required actions will be executed // only if prev_calculated is not equal to rates_total, // else branch is not needed in this example. // We just perform the actions.
Beispiel 3. Warten auf eine neue Kerze mit prev_calculated und rates_total
Hier enthält die if-Anweisung zwei Operationen: Comment() und return. Die else-Anweisung wird weggelassen, da alle notwendigen Aktionen im Hauptausführungsblock stattfinden.
Innerhalb geschweifter Klammern können wir so viele Operatoren verwenden, wie wir brauchen. Wenn die geschweiften Klammern entfernt wird, wird nur die Anweisung Comment("Nothing to do") bedingungsgemäß ausgeführt. In diesem Fall würde der Rückgabeoperator bedingungslos ausgeführt, und die Meldung über die Arbeitsbereitschaft würde nie erscheinen (ich empfehle, dies auf einem Minutenchart zu überprüfen).
Empfehlung für bewährte Verfahren:
Verwenden Sie immer geschweifte Klammern ({}) für if-Anweisungen, auch wenn sie nur eine Anweisung enthalten.
Halten Sie sich an diese Empfehlung, bis Sie genügend Erfahrung gesammelt haben, z. B. indem Sie den Code anderer Programmierer in einer Codebasis lesen. Mit geschweiften Klammern wird das Debuggen von Anwendungen anschaulicher, und Sie müssen nicht stundenlang versuchen, schwer zu findende Fehler zu isolieren. Wenn Sie geschweifte Klammern auch nur für eine Anweisung verwenden, wird es etwas einfacher, später weitere Aktionen hinzuzufügen. Zum Beispiel könnten Sie später die Ausgabe von Debugging-Informationen oder Warnungen implementieren wollen.
Der ternäre Operator
Manchmal verwenden wir bedingte Anweisungen, um einer Variablen einen von zwei möglichen Werten zuzuweisen. Zum Beispiel:
int a = 9; int b = 45; string message; if( (b%a) == 0 ) // (1) { message = "b divides by a without remainder"; // (2) } else { message = "b is NOT divisible by a"; // (3) } Print (message);
Beispiel 4. Bedingung für die Umwandlung in einen ternären Operator
In diesem Fall können wir die Notation etwas abkürzen, indem wir einen ternären (dreiteiligen) Operator verwenden:
int a = 9; int b = 45; string message; message = ( (b%a) == 0 ) /* (1) */ ? "b divides by a without remainder" /* (2) */ : "b is NOT divisible by a" /* (3) */ ; Print (message);
Beispiel 5. Ternärer Operator
Der Operator folgt demselben for wie die if-Anweisung: Bedingung -> (Fragezeichen) -> value_if_condition_is_true (Wert_wenn_Bedingung_zutrifft) -> (Doppelpunkt statt 'else') -> value_if_condition_is_false (Wert_wenn_Bedingung_nicht_zutrifft). Sie können solche ternären Operatoren verwenden, wenn sie für unsere Aufgabe geeignet sind.
Der Hauptunterschied zwischen dem ternären Operator und der traditionellen IF-Anweisung besteht darin, dass der ternäre Operator einen Wert zurückgeben muss, der dem Typ der Variablen auf der linken Seite der Zuweisung entspricht. Daher kann er nur innerhalb von Ausdrücken verwendet werden. Im Gegensatz dazu enthält eine traditionelle IF-Anweisung zwar Ausdrücke, kann aber nicht direkt einen Wert zurückgeben.
Die Anweisung switch — case
Es gibt Szenarien, in denen wir aus mehr als nur zwei Optionen wählen müssen. Es können zwar mehrere verschachtelte IF-Anweisungen verwendet werden, aber sie können die Lesbarkeit des Codes erheblich beeinträchtigen. In solchen Fällen ist die switch-Anweisung (auch Selektor genannt) die bevorzugte Lösung. Syntax-Vorlage:
switch (integer_variable) { case value_1: operation_list_1; case value_2 operation_list_2; // … default: default_operation_list; }
Beispiel 6. Struktur der Anweisung switch-case
Das funktioniert folgendermaßen. Wenn der Wert von „integer_variable“ mit einem der Werte („value_1“, „value_2“ ...) übereinstimmt, beginnt die Ausführung in diesem Fall und wird sequenziell fortgesetzt. Meistens reicht es aus, nur einen Block zu bearbeiten. Deshalb fügen wir nach jeder Liste von Operationen normalerweise eine break-Anweisung ein, die die switch-Anweisung sofort beendet. Wenn keine Fälle übereinstimmen, wird der Standardabschnitt „default“ ausgeführt. Der Standardabschnitt muss immer am Ende der Anweisung stehen.
In MQL5 wird switch-case häufig für die Behandlung von Handelsfehlern in Expert Advisors und für die Verarbeitung von Nutzereingaben, wie z. B. Tastendrucke oder Mausbewegungen, verwendet. Im Folgenden finden Sie ein typisches Beispiel für eine Funktion, die Handelsfehler behandelt:
void PrintErrorDescription() { int lastError = GetLastError(); // If (lastError == 0), there are no errors… if(lastError == 0) { return; // …no need to load cpu with unnecessary computations. } // If there are errors, output an explanatory message to the log. switch(lastError) { case ERR_INTERNAL_ERROR: Print("Unexpected internal error"); // You can select any appropriate action here break; case ERR_WRONG_INTERNAL_PARAMETER: Print("Wrong parameter in the inner call of the client terminal function"); break; case ERR_INVALID_PARAMETER: Print("Wrong parameter when calling the system function"); break; case ERR_NOT_ENOUGH_MEMORY: Print("Not enough memory to perform the system function"); break; default: Print("I don't know anything about this error"); } }
Beispiel 7. Standardverfahren zur Fehlerbehandlung
Schleifen mit Vorbedingung (while)
Eine Schleife ist eine Kontrollstruktur, die die wiederholte Ausführung eines Codeblocks ermöglicht.
Beim Programmieren gibt es oft Situationen, in denen bestimmte Aktionen mehrfach ausgeführt werden müssen, aber mit leicht unterschiedlichen Daten. So kann es beispielsweise erforderlich sein, alle Elemente eines Arrays (z. B. eines Indikatorpuffers) zu durchlaufen, um das Verhalten eines Indikators über historische Daten hinweg zu visualisieren. Ein weiteres Beispiel ist das zeilenweise Lesen von Parametern aus einer Datei für einen komplexen EA. Und eine Menge anderer Fälle.
MQL5 bietet drei Arten von Schleifen, die jeweils für unterschiedliche Szenarien geeignet sind. Zwar sind alle Schleifen austauschbar, d. h. eine Schleife kann eine andere ohne Funktionsverlust ersetzen, aber die Verwendung des richtigen Schleifentyps verbessert die Lesbarkeit und Effizienz des Codes.
Der erste Typ ist die while-Schleife, auch bekannt als Schleife mit einer Vorbedingung. Der Name leitet sich von der Tatsache ab, dass die Bedingung vor der Ausführung des Schleifenhauptteils geprüft wird. Visuell lässt sie sich wie folgt darstellen:
Abbildung 3. While-Schleifendiagramm
Die Syntaxvorlage für diesen Operator ist sehr einfach:
while (condition)
action_if_condition_is_true;
Beispiel 8. Vorlage für eine While-Schleife
Hier ist „action_if_condition_is_true“ eine einzelne Anweisung, einfach oder zusammengesetzt.
Wenn ein Programm auf eine while-Anweisung stößt, prüft es die Bedingung, und wenn die Bedingung wahr ist, führt es die Aktionen im Schleifenkörper aus und prüft dann die Bedingung erneut. Auf diese Weise wird die Schleife immer wieder ausgeführt, bis die Bedingung falsch wird. Da die Bedingung geprüft wird, bevor die Hauptanweisungen ausgeführt werden, kann es Situationen geben, in denen die Schleife nicht einmal ausgeführt wird.
Normalerweise wird diese Anweisung in Fällen verwendet, in denen die genaue Anzahl der Wiederholungen nicht berechnet werden kann. Wenn Sie zum Beispiel eine Textdatei lesen müssen, kann das Programm nicht wissen, wie lang sie ist. Das Programm liest die Datei so lange, bis es die End-of-File-Markierung erreicht. Sobald der Wert erreicht ist, wird das Lesen gestoppt. Und solche Situationen sind bei der Programmierung durchaus üblich.
In jedem Fall muss sich mindestens ein Parameter innerhalb des Schleifenkörpers ändern, was sich auf die Bedingung in Klammern auswirkt. Wenn kein solcher Parameter angegeben wird, wird die Schleife ewig durchlaufen und kann nur durch Schließen des Terminalfensters unterbrochen werden. Im schlimmsten Fall, wenn der Prozessor so in eine Endlosschleife gerät, können sogar grundlegende Operationen wie Tastatureingaben oder Mausbewegungen ignoriert werden. Die einzige Möglichkeit, das Programm zu stoppen, wäre ein erzwungener Neustart des Systems. Daher sollten Sie sicherstellen, dass Sie mindestens eine Gelegenheit haben, das Programm abzuschließen. Sie können zum Beispiel eine Bedingung (&& ! IsStopped() ), wodurch die Schleife angehalten wird, wenn das Programm abbricht.
Wir wollen eine einfache Aufgabe implementieren, bei der die Zahlen von 1 bis 5 mit einer while-Schleife summiert werden. Angenommen, wir wissen nichts über arithmetische Progressionen.
Mit Hilfe der while-Schleife wird dieses Problem wie folgt gelöst:
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations while (a <= 5) // While a is less than 5 { sum += a; // Add the value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Beispiel 9. Verwendung der while-Schleife
Auch wenn die Anzahl der Iterationen gering ist, zeigt dieses Beispiel die Funktionsweise von while-Schleifen.
Schleife mit Nachbedingung (do... while)
Die Schleife do... while verwendet eine Bedingungsprüfung, nachdem alle Aktionen ihres Hauptteils abgearbeitet wurden. Grafisch kann diese Schleife wie folgt dargestellt werden:
Abbildung 4. Diagramm der Do-while-Schleife
Syntax-Vorlage:
do actions_if_condition_is_true; while (condition);
Beispiel 10. Vorlage der Do-while-Schleife
Im Gegensatz zu while werden die Aktionen innerhalb des Schleifenhauptteils in dieser Anweisung mindestens einmal ausgeführt. Der Rest ist ähnlich: Das Programm führt eine Aktion aus, prüft die Bedingung, die ihm sagt, ob es zum Anfang zurückkehren soll, und führt, falls nötig, alle Aktionen des Schleifenkörpers erneut aus.
Was die Anwendungsbereiche anbelangt, so könnte dies zum Beispiel die Überprüfung von Handelsinstrumenten im Fenster „Marktüberwachung“ sein. Der Algorithmus könnte folgendermaßen aussehen:
- das erste Symbol aus der Liste nehmen (wenn wir absolut sicher sind, dass es definitiv existiert),
- die gewünschte Aktion durchführen (z. B. vorhandene Aufträge prüfen)
- und dann prüfen, ob es weitere Symbole in der Liste gibt;
- wenn ein weiteres Symbol gefunden wird, werden die Aktionen fortgesetzt, bis das Ende der Liste erreicht ist.
Die Summierung sieht bei dieser Form der Schleife fast genauso aus wie im vorherigen Fall. Nur zwei Zeilen haben sich geändert: die Beschreibung der Schleife selbst.
//--- Variables declaration int a = 1; // initialize (!) the variable parameter used in the condition int sum = 0; // result string message = "The sum of "+ (string) a; // show message //--- Perform main operations do // Execute { sum += a; // Add value to the sum if ( a != 1) // The first value is already added at the time of initialization { message += " + " + string (a); // Further operations } a++; // Increase parameter (very important!) } while (a <= 5) // While a is less than 5 //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Beispiel 11. Verwendung der do-while-Schleife
Die Schleife for
Die for-Schleife wird am häufigsten verwendet, da sie zur Iteration über alle Arten von Sequenzen in Situationen verwendet wird, in denen die Anzahl der Elemente, über die iteriert werden soll, leicht berechnet werden kann. Syntax-Vorlage:
for (initialize_counter ; conditions ; change_counter)
action_if_condition_is_true;
Beispiel 12. Die Vorlage der For-Schleife
Im Gegensatz zu den vorherigen Schleifenformen können Sie bei der for-Schleife deutlich sehen, welcher Parameter sich wie verändert (in der Vorlage habe ich diesen Parameter als Zähler bezeichnet). Im Hauptteil der Schleife können Sie sich dann auf die Lösung der Hauptaufgabe konzentrieren (z. B. Iteration durch die Elemente eines Arrays).
Diese Schleife entspricht im Wesentlichen der while-Schleife, d.h. die Prüfung wird ebenfalls vor der Ausführung der Body-Operatoren durchgeführt. Im Folgenden wird beschrieben, wie man mit for Zahlen summieren kann:
//--- Variables declaration int a; // do NOT initialize the variable parameter, only describe // (this is optional as you can describe it in the loop header) int sum = 0; // result string message = "The sum of "; // a shorter message for the user, // as the value is not yet known //--- Perform main operations for (a=1; a<=5; a++)// For (each `a` from 1 to 5) /loop header/ { sum += a; // Add `a` to the sum if ( a != 1) // the first value does not contain "+" { message += " + "; // add "+" before all sequence members starting from the second } message += string (a); // Add sequence elements to the message // Changes to the counter are described not here, but in the loop header as the last parameter. } //-- After the loop is completed, output a message message += " is " + (string) sum; // message text Comment (message); // show the comment
Beispiel 13. Verwendung der for-Schleife
In diesem Beispiel sehen wir mehr Änderungen als in den vorherigen (gelb hervorgehoben). Ich möchte noch einmal betonen, dass alle Schritte im Zusammenhang mit dem Schleifenzähler (veränderbarer Parameter) in den Schleifenkopf verlagert wurden: Initialisierung (a = 1), Prüfung der Bedingung (a <= 5) (wenn die Bedingung erfüllt ist, wird der Hauptschleifenkörper ausgeführt), Inkrement (a++) und erneute Bewertung der Bedingung - am Ende jeder Iteration wird der Zähler aktualisiert und die Bedingung erneut geprüft.
Die Anweisungen break und continue
Manchmal ist es nicht notwendig, alle Schritte einer Schleife auszuführen.
Bei der Suche nach einem Wert in einem Array ist es zum Beispiel nicht immer erforderlich, das gesamte Array zu durchlaufen, wenn der Zielwert in der Mitte gefunden wird. In solchen Fällen, wenn wir die Schleife vollständig beenden müssen, verwenden wir die break-Anweisung, die die Schleife sofort beendet und mit der nächsten Operation fortfährt. Zum Beispiel:
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { break; } message += " " + IntegerToString( i ); } Print(message); // Result: 0 1 (no further execution)
Beispiel 14. Verwendung der break-Anweisung
Es gibt auch Fälle, in denen der Schleifenkörper unter bestimmten Bedingungen nicht ausgeführt werden muss, die Schleife selbst aber nicht beendet werden soll - wir wollen lediglich bestimmte Iterationen überspringen. Wenn wir zum Beispiel in Beispiel 14 die Zahl 2 vom Druck ausschließen und alle anderen Zahlen beibehalten wollen, müssen wir den Code leicht abändern, indem wir „break“ durch „continue“ ersetzen. Die continue-Anweisung springt sofort zur nächsten Iteration:
string message = ""; for (int i=0; i<=3; i++) { if ( i == 2 ) { continue; } message += " " + IntegerToString( i ); } Print(message); // Output: 0 1 3 (the number 2 is skipped)
Beispiel 15. Verwendung der continue-Anweisung
Die break-Anweisung kann entweder innerhalb einer Schleife oder in case-Anweisungen verwendet werden; die continue-Anweisung kann nur innerhalb von Schleifen verwendet werden.
Und das ist alles über Aussagen. Nun wollen wir sehen, wie diese Operatoren bei der Erstellung von Programmen in der Praxis helfen können.
Ein paar Worte über den MQL5-Assistenten (Indikatormodus)
Ich hoffe, Sie haben sich bereits mit der Erstellung von Indikatoren mithilfe des Assistenten aus dem ersten Artikel vertraut gemacht. Ich werde jedoch kurz auf die Schritte des Assistenten zurückkommen und mich dabei auf andere Aspekte konzentrieren als bisher.
Das erste Fenster des Assistenten enthält keine neuen Informationen im Vergleich zum ersten Artikel, daher werde ich es überspringen. Das zweite Fenster ist zwar auch nicht neu, macht aber jetzt mehr Sinn, da Sie mit den über Eingabeparameter verwalteten globalen Einstellungen vertraut sind. Manchmal ist es intuitiver, diese Parameter direkt im Assistenten zu konfigurieren. Zur Erinnerung: Parameternamen können beliebig sein. Ich empfehle jedoch, das Präfix inp_ hinzuzufügen, um sie im Code leicht unterscheiden zu können.
Abbildung 5. Hinzufügen von Programmparametern mit dem Assistenten
Der nächste wichtige Schritt ist die Wahl des Formats der Methode OnCalculate (Abbildung 6). OnCalculate ist die Hauptmethode eines jeden Indikators in der Sprache MQL5. Sie wird jedes Mal aufgerufen, wenn das Terminal diesen Indikator berechnet. Je nach Auswahl im Assistenten kann die Funktion verschiedene Eingabeparameter annehmen.
Abbildung 6. Fenster für die Auswahl der Methode OnCalculate
Die Auswahl der oberen Option ist für die meisten Situationen geeignet, da die Funktion in diesem Fall automatisch generierte Arrays akzeptiert: open, high, low, close, volume, time. Der Nutzer kann diese Arrays nach Bedarf bearbeiten.
Es gibt jedoch spezielle Fälle, in denen wir dem Nutzer die Möglichkeit geben wollen, zu wählen, welche Kurve für die Berechnung eines bestimmten Indikators verwendet werden soll. Ein gleitender Durchschnitt kann z. B. auf dem Hoch oder Tief oder dem Ergebnis eines vorhandenen Indikators basieren. In diesem Fall sollten wir die untere Option wählen, und dann wird ein Array mit Kurvendaten an OnCalculate übergeben. Dann erscheint im kompilierten Indikator im Fenster der Eingabeparameter eine spezielle Registerkarte zur Auswahl einer Kurve. Abbildung 7 zeigt die Startfenster der vorgefertigten Indikatoren für die Optionen Upper und Lower.
Abbildung 7. Vergleich von Indikator-Startfenstern mit verschiedenen OnCalculate-Formen
Und der letzte Punkt, auf den ich Ihre Aufmerksamkeit lenken möchte, ist der Assistent. Im letzten Schritt des Dialogfensters können Sie bei der Erstellung eines Indikators wählen, wie genau der Indikator angezeigt werden soll (Abbildung 8). Um den Indikator anzuzeigen, müssen Sie einen Puffer hinzufügen, d.h. ein spezielles Array, das Daten für die Darstellung des Indikators enthält. Es stehen verschiedene Darstellungsformen zur Verfügung, von einfachen Linien (z. B. gleitende Durchschnitte) bis hin zu mehrfarbigen Histogrammen und Kerzen. Bei fortgeschrittener Anpassung können Sie sogar nutzerdefinierte Grafiken zeichnen (was allerdings über den Funktionsumfang des Assistenten hinausgeht).
Abbildung 8. Parameter für die Darstellung von Indikatoren
Wenn Sie möchten, dass der Indikator etwas zeichnet und gleichzeitig die Ergebnisse seiner Arbeit für andere Programme (z. B. Expert Advisors) leicht zugänglich sind, müssen Sie auf jeden Fall mindestens einen Puffer hinzufügen.
Ein Puffer ist einfach ein Zahlenfeld, in dem die von unserem Indikator berechneten Daten für jede Kerze gespeichert werden. Im Allgemeinen sind diese Daten nicht unbedingt für die grafische Darstellung gedacht. Manchmal werden in Puffern Farben oder Zwischenberechnungsdaten gespeichert, die für Berechnungen in anderen Puffern benötigt werden.
Eine manuelles Hinzufügen ist möglich, erfordert aber zusätzliche (wenn auch einfache) Einstellungen. Anfängern empfehle ich dringend, den Assistenten für die Erstellung von Puffern zu verwenden.
Wenn Sie Linien zeichnen, benötigen Sie in der Regel mindestens einen Puffer für jede zu berechnende Kurve. Zum Zeichnen von Pfeilen benötigt man in den meisten Fällen mindestens zwei Puffer: einen für jede Richtung. Obwohl Sie mit nur einem Pfeil auskommen können, wenn der Pfeil nur für einen berechneten Wert für eine Kerze verwendet wird, ohne die Richtung anzuzeigen. In komplexeren Fällen können zusätzliche Puffer erforderlich sein (wird in späteren Artikeln behandelt). Im Moment sind die bereitgestellten Informationen ausreichend.
Lassen Sie uns nun einen Indikator erstellen
- Name: InsideOutsideBar,
- Ein Parameter: inp_barsTypeSelector, Typ int, Standardwert 0,
- Obere Form von OnCalculate (mit der Liste der Arrays) im dritten Fenster des Assistenten,
- Zwei Darstellungspuffer im Abschnitt „Plots“ mit den Namen Up und Down und dem Zeichnungstyp „Arrow“.
Vom Assistenten erzeugter Indikatorcode
Wenn alles im vorherigen Abschnitt richtig gemacht wurde, sollten Sie den folgenden Code erhalten:
//+------------------------------------------------------------------+ //| InsideOutsideBar.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 #property indicator_buffers 2 #property indicator_plots 2 //--- plot Up #property indicator_label1 "Up" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrMediumPurple #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- input parameters input int inp_barsTypeSelector=0; //--- indicator buffers double UpBuffer[]; double DownBuffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,159); PlotIndexSetInteger(1,PLOT_ARROW,159); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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 16. Von MQL5 Assistenten (Wizard) erzeugter Indikatorcode
Der erste Block ist wie üblich der Kopf, der die Parameter enthält. Dann folgen Standardparameter zur Beschreibung des Autors. Danach folgt eine Zeile, die angibt, dass der Indikator im Chart-Fenster platziert werden soll. Und die beiden Zeilen am Ende dieses Blocks sind eine Nachricht an den Compiler, dass unser Indikator zwei Puffer für Berechnungen und zwei Puffer für das Rendering hat (wie Sie verstehen, handelt es sich in diesem Fall um dieselben Puffer, obwohl im allgemeinen Fall die Anzahl der Puffer unterschiedlich sein kann):
#property indicator_buffers 2 #property indicator_plots 2
Beispiel 17. Beschreibung der Indikatorpuffer für die Berechnung und der Darstellung
Der nächste Block von Parametern beschreibt, wie das von den einzelnen Puffern Erstellte aussehen soll:
//--- plot Up #property indicator_label1 "Up" // Display name of the buffers #property indicator_type1 DRAW_ARROW // Drawing type - arrow #property indicator_color1 clrMediumPurple // Arrow color #property indicator_style1 STYLE_SOLID // Line style - solid #property indicator_width1 1 // Line width (arrow size) //--- plot Down #property indicator_label2 "Down" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumPurple #property indicator_style2 STYLE_SOLID #property indicator_width2 1
Beispiel 18. Parameter der Darstellung
Der nächste Codeblock beschreibt die Eingabeparameter unseres Indikators. In unserem Fall gibt es nur einen Parameter: inp_barsTypeSelector:
//--- input parameters input int inp_barsTypeSelector=0;
Beispiel 19. Eingabeparameter des Indikators
Dann kommt der Code, der eine Variable für die Puffer für die Darstellung erstellt. Beachten Sie, dass dieser Puffer ein dynamisches Array mit Elementen vom Typ double verwendet. Dabei handelt es sich um eine Reihe von Preisniveaus, auf deren Grundlage die Indikatorkurve erstellt wird:
//--- indicator buffers double UpBuffer[]; double DownBuffer[];
Beispiel 20. Variable für den Puffer der Darstellung
Dann folgt die Beschreibung von zwei Funktionen: OnInit und OnCalculate.
OnInit ist genau die gleiche Funktion wie in Skripten. Sie wird auf genau dieselbe Weise unmittelbar nach dem Start des Indikators - vor der Ausführung aller anderen Funktionen - gestartet und genau einmal ausgeführt. In diesem Fall hat der Assistent zwei Aufrufe der Standardfunktion SetIndexBuffer geschrieben, die dazu dient, unser Array mit dem Zeichenpuffer des Indikators zu verbinden. Außerdem wurden den beiden Pfeilen mit der Funktion PlotIndexSetInteger Icons zugewiesen. Die Funktion OnInit nimmt keine Parameter entgegen und gibt den Erfolgsstatus der Initialisierung zurück (in diesem Fall immer „successful“ (Erfolg).
OnCalculate ist, wie ich bereits schrieb, eine Funktion, die bei jedem Tick oder jedes Mal, wenn andere Programme versuchen, unseren Indikator zu verwenden, aufgerufen wird. Jetzt ist sie leer. In dieser Funktion werden wir die wichtigsten Aktionen unseres Indikators beschreiben.
Die Funktion benötigt eine Reihe von Variablen als Parameter:
- Gesamtzahl der Balken im Chart (rates_total);
- Anzahl der zuvor durch den Indikator berechneten Balken (prev_calculated). Beim ersten Start ist der Wert von prev_calculated gleich Null, aber die Implementierung der Funktion, die der Assistent erstellt hat, gibt am Ende der Arbeit die Gesamtzahl der Balken eines bestimmten Ticks zurück, und zu Beginn des nächsten Starts übergibt das Terminal diesen Wert als prev_calculated an die Funktion. Dies ist übrigens die Grundlage für einen der Algorithmen zur Bestimmung einer neuen Kerze: Wenn rates_total==prev_calculated, dann ist die Kerze dieselbe und es muss nichts erneut berechnet werden, aber wenn es nicht gleich sind, dann beginnen wir die nächsten Schritte;
- Mehrere Arrays: Preise, Zeit, Volumen, Spreads... Dies ist erforderlich, wenn Sie Ihre eigenen Indikatoren erstellen. Beachten Sie, dass Arrays per Referenz übergeben werden (da Arrays nicht per Wert übergeben werden können), aber sie haben einen Modifikator const, der dem Compiler mitteilt, dass die Daten in diesen Arrays unveränderlich sind.
Jetzt können wir mit der Programmierung unseres Indikators beginnen.
Programmierung des Indikators
Als erstes möchte ich die Parameter des Indikators ein wenig anpassen. Es gibt nur zwei Arten von Formationen: entweder ein „inside bar“ oder „outside bar“. Lassen Sie uns eine globale Aufzählung unmittelbar nach den Präprozessoranweisungen erstellen:
enum BarsTypeSelector { Inside = 0, // Inside bar Outside = 1 // Outside bar };
Beispiel 21. Enumeration für Berechnungsarten
Ändern wir den Standardtyp und -wert für unseren Eingabeparameter:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside;
Beispiel 22. Ändern des Standardtyps und -werts eines Eingabeparameters
Lassen Sie uns nun die Möglichkeiten zur Anpassung unseres Indikators erweitern. Fügen wir die Möglichkeit hinzu, das Aussehen des Symbols und den sichtbaren Abstand des Symbols von der Leiste zu ändern. Zu diesem Zweck fügen wir manuell (damit Sie sich daran erinnern, dass das Hinzufügen von Parametern sehr einfach ist) zwei weitere Variablen in den Bereich der Eingabeparameter ein:
//--- input parameters input BarsTypeSelector inp_barsTypeSelector=Inside; input int inp_arrowCode=159; input int inp_arrowShift=5;
Beispiel 23. Zusätzliche Variablen für Erscheinungsbild-Einstellungen
Ich möchte Sie daran erinnern, dass die Eingabeparameter selbst keinen Einfluss auf die Funktionsweise des Indikators haben; sie müssen nur irgendwo verwendet werden. Um die Änderung anzuwenden, müssen wir die OnInit ändern:
int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,UpBuffer,INDICATOR_DATA); SetIndexBuffer(1,DownBuffer,INDICATOR_DATA); //--- setting a code from the Wingdings charset as the property of PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(1,PLOT_ARROW,inp_arrowCode); PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, inp_arrowShift); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -inp_arrowShift); //--- return(INIT_SUCCEEDED); }
Beispiel 24. Änderungen an der OnInit-Funktion
Lassen Sie uns nun mit der Programmierung der wichtigsten Aktionen in der Funktion OnCalculate fortfahren. Um den Code ein wenig kompakter zu gestalten, überspringe ich den Titel dieser Funktion und beschreibe gleich den Code, der die nützliche Arbeit leistet.
/******************************************************************************************** * * * Attention! All arrays in the code are NOT series, so we have larger numbers on the right. * * * ********************************************************************************************/ //--- Description of variables int i, // Loop counter start; // Initial bar for historical data const int barsInPattern = 2; // For proper preparation we need to know, // how many bars will be involved in one check // I've added a constant to avoid the presence of // "magic numbers" that come from nowhere, // we could use the #define directive instead of the constant //--- Check the boundary conditions and set the origin if(rates_total < barsInPattern) // If there are not enough bars to work return(0); // Do nothing if(prev_calculated < barsInPattern+1)// If nothing has been calculated yet { start = barsInPattern; // Set the minimum possible number of bars to start searching } else { start = rates_total — barsInPattern; // If the indicator has been running for some time, // Just count the last two bars } //--- To avoid strange artifacts on the last candlestick, initialize the last elements of the arrays // with EMPTY_VALUE UpBuffer[rates_total-1] = EMPTY_VALUE; DownBuffer[rates_total-1] = EMPTY_VALUE; //--- for(i = start; i<rates_total-1; i++) // Start counting from the starting position // and continue until there are no more closed barsя // (If we needed to include the last - unclosed - bar, // we would set the condition i<=rates_total-1) { // First, let's clear both indicator buffers (initialize to an empty value) UpBuffer[i] = EMPTY_VALUE; DownBuffer[i] = EMPTY_VALUE; if(inp_barsTypeSelector==Inside) // If the user wants to display inside bars { // Check if the current bar is inside if(high[i] <= high[i-1] && low[i] >= low[i-1]) { // And if yes, we mark the previous (larger) candlestick UpBuffer[i-1] = high[i-1]; DownBuffer[i-1] = low[i-1]; } } else // If outside bars are needed { // Check if the current bar is outside if(high[i] >= high[i-1] && low[i] <= low[i-1]) { // Mark the current candlestick if necessary UpBuffer[i] = high[i]; DownBuffer[i] = low[i]; } } } //--- return value of prev_calculated for the next call return(rates_total);
Beispiel 25. Hauptteil der Funktion OnCalculate
Meines Erachtens ist der Code ausreichend kommentiert, und es hat keinen besonderen Sinn, ihn weiter zu analysieren. Wenn ich falsch liege, stellen Sie bitte Fragen in den Kommentaren. Dieser Code sollte in die Funktion OnCalculate eingefügt werden, und zwar zwischen den öffnenden und schließenden geschweiften Klammern, wobei alles gelöscht wird, was vorher da war (ich habe den Return-Operator in das Beispiel aufgenommen - in der letzten Zeile). Der vollständige Arbeitscode des Indikators ist dem Artikel beigefügt.
Abbildung 9 zeigt die Funktionsweise dieses Indikators. Auf der linken Seite sind die Balken, die den „inside bar“ vorausgingen, rosa markiert, und die „outside bar“ sind auf der rechten Seite markiert.
Abbildung 9. Wie der InsideOutsideBar-Indikator funktioniert. Die „inside bars“ sind links, die „outside bars“ rechts
Schlussfolgerung
Sobald Sie die in diesem Artikel beschriebenen Operatoren beherrschen, werden Sie in der Lage sein, die meisten in MQL5 geschriebenen Programme zu verstehen - und Ihre eigenen Algorithmen beliebiger Komplexität zu schreiben. Im Grunde genommen lässt sich die gesamte Programmierung auf die einfachsten Operationen wie Arithmetik oder Zuweisung, die Auswahl aus mehreren Optionen (if oder switch) und die Wiederholung des gewünschten Fragments in der erforderlichen Anzahl (Schleifen) reduzieren. Funktionen bieten eine Möglichkeit, Operationen bequem zu organisieren, während Objekte eine bequeme Anordnung von Funktionen und ihren externen Daten ermöglichen.
Jetzt sind Sie definitiv keine Anfängerin mehr. Von diesem Punkt aus ist es noch ein weiter Weg zum MQL5-Profi. So müssen Sie beispielsweise herausfinden, wie Sie bereits erstellte Indikatoren in neuen Entwicklungen wiederverwenden und wie Sie Expert Advisors erstellen können, die für Sie handeln. Es gibt auch viele Funktionen der MQL5-Plattform, die es wert sind, studiert zu werden, um alles tun zu können, was Sie wollen: grafische Schnittstellen für Ihre Expert Advisors, ungewöhnliche Indikatoren wie „Heiken“ oder „Renko“, bequeme Dienste und profitable Strategien für den automatischen Handel... Deshalb wird diese Reihe fortgesetzt. Im nächsten Artikel werden wir uns so detailliert wie möglich mit der Erstellung von Expert Advisors befassen. Dann werde ich meine Vision von OOP im Kontext von MQL5 vorstellen. Damit ist der „allgemeine“ Teil abgeschlossen, und danach werden wir uns wahrscheinlich mit einigen der Plattformfunktionen und der Standard-Codebibliothek befassen, die mit dem Terminal im Include-Ordner geliefert wird.
Liste der früheren Artikel der Reihe:
- MQL5 beherrschen, vom Anfänger zum Profi (Teil I): Erste Schritte der Programmierung
- MQL5 beherrschen, vom Anfänger zum Profi (Teil II): Grundlegende Datentypen und Verwendung von Variablen
- MQL5 beherrschen vom Anfänger bis zum Profi (Teil IIII): Komplexe Datentypen und Include-Dateien
- MQL5 beherrschen vom Anfänger bis zum Profi (Teil IV): Über Arrays, Funktionen und globale Terminalvariablen
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/15499





- 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.