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

Erstellen eines EA, der automatisch funktioniert (Teil 12): Automatisierung (IV)

MetaTrader 5Handel | 24 Mai 2023, 11:23
277 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel Erstellen eines EA, der automatisch funktioniert (Teil 11): Automatisierung (III) haben wir uns angesehen, wie wir ein robustes System schaffen können, das die Fehler und Lücken, die ein Programm beeinträchtigen können, minimiert.

Manchmal höre ich Leute sagen, dass die Chance, dass etwas passiert, eins zu 500.000 ist, aber selbst wenn der Prozentsatz niedrig ist, besteht die Möglichkeit. Da wir uns bewusst sind, dass dies passieren kann, warum sollten wir nicht die Mittel schaffen, um Schäden oder Nebenwirkungen zu minimieren, falls dies geschieht? Warum sollte man ignorieren, was passieren könnte, und es nicht in Ordnung bringen oder irgendwie verhindern, nur weil die Wahrscheinlichkeit gering ist?

Wenn Sie diese kleine Artikelserie über die Automatisierung von EAs verfolgen, haben Sie sicher schon bemerkt, dass die Erstellung eines EAs für den manuellen Gebrauch recht schnell und einfach ist. Aber für einen 100% automatisierten EA ist das nicht so einfach. Sie haben vielleicht bemerkt, dass ich nie die Idee geäußert habe, dass wir ein 100%ig idiotensicheres System haben, das ohne jede Art von Überwachung verwendet werden kann. Ich glaube sogar, dass es ziemlich klar geworden ist, genau das Gegenteil von dieser Prämisse, die viele über den automatischen EA haben, indem sie denken, dass man ihn einschalten kann und das Ding einfach laufen lassen kann, ohne wirklich zu verstehen, was er tut.

Wenn wir über EA sprechen, der zu 100 % automatisiert ist, wird alles ernst und kompliziert. Dies gilt umso mehr, als wir immer mit Laufzeitfehlern zu rechnen haben und ein System entwickeln müssen, das in Echtzeit funktioniert. Beides zusammen und die Tatsache, dass es zu einem Bruch oder einer Störung im System kommen kann, macht die Arbeit für diejenigen, die den EA während seines Betriebs beaufsichtigen, extrem anstrengend.

Als Programmierer sollten Sie jedoch immer auf einige Schlüsselpunkte achten, die potenzielle Probleme verursachen können, selbst wenn alles perfekt zu funktionieren scheint. Damit will ich nicht sagen, dass Sie nach Problemen suchen sollten, wo sie vielleicht gar nicht existieren. Das ist es, was ein sorgfältiger und aufmerksamer Fachmann tun wird - nach Schwachstellen in einem System suchen, das auf den ersten Blick keine Schwachstellen aufweist.

Unser EA im derzeitigen Entwicklungsstadium, der für den manuellen und halbautomatischen Einsatz gedacht ist (mit Breakeven und Trailing Stop), hat keinen so zerstörerischen Bug wie Blockbuster(DC Comics supervillain). Wenn wir ihn jedoch zu 100 % automatisch nutzen, ändert sich die Situation und es besteht die Gefahr eines potenziell gefährlichen Ausfalls.

Im vorherigen Artikel habe ich diese Frage aufgeworfen und es Ihnen überlassen, zu verstehen, wo dieser Fehler liegt und wie er Probleme verursachen könnte, sodass wir unseren EA noch nicht zu 100 % automatisieren konnten. Konnten Sie herausfinden, wo der Fehler lag und wie er ausgelöst werden konnte? Nun, wenn die Antwort nein lautet, ist das in Ordnung. Nicht jeder kann den Fehler tatsächlich erkennen, wenn er sich den Code ansieht und ihn manuell oder halbautomatisch verwendet. Wenn Sie jedoch versuchen, den Code zu automatisieren, werden Sie ernsthafte Probleme bekommen. Dieser Fehler ist sicherlich der einfachste, der zu beobachten ist, aber nicht so einfach zu korrigieren, wie es für einen 100% automatisierten EA erforderlich wäre.

