English Русский 中文 Español 日本語 Português
preview
Erstellen eines EA, der automatisch funktioniert (Teil 06): Kontoarten (I)

Erstellen eines EA, der automatisch funktioniert (Teil 06): Kontoarten (I)

MetaTrader 5Handel | 24 März 2023, 10:14
453 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Erstellen eines EA, der automatisch funktioniert (Teil 05): Manuelle Auslöser (II)“ haben wir einen relativ einfachen EA mit einem hohen Maß an Robustheit und Zuverlässigkeit entwickelt. Es kann für den Handel mit beliebigen Vermögenswerten, einschließlich Devisen- und Aktiensymbolen, verwendet werden. Er verfügt über keinerlei Automatisierung und wird vollständig manuell gesteuert.

Unser EA in seiner jetzigen Form kann in jeder Situation funktionieren, aber er ist noch nicht bereit für die Automatisierung. Wir müssen noch an ein paar Punkten arbeiten. Bevor wir Break-Even- oder Trailing-Stop-Mechanismen einführen, müssen wir noch einiges tun, denn wenn wir diese Mechanismen früher einführen, müssen wir später einige Dinge streichen. Daher werden wir einen etwas anderen Weg einschlagen und zunächst die Schaffung eines universellen EA betrachten.


Die Geburt der Klasse C_Manager

Die Klasse C_Manager bildet die Isolierungsschicht zwischen dem EA und dem Auftragssystem. Gleichzeitig wird die Klasse damit beginnen, eine Art von Automatisierung für unseren EA zu fördern, die es ihm ermöglicht, einige Dinge automatisch zu tun.

Schauen wir uns nun an, wie die Klassenbildung beginnt. Der anfängliche Code ist unten dargestellt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
        public  :
//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade)
                        :C_Orders(magic)
                        {
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                        }
//+------------------------------------------------------------------+
                ~C_Manager() { }
//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+
};

Was Sie im obigen Code sehen, ist die Grundstruktur dessen, was wir konstruieren werden. Bitte beachten Sie, dass der EA bei der Übermittlung von Bestellungen bestimmte Informationen nicht mehr angeben muss. Alles wird in dieser C_Manager-Klasse verwaltet. Beim Aufruf des Konstruktors werden alle Werte übergeben, die für die Erstellung eines Auftrags oder die Übermittlung eines Auftrags zur Eröffnung einer Marktposition erforderlich sind.

Ich möchte Sie jedoch auf eine Tatsache aufmerksam machen: Die Klasse C_Orders wird abgeleitet von der Klasse C_Manager, aber diese Ableitung privat. Warum? Der Grund dafür ist Sicherheit und erhöhte Zuverlässigkeit. Wenn wir diese Klasse hier als eine Art „Syndikator“ platzieren, wollen wir, dass sie der einzige Kommunikationspunkt zwischen dem EA und der Klasse ist, die für das Senden von Aufträgen verantwortlich ist.

Da C_Manager den Zugriff auf das Auftragssystem kontrolliert und in der Lage ist, Aufträge zu senden und und Positionen zu schließen oder zu ändern, geben wir dem EA eine Möglichkeit, auf das Auftragssystem zuzugreifen. Dieser Zugang wird jedoch begrenzt sein. Hier sind die beiden ersten Funktionen, mit denen der EA auf das Auftragssystem zugreifen kann. Wie Sie sehen können, sind sie viel eingeschränkter als die der Klasse C_Orders, aber sie sind sicherer.

Um zu verstehen, was wir hier alles implementieren, vergleichen wir den Code des EA aus dem vorherigen Artikel mit dem aktuellen. Wir haben nur die Klasse C_Manager erstellt. Sehen Sie sich an, was in zwei Funktionen im EA passiert.

int OnInit()
{
        manager = new C_Orders(def_MAGIC_NUMBER);
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);

        return INIT_SUCCEEDED;
}

