MQL5 meistern – Vom Anfänger zum Profi (Teil VII): Grundlagen des Debuggings von MQL-Anwendungen
Einleitung
Der vorherige Artikel dieser Reihe behandelte die Grundlagen der Erstellung von Expert Advisors in MQL5. Ich hatte vor, diesen Artikel zuverlässigen Expert Advisors für den Market zu widmen, die mit abfangbaren Fehlern, einschließlich Serverantworten, umgehen können. Und ich hatte sogar schon begonnen, ihn zu schreiben.
Nachdem die Leute jedoch die vorherigen Artikel gelesen hatten, fingen sie an zu programmieren – und schickten mir Fragen per Privatnachricht. Natürlich waren das genau die Fragen, die Anfänger normalerweise stellen, und natürlich war ich bereit, sie zu beantworten (und bin es immer noch). Und während ich diese Fragen beantwortete, wurde mir sehr klar, dass ich, bevor ich zu fortgeschritteneren Programmierkonzepten übergehe, erst einmal erklären muss, wie Einsteiger Fehler im eigenen oder fremden Code beheben können. Also habe ich beschlossen, dass „zuverlässige Expert Advisors“ als Nächstes an der Reihe sind, und zunächst müssen wir über Debugging sprechen.
Jeder macht Fehler – manche öfter als andere… Wenn man sich mit etwas noch nicht auskennt, macht man viele typische Fehler, die ein Profi sofort erkennt und automatisch vermeidet. Aber auch Profis machen manchmal Fehler. Ihre Fehler sind oft einfach schwerer zu erkennen und haben schwerwiegendere Folgen. Und in diesem Sinne unterscheidet sich das Programmieren nicht von anderen menschlichen Tätigkeiten. Im Gegensatz zu vielen anderen Lebensbereichen lassen sich Programmierfehler jedoch relativ leicht beheben – vorausgesetzt, der Programmcode ist verfügbar und es steht genügend Zeit zur Verfügung.
Der Vorgang der Fehlerkorrektur in einem Programm wird als Debugging bezeichnet. Debugging ist fast schon ein unverzichtbarer Schritt beim Schreiben jedes Programms, das mehr als 100 Codezeilen umfasst. Es ist unmöglich, ohne Fehler zu leben, aber diese zu korrigieren, ist unser „heiliges“ Recht. Daher werde ich in diesem Artikel die Grundlagen des Debuggings von MQL5-Programmen mithilfe der in MetaEditor integrierten Werkzeuge erläutern. Ich glaube nicht, dass Profis hier irgendwelche großen neuen Erkenntnisse finden werden – schließlich richtet sich diese Reihe an Anfänger. Aber falls mir etwas entgeht oder ich etwas falsch verstanden habe – wie immer sind Kommentare von allen herzlich willkommen.
Fehlermeldungen analysieren
Die einfachsten Fehlerarten sind Tippfehler und Syntaxfehler. Wenn Sie beispielsweise einen in MQL4 geschriebenen Indikator umschreiben, müssen Sie wahrscheinlich einige Funktionsaufrufe wie `iMA` oder `ObjectFind` anpassen, da diese Namen zwar in beiden Sprachversionen vorkommen, jedoch eine unterschiedliche Anzahl von Parametern verwenden und oft Rückgabewerte mit unterschiedlicher Bedeutung liefern. Diese Fälle sind recht unkompliziert, da der Compiler sie automatisch erkennt und sofort detaillierte Informationen im entsprechenden Fenster anzeigt. Versuchen Sie zum Beispiel, den folgenden Code zu kompilieren:
//+------------------------------------------------------------------+ //| ErrorsResearching.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 int g_someInt; // No issues here //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping g_some_Int = 5; // There is an error here. The variable name is specified incorrectly //--- 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 next call return(rates_total); } //+------------------------------------------------------------------+
Beispiel 1. Code, der einen Kompilierungsfehler verursacht
Das Ergebnis der Kompilierung dieses Beispiels ist in Abbildung 1 dargestellt. Der Compiler meldet einen Fehler, da der Variablenname innerhalb der Funktion falsch angegeben ist.