Um also zu verstehen, worum es geht, sollten wir die Dinge in Themen unterteilen. Ich denke, es wird Ihnen leichter fallen, etwas scheinbar Unwichtiges zu bemerken, das Ihnen großen Ärger bereiten kann.


Das Problem verstehen

Das Problem beginnt, wenn wir das maximale Volumenlimit festlegen, das der EA täglich handeln kann. Verwechseln Sie dieses maximale Tagesvolumen nicht mit dem Volumen der Operationen. Jetzt interessiert uns vor allem das maximale Tagesvolumen.

Der Einfachheit halber nehmen wir an, dass das Volumen das 100-fache des Mindestvolumens beträgt. Das heißt, der EA kann so viele Operationen wie möglich durchführen, bis dieses Volumen erreicht ist. Die letzte in der Klasse C_Manager hinzugefügte Regel lautet also, dass das Volumen diese 100 nicht überschreiten darf.

Nun wollen wir sehen, was tatsächlich passiert. Zu diesem Zweck werden wir den Code analysieren, der uns den Handel ermöglicht:

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;
                        }

Der obige Code verhindert, dass das Volumen überschritten wird. Aber wie läuft das eigentlich ab?

Nehmen wir an, der Händler hat den EA mit einem Lot-Multiplikator gestartet, der das 3-fache des erforderlichen Mindestvolumens beträgt, und das im EA-Code definierte maximale Volumen beträgt das 100-fache (dies wird während der Code-Kompilierung festgelegt). Dies wurde in früheren Artikeln erläutert. Nach 33 Handelsgeschäften wird der EA das 99-fache des Mindesthandelsvolumens erreichen, was bedeutet, dass wir eine weitere Position eröffnen könnten. Aufgrund der hervorgehobenen Zeile im obigen Code muss der Händler jedoch das Volumen auf 1 Mindestvolumen ändern, um das maximale Limit zu erreichen. Andernfalls ist der EA nicht in der Lage, den Vorgang auszuführen.

Die Idee ist, das maximale Volumen so zu begrenzen, dass der EA nicht mehr als den vorher festgelegten Parameter verliert (dies muss immer die wichtigste und wichtigste Sorge sein). Denn wenn der EA eine Position nicht mit einem Volumen eröffnet, das viel höher ist als das festgelegte, werden wir immer noch Verluste haben, aber diese können irgendwie kontrolliert werden.

Aber Sie könnten denken: Ich sehe keine Schwachstellen in diesem Code. In der Tat gibt es keinen Fehler in diesem Code. Die Funktionen, die ihn verwenden (siehe unten), können das vom EA gehandelte Volumen auf das angegebene Höchstlimit begrenzen.

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) 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;
                                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));
                        }
//+------------------------------------------------------------------+

Aber wo liegt dann der Fehler? Eigentlich ist das gar nicht so einfach zu verstehen. Um das Problem zu verstehen, müssen wir darüber nachdenken, wie der EA funktionieren könnte, wenn er keine Aufträge sendet. Denn mit diesem Code der Klasse C_Manager wird es gelingen, das Auslösen des EA zu verhindern. Das Problem tritt auf, wenn wir verschiedene Auftragsarten kombinieren. An diesem Punkt haben wir ein Problem mit dem Auslösen einer Blockzerschlagung. Es gibt einige Möglichkeiten, diese Art von Auslösern einzuschränken bzw. zu vermeiden. Sie lauten wie folgt:

  • Wählen Sie die Art der Auftragssendung oder das Modell. In diesem Fall würde der EA nur auf Market- oder schwebende Aufträge zurückgreifen. Diese Art von Lösung ist für einige automatisierte Handelsmodelle geeignet, da es Fälle gibt, in denen die ausschließliche Durchführung von Geschäften auf dem Markt geeigneter und üblicher ist. Aber in diesem Fall hätten wir nur begrenzte Arten von Handelssystemen. Hier soll jedoch gezeigt werden, wie ein System geschaffen werden kann, das die größtmögliche Anzahl von Fällen abdeckt, ohne dass man sich Gedanken darüber machen muss, welche Auftragsart zu verwenden ist.
  • Eine weitere Möglichkeit, den Auslöser zu vermeiden, besteht darin, genauer zu berücksichtigen, was bereits als Position eingegeben wurde und was die Position verändern wird (in diesem Fall schwebende Aufträge). Diese Lösung ist die bessere Wahl, hat jedoch einige erschwerende Faktoren, die die Programmierung kompliziert machen.
  • Ein anderer Weg (den wir einschlagen werden) ist derjenige, der die Grundlagen des bereits entwickelten Systems nutzt, sodass wir zwar berücksichtigen, was getan wird, aber keine Annahmen darüber machen, was getan werden kann. Aber auch so werden wir versuchen, den EA irgendwie dazu zu bringen, das Volumen vorherzusagen, das während des gesamten Zeitraums eines Tages gehandelt werden wird. Auf diese Weise können wir einen Trigger vermeiden, ohne die Arten von Handelssystemen einzuschränken, die der EA unterstützen kann.