Der bisherige Code wurde entfernt und durch einen neuen ersetzt, der jedoch eine große Anzahl von Parametern enthält. Aber das ist nur ein kleines Detail. Die Hauptsache (und das macht meiner Meinung nach alles noch riskanter) ist die folgende Darstellung:

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 (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL);
        }
        if ((def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) && def_BtnLeftClick(BtnStatus))
        {
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price, user03, user02, user01, user04);
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price);
        }else mem = 0;
}

Es gibt Teile, die einfach entfernt worden sind. Sie werden sehen, dass der neue Code viel einfacher ist. Aber nicht nur das. Die Verwendung einer Klasse für die Arbeit des „Administrators“ garantiert, dass der EA genau die Parameter verwendet, die bei der Initialisierung der Klasse definiert wurden. Es besteht also keine Gefahr, dass eine falsche oder ungültige Information in einen der Anrufe eingegeben wird. Alles ist an einem Ort versammelt, in der Klasse C_Manager, die nun als Kommunikationsvermittler zwischen dem EA und C_Orders dient. Dies erhöht das Sicherheitsniveau und die Zuverlässigkeit des EA-Codes erheblich.


NETTING-, EXCHANGE- oder HEDGING-Konto... das ist hier die Frage

Obwohl viele Menschen diese Tatsache ignorieren oder sich dessen nicht bewusst sind, gibt es hier ein ernstes Problem. Aus diesem Grund kann der EA gut oder schlecht funktionieren — das ist die Art des Kontos. Die meisten Händler und Nutzer der MetaTrader 5-Plattform wissen nicht, dass es drei Arten von Konten auf dem Markt gibt. Aber für diejenigen, die einen Expert Advisor entwickeln wollen, der im vollautomatischen Modus läuft, ist dieses Wissen entscheidend.

In dieser Artikelserie werden wir über zwei Kontotypen sprechen: NETTING und HEDGING. Der Grund dafür ist einfach: Das NETTING-Konto funktioniert für den EA auf die gleiche Weise wie das EXCHANGE-Konto.

Selbst wenn ein EA eine einfache Automatisierung hat, z.B. Breakeven- oder Trailing-Stop-Aktivierung, macht die Tatsache, dass er auf einem NETTING-Konto läuft, seine Funktionsweise völlig anders als die auf einem HEDGING-Konto. Der Grund liegt in der Funktionsweise des Handelsservers. Bei einem NETTING-Konto erstellt der Handelsserver einen Durchschnittspreis, wenn Sie Ihre Position erhöhen oder verringern.

Dies geschieht nicht auf dem Server eines HEDGING-Kontos. Es behandelt alle Positionen getrennt, sodass Sie gleichzeitig offene Kauf- und Verkaufspositionen für denselben Vermögenswert haben können. Das kann bei einem NETTING-Konto niemals passieren. Wenn Sie versuchen, eine entgegengesetzte Position mit derselben Losgröße zu eröffnen, wird der Server die Position schließen.

Aus diesem Grund müssen wir wissen, ob ein EA für NETTING- oder HEDGING-Konten konzipiert ist, da das Funktionsprinzip völlig unterschiedlich ist. Dies gilt jedoch nur für automatisierte EAs oder EAs mit einem gewissen Automatisierungsgrad. Bei einem manuellen EA spielt dies keine Rolle.

Aus diesem Grund können wir keinen Automatisierungsgrad schaffen, ohne dass es zu Schwierigkeiten bei der Programmierung oder der Nutzerfreundlichkeit kommt.

Hier müssen wir die Dinge ein wenig standardisieren. Mit anderen Worten, wir müssen sicherstellen, dass der EA auf jeden Kontotyp in einer standardisierten Weise funktionieren kann. Es stimmt, dass dies die Möglichkeiten der EA einschränken wird. Ein automatisierter EA sollte jedoch ein hohes Maß an Freiheit haben. Am besten ist es, den EA zu beschränken, damit er sich gut verhält. Wenn sie ein wenig abweicht, muss sie verboten werden oder zumindest eine Strafe erhalten.

