English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Fehler finden und Protokollierung

Fehler finden und Protokollierung

MetaTrader 5Beispiele | 8 März 2016, 15:32
1 188 0
Дмитрий Александрович
Дмитрий Александрович

Einleitung

Hallo lieber Leser!

In diesem Beitrag betrachten wir uns verschiedene Möglichkeiten, wie man in Expert Advisors/Skripts/Indikatoren Fehler finden kann und sehen uns Methoden der Protokollierung an. Zudem lernen Sie ein kleines Programm zur Ansicht von Protokollen kennen - LogMon.

Fehler zu finden ein wesentlicher Bestandteil des Programmierens. Wenn Sie einen neuen Code-Block schreiben, müssen Sie stets prüfen, ob er auch korrekt funktioniert und keine logischen Fehler enthält. Einen Fehler in Ihrem Programm stellen Sie auf drei verschiedene Arten fest:

  1. Einschätzung des Endergebnisses
  2. Schrittweise Fehlersuche
  3. Schreiben von logischen Schritten in ein Protokoll

Wir sehen uns jede dieser Arten nun genauer an.

1. Einschätzung des Endergebnisses

Mit dieser Methode analysieren wir das Ergebnis der Arbeit eines Programms oder einen Teil seines Codes. Nehmen wir z.B. eine einfachen Code her, der zur besseren Verständlichkeit einen offensichtlichen Fehler enthält:

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
     }
   Alert(intArray[9]);

  }

Erstellen Sie ihn und lassen ihn laufen und auf dem Bildschirm erscheint eine "0". Nach Analyse der Ergebnisse erwarten wir eine "9", also können wir davon ausgehen, das unser Programm nicht so arbeitet, wie wir uns das wünschen. Diese Methode zur Fehlersuche ist gängig und kann nicht herausfinden, wo sich der Fehler befindet. Sehen wir uns nun die zweite Möglichkeit an, das Fehlersuch-Feature.

2. Schrittweise Fehlersuche

Mit dieser Methode können Sie exakt herausfinden, an welcher Stelle die Programmlogik verletzt wurde. Setzen Sie in MetaEditor innerhalb der 'for' Schleife einen Haltepunkt, beginnen die Fehlersuche und warten auf die i-Variable:

Fehlerbehebung

Anschließend klicken Sie auf "Fehlersuche wieder aufnehmen" und zwar für so lange, wie eir den gesamten Arbeitsvorgang des Programms betrachten. Wir sehen, dass, Wenn die "i" Variable den Wert "8" erreicht, wir die Schleife verlassen. Also können wir darauf schließen, dass der Fehler in der Zeile:

for(int i=0;i<9;i++)

Nämlich dann, Wenn der Wert von "i" und die Zahl "9" verglichen werden. Korrigieren Sie die Zeile "i<9" " zu "i<10" oder zu "i<=9" und überprüfen die Ergebnisse. Okay, jetzt bekommen wir die "9" - so wie wir es wollten Mit Hilfe des Fehlersuchprogramms haben wir erfahren, wie das Programm während seiner Laufzeit agiert und konnten das Problem beheben. Nachteile dieser Methode:

  1. Es ist nicht klar, wo der Fehler auftrat - eher reine Intuition.
  2. Man muss die Watchlist mit Variablen ergänzen und diese nach jedem Schritt kontrollieren..
  3. Diese Methode kann keine Fehler während der Ausführung des fertigen Programms finden, wie z.B. EA-Handel bei einem echten oder Demo-Konto.

Sehen wir uns nun die dritte Möglichkeit an, Fehler zu finden.

3. Schreiben von logischen Schritten in ein Protokoll

Mit Hilfe dieser Methode können wir wichtige Schritte unseres Programms aufzeichnen. Zum Beispiel: Initialisierung, einen Abschluss machen, Indikatorberechnung, usw. Wir erweitern unser Skript mit einer Codezeile. Wir drucken nämlich den Wert der i Variable auf jede Wiederholung:

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<9;i++)
     {
      intArray[i]=i;
      Alert(i);
     }
   Alert(intArray[9]);

  }

Laufen lassen und die Protokollausgabe ansehen - Ziffern "0 1 2 3 4 5 6 7 8 0" und überlegen, woran das liegen könnte und das Skript korrigieren - genauso wie vorhin.

Vor- und Nachteile dieser Methode der Fehlersuche:

  1. + Das Programm muss nicht Schritt für Schritt gefahren werden - spart also eine Menge Zeit.
  2. + Es ist oft schnell ersichtlich, wo der Fehler liegt.
  3. + Sie können auch während das Programm läuft weiter protokollieren.
  4. + Sie können das Protokoll für spätere Analyse- und Vergleichszwecke speichern (z.B. wenn Sie es in eine Datei schreiben. vgl. unten).
  5. - Aufgrund der zusätzlichen Operatoren, die Daten ins Protokoll schreiben, wird der Quellcode größer.
  6. - Erhöhte Programmlaufzeit (hauptsächlich wichtig für Optimierung).

