MetaTrader 5 herunterladen

OOP in MQL5 anhand von Beispielen: Fehlercodes und Warnmeldungen bearbeiten

8 März 2016, 09:02
KlimMalgin
0
270

Kurze Einführung in die objektorientierte Programmierung (OOP)

Ich schlage vor, dass wir uns, bevor wir in die Programmierung einsteigen, mit den Möglichkeiten der OOP vertraut machen, die wir im weiteren Verlauf dieses Beitrags verwenden werden. 

Natürlich kommen Gerüste (Strukturen) und Klassen zum Einsatz. Sie sind Grundbestandteile objektorientierter Sprachen. Was ist ein Gerüst, was eine Klasse, und wodurch unterscheiden sie sich?

Ein Gerüst ist ein Gebilde, das einen Satz Variablen und Funktionen verschiedener Art (ausgenommen void) enthalten kann.

Bei einer Klasse handelt es sich wie bei einem Gerüst um einen Satz Datenfelder (Arrays). Eine Klasse ist jedoch ein komplexeres und anpassungsfähigeres Gebilde. Eben diese Klassen sind der zentrale Begriff in der OOP. In der Dokumentation werden die Unterschiede zwischen Klassen und Gerüsten ausgewiesen, und um es noch einmal zu verdeutlichen:

  • in der Deklarierung wird der Schlüsselbegriff „class“ verwendet;
  • standardmäßig weisen alle Elemente einer Klasse sofern nicht anders angegeben die Zugriffskennung „private“ auf. Die Datenelemente in den Gerüsten weisen sofern nicht anders angegeben die Zugriffskennung „public“ auf;
  • die Objekte der Klassen verfügen stets über eine Tabelle mit virtuellen Funktionen, und zwar auch dann, wenn in der Klasse keine einzige virtuelle Funktion deklariert wurde. Gerüste können keine virtuellen Funktionen besitzen;
  • auf Klassenobjekte kann der Operator new angewendet werden, auf Gerüste dagegen nicht;
  • Klassen können nur aus Klassen hervorgehen, Gerüste nur aus Gerüsten.

 

Sehen wir uns die Klassen einmal näher an. Alle Felder einer Klasse verteilen sich auf zwei Arten von Feldern. Dies sind die Datenelemente (Variablen, Datenfelder usw.) sowie die innerhalb der Klasse festgelegten Funktionen.

Die Datenelemente werden gewöhnlich als Eigenschaften der Klasse bezeichnet, weil die Datenelemente, wenn aus der Klasse ein Objekt angelegt wird, nichts anderes wiedergeben als die Eigenschaften für dieses Objekt. Nehmen wir zum Beispiel eine geometrische Figur, einen Kreis, dann befinden sich in den Eigenschaften der Radius, die Linienstärke und die Füllfarbe, also die Objekteigenschaften.

Die in der Klasse festgelegten Funktionen werden als Methoden bezeichnet. Mit ihrer Hilfe erfolgt sowohl die Bearbeitung der Klasseneigenschaften als auch die Ausführung aller übrigen in ihnen programmierten Algorithmen.

In der objektorientierten Programmierung gibt es den Begriff der Einkapselung (Incapsulation). Er beschreibt die Möglichkeit der Ausblendung der Daten und der Anlage eines Objektes durch unmittelbares Eingreifen eines Anwenders (Anwendungsprogrammierers). Dabei erhält der Programmierer lediglich eine Dokumentation, in der dargestellt wird, mithilfe welcher Methoden die eine oder andere Objekteigenschaft geändert werden kann, die unmittelbare Änderung dieser Eigenschaften scheint dagegen nicht möglich zu sein.

Ähnliche Schutzvorkehrungen sind in den Fällen erforderlich, in denen vor der Änderung des Wertes einer Eigenschaft mehrere Prüfungen vorgenommen werden müssen. Genau diese erforderlichen Prüfungen werden in den Methoden umgesetzt und lassen nach erfolgreicher Ausführung die Änderung der Eigenschaftswerte zu. Falls jedoch der Anwender unmittelbaren Zugriff auf die Eigenschaften hat, unterbleiben diese Prüfungen, weswegen es zu einer fehlerhaften Festlegung des Eigenschaftswerts und in der Folge zu einer Fehlfunktion des MQL-Programms kommen kann.

Wenn wir mit der Materie etwas vertrauter sind, können wir zu jeder Eigenschaft und jeder Methode der Klasse eine Zugriffsstufe festlegen, das geschieht mithilfe der drei Attribute: private, protected und public.

Wird für ein Feld der Klasse das Attribut private verwendet, so ist der Zugriff auf dieses nur mithilfe der Methoden eben dieser Klasse möglich, es kann also nicht von außen geändert werden. Durch das Attribut protected wird der Zugriff von außen auf das Feld ebenfalls beschränkt, wobei jedoch der Zugriff auf die Felder Klasse nicht nur für Methoden derselben Klasse sondern auch für Methoden aus dieser abgeleiteter Kindklassen möglich ist. Im Gegensatz dazu werden durch das Attribut public alle Zugriffsbeschränkungen aufgehoben, alle Felder der Klasse sind demnach frei zugänglich.

Anlegen einer einbindbaren mqh-Datei

Die Klasse, die wir gemeinsam programmieren wollen, muss sich in einer eigenen mqh-Datei befinden, damit wir sie in unsere Programme (Expert-Systeme, Skripte und Indikatoren) einbinden können. 