Um die Dinge zu vereinheitlichen, sollte das HEDGING-Konto aus Sicht des EA ähnlich wie das NETTING-Konto funktionieren. Ich weiß, das mag verwirrend und kompliziert erscheinen, aber was wir wirklich wollen, ist, dass der EA nur eine offene Position und nur einen schwebenden Auftrag hat, mit anderen Worten, er wird extrem eingeschränkt sein und nichts anderes tun können.

Daher fügen wir der Klasse C_Manager den folgenden Code hinzu:

class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
//---
                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        int     Leverage;
                }m_Position;
                ulong           m_TicketPending;
                bool            m_bAccountHedging;
		double		m_Trigger;


In dieser Struktur erstellen wir alles, was wir für die Arbeit mit einer offenen Position benötigen. Es verfügt bereits über einige Funktionen, die mit der ersten Automatisierungsebene zusammenhängen, z. B. Break-Even und Trailing-Stop. Ein schwebender Auftrag wird auf eine einfachere Art und Weise gespeichert, nämlich mit Hilfe eines Tickets. Aber wenn wir in Zukunft mehr Daten benötigen, können wir sie einführen. Das wird für den Moment reichen. Außerdem gibt es eine weitere Variable, die uns sagt, ob wir ein HEDGING- oder NETTING-Konto verwenden. Sie wird in bestimmten Momenten besonders nützlich sein. Wie üblich wurde eine weitere Variable hinzugefügt, die in diesem Stadium noch nicht verwendet wird, die wir aber später bei der Erstellung von Auslösern von Break-Even und Trailing-Stop benötigen werden.

Auf diese Weise beginnen wir, die Dinge zu normalisieren. Danach können wir Änderungen am Klassenkonstruktor vornehmen, wie unten gezeigt:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                        }

Ich werde den Code nach und nach für diejenigen entwickeln, die keine Programmiererfahrung haben. Ich hoffe, ich bin nicht zu langweilig, denn ich möchte, dass jeder versteht, was wir hier tun. Hier sind die Erklärungen. Diese Zeilen teilen dem Compiler mit, dass diese Variablen initialisiert werden sollen, bevor die Ausführung des Konstruktorcodes beginnt. Wenn eine Variable erstellt wird, weist der Compiler ihr normalerweise den Wert Null zu.

In diesen Zeilen teilen wir dem Compiler mit, welchen Wert die Variable haben wird, wenn wir sie erstellen. In diesem Moment setzen wir den gesamten Inhalt der Struktur zurück. Auf diese Weise verwenden wir weniger Code und erhalten ein schnelleres Ergebnis. Hier legen wir fest, dass wir mit einem HEDGING-Konto arbeiten werden. Wenn diese Informationen zu einem bestimmten Zeitpunkt erforderlich werden, haben wir eine Variable, um dies mitzuteilen. Bereits hier stellen wir im Terminal fest, welcher Kontotyp gefunden wurde. Dies geschieht, um den Typ anzugeben, falls der Nutzer ihn nicht kennt.

Doch bevor wir uns diese Verfahren ansehen, sollten wir Folgendes bedenken: Was ist, wenn der EA mehr als eine Position (HEDGING-Konto) oder mehr als einen schwebenden Auftrag findet? Was wird dann geschehen? In diesem Fall erhalten wir einen Fehler, da der EA nicht in der Lage ist, mit mehr als einer Position und einem Auftrag zu arbeiten. Um dies zu bewerkstelligen, müssen wir im Code die folgende Enumeration erstellen:

class C_Manager : private C_Orders
{
        enum eErrUser {ERR_Unknown};
        private :

// ... The rest of the code...

};

Hier werden wir eine Enumeration verwenden, da es einfacher ist, neue Fehlercodes hinzuzufügen. Dazu müssen wir nur einen neuen Namen angeben, und der Compiler generiert einen Wert für den Code, ohne dass die Gefahr besteht, dass aus Unachtsamkeit Duplikate erzeugt wird. Beachten Sie, dass die Enumeration vor dem privaten Teil des Codes steht, sodass sie öffentlich sein wird. Um jedoch außerhalb der Klasse darauf zuzugreifen, müssen wir ein kleines Detail verwenden, um dem Compiler mitzuteilen, welche Enumeration korrekt ist. Dies ist besonders nützlich, wenn wir Enumerationen verwenden wollen, die sich auf eine bestimmte Klasse beziehen. Schauen wir uns nun die Prozeduren an, mit denen die Dinge geladen werden, die auf dem Chart zurückbleiben könnten und die der EA wiederherstellen muss, bevor er mit der Arbeit beginnt. Die erste lautet wie folgt:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

