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

Erstellen eines EA, der automatisch funktioniert (Teil 11): Automatisierung (III)

MetaTrader 5Handel | 8 Mai 2023, 11:18
285 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel „Erstellen eines EA, der automatisch funktioniert (Teil 10): Automation (II)“ haben wir uns eine Möglichkeit angesehen, dem EA einen Zeitplan hinzuzufügen. Obwohl das gesamte EA-System auf Autonomie ausgelegt ist, müssen wir vor der letzten Phase, in der wir einen zu 100 % automatisierten EA erhalten werden, noch einige kleinere Änderungen am Code vornehmen.

Während der Automatisierungsphase dürfen wir keinen Teil des bestehenden Codes verändern, erstellen oder in irgendeiner Weise verändern. Wir müssen nur die Punkte entfernen, an denen es eine Interaktion zwischen dem Händler und dem EA gab, und stattdessen eine Art automatischen Auslöser hinzufügen. Jede weitere Änderung des alten Codes sollte vermieden werden. Wenn Sie jedoch bei der Implementierung von Automatisierungsfunktionen den alten Code ändern müssen, bedeutet dies, dass der Code schlecht geplant war und Sie die gesamte Planung neu vornehmen müssen, um ein System mit den folgenden Eigenschaften zu erhalten.

  • Robust: Das System sollte keine primären Fehler aufweisen, die die Integrität eines beliebigen Teils des Codes verletzen können.
  • Zuverlässig: Ein zuverlässiges System ist ein System, das einer Reihe von potenziell gefährlichen Situationen ausgesetzt war und dennoch ohne Ausfall funktioniert hat.
  • Stabilität: Es macht keinen Sinn, ein System zu haben, das zeitweise gut funktioniert, aber ohne Erklärung die Plattform zum Absturz bringt.
  • Skalierbar: Das System sollte ohne großen Programmieraufwand wachsen können.
  • Gekapselt: Nur die für die Verwendung wirklich notwendigen Funktionen müssen außerhalb des Ortes, an dem der Code erstellt wird, sichtbar sein.
  • Schnell: Es nützt nichts, das beste Modell zu haben, wenn es aufgrund von schlecht vorbereitetem Code langsam ist.

Einige dieser Merkmale beziehen sich auf das objektorientierte Programmiermodell. Dieses Modell eignet sich bei weitem am besten für die Erstellung von Programmen, die ein hohes Maß an Sicherheit erfordern. Aus diesem Grund haben Sie sicher schon zu Beginn dieser Serie bemerkt, dass sich die gesamte Programmierung auf die Verwendung von Klassen konzentriert, d. h. auf die objektorientierte Programmierung. Ich weiß, dass diese Art der Programmierung auf den ersten Blick verwirrend und schwer zu erlernen sein mag, aber glauben Sie mir, Sie werden viel davon profitieren, wenn Sie sich wirklich die Mühe machen und lernen, Ihre Codes als Klassen zu erstellen.

Ich sage das, um Ihnen, den angehenden professionellen Programmierern, zu zeigen, wie Sie Ihren Code erstellen können. Sie müssen sich daran gewöhnen, mit 3 Ordnern zu arbeiten, in denen der Code aufbewahrt wird. Unabhängig davon, wie komplex oder einfach das Programm ist, müssen Sie immer in 3 Schritten arbeiten:

  1. In der ersten Phase, die im Entwicklungsordner stattfinden wird, wird der gesamte Code erstellt, geändert und getestet. Alle neuen Funktionen oder Änderungen dürfen nur in dem Code in diesem Ordner vorgenommen werden.
  2. Nachdem der Code erstellt und getestet wurde, muss er in den zweiten Ordner, den Arbeitsordner, verschoben werden. In diesem Ordner kann der Code noch einige Fehler enthalten, aber Sie sollten ihn NICHT ändern. Wenn Sie den Code bearbeiten müssen, während er sich in diesem Ordner befindet, verschieben Sie ihn zurück in den Entwicklungsordner. Ein Detail: Wenn Sie den Code bearbeiten, um einen gefundenen Fehler zu korrigieren, ohne andere, schwerwiegendere Änderungen vorzunehmen, kann der Code dieses Arbeitsordners darin verbleiben und wird entsprechend korrigiert.
  3. Nachdem Sie den Code mehrmals in verschiedenen Situationen ohne neue Änderungen verwendet haben, verschieben Sie ihn in den dritten und letzten Ordner „stable“. Der in dieser Mappe enthaltene Code hat bereits bewiesen, dass er keine Mängel aufweist und für die Aufgabe, für die er entwickelt wurde, sehr nützlich und effizient ist. Fügen Sie niemals neuen Code in diesen Ordner ein.

