Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 10): Zugriff auf nutzerdefinierte Indikatoren

Daniel Jose | 30 Juni, 2022

Einführung

Ein Handels-EA kann nur dann wirklich nützlich sein, wenn er nutzerdefinierte Indikatoren verwenden kann. Andernfalls handelt es sich nur um eine Reihe von Codes und Anweisungen, die gut gestaltet sein können und bei der Verwaltung von Positionen oder der Ausführung von Marktgeschäften helfen - und das ist wahrscheinlich alles.

Nun, das Hinzufügen von Indikatoren zu einem MetaTrader 5-Chart ist nicht der schwierigste Teil. Aber der Zugriff auf die von diesen Indikatoren berechneten Daten direkt im Expert Advisor wird ohne entsprechende Planung zu einer fast unmöglichen Aufgabe. Und wenn wir nicht wissen, wie das geht, können wir nur auf die Standradindikatoren zurückgreifen. Für den Handel brauchen wir jedoch mehr. Ein gutes Beispiel ist der VWAP-Indikator (Volume Weighted Average Price). Er ist ein sehr wichtiger gleitender Durchschnitt für alle, die mit Futures an der brasilianischen Börse handeln. Dieser MA ist nicht in den Standardindikatoren von MetaTrader enthalten, aber wir können einen nutzerdefinierten Indikator erstellen, der den VWAP berechnet und auf dem Bildschirm anzeigt. Die Dinge werden jedoch viel komplizierter, wenn wir beschließen, denselben Indikator in einem System zu verwenden, das im EA analysiert werden soll. Ohne die entsprechenden Kenntnisse können wir diesen nutzerdefinierten Indikator nicht in einem EA verwenden. In diesem Artikel werden wir sehen, wie man diese Einschränkung umgehen und dieses Problem lösen kann.


Planung

Lassen Sie uns zunächst versuchen, die Berechnungen zu erstellen, die in unserem nutzerdefinierten Indikator verwendet werden sollen. Glücklicherweise ist die VWAP-Berechnungsformel, die wir als Beispiel verwenden werden, recht einfach.


In eine Programmiersprache übersetzt, ergibt sich für MQL5 Folgendes:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
        double          Price = 0;
        ulong           Volume = 0;
        static int      siPos = 0;

        if (macroGetDate(time[rates_total - 1]) != macroGetDate(time[siPos]))
        {
                for (int c0 = rates_total - 1; macroGetDate(time[siPos]) != macroGetDate(time[c0]); siPos++);
                ArrayInitialize(VWAP_Buff, EMPTY_VALUE);
        }
        for (int c0 = siPos; c0 < rates_total; c0++)
        {
                Price += ((high[c0] + low[c0] + close[c0]) / 3) * volume[c0];
                Volume += volume[c0];
                VWAP_Buff[c0] = Price / Volume;
        }

    return rates_total;
}

Die Zeile mit den Berechnungen ist hervorgehoben, während der Rest der Funktion für eine korrekte Initialisierung des DAILY VWAP verwendet wird. Unser Indikator kann jedoch immer noch nicht auf dem Chart ausgeführt werden, und wir müssen dem Code noch ein paar Dinge hinzufügen. Der Rest des Codes ist unten zu sehen:

#property copyright "Daniel Jose - Indicador VWAP ( IntraDay )"
#property version "1.01"
#property indicator_chart_window
#property indicator_buffers     1
#property indicator_plots       1
#property indicator_width1      2
#property indicator_type1 	DRAW_LINE
#property indicator_color1 	clrBlack
//+------------------------------------------------------------------+
#define macroGetDate(A) (A - (A % 86400))
//+------------------------------------------------------------------+
double VWAP_Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA);   
        
        return INIT_SUCCEEDED;
}

Dies ermöglicht die Darstellung von VWAP auf einem Chart, wie zuvor gezeigt:


Nun, dieser Teil war nicht allzu kompliziert. Jetzt müssen wir einen Weg finden, damit der EA den VWAP sieht, sodass er den Indikator auf eine bestimmte Weise analysiert. Auf diese Weise kann der Indikator für den Handel genutzt werden.

