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

Erstellen eines EA, der automatisch funktioniert (Teil 15): Automatisierung (VII)

MetaTrader 5Beispiele | 29 Juni 2023, 10:29
537 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel Erstellen eines EA, der automatisch funktioniert (Teil 14): Automation (VI) haben wir uns mit der Klasse C_Automaton beschäftigt und ihre Grundlagen besprochen. Da die Verwendung der Klasse C_Automaton zur Automatisierung eines Systems jedoch nicht so einfach ist, wie es scheinen mag, werden wir uns in diesem Artikel Beispiele ansehen, wie dies geschehen kann.

Hier werden wir 3 verschiedene Modelle sehen. Der Artikel konzentriert sich ausschließlich auf die Erläuterung, wie die Klasse C_Automaton angepasst werden kann, um jedes der Modelle zu implementieren. Für weitere Details über das System sollten Sie die vorherigen Artikel lesen, da wir in diesem Artikel nur auf die Anpassung, d.h. den abhängigen Teil, eingehen werden.

Das Thema ist eigentlich ziemlich lang, also lassen Sie uns zu praktischen Beispielen übergehen. 


Automatisierungsbeispiel 1: Exponentiell gleitender Durchschnitt mir der Periodenlänge 9

Das Beispiel verwendet den EA-Code, der in der unten angehängten Datei EA_v1.mq5 verfügbar ist. Beginnen wir mit dem Klassenkonstruktor:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, 0, 0, Leverage, IsDayTrade, 0, false, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars  = iBars(NULL, m_TF);
                                m_Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                        }

Betrachten wir die Unterschiede im Vergleich zum Standardsystem:

  • Das System wird keinen Stop-Loss oder Take-Profit haben, d.h. es wird kein Gewinn- oder Verlustlimit im System geben, da der EA selbst diese Limits in Abhängigkeit von der Preisbewegung generieren wird.
  • Das System erstellt keine Trailing-Stop-, Breakeven- oder Pending-Stop-Aufträge.
  • Wir werden einen Handle verwenden, und zwar den 9-Perioden-Indikator des exponentiellen gleitenden Durchschnitts auf der Grundlage des Schlusskurses.

Der Rest wurde bereits weiter oben erklärt, sodass wir mit der Berechnung des Mechanismus fortfahren können. Die entsprechenden Möglichkeiten wurden bereits im vorangegangenen Artikel erörtert, lesen Sie ihn also bitte für weitere Einzelheiten. Sobald jedoch die Berechnungsmethode definiert ist, erscheint der folgende Code:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if (m_Buff[0] > m_Buff[m_Infos.Shift]) return TRIGGER_BUY;
                                        if (m_Buff[0] < m_Buff[m_Infos.Shift]) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

Klingt zu kompliziert? Das ist nicht der Fall. Das ist ganz einfach, wenn Sie Regeln in einem Flussdiagramm erstellt haben, das wir im vorherigen Artikel besprochen haben. Zuerst versuchen wir, den Inhalt des Indikators zu lesen; wenn das nicht möglich ist, geben wir einen Trigger_None zurück, und das Signal kann verloren gehen.

In einigen Fällen, vor allem wenn das verlorene Signal ein Ausstiegssignal war, kann das System beginnen, Verluste zu generieren. Wir sollten jedoch keine voreiligen Vermutungen anstellen. Ich denke, das erklärt, warum Sie den EA nicht unbeaufsichtigt lassen sollten. Eine mögliche Lösung wäre, im Falle eines Fehlers ein Signal zum Schließen der Position an die Klasse C_Manager zu senden. Aber wie ich bereits erwähnt habe, sollten wir keine Vermutungen anstellen, also liegt es an Ihnen, ob Sie dieses Signal hinzufügen oder nicht.

Dann haben wir den Zähler für die Balken aktualisiert, sodass das Signal erst beim nächsten Balken ausgelöst wird. Dies geschieht jedoch nur, wenn der Indikator eine Antwort gibt. Andernfalls wird das Signal beim nächsten OnTime-Ereignis erneut geprüft. Wir sollten also wirklich keine Vermutungen darüber anstellen, was passiert. Achten Sie auf die Reihenfolge, in der alles geschieht. Wenn die Aktualisierung der Anzahl der Balken früher erfolgt, würden wir das nächste OnTime-Ereignis verpassen. Da wir ihn jedoch später aktualisieren, können wir das OnTimer-Ereignis empfangen und so versuchen, den Indikator erneut zu lesen.