Angesichts der Tatsache, dass das Problem bei der Kombination von Aufträgen auftritt, sollten wir verstehen, wie die Dinge wirklich ablaufen. Um auf das Beispiel mit dem Lot-Multiplikator von 3 zurückzukommen, bei dem bereits 33 Operationen stattgefunden haben, während das Tageslimit bei 100 liegt, wäre alles in perfekter Kontrolle, wenn der EA nur mit Marktaufträgen arbeiten würde. Wenn wir aber, aus welchem Grund auch immer, einen schwebenden Auftrag auf dem Handelsserver haben, sieht die Situation anders aus. Wenn dieser schwebende Auftrag weitere 3 Einheiten zum Mindestvolumen hinzufügt, dann hat er, sobald er aktiviert ist, das im EA zulässige Höchstvolumen überschritten.

Sie können sich vorstellen, dass diese 2 Volumeneinheiten, die die 100er Grenze überschritten haben, nicht viel sind, aber so einfach ist es nicht. Denken Sie an den Fall, dass der Händler den EA für den Handel mit 50 Lots konfiguriert. Wir können 2 Marktaufträge ausführen, die das Limit von 100 überschreiten. Nach dem Senden einer Market Order sendet der EA eine Pending Order, um das Positionsvolumen zu erhöhen. Jetzt wird sie Volumen 100 erreicht haben. Aber aus welchem Grund auch immer wurde dieser Auftrag noch nicht ausgeführt, da er immer noch ein schwebender Auftrag ist. Zu einem bestimmten Zeitpunkt entscheidet der EA, dass er einen weiteren Marktauftrag mit demselben Volumen von 50 Lots senden kann.

Die Klasse C_Manager bemerkt sofort, dass der EA das Tageslimit erreicht hat, da er 100 Einheiten haben wird. Aber die Klasse C_Manager weiß zwar, dass der Auftrag eine Pending Order ist, aber sie weiß nicht, was er damit anfangen soll. In diesem Fall wird der EA, wenn der Auftrag auf dem Server ausgeführt wird, die 100er-Regel überschreiten und ein Volumen von 150 Einheiten haben. Verstehen Sie das Problem? Vor einiger Zeit haben wir eine Sperre eingerichtet, um zu verhindern, dass der EA zu viele schwebende Aufträge im Buch hängen lässt oder zu viele Positionen mit einem bestimmten Volumen eröffnet. Diese Sperre wurde durch die einfache Tatsache gebrochen, dass der Trigger, der den EA automatisiert, nicht vorhersah, dass dies passieren könnte. Es wurde davon ausgegangen, dass die Klasse C_Manager in der Lage sein würde, den EA zu halten und zu verhindern, dass er das festgelegte maximale Volumen überschreitet. Die Klasse ist jedoch gescheitert, weil wir schwebende Aufträge mit Marktaufträgen kombiniert haben. 