Schauen wir uns an, wie dieser Code funktioniert und warum er so ungewöhnlich aussieht. Hier verwenden wir eine Schleife, um alle schwebenden Aufträge im Auftragsbuch zu lesen. Die Funktion OrdersTotal gibt einen Wert größer als Null zurück, wenn Aufträge vorhanden sind. Die Indexierung beginnt immer bei Null. Das stammt aus C/C++. Es gibt jedoch zwei Bedingungen für das Ende der Schleife: Erstens muss der Wert der Variablen c0 kleiner als Null sein und zweitens muss _LastError eine andere Form als ERR_SUCESS haben, was darauf hinweist, dass im EA ein Fehler aufgetreten ist.

Wir treten also in die Schleife ein und erfassen die erste Bestellung, deren Index durch die Variable c0 angegeben wird. OrderGetTicket gibt den Wert des Tickets oder Null zurück. Wenn sie Null ist, kehren wir zur Schleife zurück, aber jetzt subtrahieren wir Eins von der Variablen c0.

Da OrderGetTicket Orderwerte lädt und das System nicht zwischen ihnen unterscheidet, müssen wir alles filtern, damit der EA nur unsere spezifische Order erkennt. Der erste Filter, den wir verwenden, ist der Name des Vermögenswerts; dazu vergleichen wir den Vermögenswert in der Reihenfolge mit dem Vermögenswert, auf dem der EA ausgeführt wird. Wenn sie nicht übereinstimmen, wird die Order ignoriert und wir verlassen die Methode, um eine andere zu holen.

Der nächste Filter ist die magische Zahl, da das Orderbuch Aufträge enthalten kann, die manuell oder von anderen EAs platziert wurden. Anhand der magischen Zahl, die jeder EA haben sollte, können wir herausfinden, ob der Auftrag von unserem EA erteilt wurde. Weicht die magische Zahl von der vom EA verwendeten ab, sollte der Auftrag ignoriert werden. Dann werden wir zum Anfang zurückkehren und nach einer neuen Order suchen.

Jetzt kommen wir an einen Scheideweg. Wenn der EA die Order gefunden hat, die er platziert hat, bevor sie aus irgendeinem Grund aus dem Chart entfernt wurde (wir werden später sehen, was die Gründe dafür sein können), dann wird sein Speicher, d.h. die Variable, die das Ticket des schwebenden Auftrags (pending order) anzeigt, einen anderen Wert als Null haben. Wenn dann eine zweite Order auftaucht, wird dies als Fehler gewertet. Die Funktion verwendet eine Enumeration, um anzuzeigen, dass ein Fehler aufgetreten ist.

