English Русский 中文 Español 日本語 Português
preview
Erstellen eines EA, der automatisch funktioniert (Teil 05): Manuelle Auslöser (II)

Erstellen eines EA, der automatisch funktioniert (Teil 05): Manuelle Auslöser (II)

MetaTrader 5Handel | 9 März 2023, 09:26
655 0
Daniel Jose
Daniel Jose

Einführung

Im vorigen Artikel mit dem Titel „Erstellen eines EA, der automatisch funktioniert (Teil 04): Manuelle Auslöser (I)“ Ich habe gezeigt, wie man mit ein wenig Programmierung Marktaufträge senden und schwebende Aufträge mit einer Kombination aus Tasten und Maus platzieren kann.

Am Ende des vorigen Artikels habe ich vorgeschlagen, dass es angebracht wäre, eine manuelle Nutzung des EA zuzulassen, zumindest für eine Weile. Dies erwies sich als viel interessanter als erwartet, da die ursprüngliche Idee war, 3-4 Artikel zu veröffentlichen, die zeigen, wie man tatsächlich mit der Entwicklung eines EAs beginnt, der automatisch handeln kann. Obwohl dies für Programmierer recht einfach ist, kann es für Anfänger, die gerade erst mit dem Programmieren beginnen, schwierig sein, da es nur wenig Material gibt, das klar erklärt, wie man bestimmte Dinge tatsächlich programmiert. Außerdem sollte man sich nicht auf einen bestimmten Wissensstand beschränken.

Da viele die in dieser Gemeinschaft veröffentlichten Artikel nutzen, um mit dem Programmieren zu beginnen, sehe ich dies als eine Gelegenheit, einige meiner Erfahrungen zu teilen, die auf jahrelanger Programmierung in C/C++ basieren, und zu zeigen, wie man einige Dinge in MQL5 implementiert, das C/C++ sehr ähnlich ist. Ich möchte zeigen, dass Programmieren nichts Mythisches ist, sondern dass alles real ist.

Nun, um die Nutzung unseres EA im manuellen Modus komfortabler zu gestalten, müssen wir ein paar Dinge tun. Diese Arbeit ist einfach und leicht für Programmierer, sodass wir direkt zur Sache kommen können. Wir werden nämlich Zeilen erstellen, die den Ort der Auftragslimits für die Aufträge angeben, die wir an den Handelsserver senden.

Diese Limits sind besser zu sehen, wenn wir mit der Maus einen Auftrag erteilen, d.h. wenn wir einen schwebenden Auftrag erstellen. Wenn der Auftrag bereits auf dem Server ist, wird die Anzeige von der MetaTrader 5-Plattform verwaltet. Doch bevor dies geschieht, müssen wir dem Nutzer zeigen, wo die Auftragslimits am wahrscheinlichsten platziert werden. Dies wird von uns, den Programmierern, erledigt. Die einzige Unterstützung, die wir von MetaTrader 5 erhalten, ist die Möglichkeit, horizontale Linien auf dem Chart zu verwenden. Abgesehen davon muss die gesamte Arbeit über die EA-Programmierung umgesetzt werden.

Dazu müssen wir lediglich den Code schreiben, der diese Linien an den richtigen Stellen im Diagramm platziert. Aber wir wollen das nicht auf irgendeine Art und Weise tun. Dies sollte gut kontrolliert werden, da wir den Code, den wir bereits erstellt haben, nicht gefährden wollen, und wir wollen keine zusätzliche Arbeit machen, falls wir die C_Mouse-Klasse und den OnChartEvent-Event-Handler in Zukunft aus dem EA entfernen müssen. Das liegt daran, dass ein automatisierter EA diese Dinge nicht braucht, ein manueller EA aber schon. Wir müssen dafür sorgen, dass diese Dinge so gut wie möglich nutzbar sind. 


Erstellen der Klasse C_Terminal

Zu diesem Zweck werden wir aus manuellen Vorgängen etwas Bequemes machen. Wir müssen Zeilen hinzufügen, die mögliche Grenzen eines Auftrags oder einer Position angeben, die gesendet werden sollen. Dabei werden wir doppelt geschriebenen Code in den Klassen C_Orders und C_Mouse entfernen. Wir werden also eine neue Klasse erstellen: die Klasse C_Terminal, die uns helfen wird, einige Dinge aufzubauen und zu isolieren, damit wir bequem arbeiten können. Durch die Verwendung dieser Klasse können wir in Zukunft sowohl automatisierte als auch manuelle EAs erstellen, ohne Gefahr zu laufen, dass der neue EA eine Art katastrophalen Fehler aufweist.

Das größte Problem besteht darin, dass viele Menschen bei der Erstellung eines neuen automatisierten EA von Grund auf neu beginnen. Dieser Ansatz führt häufig zu zahlreichen Fehlern, da es nicht genügend Kontrollen gibt.

Es wäre in der Tat sehr interessant, diese Klassen in eine private Bibliothek umzuwandeln. Da wir aber mit dem Modus etwas anderes vorhaben, werden wir jetzt nicht darüber nachdenken. Vielleicht werde ich das in Zukunft tun. Mal sehen, was wir tatsächlich tun werden. Wir beginnen wie folgt: Wie üblich erstellen wir eine Header-Datei namens C_Terminal.mqh. Dies ist der grundlegendste Code, der in jeder Klasse, die wir erstellen werden, immer vorhanden ist. Er ist unten aufgeführt:

class C_Terminal
{
        private :
        public  :
};

Initialisieren Sie Ihren Code in einer Klasse immer so, dass Sie nie vergessen, dass einige Punkte im privaten Bereich liegen müssen und andere im öffentlichen Bereich liegen können. Auch wenn Sie in einer Klasse nichts Privates zu deklarieren haben, ist es immer eine gute Idee, Dinge zu klären. Vor allem, weil Sie Ihren Code anderen Menschen zeigen können.

Ein gut abgegrenzter und gut geschriebener Code, d. h. ein Code, der leicht zu lesen ist, wird mit Sicherheit das Interesse anderer Personen wecken und sie dazu ermutigen, ihn zu analysieren, wenn Sie Hilfe bei der Behebung eines Problems benötigen. Code, der unübersichtlich, unorganisiert, ohne Registerkarten und oft ohne erklärende Kommentare ist, wird uninteressant. Selbst wenn die Idee gut ist, verbringt niemand gerne Zeit damit, den Code eines anderen zu organisieren, um zu verstehen, was man damit macht.