Viele Programmierer würden dieses Problem einfach umgehen, indem sie nur Marktaufträge oder nur schwebende Aufträge verwenden. Das Problem ist damit aber noch nicht gelöst. Jeder neue automatisierte EA muss denselben Test- und Analysemodus durchlaufen, um zu verhindern, dass der Trigger ausgelöst wird.

Obwohl das oben beschriebene Problem auch bei der manuellen Anwendung des Systems beobachtet werden kann, ist die Wahrscheinlichkeit, dass ein Händler diesen Fehler macht, viel geringer. Die Schuld läge beim Händler und nicht beim Schutzsystem. Aber für ein 100 % automatisiertes System ist dies völlig inakzeptabel. Aus diesem Grund und aus einigen anderen Gründen sollten Sie NIEMALS einen 100% automatisierten EA ohne Aufsicht laufen lassen, selbst wenn Sie ihn programmiert haben. Auch wenn Sie ein hervorragender Programmierer sind, ist es keineswegs ratsam, es dabei bewenden zu lassen.

Wenn Sie glauben, dass ich Unsinn rede, bedenken Sie Folgendes. Es gibt Autopilotsysteme in Flugzeugen, die es ermöglichen, ohne menschliches Eingreifen zu starten, die Route zu fliegen und zu landen. Aber auch dann ist immer ein qualifizierter Pilot in der Kabine, der das Flugzeug steuert. Warum, glauben Sie, geschieht dies? Die Industrie würde nicht ein Vermögen für die Entwicklung eines Autopiloten ausgeben, um dann einen Piloten für die Bedienung des Flugzeugs ausbilden zu müssen. Das würde keinen Sinn machen, es sei denn, die Industrie selbst würde nicht auf Autopilot setzen. Denken Sie ein wenig darüber nach.


Behebung des Absturzes

Die bloße Anzeige des Fehlers behebt ihn nicht. Es braucht ein bisschen mehr als das. Aber die Tatsache, dass wir das Versagen kennen und verstehen, wie es ausgelöst wird und welche Folgen es hat, bedeutet, dass wir versuchen können, eine Art von Lösung zu finden.

Die Tatsache, dass wir darauf vertrauen, dass die Klasse C_Manager in der Lage sein wird, eine Verletzung des Volumens zu vermeiden, macht den Unterschied aus. Um das Problem zu lösen, müssen wir ein paar Dinge im Code ändern. Zunächst fügen wir dem System eine neue Variable hinzu:

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

Diese neue Variable wird uns helfen, das Volumenproblem zu lösen, indem wir eine Art Prognose erstellen. Wegen seines Aussehens wurde ein weiterer Punkt des Codes gestrichen.

Auf die Hinzufügung der neuen Variablen folgt eine Reihe von Änderungen. Ich werde aber nur die neuen Teile hervorheben. Im beigefügten Code können Sie alle Änderungen im Detail sehen. Als Erstes müssen Sie eine Routine zur Erfassung der Daten ausstehender Aufträge erstellen:

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

Dies ist etwas anderes als die Routine, die die Positionsdaten erfasst. Der Hauptunterschied besteht darin, dass wir prüfen, ob wir kaufen oder verkaufen. Dazu wird geprüft, ob es Typen gibt, die einen Kaufauftrag anzeigen. Der Rest der Funktion ist selbsterklärend.

Wir brauchen eine weitere neue Funktion:

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

Er aktualisiert die Daten, wenn der ausstehende Auftrag eine Aktualisierung vom Server erhält, und gibt die Informationen an den EA weiter.

Damit der EA den obigen Aufruf ausführen kann, müssen wir der Ereignisbehandlung durch OnTradeTransaction ein neues Ereignis hinzufügen:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

Die oben hervorgehobene Zeile ruft die Klasse C_Manager auf.

Kehren wir also zur Klasse C_Manager zurück, um die Lösung des Volumenproblems weiter zu implementieren.