Wir legen diese Datei mithilfe des MQL5-Assistenten an. In dem Menü Datei -> Erstellen wählen wir Include-Datei (*.mqh) und klicken auf Weiter. In dem sich öffnenden Fenster geben wir den Dateinamen ein, ich habe mich für ControlErrors entschieden, und klicken auf Fertig. Es öffnet sich eine Schablone für eine mqh-Datei. Mit dieser machen wir weiter.

An die Arbeit

Jetzt haben Sie alle theoretischen Grundlagen der OOP, die Ihnen beim Studium dieses Beitrags von Nutzen sein können, zur Hand und wir können fortfahren.

Wir schauen uns den Code der Klasse genauer an und deklarieren ihre Eigenschaften und Methoden:

class ControlErrors
{
private:

   // Flags determining what types of statements should be introduced
   bool _PlaySound;    // Play or don't play a sound when an error occurs
   bool _PrintInfo;    // Enter error data the the journal of Expert Advisors
   bool _AlertInfo;    // Generate Alert alert with error data
   bool _WriteFile;    // Write reports on errors onto a file or not
   
   // A structure for storing error data and elements that use this structure
   struct Code
   {
      int code;      // Error code
      string desc;   // Description of an error code
   };
   Code Errors[];    // Array that contains error codes and their descriptions
   Code _UserError;  // Stores information about a custom error
   Code _Error;      // Stores information about the last error of any type   
   
   // Different service properties
   short  _CountErrors;     // Number of errors stored in array Errors[]
   string _PlaySoundFile;   // File that will be played for an alert sound
   string _DataPath;        // Path to the log storing directory

   
public:
   // Constructor
   ControlErrors(void);
   
   // Methods for setting flags
   void SetSound(bool value);          // Play or don't play a sound when an error occurs
   void SetPrint(bool value);          // Enter error data the the journal of Expert Advisors or not
   void SetAlert(bool value);          // Generate an Alert message or not
   void SetWriteFlag(bool flag);       // Set the writing flag. true - keep logs, false - do not keep
   
   // Methods for working with errors
   int  mGetLastError();            // Returns contents of the system variable _LastError
   int  mGetError();                // Returns code of the last obtained error
   int  mGetTypeError();            // Returns error type (Custom = 1 ore predefined = 0)
   void mResetLastError();          // Resets the contents of the system variable _LastError
   void mSetUserError(ushort value, string desc = "");   // Sets the custom error
   void mResetUserError();          // Resets class fields that contain information about the custom error
   void mResetError();              // Resets the structure that contains information about the last error
   string mGetDesc(int nErr = 0);   // Returns error description by the number, or that of the current error of no number
   int Check(string st = "");       // Method to check the current system state for errors
   
   // Alert methods(Alert, Print, Sound)
   void mAlert(string message = "");
   void mPrint(string message = "");
   void mSound();
      
   // Various service methods
   void SetPlaySoundFile(string file); // Method sets the file name to play an sound
   void SetWritePath(string path);     // Set the path to store logs  
   int mFileWrite();                   // Record into a file the available information about the last error
};

Die Eigenschaften der Klasse

Zunächst müssen die Eigenschaften der Klasse deklariert werden, wobei auf alle Eigenschaften das Attribut private angewendet wird, wodurch die unmittelbare Bearbeitung der Eigenschaften von außerhalb der Klasse unmöglich wird. Der Übersichtlichkeit halber sind die Eigenschaften in drei Gruppen aufgeteilt:

  1. Auszeichnungen (Flags), die die Art der auszugebenden Meldungen angeben. Diese Auszeichnungen können jeweils nur einen von zwei Werten annehmen: „true“, wenn die betreffende Art Meldung (Benachrichtigung) dazugehört; bzw. „false“ wenn Meldungen dieser Art nicht ausgegeben werden.
    • _PlaySound - Diese Variable ermöglicht oder unterbindet das Abspielen der angegebenen Melodie oder des Tons beim Auftreten eines Fehlers.
    • _PrintInfo - sorgt dafür, dass die Angaben zu dem Fehler in das Protokoll des betreffenden Expert-Systems eingetragen werden.
    • _AlertInfo - schaltet die Ausgabe einer Warnmeldungen (Alert) mit Angaben zu dem Fehler ein- bzw. aus.
    • _WriteFile - ermöglicht oder unterbindet die Aufzeichnung der Angaben zu dem Fehler in einer Datei.
  2. Ein Gerüst zur Speicherung der Angaben zu dem Fehler sowie die Elemente, die dieses Gerüst nutzen.
    • Code - das Gerüst schlechthin. Er wurde für die vereinfachte Speicherung der Angaben zu Fehlern in einem Datenfeld erzeugt.
    • Errors - ein Code-Datenfeld, das heißt, bei jedem Datenfeldelement handelt es sich um ein Code-Gerüst.
    • _UserError - eine Code-Variable. Sie dient zur Bearbeitung benutzerdefinierter Fehler.
    • _Error - eine Code-Variable. Der zuletzt aufgetretene Fehler wird in diese Variable eingestellt, und die weitere Arbeit mit ihm erfolgt dann über eben dies Variable.
  3. Weitere Hilfseigenschaften.
    • _CountErrors - eine Variable, die die Anzahl der Fehler enthält, deren Angaben in dem Datenfeld Errors gespeichert sein müssen. Sie dient zur Angabe der Größe des Datenfeldes.
    • _PlaySoundFile - enthält den Namen der Datei, die bei Eingang eines entsprechenden Signals abgespielt wird.
    • _DataPath - enthält den Pfad und den Namen der Protokolldatei zur Aufzeichnung der Angaben zu den Fehlern.