Wenn Sie diesen Ansatz einige Zeit anwenden, werden Sie schließlich eine sehr interessante Datenbank mit Funktionen und Prozeduren erstellen, und Sie werden in der Lage sein, extrem schnell und sicher zu programmieren. So etwas wird sehr geschätzt, vor allem in einer Branche wie dem Finanzmarkt, wo niemand wirklich daran interessiert ist, einen Code zu verwenden, der nicht dem Risiko entspricht, das auf dem Markt immer vorhanden ist. Da wir immer in einem Bereich arbeiten, in dem Laufzeitfehler nicht erwünscht sind und alles im schlimmsten Fall in Echtzeit abläuft, muss Ihr Code in der Lage sein, unerwarteten Ereignissen in jedem Moment standzuhalten.

All dies wurde gesagt, um zu dem folgenden Punkt zu gelangen, der in Abbildung 01 dargestellt ist:

Abbildung 01

Abbildung 01. Manuelles Kontrollsystem

Viele Menschen verstehen nicht ganz, was genau sie programmieren oder erstellen. Das liegt daran, dass viele von ihnen nicht wirklich verstehen, was innerhalb des Systems vor sich geht, und schließlich denken, dass die Plattform genau das bieten sollte, was der Händler tun möchte. Wenn Sie sich jedoch Abbildung 01 ansehen, werden Sie verstehen, dass sich die Plattform nicht darauf konzentrieren sollte, das anzubieten, was der Händler wünscht. Stattdessen sollte es Möglichkeiten zur Interaktion mit dem Händler und gleichzeitig zur stabilen, schnellen und effizienten Interaktion mit dem Handelsserver bieten. Gleichzeitig muss die Plattform funktionsfähig bleiben und die Elemente unterstützen, die zur Interaktion mit dem Händler genutzt werden.

Achten Sie darauf, dass keiner der Pfeile über seinen Punkt hinausgeht, was bedeutet, dass es sich um ein manuelles Handelssystem handelt. Beachten Sie auch, dass der EA von der Plattform bedient wird, nicht umgekehrt, und dass der Händler nicht direkt mit dem EA kommuniziert. Auch wenn es seltsam erscheinen mag, greift der Händler über die Plattform auf den EA zu, und die Plattform steuert den EA, indem sie ihm die Ereignisse sendet, die der Händler erstellt oder ausführt. Daraufhin sendet der EA Anfragen an die Plattform, die an den Handelsserver weitergeleitet werden müssen. Wenn der Server antwortet, sendet er diese Antworten zurück an die Plattform, die sie an den EA weiterleitet. Nachdem der EA die Antwort des Servers analysiert und verarbeitet hat, sendet er einige Informationen an die Plattform, um dem Händler zu zeigen, was passiert ist oder was das Ergebnis der vom Händler gestellten Anfrage ist.

Vielen Menschen sind solche Dinge nicht bewusst. Wenn im EA irgendwelche Störungen auftreten, wird die Plattform keine Probleme haben. Das Problem liegt im EA, aber weniger erfahrene Händler können fälschlicherweise die Plattform dafür verantwortlich machen, dass sie nicht das tut, was sie wollen.

Wenn Sie nicht als Programmierer an der Entwicklung und Wartung der Plattform beteiligt sind, sollten Sie nicht versuchen, die Funktionsweise der Plattform zu beeinflussen. Vergewissern Sie sich, dass Ihr Code den Anforderungen der Plattform gerecht wird.

Schließlich kommen wir zu der Frage: Warum sollte man einen manuellen EA erstellen, ihn eine Zeit lang verwenden und erst dann automatisieren? Der Grund dafür ist genau dieser: Wir schaffen eine Möglichkeit, den Code tatsächlich zu testen und genau das zu erstellen, was wir brauchen, nicht mehr und nicht weniger.