Hier verwende ich den allgemeinen Wert ERR_Unknown, aber Sie können einen Wert zur Angabe des Fehlers erstellen, der im Wert _LastError angezeigt wird. Die Funktion SetUserError ist für das Setzen des Fehlerwertes in der Variablen _LastError verantwortlich. Wenn jedoch alles in Ordnung ist und die Variable, die das Auftragsticket enthält, auf Null gesetzt wird, wird der Wert des nach allen Filtern gefundenen Auftrags in der Variablen m_TicketPending zur weiteren Verwendung gespeichert. An dieser Stelle endet die Erklärung dieser Funktion. Betrachten wir die nächste, die nach offene Positionen sucht. Ihr Code ist unten dargestellt:

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Alles, was ich über den vorherigen Code gesagt habe, gilt auch für diesen. Der einzige Unterschied besteht darin, dass wir oben Orders manipuliert haben und jetzt Positionen. Aber die Logik ist die gleiche, bis auf den nächsten Aufruf: SetInfoPositions, womit die die neuesten Positionsdaten gespeichert, korrigiert und verarbeitet werden. Dazu wird der folgende Code verwendet:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                m_Position.EnableBreakEven = (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

Dieser Code ist besonders interessant, wenn es darum geht, mit den neuesten Positionsdaten zu arbeiten. Aber Vorsicht: Bevor Sie ihn aufrufen, müssen Sie die Positionsdaten mit einem der folgenden Aufrufe aktualisieren: PositionGetTicket, PositionSelect, PositionGetSymbol oder PositionSelectByTicket. Im Allgemeinen wird hier alles nach Bedarf initialisiert oder konfiguriert. Wir mussten diesen Code separat platzieren, weil wir ihn an anderen Stellen verwenden werden, um die Positionsdaten bei Bedarf zu aktualisieren.

Das war's im Grunde, aber jetzt müssen wir eine neue Änderung am Konstruktor der Klasse vornehmen, damit der EA vollständig und korrekt initialisiert werden kann. Wir müssen nur noch die oben gezeigten Aufrufe hinzufügen. Der endgültige Code des Konstruktors sieht dann wie folgt aus:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadPositionValid();
                                LoadOrderValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Mit diesen beiden Zeilen lädt der EA die offene Position und den schwebenden Auftrag. Achten Sie nun darauf, dass der Konstruktor keinen Wert zurückgeben kann, da dies ein Fehler ist. Wir brauchen eine Möglichkeit, dem Rest des Codes mitzuteilen, dass im Konstruktor etwas schief gelaufen ist.

Jedes Programmiersystem bietet solche Mittel an oder zwingt uns, sie zu schaffen. Aber MQL5 bietet eine sehr praktische Möglichkeit, nämlich die Verwendung der _LastError-Variable für diesen Zweck. Wenn bei der Initialisierung alles in Ordnung ist, wird im Terminal eine entsprechende Meldung angezeigt. Wenn das System Positionen gefunden hat, wird auch eine Meldung angezeigt, die angibt, welches Positionsticket der EA beobachten wird. Wenn eine Order gefunden wurde, wird auch eine Meldung angezeigt, die uns über das vom EA gefundene Ticket der pending order informiert.

Der Wert _LastError wird verwendet, um zu prüfen, ob der EA zu einem bestimmten Zeitpunkt offline gegangen ist. Es könnte also interessant sein, wenn Sie weitere Meldungstypen in die Fehlerliste aufnehmen, um einen genaueren Hinweis darauf zu erhalten, was tatsächlich passiert ist.


Ein Problem mit HEDGING Konten für einen automatisierten EA

Obwohl alles schön und wunderbar aussieht, insbesondere für diejenigen, die mit dem Programmieren beginnen, setzen wir die Entwicklung fort, um ein höheres Maß an Robustheit in einem automatisierten EA zu erreichen. Wir haben immer noch ein potenzielles Problem im System, wenn es auf HEDGING-Konten verwendet wird. Und das sogar, bevor wir zu dem Code kommen, der es dem EA ermöglicht, Aufträge oder Anfragen an den Server zu senden. Das Problem liegt in der Tatsache, dass im Gegensatz zu einem NETTING-Konto, bei dem der Server einen Durchschnittspreis erstellt, wenn die Position geändert wird, entweder durch die Eingabe neuer Marktaufträge oder durch die Ausführung schwebender Aufträge, das HEDGING-Konto nicht so viel Kontrolle hat, die einfach ist und vom Server kommt.

Das Problem mit dem HEDGING-Konto ist, dass wir eine offene Position haben können, und wenn ein schwebender Auftrag ausgeführt wird, ändert sich die offene Position nicht direkt. Was passieren kann und auch passieren wird, ist, dass eine neue Position eröffnet wird, wenn der schwebende Auftrag ausgeführt wird. Diese neue offene Position kann den Preis festhalten, sodass wir weder Gewinn noch Verlust haben. Sie kann aber auch unsere Position insgesamt verbessern. Dies geschieht, sobald der Auftrag ausgeführt wird.

Dieses Detail, das im HEDGING-Konto vorhanden ist, zwingt uns, eine andere Maßnahme zu ergreifen. Wir können verhindern, dass der EA Aufträge an den Markt sendet, wenn eine Position offen ist oder sich bereits ein schwebender Auftrag im Auftragsbuch befindet. Dies ist anhand des von mir gezeigten Codes leicht zu bewerkstelligen. Das Problem ist jedoch, dass der EA während der Initialisierung eine offene Position und einen schwebenden Auftrag auf einem HEDGING-Konto finden kann. Dies ist kein Problem für das NETTING-Konto, wie ich oben erklärt habe.

Was sollte der EA in diesem Fall tun? Wie Sie sich erinnern, lässt die Klasse C_Manager, die den EA steuert, nicht zu, dass zwei offene Positionen oder zwei schwebende Aufträge existieren. In diesem Fall müssen wir den schwebenden Auftrag entfernen oder die offene Position schließen. Auf die eine oder andere Weise muss etwas getan werden, denn wir dürfen diese Situation in einem automatisierten EA nicht zulassen. Ich betone noch einmal, dass ein automatisierter EA niemals mit mehr als einer offenen Position oder mehr als einem schwebenden Auftrag zur gleichen Zeit arbeiten darf. Bei einem manuellen EA liegen die Dinge anders.

Daher müssen Sie entscheiden, welche Maßnahme ergriffen werden soll: Die Position schließen oder den schwebenden Auftrag entfernen? Für den Fall, dass Sie die Position schließen wollen, bietet die Klasse C_Orders bereits das Verfahren dafür. Wenn Sie jedoch die ausstehende Bestellung löschen müssen, gibt es in der Klasse C_Orders keine entsprechende Prozedur. Wir müssen also eine Möglichkeit schaffen, dies zu tun. Beginnen wir an dieser Stelle, indem wir dem System die Möglichkeit geben, ausstehende Aufträge zu löschen. Zu diesem Zweck fügen wir dem System einen neuen Code hinzu:

class C_Orders : protected C_Terminal
{
        protected:
//+------------------------------------------------------------------+
inline const ulong GetMagicNumber(void) const { return m_MagicNumber; }
//+------------------------------------------------------------------+
                void RemoveOrderPendent(const ulong ticket)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action   = TRADE_ACTION_REMOVE;
                                m_TradeRequest.order    = ticket;
                                ToServer();
                        };

// ... The rest of the class code 

}