Das ist ein guter Rat. Natürlich sind meine eigenen Codes nicht perfekt, aber: bereinigen Sie immer Ihre Codes, verwenden Sie Tabulatoren, wenn Sie mehrere Zeilen, die innerhalb einer Prozedur verschachtelt sind, einfügen müssen, das hilft sehr. Nicht nur andere Menschen, sondern vor allem Sie selbst. Manchmal ist der Code so schlecht organisiert, dass selbst sein Schöpfer ihn nicht durchschauen kann. Wie sollten es dann andere Programmierer können?

Fangen wir also an, den Code zu schreiben und unserer Klasse eine Struktur zu geben. Die ersten Zeilen des Codes sind unten dargestellt.

class C_Terminal
{
        protected:
//+------------------------------------------------------------------+
                struct stTerminal
                {
                        ENUM_SYMBOL_CHART_MODE ChartMode;
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                };
//+------------------------------------------------------------------+
        private :
        public  :
};

Hier haben wir eine neue Sache - das reservierte Wort protected (geschützt). Aber was sagt uns das? In der Regel haben wir nur private und öffentliche Deklarationen verwendet. Also, was ist das? Eigentlich liegt sie irgendwo in der Mitte, zwischen den öffentlichen und den privaten. Um zu verstehen, was hier vor sich geht, müssen wir einige grundlegende Konzepte der objektorientierten Programmierung verstehen.

Eines der Konzepte ist Vererbung. Doch bevor wir uns mit dem Thema Vererbung befassen, wollen wir die Klasse am Beispiel einer einzelnen Person betrachten. Um das Konzept besser zu verstehen, sollten Sie sich jede Klasse als ein individuelles, einzigartiges und exklusives Lebewesen vorstellen. Nun können wir zur Erklärung übergehen.

Einige Informationen sind öffentlich, sodass nicht nur derjenige, der sie verwaltet, sondern auch andere von ihrer Nutzung und ihrem Wissen profitieren können. Diese Informationen sind immer im öffentlichen Teil des Codes enthalten. Bei anderen Informationen handelt es sich um private Daten einer Person, d. h. sie sind nur für diese Person zugänglich. Wenn die Person aufhört zu existieren, werden diese Informationen mit ihr sterben; und er war der einzige, der davon profitieren konnte. Betrachten Sie Informationen als eine persönliche Fähigkeit. Die Person kann sie nicht lehren oder an andere weitergeben, und niemand kann sie ihr wegnehmen. Diese Art von Informationen befindet sich im privaten Teil des Codes.

Es gibt aber auch Informationen, die in keines der genannten Konzepte passen. Sie befinden sich im geschützten Bereich, sodass die Person sie nutzen kann oder nicht. Die Hauptsache ist, dass sie an die Mitglieder der Familie weitergegeben werden können. Um zu verstehen, wie dies geschieht, sollten wir uns mit dem Thema Vererbung befassen.

Wenn wir zum Thema Vererbung kommen, ist es am einfachsten, an die Blutlinie zu denken. Es gibt drei Arten der Vererbung: public (öffentlich), private (privat) und protected (geschützt). Ich spreche hier von der Vererbung und nicht von den individuellen Problemen der einzelnen Mitglieder des Stammbaums.

Bei einer öffentlichen Vererbung werden die Informationen, Daten und Inhalte der Eltern an die Kinder und alle ihre Nachkommen, einschließlich der Enkelkinder und darüber hinaus, weitergegeben, und jeder außerhalb der Blutlinie kann theoretisch auf diese Dinge zugreifen. Achten Sie auf die Formulierung „theoretisch“ da es bei einer solchen Übertragung einige Nuancen gibt. Darauf werden wir später noch genauer eingehen. Konzentrieren wir uns zunächst auf die Vererbung. Bei der privaten Vererbung hat nur die erste Generation Zugriff auf Informationen, während die nachfolgenden Generationen keinen Zugriff haben, selbst wenn sie Teil der Blutlinie sind.

Und das letzte, was wir haben, ist ein geschütztes Vererbung. Damit wird etwas geschaffen, das der privaten Vererbung sehr ähnlich ist. Aber es gibt einen erschwerenden Faktor, der dazu führt, dass viele Menschen diese Konzepte nicht verstehen: die Elternklausel. Das liegt daran, dass es eine Art Regel für die Weitergabe von Informationen gibt, auch in Fällen öffentlicher Vererbung. Es gibt Dinge, die außerhalb der Blutlinie nicht zugänglich sind. Um dies zu verstehen, siehe die nachstehende Tabelle, in der ich dieses Thema kurz zusammenfasse:

Definition in der übergeordneten Klasse Vererbungsart Zugriff von der Kindklasse aus Zugriff durch Aufruf von der untergeordneten Klasse 
privat public: Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
public: public: Zugriff ist erlaubt Zugriff auf Daten oder Prozeduren der Basisklasse möglich
protected public: Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
privat privat Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
public: privat Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
protected privat Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
privat protected Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
public: protected Zugriff ist erlaubt  Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse
protected protected Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Prozeduren der Basisklasse

Tabelle 1) Vererbungssystem auf der Grundlage der Informationsdefinition

Bitte beachten Sie, dass je nach dem in der Datentypdefinition bei der Vererbung verwendeten Codeteil das Kind Zugriff auf die Daten haben kann oder nicht. Aber jeder Aufruf außerhalb der Blutlinie hat keinen Zugriff, außer in dem einen Fall, in dem die Daten des Elternteils als öffentlich deklariert sind und das Kind auf die gleiche Weise erbt. Darüber hinaus ist es nicht möglich, auf Informationen außerhalb des Stammbaums zuzugreifen.

Ohne das in Tabelle 01 dargestellte Schema zu verstehen, respektieren viele, weniger erfahrene Programmierer die objektorientierte Programmierung nicht. Das liegt daran, dass sie gar nicht wissen, wie die Dinge wirklich funktionieren. Diejenigen von Ihnen, die meine Artikel und meinen Code verfolgt haben, sollten bemerkt haben, dass ich viel objektorientierte Programmierung verwende.