Um das System ordnungsgemäß zu automatisieren, ohne eine einzige Zeile des Codes zu verändern, der als Kontrollpunkt und Bestellsystem verwendet wird, müssen wir einige Ergänzungen und kleine Änderungen vornehmen. So wird der bis zum letzten Artikel erstellte Code in den Arbeitsordner, der im letzten Artikel erstellte Code in den stabilen Ordner und der in diesem Artikel vorgestellte Code in den Entwicklungsordner verschoben. Auf diese Weise wird der Entwicklungsprozess ausgeweitet und weiterentwickelt, während wir gleichzeitig viel schneller programmieren können. Wenn etwas schief geht, können wir immer noch zwei Versionen zurückgehen, bei denen alles ohne Probleme funktioniert hat.


Umsetzung der Änderungen

Das erste, was wir tun werden, ist das Timing-System zu ändern. Diese Änderung ist im nachstehenden Code dargestellt:

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                TimeCurrent(mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

Die gelöschte Zeile wurde durch die hervorgehobene Zeile ersetzt. Warum haben wir diese Änderungen vorgenommen? Dafür gibt es zwei Gründe: Erstens ersetzen wir zwei Aufrufe durch einen. In der gelöschten Zeile hatten wir zunächst einen Aufruf, um die Zeit zu erhalten, und dann einen weiteren, um sie in eine Struktur umzuwandeln. Der zweite Grund ist, dass TimeLocal tatsächlich die Computerzeit zurückgibt und nicht die im Marktbeobachtungselement angezeigte Zeit, wie in Abbildung 02 dargestellt.

Abbildung 02

Abbildung 02. Zeit, die der Server bei der letzten Aktualisierung angegeben hat.

Die Verwendung der Computerzeit ist kein Problem, wenn sie über einen NTP-Server (der die offizielle Zeit auf dem neuesten Stand hält) synchronisiert wird. In den meisten Fällen werden solche Server jedoch nicht genutzt. Daher könnte es vorkommen, dass das Zeitsteuerungssystem dem EA erlaubt, zu früh oder zu spät zu handeln. Die Änderung war notwendig, um diese Unannehmlichkeiten zu vermeiden.

Die Änderung dient nicht dazu, den Code radikal zu ändern, sondern dazu, die vom Händler erwartete Stabilität zu gewährleisten. Denn wenn der EA früher als erwartet ein- und aussteigt, könnte der Händler denken, dass es einen Fehler in der Plattform oder im Code gibt. Die Ursache liegt jedoch in der fehlenden Zeitsynchronisation zwischen dem Computer und dem Handelsserver. Der Handelsserver verwendet höchstwahrscheinlich einen NTP-Server, um die offizielle Zeit beizubehalten, während der für die Arbeit verwendete Computer diesen Server möglicherweise nicht verwendet.

Die nächste Änderung wurde im Auftragssystem vorgenommen:

                ulong ToServer(void)
                        {
                                MqlTradeCheckResult     TradeCheck;
                                MqlTradeResult          TradeResult;
                                bool bTmp;
                                
                                ResetLastError();
                                ZeroMemory(TradeCheck);
                                ZeroMemory(TradeResult);
                                bTmp = OrderCheck(m_TradeRequest, TradeCheck);
                                if (_LastError == ERR_SUCCESS) bTmp = OrderSend(m_TradeRequest, TradeResult);
                                if (_LastError != ERR_SUCCESS) MessageBox(StringFormat("Error Number: %d", GetLastError()), "Order System", MB_OK);
                                if (_LastError != ERR_SUCCESS) PrintFormat("Order System - Error Number: %d", _LastError);
                                return (_LastError == ERR_SUCCESS ? TradeResult.order : 0);
                        }

Dies ist auch eine notwendige Änderung, um das System für den vorgesehenen Zweck nutzbar zu machen, sodass der EA ohne allzu große Schwierigkeiten automatisiert werden kann. Tatsächlich wurde die gelöschte Zeichenfolge durch die hervorgehobene Zeichenfolge ersetzt, nicht weil der Code schneller oder stabiler sein könnte, sondern weil es notwendig war, das Auftreten eines Fehlers angemessen zu behandeln. Wenn wir einen automatisierten EA haben, können einige Arten von Fehlern ignoriert werden, wie wir in früheren Artikeln besprochen haben.

Das Problem besteht darin, dass die gelöschte Zeile immer ein Meldungsfenster auslöst, das über den Fehler informiert, aber in einigen Fällen kann der Fehler vom Code korrekt behandelt werden und das Feld ist nicht erforderlich. In solchen Fällen können wir einfach eine Meldung im Terminal ausgeben, damit der Händler entsprechende Maßnahmen ergreifen kann.

Denken Sie daran, dass ein 100% automatischer EA nicht darauf warten kann, dass der Händler eine Entscheidung trifft. Auch wenn dies eine Weile dauern könnte, können Sie nichts tun, ohne zu berichten, welche Art von Problem aufgetreten ist. Auch hier wurde der Code geändert, um die Agilität zu verbessern. Es gab keine größeren Änderungen, die es erforderlich gemacht hätten, das System einer intensiveren Testphase zu unterziehen, um nach Fehlern zu suchen, die durch die Änderungen verursacht worden sein könnten.

Aber im Gegensatz zu den oben genannten Änderungen wird es weitere geben, die eingehend getestet werden müssen, da die Änderungen die Funktionsweise des Systems beeinflussen werden.


Den Weg für die Automatisierung ebnen

Die Änderungen, die jetzt vorgenommen werden, werden es uns ermöglichen, ein 100%ig automatisches System zu schaffen. Ohne diese Änderungen wären uns für den nächsten Artikel die Hände gebunden, in dem ich zeigen werde, wie man einen bereits getesteten EA (und ich hoffe, Sie führen alle erforderlichen Tests durch, um zu verstehen, wie alles tatsächlich funktioniert) in einen autonomen EA verwandelt. Um die notwendigen Änderungen durchzuführen, müssen wir einige Dinge entfernen oder besser gesagt ändern und andere hinzufügen. Beginnen wir mit der Änderung. Was geändert wird, ist im nachstehenden Code beschrieben:

//+------------------------------------------------------------------+
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : public C_ControlOfTime

Diese beiden Definitionen wird es nicht mehr geben. An ihrer Stelle erscheinen 2 neue Variablen, die nicht vom Händler geändert werden können, sondern von Ihnen, dem Programmierer, definiert werden. Warum eine solche Änderung? Der Grund dafür ist, dass wir bei diesen Änderungen, bei denen die Definitionen durch Variablen ersetzt werden, ein wenig an Geschwindigkeit verlieren werden. Selbst wenn es sich um einige Maschinenzyklen handelt, ist der Leistungsverlust gering, da der Zugriff auf einen konstanten Wert wesentlich schneller ist als auf eine Variable. Aber im Gegenzug gewinnen wir an Klassenwiederverwendung, und das werden Sie im nächsten Artikel besser verstehen. Glauben Sie mir, der Unterschied in Bezug auf Leichtigkeit und Tragbarkeit macht den kleinen Leistungsverlust wett. Die beiden oben genannten Zeilen wurden also durch die folgenden ersetzt:

class C_Manager : public C_ControlOfTime
{
        enum eErrUser {ERR_Unknown, ERR_Excommunicate};
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage,
                                MaxLeverage;
                        bool    IsDayTrade,
                                IsOrderFinish;                                          
                }m_InfosManager;

Seien Sie vorsichtig bei der Arbeit mit dem Code: Sie als Programmierer sollten den Wert dieser beiden Variablen nicht außerhalb der Stelle ändern, an der sie initialisiert werden. Seien Sie sehr vorsichtig, dies nicht zu tun. Der Ort, an dem sie initialisiert werden, ist genau im Klassenkonstruktor, wie im folgenden Code gezeigt:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger, const bool IsOrderFinish, const uint MaxLeverage)
                        :C_ControlOfTime(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.IsOrderFinish    = IsOrderFinish;
                                m_InfosManager.MaxLeverage      = MaxLeverage;
                                m_InfosManager.FinanceStop      = FinanceStop;
                                m_InfosManager.FinanceTake      = FinanceTake;
                                m_InfosManager.Leverage         = Leverage;
                                m_InfosManager.IsDayTrade       = IsDayTrade;

Der Konstruktor erhält nun zwei neue Argumente, die unsere Variablen initialisieren. Danach werden wir die Punkte ändern, an denen die Definitionen instanziiert wurden. Diese Änderungen werden in den folgenden Punkten vorgenommen:

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);
                                if (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                if (m_InfosManager.IsOrderFinish) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }

                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }

                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