Abbildung 1. Fehlermeldung (nicht deklarierte Variable)
Es ist sinnvoll, sich das in Abbildung 1 gezeigte Fenster genauer anzusehen, auch wenn seine Elemente offensichtlich sind. In der ersten Spalte wird der Fehler beschrieben. Außerdem wird links ein Hinweis angezeigt, zu welcher Kategorie der Fehler gehört. In diesem Fall:
- Warnungen werden mit einem gelben Dreieck gekennzeichnet; grundsätzlich können so gekennzeichnete Zeilen ignoriert werden, doch in manchen Fällen kann ihr Vorhandensein zu Fehlern in der Programmlogik führen, weshalb sie dennoch sorgfältig geprüft werden sollten;
- Fehler werden mit einem roten „Stopp“-Zeichen gekennzeichnet; diese machen die Kompilierung unmöglich;
- Neutrale Meldungen sind mit grauen Punkten gekennzeichnet; dabei handelt es sich nicht um Fehler, sondern sie vermitteln lediglich einige Informationen.
In der Spalte nach der Fehlerbeschreibung wird der Name der Datei angezeigt, in der der Fehler aufgetreten ist, und in den letzten beiden Spalten werden die Zeilen- bzw. Spaltennummern des Fehlers angegeben. All dies zusammen ergibt die genaue Stelle, an der der Compiler das Problem „sieht“. In den meisten Fällen reicht ein Doppelklick auf die entsprechende Zeile aus, um zu dieser Stelle zu gelangen.
Manchmal kann es vorkommen, dass der Compiler für einen einzelnen Fehler zwei oder mehr Meldungen ausgibt. Wenn beispielsweise die Zuweisungsoperation aus Beispiel 1 in den globalen Gültigkeitsbereich verschoben wird, wie in Beispiel 2 gezeigt:
#property indicator_chart_window int g_someInt; // No issues here g_someInt = 5; // There is an error here. Operations cannot be performed // (except for initialization) in the global scope
Beispiel 2. Versuch, im globalen Gültigkeitsbereich einen Wert zuzuweisen
Das Ergebnis des Versuchs, Beispiel 2 zu kompilieren, ist in Abbildung 2 dargestellt.

Abbildung 2. Ein einziger Fehler kann mehrere Meldungen auslösen
In diesem Fall versucht der Compiler, unsere Variable als neue Deklaration zu interpretieren. Allerdings fehlt in dieser Deklaration nicht nur der Variablentyp, sondern es gibt auch bereits eine Variable mit diesem Namen (die in der Zeile darüber deklariert wurde). Es liegt auf der Hand, dass man in diesem Fall lediglich die Zuweisungsanweisung wieder in eine beliebige Funktion verschieben muss, und beide Fehler verschwinden.
Dieses Beispiel gibt einen klaren Hinweis: Fehler sollten in der Regel einzeln, von oben nach unten, korrigiert werden, und nach jeder Korrektur sollten Sie versuchen, den Code zu kompilieren. Natürlich gilt diese Regel nicht strikt. Wenn Ihr Compiler also mehrere Meldungen desselben Typs ausgibt – beispielsweise zu unbekannten Variablen oder nicht deklarierten Funktionen –, können Sie diese natürlich alle auf einmal beheben.
Zum Abschluss dieses Abschnitts stelle ich ein sehr kurzes Glossar mit den wichtigsten Begriffen bereit – für diejenigen, deren Englischkenntnisse noch nicht ausreichen, um Fehlermeldungen problemlos zu verstehen. Dieses Glossar soll Ihnen ausschließlich dabei helfen, Compiler-Meldungen zu verstehen, wenn Sie gerade erst anfangen; es wird die Nutzung gewöhnlicher Wörterbücher wahrscheinlich nicht überflüssig machen, kann aber für Anfänger hilfreich sein, die noch nicht dazu gekommen sind, die Sprache gründlich zu erlernen.
| Englisch | Deutsch | Beschreibung |
|---|---|---|
| unexpected | unerwartet | Dies geschieht in der Regel, wenn eine Aktion oder ein wichtiges Zeichen – wie etwa ein Semikolon am Ende einer Anweisung – ausgelassen wird. In Beispiel 2 geht der Compiler beispielsweise davon aus, dass der Datentyp bei der Deklaration der Variablen weggelassen wurde. |
| undeclared | nicht deklariert | Das passiert in der Regel, wenn ein Programmierer einen Fehler im Variablennamen macht oder vergisst, eine Funktion zu deklarieren, die an anderer Stelle aufgerufen wird. |
| unbalanced | nicht paarig | Bezieht sich in der Regel auf Klammern. Angenommen, es gibt eine öffnende Klammer, aber keine schließende, oder umgekehrt. |
| missing | fehlend | Der Compiler konnte etwas Wichtiges nicht finden. |
| already | bereits | Meistens im Zusammenhang mit „existiert bereits“. |
| wrong | fehlerhaft, falsch | Dieser Fehler tritt am häufigsten in Meldungen auf, die auf eine falsche Anzahl von Parametern beim Aufruf einer Funktion hinweisen. |
| invalid | inkorrekt, unzulässig | Zum Beispiel führt der folgende Code int array_variable[]; array_variable = 5;zu einem Fehler „invalid array access“, also einem ungültigen Zugriff auf ein Array, weil ich versucht habe, eine Zahl nicht einem Array-Element, sondern direkt der Variablen zuzuweisen. |
Tabelle 1. Kurzes Glossar englischer Begriffe in Fehlermeldungen
Laufzeitfehler
Nachdem wir nun die Kompilierungsfehler behoben haben, beginnt die eigentliche Debugging-Arbeit erst richtig, denn als Nächstes müssen wir überprüfen, ob unser Programm korrekt funktioniert. Sehr oft überspringt ein Programmierer aus Unachtsamkeit entweder einen wichtigen Schritt beim Entwerfen eines Algorithmus, ändert wegen „Copy-Paste“ die falsche Variable oder führt die Schritte in der falschen Reihenfolge aus …
Tatsächlich enthält der Beispielcode in der letzten Zeile von Tabelle 1 zwei Fehler. Selbst wenn Sie in der zweiten Zeile dieses Codes eckige Klammern und die Elementnummer hinzufügen, funktioniert das Programm immer noch nicht. Die Sache ist die: Das Array array_variable ist als dynamisch deklariert, seine Größe ist jedoch nirgendwo festgelegt, sodass nichts darin gespeichert werden kann.
Leider lässt der Compiler diesen Fehler durchgehen, da formal gesehen alles der Syntax der Sprache entspricht. Zur Laufzeit gibt das Programm jedoch eine Fehlermeldung aus: „Der Arrayindex liegt außerhalb des gültigen Bereichs“ („array out of range“). Sie können diese Meldungen (sowie alle anderen Meldungen Ihres Programms) auf der Registerkarte „Experts“ des Terminals einsehen (Abbildung 3).