Der Grund dafür ist, dass es ein Höchstmaß an Sicherheit bei der Implementierung sehr komplexer Dinge bietet, die anders nicht möglich wären. Und ich spreche nicht nur von der Vererbung. Darüber hinaus haben wir auch Polymorphismus und Kapselung, aber das sind Themen für ein anderes Mal. Obwohl die Kapselung Teil von Tabelle 01 ist, verdient sie eine ausführlichere Erklärung, die den Rahmen dieses Artikels sprengen würde.

Wenn Sie genau hinsehen, können Sie feststellen, dass die Struktur im obigen Code dieselbe ist wie in der Klasse C_Orders. Achten Sie darauf, denn die Klasse C_Order verliert die Definition dieser Daten und beginnt, die Daten von der Klasse C_Terminal zu erben. Aber bleiben wir erst einmal innerhalb der Klasse C_Terminal.

Das nächste, was der Klasse C_Terminal hinzuzufügen ist, sind Funktionen, die sowohl der Klasse C_Mouse als auch der Klasse C_Orders gemeinsam sind. Diese Funktionen werden dem geschützten Teil der Klasse C_Terminal hinzugefügt. Wenn also die Klassen C_Mouse und C_Orders von C_Terminal geerbt werden, werden diese Funktionen und Prozeduren der Tabelle 01 folgen. Wir fügen also den folgenden Code hinzu:

//+------------------------------------------------------------------+
inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_TerminalInfo.PointPerTick) * m_TerminalInfo.PointPerTick;
                        }
//+------------------------------------------------------------------+
inline double FinanceToPoints(const double Finance, const uint Leverage)
                        {
                                double volume = m_TerminalInfo.VolMinimal + (m_TerminalInfo.VolStep * (Leverage - 1));
                                
                                return AdjustPrice(MathAbs(((Finance / volume) / m_TerminalInfo.AdjustToTrade)));
                        };
//+------------------------------------------------------------------+

Diese Codes werden in beiden Klassen nicht mehr doppelt aufgeführt. Jetzt befindet sich der gesamte Code ausschließlich innerhalb der Klasse C_Terminal, was die Wartung, das Testen und mögliche Änderungen erleichtert. So wird unser Code zuverlässiger und attraktiver, je mehr wir ihn verwenden und erweitern.

Innerhalb der Klasse C_Terminal gibt es noch ein paar weitere Dinge zu beachten. Aber sehen wir uns zunächst den Klassenkonstruktor an. Er ist unten aufgeführt:

        public  :
//+------------------------------------------------------------------+
                C_Terminal()
                        {
                                m_TerminalInfo.nDigits          = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_TerminalInfo.VolMinimal       = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_TerminalInfo.VolStep          = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_TerminalInfo.PointPerTick     = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_TerminalInfo.ValuePerPoint    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_TerminalInfo.AdjustToTrade    = m_TerminalInfo.ValuePerPoint / m_TerminalInfo.PointPerTick;
                                m_TerminalInfo.ChartMode        = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE);
                        }
//+------------------------------------------------------------------+

Sie ist fast identisch mit der in der Klasse C_Orders vorhandenen. Jetzt können wir den Code der Klasse C_Orders so ändern, dass sie erbt, was wir in der Klasse C_Terminal implementieren. Aber es gibt eine wichtige Sache. Sehen Sie sich den Codeteil an, der die Deklaration der Struktur enthält, die im obigen Konstruktor initialisiert wird. Sie können sehen, dass es keine Variable gibt. Warum?

Der Grund dafür ist das Einkapseln. Sie sollten dem Code außerhalb der Klasse nicht erlauben, auf den Inhalt interner Klassenvariablen zuzugreifen und ihn zu verändern. Dies ist ein schwerwiegender Programmierfehler, den der Compiler zwar nicht beanstandet, den Sie aber NIEMALS zulassen sollten. Absolut alle globalen Variablen einer Klasse müssen immer innerhalb des privaten Teils deklariert werden. Die Variablendeklaration sieht wie folgt aus.

//+------------------------------------------------------------------+
        private :
                stTerminal m_TerminalInfo;
        public  :
//+------------------------------------------------------------------+

Beachten Sie, dass die globale Klassenvariable zwischen dem privaten und dem öffentlichen Teil definiert ist. Mit anderen Worten, wir garantieren die Kapselung von Informationen und fügen gleichzeitig Vererbung zu unserem Code hinzu. Neben der Erweiterung des Code-Nutzens steigt auch die Robustheit exponentiell an.

Aber wie werden wir auf die erforderlichen Daten in der oben genannten Klasse zugreifen? Wir müssen einen gewissen Zugriff auf die Variablen der übergeordneten Klasse gewähren, die in diesem Fall die Klasse C_Terminal ist. Ja, wir brauchen das. Aber wir sollten dies nicht tun, indem wir diese Variablen öffentlich oder geschützt machen. Dies ist ein Programmierfehler. Wir müssen einige Mittel hinzufügen, damit die abgeleiteten Klassen auf die Werte der übergeordneten Klasse zugreifen können. Aber hier liegt die Gefahr, die wichtig ist: ES DARF ABGELEITETEN KLASSEN NICHT GESTATTET WERDEN, DIE VARIABLEN DER ELTERNKLASSE ZU VERÄNDERN

Zu diesem Zweck müssen wir die Variable in eine Konstante umwandeln. Das heißt, die übergeordnete Klasse kann die Werte der Variablen nach Bedarf und bei Bedarf ändern. Wenn eine untergeordnete Klasse eine Änderung an einer Variablen der übergeordneten Klasse vornehmen will, muss die untergeordnete Klasse eine von der übergeordneten Klasse bereitgestellte Prozedur aufrufen, um den gewünschten Wert für eine bestimmte Variable in der übergeordneten Klasse zu ermitteln. Eine solche Prozedur, die in der übergeordneten Klasse implementiert sein sollte, prüft, ob die von der untergeordneten Klasse übergebenen Daten gültig sind. Wenn sie gültig ist, wird die Prozedur die vom Kind angeforderten Änderungen in der Elternklasse durchsetzen.