Was die erste Gruppe betrifft, dürfte meiner Ansicht nach alles klar sein: sie ermöglicht oder unterbindet die Ausgabe bestimmter Meldungen. Inder zweiten Gruppe dagegen interessiert uns das Code-Gerüst. Was ist es, und wieso wird ausgerechnet ein Gerüst als Datenfeldelement verwendet? Das ist ganz einfach! Es ist doch viel bequemer, alle notwendigen Daten in einem einzigen Datenfeldelement zu speichern, als jeweils eigene Datenfelder für den Code und die Beschreibung der Fehler zu pflegen. Zur Umsetzung dieser Möglichkeit verwenden wir ein Gerüst. Alle erforderlichen Felder werden darin deklariert. In unserem Fall sind das:

  • code - ein Feld der Art „int“ mit dem Fehlercode;
  • desc - ein Feld der Art „string“. Es enthält die Beschreibung des Fehlers.

Im Grunde handelt es sich bei einem Gerüst um eine Datenbestandsart, d. h. es kann zur Deklarierung von Variablen und Datenfeldern verwendet werden, was auch geschehen ist. Daraus folgt, dass jede Code-Variable Felder dieses Gerüstes beinhaltet. Genauso weist auch jedes Datenfeldelement der Art „Code“ zwei Felder zum Speichern des Codes und seiner Beschreibung auf. Auf diese Weise wird in MQL5 ein recht einfaches Verfahren zur Speicherung unterschiedlicher Arten von Daten zu einem Objekt an einem Speicherort umgesetzt.

Im Programmcode folgen die Variablen _UserError und _Error. Beide enthalten Angaben zu dem zuletzt aufgetretenen Fehler mit dem Unterschied, dass _UserError lediglich Angaben zu benutzerdefinierten Fehlern speichert, _Error dagegen zu allen.

Kommen wir zur dritten und letzten Gruppe von Eigenschaften. Dieser habe ich alle Eigenschaften zugeordnet, die ihrer Zweckbestimmung nach weder zur ersten noch zur zweiten Gruppe gehören. Es gibt insgesamt drei davon. Die erste ist die Eigenschaft _CountErrors, sie enthält die Anzahl der Fehler, deren Angaben in dem Datenfeld _Errors gespeichert sind. Diese Eigenschaft wird zur Angabe der Größe des Datenfeldes _Errors im Konstruktor und einigen Methoden zum Aufrufen von Elementen des Datenfeldes verwendet. Die zweite Eigenschaft ist _PlaySoundFile. Sie beinhaltet die Bezeichnung der Tondatei, die bei Eintreten eines Fehlers abgespielt wird. Die dritte Eigenschaft ist _DataPath. Hier werden der Pfad und der Name der Datei für das Protokoll gespeichert.

Klassenmethoden. Konstruktor

Es folgt die Darstellung des Konstruktors und der Methoden der Klasse. Wir beginnen mit dem Konstruktor und versuchen herauszufinden, was er ist. Wie alle Methoden ist er eine gewöhnliche innerhalb der Klasse festgelegte Funktion, dennoch weist er einige Besonderheiten auf:

  • Die Bezeichnung des Konstruktors entspricht der der Klasse.
  • Ein Konstruktor gibt keine Werte aus (als seine Art wird void ausgewiesen).
  • Ein Konstruktor besitzt keine Eingangsparameter.

In Konstruktoren werden für gewöhnlich die Elemente einer Klasse bereitgestellt. In dem Konstruktor unserer Klasse werden beispielsweise alle Auszeichnungen zur Unterbindung der Ausgabe von Meldungen gesetzt, die Bezeichnungen der Ton- und der Protokolldatei angegeben, die Größe des Datenfeldes _Errors festgelegt sowie eben dieses Datenfeld mit Daten gefüllt. Unten ist lediglich ein Teil des Programmcodes abgebildet, da er viel zu groß und gleichförmig ist, den Hauptteil nimmt die Füllung des Datenfeldes _Errors mit Fehlercodes und ihrer Beschreibung ein. Der vollständige Code findet sich in den Dateien im Anhang zu diesem Beitrag.