// ... The rest of the code ...

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((def_ORDER_FINISH) && (m_TicketPending > 0))
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                }
                                ResetLastError();
                        }

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH)
                                        if (m_InfosManager.IsOrderFinish)
                                                if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                if (m_InfosManager.IsOrderFinish)
                                                        ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

Absolut alle entfernten Teile sind durch hervorgehobene Fragmente ersetzt worden. Auf diese Weise ist es uns gelungen, nicht nur die erwünschte Wiederverwendung von Klassen zu fördern, sondern sie auch in Bezug auf die Nutzerfreundlichkeit zu verbessern. Obwohl dies in diesem Artikel nicht ganz klar ist, werden Sie im nächsten Artikel sehen, wie dies geschehen wird.

Zusätzlich zu den bereits vorgenommenen Änderungen müssen wir noch einige Ergänzungen vornehmen, um den Zugriff des Automatisierungssystems auf das Auftragssendesystem zu maximieren und damit die Klassenwiederverwendung zu erhöhen. Zu diesem Zweck fügen wir zunächst die folgende Funktion hinzu:

inline void ClosePosition(void)
	{
		if (m_Position.Ticket > 0)
		{
			C_Orders::ClosePosition(m_Position.Ticket);
			ZeroMemory(m_Position.Ticket);
		}                               
	}