Aber auf keinen Fall wird ein untergeordnetes Element in der Lage sein, übergeordnete Daten zu ändern, ohne dass die übergeordnete Klasse von einer solchen Änderung weiß. Ich habe viele potenziell gefährliche Codes gesehen, die dies tun. Manche Leute behaupten, dass der Aufruf einer Prozedur innerhalb der übergeordneten Klasse zur Überprüfung der von der untergeordneten Klasse bereitgestellten Daten den Code verlangsamt oder das Programm bei der Ausführung zum Absturz bringt. Dies ist jedoch ein Irrtum. Die Kosten und das Risiko, dass innerhalb der übergeordneten Klasse falsche Werte geworfen werden, sind die geringe Geschwindigkeitssteigerung nicht wert, wenn eine solche Steigerung durch den Verzicht auf den Aufruf von Datenprüfverfahren erreicht wird. Machen Sie sich also keine Sorgen, dass der Code dadurch langsam wird.

Jetzt kommen wir zu einem weiteren Punkt. Als Programmierer, der ein Profi ist oder werden will, sollten Sie jede Prozedur, die an andere Klassen vererbt werden soll, immer zuerst in einem geschützten Codeteil unterbringen und erst als letzten Ausweg die Prozedur in den öffentlichen Bereich verlegen. Wir geben der Kapselung immer den Vorrang. Nur wenn es wirklich notwendig ist, verlassen wir die Kapselung und erlauben die öffentliche Nutzung von Funktionen und beliebigen Prozeduren. Aber wir tun dies nie mit Variablen, da sie immer privat sein sollten.

Um eine Prozedur oder eine Funktion zu erstellen, die es einer untergeordneten Klasse ermöglicht, auf Daten der übergeordneten Klasse zuzugreifen, verwenden wir die folgende Funktion:

inline const stTerminal GetTerminalInfos(void) const
                        {
                                return m_TerminalInfo;
                        }

Jetzt möchte ich, dass Sie genau aufpassen, was ich Ihnen jetzt erkläre, denn es ist extrem wichtig und macht den Unterschied zwischen gut geschriebenem Code und einfach nur gut gemachtem Code aus.

In diesem Artikel habe ich immer wieder darauf hingewiesen, dass wir dem Code außerhalb der Klasse, in der die Variablen deklariert und verwendet werden, irgendwie den Zugriff auf sie ermöglichen müssen. Ich sagte, dass die Variable im Idealfall innerhalb der Klasse, in der sie deklariert ist, jederzeit geändert werden kann. Außerhalb der Klasse sollte die Variable jedoch als Konstante behandelt werden, d. h. ihr Wert kann nicht geändert werden.

Der obige Code ist zwar extrem einfach, tut aber genau dies. Mit anderen Worten, Sie können garantieren, dass innerhalb der Klasse C_Terminal eine zugängliche Variable vorhanden ist und ihr Wert geändert werden kann, aber außerhalb der Klasse wird die gleiche Variable als Konstante behandelt. Wie wird das gemacht und warum haben wir hier zwei reservierte Wörter?

Betrachten wir sie der Reihe nach: Das erste Auftreten von const teilt dem Compiler mit, dass die Rückgabevariable m_TerminalInfo als Konstante in der aufrufenden Funktion betrachtet werden soll. Versucht der Aufrufer, den Wert eines der in der zurückgegebenen Variablen enthaltenen Strukturelemente zu ändern, erzeugt der Compiler einen Fehler und verhindert die Kompilierung des Codes. Das zweite Auftreten von const teilt dem Compiler mit, dass, wenn hier ein Wert aus irgendeinem Grund geändert wird, er einen Fehler auswerfen soll. Sie können also keine Daten innerhalb dieser Funktion ändern, selbst wenn Sie dies wollen, da sie nur dazu da ist, einen Wert zurückzugeben.

Einige Programmierer machen manchmal diese Art von Fehler: Sie ändern Variablenwerte innerhalb von Funktionen oder Prozeduren, wo diese Variablen nur für den externen Zugriff, nicht aber für eine Art von Faktorisierung verwendet werden sollten. Durch die Anwendung des obigen Code-Prinzips vermeiden Sie diese Art von Fehler.

Wir haben zwar unsere Basisklasse C_Terminal noch nicht fertiggestellt, aber wir können doppelte Teile im Code entfernen, sodass die Klasse C_Mouse den gleichen Codetyp hat wie die Klasse C_Orders. Da es aber viel einfacher ist, die Klasse C_Mouse zu ändern, wollen wir sehen, wie sie jetzt aussieht, da sie die Klasse C_Terminal erbt. Dies ist im folgenden Code zu sehen:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_MouseName "MOUSE_H"
//+------------------------------------------------------------------+
#define def_BtnLeftClick(A)     ((A & 0x01) == 0x01)
#define def_SHIFT_Press(A)      ((A & 0x04) == 0x04)
#define def_CTRL_Press(A)       ((A & 0x08) == 0x08)
//+------------------------------------------------------------------+
class C_Mouse : private C_Terminal
{
// Inner class code ....
};

Hier binden wir die Header-Datei der Klasse C_Terminal ein. Beachten Sie, dass der Dateiname hier in doppelte Anführungszeichen gesetzt wird. Dies teilt dem Compiler mit, dass sich die Datei C_Terminal.mqh im gleichen Verzeichnis befindet wie die Datei C_Mouse.mqh. Wenn Sie beide Dateien an einen anderen Ort verschieben müssen, kann der Compiler auf diese Weise immer die richtige Datei finden, da er weiß, dass sie sich im selben Verzeichnis befinden.

Nach dem Prinzip, die Arbeit immer mit dem geringstmöglichen Zugriff zu beginnen, machen wir die Klasse C_Mouse zu einem privaten Kind der Klasse C_Terminal. Jetzt können wir die Funktion AdjustPrice aus der Klasse C_Mouse löschen, ebenso wie die Klasse PointPerTick, die in C_Mouse existiert, da wir jetzt die Prozedur aus der Klasse C_Terminal verwenden werden. Da die Klasse privat vererbt wird und die Funktion AdjustPrice sich im geschützten Teil des Codes befindet, erhalten wir in C_Terminal das Ergebnis gemäß Tabelle 01. Daher ist es nicht möglich, die AdjustPrice-Prozedur außerhalb der Klasse C_Mouse aufzurufen, wie es zuvor geschehen ist.

Alle diese Änderungen in der Klasse C_Mouse sind jedoch vorübergehend. Wir werden noch ein paar Änderungen vornehmen, um Grenzlinien hinzuzufügen, die wir benötigen, wenn wir den EA manuell verwenden. Aber das werden wir später tun. Schauen wir uns an, wie man in der Klasse C_Orders tiefgreifendere Änderungen vornehmen kann. Diese Änderungen erfordern ein eigenes Thema. Lassen Sie uns also weitermachen.