Achten Sie auf einige Details des Codes. Erstens befindet er sich im geschützten Codeteil, d. h. selbst wenn wir versuchen, die Klasse C_Orders im EA direkt zu verwenden, haben wir aus dem bereits erläuterten Grund keinen Zugriff auf diesen Code. Zweitens wird sie zum Löschen schwebender Aufträge verwendet, nicht aber zum Schließen von Positionen oder zum Ändern schwebender Aufträge.

Dieser Code ist also bereits in der Klasse C_Orders implementiert. Wir können auf C_Manager zurückgreifen und ein System implementieren, das verhindert, dass der automatisierte EA eine ausstehende Order hat, wenn er auf einem HEDGING-Konto läuft und bereits eine offene Position hat. Wenn Sie jedoch möchten, dass die Position geschlossen wird und der schwebende Auftrag beibehalten wird, reicht es aus, Änderungen am Code vorzunehmen, um das gewünschte Verhalten zu erzielen. Das Einzige, was nicht passieren darf, ist, dass der automatisierte EA, der auf einem HEDGING-Konto läuft, sowohl eine offene Position als auch einen schwebenden Auftrag hat. Dies kann nicht zugelassen werden.

Das ist wichtig: Wenn es sich um ein HEDGING-Konto handelt, können Sie mehr als einen EA für denselben Vermögenswert einsetzen. In diesem Fall hat die Tatsache, dass ein EA eine offene Position und der andere einen schwebenden Auftrag hat, keinerlei Auswirkungen auf die Funktionsweise der beiden EAs. In diesem Fall sind sie unabhängig. Es kann also vorkommen, dass wir mehr als eine offene Position oder mehr als einen schwebenden Auftrag für denselben Vermögenswert haben. Diese Situation ist für einen einzelnen EA nicht möglich. Außerdem betrifft dies nur automatisierte EAs. Ich werde dies immer wieder wiederholen, da es äußerst wichtig ist, dies zu verstehen und sich daran zu erinnern.