Diese Funktion wird in einigen Betriebsmodellen benötigt, daher müssen wir sie in den Code der Klasse C_Manager aufnehmen. Nachdem wir diese Funktion hinzugefügt haben, gibt der Compiler jedoch mehrere Warnungen aus, wenn er versucht, den Code zu kompilieren. Siehe Abbildung 03 unten.

Abbildung 03

Abbildung 03. Compiler-Warnungen

Im Gegensatz zu Compiler-Warnungen, die ignoriert werden können (obwohl dies nicht unbedingt empfohlen wird), können die Warnungen in Abbildung 03 dem Programm schaden und dazu führen, dass der generierte Code nicht korrekt funktioniert.

Wenn Sie feststellen, dass der Compiler solche Warnungen erzeugt hat, sollten Sie versuchen, den Fehler zu beheben, der diese Warnungen erzeugt. Manchmal ist es etwas ganz Einfaches, manchmal etwas Komplizierteres. Dabei kann es sich um eine Art von Änderung handeln, bei der ein Teil der Daten während der Konvertierung verloren geht. So oder so ist es immer wichtig zu prüfen, warum der Compiler solche Warnungen erzeugt, selbst wenn der Code kompiliert ist.

Das Vorhandensein von Compiler-Warnungen ist ein Zeichen dafür, dass etwas im Code nicht stimmt, da der Compiler Schwierigkeiten hat zu verstehen, was Sie programmieren. Wenn er es nicht versteht, kann er keinen 100% zuverlässigen Code erzeugen.

Einige Programmierplattformen erlauben es, Compiler-Warnungen abzuschalten, aber ich persönlich empfehle dies nicht. Wenn Sie 100 % zuverlässigen Code haben wollen, lassen Sie am besten alle Warnungen aktiviert. Sie werden im Laufe der Zeit feststellen, dass die Beibehaltung der Standard-Plattformeinstellungen der beste Weg ist, um einen zuverlässigeren Code zu gewährleisten.

Es gibt zwei Möglichkeiten, die oben genannten Warnungen zu beseitigen. Die erste besteht darin, Aufrufe der Funktion ClosePosition, die sich derzeit auf eine Funktion in der Klasse C_Orders beziehen, durch eine neue Funktion zu ersetzen, die in der Klasse C_Manager hinzugefügt wurde. Dies ist die beste Option, da wir den Aufruf in C_Manager überprüfen werden. Die zweite Möglichkeit besteht darin, dem Compiler mitzuteilen, dass sich die Aufrufe auf die Klasse C_Orders beziehen sollen.

Ich werde jedoch den Code ändern, um den neu erstellten Aufruf zu verwenden. So werden die problematischen Punkte, die zu Warnungen führen, gelöst, und der Compiler versteht, was wir zu tun versuchen.

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        if (m_Position.Ticket > 0) ClosePosition(m_Position.Ticket);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