Ändern der Klasse C_Orders nach der Vererbung von C_Terminal

Wir beginnen die Änderungen ähnlich wie bei der Klasse C_Mouse. Dann kommen die Unterschiede, die Sie im unten stehenden Code sehen können.

#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_Orders : private C_Terminal
{
        private :
//+------------------------------------------------------------------+
                MqlTradeRequest m_TradeRequest;
                ulong           m_MagicNumber;
                struct st00
                {
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                        bool    PlotLast;
                        ulong   MagicNumber;
                }m_Infos;
//+------------------------------------------------------------------+

Das ganze Prinzip ist fast dasselbe wie das der Klasse C_Mouse, aber es gibt einige Unterschiede. Zunächst löschen wir die Struktur der Klasse C_Orders, wie in den hervorgehobenen Zeilen gezeigt. Aber wir brauchen bestimmte Daten aus der Struktur, also machen wir sie privat, aber als eine normale Variable.

Da wir die hervorgehobenen Teile entfernen, werden Sie vielleicht denken, dass es sehr viel Mühe kostet, den neuen Code zu schreiben. Es wird ein ziemliches Stück Arbeit sein. Gehen wir gleich zum Konstruktor für diese Klasse C_Orders über. Hier werden die Veränderungen tatsächlich beginnen. Nachfolgend finden Sie den Konstruktor der neuen Klasse.

                C_Orders(const ulong magic)
                        :C_Terminal(), m_MagicNumber(magic)
                        {
                                m_Infos.MagicNumber     = magic;
                                m_Infos.nDigits         = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
                                m_Infos.VolMinimal      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                                m_Infos.VolStep         = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                                m_Infos.PointPerTick    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                m_Infos.ValuePerPoint   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
                                m_Infos.AdjustToTrade   = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
                                m_Infos.PlotLast        = (SymbolInfoInteger(_Symbol, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_LAST);
                        };

Wie Sie sehen können, wurde der gesamte interne Inhalt des Konstruktors entfernt, aber wir erzwingen hier den Aufruf des Konstruktors der Klasse C_Terminal. Damit soll sichergestellt werden, dass er vor allen anderen aufgerufen wird. Normalerweise macht das der Compiler für uns, aber wir werden es explizit implementieren und gleichzeitig an einer anderen Stelle im Code eine Variable initialisieren, die die magische Zahl angibt

Normalerweise geschieht dies in den Konstruktoren, da der Wert der Variablen bereits vor der Ausführung des Codes definiert werden soll, damit der Compiler einen geeigneten Code erzeugen kann. Wenn der Wert jedoch konstant ist, wie es normalerweise der Fall ist, sparen wir auf diese Weise etwas Zeit bei der Initialisierung der Klasse C_Orders. Beachten Sie jedoch folgendes Detail: Wir haben nur dann einen Nutzen, wenn der Wert eine Konstante ist, andernfalls wird der Compiler einen Code erzeugen, der uns keinen praktischen Nutzen bringt.

Als Nächstes müssen die Funktionen AdjustPrice und FinanceToPoints aus der Klasse C_Orders entfernt werden, aber da dies direkt geschehen kann, werde ich es hier nicht zeigen. Von nun an werden diese Aufrufe den Code innerhalb der Klasse C_Terminal verwenden.

Schauen wir uns einen der Codeteile an, die die in der Klasse C_Terminal deklarierte Variable verwenden werden. Dies dient dazu, zu verstehen, wie man auf die Variablen der übergeordneten Klasse zugreifen kann. Schauen wir uns den folgenden Code an:

inline void CommonData(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double Desloc;
                                
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.magic            = m_Infos.MagicNumber;
                                m_TradeRequest.magic            = m_MagicNumber;
                                m_TradeRequest.symbol           = _Symbol;
                                m_TradeRequest.volume           = NormalizeDouble(m_Infos.VolMinimal + (m_Infos.VolStep * (Leverage - 1)), m_Infos.nDigits);
                                m_TradeRequest.volume           = NormalizeDouble(GetTerminalInfos().VolMinimal + (GetTerminalInfos().VolStep * (Leverage - 1)), GetTerminalInfos().nDigits);
                                m_TradeRequest.price            = NormalizeDouble(Price, m_Infos.nDigits);
                                m_TradeRequest.price            = NormalizeDouble(Price, GetTerminalInfos().nDigits);
                                Desloc = FinanceToPoints(FinanceStop, Leverage);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), m_Infos.nDigits);
                                m_TradeRequest.sl               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? -1 : 1)), GetTerminalInfos().nDigits);
                                Desloc = FinanceToPoints(FinanceTake, Leverage);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), m_Infos.nDigits);
                                m_TradeRequest.tp               = NormalizeDouble(Desloc == 0 ? 0 : Price + (Desloc * (type == ORDER_TYPE_BUY ? 1 : -1)), GetTerminalInfos().nDigits);
                                m_TradeRequest.type_time        = (IsDayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
                                m_TradeRequest.stoplimit        = 0;
                                m_TradeRequest.expiration       = 0;
                                m_TradeRequest.type_filling     = ORDER_FILLING_RETURN;
                                m_TradeRequest.deviation        = 1000;
                                m_TradeRequest.comment          = "Order Generated by Experts Advisor.";
                        }

Die hervorgehobenen Teile wurden entfernt und durch anderen hervorgehobenen Code ersetzt. Achten Sie nun bitte auf den gelb markierten Code. Er enthält etwas, das viele vielleicht noch nie gesehen haben. Bitte beachten Sie, dass diese gelb markierten Codezeilen eine Funktion enthalten, die als Struktur betrachtet wird. Aber was ist das für eine verrückte Sache! 😵😱

Beruhigen Sie sich, lieber Leser. Machen Sie sich keine Sorgen. Das ist nicht verrückt. Es geht nur darum, das Programmieren auf eine etwas exotischere Art und Weise anzuwenden, als Sie es bisher gesehen haben. Um zu verstehen, warum dies erlaubt ist und warum es funktioniert, sehen wir uns die Funktion separat an:

GetTerminalInfos().nDigits

Gehen Sie nun zurück zum Code der Klasse C_Terminal und sehen Sie sich an, wie diese Funktion deklariert ist. Sie lautet wie folgt:

inline const stTerminal GetTerminalInfos(void) const
                        {
                                return m_TerminalInfo;
                        }

Beachten Sie, dass die Funktion GetTerminalInfos eine Struktur zurückgibt, die im folgenden Code dargestellt ist:

                struct stTerminal
                {
                        ENUM_SYMBOL_CHART_MODE ChartMode;
                        int     nDigits;
                        double  VolMinimal,
                                VolStep,
                                PointPerTick,
                                ValuePerPoint,
                                AdjustToTrade;
                };

Für den Compiler wäre das, was wir mit dem Code GetTerminalInfos().nDigits tun, gleichbedeutend mit der Aussage, dass GetTerminalInfos() keine Funktion, sondern eine Variable ist 😲. Sind Sie verwirrt? Nun, die Dinge werden noch interessanter, denn für einen Compiler entspricht der Code GetTerminalInfos().nDigits dem folgenden Code:

stTerminal info;
int value = info.nDigits;

value = 10;
info.nDigits = value;


Das heißt, Sie können den Wert nicht nur lesen, sondern auch schreiben. Wenn Sie also versehentlich das folgende Fragment schreiben:

GetTerminalInfos().nDigits = 10;

Der Compiler wird verstehen, dass der Wert 10 der Variablen zugewiesen werden soll, auf die die Funktion GetTerminalInfos() verweist. Dies wäre ein Problem, da sich die betreffende Variable in der Klasse C_Terminal befindet und diese Variable im privaten Teil des Codes deklariert ist. Das bedeutet, dass er durch den vorhergehenden Aufruf nicht verändert werden kann. Da aber die Funktion GetTerminalInfos() ebenfalls geschützt ist (obwohl sie auch öffentlich sein kann und das das Gleiche wäre), hat die als privat deklarierte Variable die gleiche Zugriffsebene wie die Funktion, die sich auf sie bezieht.

Ist Ihnen klar, wie gefährlich diese Dinge sein können? Das heißt, selbst wenn Sie eine Variable als privat deklarieren, aber den Code für eine Funktion oder Prozedur, die sich auf sie bezieht, falsch schreiben, können Sie oder jemand anderes ihren Wert versehentlich ändern. Und es bricht das ganze Konzept der Verkapselung.

Da die Funktion jedoch bei der Deklaration mit dem Schlüsselwort const eingeleitet wurde, ändert dies alles, da der Compiler die Funktion GetTerminalInfo() nun anders sieht. Um dies zu verstehen, versuchen Sie einfach, den unten stehenden Code irgendwo in der Klasse C_Orders zu verwenden:

GetTerminalInfos().nDigits = 10;

Wenn Sie dies versuchen, wird der Compiler einen Fehler ausgeben. Denn der Compiler betrachtet GetTerminalInfos().nDigits oder irgendetwas anderes innerhalb der Struktur, auf die sich GetTerminalInfos() bezieht, als Konstante, und der konstante Wert kann nicht geändert werden. Dies wird als Fehler angesehen.

Verstehen Sie jetzt, wie man sich mit einer Variablen auf konstante Daten bezieht? So ist die Struktur, auf die sich die Funktion GetTerminalInfos() bezieht, für die Klasse C_Terminal eine Variable, aber für jeden anderen Code ist sie eine Konstante 😁.

Nun, da ich diesen Teil erklärt habe, lassen Sie uns zu anderen Änderungen übergehen. Ich denke, Sie können jetzt verstehen, was passiert und woher die Daten stammen, auf die C_Orders verweist. Die nächste Funktion, die geändert werden muss, finden Sie gleich unten:

                ulong CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const double FinanceStop, const double FinanceTake, const uint Leverage, const bool IsDayTrade)
                        {
                                double  bid, ask;
                                
                                bid = SymbolInfoDouble(_Symbol, (m_Infos.PlotLast ? SYMBOL_LAST : SYMBOL_BID));
                                bid = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : SYMBOL_BID));
                                ask = (m_Infos.PlotLast ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                ask = (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? bid : SymbolInfoDouble(_Symbol, SYMBOL_ASK));
                                CommonData(type, AdjustPrice(Price), FinanceStop, FinanceTake, Leverage, IsDayTrade);
                                m_TradeRequest.action   = TRADE_ACTION_PENDING;
                                m_TradeRequest.type     = (type == ORDER_TYPE_BUY ? (ask >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : 
                                                                                    (bid < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));                              
                                
                                return (((type == ORDER_TYPE_BUY) || (type == ORDER_TYPE_SELL)) ? ToServer() : 0);
                        };

Und das letzte, was geändert werden muss:

                bool ModifyPricePoints(const ulong ticket, const double Price, const double PriceStop, const double PriceTake)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.symbol   = _Symbol;
                                if (OrderSelect(ticket))
                                {
                                        m_TradeRequest.action   = (Price > 0 ? TRADE_ACTION_MODIFY : TRADE_ACTION_REMOVE);
                                        m_TradeRequest.order    = ticket;
                                        if (Price > 0)
                                        {
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), m_Infos.nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                                m_TradeRequest.price      = NormalizeDouble(AdjustPrice(Price), GetTerminalInfos().nDigits);
                                                m_TradeRequest.sl         = NormalizeDouble(AdjustPrice(PriceStop), GetTerminalInfos().nDigits);
                                                m_TradeRequest.tp         = NormalizeDouble(AdjustPrice(PriceTake), GetTerminalInfos().nDigits);
                                                m_TradeRequest.type_time  = (ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME) ;
                                                m_TradeRequest.expiration = 0;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        m_TradeRequest.action   = TRADE_ACTION_SLTP;
                                        m_TradeRequest.position = ticket;
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), m_Infos.nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), m_Infos.nDigits);
                                        m_TradeRequest.tp       = NormalizeDouble(AdjustPrice(PriceTake), GetTerminalInfos().nDigits);
                                        m_TradeRequest.sl       = NormalizeDouble(AdjustPrice(PriceStop), GetTerminalInfos().nDigits);
                                }else return false;
                                ToServer();
                                
                                return (_LastError == ERR_SUCCESS);
                        };