Lassen Sie uns nun die Berechnung durchführen, um festzustellen, ob wir kaufen oder verkaufen sollen. Um diese Berechnung zu verstehen, muss man wissen, wie der exponentielle gleitende Durchschnitt berechnet wird. Im Gegensatz zu anderen gleitenden Durchschnitten reagiert der exponentielle gleitende Durchschnitt schneller auf Preisänderungen. Sobald er also abfällt, wissen wir anhand der Position des Schlusskurses, die wir im Konstruktor definiert haben, ob der Balken über oder unter ihm geschlossen hat.

Aber es gibt ein Detail in dieser Berechnung: Sie wird diese Information nur schnell genug melden, wenn wir den aktuellen Durchschnittswert mit dem unmittelbar vorhergehenden Wert vergleichen. Jede noch so kleine Veränderung kann also den Auslöser auslösen. Wenn Sie die Empfindlichkeitsstufe verringern möchten, müssen Sie den Wert in der Variablen m_Infos.Shift von 1 auf 2 oder auf einen höheren Wert ändern. Dadurch wird die Verschiebung des gleitenden Durchschnitts simuliert, um eine bestimmte Art von Bewegung zu erfassen, wobei die Empfindlichkeit des Systems verringert oder erhöht wird.

Diese Art der Verschiebung ist bei einigen Einstellungen üblich, z. B. bei Joe di Napoli. Viele Leute denken, dass es notwendig ist, den Balken im Verhältnis zum gleitenden Durchschnitt zu betrachten, aber in Wirklichkeit ist es nur notwendig, den MA entsprechend anzupassen, um zu verstehen, ob der Balken dem Muster folgt. Im Falle des Joe di Napoli-Setups sollten wir eine Zickzack-Berechnung auf dem gleitenden Durchschnitt durchführen, damit der Auslöser zum richtigen Zeitpunkt aktiviert wird. Dennoch brauchen wir uns die Balken nicht anzusehen, wir brauchen nur die Durchschnittswerte.

Ein wichtiges Detail in der obigen Berechnung: Der Nullpunkt in der Berechnung gibt den letzten Wert des Puffers an, d. h. den letzten vom Indikator berechneten Wert. 

Wenn der letzte Wert höher ist als der vorherige, dann sollte der EA sofort kaufen. Wenn er niedriger ist, sollte der EA verkaufen.

Hier ist eine kuriose Tatsache: Dieses System ähnelt dem bekannten System von Larry Williams. Manche Leute verwenden ein zusätzliches Element: Anstatt sofort zu kaufen oder zu verkaufen, können Sie eine schwebende Order zum Hoch oder Tief des Balkens senden, bei dem das Signal des gleitenden Durchschnitts ausgelöst wurde. Da die Klasse C_Manager garantiert, dass der Server nur eine ausstehende Bestellung hat, müssen wir nur die Auslöser Funktion ändern, ohne die Berechnung zu ändern. Anstelle einer Anfrage für einen Markthandel sendet das System also einen schwebenden Auftrag mit den Daten des Balkens, der das Signal erzeugt hat.

Dies wird nicht der Code sein, der in der beigefügten Datei enthalten ist, sondern er wird wie unten dargestellt aussehen:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_BUY, iHigh(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_SELL, iLow(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                        };

Wir haben dem Code der Klasse C_Manager eine neue Funktion hinzugefügt; sie ist in der beigefügten Datei verfügbar. Achten Sie nun darauf, dass sich der zu erstellende Auftrag von einem Markteintritt unterscheidet. Wir haben jetzt einen Eintrag, der auf dem Preis eines der Limits des Balkens basiert. Dies wird sich mit der Entwicklung der Situation ändern. Wenn der Auftrag in dem Balken, in dem der Trigger erstellt wurde, nicht ausgeführt wurde, wird der Auftrag im nächsten Balken automatisch zurückgesetzt. Bitte beachten Sie, dass dieses Modell nicht in der Anlage enthalten ist, ich zeige nur sein Potenzial.

Ich denke, Sie haben bereits verstanden, dass das System recht flexibel ist. Wir haben jedoch nur an der Oberfläche dessen gekratzt, was mit diesem Klassensystem erreicht werden kann. Um eine weitere Anwendung zu veranschaulichen, sehen wir uns das zweite Beispiel an.


Automatisierungsbeispiel 2: Verwendung von RSI oder IFR

Wir haben gesehen, wie das MA-basierte System funktioniert. Betrachten wir nun die Verwendung eines anderen Indikators. In diesem Beispiel werden wir einen weit verbreiteten Indikator verwenden. Die Methode ist jedoch auch auf jeden anderen Indikator oder Oszillator anwendbar, und die Idee ist ziemlich universell.

Der EA-Code für dieses Beispiel ist in der dem Artikel beigefügten Datei EA_v2.mq5 verfügbar. Beginnen wir wieder mit dem Konstruktor:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, 0, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars = iBars(NULL, m_TF);
                                m_Handle = iRSI(NULL, m_TF, 14, PRICE_CLOSE);

                        }