Sie haben vielleicht bemerkt, dass wir im Konstruktorcode zuerst eine Position erfassen und erst dann den Auftrag. Dadurch wird es einfacher, den Auftrag bei Bedarf zu löschen. Wenn Sie jedoch die Position schließen und die Order behalten wollen, kehren Sie dies im Konstruktor einfach um, sodass zuerst die Orders und dann die Position erfasst werden. Wenn dann ein Bedarf besteht, wird die Stelle geschlossen. Schauen wir uns an, wie wir das in dem von uns betrachteten Fall tun werden. Wir erfassen die Position und entfernen dann, falls erforderlich, alle gefundenen schwebenden Aufträge. Der Code dafür ist unten zu sehen:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                        {
                                                RemoveOrderPendent(value);
                                                continue;
                                        }
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

Die Änderung, die wir vornehmen müssen, besteht darin, dass wir den hervorgehobenen Code hinzufügen. Beachten Sie, wie einfach es ist, einen schwebenden Auftrag zu löschen, aber hier brauchen wir nur den Auftrag zu entfernen. Wenn wir eine offene Position haben und der Kontotyp HEDGING ist, dann tritt eine Situation ein, in der der schwebende Auftrag gelöscht wird. Aber wenn wir ein NETTING-Konto haben oder es keine offene Position gibt, dann wird dieser Code nicht ausgeführt, was dem EA erlaubt, reibungslos zu arbeiten.

Da Sie jedoch die Position schließen und den schwebenden Auftrag behalten möchten, sehen wir uns an, wie der Code in diesem Fall aussehen sollte. Sie brauchen den Code für das Laden der schwebenden Aufträge nicht zu ändern — verwenden Sie den oben gezeigten Code. Sie müssen jedoch einige Änderungen vornehmen. Als erstes müssen Sie den folgenden Code zu der Prozedur hinzufügen, die die Position lädt:

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Durch Hinzufügen des hervorgehobenen Codes können Sie die offene Position schließen, während der schwebende Auftrag beibehalten wird. Allerdings gibt es hier ein Detail: Um einen schwebenden Auftrag zu behalten und eine Position auf dem HEDGING-Konto zu schließen, müssen wir einen Punkt im Konstruktorcode wie folgt ändern:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadOrderValid();
                                LoadPositionValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket + "\n" : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Vielleicht haben Sie die Änderungen nicht bemerkt. Aber wenn Sie ihn mit dem Code am Ende des vorherigen Abschnitts vergleichen, werden Sie sehen, dass der hervorgehobene Teil anders ist. In diesem Fall wird die Position geschlossen, während wir in der vorherigen Version den Auftrag gelöscht haben. Das ist die Gnade der Programmierung. Manchmal macht ein einfaches Detail den Unterschied aus. Hier ändern wir nur die Reihenfolge der Codeausführung, aber das Ergebnis ist völlig anders.

In der Theorie ist der bisher betrachtete Code völlig unproblematisch und wird perfekt funktionieren. Aber das ist nur die Theorie. Es kann sich herausstellen, dass der Handelsserver einen Fehler meldet, nicht weil etwas mit den gesendeten Anfragen nicht stimmt, sondern aufgrund einer Art von Interaktion, die auftreten kann. Die Variable _LastError enthält einen Wert, der eine Art von Fehler anzeigt.