Zusammenfassung:

Die erste Methode der Fehlersuche kann den aktuellen Standort des Fehlers nicht ermitteln. Sie wird hauptsächlich aufgrund ihrer Geschwindigkeit verwendet. Die zweite Methode - die schrittweise Fehlersuche - erlaubt Ihnen zwar, den exakten Standort des Fehlers zu finden, braucht dazu aber unglaublich lange. Und wenn Sie am gewünschten Codeblock vorbeirauschen, dann müssen Sie nochmal von vorne anfangen.

Und die dritte Methode schließlich - Aufzeichnungen logischer Schritte in ein Protokoll - erlaubt Ihnen die rasche Analyse der Arbeit des Programms und speichert das Ergebnis. Beim Schreiben der Ereignisse Ihres Expert Advisors/Indikatoren/Skripts in ein Protokoll, können Sie leicht einen Fehler finden und müssen dabei nicht nach den richtigen Bedingungen Ausschau halten, unter denen ein Fehler passiert. Außerdem müssen Sie Ihr Programm auch nicht stundenlang auf Fehler untersuchen. Sehen wir uns jetzt die Arten der Protokollierung im Einzelnen an und vergleichen sie miteinander. Zudem zeige ich Ihnen die Variante, die am bequemsten und schnellsten ist.


Wann braucht man denn überhaupt ein Protokoll?

Eine Protokollierung braucht man aus folgenden Gründen:

  1. Fehlerhaftes Verhalten des Programms.
  2. Zu lange Programmlaufzeit (Optimierung).
  3. Kontrolle der Laufzeit (gedruckte Benachrichtigungen zu Eröffnen/Schließen von Positions, ausgeführten Handlungen, usw.).
  4. Erlernen von MQL5, z.B. Drucken von Arrays.
  5. Überprüfen des Expert Advisors vor einer Meisterschaft, usw.


Methoden der Protokollierung

Es gibt viele Möglichkeiten Meldungen in ein Protokoll zu schreiben - einige werden verwendet und andere nur in ganz bestimmten Fällen. So ist es z.B. nicht immer nötig, ein Protokoll via E-Mail oder via ICQ zu senden.

Die folgende Aufzählung zeigt die im MQL5 Programmieren gängigsten Methoden:

  1. Einsatz der Comment() Funktion
  2. Einsatz der Alert() Funktion
  3. Einsatz der Print() Funktion
  4. Schreiben eines Protokolls in eine Datei mit Hilfe der FileWrite() Funktion

Jetzt geben ich Ihnen Beispiele für jede Methode mit den Quellcodes und beschreibe ihre jeweiligen Features. Diese Quellcodes sind eher abstrakt, daher werden wir uns nicht weit vom Eigentlichen entfernen.

Einsatz der Comment() Funktion

void OnStart()
  {
//---
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Comment("Variable i: ",i);
      Sleep(5000);
     }
   Alert(intArray[9]);
  }

Oben links sehen wir den aktuellen Wert der "i" Variable:

Comment()

Also können wir den aktuellen Status des laufenden Programms kontrollieren. Und das sind die Vor- und Nachteile:

  1. + Man sieht den Wert sofort.
  2. - Einschränkungen bei der Ausgabe.
  3. - Man kann keine bestimmte Meldung auswählen.
  4. - Man sieht die Arbeit nicht über den gesamten Verlauf der Laufzeit, sondern nur im aktuellen Status.
  5. - Reichlich langsame Methode.
  6. - Ungeeignet für kontinuierliche Überwachung der Arbeit, da man ja ständig die Anzeige im Auge behalten muss.

Die Comment() Funktion ist sinnvoll zur Anzeige des aktuellen Status des Expert Advisors. Zum Beispiel: "2 Abschlüsse öffnen" oder "GBRUSD Posten 0,7 kaufen".

Einsatz der Alert() Funktion

Diese Funktion zeigt Meldungen in einem extra Fenster mit akustischer Benachrichtigung an. Das Beispiel des Codes:

void OnStart()
  {
//---
   Alert("Start script");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Alert("Variable i:", I);
      Sleep(1000);
     }
   Alert(intArray[9]);
   Alert("Stop script");
  }
Ergebnis des ausgeführten Codes:

Alert()

Ahhh, jetzt sind wir überglücklich: alles ist sofort erkennbar und wird uns sogar noch akustisch gemeldet. Und da sind die Vor- und Nachteile:

  1. + Alle Meldungen werden durchgängig aufgezeichnet.
  2. + Akustische Benachrichtigung.
  3. + Alles wird in die "Terminal_dir\MQL5\Logs\data.txt" Datei geschrieben.
  4. - Alle Meldungen von allen Skripts/Expert Advisors/Indikatoren werden in ein Protokoll geschrieben.
  5. - Funktioniert im Strategie-Tester nicht.
  6. - Bei häufigem Aufruf kann es das Terminal für lange Zeit "blockieren" (z.B. bei Aufruf bei jeder Kursschwankung oder beim Drucken eines Arrays in der Schleife).
  7. - Meldungen können nicht gruppiert werden.
  8. - Umständliche Ansicht der Protokolldatei.
  9. - Kann Meldungen in keinem anderen Ordner speichern, außer dem Standard-Datenordner.