Am einfachsten wäre es, dies im Destruktor zu lösen, aber es gibt einen etwas kniffligen Teil, der unten gezeigt wird:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                if (PositionSelectByTicket(m_Position.Ticket)) ClosePosition(m_Position.Ticket);
                                                ClosePosition();
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                ZeroMemory(m_Position.Ticket);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

Ich habe einige Zeilen gelöscht und Aufforderungen zum Schließen einer Position hinzugefügt. Wenn jedoch ein schwebender Auftrag aus irgendeinem Grund zu einer Position wird, müssen wir die Position wie im ursprünglichen Code entfernen. Die Position ist jedoch noch nicht von der Klasse C_Manager erfasst worden. In diesem Fall teilen wir dem Compiler mit, dass sich der Aufruf auf die Klasse C_Orders bezieht, wie im hervorgehobenen Code gezeigt.

Nachstehend eine weitere Änderung, die wir vornehmen müssen:

inline void EraseTicketPending(const ulong ticket)
                        {
                                if ((m_TicketPending == ticket) && (m_TicketPending > 0))
                                {
                                        if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); 
                                        else RemoveOrderPendent(m_TicketPending);
                                        m_TicketPending = 0;
                                }
                                ResetLastError();
                                m_TicketPending = (ticket == m_TicketPending ? 0 : m_TicketPending);
                        }

Der durchgestrichene Code, der der ursprüngliche Code war, wurde durch einen etwas komplizierteren Code ersetzt, der uns jedoch die Möglichkeit gibt, einen schwebenden Auftrag zu entfernen oder, wenn er zu einer Position geworden ist, zu löschen. Zuvor haben wir einfach auf ein Ereignis reagiert, über das uns die MetaTrader 5-Plattform informiert hat, wodurch der Wert, der als Ticket für eine schwebender Auftrag angezeigt wurde, auf Null gesetzt wurde, sodass eine neue schwebender Auftrag gesendet werden konnte. Aber jetzt tun wir etwas mehr als das, denn wir brauchen diese Funktionalität in einem 100% automatisierten System.

Durch die Implementierung dieser kleinen Änderungen erhalten wir einen systemweiten Bonus, der die Wiederverwendung von Code und das Testen verbessert.


Letzte Verbesserungen vor der Endphase

Vor der abschließenden Phase können wir noch einige Verbesserungen vornehmen, um die Wiederverwendung von Code zu erhöhen. Die erste ist im folgenden Code dargestellt:

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket) || (m_Position.Ticket == 0)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                        EraseTicketPending(m_TicketPending);
                                                }
                                                if (m_Position.Ticket > 0)      m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
                                {
                                        ZeroMemory(m_Position);
                                        if ((m_InfosManager.IsOrderFinish) && (m_TicketPending > 0))
                                        {
                                                RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                        }
                                        if (m_InfosManager.IsOrderFinish) EraseTicketPending(m_TicketPending);
                                }
                                ResetLastError();
                        }

Ein weiterer Punkt, der von diesen Verbesserungen profitieren kann, ist der folgende Code:

                ~C_Manager()
                        {
                                if (_LastError == (ERR_USER_ERROR_FIRST + ERR_Excommunicate))
                                {
                                        if (m_TicketPending > 0) RemoveOrderPendent(m_TicketPending);
                                        EraseTicketPending(m_TicketPending);
                                        ClosePosition();
                                        Print("EA was kicked off the chart for making a serious mistake.");
                                }
                        }

Und schließlich der letzte Punkt, der ebenfalls von der Wiederverwendung von Code profitiert:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (m_InfosManager.IsOrderFinish)
                                        {
                                                ClosePosition();
                                                EraseTicketPending(m_TicketPending);
                                                if (PositionSelectByTicket(m_TicketPending)) C_Orders::ClosePosition(m_TicketPending); else RemoveOrderPendent(m_TicketPending);
                                                m_TicketPending = 0;
                                                ResetLastError();
                                        }else   SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

Um die Frage der Verbesserungen und Änderungen abzuschließen, gibt es noch ein kleines Detail. Der EA kann z. B. ein bestimmtes maximales Volumen haben, das 10 Mal so groß ist wie das minimale Volumen. Wenn der Händler jedoch bei der Konfiguration des Volumens den 3-fachen Hebelwert angibt, wenn er die dritte Operation durchführt, wird der EA sehr nahe am maximal zulässigen Volumen liegen. Wenn Sie dann eine vierte Anfrage senden, würde dies gegen das maximal zulässige Volumen verstoßen.