void ControlErrors::ControlErrors(void)
{
   SetAlert(false);
   SetPrint(false);
   SetSound(false);
   SetWriteFlag(false);
   SetPlaySoundFile("alert.wav");
   SetWritePath("LogErrors.txt");
   
   _CountErrors = 150;
   ArrayResize(Errors, _CountErrors);

   // Return codes of a trade server
   Errors[0].code = 10004;Errors[0].desc = "Requote";
   Errors[1].code = 10006;Errors[1].desc = "Request rejected";
   Errors[2].code = 10007;Errors[2].desc = "Request canceled by trader";
   Errors[3].code = 10008;Errors[3].desc = "Order placed";
   Errors[4].code = 10009;Errors[4].desc = "Request is completed";
   Errors[5].code = 10010;Errors[5].desc = "Request is partially completed";
   Errors[6].code = 10011;Errors[6].desc = "Request processing error";
   Errors[7].code = 10012;Errors[7].desc = "Request canceled by timeout";
   Errors[8].code = 10013;Errors[8].desc = "Invalid request";
   Errors[9].code = 10014;Errors[9].desc = "Invalid volume in the request";
   Errors[10].code = 10015;Errors[10].desc = "Invalid price in the request";
   Errors[11].code = 10016;Errors[11].desc = "Invalid stops in the request";
   Errors[12].code = 10017;Errors[12].desc = "Trade is disabled";
   Errors[13].code = 10018;Errors[13].desc = "Market is closed";
   Errors[14].code = 10019;Errors[14].desc = "There is not enough money to fulfill the request";
   Errors[15].code = 10020;Errors[15].desc = "Prices changed";
   Errors[16].code = 10021;Errors[16].desc = "There are no quotes to process the request";
   Errors[17].code = 10022;Errors[17].desc = "Invalid order expiration date in the request";
   Errors[18].code = 10023;Errors[18].desc = "Order state changed";
   Errors[19].code = 10024;Errors[19].desc = "Too frequent requests";
   Errors[20].code = 10025;Errors[20].desc = "No changes in request";
   Errors[21].code = 10026;Errors[21].desc = "Autotrading disabled by server";
   Errors[22].code = 10027;Errors[22].desc = "Autotrading disabled by client terminal";
   Errors[23].code = 10028;Errors[23].desc = "Request locked for processing";
   Errors[24].code = 10029;Errors[24].desc = "Order or position frozen";
   Errors[25].code = 10030;Errors[25].desc = "Invalid order filling type";

   // Common Errors
   Errors[26].code = 4001;Errors[26].desc = "Unexpected internal error";
   Errors[27].code = 4002;Errors[27].desc = "Wrong parameter in the inner call of the client terminal function";
   Errors[28].code = 4003;Errors[28].desc = "Wrong parameter when calling the system function";
   Errors[29].code = 4004;Errors[29].desc = "Not enough memory to perform the system function";
   Errors[30].code = 4005;Errors[30].desc = "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes";
   Errors[31].code = 4006;Errors[31].desc = "Array of a wrong type, wrong size, or a damaged object of a dynamic array";
   Errors[32].code = 4007;Errors[32].desc = "Not enough memory for the relocation of an array, or an attempt to change the size of a static array";
   Errors[33].code = 4008;Errors[33].desc = "Not enough memory for the relocation of string";
   Errors[34].code = 4009;Errors[34].desc = "Not initialized string";
   Errors[35].code = 4010;Errors[35].desc = "Invalid date and/or time";
   Errors[36].code = 4011;Errors[36].desc = "Requested array size exceeds 2 GB";
   Errors[37].code = 4012;Errors[37].desc = "Wrong pointer";
   Errors[38].code = 4013;Errors[38].desc = "Wrong type of pointer";
   Errors[39].code = 4014;Errors[39].desc = "System function is not allowed to call";

}

Beachten Sie bitte, dass die Darstellung der Umsetzung außerhalb der Klasse erfolgt! In der Klasse selbst werden ausschließlich die Methoden deklariert! Obwohl das nicht unbedingt geschehen muss. Wenn Sie möchten, können Sie den Hauptteil jeder Methode in der Klasse selbst beschreiben, aber ich halte das für ungeeignet und störend beim Lesen.

Wie bereits erwähnt werden im Hauptteil der Klasse lediglich die Kopfzeilen der Funktionen und Methoden deklariert, während die Beschreibung ihrer Umsetzung außerhalb der Klasse erfolgt. Bei der Beschreibung einer Methode muss angegeben werden, zu welcher Klasse sie gehört. Dazu verwenden wir die Operation zur Zulassung von Kontext ::. Wie dem obigen Programmcode zu entnehmen ist, wird zunächst die Ausgabeart der Methode festgelegt (bei einem Konstruktor ist das void) gefolgt von dem Namen der Klasse (der Bezeichnung des Kontextes, zu dem die Methode gehört), nach dem Klassennamen wird die Operation zur Zulassung von Kontext angegeben und anschließend die Bezeichnung der Methode nebst ihren Eingangsparametern. Wenn all das erfolgt ist, wird der Algorithmus für die Methode geschrieben.

Zu Beginn werden im Konstruktor einer Klasse alle Auszeichnungen (Flags) gesetzt sowie die Ton- und die Protokolldatei ausgewiesen: 

SetAlert(false);
SetPrint(false);
SetSound(false);
SetWriteFlag(false);
SetPlaySoundFile("alert.wav");
SetWritePath("LogErrors.txt"); 

Jede dieser Methoden arbeitet mit einer bestimmten Eigenschaft der Klasse. Das wurde eigens für den Fall eingerichtet, dass es erforderlich wird, die den Eigenschaften von dem Anwender zugewiesenen Werte zu filtern. Man kann beispielsweise ein bestimmtes Muster vorgeben, dem sowohl der vom Anwender angegebene Pfad als auch die Bezeichnung, die er seiner Datei gegeben hat, entsprechen müssen. Der Anwender wird benachrichtigt, wenn keine Übereinstimmung mit dem Muster vorliegt. 