Um die Arbeit mit dem Indikator zu erleichtern, sollten wir den VWAP speichern, damit er leicht zugänglich ist.


Danach können wir zu einer neuen Art der Projektion übergehen. Obwohl der VWAP-Indikator im Wesentlichen korrekt ist, ist er für die Verwendung in einem EA falsch programmiert. Warum? Das Problem ist, dass der EA nicht wissen kann, ob der Indikator auf dem Chart ist oder nicht. Ohne dies zu wissen, kann er den Indikator nicht lesen.

Ein weiters Problem ist, dass der Dateiname für das System keine Rolle spielt. Sie können einen beliebigen Namen in die Datei schreiben, aber der Name des Indikators sollte widerspiegeln, was er berechnet. Und unser Indikator hat noch keinen Namen, der dies widerspiegelt. Selbst wenn es VWAP hieße, würde es für das System nichts bedeuten. Aus diesem Grund ist der EA nicht in der Lage zu erkennen, ob der Indikator auf dem Chart läuft oder nicht.

Damit der Indikator widerspiegelt, was er berechnet, müssen wir dies im Code angeben. Auf diese Weise wird ein eindeutiger Name erstellt, der nicht unbedingt mit dem Dateinamen verknüpft ist. In unserem Fall sollte der Initialisierungscode des Indikators wie folgt aussehen. In unserem Fall sollte der Initialisierungscode des Indikators wie folgt aussehen:

int OnInit()
{
        SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA);   
        IndicatorSetString(INDICATOR_SHORTNAME, "VWAP");
        
        return INIT_SUCCEEDED;
}

Durch Hinzufügen der hervorgehobenen Zeile haben wir das Problem bereits gelöst. In bestimmten Fällen kann es schwieriger sein - wir werden später darauf zurückkommen. Lassen Sie uns zunächst den Code des Indikators eines NUTZERDEFINIERTEN GLEITENDEN DRURSCHNITTS aus der MetaTrader 5 Bibliothek als Beispiel verwenden. Ihr Code ist wie folgt:

void OnInit()
{
	SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
	IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
	PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod);
	PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift);

	string short_name;
	switch(InpMAMethod)
	{
      		case MODE_EMA :
			short_name="EMA";
         		break;
      		case MODE_LWMA :
	         	short_name="LWMA";
	         	break;
      		case MODE_SMA :
         		short_name="SMA";
         		break;
      		case MODE_SMMA :
         		short_name="SMMA";
         		break;
      		default :
         		short_name="unknown ma";
     	}
   	IndicatorSetString(INDICATOR_SHORTNAME, short_name + "(" + string(InpMAPeriod) + ")");
   	PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
}

Der hervorgehobene Teil ist der Name, den wir brauchen. Beachten Sie, dass dies nichts mit dem Dateinamen zu tun hat. Dies muss jedoch genau innerhalb des nutzerdefinierten Indikators erfolgen.

Nachdem wir dies getan und sichergestellt haben, dass der EA in der Lage ist zu prüfen, ob der nutzerdefinierte Indikator auf dem Chart läuft oder nicht, können wir zum nächsten Schritt übergehen.


Zugriff auf den Indikator über den EA

Wir können so weitermachen, wie wir es früher getan haben. Aber um wirklich zu verstehen, was vor sich geht, sollten Sie idealerweise einen völlig neuen Code erstellen. Da wir lernen wollen, einen Expert Advisor für den Handel von Grund auf zu entwickeln, sollten wir diese Phase durchlaufen. Daher werden wir im weiteren Verlauf unserer Reise einen isolierten Expert Advisor erstellen. Dann können wir sie in den endgültigen Code aufnehmen oder nicht. Lassen Sie uns nun mit dem Schreiben des Codes fortfahren. Der EA beginnt mit dem sauberen Code, wie Sie unten sehen können:

//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick(){}
//+------------------------------------------------------------------+
void OnTimer(){}
//+------------------------------------------------------------------+