Jetzt werden wir diesen Teil beenden. Die Stabilität des Codes ist gleich geblieben, während wir seine Robustheit verbessert haben. Da wir bisher doppelte Funktionen hatten, bestand die Gefahr, dass sie in einer Klasse geändert werden und in einer anderen gleich bleiben. Wenn ein Fehler auftritt, wäre es nicht einfach, ihn zu korrigieren, da wir ihn zwar in einer Klasse beheben könnten, er aber in der anderen Klasse bestehen bliebe, sodass der Code weniger robust und zuverlässig wäre.

Denken Sie immer daran: Ein wenig Arbeit, um Ihren Code in Bezug auf Stabilität und Robustheit zu verbessern, ist keine Arbeit, sondern nur ein Hobby 😁.

Aber wir sind noch nicht fertig mit dem, was wir in diesem Artikel tun wollten. Wir wollten Grenzlinien (Take-Profit und Stop-Loss) hinzufügen, um diese Niveaus zu kennen, wenn wir eine Order manuell platzieren. Dieser Teil fehlt noch. Ohne sie können wir diesen Artikel nicht beenden und mit dem nächsten fortfahren. Wir werden hier jedoch einen neuen Abschnitt einrichten, um dieses Thema von den bereits behandelten Themen zu trennen.


Erstellen von Take-Profit- und Stop-Loss-Linien

Lassen Sie uns nun über die folgende Frage nachdenken: Wo ist der beste Platz, um den Code einzufügen, der diese Zeilen erzeugt? Wir haben einen Aufrufpunkt. Er ist unten aufgeführt:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint            BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
        {
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
        }
        if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus))
        {
// This point ...
                if (def_BtnLeftClick(BtnStatus) && (mem == 0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04);
        }else mem = 0;
}

Der gelb markierte Teil ist der Bereich, in dem wir die Aufforderung zur Anzeige der Take-Profit- und Stop-Loss-Linien hinzufügen sollten. Aber es gibt ein wichtiges Detail: Wo sollen wir den Code für diese Zeilen schreiben?

Die beste Alternative dazu (und ich denke, jeder wird ihr zustimmen) ist, diesen Code zur Klasse C_Mouse hinzuzufügen. Wenn wir also die Maus entfernen, verschwinden auch die Linien. So werden wir das machen. Öffnen wir die Klasse C_Mouse, um die Linien zu erstellen, die Take-Profit und Stop-Loss darstellen.

Aber ich werde etwas anderes machen als das, was wir bisher gesehen haben. Ich werde die Zeilen nicht zu OnChartEvent hinzufügen, sondern zur Ereignisbehandlung innerhalb der C_Mouse-Klasse. Das ist der bessere Weg, auch wenn Sie einige andere Änderungen am EA-Code vornehmen müssen, aber wir werden dies für später aufheben. Öffnen wir die Header-Datei C_Mouse.mqh und implementieren wir alles, was wir brauchen.

Als erstes fügen wir einige neue Definitionen hinzu, wie unten gezeigt:

#define def_PrefixNameObject    "MOUSE_"
#define def_MouseLineName       def_PrefixNameObject + "H"
#define def_MouseLineTake       def_PrefixNameObject + "T"
#define def_MouseLineStop       def_PrefixNameObject + "S"
#define def_MouseName           "MOUSE_H"

Beachten Sie, dass die alte Definition gelöscht wurde. Wir machen die Dinge ein wenig anders, obwohl das Programmieren immer noch angenehm ist. Um den Programmieraufwand zu verringern, sollten wir das Erstellungsverfahren auf eine andere Weise ändern:

                void CreateLineH(void)
                void CreateLineH(const string szName, const color cor)
                        {
                                ObjectCreate(m_Infos.Id, def_MouseName, OBJ_HLINE, 0, 0, 0);
                                ObjectSetString(m_Infos.Id, def_MouseName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_BACK, false);
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_COLOR, m_Infos.Cor);
                                ObjectCreate(m_Infos.Id, szName, OBJ_HLINE, 0, 0, 0);
                                ObjectSetString(m_Infos.Id, szName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(m_Infos.Id, szName, OBJPROP_BACK, false);
                                ObjectSetInteger(m_Infos.Id, szName, OBJPROP_COLOR, cor);
                        }

Jetzt werden alle Linien auf eine eindeutige Weise erstellt, und wir müssen nur noch den Namen und die Farbe angeben. Ich musste 2 weitere Variablen erstellen, um Farben zu speichern, aber ich denke nicht, dass sie hier gezeigt werden müssen. Gehen wir also zum Konstruktor über, da dieser jetzt viel mehr Daten als zuvor erhalten muss, was Sie unten sehen können:

                C_Mouse(const color corPrice, const color corTake, const color corStop, const double FinanceStop, const double FinanceTake, const uint Leverage)
                        {
                                m_Infos.Id        = ChartID();
                                m_Infos.CorPrice  = corPrice;
                                m_Infos.CorTake   = corTake;
                                m_Infos.CorStop   = corStop;
                                m_Infos.PointsTake= FinanceToPoints(FinanceTake, Leverage);
                                m_Infos.PointsStop= FinanceToPoints(FinanceStop, Leverage);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_MOUSE_MOVE, true);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, true);
                                CreateLineH(def_MouseLineName, m_Infos.CorPrice);
                        }

Wie ich bereits erwähnt habe, musste ich ein paar mehr Variablen erstellen, aber der Preis dafür ist gering im Vergleich zu dem, was wir in Bezug auf die Funktionen bekommen können. Achten Sie auf die folgenden Punkte: Ich werde nicht auf den EA-Aufruf warten, um die finanziellen Werte in Punkte umzurechnen. Wir werden es genau hier und jetzt tun. Das spart uns später Zeit, denn der Zugriff auf eine Variable ist viel schneller als der Aufruf einer Funktion. Aber was ist mit dem Destruktor? In der Tat ist es nicht schwieriger. Alles, was wir dort tun müssen, ist, den Typ der Funktion zu ändern, die für das Löschen des Objekts verantwortlich ist. Siehe unten:

                ~C_Mouse()
                        {
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, false);
                                ObjectsDeleteAll(m_Infos.Id, def_PrefixNameObject);
                                ObjectDelete(m_Infos.Id, def_MouseName);
                        }