Wir müssen ein Korrektursystem schaffen, wie in diesem Abschnitt beschrieben, um das System auf ein angemesseneres Sicherheitsniveau zu bringen. Der Fehler, der uns aufgefallen ist und den wir lange Zeit ignoriert haben, ist für die Aktualisierung des offenen Volumens verantwortlich. Dies würde das manuelle System nicht beeinträchtigen, ist aber für ein automatisiertes System fatal: Um diesen Fehler zu beheben, müssen wir daher die folgende Codezeile hinzufügen:

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))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

Die Entwicklung eines vollautomatischen Systems ist eine anspruchsvolle Aufgabe. Bei einem manuellen oder halbautomatischen System macht das Fehlen der oben genannten Zeile nicht den geringsten Unterschied. Doch bei einem automatisierten System kann jeder noch so kleine Fehler zu einer Katastrophe führen. Dies gilt umso mehr, wenn der Händler den EA nicht beaufsichtigt oder nicht weiß, was er tatsächlich tut. Dadurch werden Sie auf dem Markt definitiv Geld verlieren.

Der nächste Schritt besteht darin, die Funktionen zum Senden von Aufträgen und zum Anfordern von Märkten zu ändern. Sie müssen in der Lage sein, einen Wert an den Aufrufer zurückzugeben und gleichzeitig den Aufrufer darüber zu informieren, was gerade passiert. Hier ist der Code:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = 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);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                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));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

Achten Sie darauf, dass ich einen Test verwende, um zu prüfen, ob das Ticket noch gültig ist. Der Grund dafür ist, dass Sie bei einem NETTING-Konto eine Position schließen können, indem Sie ein der Position entsprechendes Volumen absenden. In diesem Fall, wenn die Position geschlossen wurde, müssen wir die Daten entfernen, um die Sicherheit und Zuverlässigkeit des Systems zu verbessern. Dies gilt für einen 100% automatisierten EA, während für einen manuellen EA solche Dinge unnötig sind.

Als Nächstes müssen wir eine Möglichkeit hinzufügen, mit der der EA weiß, welches Volumen er senden soll. Wenn Sie die Position im aktuellen System umkehren möchten, können Sie dies tun, allerdings sind dazu zwei Aufrufe oder besser gesagt zwei Anfragen an den Server erforderlich, anstatt einer einzigen Anfrage. Derzeit müssen Sie die Position schließen und dann den Antrag auf Eröffnung einer neuen Position senden. Wenn der EA das offene Volumen kennt, kann er wissen, welches Volumen er senden muss.

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

Der obige einfache Code reicht aus, um dies zu realisieren. Im Klassencode gibt es jedoch keine Möglichkeit, die Position tatsächlich umzukehren.

Um dies zu realisieren, werden wir die Funktionen zum Senden von Aufträgen erneut ändern. Beachten Sie aber, dass dies nicht etwas ist, was ein manueller oder halbautomatischer EA haben muss. Wir nehmen diese Änderungen vor, weil wir diese Mittel benötigen, um einen automatisierten EA zu erstellen. Außerdem ist es interessant, an einigen Punkten eine Art von Nachricht zu platzieren, damit der Händler, der den EA überwacht, herausfinden kann, was der EA tatsächlich tut. Auch wenn wir dies im Demonstrationscode nicht tun, sollten Sie es ernsthaft in Erwägung ziehen, dies zu tun. Denn blind zu sein und nur den Chart zu beobachten, reicht nicht aus, um ein seltsames EA-Verhalten zu erkennen.

Nach der Umsetzung all dieser Änderungen haben wir unseren Kernpunkt erreicht, der das Problem, mit dem wir es zu tun haben, tatsächlich lösen wird. Wir haben eine Möglichkeit hinzugefügt, mit der der EA jedes beliebige Volumen an den Server senden kann. Dies geschieht nur in zwei Aufrufen, in denen die Klasse C_Manager Zugriff auf den EA gewährt. Auf diese Weise beheben wir das Problem, dass der EA am Ende ein höheres Handelsvolumen verwenden könnte als im Code angegeben. Nun beginnen wir mit der Vorhersage des Volumens, das möglicherweise in die zu platzierende oder bereits platzierte Position ein- oder aussteigen wird.

Der neue Aufrufcode ist unten zu sehen:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = 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);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                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));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