Wir gehen wie folgt vor: Zunächst gehen wir davon aus, dass der VWAP-Indikator auf dem Chart ist und laden den letzten vom Indikator berechneten Wert in den Expert Advisor. Wir werden dies jede Sekunde wiederholen. Aber wie soll das gehen? Das ist ganz einfach. Sehen Sie sich an, wie der EA-Code nach der Änderung aussieht:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
double  Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        handle = ChartIndicatorGet(ChartID(), 0, "VWAP");
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle != INVALID_HANDLE)
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Die hervorgehobenen Teile sind diejenigen, die wir dem sauberen Code hinzugefügt haben. Das Ergebnis sieht wie folgt aus:


Warum hat es funktioniert? Das liegt daran, dass MQL5 Mittel zum Lesen und Schreiben von Daten zwischen Systemen bereitstellt. Eine der Möglichkeiten zum Lesen ist die Verwendung der Funktion CopyBuffer. Es funktioniert wie folgt:


Wir können also Daten von jedem beliebigen nutzerdefinierten Indikator lesen, d.h. wir sind nicht auf die Standard-MetaTrader 5-Indikatoren beschränkt. Das bedeutet, dass wir jeden beliebigen Indikator erstellen können und er wird funktionieren.

Betrachten wir nun ein anderes Szenario. Diesmal ist der VWAP nicht auf dem Chart zu sehen. Aber der EA braucht sie, und deshalb müssen wir sie in den Chart laden. Wie kann man das tun? Es ist auch ziemlich einfach. Außerdem haben wir es schon einmal für andere Zwecke verwendet - beim Erstellen eines Unterfensters für den Expert Advisor. Wir werden nun die Funktion iCustom verwenden. Aber dieses Mal werden wir einen nutzerdefinierten Indikator laden. Dann sieht der EA-Code wie folgt aus:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
double  Buff[];
//+------------------------------------------------------------------+
int OnInit()
{
        handle = ChartIndicatorGet(ChartID(), 0, "VWAP");
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle == INVALID_HANDLE) handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5");else
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Der hervorgehobene Code ist der einzige Zusatz, den wir zum ursprünglichen System hinzugefügt haben. Die Ausführung des EA führt nun zu folgendem Ergebnis:


Die folgende Abbildung zeigt, was wir umgesetzt haben:

Das ist alles, was wir auf der grundlegendsten Ebene brauchen. Wenn wir jedoch genau hinsehen, stellen wir fest, dass der VWAP nicht auf dem Chart zu sehen ist. Selbst wenn der EA sie verwendet, weiß der Nutzer nicht, was vor sich geht. Auch dies lässt sich leicht beheben, und der endgültige Code sieht wie der unten stehende aus. Denken wir daran: Es ist immer gut, zu analysieren und zu beobachten, was der EA tut, denn es ist nicht sehr sicher, ihm völlige Freiheit zu geben, daher würde ich nicht empfehlen, dies zu tun:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
int     handle;
long    id;
double  Buff[];
string  szCmd;
//+------------------------------------------------------------------+
int OnInit()
{
        szCmd = "VWAP";
        handle = ChartIndicatorGet(id = ChartID(), 0, szCmd);
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        ChartIndicatorDelete(id, 0, szCmd);
        IndicatorRelease(handle);
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i;
        
        if (handle == INVALID_HANDLE)
        {
                if ((handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5")) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle);
        }else
        {
                i = CopyBuffer(handle, 0, 0, 1, Buff);
                Print(Buff[0]);
        }
}
//+------------------------------------------------------------------+

Der obige EA-Code liest den letzten von VWAP berechneten Wert und zeigt ihn auf dem Bildschirm an. Befindet sich der Indikator nicht auf dem Chart, wird er geladen und angezeigt. Wenn wir den EA aus dem Chart entfernen, wird auch der VWAP vom Bildschirm verschwinden. Auf diese Weise verfügt der EA immer über die Dinge, die er zur Durchführung von Berechnungen benötigt. Die Ergebnisse meiner Ausführungen sind im Folgenden dargestellt:


Man könnte meinen, dass dies nicht sehr praktikabel ist, da wir offensichtlich keine Änderungen an dem Indikator vorgenommen haben. Aber auch mit den oben genannten Schritten können wir alles implementieren, was mit nutzerdefinierten Indikatoren zu tun hat. Zur abschließenden Erläuterung betrachten wir ein weiteres Beispiel. Wenden wir einen gleitenden Durchschnitt an und verwenden den Expert Advisor auf die gleiche Weise wie beim VWAP, nur dass wir jetzt die Parameter für den Durchschnitt festlegen.


Zweiter Fall: Verwendung gleitender Durchschnitte

Die Berechnung des gleitenden Durchschnitts ist hier nicht wichtig, da wir uns darauf konzentrieren werden, wie man Parameter an einen benutzerdefinierten Indikator übergibt. Hier ist der neue nutzerdefinierte Indikator:

#property copyright "Daniel Jose 16.05.2021"
#property description "Basic Moving Averages (Optimizes Calculation)"
#property indicator_chart_window
//+------------------------------------------------------------------+
enum eTypeMedia
{
        MME,    //Exponential moving average
        MMA     //Arithmetic moving average
};
//+------------------------------------------------------------------+
#property indicator_buffers             1
#property indicator_plots               1
#property indicator_type1               DRAW_LINE
#property indicator_width1              2
#property indicator_applied_price       PRICE_CLOSE
//+------------------------------------------------------------------+
input color      user00 = clrRoyalBlue; //Cor
input int        user01 = 9;            //Periods
input eTypeMedia user02 = MME;          //MA type
input int user03 = 0;            //Displacement
//+------------------------------------------------------------------+
double Buff[], f_Expo;
//+------------------------------------------------------------------+
int OnInit()
{
        string sz0 = "MM" + (user02 == MME ? "E": (user02 == MMA ? "A" : "_")) + (string)user01;
        
        f_Expo = (double) (2.0 / (1.0 + user01));
        ArrayInitialize(Buff, EMPTY_VALUE);
        SetIndexBuffer(0, Buff, INDICATOR_DATA);
        PlotIndexSetInteger(0, PLOT_LINE_COLOR, user00);
        PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, user01);
        PlotIndexSetInteger(0, PLOT_SHIFT, user03);
        IndicatorSetString(INDICATOR_SHORTNAME, sz0);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        double Value;
        int c0;

        switch (user02)
        {
                case MME:
                        if (user01 < rates_total)
                        {       
                                for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : 0); c0 < rates_total - user03; c0++)
                                        Buff[c0] = (c0 > 0? ((price[c0] - Buff[c0 - 1]) * f_Expo) + Buff[c0 - 1] : price[c0] * f_Expo);
                                for (; c0 < rates_total; c0++)
                                        Buff[c0] = EMPTY_VALUE;
                        }
                        break;
                case MMA:
                        if (user01 < rates_total)
                        {
                                if (prev_calculated == 0) 
                                {       
                                        Value = 0;
                                        for (int c1 = 0; c1 < user01; c1++) Value += price[user01 - c1];
                                        Buff[user01] = Value / user01;
                                }
                                for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : user01 + 1); c0 < rates_total - user03; c0++)
                                        Buff[c0] = ((Buff[c0 - 1] * user01) - price[c0 - user01] + price[c0]) / user01;
                                for (; c0 < rates_total; c0++)
                                        Buff[c0] = EMPTY_VALUE;
                        }
                        break;
        }
        
        return rates_total;
}
//+------------------------------------------------------------------+

Der Name des Indikators hängt nun von mehreren Faktoren ab. Später können wir den EA-Check durchführen und an die jeweilige Situation anpassen. Nehmen wir zum Beispiel an, unser EA verwendet zwei gleitende Durchschnitte und zeigt sie auf dem Chart an. Achten Sie auf die hervorgehobenen Teile im obigen Code - die Aktivierung des EA und die Funktion iCustom in diesem Fall, um Indikatorparameter zu ändern und zu konfigurieren. Es ist wichtig, dies zu verstehen, um es im Bedarfsfall umsetzen zu können. Einer der Durchschnitte ist also ein exponentieller MA mit einer Periodenlänge von 17 und der andere ein arithmetischer MA mit einer von 52 Perioden. Der 17-Perioden-MA wird grün und der 52-Perioden-MA wird rot sein. Der EA wird den Indikator als Funktion in der folgenden Form sehen:

Durchschnitt (Farbe, Zeitraum, Typ, Verschiebung) Der Indikator ist nun also keine separate Datei, sondern eine EA-Funktion. Dies ist in der Programmierung sehr üblich, da wir ein Programm mit den entsprechenden Parametern aufrufen, um eine bestimmte Aufgabe auszuführen und am Ende das Ergebnis einfacher zu erhalten. Aber die Frage ist: Wie können wir unseren EA dazu bringen, dieses Szenario auf die gleiche Weise zu erstellen und zu verwalten, wie wir es mit VWAP getan haben?

Hierfür müssen wir den EA-Code ändern. Der vollständige Code des neuen EA ist unten dargestellt:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
long    id;
int     handle1, handle2;
double  Buff1[], Buff2[];
string  szCmd1, szCmd2;
//+------------------------------------------------------------------+
int OnInit()
{
        szCmd1 = "MME17";
        szCmd2 = "MMA52";
        id = ChartID();
        handle1 = ChartIndicatorGet(id, 0, szCmd1);
        handle2 = ChartIndicatorGet(id, 0, szCmd2);
        SetIndexBuffer(0, Buff1, INDICATOR_DATA);
        SetIndexBuffer(0, Buff2, INDICATOR_DATA);
        
        EventSetTimer(1);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        ChartIndicatorDelete(id, 0, szCmd1);
        ChartIndicatorDelete(id, 0, szCmd2);
        IndicatorRelease(handle1);
        IndicatorRelease(handle2);
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        int i1, i2;
        
        if (handle1 == INVALID_HANDLE)
        {
                if ((handle1 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrGreen, 17, 0)) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle1);
        };
        if (handle2 == INVALID_HANDLE)
        {
                if ((handle2 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrRed, 52, 1)) != INVALID_HANDLE)
                        ChartIndicatorAdd(id, 0, handle2);
        };
        if ((handle1 != INVALID_HANDLE) && (handle2 != INVALID_HANDLE))
        {
                i1 = CopyBuffer(handle1, 0, 0, 1, Buff1);
                i2 = CopyBuffer(handle2, 0, 0, 1, Buff2);
                Print(Buff1[0], "<< --- >>", Buff2[0]);
        }
}
//+------------------------------------------------------------------+

Hier ist das Ergebnis:


Achten Sie auf die hervorgehobenen Teile des EA-Codes. Das ist genau das, was wir brauchen: Wir übergeben dem Indikator Parameter, indem wir den gleichen Mechanismus wie beim VWAP verwenden. Im Falle des VWAP mussten jedoch keine Parameter übergeben werden, im Gegensatz zu den gleitenden Durchschnitten, bei denen Parameter zu übergeben sind. All dies bietet eine sehr große Gestaltungsmöglichkeit.


Schlussfolgerung

Dieser Artikel enthält keinen Universalcode. Wir haben uns zwei verschiedene Expert Advisors und zwei verschiedene nutzerdefinierte Indikatoren angeschaut, um zu verstehen, wie man diese Art von System in einem komplexeren und durchdachten Expert Advisor verwenden kann. Ich glaube, dass wir mit diesem Wissen unsere eigenen nutzerdefinierten Indikatoren verwenden können. Auch unser EA kann eine sehr interessante Analyse liefern. All dies beweist, dass der MetaTrader 5 die vielseitigste Plattform ist, die sich ein Händler wünschen kann. Wenn jemand das nicht verstanden hat, dann hat er es einfach nicht bis zum Ende studiert.

Nutzen Sie das in diesem Artikel vermittelte Wissen, denn mit MetaTrader 5 können Sie viel weiter gehen, als es vielen bisher möglich war.

Wir sehen uns im nächsten Artikel.


Links

Wie man Indikatoren in MQL5 aufruft

Nutzerdefinierte Indikatoren in MQL5 für Anfänger

MQL5 für Dummies: Leitfaden zur Verwendung von technischen Indikatorwerten in Expert Advisors