Betrachten wir nun die Unterschiede zum ersten Beispiel. Sie können den gleichen Prozess wie im vorherigen Beispiel wiederholen oder das vorherige Beispiel so anpassen, dass es wie dieses aussieht:

  • Hier haben wir bereits einen Wert definiert, der als Stop-Loss verwendet wird nach Eröffnung einer Position zu verwenden;
  • Geben Sie einen Wert an, der als Breakeven und Trailing-Stop verwendet werden soll;
  • Wir werden einen schwebenden Auftrag als Stop-Punkt verwenden;
  • Geben Sie die Verwendung des 14-Perioden-RSI auf der Grundlage des Schlusskurses an.
  • Überkaufte (overbought) und überverkaufte (oversold) Werte werden hier eingegeben und zur späteren Verwendung gespeichert.

Sehen Sie, wie einfach das Verfahren ist? Sie können einfach einen anderen Indikator anstelle des iRSI verwenden. Der nächste Schritt ist die folgende Berechnung:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if ((m_Buff[0] > m_Buff[m_Infos.Shift]) && (m_Buff[0] > m_Infos.OverSold) && (m_Buff[m_Infos.Shift] < m_Infos.OverSold)) return TRIGGER_BUY;
                                        if ((m_Buff[0] < m_Buff[m_Infos.Shift]) && (m_Buff[0] < m_Infos.OverBought) && (m_Buff[m_Infos.Shift] > m_Infos.OverBought)) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

Bei der Berechnung führen wir dieselben Tests wie im vorherigen Beispiel durch, allerdings mit einigen zusätzlichen Details. Vielleicht ist Ihnen das folgende Detail aufgefallen: Wir analysieren, ob die Bewegung des Indikators nach unten oder nach oben gerichtet ist. Dieser Faktor ist sehr wichtig, da er auf eine Korrektur hindeuten kann. Deshalb haben wir eine Verschiebung, um diese Art von Bewegung zu bemerken. Aber so oder so prüfen wir, ob der Indikator auf eine überverkaufte oder überkaufte Situation hin weist, bevor wir eine zweite Entscheidung treffen, die auf dem Ausstieg des Indikators aus diesem Bereich basiert. Dies erzeugt einen Auslöser für einen Kauf oder Verkauf.

Ansonsten gibt es keine großen Veränderungen. Wie Sie sehen können, ist das Verfahren recht einfach und ändert sich je nach Analyse nicht wesentlich. Wir definieren einfach einen Auslöser, der eine Aktion auslöst, um sie an das Modell und die Methodik anzupassen, die für die Automatisierung des EA erforderlich sind.

Im Gegensatz zum vorherigen Beispiel wollen wir in diesem Beispiel jedoch eine Breakeven-Bewegung und einen Trailing-Stop implementieren. Der Gedanke dahinter ist, dass wir eine Position gewinnbringend schließen können, wenn es eine Bewegung gibt, die Gewinn bringen kann. Diese Änderung wird durch den folgenden Zusatz zum Code umgesetzt:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                TriggerTrailingStop();
                        };

Beachten Sie, dass das Verfahren gegenüber dem Original praktisch unverändert geblieben ist. Wir haben jedoch diese Aufforderung hinzugefügt, die den Breakeven und den Trailing-Stop implementieren wird. Wie Sie sehen, kommt es nur darauf an, wann und wo man anruft, denn das System hat bereits alle Details für die spätere Verwendung und kann sich an unsere Bedürfnisse in jedem gewünschten Fall anpassen.