Diese Funktion kann alle Objekte entfernen, deren Namen auf eine bestimmte Weise beginnen. Sie ist in vielen Situationen sehr nützlich und vielseitig einsetzbar. Hier ist das letzte Verfahren, das geändert werden muss. Schauen wir uns an, wie ich die Zeilen für die Preislimits im folgenden Code implementiert habe:

                void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
                        {
                                int w;
                                datetime dt;
                                static bool bView = false;
                                
                                switch (id)
                                        {
                                                case CHARTEVENT_OBJECT_DELETE:
                                                        if (sparam == def_MouseName) CreateLineH();
                                                        if (sparam == def_MouseLineName) CreateLineH(def_MouseLineName, m_Infos.CorPrice);
                                                        break;
                                                case CHARTEVENT_MOUSE_MOVE:
                                                        ChartXYToTimePrice(m_Infos.Id, (int)lparam, (int)dparam, w, dt, m_Infos.Price);
                                                        ObjectMove(m_Infos.Id, def_MouseName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price));
                                                        ObjectMove(m_Infos.Id, def_MouseLineName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price));
                                                        m_Infos.BtnStatus = (uint)sparam;
                                                        if (def_CTRL_Press(m_Infos.BtnStatus) != def_SHIFT_Press(m_Infos.BtnStatus))
                                                        {
								if (!bView)
								{
									if (m_Infos.PointsTake > 0) CreateLineH(def_MouseLineTake, m_Infos.CorTake);
									if (m_Infos.PointsStop > 0) CreateLineH(def_MouseLineStop, m_Infos.CorStop);
									bView = true;
								}
								if (m_Infos.PointsTake > 0) ObjectMove(m_Infos.Id, def_MouseLineTake, 0, 0, m_Infos.Price + (m_Infos.PointsTake * (def_SHIFT_Press(m_Infos.BtnStatus) ? 1 : -1)));
								if (m_Infos.PointsStop > 0) ObjectMove(m_Infos.Id, def_MouseLineStop, 0, 0, m_Infos.Price + (m_Infos.PointsStop * (def_SHIFT_Press(m_Infos.BtnStatus) ? -1 : 1)));
                                                        }else if (bView)
                                                        {
                                                                ObjectsDeleteAll(m_Infos.Id, def_PrefixNameObject);
                                                                bView = false;
                                                        }
                                                        ChartRedraw();
                                                        break;
                                        }
                        }

Zuerst musste ich zwei Zeilen des alten Codes entfernen und dann zwei Zeilen des aktualisierten Codes hinzufügen. Aber das wichtige Detail kommt, wenn wir uns mit dem Mausbewegungsereignis befassen werden. Dort fügen wir einige neue Zeilen hinzu. Als Erstes prüfen wir, ob die SHIFT- oder die STRG-Taste gedrückt ist, aber nicht gleichzeitig, und wenn ja, dann gehen wir zum nächsten Schritt über.

Wenn das Ergebnis Falsch ist, prüfen wir nun, ob die Grenzwertlinien im Chart angezeigt werden. Wenn ja, entfernen wir alle Mauszeilen. Dies ist kein Problem, da MetaTrader 5 sofort ein Ereignis erzeugt, um zu melden, dass Objekte vom Bildschirm entfernt wurden. Wenn die Ereignisbehandlung des Bildschirms aufgerufen wird, werden wir angewiesen, die Kurslinie wieder auf dem Chart zu platzieren.

Aber kommen wir zurück zu dem Moment, in dem die Grenzwertlinien angezeigt werden, wenn Sie die SHIFT- oder STRG-Taste gedrückt halten. In diesem Fall wird geprüft, ob bereits Zeilen auf dem Bildschirm vorhanden sind. Wenn dies nicht der Fall ist, erstellen wir sie, solange der Wert größer als Null ist, da wir kein fremdes Element im Diagramm benötigen. Wir markieren dies als erledigt, damit wir nicht bei jedem Aufruf versuchen, diese Objekte neu zu erstellen. Dann positionieren wir sie an ihren Plätzen, je nachdem, wo die Preislinie liegt.


Schlussfolgerung

Wir haben ein EA-System entwickelt, das manuell betrieben werden kann. Wir sind bereit für den nächsten großen Schritt, den wir im nächsten Artikel behandeln werden. Wir werden einen Auslöser hinzufügen, damit das System automatisch etwas tun kann. Sobald das erledigt ist, zeige ich Ihnen, was nötig ist, um einen solchen manuellen Handels-EA in einen vollautomatischen EA zu verwandeln. Im nächsten Artikel werden wir erörtern, wie man die Arbeit des EA automatisiert und menschliche Entscheidungen vom Handel ausschließt.


Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/11237

Beigefügte Dateien |
Kategorientheorie in MQL5 (Teil 2) Kategorientheorie in MQL5 (Teil 2)
Die Kategorientheorie ist ein vielfältiger und expandierender Zweig der Mathematik, der in der MQL-Gemeinschaft noch relativ unentdeckt ist. In dieser Artikelserie sollen einige der Konzepte vorgestellt und untersucht werden, mit dem übergeordneten Ziel, eine offene Bibliothek einzurichten, die zu Kommentaren und Diskussionen anregt und hoffentlich die Nutzung dieses bemerkenswerten Bereichs für die Strategieentwicklung der Händler fördert.
Algorithmen zur Populationsoptimierung Optimierung mit invasiven Unkräutern (IWO) Algorithmen zur Populationsoptimierung Optimierung mit invasiven Unkräutern (IWO)
Die erstaunliche Fähigkeit von Unkräutern, unter verschiedensten Bedingungen zu überleben, wurde zur Idee für einen leistungsstarken Optimierungsalgorithmus. IWO (Invasive Weed Optimization) ist einer der besten Algorithmen unter den bisher geprüften.
Das Murray-System neu überdenken Das Murray-System neu überdenken
Grafische Preisanalysesysteme sind bei den Händlern zu Recht sehr beliebt. In diesem Artikel beschreibe ich das komplette Murray-System, einschließlich seiner berühmten Level, sowie einige andere nützliche Techniken, um die aktuelle Kurslage zu bewerten und eine Handelsentscheidung zu treffen.
Algorithmen zur Optimierung mit Populationen Fledermaus-Algorithmus (BA) Algorithmen zur Optimierung mit Populationen Fledermaus-Algorithmus (BA)
In diesem Artikel werde ich den Fledermaus-Algorithmus (Bat-Algorithmus, BA) betrachten, der gute Konvergenz bei glatten Funktionen zeigt.