Wie Sie bemerkt haben dürften, sind alle Auszeichnungen mit dem Wert „false“ ausgezeichnet. Das heißt: Beim Anlegen einer Klasseninstanz unter Beibehaltung der Voreinstellungen werden keine Meldungen ausgegeben. Der Anwender muss selbst entscheiden, welche Meldungen auszugeben sind und diese in der Funktion OnInit() mithilfe der Methoden „Set“ einschalten. In gleicher Weise lassen sich die Bezeichnung und der Pfad zu der Datei, in der das Protokoll geführt wird (der Pfad wird in Bezug auf das Verzeichnis 'MetaTrader 5\MQL5\Files\‘ angegeben), sowie zu der Tondatei (Pfad in Bezug auf das Verzeichnis 'MetaTrader 5\Sounds\') ändern.

Nachdem die Auszeichnungen gesetzt sind, stellen wir die Variable _CountErrors bereit und weisen ihr den Wert 150 zu (in dem Datenfeld werden Angaben zu 149 Fehlern gespeichert), anschließend legen wir mithilfe der Funktion ArrayResize() die erforderliche Größe des Datenfeldes fest. Dann beginnen wir, es zu füllen.

Methoden zum Setzen von Auszeichnungen (Flags)

Der Beschreibung des Konstruktors folgt die Beschreibung der Methoden zum Setzen der Auszeichnungen sowie zur Festlegung der Bezeichnungen der Ton- und der Protokolldatei:

void ControlErrors::SetAlert(bool value)
{
   _AlertInfo = value;
}

void ControlErrors::SetPrint(bool value)
{
   _PrintInfo = value;
}

void ControlErrors::SetSound(bool value)
{
   _PlaySound = value;
}

void ControlErrors::SetWriteFlag(bool flag)
{
   _WriteFile = flag;
}

void ControlErrors::SetWritePath(string path)
{
   _DataPath = path;
}

void ControlErrors::SetPlaySoundFile(string file)
{
   _PlaySoundFile = file;
}

Der Code zeigt, dass sich alles auf die simple Übernahme des auf die Methode bzw. die Eigenschaft der Klasse übertragenen Parameters beschränkt. Und wenn die Auszeichnungen keiner besonderen Überprüfung bedürfen, weil insgesamt nur zwei Werte annehmen, können ihre Bezeichnungen und die Dateipfade vor der Übernahme durch die erforderlichen Filter geleitet werden.

Der Aufruf dieser und aller sonstigen Methoden sieht wie folgt aus: 

type Class_name::Function_Name(parameters_description)
{
   // function body
}

Weiter geht es mit der Darstellung der Methoden für den Umgang mit Fehlern, und die beiden ersten sind mGetLastError() bzw. mResetLastError().

Die Methoden mGetLastError() und mResetLastError() 

Die Bezeichnung der Methode mGetLastError() spricht für sich selbst. Sie ist ein Duplikat der Funktion GetLastError(). Aber außer dem Aufruf der Funktion GetLastError() sucht sie für den erhaltenen Fehlercode in dem Datenfeld _Errors auch nach einer Beschreibung sowie nach allen Informationen über den Fehler (den Code und seine Beschreibung) und speichert diese in der Variablen _Error, damit sie im Weiteren auf den gespeicherten Wert zurückgreifen kann, statt jedes Mal die Funktion GetLastError() aufrufen zu müssen.

Der Programmcode der Methode:

int ControlErrors::mGetLastError(void)
{
   _Error.code = GetLastError();
   _Error.desc = mGetDesc(_Error.code);
   return _Error.code;
}

Die Methode mResetLastError() ist ein Duplikat der Funktion ResetLastError():

void ControlErrors::mResetLastError(void)
{
   ResetLastError();
}

Methoden für die Bearbeitung der letzten Fehlermeldung

Es handelt sich um zwei Methoden: mGetError() bzw. mResetError().

Die Methode mGetError() gibt den in _Error.code enthaltenen Fehlercode aus:

int ControlErrors::mGetError(void)
{
   return _Error.code;
}

Die Methode mResetError() setzt den Inhalt der Variablen _Error auf Null zurück:

void ControlErrors::mResetError(void)
{
   _Error.code = 0;
   _Error.desc = "";
}

Die Methode mGetTypeError() zur Ermittlung der Art des Fehlers

Die nächste Methode ist: mGetTypeError(). Sie prüft, ob der zuletzt aufgetretene Fehler benutzer- oder vordefiniert (im Datenfeld _Errors enthalten) war.

Der Programmcode der Methode lautet:

int ControlErrors::mGetTypeError(void)
{
   if (mGetError() < ERR_USER_ERROR_FIRST)
   {
      return 0;
   }
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
   {
      return 1;
   }
   return -1;
}

Die Konstante ERR_USER_ERROR_FIRST hat den Wert 65536. Ab dieser Kennziffer fangen die benutzerdefinierten Fehler an. Deshalb wird im Hauptteil der Methode die Überprüfung des zuletzt aufgetretenen Fehlercodes ausgeführt. Gibt die Methode den Wert „Null“ aus, handelt es sich um einen vordefinierten Standardfehler. Bei „1“ war er benutzerdefiniert.

Methoden für die Arbeit mit benutzerdefinierten Fehlern

In MQL5 haben Anwender die Möglichkeit, im Verlauf der Arbeit mit einem Programm eigene Fehlermeldungen anzulegen. Um die benutzerdefinierten Fehlercodes in eine entsprechende Schreibweise zu bringen, ist in der Klasse die Eigenschaft _UserError vorhanden. Zur Bearbeitung dieser Eigenschaft stehen zwei Methoden zur Verfügung.

Die Methode mSetUserError() wird zur Festlegung der Kennziffer (des Codes) und der Beschreibung eines benutzerdefinierten Fehlers verwendet:

void ControlErrors::mSetUserError(ushort value, string desc = "")
{
   SetUserError(value);
   _UserError.code = value;
   _UserError.desc = desc;
}

Zunächst stellt die Funktion SetUserError() die vordefinierte Variable _LastError auf den Wert „ERR_USER_ERROR_FIRST + value“ ein. Anschließend wird der value nebst der entsprechenden ihm zugewiesenen Beschreibung in der Variablen _UserError gespeichert.

Die zweite Methode mResetUserError() setzt die Felder der Variablen _UserError auf Null zurück:

void ControlErrors::mResetUserError(void)
{
   _UserError.code = 0;
   _UserError.desc = "";
}

Diese Methode bearbeitet ausschließlich die Variable _UserError. Zum Zurücksetzen des Wertes der Systemvariablen _LastError ist eine andere Methode vorgesehen: mResetLastError() wie oben dargestellt.

Die Methode zum Abrufen der Beschreibung des Fehlercodes

In der Klasse ist außerdem die Sondermethode mGetDesc() angelegt, die, wenn sie aufgerufen wird, die Beschreibung des Fehlercodes aus dem Datenfeld Errors oder, wenn es sich um einen benutzerdefinierten Fehler handelt, aus dem desc-Feld der Variablen _UserError ausgibt:

string ControlErrors::mGetDesc(int nErr=0)
{
   int ErrorNumber = 0;
   string ReturnDesc = "";
   
   ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
   ErrorNumber = (nErr>0)?nErr:ErrorNumber;
   
   if ((ErrorNumber > 0) && (ErrorNumber < ERR_USER_ERROR_FIRST))
   {
      for (int i = 0;i<_CountErrors;i++)
      {
         if (Errors[i].code == ErrorNumber)
         {
            ReturnDesc = Errors[i].desc;
            break;
         }
      }
   }
   else if (ErrorNumber > ERR_USER_ERROR_FIRST)
   {
      ReturnDesc = (_UserError.desc=="")?"Cusrom error":_UserError.desc;
   }
      
   if (ReturnDesc == ""){return "Unknown error code: "+(string)ErrorNumber;}
   return ReturnDesc;
}

Diese Methode besitzt nur den Parameter nErr. Er ist auf Null voreingestellt. Wird dem Parameter bei Aufruf der Methode ein Wert zugewiesen, erfolgt die Suche nach der Beschreibung anhand dieses eingegebenen Wertes. Wird kein Parameterwert eingegeben, so wird nach der Beschreibung zu dem Code des zuletzt aufgetretenen Fehlers gesucht.

Zu Beginn werden in der Methode zwei Variablen deklariert: ErrorNumber, mit ihrer Hilfe erfolgt die Arbeit mit dem Fehlercode, sowie ReturnDesc zur Speicherung der zu ErrorNumber erhaltenen Beschreibung. In den beiden folgenden Zeilen wird bei der Zuordnung des Wertes ErrorNumber der bedingte Operator ?: verwendet. Dabei handelt es sich um eine vereinfachte Entsprechung zu der Konstruktion if-else.

ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
ErrorNumber = (nErr>0)?nErr:ErrorNumber;

In der ersten Zeile legen wir fest: wenn ein Fehler ermittelt wurde, das heißt, wenn von der Methode mGetError() ein anderes Ergebnis als „0“ ausgegeben wurde, dann wird der Variablen ErrorNumber der erhaltene Fehlercode (der von der Methode mGetError() ausgegebene Wert) zugewiesen, andernfalls der Wert der Variablen ErrorNumber selbst. In der zweiten Zeile erfolgt dieselbe Prüfung, bloß für den Parameter der Methode mGetError(). Falls der Wert nErr nicht gleich Null ist, wird er in die Variable ErrorNumber aufgenommen.

Nach dem Empfang des Fehlercodes beginnt die Suche nach seiner Beschreibung. Ist die empfangene Kennziffer höher als Null und kleiner als ERR_USER_ERROR_FIRST, so bedeutet das, dass es sich nicht um einen benutzerdefinierten Fehler handelt, wir setzen die Suche nach seiner Beschreibung also in einer Schleife fort. Und wenn die bezogene Kennziffer höher ist als ERR_USER_ERROR_FIRST, dann nehmen wir die Beschreibung aus dem desc-Feld der Variablen _UserError.

Ganz am Schluss prüfen wir, ob wir die Beschreibung gefunden haben, und wenn nicht, melden wir, dass ein unbekannter (kennzifferloser) Fehler vorliegt.

Signalmethoden

Zu den Signalmethoden gehören mAlert(), mPrint() und mSound(). In ihrem Aufbau sind diese Methoden einander sehr ähnlich:

void ControlErrors::mAlert(string message="")
{
   if (_AlertInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Alert("Error №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Alert(message);
      }   
   }
}

void ControlErrors::mPrint(string message="")
{
   if (_PrintInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Print("Error №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Print(message);
      }
   }
}

void ControlErrors::mSound(void)
{
   if (_PlaySound == true)
   {
      PlaySound(_PlaySoundFile);
   }
}

In allen drei Methoden wird zu Beginn die Auszeichnung (Flag) bezüglich der Erlaubnis der Ausgabe einer Meldung oder eines Signals geprüft. Danach wird bei den Methoden mAlert() und mPrint() der Eingangsparameter message auf das Vorhandensein einer in dem Dialogfeld Alert anzuzeigenden oder in das Protokoll einzutragenden Meldung geprüft. Wenn in message eine Meldung angelegt ist, und die Kennziffer (der Code) des letzten Fehlers größer ist als Null, wird sie angezeigt, wenn nicht erscheint eine Standardmeldung. Die Methode mSound() weist keinerlei Parameter auf, weswegen unmittelbar nach der Prüfung der Auszeichnung die Funktion PlaySound() zur Ausgabe eines akustischen Signals aufgerufen wird.

Die Methode Check()

Diese Methode ruft einfach alle Funktionen der gegebenen Klasse in der erforderlichen Reihenfolge auf, wobei das Auftreten eines neuen Fehlers gleich mit geprüft, die zugelassenen Meldungen ausgegeben und der Fehlercode nebst Beschreibung gleich danach gelöscht wird. Somit führt die Methode Check() eine umfassende Überprüfung durch:

int ControlErrors::Check(string st="")
{
   int errNum = 0;
   errNum = mGetLastError();
   mFileWrite();
   mAlert(st);
   mPrint(st);
   mSound();
   mResetError();
   mResetLastError();
   mResetUserError();
   return errNum;
}

Die Methode Check() besitzt lediglich einen Parameter der Art „string“. Dabei handelt es sich um eine benutzerdefinierte Mitteilung, die an die Methoden mAlert() und mPrint() zur Aufzeichnung in den Meldungen übertragen wird.

Methoden zur Aufzeichnung von Mitteilungen in einer Protokolldatei

Die Bezeichnung für diese Methode lautet mFileWrite(). Wenn das Führen einer Protokolldatei zugelassen und der Pfad zu ihr richtig angegeben ist, nimmt diese Methode die Aufzeichnungen in der angegebenen Datei vor.

int ControlErrors::mFileWrite(string message = "")
{
   int      handle  = 0,
            _return = 0;
   datetime time    = TimeCurrent();
   string   text    = (message != "")?message:time+" - Error №"+mGetError()+" "+mGetDesc();
   
   if (_WriteFile == true)
   {
      handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
      if (handle != INVALID_HANDLE)
      {
         ulong size = FileSize(handle);
         FileSeek(handle,size,SEEK_SET);
         _return = FileWrite(handle,text);
         FileClose(handle);
      }
   }
   return _return;
}

Anfangs werden vier Variablen deklariert, es sind: handle zur Speicherung der Bezeichnung der geöffneten Datei; _return zum Speichern des ausgegebenen Wertes; time zur Speicherung des Zeitpunktes der Aufzeichnung; sowie text, der Text, der in die Protokolldatei einzutragenden Mitteilung. Die Methode mFileWrite() besitzt den Eingangsparameter „message“, in den der Anwender eine beliebige Zeile eingeben kann, die in der Datei aufgezeichnet werden soll.

Diese Möglichkeit kann dazu genutzt werden, zu bestimmten Zeitpunkten Indikatorwerte, Kurse sowie weitere erforderliche Daten aufzuzeichnen.

Nach der Deklarierung der Variablen wird die Auszeichnung _WriteFile geprüft. Wenn das Führen einer Protokolldatei zugelassen ist, wird die Datei mithilfe der Funktion FileOpen() zur Bearbeitung geöffnet. Der erste Parameter der Funktion FileOpen() ist die Eigenschaft DataPath, die den Pfad zu der Datei sowie ihren Namen enthält. Als zweiter Parameter bietet sich eine Auswahl an Auszeichnungen zur Bestimmung der Art der Dateibearbeitung. In unserem Fall sind das vier Auszeichnungen (Flags):

  • FILE_READ und FILE_WRITE geben gemeinsam die Anweisung zum Öffnen einer nicht leeren Datei mit der Möglichkeit, Daten in sie einzufügen.
  • FILE_TXT zeigt an, dass die Arbeit in einer einfachen Textdatei erfolgt.
  • FILE_ANSI besagt, dass die Aufzeichnungen in der Datei zeilenweise in ANSI-Zeichensatz erfolgen (Ein Byte je Zeichen).

Als Nächstes prüfen wir, ob die Datei geöffnet wurde. Falls nicht, erhält das Handle den Wert INVALID_HANDLE, und die Ausführung der Methode wird beendet. Hatten wir dagegen Erfolg, erhalten wir mithilfe der Funktion FileSize() die Dateigröße, anschließend versetzen wir mittels FileSeek() den Dateizeiger ans Ende der Datei und schreiben mit FileWrite() die Mitteilung eben dorthin, ans Ende der Datei. Nach all diesen Vorgängen schließen wir die zuvor geöffnete Datei mit der Funktion FileClose().

In die Funktion FileSize() muss als Eingangsparameter die Bezeichnung (das Handle) der Datei eingegeben werden, deren Größe wir abrufen möchten. Das ist der einzige Parameter dieser Funktion.

Für die Arbeit der Funktion FileSeek() müssen drei Parameter ausgewiesen werden:

  • Die Bezeichnung (Das Handle) der Datei, mit der wir arbeiten.
  • Die Verlagerung des Dateizeigers.
  • Der Bezugspunkt für die Verlagerung. Er nimmt einen der Werte aus ENUM_FILE_POSITION an.

Für die Arbeit der Funktion FileWrite() sind immerhin zwei Parameter erforderlich. Dabei handelt es sich zum einen um die Bezeichnung (das Handle) der Datei, in die die Textdaten eingegeben werden sollen. Der zweite ist die zu schreibende Textzeile sowie alle nachfolgenden Textzeilen, die in der Datei aufgezeichnet werden. Insgesamt dürfen es nicht mehr als 63 Parameter sein.

Die Funktion FileClose() benötigt ebenfalls die Bezeichnung der Datei, um sie zu schließen.

Anwendungsbeispiele

Zum Abschluss meines Beitrags möchte ich einige typische Beispiele für die Anwendung der von uns programmierten Klasse vorstellen. Wir beginnen mit der Objekterstellung und der Zulassung der Ausgabe der von uns benötigten Meldungen.

Also, legen wir ein Objekt der Klasse an:

#include <ControlErrors.mqh>

ControlErrors mControl;

Vor dem Erstellen eines Objekts muss die Datei mit der Beschreibung der Klasse in das Expert-System eingebunden werden. Das erfolgt ganz am Anfang des Programms mithilfe der Anweisung #include. Erst danach wird das Objekt erstellt, was genauso aussieht, als würden wir eine neue Variable anlegen. Anstelle der Datenart wird jedoch die Bezeichnung der Klasse eingefügt. 

Jetzt wählen wir die Fehlermeldungen aus, die wir erhalten möchten. Das geschieht in der Funktion OnInit(): 

int OnInit()
{
//---
mControl.SetAlert(true);
mControl.SetPrint(true);
mControl.SetSound(false);
mControl.SetWriteFlag(true);
mControl.SetPlaySoundFile("news.wav");
//---
return(0);
}

Ich erinnere daran, dass bei der Erstellung eines Objektes alle Auszeichnungen, die die Ausgabe von Meldungen zulassen, standardmäßig auf den Wert „false“ voreingestellt sind, das heißt, dass nicht eine Meldung ausgegeben wird. Deshalb müssen in OnInit() Methoden mit diesem Wert anders als in dem obigen Beispiel [der Methode SetSound()] nicht zwingend aufgerufen werden. Der Aufruf dieser Methoden kann auch in anderen Teilen des Programms erfolgen. Wenn zum Beispiel die Ausgabe von Meldungen unter bestimmten Bedingungen ausgeschaltet werden muss, programmiert man diese Bedingungen und stellt die Auszeichnungen auf die Werte ein, bei denen die Bedingungen erfüllt sind.

Der letzte Punkt, der anzusprechen wäre, ist der Aufruf der Methoden, während das Programm läuft, und das „Einfangen“ von Fehlern. Das ist nicht schwer, da hier allein die Verwendung der Methode Check() ausreicht, nachdem vorher alle Auszeichnungen gesetzt worden sind:

mControl.Check();

Diese Methode gibt wie oben bereits erwähnt den Code des aufgetretenen Fehlers aus, ruft alle Methoden auf, die Meldungen ausgeben und setzt anschließend die Werte aller Variablen, die Angaben zu dem zuletzt aufgetretenen Fehlern enthalten, auf Null zurück. Wenn die von Check() bereitgestellte Art der Verarbeitung von Fehlermeldungen aus irgendeinem Grund ungeeignet sein sollte, können Sie aus allen verfügbaren Methoden der Klasse Ihre eigenen Meldungen anlegen.

Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/70

Beigefügte Dateien |
controlerrors.mqh (19.82 KB)
Praktische Anwendung von Datenbanken für die Marktanalyse Praktische Anwendung von Datenbanken für die Marktanalyse

Die Arbeit mit Daten ist zur Hauptaufgabe moderner Software geworden, sowohl autonomer als auch vernetzter Programme. Dazu wurde eine besondere Form von Software entwickelt. Diese Programme zur Verwaltung von Datenbanken (DBMS) ermöglichen die Strukturierung, Systematisierung und Organisation von Daten für ihre Speicherung und Verarbeitung auf Computern. Was den Börsenhandel betrifft, nutzen nur die wenigsten Analysten Datenbanken für ihre Arbeit. Es gibt jedoch Aufgaben, für die eine solche Lösung geeignet sein müsste. In diesem Beitrag wird ein Beispiel für Indikatoren vorgestellt, die sowohl unter einer Client-Server- als auch einer Datei-Server-Architektur Daten in Datenbanken speichern sowie aus diesen laden können.

Übertragung von MQL4-Indikatoren nach MQL5 Übertragung von MQL4-Indikatoren nach MQL5

Dieser Beitrag ist den Feinheiten der Übertragung in MQL4 programmierter Kurskonstruktion nach MQL5 gewidmet. Um die Übertragung von Indikatorberechnungen aus MQL4 nach MQL5 zu vereinfachen, empfiehlt sich die Funktionsbibliothek mql4_2_mql5.mqh. Ihre Verwendung wird am Beispiel der Übertragung der Indikatoren MACD, Stochastik und RSI veranschaulicht.

MetaTrader 5: Handelsprognosen und Umsatzzahlen per E-Mail in Blogs, sozialen Netzen und auf thematischen Webseiten veröffentlichen MetaTrader 5: Handelsprognosen und Umsatzzahlen per E-Mail in Blogs, sozialen Netzen und auf thematischen Webseiten veröffentlichen

Dieser Beitrag zielt darauf ab, gebrauchsfertige Lösungen für die Veröffentlichung von Prognosen mittels MetaTrader 5. Er behandelt eine Reihe von Gedanken: von der Verwendung thematischer Webseiten zur Veröffentlichung von MetaTrader-Meldungen über die Erstellung einer eigenen Webseite ohne Erfahrung in Sachen Webprogrammierung bis hin zur abschließenden Einbindung in den Mikrobloggingdienst eines sozialen Netzes, die zahlreichen Leserinnen und Lesern ermöglicht, die Prognosen zu lesen und zu verfolgen. Alle hier vorgestellten Lösungen sind hundertprozentig kostenlos und können von allen eingerichtet werden, die über E-Mail- und FTP-Grundkenntnisse verfügen. Dem Einsatz derselben Verfahren für berufsmäßige Webhosting- und gewerbliche Handelsprognosedienste steht nichts entgegen.

Übersetzung von MQL4 in MQL5 Übersetzung von MQL4 in MQL5

Dieser Beitrag bietet eine Kurzanleitung zu den Funktionen der Programmiersprache MQL4 und hilft bei der Umstellung Ihrer Programme von MQL4 auf MQL5. Für jede MQL4-Funktion (mit Ausnahme der Handelsfunktionen) werden die entsprechende Beschreibung und die Bereitstellung in MQL5 vorgestellt, was den Zeitaufwand für den Wechsel erheblich verkürzt. Der Einfachheit halber wurden die MQL4-Funktionen ähnlich wie in der MQL4-Hilfe in Gruppen aufgeteilt.