Sehen wir uns also ein weiteres Beispiel an, um die Ideen besser zu verstehen.


Automatisierungsbeispiel 3: Kreuzung gleitender Durchschnitte

Dieses Beispiel ist in der Datei EA_v3.mq5 enthalten. Aber wir werden uns nicht mit dem einfachsten Automatisierungsprozess begnügen: Sie können einige kleinere Änderungen in der Klasse C_Manager sehen. Die erste ist die Erstellung einer Routine, die dem Automatisierungssystem mitteilt, ob eine Kauf- oder Verkaufsposition besteht. Er ist unten aufgeführt:

const bool IsBuyPosition(void) const
                        {
                                return m_Position.IsBuy;
                        }

Diese Funktion bestätigt nämlich nicht, ob die Position offen ist oder nicht, sondern gibt nur zurück, was die Variable für Kauf oder Verkauf anzeigt. Es geht nicht darum, zu prüfen, ob eine Position offen ist, sondern ob sie gekauft oder verkauft wurde, daher die Einfachheit der Funktion. Wenn Ihr Automatisierungssystem diese Überprüfung jedoch erfordert, können Sie prüfen, ob eine offene Position vorhanden ist. Wie auch immer, für unser Beispiel reicht diese Funktion aus. Das liegt an der Funktion, die darunter liegt:

                void LockStopInPrice(const double Price)
                        {
                                if (m_InfosManager.IsOrderFinish)
                                {
                                        if (m_Pending.Ticket == 0) return;
                                        if ((m_Pending.PriceOpen > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Pending.PriceOpen < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = Price, m_Pending.SL = 0, m_Pending.TP = 0);
                                }else
                                {
                                        if (m_Position.SL == 0) return;
                                        if ((m_Position.SL > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Position.SL < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, Price, m_Position.TP);
                                }
                        }

Diese Funktion dient dazu, einen Stoppkurs an einem bestimmten Punkt zu setzen. Er führt nämlich dieselbe Aktion aus wie der Trailing-Stop-Code, wenn der Breakeven bereits aktiviert wurde. Bei einigen Handelsarten wird die Position jedoch nicht ausgeglichen, sondern der Stop-Loss wird auf der Grundlage bestimmter Kriterien verschoben, z. B. dem Hoch oder Tief des vorherigen Balkens, dem vom gleitenden Durchschnitt angezeigten Kurs oder einem anderen Automatisierungsmechanismus. In diesem Fall benötigen wir eine spezielle Funktion, um diese Aufgabe zu erfüllen. Beachten Sie, dass es Mechanismen gibt, die verhindern, dass sich der Wert in die angegebene Richtung bewegt, was den Verlust erhöhen würde. Diese Art von Sperre ist sehr wichtig, insbesondere wenn Sie Werte auf der Grundlage des gleitenden Durchschnitts senden möchten.

Daraus ergibt sich nun der folgende Code für die Trailing-Stop-Funktion:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 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_InfosManager.IsOrderFinish ? m_Pending.PriceOpen : m_Position.SL);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                                LockStopInPrice(v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1)));
                                        {                                               
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (m_InfosManager.IsOrderFinish) ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = price, m_Pending.SL = 0, m_Pending.TP = 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

Die durchgestrichenen Teile wurden entfernt, weil sie jetzt einen eigenen Aufruf zur besseren Wiederverwendung von Code.

Nachdem wir nun die Änderungen an der Klasse C_Manager implementiert haben, sehen wir uns an, wie wir dieses dritte Beispiel erstellen können, wobei wir mit den Änderungen beginnen. Diese können von Fall zu Fall unterschiedlich sein. Sie sollten jedoch immer auf die Planung achten, um eine Automatisierung zu erreichen. Und da die Automatisierung hier ein wenig mehr Aufwand erfordert als in den vorherigen Fällen, sehen wir uns die erforderlichen Änderungen an. Diese Änderungen reichen für jedes Modell aus, das 2 Indikatoren gleichzeitig verwendet.

Beginnen wir mit der Deklaration von Variablen:

class C_Automaton : public C_Manager
{
        protected:
                enum eTrigger {TRIGGER_NONE, TRIGGER_BUY, TRIGGER_SELL};
        private :
                enum eSelectMedia {MEDIA_FAST, MEDIA_SLOW};
                struct st00
                {
                        int     Shift,
                                nBars;
                        double  OverBought,
                                OverSold;
                }m_Infos;
                struct st01
                {
                        double  Buff[];
                        int     Handle;
                }m_Op[sizeof(eSelectMedia) + 1];
                int     m_nBars;
                ENUM_TIMEFRAMES m_TF;

Hier haben wir eine Auflistung, die uns helfen wird, auf die MA-Daten in einer Hochsprache zuzugreifen, um Fehler bei der Programmierung der Berechnungen zu vermeiden.

Als Nächstes haben wir die Struktur, die uns den Zugriff sowohl auf den Puffer als auch auf das Handle des Indikatorsermöglichen wird. Beachten Sie jedoch, dass die Struktur als Array deklariert ist und die Größe dieses Arrays genau der Menge der Daten in der Auflistung plus 1 entspricht. Mit anderen Worten: Unabhängig von der Anzahl der Elemente, die wir verwenden wollen, müssen wir sie lediglich der Auflistung hinzufügen. Auf diese Weise passt sich die Anordnung an das endgültige Modell an, das wir bauen werden.

In gewisser Weise ist dies eine bessere Option als das Standardklassenmodell. Da wir aber ein einfacheres Modell implementieren konnten, wurde es zuerst umgesetzt. Ich habe den Eindruck, dass dadurch alles klarer und leichter zu verstehen wird.

Jetzt wissen Sie, wie Sie der Klasse C_Automaton auf sehr einfache Weise mehrere Indikatoren hinzufügen können. Aber sehen wir uns an, wie man die Dinge im Klassenkonstruktor initialisiert:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                                                bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                                                const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, FinanceTake, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod)
                        {
                                for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                {
                                        m_Op[c0].Handle = INVALID_HANDLE;
                                        ArraySetAsSeries(m_Op[c0].Buff, true);
                                }
                                m_Infos.Shift      = (iShift < 3 ? 3 : iShift);
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                m_nBars = iBars(NULL, m_TF);
                                m_Op[MEDIA_FAST].Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                                m_Op[MEDIA_SLOW].Handle = iMA(NULL, m_TF, 20, 0, MODE_SMA, PRICE_CLOSE);
                        }

Hier beginnt der Zauber. Sehen Sie, wie ich alle Indikatoren standardmäßig initialisiere, unabhängig von ihrer Nummer. Der einfachste Weg ist die Verwendung dieser Schleife. Sie müssen sich nicht um die Anzahl der Indikatoren in der Auflistung kümmern. Das spielt keine Rolle, denn diese Schleife kann sie alle verwalten.

Jetzt kommt ein Punkt, der wirklich unsere Aufmerksamkeit erfordert. In diesem Stadium verwenden alle Indikatoren denselben Zeitrahmen, aber Sie benötigen möglicherweise unterschiedliche Zeitrahmen für verschiedene Indikatoren. In diesem Fall müssen Sie den Code des Konstruktors anpassen. Die Änderungen werden jedoch minimal sein und nur für das von Ihnen erstellte Modell gelten.

Seien Sie jedoch vorsichtig damit, wenn Sie die Handles für die spätere Verwendung erfassen. Sie müssen sie erfassen, damit jeder der Indikatoren korrekt instanziiert wird. Wenn dies nicht richtig gemacht wird, können Sie Probleme mit Ihrem Modell bekommen. Hier gebe ich an, dass ich den exponentiellen gleitenden Durchschnitt über 9 Perioden und den arithmetischen gleitenden Durchschnitt über 20 Perioden im System verwenden werde. Sie können aber auch verschiedene Indikatoren miteinander kombinieren, vorausgesetzt natürlich, dass sie für Ihr Betriebssystem erforderlich sind.

Wichtiger Hinweis! Es gibt einen wichtigen Punkt, der hier zu beachten ist: Wenn Sie einen von Ihnen erstellten nutzerdefinierten Indikator verwenden, muss sich dieser nicht im Asset-Chart befinden. Da Sie diesen Indikator jedoch nicht unter den Standardindikatoren finden, sollten Sie die Funktion iCustom verwenden, um das Handle zu starten und die Daten dieses nutzerdefinierten Indikators zu erhalten. Bitte lesen Sie in der Dokumentation nach, wie Sie diese Funktion verwenden, um auf Ihren nutzerdefinierten Indikator zugreifen zu können. Auch hier muss es nicht unbedingt auf der Chart erscheinen.

Achten Sie auf diesen Moment, denn er ist wirklich wichtig. Wenn wir im EA keinen Offset-Wert angeben, können wir auch nicht den Standardwert verwenden. Der Grund dafür ist, dass wir bei Verwendung des Standardwerts Schwierigkeiten haben werden, die Kreuzung der Durchschnitte tatsächlich zu überprüfen. Wir müssen einen Mindestversatzwert angeben, und das ist der Wert von 3. Wir könnten sogar 2 verwenden, um den Auslöser empfindlicher zu machen. Der Wert 1 kann jedoch nicht verwendet werden, da er keine angemessene Analyse zulässt. Um zu verstehen, warum das so ist, sollten wir uns ansehen, wie die Berechnung durchgeführt wird.

Sobald der Konstruktor die zu verwendenden Daten korrekt initialisiert hat, müssen wir den Teil, der für die Berechnungen verantwortlich ist, so gestalten, dass der Auslösemechanismus den EA automatisch arbeiten lassen kann. Wenn der Mechanismus mehrere Indikatoren hat, sollte das System ein wenig anders funktionieren als bei der Verwendung von nur einem Indikator. Dies lässt sich aus dem folgenden Code ersehen:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                bool bOk = false;
                                        
                                if (iRet = iBars(NULL, m_TF)) > m_nBars)
                                {
                                        for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                        {
                                                if (m_Op[c0].Handle == INVALID_HANDLE) return TRIGGER_NONE;
                                                if (CopyBuffer(m_Op[c0].Handle, 0, 0, m_Infos.Shift + 1, m_Op[c0].Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                                bOk = true;
                                        }
                                        if (!bOk) return TRIGGER_NONE; else m_nBars = iRet;
                                        if ((m_Op[MEDIA_FAST].Buff[1] > m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] < m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_BUY;
                                        if ((m_Op[MEDIA_FAST].Buff[1] < m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] > m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

Wir verwenden eine Schleife, die Aufgaben ausführt, ohne sich um die Anzahl der verwendeten Indikatoren zu kümmern. Es ist jedoch äußerst wichtig, dass sie alle im Konstruktor korrekt initialisiert werden, da die Berechnungsphase sonst nicht in der Lage ist, Kauf- oder Verkaufsauslöser zu erzeugen.

Zunächst wird geprüft, ob die Indikator-ID korrekt initialisiert ist. Wenn nicht, dann haben wir keinen gültigen Auslöser. Sobald wir diese Prüfung abgeschlossen haben, beginnen wir mit der Erfassung von Daten aus dem Indikatorpuffer. Wenn Sie Zweifel an der Verwendung dieser Funktion haben, empfehle ich Ihnen, in der Dokumentation über die Funktion CopyBuffer nachzulesen. Diese Funktion ist besonders nützlich, wenn Sie mit nutzerdefinierten Indikatoren arbeiten.

Sobald wir alle Indikatoren mit ihren jeweiligen Puffern haben, können wir mit dem eigentlichen Berechnungsteil fortfahren. Aber Moment mal...Was ist das für ein Code vor der Berechnung? Dieser Code dient dazu, die Situation zu vermeiden, in der wir eine leere Liste haben. In diesem Fall wird das Berechnungssystem nicht ausgelöst.. Ohne diesen Code könnte die Berechnung auch dann ausgelöst werden, wenn die Länge der Liste Null wäre. Dies würde die Robustheit des Systems völlig zerstören. Aber zurück zum Berechnungssystem, und da wir den MA-Crossover verwenden, müssen wir hier sehr vorsichtig sein.

Beachten Sie, dass wir nicht rücksichtslos den letzten Wert im Puffer auf Null prüfen. Der Grund dafür ist, dass es vor dem tatsächlichen Schließen des Zeitrahmens zu einem falschen Crossover der Durchschnitte kommen kann, was zu einem versehentlichen Auslösen führen kann.

Prüft das System die Puffer nur, wenn ein neuer Balken erzeugt wird? Die Antwort ist ja, aber wenn sich die Durchschnitte genau zum Zeitpunkt der Balkenbildung kreuzen, wird das System einen Auftrag auslösen. Daher ignorieren wir den letzten Wert und analysieren den vorherigen. Aus diesem Grund stellen wir den Offset auf mindestens 2 für maximale Empfindlichkeit oder 3, wie in diesem Beispiel, ein, damit der Crossover ein wenig weiter von dem sich bildenden Balken entfernt stattfinden kann. Sie können jedoch versuchen, andere Berechnungsmethoden zu verwenden. Diese ist nur zu Demonstrationszwecken gedacht und sollte auf keinen Fall für ein echtes Konto verwendet werden.

Um dieses letzte Modell zu vervollständigen, wollen wir uns noch etwas anderes über das System ansehen:

inline virtual void Triggers(void) final
                        {
#define def_HILO 20
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                LockStopInPrice(IsBuyPosition() ?  iLow(NULL, m_TF, iLowest(NULL, m_TF, MODE_LOW, def_HILO, 0)) : iHigh(NULL, m_TF, iHighest(NULL, m_TF, MODE_HIGH, def_HILO, 0)));
#undef def_HILO
                        };

Der große Vorteil dieser Funktion ist genau dieser Code, in dem wir einen sehr kuriosen Trailing-Stop haben. Der Order- oder Stoppkurs liegt also je nach Situation beim Hoch oder Tief, je nachdem, ob wir kaufen oder verkaufen. Der verwendete Wert ist dem HILO-Indikator sehr ähnlich, der vielen B3-Händlern bekannt ist. Dieser Indikator sucht nach einem Kurshoch oder -tief innerhalb einer bestimmten Anzahl von Balken, falls Sie ihn noch nicht kennen. Dieser Code ist dafür verantwortlich: Hier suchen wir nach dem Wert LO und hier nach dem Wert HI; in beiden Fällen ist HILO 20.

Damit ist das dritte Beispiel abgeschlossen.


Abschließende Gedanken

In dieser kleinen Sequenz habe ich gezeigt, wie Sie einen EA entwickeln können, der automatisch arbeitet. Ich habe versucht, es auf spielerische und einfache Weise zu demonstrieren. Auch mit dieser Präsentation wird es noch einige Zeit dauern, bis man tatsächlich lernt, wie man den EA entwickelt.

Ich habe die wichtigsten Fehler, Probleme und Schwierigkeiten aufgezeigt, die die Arbeit eines Programmierers bei der Erstellung eines automatisch arbeitenden EA bestimmen. Aber ich habe Ihnen auch gezeigt, dass dies eine Menge Wissen bringen und die Art und Weise, wie Sie den Markt beobachten, verändern kann.

Ich habe versucht, die Dinge so darzustellen, dass man tatsächlich ein sicheres, zuverlässiges und robustes System aufbauen kann. Gleichzeitig sollte es modular, kompakt und sehr leicht sein. Und Sie sollten in der Lage sein, es in Verbindung mit vielen anderen Dingen zu verwenden. Es nützt nichts, ein System zu haben, mit dem man nicht mehrere Dinge gleichzeitig bedienen kann. Denn Sie werden nicht immer in der Lage sein, einen guten Gewinn zu erzielen, wenn Sie nur einen Vermögenswert handeln.

Vielleicht werden die meisten Leser an diesem letzten Artikel der Reihe interessiert sein, in dem ich die Idee anhand von 3 praktischen Beispielen erläutere. Bitte beachten Sie jedoch, dass die Kenntnis der gesamten Artikelreihe erforderlich ist, um die Vorteile dieses Artikels nutzen zu können. Aber ich glaube, dass es mir auf sehr einfache Weise gelungen ist, die Idee zu vermitteln, dass es nicht notwendig ist, ein Genie in der Programmierung zu sein oder mehrere Kurse und Abschlüsse zu haben. Sie müssen nur wirklich verstehen, wie die MetaTrader 5 Plattform und die MQL5 Sprache funktionieren.

Ich habe auch gezeigt, wie Sie besondere Umstände für ein effizientes Arbeitssystem schaffen können, selbst wenn MQL5 oder MetaTrader 5 den Indikator, den Sie verwenden möchten, nicht bereitstellen. Dies wurde in Beispiel 3 getan, wo ich gezeigt habe, wie man den HILO-Indikator intern erstellt. Unabhängig davon sollte das System aber immer korrekt implementiert und getestet werden. Denn es hat keinen Sinn, ein wunderbares System zu entwickeln, das Ihnen am Ende keinen Gewinn bringt.

Zum Abschluss dieser Artikelserie möchte ich betonen, dass ich nicht alle möglichen Optionen behandelt habe. Die Idee war, sich nicht mit allen Details bis hin zur Erstellung einer Modellierungsbibliothek für die Erstellung automatisierter EAs zu befassen. Ich werde auf dieses Thema in einer neuen Artikelserie zurückkommen, in der wir über die Entwicklung eines nützlichen Instruments für Marktneulinge sprechen werden.

Denken Sie daran, dass das echte Testen eines automatisierten EAs nicht im MetaTrader 5 Strategy Tester stattfindet, sondern auf einem DEMO-Konto mit vollwertiger Marktarbeit. Dort wird der EA ohne Einstellungen getestet, die seine tatsächliche Leistung verbergen können. Damit ist diese Serie beendet, bis zum nächsten Mal. Alle in dieser Serie besprochenen Codes sind beigefügt, sodass Sie sie studieren und analysieren können, um wirklich zu verstehen, wie ein automatisierter EA funktioniert.


WICHTIGER HINWEIS: Verwenden Sie den im Anhang verfügbaren EA nicht ohne entsprechende Kenntnisse. Solche EAs werden nur zu Demonstrations- und Lehrzwecken zur Verfügung gestellt.

Wenn Sie sie auf einem LIVE-Konto verwenden, tun Sie dies auf eigenes Risiko, da sie zu erheblichen Verlusten auf Ihrem Konto führen können.


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

Beigefügte Dateien |
MQL5 Wizard-Techniken, die Sie kennen sollten (Teil 06): Fourier-Transformation MQL5 Wizard-Techniken, die Sie kennen sollten (Teil 06): Fourier-Transformation
Die von Joseph Fourier eingeführte Fourier-Transformation ist ein Mittel zur Zerlegung komplexer Wellen aus Datenpunkten in einfache Teilwellen. Diese Funktion könnte für Händler sehr nützlich sein, und dieser Artikel wirft einen Blick darauf.
Erstellen eines EA, der automatisch funktioniert (Teil 14): Automatisierung (VI) Erstellen eines EA, der automatisch funktioniert (Teil 14): Automatisierung (VI)
In diesem Artikel werden wir das gesamte Wissen aus dieser Serie in die Praxis umsetzen. Wir werden endlich ein 100%ig automatisiertes und funktionierendes System aufbauen. Aber vorher müssen wir noch ein letztes Detail klären.
Matrizen und Vektoren in MQL5: Die Aktivierungsfunktionen Matrizen und Vektoren in MQL5: Die Aktivierungsfunktionen
Hier wird nur einer der Aspekte des maschinellen Lernens beschrieben — die Aktivierungsfunktionen. In künstlichen neuronalen Netzen berechnet eine Neuronenaktivierungsfunktion einen Ausgangssignalwert auf der Grundlage der Werte eines Eingangssignals oder eines Satzes von Eingangssignalen. Wir werden uns mit den inneren Abläufen des Prozesses befassen.
Mehrschichtiges Perzeptron und Backpropagation-Algorithmus (Teil 3): Integration mit dem Strategy Tester - Überblick (I). Mehrschichtiges Perzeptron und Backpropagation-Algorithmus (Teil 3): Integration mit dem Strategy Tester - Überblick (I).
Das mehrschichtige Perzeptron ist eine Weiterentwicklung des einfachen Perzeptrons, das nichtlineare separierbare Probleme lösen kann. Zusammen mit dem Backpropagation-Algorithmus kann dieses neuronale Netz effektiv trainiert werden. In Teil 3 der Serie Multilayer Perceptron und Backpropagation werden wir sehen, wie man diese Technik in den Strategy Tester integriert. Diese Integration ermöglicht die Nutzung komplexer Datenanalysen, um bessere Entscheidungen zur Optimierung Ihrer Handelsstrategien zu treffen. In diesem Artikel werden wir die Vorteile und Probleme dieser Technik erörtern.