Dies mag wie ein unbedeutender Mangel erscheinen, und viele mögen ihn als wenig gefährlich ansehen. In gewisser Weise stimme ich dieser Idee zu, denn der fünfte Auftrag wird niemals akzeptiert werden. Das bei der Programmierung angegebene Volumen betrug jedoch das 10-fache. Wenn also der vierte Auftrag angenommen wurde, hätte der EA das Volumen 12 Mal ausgeführt und damit das 2-fache des maximal konfigurierten Volumens überschritten. Der Grund dafür ist, dass der Händler eine 3-fache Hebelwirkung einstellt, aber was wäre, wenn er eine 9-fache Hebelwirkung angegeben hätte? Der Händler würde in diesem Fall erwarten, dass der EA nur einen Handel durchführt.

Stellen Sie sich also die Überraschung und das Erstaunen des Nutzers vor, der sieht, dass der EA die zweiten Position eröffnet hat, der das maximale Volumen um das 8-fache übersteigt. Eine solche Person kann sogar einen Herzinfarkt erleiden.

Wie Sie sehen, handelt es sich zwar um einen Fehler mit geringem Potenzial, aber dennoch um einen Fehler, der nicht zugelassen werden sollte, insbesondere bei einem automatisierten EA. Für einen manuellen EA wäre dies kein Problem, da der Händler prüfen würde, ob er nicht einen weiteren Eintrag mit demselben Hebelwert erstellt. Auf die eine oder andere Weise sollten wir eine Art EA-Block für solche Fälle bereitstellen. Und das sollte noch vor dem nächsten Artikel umgesetzt werden. Ich möchte mir über solche Dinge später keine Gedanken machen müssen.

Um dies zu beheben, fügen wir ein paar Codezeilen hinzu, wie unten gezeigt:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {                               
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Der grün dargestellte Code vermeidet das oben beschriebene Ereignis. Schauen Sie sich jedoch den hervorgehobenen Code genauer an. Haben Sie bemerkt, dass es genau derselbe ist? Wir können hier zwei Dinge tun: Ein Makro erstellen, um den gesamten Code zu platzieren, oder eine Funktion erstellen, um den gesamten gemeinsamen Code zu ersetzen oder vielmehr zu akkumulieren.

Ich habe mich entschlossen, eine Funktion zu erstellen, um alles einfach und übersichtlich zu halten, da viele Leser dieser Artikel gerade erst mit dem Programmieren beginnen und vielleicht nicht viel Erfahrung oder Wissen haben. Wir werden diese neue Funktion in einem privaten Teil des Codes unterbringen, während der Rest des EA-Codes nichts von ihrer Existenz wissen muss. Hier ist die neue Funktion:

inline bool IsPossible(const bool IsPending)
	{
		if (!CtrlTimeIsPassed()) return false;
		if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
		if ((IsPending) && (m_TicketPending > 0)) return false;
		if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
		{
			Print("Request denied, as it would violate the maximum volume allowed for the EA.");
			return false;
		}
                                
		return true;
	}

Wir müssen verstehen, was hier geschieht. Alle Codezeilen, die in den Auftragssendecodes wiederholt wurden, sind nun im obigen Code zusammengefasst. Es gibt jedoch kaum einen Unterschied zwischen dem Senden eines schwebenden Auftrags und eines Marktauftrags, und dieser Unterschied wird durch diesen speziellen Punkt bestimmt. Wir sollten also prüfen, ob es sich um einen schwebenden Auftrag oder um einen Marktauftrag handelt. Um diese beiden Arten von Aufträgen zu unterscheiden, verwenden wir das Argument, mit dem wir den gesamten Code in einem einzigen zusammenfassen können. Wenn es nicht möglich ist, einen Auftrag zu senden, wird false zurückgegeben. True wird zurückgegeben, wenn der Auftrag gesendet werden kann.

Der neue Funktionscode ist unten abgebildet:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return;
                                }
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Die Aufrufe hängen nun vom jeweiligen Fall ab, und alle durchgestrichenen Zeilen wurden aus dem Code entfernt, da sie nicht mehr sinnvoll sind. Auf diese Weise entwickeln wir Programme, die sicher, zuverlässig, stabil und robust sind, indem wir redundante Komponenten eliminieren: indem wir analysieren, was geändert und verbessert werden kann, und indem wir den Code so oft wie möglich testen und wiederverwenden.