In diesem Code haben wir dieses Argument hinzugefügt, aber beachten Sie bitte, dass sein Standardwert Null ist. Wenn Sie bei der Deklaration der Funktion keinen Wert angeben, wird der beim Aufruf des Konstruktors angegebene Wert verwendet.

In diesem Schritt wird jedoch keine Bearbeitung vorgenommen, da die Analyse dessen, was die Klasse C_Manager ausführen oder zulassen soll, sowohl für die Platzierung eines schwebenden Auftrags als auch für das Senden einer Anforderung zur Ausführung eines Marktauftrags gleich ist. Um herauszufinden, welchen Wert der EA erwartet, um von C_Manager aktiviert zu werden, verwenden wir einen kleinen Test mit einem ternären Operator, um den Wert korrekt auszufüllen. Sehen wir uns nun an, was wir der Funktion hinzufügen müssen, um den Test korrekt einzurichten:

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

Bei der Programmierung des Auslösemechanismus sollten Sie etwas beachten, um zu verhindern, dass Anträge abgelehnt werden. Wenn es einen schwebenden Auftrag gibt, muss dieser entfernt werden, bevor ein Auftrag zur Änderung des Volumens ausgeführt wird. Ist dies nicht der Fall, wird der Antrag abgelehnt. Warum? Das erscheint absolut seltsam. Das ist also wirklich erklärungsbedürftig.

Der Grund dafür ist, dass es keine Möglichkeit gibt, zu wissen, ob der EA die Ausführung des ausstehenden Auftrags zulässt oder nicht. Wenn sich jedoch das offene Volumen ändert und das System einen schwebenden Auftrag verwendet, um die Position zu stoppen, ist es wichtig, dass dieser Auftrag entfernt wird, bevor das Volumen geändert wird. Dieser Auftrag wird in jedem Fall gelöscht, sodass ein neuer Auftrag mit der richtigen Menge aufgegeben werden kann.

Es ist also klar, dass die Klasse C_Manager verlangt, dass bei der Änderung des offenen Volumens kein schwebender Auftrag vorhanden ist. Ein weiterer Grund dafür ist, dass Sie bei einer Kaufposition, die Sie umkehren möchten, einen Auftrag mit einem Volumen erteilen, das größer ist als das offene Volumen. Wenn der schwebende Auftrag, der ursprünglich ein Stop-Auftrag war, weiterhin besteht, kann es bei der Ausführung des Auftrags zu einem Problem kommen. Das Volumen wird weiter zunehmen. Es gibt also einen weiteren Grund, warum C_Manager bei der Änderung des Volumens verlangt, dass keine schwebende Aufträge vorliegen.

Ich hoffe, es ist jetzt klar, warum die Klasse das Löschen eines schwebenden Auftrags verlangt, wenn das Volumen geändert wird. Entfernen Sie diese Zeile nicht, da der EA dann keine Anfragen an den Server sendet.

Jetzt haben wir eine ziemlich seltsame Berechnung und einen noch seltsameren Test, der Ihnen höllische Kopfschmerzen bereiten kann, wenn Sie versuchen, ihn zu verstehen, ohne die Berechnung wirklich zu verstehen. Und am Ende gibt es noch einen weiteren Test, der auf den ersten Blick keinen Sinn ergibt.

Lassen Sie uns diesen Moment sorgfältig analysieren, damit jeder diesen kompletten mathematischen Wahnsinn auch wirklich verstehen kann. Zur Veranschaulichung sehen wir uns einige Beispiele an. Seien Sie also sehr vorsichtig, wenn Sie die Beispiele lesen, um den gesamten hervorgehobenen Code zu verstehen.

Beispiel 1:

Angenommen, es gibt keine offene Position, dann ist der Wert i1 gleich dem absoluten Wert in der Variablen Leverage; dies ist der einfachste Fall von allen. Es spielt also keine Rolle, ob wir eine Position eröffnen oder einen Auftrag in das Orderbuch einstellen. Wenn die Summe aus i1 und dem kumulierten Wert des EA kleiner ist als der angegebene Höchstwert, wird der Auftrag an den Server gesendet.