Abbildung 3. Laufzeitfehlermeldung
Übrigens treten Fehler vom Typ „Arrayindex außerhalb des gültigen Bereichs“ (wie in diesem Beispiel) sehr häufig auf. Die häufigsten Fälle sind:
- Preisdaten für einen bestimmten Zeitraum wurden noch nicht geladen, aber das Programm versucht, sie zu lesen;
- Der Arrayindex ist falsch angegeben; so könnte ein Programmierer beispielsweise vergessen, dass der letzte Index eines Arrays um eins kleiner ist als dessen Länge, wie in der dritten (auskommentierten) Zeile des folgenden Beispiels:
int testArray[3]={3,4,5}; int size = ArraySize(testArray); // Print (testArray[size]); // Error! The program is attempting to access a nonexistent array element Print (testArray[size-1]); // Everything is fine
Beispiel 3. Ein typischer Fall eines möglichen Fehlers durch einen falschen Arrayindex
Nicht jeder algorithmische Fehler führt zu kritischen Programmfehlermeldungen. Oftmals „zeichnet“ der Indikator einfach nicht, oder er zeichnet etwas völlig anderes als beabsichtigt; der Expert Advisor handelt nicht, selbst wenn in den historischen Daten eindeutig Handelssituationen zu erkennen sind; das Skript „friert“ so stark ein, dass man das Terminal neu starten muss … Und dann muss man die Fehler aufspüren. Im Folgenden werden die gängigsten Methoden beschrieben, die ich verwende, um sie zu finden.
Wo das Debugging beginnt
Dieser Abschnitt enthält Empfehlungen, die für erfahrene Programmierer so selbstverständlich sind, dass sie keinerlei Erklärung bedürfen. Allerdings machen Anfänger (nach den Personen zu urteilen, mit denen ich persönlich oder in Foren gesprochen habe) immer wieder dieselben Fehler, kommen an denselben Stellen immer wieder nicht weiter und stellen dieselbe Frage auf unterschiedliche Weise: „Wo fange ich an?“ Also …
Beginnen Sie ganz von vorn. Wenn es Ihr Code ist, dann kennen Sie ihn natürlich besser als jeder andere. Wenn es sich um den Code eines anderen handelt, versuchen Sie zu verstehen, was er tut. Verlieren Sie sich zunächst aber nicht in den Details. Verschaffen Sie sich zunächst einen groben Überblick.
- Denken Sie daran, dass bei Expert Advisors und Indikatoren alles mit der Funktion OnInit beginnt, während es bei Skripten und Diensten alles mit OnStart beginnt. Jede dieser Funktionen wird genau einmal ausgeführt – beim Start des Programms (obwohl Dienste in der Regel eine Endlosschleife enthalten, die bestimmte Ereignisse während des Handels verarbeitet).
- Bei Indikatoren wird bei jedem Tick die Funktion OnCalculate aufgerufen; bei Expert Advisors wird die Funktion OnTick aufgerufen. Zudem kann in Expert Advisors oder Indikatoren ein Timer eingerichtet werden, der in regelmäßigen Abständen von der Funktion OnTimer verarbeitet wird.
- Diese fünf Funktionen (OnInit, OnStart, OnCalculate, OnTick, OnTimer) sind Einstiegspunkte. Jede Analyse von fremdem Code sollte mit diesen Funktionen beginnen. Wenn diese Kommentare vorhanden sind, ist das ein großer Vorteil – sehen Sie sie sich einfach an. Es ist gut möglich, dass Kommentare dazu dienen, die wichtigsten Logikblöcke hervorzuheben. Wenn keine Kommentare vorhanden sind, versuchen Sie, sich schnell einen Überblick darüber zu verschaffen, welche Funktionen verwendet werden, und anhand ihrer Namen herauszufinden, was sie bewirken. Wenn der Code gut strukturiert ist und der Autor nicht alles in eine einzige Funktion packt – dann haben Sie Glück. Wenn nicht, ist das auch in Ordnung, obwohl das Debugging dann möglicherweise länger dauert. Versuchen Sie auf jeden Fall, die einzelnen Schritte nachzuvollziehen, die an der Ausführung des Algorithmus beteiligt sind – die relativ großen Blöcke wie „Parameter berechnen“, „Bedingungen prüfen“ (zum Beispiel, ob gezeichnet werden soll oder nicht), „alle Bars durchlaufen“ – und so weiter. Natürlich ist diese Liste bei weitem nicht vollständig und hängt stark von der jeweiligen Aufgabe ab.
- Sehen Sie sich die Liste der in dieser Datei definierten zusätzlichen Funktionen über die spezielle Schaltfläche in der Symbolleiste (
) oder über die Tastenkombination <Alt>+<M> an, und versuchen Sie anhand ihrer Namen herauszufinden, wofür sie gedacht sind; - Für Anfänger kann es äußerst hilfreich sein, an wichtigen Stellen Kommentare mit den Namen der Blöcke hinzuzufügen (falls solche Kommentare fehlen). Es ist außerdem sehr hilfreich, ein Flussdiagramm des Algorithmus zu zeichnen (zum Beispiel mithilfe der DRAKON-Sprache oder eines anderen Tools Ihrer Wahl). Ich sage es noch einmal: In dieser Phase ist es am besten, das Diagramm mit relativ großen Blöcken zu zeichnen.
- Wenn Sie einem Indikator einen Indikatorpuffer hinzugefügt haben und der Indikator plötzlich überhaupt nichts mehr zeichnet, prüfen Sie Folgendes:
- Achten Sie beim Zuordnen von Indikatorpuffern zu Arrays (Aufrufe der Funktion SetIndexBuffer) darauf, dass die Nummerierung nicht durcheinandergeraten ist; das heißt, alle Nummern müssen fortlaufend und lückenlos sein und bei 0 beginnen;
- Die Eigenschaft indicator_buffers gibt die Gesamtzahl der Puffer korrekt an, während die Eigenschaft indicator_plots die Anzahl der auf dem Bildschirm sichtbaren Puffer korrekt beschreibt;
- Die Eigenschaften indicator_type, indicator_color usw. sind ebenfalls korrekt nummeriert; ihre Nummerierung beginnt jedoch bei 1. Außerdem müssen Sie jeden angezeigten Puffer beschrieben haben, sei es über Eigenschaften oder über integrierte Funktionen.
Verwendung der Debug-Ausgabe
Die älteste Methode zur Fehlersuche besteht darin, an wichtigen Stellen im Programm Meldungen mit der Funktion Print (oder PrintFormat, je nachdem, was Sie bevorzugen) auszugeben.
Vor langer Zeit war dies die einzige Möglichkeit, herauszufinden, warum ein Programm nicht so funktionierte, wie es sollte. Doch auch heute noch ist diese Debugging-Methode beliebt, weil sie einfach und schnell ist und es außerdem ermöglicht, die Debug-Ausgabe als Dokument zu speichern, das man sogar unterwegs in öffentlichen Verkehrsmitteln oder auf einer Parkbank durchsehen kann.
Es versteht sich von selbst, dass solche Meldungen informativ sein und dem Programmierer klar anzeigen sollten, an welcher Stelle des Programms er sich gerade befindet und was dort genau geschieht. Häufig werden darin spezielle Zeichenkombinationen verwendet – solche, die im alltäglichen Gebrauch normalerweise nicht vorkommen (wie etwa „~~“ oder Ähnliches) –, um die Art der Meldung oder ihre Quelle anzugeben. Das Grundprinzip ihrer Verwendung ist sehr einfach: An wichtigen Stellen im Code werden Meldungen eingefügt, anhand derer Programmierer die Situation im laufenden Programm überwachen können.
Versuchen wir doch einmal, mithilfe von Meldungen die Probleme im folgenden Code zu verstehen und zu beheben:
void OnStart() { //--- int i; //--- Print("Program starting, 'i' hasn't initialized"); for(i=0;i<3;i++); { Print("In the cycle 'i="+i+"'"); } Print("After cycle 'i="+i+"'"); }
Beispiel 4. Der Code eines Programms, das nicht korrekt funktioniert.
Versuchen Sie als Übung einmal herauszufinden, wie viele Meldungen dieses Programm anzeigen wird – und welche genau. Ich empfehle Ihnen, diese Fragen zu beantworten, bevor Sie mit der Lektüre der folgenden Erläuterung beginnen.
Es ist anzunehmen, dass das Programm zum Zeitpunkt der Erstellung dieses Codes fünf Meldungen ausgeben sollte: zwei außerhalb der Schleife und drei innerhalb. Aber wird das in diesem Fall funktionieren? Prüfen wir es:
Program starting, 'i' hasn't initialized In the cycle 'i=3' After cycle 'i=3'
Beispiel 5. Programmausgabe aus Beispiel 4.
Das Ergebnis war unerwartet… Warum nur drei Meldungen? Warum wird die Zahl „3“ innerhalb der Schleife ausgegeben, obwohl die Zahlen doch von 0 bis 2 reichen sollten? Sollte die Zahl in der zweiten Zeile mit der Zahl in der dritten Zeile übereinstimmen? Denken wir darüber nach.
- Alle drei Phasen unseres Programms werden ausgeführt: vor der Schleife, innerhalb der Schleife und nach der Schleife – und das ist ermutigend.
- Die erste Phase läuft fehlerfrei ab.
- In der dritten Phase erhalten wir das erwartete Ergebnis, sodass wir davon ausgehen können, dass auch hier alles in Ordnung ist.
- In der zweiten Phase wird dieselbe Zahl ausgegeben wie in der dritten, was bedeutet, dass die Schleife funktioniert … wenn auch nicht ganz korrekt.
Aus dem Obigen können wir schließen, dass das Problem irgendwo zwischen dem for(i=0; …)-Ausdruck und der Ausgabe der benötigten Daten liegt. Schauen wir uns genauer an, was sich dort – dazwischen – befindet … Tatsächlich stehen dort eine geschweifte Klammer und ein Semikolon.
Die geschweiften Klammern sind ausgeglichen; andernfalls hätten wir anstelle der angezeigten Warnungen einen Kompilierungsfehler erhalten. Geschweifte Klammern dienen dazu, eine oder mehrere einfache Anweisungen zu einem Block zusammenzufassen, und in diesem Fall gelingt das auch; alles zwischen den Klammern ist in Ordnung: Sie enthalten genau das, was wir brauchen – nicht mehr und nicht weniger.
Ein Semikolon kennzeichnet das Ende einer Anweisung. In diesem Fall … eine leere. Es stellt sich heraus, dass die Schleife in diesem Fall ihre Variable dreimal unnötigerweise um eins erhöht und erst danach die von der Schleife berechnete Variable dem Benutzer angezeigt wird. Es sieht so aus, als würde alles funktionieren, wenn wir dieses Semikolon entfernen. Prüfen wir es:
Program starting, 'i' hasn't initialized In the cycle 'i=0' In the cycle 'i=1' In the cycle 'i=2' After cycle 'i=3'
Beispiel 6. Ausgabe des korrigierten Beispiels
Es hat geklappt! Das Problem wurde gelöst.
Nach einem solchen Debugging ist der Code jedoch mit zahlreichen Meldungen übersät, die für den Endnutzer absolut nutzlos sind. Wenn Sie die App nicht für sich selbst entwickeln, müssen Sie wahrscheinlich alle diese Meldungen nach Abschluss des Debuggings suchen und löschen. Allerdings ist es umständlich, dies mit einer normalen Suche zu tun (auch wenn es manchmal notwendig ist). Wenn erfahrene Programmierer daher Debug-Ausgaben in ihren Code einfügen müssen, kombinieren sie diese oft mit speziellen Präprozessordirektiven.
Wenn unsere Aufgabe darin bestünde, die aufeinanderfolgenden Werte des Schleifenzählers auszugeben, und die anderen Meldungen in der fertigen Anwendung nicht benötigt würden, sähe der korrekte und praktische Code wie folgt aus:
#define DEBUG //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- int i; //--- #ifdef DEBUG Print("Program starting, 'i' hasn't initialized"); #endif for(i=0;i<3;i++) // NO semicolon!!! { Print("In the cycle 'i="+i+"'"); } #ifdef DEBUG Print("After cycle 'i="+i+"'"); #endif }
Beispiel 7. Verwendung von Direktiven zur bedingten Kompilierung.
In diesem Beispiel habe ich ein Makro namens DEBUG deklariert und anschließend in den Programmteilen, die nur während des Debuggings benötigt werden, den Präprozessor angewiesen, den Code zur Ausgabe von Meldungen nur dann in die kompilierte Datei aufzunehmen, wenn das Makro DEBUG vorhanden ist. Das Vorhandensein eines Makros wird mithilfe der Direktive #ifdef überprüft. Mit anderen Worten: Alles, was zwischen #ifdef und #endif steht, wird nicht kompiliert, wenn im Programm kein Makro namens DEBUG vorhanden ist. Auf diese Weise können Sie diese Abschnitte einfach durch Löschen oder Hinzufügen der ersten Zeile aktivieren oder deaktivieren – und zwar an einer einzigen Stelle in Ihrem Projekt, selbst wenn das Projekt mehrere Dateien enthält.
Nun, zum Abschluss dieses Kapitels möchte ich noch anmerken, dass dieser spezielle Fehler (aus Beispiel 4) noch leichter hätte gefunden werden können, wenn wir bereits während der Kompilierung sofort auf die Warnung geachtet hätten, die uns der Compiler mitteilen wollte:
empty controlled statement found ErrorsResearching.mq5 23 19
Beispiel 8. Compiler-Warnung bezüglich einer leeren gesteuerten Anweisung
Diese Meldung besagt, dass der Compiler ein „empty controlled statement“ gefunden hat. Wenn eine solche Meldung erscheint, befindet sich höchstwahrscheinlich ein überflüssiges Semikolon an der angegebenen Stelle. Unbedingt prüfen!
Verwendung des interaktiven Debuggers in MQL5-Programmen
MetaEditor verfügt, wie viele moderne IDEs, über einen integrierten interaktiven Debugger. Das Wort „interaktiv“ bedeutet, dass Sie während des Debugging-Vorgangs mit dem Debugger interagieren können. Das Grundprinzip dieser Interaktion besteht darin, dass das Programm in einem speziellen Modus kompiliert wird – zum Debugging. In diesem Modus können Sie die Ausführung des Programms direkt im Editor Zeile für Zeile verfolgen und sehen, wie sich Variablen an wichtigen Stellen im Programm ändern.
Wenn jedoch keine besonderen Maßnahmen ergriffen werden, läuft das Programm zu schnell ab, und wir werden nichts sehen. Deshalb fügen wir im Editor spezielle Markierungen ein, die als „Haltepunkte“ (englisch: Breakpoint) bezeichnet werden. Sie ermöglichen es, innerhalb der Funktionen unseres Programms anzuhalten und die Werte der Variablen zum Zeitpunkt der Unterbrechung anzuzeigen. Sobald alle Variablenwerte am Haltepunkt klar sind, können Sie entweder die nächste Anweisung ausführen oder die Ausführung bis zum nächsten Haltepunkt „fortsetzen“.
Während des Debugging-Vorgangs selbst erscheint mir alles intuitiv, aber zur Sicherheit werde ich beschreiben, auf welche Schaltflächen man klicken muss.
- Haltepunkte werden in der Regel entweder ganz zu Beginn der Ausführung oder nach einer Unterbrechung gesetzt.
- Um einen Haltepunkt zu setzen oder zu entfernen, setzen Sie den Cursor auf eine beliebige Zeile innerhalb der Funktion und verwenden Sie eine der folgenden Methoden:
-
Doppelklick in die Spalte, die die Zeilennummern des aktuellen Programms enthält;

Abbildung 4. Einen Haltepunkt per Doppelklick aktivieren bzw. deaktivieren
- die Taste <F9>;
- Verwenden Sie das Kontextmenü der gewünschten Zeile oder wählen Sie im Hauptmenü „Debug“ -> „Haltepunkt de-/aktivieren“ (Abbildung 5).

Abbildung 5. Der Menüpunkt „Haltepunkt umschalten“.
- Wenn Sie mehrere Haltepunkte gesetzt haben, diese aber nicht mehr benötigen, können Sie sie über das Menü „Debug“ alle auf einmal löschen (in Abbildung 5 befindet sich der entsprechende Menüpunkt im Menü auf der rechten Seite, direkt unterhalb des roten Rahmens) oder durch Drücken der Tastenkombination <Ctrl>+<Shift>+<F9>.
Mithilfe des in Abbildung 6 gezeigten Bereichs der Symbolleiste können Sie im Debug-Modus mit dem Programm arbeiten.

Abbildung 6. Ein für das Debugging vorgesehener Bereich der Symbolleiste.
Die linke Schaltfläche (
) startet das Debugging anhand historischer Daten, während die rechte Schaltfläche (
) das Debugging anhand von Live-Marktdaten startet. Über die blaue Schaltfläche wird der Strategietester geöffnet, über die grüne Schaltfläche direkt im Terminal ein neuer Chart. Hier sind einige Unterschiede zwischen den Modi:
- Wenn Sie verstehen möchten, wie das zu debuggende Programm funktioniert, d. h. wenn Sie ein schrittweises Debugging an der Nullbar durchführen möchten, gibt es keinen grundlegenden Unterschied zwischen den Schaltflächen, obwohl natürlich weiterhin ein großer Unterschied zwischen dem Strategietester und dem Terminal besteht;
- Im Strategietester können Sie außer dem laufenden Programm keine anderen Analysewerkzeuge verwenden;
- Allerdings ist dort eine Parameteroptimierung für das Programm verfügbar;
- Sie können Schritt für Schritt verfolgen, wie sich das Programm über einen längeren Zeitraum verhält;
- Die Daten des Terminals werden beim ersten Laden schneller geladen;
- In der Regel ist es praktisch, Programme mit Live-Marktdaten zu debuggen, wenn sie keinen Handel ausführen, insbesondere Indikatoren und Skripte;
- Aber automatisierten Handel debuggt man besser anhand historischer Daten, schon allein, um Verluste zu vermeiden.
Unabhängig davon, welchen Debugging-Modus Sie wählen: Sobald das Programm gestartet ist, stehen Ihnen die in Abbildung 5 dargestellten Schaltflächen für die schrittweise Codeausführung (auf der rechten Seite der Leiste) zur Verfügung. Jede dieser Schaltflächen führt den nächsten Schritt des Programms aus, jedoch jeweils auf unterschiedliche Weise.
- Die Schaltfläche ganz links (
) führt den nächsten Schritt aus, indem sie in jede Funktion hineinspringt, deren Quellcode verfügbar ist (nicht jedoch in Standardfunktionen des Terminals). - Die mittlere Schaltfläche (
) führt jede Funktion in einem einzigen Schritt aus, als wäre sie, außen bleibend, ein einziger Befehl, unabhängig davon, ob es sich um eine integrierte Terminalfunktion oder eine benutzerdefinierte Funktion handelt. - Mit der Schaltfläche ganz rechts in dieser Gruppe (
) können Sie die aktuelle Funktion in einem Schritt bis zum Ende ausführen und zu der Stelle zurückkehren, von der aus die Funktion aufgerufen wurde.
Die Schaltfläche (
) beendet die aktuelle Debugging-Sitzung.
Die Schaltfläche (
) hält das laufende Programm an einer unvorhersehbaren Stelle an (in der Regel am Anfang der Funktion „OnCalculate“ oder anderer Funktionen, die vom Terminal in regelmäßigen Abständen aufgerufen werden).
Im unteren Bereich des Debugging-Fensters wird links der gesamte Aufrufstapel angezeigt: die letzten Funktionen, die aufgerufen wurden, bevor die aktuelle Funktion erreicht wurde. Rechts befindet sich eine Liste aller Variablen in der aktuellen Funktion. Die Werte in diesem Fenster werden automatisch aktualisiert, sobald sie sich im Programm ändern.

Abbildung 7. Unterer Bereich im Debugging-Modus: Liste der Variablen und Aufrufstapel
Sie können der rechten Seite dieses Bereichs auch Ausdrücke zur Auswertung hinzufügen, beispielsweise Variablennamen (z. B. globale Variablen) oder Operationen wie arithmetische, logische oder bitweise Operationen. Um einen Ausdruck zur Liste hinzuzufügen, haben Sie folgende Möglichkeiten:
- den Ausdruck im Programmtext markieren und <Shift>+<F9> drücken;
- mit der rechten Maustaste auf den ausgewählten Ausdruck klicken und im Kontextmenü „Überwachung hinzufügen“ (englisch: „Add Watch“) auswählen;
- auf eine leere Zeile in der Variablenliste doppelklicken und den Ausdruck direkt eingeben.
Um den Inhalt eines Arrays oder einer Struktur anzuzeigen, doppelklicken Sie darauf. Es ist jedoch ratsam, in Indikatoren nicht zu versuchen, Arrays wie „high“ oder „time“ aufzuklappen: Manchmal kann dies dazu führen, dass selbst ein leistungsstarker Computer für eine ganze Weile einfriert. Es ist besser, den Namen des Arrays einfach mit einem Index in einer eigenen Zeile einzugeben, zum Beispiel time[i].
Wenn Sie ein bestimmtes Element einer Struktur oder Klasse anzeigen möchten, funktioniert die Autovervollständigung hervorragend: Sie können einen Punkt ans Ende des Namens setzen oder <Ctrl>+<Space> drücken – dann sollte der übliche Popup-Hinweis erscheinen. Das Hinzufügen von Funktionsaufrufen ist leider nicht möglich.
Als Übung schlage ich vor, sich einen Standardindikator wie beispielsweise ZigZag einmal genauer anzusehen. Öffnen Sie die Datei „Indicators -> Examples -> ZigZag.mq5“ und führen Sie in Ihrem Editor alle in den folgenden Abschnitten beschriebenen Schritte aus.
-
Als Erstes müssen wir herausfinden, über welche benutzerdefinierten Funktionen dieser Indikator verfügt. Um zu einer Funktion zu springen, klicken Sie einfach auf deren Namen in der Liste (Abbildung 8).

Abbildung 8. Liste der Funktionen im ZigZag-Indikator
-
Setzen wir nun die Haltepunkte. Beginnen wir mit der Funktion OnInit, in der wir am Anfang einen Haltepunkt setzen (zum Zeitpunkt des Verfassens dieses Artikels ist das bei mir Zeile 41), und setzen wir in OnCalculate Haltepunkte am Anfang (Zeile 66) sowie an der Stelle, an der die Suche nach Extremwerten beginnt (Zeile 118), um zu sehen, wie sich die Schaltflächen (
) und (
) in ihrer Funktionsweise unterscheiden. -
Jetzt können wir unser Programm ausführen. In diesem Fall funktioniert jede der Schaltflächen auf der linken Seite. Nehmen wir an, wir klicken auf die grüne Schaltfläche (
). -
Sobald wir den ersten Haltepunkt in der Funktion OnInit erreichen, können wir sie kurz durchgehen, sicherstellen, dass sie einfach aufgebaut ist und alles klar ist, und dann bedenkenlos auf die Schaltfläche (
) klicken, um zur nächsten Funktion zu wechseln. - Wenn Sie die Empfehlungen befolgt haben, erfolgt der nächste Halt in der Funktion OnCalculate, und zwar in der ersten Zeile. Es lohnt sich, diese Funktion genauer unter die Lupe zu nehmen, da sie viele Lösungen enthält, die für Entwickler von Indikatoren nützlich sind. Probieren Sie also ruhig ein wenig herum und verwenden Sie die Schaltflächen (
) oder (
), um zum nächsten Schritt zu gelangen. - Um die for-, while- und do-…-while-Schleifen zu erkunden, können Sie sie ein paar Mal von Anfang bis Ende Schritt für Schritt durchlaufen und dann Haltepunkte nach dem Ende jeder Schleife setzen und die Ausführung der Schleifen mit den Schaltflächen (
) oder (
) fortsetzen, bis Sie Zeile 118 erreichen. - Durchlaufen Sie die folgende Schleife mindestens einmal mit der Schaltfläche (
) (In Funktion springen (Step Into)) und beobachten Sie, wie sich die Einträge im Aufrufstapel (siehe Abbildung 7, links) ändern. - Führen Sie den zweiten Schleifendurchlauf mit der Schaltfläche (
) „Funktion überspringen“ (Step Over) aus, um den Unterschied zu sehen. - Der restliche Code des Indikators bringt in Bezug auf Debugging-Techniken nichts Neues. Nutzen Sie daher die bereits erlernten Techniken, um herauszufinden, was geschieht.
- Um das Debugging zu beenden, können Sie auf die Schaltfläche
(Debugging beenden) klicken.
Profiling
Wenn auf einem Chart mehrere Programme laufen und es viele solcher Charts gibt, ist es wichtig, dass jedes Programm schnell läuft. Manchmal verwendet ein Programmierer jedoch ineffiziente Algorithmen, und obwohl die Aufgabe gelöst wird, kann das Programm dennoch in der Praxis zu langsam sein. Dies gilt insbesondere für leistungsschwache Geräte, etwa ältere Netbooks, die bei Tradern manchmal noch in einwandfreiem Zustand zu finden sind.
Mithilfe von Profiling lässt sich feststellen, welche Funktion das Programm ausbremst (oder, genauer gesagt, wie sich die Zeit auf die einzelnen Funktionen verteilt). Während dieses Vorgangs misst MetaEditor die Ausführungszeit jeder Funktion in unserem Programm und zeigt sie, sofern diese Zeit nennenswert ist, in Form eines separaten Diagramms an.
Versuchen wir einmal, für denselben ZigZag-Indikator ein Profiling durchzuführen. Dazu können Sie entweder die entsprechende Symbolleiste (Abbildung 9) oder das Menü „Debug“ (Abbildung 10) verwenden. Da Profiling nicht sehr häufig benötigt wird, haben die Entwickler dafür keine Tastenkombination vorgesehen.
![]()
Abbildung 9. Profiling-Bedienfeld.

Abbildung 10. Der Abschnitt „Profiling“ im Menü „Debug“.
Wie beim Debugging kann Profiling entweder mit Live-Marktdaten oder mit historischen Daten ausgeführt werden (über die grüne bzw. blaue Schaltfläche). Der zweite Befehl (
) beendet das Profiling (in den Abbildungen 9 und 10 als inaktiv dargestellt).
Der gesamte Vorgang ist einfach und unkompliziert:
- Starten Sie den Profiling-Vorgang mit der Schaltfläche „Start“;
- Warten Sie einen Moment, damit das Programm Zeit hat, bis zum Ende durchzulaufen;
- Beenden Sie den Vorgang mit der Schaltfläche „Stopp“;
- Untersuchen Sie die Charts und korrigieren Sie, was sich korrigieren lässt (im Fall von ZigZag gibt es wahrscheinlich nichts zu korrigieren …).
Als ich für den Standard-ZigZag-Indikator ein Profiling durchführte (auf einem dieser wirklich alten Netbooks), erhielt ich am Ende die folgende Tabelle (Abbildung 11):

Abbildung 11. Profiling-Ergebnis für den Standard-ZigZag-Indikator.
Es stellte sich heraus, dass „SetIndexBuffer“ den Löwenanteil der Zeit in „OnInit“ in Anspruch nimmt, während „Highest“ und „Lowest“ so schnell sind, dass sie in diesem Vergleich gar nicht auftauchen. Wie erwartet erwies sich „OnCalculate“ als die langsamste Funktion – schließlich findet hier tatsächlich der Großteil der Arbeit statt, und sie wird viel häufiger aufgerufen als „OnInit“. Möglicherweise ließe sich die Performance dieser Funktion erheblich verbessern.
Die Entwickler bei MetaQuotes haben den Code dieses Indikators sehr gut optimiert, aber … Vielleicht entwickeln ja gerade Sie einen schnelleren Algorithmus?
Fazit
Das Debugging von Anwendungen ist eine der anspruchsvollsten Phasen im Lebenszyklus der Softwareentwicklung. Bei manchen Projekten kann dies bis zu 70 % der Zeit in Anspruch nehmen, manchmal sogar noch mehr. Selbst Projekte, die erfolgreich laufen und Tausende Nutzer haben, können Fehler enthalten. Was soll man da erst über den Code von Anfängern sagen? Ich hoffe jedoch, dass die in diesem Artikel beschriebenen Debugging-Techniken Programmieranfängern dabei helfen werden, ihre eigenen Fehler leichter zu finden und fremde Algorithmen effektiver zu untersuchen.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18075
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Von den Grundlagen bis zur Mittelstufe: Direkter Dateizugriff (II)
Von den Grundlagen zur Mittelstufe: Objektereignisse (II)
Von den Grundlagen bis zur Mittelstufe: Navigation durch die Sandbox
Neuronale Netze im Trading: Effektive Merkmalsextraktion für präzise Klassifikation (Objekterstellung)
- 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.