Punkt 6 ist bei echtem Handel extrem kritisch, insbesondere beim 'Scalping' oder einer Veränderung des Stop Loss. Das sind schon eine ganze Menge Nachteile - und man kann sicher noch weitere finden - doch für jetzt reicht das erst einmal.

Einsatz der Print() Funktion

Diese Funktion schreibt Protokollmeldungen in ein spezielles Fenster, das sog. "Experts". Und das ist der Code:

void OnStart()
  {
//---
   Print("Старт скрипта");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      Print("Variable i: ",i);
     }
   Print(intArray[9]);
   Print("Stop script");
  }

Print()

Wie Sie sehen wird diese Funktion genauso wie die Alert() Funktion aufgerufen, doch jetzt werden alle Meldungen ohne Benachrichtigungen in die "Experts"-Registerkarte und in die "Terminal_dir\MQL5\Logs\data.txt" Datei geschrieben. Sehen wir uns ihre Vor- und Nachteile an:

  1. + Alle Meldungen werden durchgängig aufgezeichnet.
  2. + Alles wird in die "Terminal_dir\MQL5\Logs\data.txt" Datei geschrieben
  3. + Geeignet für kontinuierliche Überwachung der Arbeit des Programms.
  4. - Alle Meldungen von allen Skripts/Expert Advisors/Indikatoren werden in ein Protokoll geschrieben.
  5. - Meldungen können nicht gruppiert werden.
  6. - Umständliche Ansicht der Protokolldatei.
  7. - Kann Meldungen in keinem anderen Ordner speichern, außer dem Standard-Datenordner.

Diese Methode wird wahrscheinlich von den meisten MQL5 Programmieren benutzt, da sie ziemlich schnell und für große Mengen an Protokollaufzeichnungen gut geeignet ist.

Das Schreiben eines Protokolls in eine Datei

Kommen wir nun zur letzen Methode der Protokollierung - das Schrieben von Meldungen in Dateien. Diese Methode ist weitaus komplizierter als alle vorherigen, gewährleistet jedoch bei ordentlicher Vorbereitung eine gute Schreibgeschwindigkeit und bequeme Ansicht des Protokolls und liefert zudem Benachrichtigungen. Dies ist der einfachste Code für das Schreiben eines Protokolls in eine Datei:

void OnStart()
  {
//--- Open log file
   int fileHandle=FileOpen("log.txt",FILE_WRITE|FILE_TXT|FILE_SHARE_READ|FILE_UNICODE); 
   FileWrite(fileHandle,"Start script");
   int intArray[10];
   for(int i=0;i<10;i++)
     {
      intArray[i]=i;
      FileWrite(fileHandle,"Variable i: ",i);
      // Sleep(1000);
     }
   FileWrite(fileHandle,intArray[9]);
   FileWrite(FileHandle,"Stop script");
   FileClose(fileHandle); // close log file
  }

Lassen Sie sie ablaufen, browsen zum "Terminal_dir\MQL5\Files" Ordner und öffnen die "log.txt" Datei im Text-Editor Das ist der Inhalt:

In Datei protokollieren

Wie Sie sehen ist die Ausgabe konsequent und einheitlich - ohne extra Meldungen - und enthält nur das, was wir in die Datei geschrieben haben. Ihre Vor- und Nachteile:

  1. + Schnell.
  2. + Schreibt nur was wir wollen.
  3. + Man kann Meldungen von unterschiedlichen Programmen in unterschiedliche Dateien schreiben, um so eine Überschneidung von Protokollen auszuschließen.
  4. - Keine Benachrichtigungen über neue Meldungen im Protokoll.
  5. - Unmöglich, eine bestimmte Meldung oder Kategorie unterscheiden zu können.
  6. - Das Öffnen des Protokolls nimmt viel zeit in Anspruch - man muss zuerst zum Ordern browsen und dort die Datei öffnen.

Zusammenfassung:

Alle oben vorgestellten Methoden haben ihre Nachteile, doch können einige entsprechend angepasst werden. Die ersten drei Protokollierungsmethoden sind unflexibel - ihr Verhalten können wir kaum beeinflussen. Die letzte Methode jedoch, Schreiben eines Protokolls in eine Datei, ist noch die flexibelste. Wir können nämlich entscheiden, wie und wann Meldungen protokolliert werden. Wenn Sie allerdings eine einzelne Zahl anzeigen wollen, dann ist es ganz klar einfacher, mit den ersten drei Methoden zu arbeiten. Wenn Sie jedoch ein kompliziertes Programm mit einer Menge Codes haben, wird es schwierig, dies ohne Protokollierung zu verwenden.


Neuer Protokollierungsansatz