Beispiel 2:

Nehmen wir an, wir haben eine Short-Position mit dem Volumen X und haben keine offenen Aufträge. In dieser Situation sind verschiedene Szenarien denkbar:

  • Wenn der EA einen Auftrag zum Verkauf des Volumens Y sendet, dann ist der Wert i1 die Summe von X und Y. In diesem Fall wird der Auftrag möglicherweise nicht ausgeführt, weil wir die Verkaufsposition erhöhen.
  • Wenn der EA einen Auftrag Auftrag zum Kauf des Volumens Y sendet und dieses geringer ist als das Volumen X, dann ist der i1-Wert gleich der Differenz zwischen X und Y. In diesem Fall wird der Auftrag angenommen, da die Verkaufsposition reduziert wird.
  • Wenn der EA den Auftrag hat, Y zu kaufen, während Y gleich X ist, dann ist i1 gleich Null. In diesem Fall wird der Auftrag angenommen, da die Verkaufsposition geschlossen wird.
  • Wenn der EA einen Auftrag zum Kauf des Volumens Y sendet und dieses größer ist als das Volumen X, dann ist der Wert i1 gleich der Differenz zwischen X und Y. In diesem Fall kann der Auftrag nicht zugelassen werden, da wir die Position in eine Kaufposition umwandeln.

Das Gleiche gilt für eine Kaufposition. Die Anfrage des EA wird jedoch ebenfalls geändert, sodass wir am Ende das gleiche Verhalten haben, das in der Variablen i1 angegeben ist. Beachten Sie, dass wir in dem Test, in dem wir prüfen, ob die Summe von i1 und dem vom EA akkumulierten Wert kleiner als der zulässige Grenzwert ist, die Funktion MathAbs aufrufen. Wir tun dies, weil wir in einigen Fällen das negative i1 haben werden. Aber wir brauchen ein positives Ergebnis, damit der Test ordnungsgemäß durchgeführt werden kann.