Wenn man sich den fertigen Code ansieht, hat man oft den Eindruck, dass er so geboren wurde. Es bedurfte jedoch mehrerer Schritte, um die endgültige Form zu erreichen. Diese Schritte umfassen kontinuierliche Tests und Experimente, um Fehler und potenzielle Lücken zu minimieren. Dieser Prozess ist schrittweise und erfordert Hingabe und Beharrlichkeit, um den Code auf die gewünschte Qualität zu bringen.


Abschließende Schlussfolgerungen

Trotz allem, was bisher gesagt, gesagt und gezeigt wurde, gibt es immer noch eine Lücke, die wir schließen müssen, weil sie für ein 100% automatisches System sehr schädlich ist. Ich weiß, dass viele darauf gespannt sein sollten, dass der EA automatisch funktioniert. Aber glauben Sie mir, Sie werden keinen 100% automatisierten EA wollen, der eine Lücke enthält oder nicht funktioniert. Einige weniger schwerwiegende Fehler können sogar in einem manuellen System passieren, aber bei einem 100% automatisierten System ist dies nicht zulässig.

Da dieses Thema schwer zu erklären ist, werden wir es im nächsten Artikel behandeln. Der Code in seiner derzeitigen Fassung ist unten angefügt. Bevor Sie den nächsten Artikel lesen, möchte ich Ihnen eine kleine Herausforderung stellen. Sie wissen, dass der EA immer noch einen schwachen Teil enthält, der verhindert, dass er sicher automatisiert werden kann. Können Sie es erkennen? Hinweis: Es liegt an der Art und Weise, wie die Klasse C_Manager die Arbeit des EA analysiert.


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

Beigefügte Dateien |
Indikatoren mit Hintergrund: Kanäle mit Transparenz Indikatoren mit Hintergrund: Kanäle mit Transparenz
In diesem Artikel stelle ich eine Methode zur Erstellung von nutzerdefinierten Indikatoren vor, deren Zeichnungen mit der Klasse CCanvas aus der Standardbibliothek erstellt werden, und zeige die Eigenschaften von Charts für die Koordinatenkonvertierung. Ich werde speziell auf Indikatoren eingehen, die den Bereich zwischen zwei Linien mit Transparenz füllen müssen.
Erstellen eines EA, der automatisch funktioniert (Teil 10): Automatisierung (II) Erstellen eines EA, der automatisch funktioniert (Teil 10): Automatisierung (II)
Automatisierung bedeutet nichts, wenn Sie den Zeitplan nicht kontrollieren können. Kein Arbeitnehmer kann effizient sein, wenn er 24 Stunden am Tag arbeitet. Viele sind jedoch der Meinung, dass ein automatisiertes System 24 Stunden am Tag funktionieren sollte. Aber es ist immer gut, eine Möglichkeit zu haben, einen Arbeitsbereich für den EA festzulegen. In diesem Artikel geht es darum, wie man einen solchen Zeitbereich richtig festlegt.
Algorithmen zur Optimierung mit Populationen: Saplings Sowing and Growing up (SSG) Algorithmen zur Optimierung mit Populationen: Saplings Sowing and Growing up (SSG)
Der Algorithmus Saplings Sowing and Growing up (SSG, Setzen, Säen und Wachsen) wurde von einem der widerstandsfähigsten Organismen der Erde inspiriert, der unter den verschiedensten Bedingungen überleben kann.
Erstellen eines EA, der automatisch funktioniert (Teil 09): Automatisierung (I) Erstellen eines EA, der automatisch funktioniert (Teil 09): Automatisierung (I)
Obwohl die Erstellung eines automatisierten EA keine sehr schwierige Aufgabe ist, können ohne die notwendigen Kenntnisse viele Fehler gemacht werden. In diesem Artikel werden wir uns ansehen, wie man die erste Stufe der Automatisierung aufbaut, die darin besteht, einen Auslöser zu erstellen, um den Breakeven und einen Trailing-Stop zu aktivieren.