Einige Fehler können zugelassen werden, da sie nicht kritisch sind, während andere nicht ignoriert werden können. Wenn Sie diesen Unterschied verstehen und diese Idee akzeptieren, können Sie den Aufruf von ResetLastError in bestimmte Codeteile einfügen, um zu verhindern, dass der EA aus dem Chart geworfen wird, weil ein Fehler aufgetreten ist, der höchstwahrscheinlich nicht vom EA, sondern von einer fehlerhaften Interaktion zwischen dem EA und dem Handelsserver verursacht wurde.

In diesem frühen Stadium werde ich nicht zeigen, wo wir diese Aufrufe hinzufügen können. Ich tue dies, damit Sie nicht in Versuchung kommen, diese Aufrufe wahllos an irgendeiner Stelle vorzunehmen oder die in der Variablen _LastError enthaltenen Werte zu ignorieren. Dies würde die gesamte These vom Aufbau eines starken, robusten und zuverlässigen automatisierten EA zunichte machen.


Schlussfolgerung

In diesem Artikel habe ich die Grundlagen dargestellt, um Ihnen zu zeigen, dass Sie immer darüber nachdenken sollten, wie Sie einen EA auf sichere, stabile und robuste Weise automatisieren können. Die Programmierung eines automatisch ablaufenden EA ist keine Aufgabe, die Menschen mit wenig Erfahrung problemlos bewältigen können, sondern eine äußerst schwierige Aufgabe, die große Sorgfalt seitens des Programmierers erfordert.

Im nächsten Artikel werden wir uns mit weiteren Aspekten befassen, die für einen automatisierten EA umgesetzt werden müssen. Wir werden überlegen, wie wir sie sicher auf dem Chart platzieren können. Wir müssen stets mit der gebotenen Sorgfalt und den richtigen Maßnahmen handeln, um unser hart erarbeitetes Erbe nicht zu beschädigen.


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

Erstellen eines EA, der automatisch funktioniert (Teil 07): Kontoarten (II) Erstellen eines EA, der automatisch funktioniert (Teil 07): Kontoarten (II)
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet. Der Händler sollte sich immer darüber im Klaren sein, was der automatische EA tut, sodass er ihn im Falle einer „Entgleisung“ so schnell wie möglich aus dem Chart entfernen und die Kontrolle über die Situation übernehmen kann.
Neuronale Netze leicht gemacht (Teil 33): Quantilsregression im verteilten Q-Learning Neuronale Netze leicht gemacht (Teil 33): Quantilsregression im verteilten Q-Learning
Wir setzen die Untersuchung des verteilten Q-Learnings fort. Heute wollen wir diesen Ansatz von der anderen Seite her betrachten. Wir werden die Möglichkeit prüfen, die Quantilsregression zur Lösung von Preisvorhersageaufgaben einzusetzen.
Wie man einen Expert Advisor auswählt: Zwanzig starke Kriterien für die Ablehnung eines Handelsroboter Wie man einen Expert Advisor auswählt: Zwanzig starke Kriterien für die Ablehnung eines Handelsroboter
Dieser Artikel versucht, die Frage zu beantworten: Wie kann man die richtigen Expert Advisor auswählen? Welche sind die besten für unser Portfolio, und wie können wir die große Liste der auf dem Markt erhältlichen Handelsroboter filtern? In diesem Artikel werden zwanzig klare und starke Kriterien für die Ablehnung eines Expert Advisors vorgestellt. Jedes Kriterium wird vorgestellt und gut erklärt, um Ihnen zu helfen, eine nachhaltigere Entscheidung zu treffen und eine profitablere Expert Advisor-Sammlung für Ihre Gewinne aufzubauen.
Algorithmen zur Optimierung mit Populationen Optimierung gemäß einer bakteriellen Nahrungssuche (BFO) Algorithmen zur Optimierung mit Populationen Optimierung gemäß einer bakteriellen Nahrungssuche (BFO)
Die Strategie der Nahrungssuche des Bakteriums E. coli inspirierte die Wissenschaftler zur Entwicklung des BFO-Optimierungsalgorithmus. Der Algorithmus enthält originelle Ideen und vielversprechende Optimierungsansätze und ist es wert, weiter untersucht zu werden.