Jetzt erkläre und zeige ich Ihnen, wie Sie die Protokollierung in eine Datei verbessern können und gebe Ihnen ein praktisches Tool zur Ansicht von Protokollen zur Hand. Voilà: LogMon - eine Windows-Anwendung, die ich in C++ geschrieben habe.

Beginnen wir mit dem Schreiben der Klasse, die die gesamte Protokollierung übernimmt, nämlich:

  1. Den Standort der Datei halten, in welche das Protokoll und andere Protokolleinstellungen geschrieben werden.
  2. Protokolldateien je nach gegebenem Namen und Datum/Uhrzeit erzeugen.
  3. Die übertragenen Parameter in die Protokollzeile übertragen.
  4. Der Protokollmeldung die Uhrzeit hinzufügen.
  5. Der Meldung eine Farbe geben.
  6. Eine Meldungskategorie hinzufügen.
  7. Meldungen im Cache ablegen und sie einmal pro n-Sekunden oder jede n-Meldungen schreiben.

Da MQL5 ein Objekt-orientierte Programmiersprache ist und, was die Geschwindigkeit angeht, sich nicht sehr von C++ unterscheidet, schreiben wir eine Klasse ganz speziell für MQL5. Und los gehts.


Implementierung der Klasse für das Schreiben von Protokollen in eine Datei

Wir legen unsere Klasse in eine separate, mit einzuschließende Datei mit der Erweiterung 'mqh'. Dies ist die allgemeine Struktur der Klasse.

CLogger

Und das ist der Quellcode mit detaillierten Kommentaren:

//+------------------------------------------------------------------+
//|                                                      Clogger.mqh |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"

// Max size of cache (quantity)
#define MAX_CACHE_SIZE   10000
// Max file size in megabytes
#define MAX_FILE_SIZEMB 10
//+------------------------------------------------------------------+
//|   Logger                                                         |
//+------------------------------------------------------------------+
class CLogger
  {
private:
   string            project,file;             // Name of project and log file
   string            logCache[MAX_CACHE_SIZE]; // Cache max size
   int               sizeCache;                // Cache counter
   int               cacheTimeLimit;           // Caching time
   datetime          cacheTime;                // Time of cache last flush into file
   int               handleFile;               // Handle of log file
   string            defCategory;              // Default category
   void              writeLog(string log_msg); // Writing message into log or file, and flushing cache
public:
   void              CLogger(void){cacheTimeLimit=0; cacheTime=0; sizeCache=0;};    // Constructor
   void             ~CLogger(void){};                                               // Destructor
   void              SetSetting(string project,string file_name,
                                string default_category="",int cache_time_limit=0); // Settings
   void              init();                   // Initialization, open file for writing
   void              deinit();                 // Deinitialization, closing file
   void              write(string msg,string category="");                                         // Generating message
   void              write(string msg,string category,color colorOfMsg,string file="",int line=0); // Generating message
   void              write(string msg,string category,uchar red,uchar green,uchar blue,
                           string file="",int line=0);                                             // Generating message
   void              flush(void);              // Flushing cache into file

  };
//+------------------------------------------------------------------+
//|  Settings                                                        |
//+------------------------------------------------------------------+
void CLogger::SetSetting(string project_name,string file_name,
                        string default_category="",int cache_time_limit=0)
  {
   project=project_name;             // Project name
   file=file_name;                   // File name
   cacheTimeLimit=cache_time_limit;  // Caching time
   if(default_category=="")          // Setting default category
     {  defCategory="Comment";   }
     else
     {defCategory = default_category;}
  }
//+------------------------------------------------------------------+
//|  Initialization                                                  |
//+------------------------------------------------------------------+
void CLogger::init(void)
  {
   string path;
   MqlDateTime date;
   int i=0;
   TimeToStruct(TimeCurrent(),date);                            // Get current time
   StringConcatenate(path,"log\\log_",project,"\\log_",file,"_",
                     date.year,date.mon,date.day);              // Generate path and file name
   handleFile=FileOpen(path+".txt",FILE_WRITE|FILE_READ|
                       FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);  // Open or create new file
   while(FileSize(handleFile)>(MAX_FILE_SIZEMB*1000000))        // Check file size
     {
      // Open or create new log file
      i++;
      FileClose(handleFile);
      handleFile=FileOpen(path+"_"+(string)i+".txt",
                          FILE_WRITE|FILE_READ|FILE_UNICODE|FILE_TXT|FILE_SHARE_READ);
     }
   FileSeek(handleFile,0,SEEK_END);                             // Set pointer to the end of file
  }
//+------------------------------------------------------------------+
//|   Deinitialization                                               |
//+------------------------------------------------------------------+
void CLogger::deinit(void)
  {
   FileClose(handleFile); // Close file
  }