Aber wir haben noch ein letztes Problem, das gelöst werden muss. Wenn wir die Position umkehren, wird die Aktualisierung des Handelsvolumens nicht korrekt erfolgen. Um dieses Problem zu lösen, müssen wir also eine kleine Änderung im Analysesystem vornehmen. Diese Änderung ist unten dargestellt:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(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 (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

Zunächst speichern wir die Position, in der sich das System vor der Aktualisierung befand. Danach greifen wir nicht mehr ein, bis wir am Ende überprüfen, ob sich die Richtung der Position geändert hat. Es geht darum, zu prüfen, ob wir den Kauf behalten oder in einen Verkauf umdrehen. Das Gleiche gilt für den umgekehrten Fall. Wenn es eine Änderung gibt, geben wir das offene Volumen zurück. Andernfalls führen wir die Berechnung ganz normal durch, um zu wissen, welches Volumen gerade geöffnet ist. Auf diese Weise können wir das Volumen, das der EA bereits gehandelt hat, genau kennen und aktualisieren.


Abschließende Schlussfolgerungen

Obwohl das aktuelle System viel komplizierter ist, weil wir sicherstellen müssen, dass wir keine Probleme mit dem automatisierten System haben, haben Sie sicher bemerkt, dass im Terminal fast keine Meldungen ausgegeben werden, anhand derer der Händler die Aktionen des EA überwachen könnte. Was ziemlich wichtig ist.

Da ich hier jedoch nur zeige, wie Sie ein 100% automatisiertes System erstellen können, weiß ich nicht, an welchen Stellen es für Sie sinnvoller wäre, zu wissen, was innerhalb des EAs vor sich geht. Verschiedene Informationen können für verschiedene Menschen mehr oder weniger wichtig sein. Aber ich rate niemandem, sich den Code einfach zu besorgen und ihn direkt in einem echten Konto zu starten, ohne mehrere Tests und Anpassungen vorzunehmen, um genau zu wissen, was passieren wird.

Im nächsten Artikel werde ich zeigen, wie man den EA einrichtet und startet, sodass er autonom arbeiten kann. Bis zum Erscheinen des Artikels, in dem ich die Erklärung liefere, sollten Sie also versuchen, diesen Artikel zu lesen. Versuchen Sie zu verstehen, wie einfache Maßnahmen einige Probleme lösen können, die oft viel komplizierter sind, als es scheint.

Ich versuche nicht, Ihnen den ultimativen Weg zur Erstellung eines automatisierten EA zu zeigen. Ich zeige hier nur eine von vielen Möglichkeiten auf, die genutzt werden können. Ich zeige auch die Risiken auf, die mit der Verwendung eines automatisierten EA verbunden sind, damit Sie wissen, wie Sie diese minimieren oder auf ein akzeptables Maß reduzieren können.

Ein letzter Tipp für diejenigen, die versuchen, den EA manuell oder mit einer Automatisierung zu verwenden. Wenn das System das Senden von Aufträgen mit der Begründung verweigert, dass das Volumen gegen die Nutzungsregel verstößt, müssen Sie nur den folgenden Wert ändern:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

Der Wert ist standardmäßig 10. Es kann jedoch vorkommen, dass Sie einen größeren Wert verwenden müssen, insbesondere auf dem Forex-Markt. Viele verwenden normalerweise 100. Sie sollten also einen viel größeren Wert als 10 verwenden. Wenn Sie z. B. das 10-fache des üblichen Volumens von 100 verwenden möchten, geben Sie den Wert 1000 ein. Der Code wird also wie folgt aussehen:

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

So können Sie bis zu 10 Aufträge mit dem angegebenen Volumen senden, bevor der EA das Senden neuer Aufträge blockiert.

In dem folgenden Video sehen Sie das System in seiner aktuellen Konfiguration.



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

Beigefügte Dateien |
Wie man MQL5 verwendet, um Kerzenmuster zu erkennen Wie man MQL5 verwendet, um Kerzenmuster zu erkennen
Ein neuer Artikel, um zu lernen, wie man Kerzenmuster der Preisen automatisch durch MQL5 erkennt.
Kategorientheorie in MQL5 (Teil 5): Differenzkern oder Egalisator Kategorientheorie in MQL5 (Teil 5): Differenzkern oder Egalisator
Die Kategorientheorie ist ein vielfältiger und expandierender Zweig der Mathematik, der erst seit kurzem in der MQL5-Gemeinschaft Beachtung findet. In dieser Artikelserie sollen einige der Konzepte und Axiome erforscht und untersucht werden, mit dem übergeordneten Ziel, eine offene Bibliothek einzurichten, die Einblicke gewährt und hoffentlich auch die Nutzung dieses bemerkenswerten Bereichs für die Strategieentwicklung von Händlern fördert.
Algorithmen zur Optimierung mit Populationen: Ein dem Elektro-Magnetismus ähnlicher Algorithmus (ЕМ) Algorithmen zur Optimierung mit Populationen: Ein dem Elektro-Magnetismus ähnlicher Algorithmus (ЕМ)
Der Artikel beschreibt die Prinzipien, Methoden und Möglichkeiten der Anwendung des elektromagnetischen Algorithmus bei verschiedenen Optimierungsproblemen. Der EM-Algorithmus ist ein effizientes Optimierungswerkzeug, das mit großen Datenmengen und mehrdimensionalen Funktionen arbeiten kann.
Kategorientheorie in MQL5 (Teil 4): Spannen, Experimente und Kompositionen Kategorientheorie in MQL5 (Teil 4): Spannen, Experimente und Kompositionen
Die Kategorientheorie ist ein vielfältiger und expandierender Zweig der Mathematik, der in der MQL-Gemeinschaft noch relativ unentdeckt ist. In dieser Artikelserie sollen einige der Konzepte vorgestellt und untersucht werden, mit dem übergeordneten Ziel, eine offene Bibliothek einzurichten, die Einblicke gewährt und hoffentlich die Nutzung dieses bemerkenswerten Bereichs für die Strategieentwicklung von Händlern fördert.