//+------------------------------------------------------------------+
//|   Write message into file or cache                               |
//+------------------------------------------------------------------+
void CLogger::writeLog(string log_msg)
  {
   if(cacheTimeLimit!=0)  // Check if cache is enabled
     {
      if((sizeCache<MAX_CACHE_SIZE-1 && TimeCurrent()-cacheTime<cacheTimeLimit)
         || sizeCache==0) // Check if cache time is out or if cache limit is reached
        {
         // Write message into cache
         logCache[sizeCache++]=log_msg;
        }
      else
        {
         // Write message into cache and flush cache into file
         logCache[sizeCache++]=log_msg;
         flush();
        }

     }
   else
     {
      // Cache is disabled, immediately write into file
      FileWrite(handleFile,log_msg);
     }
   if(FileTell(handleFile)>(MAX_FILE_SIZEMB*1000000)) // Check current file size
     {
      // File size exceeds allowed limit, close current file and open new
      deinit();
      init();
     }
  }
//+------------------------------------------------------------------+
//|   Generate message and write into log                            |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category="")
  {
   string msg_log;
   if(category=="")                // Check if passed category exists
     {   category=defCategory;   } // Set default category

// Generate line and call method of writing message
   StringConcatenate(msg_log,category,":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Generate message and write into log                           |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,color colorOfMsg,string file="",int line=0)
  {
   string msg_log;
   int red,green,blue;
   red=(colorOfMsg  &Red);           // Select red color from constant
   green=(colorOfMsg  &0x00FF00)>>8; // Select green color from constant
   blue=(colorOfMsg  &Blue)>>16;     // Select blue color from constant
                                     // Check if file or line are passed, generate line and call method of writing message
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "file: ",file,"   line: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Generate message and write into log                           |
//+------------------------------------------------------------------+
void CLogger::write(string msg,string category,uchar red,uchar green,uchar blue,string file="",int line=0)
  {
   string msg_log;

// Check if file or line are passed, generate line and call method of writing message
   if(file!="" && line!=0)
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",
                        "file: ",file,"   line: ",line,"   ",msg);
     }
   else
     {
      StringConcatenate(msg_log,category,":|:",red,",",green,",",blue,
                        ":|:",TimeToString(TimeCurrent(),TIME_SECONDS),"    ",msg);
     }
   writeLog(msg_log);
  }
//+------------------------------------------------------------------+
//|    Flush cache into file                                         |
//+------------------------------------------------------------------+
void CLogger::flush(void)
  {
   for(int i=0;i<sizeCache;i++) // In loop write all messages into file
     {
      FileWrite(handleFile,logCache[i]);
     }
   sizeCache=0; // Reset cache counter
   cacheTime=TimeCurrent(); // Set time of reseting cache
  }
//+------------------------------------------------------------------+

Erzeugen Sie die mit einzuschließende Datei (.mqh) im MetaEditor, kopieren den Quellcode der Klasse uns speichern ihn unter dem Namen "CLogger.mqh" ab. Sehen wir uns nun jede Methode genauer an und auch wie die Klasse verwendet wird.

Verwendung der CLogger Klasse

Um mit Hilfe dieser Klasse mit der Aufzeichnung von Meldungen in das Protokoll zu beginnen, müssen wir die Klassendatei in Expert Advisor/Indikator/Skript mit aufnehmen:

#include <CLogger.mqh>

Dann müssen wir ein Objekt dieser Klasse erzeugen:

CLogger logger;

Wir führen dann alle Handlungen mit dem "Logger" Objekt aus. Nun müssen wir Einstellungen durch Aufrufen der "SetSetting()" Methode anpassen und in diese Methode den Projekt- und den dateienamen übertragen. Es gibt da auch noch zwei optionale Parameter - den Namen der Standardkategorie und die Cache-Dauer (in Sekunden), in der Cache gespeichert wird, bevor er in eine Datei geschrieben wird. Wenn Sie "0" angeben, werden alle Meldungen sofort in die Datei geschrieben.

SetSetting(string project,             // Project name
           string file_name,           // Log file name
           string default_category="", // Default category
           int cache_time_limit=0      // Cache lifetime in seconds
           );

Beispiel eines Aufrufs:

logger.SetSetting("MyProject","myLog","Comment",60);

Als Ergebnis werden alle Meldungen in die Datei "Client_Terminal_dir\MQL5\Files\log\log_MeinProjekt\log_myLog_date.txt" geschrieben, die Standardkategorie heißt "Kommentar" und die Cache-Dauer beträgt 60 Sekunden. Dann müssen Sie die init() Methode zum Öffnen/Erzeugen der Protokolldatei aufrufen. Ein Beispiel dieses Aufrufs ist einfach, da Sie hier keine Parameter übertragen müssen:

logger.init();

Diese Methode generiert den Pfad und den Namen der Protokolldatei, öffnet sie und prüft, ob sie die Maximalgröße nicht übersteigt. Sollte ihre Größe den zuvor gesetzten konstanten Wert übersteigen, wird eine andere Datei geöffnet und mit ihrem Namen eine 1 verknüpft. Dann wird die Größe nochmals geprüft, bis die Datei mit der passenden Größe geöffnet wird.

Der Pointer wird dann ans Ende der Datei verschoben. Und jetzt ist das Objekt fertig und kann das Protokoll schreiben. Die Schreiben-Methode haben wir übergangen, denn so können wir verschiedene Meldungsstrukturen, ein Beispiel des Aufrufs der Schreiben-Methode und das Ergebnis in der Datei einrichten:

// Write message with default caegory
logger.write("Test message");
// Write message with "Errors" category
logger.write("Test message", "Errors");
// Write message with "Errors" category, that will be highlighted with red color in LogMon
logger.write("Test message", "Errors",Red);
// Write message with "Errors" category, that will be highlighted with red color in LogMon
// Also message will contain current file name and current line
logger.write("Test message", "Errors",Red,__FILE__,__LINE__);
// Write message with "Errors" category, that will be highlighted with GreenYellow color in LogMon
// But now we specify each color independently as: red, green, blue. 0-black, 255 - white
logger.write("Test message", "Errors",173,255,47);
// Write message with "Errors" category, that will be highlighted with GreenYellow color in LogMon
// But now we specify each color independently as: red, green, blue. 0-black, 255 - white
// Also message will contain current file name and current line
logger.write("Test message", "Errors",173,255,47,__FILE__,__LINE__);

Die Protokolldatei enthält die folgenden Zeilen:

Comment:|:23:13:12    Test message
Errors:|:23:13:12    Test message
Errors:|:255,0,0:|:23:13:12    Test message
Errors:|:255,0,0:|:23:13:12    file: testLogger.mq5   line: 27   Test message
Errors:|:173,255,47:|:23:13:12    Test message
Errors:|:173,255,47:|:23:13:12    file: testLogger.mq5   line: 29   Test message

Sie sehen, das ist alles ziemlich einfach. Wenn Sie nun die write() Methode mit den erforderlichen Parametern irgendwo aufrufen, wird die Meldung in die Datei geschrieben. Am Ende Ihres Programms müssen Sie den Aufruf von zwei Methoden einfügen - flush() und deinit().

logger.flush();  // Forcibly flush cache to hard disk
logger.deinit(); // Close the log file

Hier ist ein Beispiel des Skripts, das die Zahlen in einer Schleife in das Protokoll schreibt:

//+------------------------------------------------------------------+
//|                                                   testLogger.mq5 |
//|                                                             ProF |
//|                                                          http:// |
//+------------------------------------------------------------------+
#property copyright "ProF"
#property link      "http://"
#property version   "1.00"
#include <Сlogger.mqh>
CLogger logger;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+

void OnStart()
  {
//---
   logger.SetSetting("proj","lfile");      // Settings
   logger.init();                          // Initialization
   logger.write("Start script","system");  
   for(int i=0;i<100000;i++)               // Write 100000 messages to the log
     {
      logger.write("log: "+(string)i,"Comment",100,222,100,__FILE__,__LINE__);
     }
   logger.write("Stop script","system"); 
   logger.flush();                         // Flush buffer
   logger.deinit();                        // Deinitialization
  }
//+------------------------------------------------------------------+

Das Skript wurde in 3 Sekunden ausgeführt und hat 2 Dateien erzeugt:

Protokolldateien

Dateiinhalte:

Inhalte der Protokolldatei

Also alle 100000 Meldungen. Wie Sie sehen funktioniert alles reichlich schnell hier. Sie können diese Klasse verändern, neue Features hinzufügen oder sie optimieren.


Ebene der Meldungsausgabe

Wenn Sie ein Programm schreiben, müssen Sie unterschiedliche Arten von Meldungen anzeigen:

  1. Schwerwiegender Fehler (Programm verhält sich nicht ordentlich)
  2. Benachrichtigungen über nicht schwerwiegende Fehler, Handels-Operationen, usw. (das Programm erfährt einen temporären Fehler, oder das Programm hat eine wichtige Handlung ausgeführt, über die der Anwender informiert werden muss)
  3. Information zur Fehlersuche (Inhalte von Arrays und Variablen sowie weitere Informationen, die bei der echten Arbeit nicht notwendig sind).

Es empfiehlt sich auch, eine Anpassung vornehmen zu können, welche Meldungen man drucken möchte, ohne den Quellcode zu verändern. Dies implementieren wir als einfache Funktion und verwenden dazu weder Klassen noch Methoden.

Wir deklarieren den Variablenparameter, der die Ebene der Meldungsausgabe speichert. Je größer die Zahl in der Variable ist, umso mehr Meldungskategorien werden angezeigt. Wenn Sie die Meldungsausgabe komplett deaktivieren wollen, geben sie ihr den Wert "-1".

input int dLvl=2;

Hier kommt der Quellcode der Funktion, der nach der Erzeugung des Objekts der CLogger Klasse deklariert werden muss.

void debug(string debugMsg,             // Message text
          int lvl        )              // Message level
{
   if (lvl<=dLvl)                       // Compare message level with level of messages output
   {
       if (lvl==0)                      // If message is critical (level = 0)
       {logger.write(debugMsg,"",Red);} // mark it with red color
       else
       {logger.write(debugMsg);}        // Else print it with default color
   }
}

Ein Beispiel: wir legen für die allerwichtigsten Meldungen die Ebene "0" fest und für die unwichtigsten irgendeine Nummer (aufsteigend, bei 0 beginnend):

debug("Error in Expert Advisor!",0);      // Critical error
debug("Stop-Loss execution",1);      // Notification
int i = 99;
debug("Variable i:"+(string)i,2); // Debugging information, variable contents


Leichtes Ansehen von Protokollen mit Hilfe von LogMon

Ok. Jetzt haben wir Protokolldateien mit Tausenden von Zeilen. Doch Informationen zu finden, ist dort keine leichte Sache. Sie sind nämlich nicht in Kategorien unterteilt und unterscheiden sich auch nicht groß voneinander. Diese Problem habe ich zu lösen versucht, indem ich nämlich ein Programm zur Ansicht von durch die CLogger Klasse generierte Protokolle, geschrieben habe. Ich stelle also jetzt kurz das LogMon Programm vor, das mit Hilfe der WinAPI in C++ geschrieben wurde. Deshalb ist es so schnell und ziemlich klein. Das Programm ist absolut kostenlos.

Zur Arbeit mit diesem Programm müssen Sie:

  1. Es in den Order "Client_Terminal_dir\MQL5\Files\" kopieren und laufen lassen - für normale Verwendung.
  2. Es in den Order "Agents_dir\Agent\MQL5\Files\" kopieren und laufen lassen - für Tests oder Optimierung Optimierung.

Das Hauptfenster des Programms sieht so aus:

LogMon Hauptfenster

Das Hauptfenster enthält die Werkzeugleiste und das Fenster mit der Strukturansicht. Um ein Element aufzuziehen, einfach mit der linken Maustaste mit Doppelklick draufgehen. Ordner in der Liste - sind ie Projekte, die sich im Ordner "Client_Terminal_dir\MQL\Files\log\" befinden. Mit Hilfe der SetSetting() Methode können Sie den Projektnamen in der CLogger Klasse einrichten Die Dateien in der Liste der Ordner - sind alles die Protokolldateien. Die Meldungen in den Protokolldateien sind in Kategorien unterteilt, die Sie in der write() Methode angegeben haben, Zahlen in Klammern - ist die Anzahl der Meldungen in dieser Kategorie.

Sehen wir uns nun die Schaltflächen in der Werkzeugleiste einzelne, von links nach rechts, an.

Schaltfläche zum Löschen der Projekt- oder Protokolldatei, sowie zum Zurücksetzen der Strukturansicht

Wenn Sie da drauf drücken, geht folgendes Fenster auf:

Löschen, leeren

Sobald Sie "Löschen und Leeren" drücken, werden alle Threads des Scannens von Dateien/Ordnern gestoppt, die Strukturansicht wird zurückgesetzt und Sie werden aufgefordert, die ausgewählte Datei oder das Projekt zu löschen (zum Auswählen einfach auf ein Element klicken - Sie müssen nicht das Kästchen auswählen!). Die Schaltfläche "Zurücksetzen" stoppt alle Threads des Scannens von Dateien/Ordnern und bereinigt die Strukturansicht.

Schaltfläche zur Ansicht des Dialogfelds "Über"

Präsentiert eine kurze Information über das Programm und seinen Verfasser.

Schaltfläche, um das Programmfenster immer als oberstes zu haben

Platziert das Programmfenster immer über alle anderen Fenster.

Schaltfläche zur Aktivierung der Kontrolle nach neuen Meldungen in den Protokolldateien

Mit dieser Schaltfläche können Sie das Programmfenster in der Ablage verbergen System-Ablage die Kontrolle nach neuen Meldungen in den Protokolldateien aktivieren. Zur Auswahl des Projekts/Datei/Kategorie, die gescannt werden soll(en), wählen Sie das Kästchen neben dem entsprechenden Element aus.

Wenn Sie das Kästchen neben der Kategorie der Meldungen auswählen, wird die Benachrichtigung bei jeder neuen Meldung in diesem Projekt/Datei/Kategorie ausgelöst. Wenn Sie das Kästchen neben der Datei auswählen, wird die Benachrichtigung bei jeder neuen Meldung in dieser Datei für jede Kategorie ausgelöst. Und Wenn Sie das Kästchen neben dem Projekt auswählen, wird die Benachrichtigung bei neuen Protokolldateien und Meldungen in ihnen ausgelöst.

Kontrolle

Wenn Sie die Kontrolle aktiviert haben und das Programmfenster ist auf Ablage minimiert, wird das Hauptanwendungsfenster mit einer akustischen Benachrichtigung immer dann maximiert, sobald eine neue Meldung in den ausgewählten Elementen auftaucht. Zur Deaktivierung der akustischen Benachrichtigung, klicken sie mit der linken Maustaste einfach irgendwo auf die Liste. Zur Beendigung der Kontrolle, klicken Sie auf das Programmsymbol in der Ablage LogMon Symbol. Zur Änderung des Tons der akustischen Benachrichtigung auf Ihren eigenen Lieblingston, platzieren Sie die place .wav Datei namens "alert.wav" in den gleichen Ordner wie die ausführbare Programmdatei.

Protokollkategorie ansehen

Zur Ansicht einer bestimmten Kategorie einfach mit Doppelklick aktivieren. Dann erscheint ein Nachrichtenfeld:

LogMon Suche

In diesem Fester können Sie nach Nachrichten suchen. Schieben Sie das Fenster immer nach oben und schalten auf Auto-Scrollen um. Die Farbe jeder Meldung kann mit Hilfe der write() Methode der CLogger Klasse individuell angepasst werden. Der Hintergrund der Meldung erscheint dann in der gewählten Farbe

Wenn Sie mit Doppelklick auf eine Meldung gehen, geht sie in einem extra Fenster auf. Das ist ziemlich praktisch, Wenn die Meldung sehr lang ist und nicht in das Dialogfenster passt:

LogMon Meldung

Jetzt haben Sie ein praktisches Tool zur Ansciht und Kontrolle von Protokolldateien zur Hand. Ich hoffe, dass Ihnen dieses Programm bei der Entwicklung und Verwendung von MQL5 Programmen helfen wird.


Fazit

Die Protokollierung von Ereignissen in Ihrem Programm ist sehr nützlich, da Sie dadurch versteckte Fehler identifizieren und Möglichkeiten zur Verbesserung Ihres Programms entdecken können. In diesem Beitrag habe ich Methoden und Programme für das allereinfachste Protokollieren in Dateien sowie die Kontrolle und Ansicht der Protokolle beschrieben.

Ich freue mich sehr über Ihre Anmerkungen und Vorschläge!

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/150

Beigefügte Dateien |
clogger.mqh (8.72 KB)
testlogger.mq5 (1.29 KB)
logmon_source_en.zip (119.02 KB)
logmonen.zip (88.78 KB)
Wie man rasch einen Expert Advisor für den Automatisierten Trading-Wettbewerb 2010 erzeugt Wie man rasch einen Expert Advisor für den Automatisierten Trading-Wettbewerb 2010 erzeugt
Zur Entwicklung eines Expert Advisors zur Teilnahme am Automatisierten Trading-Wettbewerb 2010, nehmen wir ein Template eines fertigen Expert Advisors her. Selbst noch unerfahrene MQL5 Programmierer können diese Aufgabe bewältigen, da ja für die Strategien die grundlegenden Klassen, Funktionen und Templates schon entwickelt sind. Daher genügt es, nur ein bisschen Code zur Implementierung Ihres Trading-Konzepts zu schreiben.
Simulink: ein Leitfaden für Expert Advisor-Entwickler Simulink: ein Leitfaden für Expert Advisor-Entwickler
Ich bin kein professioneller Programmierer, deshalb ist das Prinzip "vom Einfachen zum Komplexen" bei der Arbeit an Entwicklungen von Handelssystemen für mich von äußerster Wichtigkeit. Was genau heißt "einfach" für mich? Zunächst heißt das die Anschaulichkeit des Erzeugungsprozesses eines Systems und die Logik seiner Funktionsweise. Und es heißt auch möglichst wenig handgeschriebener Code. In diesem Beitrag versuche ich ein Handelssystem auf Grundlage des Matlab-Pakets zu erzeugen und zu testen und anschließend einen Expert Advisor für MetaTrader 5 zu schreiben. Im Testvorgang werden die historischen Daten von MetaTrader 5 eingesetzt.
Leitfaden zum Schreiben einer DLL für MQL5 in Delphi Leitfaden zum Schreiben einer DLL für MQL5 in Delphi
Dieser Beitrag befasst sich mit dem Mechanismus zur Erstellung eines DLL-Moduls mithilfe der beliebten Programmiersprache ObjectPascal innerhalb einer Delphi-Programmierumgebung. Die Inhalte dieses Beitrags richten sich mit der Einbindung äußerer DLL-Module vorrangig an Neueinsteiger in der Programmierung, die mit Problemen kämpfen, die die Grenzen der eingebetteten Programmiersprache MQL5 sprengen.
Ein Beispiel für ein Handelssystem auf der Grundlage des Indikators Heiken-Ashi Ein Beispiel für ein Handelssystem auf der Grundlage des Indikators Heiken-Ashi
In diesem Beitrag betrachten wir die Verwendung eines Heikin-Ashi-Indikators im Handel. Basierend auf diesem Indikator wird ein einfaches Handelssystem betrachtet und ein Expert Advisor in MQL5 geschrieben. Handelstätigkeiten werden auf Basis der Klassen der Standard-Klassenbibliothek implementiert. Die auf der Historie basierenden und mithilfe des in MetaTrader 5 eingebauten Strategietesters erhaltenen Testergebnisse der geprüften Handelsstrategie werden in diesem Beitrag bereitgestellt.