Mehrere Indikatoren in einem Chart (Teil 04): Weiterentwicklung zum Expert Advisor

Daniel Jose | 13 Mai, 2022

Einführung

In meinen früheren Artikeln habe ich erklärt, wie man einen Indikator mit mehreren Unterfenstern erstellt, was bei der Verwendung von nutzerdefinierten Indikatoren interessant wird. Das war ziemlich einfach zu bewerkstelligen. Wenn wir jedoch versuchen, die gleiche Funktionsweise in einem Expert Advisor zu implementieren, wird es etwas komplizierter, da wir nicht über die Werkzeuge verfügen, die wir in einem nutzerdefinierten Indikator verwendet haben. An diesem Punkt wird das Programmieren unabdingbar: Die Fähigkeit, den richtigen Code zur Erstellung eines Unterfensters zu schreiben, ist von größter Bedeutung. Auch wenn diese Aufgabe nicht ganz einfach ist, erfordert das Wissen, wie man ein Unterfenster in einen EA einfügt, nicht viel Programmierarbeit, sondern nur einige Kenntnisse über die Arbeitsweise von MQL5.


Planung

Unser nutzerdefinierter Indikator funktioniert bereits, d. h. unsere Objektklasse ist bereits funktionsfähig, und da es sich um eine Objektklasse handelt, können wir sie problemlos auf andere Modelle übertragen. Wenn wir die Klasse jedoch einfach deklarieren und versuchen, sie in unserem EA zu verwenden, werden die Dinge nicht so funktionieren wie in unserem nutzerdefinierten Indikator, und der Grund dafür ist, dass wir in unserem EA keine Fähigkeit zu Unterfenstern haben. Aber dann kam die Idee: "Was wäre, wenn wir einen bereits kompilierten und funktionierenden nutzerdefinierten Indikator verwenden und ihn vom EA aus mit dem Befehl iCustom aufrufen? Nun, das könnte tatsächlich funktionieren, da das Unterfenster nicht benötigt wird und der Befehl wie folgt aussehen würde:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
input string user01 = "";                //Used indicators
input string user02 = "";                //Assets to follow
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//... Expert Advisor code ...

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), 0, m_handleSub)) return INIT_FAILED;
//... Expert Advisor code ...

        ChartRedraw();
        
        return(INIT_SUCCEEDED);
}
//...The rest of the Expert Advisor code ...

Dieses einfache Codeschnipsel ist in der Lage, unseren nutzerdefinierten Indikator zu laden, obwohl er nicht richtig funktioniert, weil wir kein Unterfenster haben. In diesem Fall, wenn der Code im EA ausgeführt wird, läuft unser Indikator durch den EA direkt im Hauptfenster, was bedeutet, dass unser Chart durch die vom Indikator geladenen Vorlagen verdeckt wird, was definitiv nicht das ist, was wir wollen.

Unser reales Hauptproblem besteht also darin, ein Unterfenster zu erstellen, in dem wir unseren bereits funktionierenden Indikator verwenden können. Aber warum ein Unterfenster für den späteren Start unseres Indikators erstellen? Das macht keinen Sinn, es ist besser, die Funktion direkt in unseren EA einzubauen und so eventuelle Einschränkungen zu überwinden.

Auf dieser Grundlage müssen wir mehrere Aufgaben erfüllen:

Aufgabe Zweck
1 => Erstellen eines Allzweckindikators. Er ermöglicht das Erstellen und Verwenden des iCustom-Befehls, ohne den Chart zu verderben.
2 => Binden Sie diesen Indikator auf irgendeine Weise in den EA ein.  So können Sie den Expert Advisor mit voller Funktionalität problemlos übertragen.
3 => Generieren Sie eine allgemeine Objektklasse für das Unterfenster.  Ermöglicht das Hinzufügen von Unterfenstern über den EA.
4 => Holen Sie sich unsere Klasse C_TemplateChart, die an die Fensterklasse gebunden ist. So können wir den Inhalt von Unterfenstern verwalten, ohne etwas am bereits laufenden Code zu ändern.

Obwohl dies schwierig erscheinen mag, sind die Schwierigkeiten recht einfach zu lösen. Gehen wir also auf jeden der Punkte ein.


Die Implementierung: Erstellen eines Allzweckindikators

Dieser Teil kann durch die Erstellung eines völlig sauberen, aber funktionalen, nutzerdefinierten Indikatorcodes gelöst werden. Der Code sieht in diesem Fall wie folgt aus:

#property copyright "Daniel Jose"
#property version   "1.00"
#property description "This file only enables support of indicators in SubWin."
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+


Nur dies und nichts weiter. Wir speichern diese Datei unter dem Namen SubSupport.mq5. Aber sie wird nicht zusammen mit anderen Indikatoren gespeichert, sondern im Verzeichnis RESOURCE unseres Expert Advisors. Die Dateistruktur sieht dann wie in der folgenden Abbildung aus:


Es gibt einen guten Grund dafür, aber lassen wir ihn vorerst beiseite. Gehen wir nun zur nächsten Aufgabe über.


Implementierung: Einbinden des allgemeinen Indikators in den EA

Dazu müssen wir den folgenden Code oben in unseren EA einfügen.

//+------------------------------------------------------------------+
#define def_Resource "Resources\\SubSupport.ex5"
//+------------------------------------------------------------------+
#resource def_Resource
//+------------------------------------------------------------------+

Dadurch wird der kompilierte Code des allgemeinen Indikators in unseren EA aufgenommen. Sobald dies geschehen ist, wird die .ex5-Datei des allgemeinen Indikators gelöscht, da sie nicht mehr benötigt wird. Nun sollten Sie darauf achten, dass, wenn die SubSupport.ex5 Datei zum Zeitpunkt der Kompilierung des EA-Codes nicht gefunden wird, der Compiler automatisch den Code des SubSupport.mq5 allgemeinen Indikators kompilieren und diese neu kompilierte ausführbare Datei zu unserem Expert Advisor hinzufügen. Wenn Sie also die Datei SubSupport.mq5 bearbeiten und die Änderungen in den Expert Advisor einfügen möchten, sollten Sie SubSupport.ex5 löschen; andernfalls werden die Änderungen nicht hinzugefügt.

Dieses Detail ist wichtig: Manchmal muss man wirklich wissen, wie man neu implementierte Änderungen zur Ressource hinzufügt.

Nun, der allgemeine Indikator ist jetzt Teil des Expert Advisors, also gehen wir zur nächsten Aufgabe über.


Implementierung : Erstellen einer Unterfenster-Objektklasse

Auch dieser Teil ist einfach. Hier müssen wir vor der Codierung einige Punkte definieren, nämlich: welche Funktionen brauchen wir wirklich in dieser Klasse? Ursprünglich habe ich mich für Folgendes entschieden:

Funktion Beschreibung
Init Ermöglicht das Hinzufügen von Unterfenstern über den EA.
Close Ermöglicht das Hinzufügen von Unterfenstern über den EA.

Diese Funktionen werden nicht getestet, daher gehe ich davon aus, dass sie während der Lebensdauer des EA nur einmal aufgerufen werden. Aber da unser EA umfangreicher wird, ist es gut, darüber nachzudenken, wie wir ihn in Zukunft noch praktischer machen können. Deshalb erstellen wir eine neue Objektklasse mit dem Namen C_Terminal - diese Klasse wird ein paar Dinge im Zusammenhang mit dem grafischen Terminal unterstützen. Wir werden später mehr darüber erfahren. Sehen wir uns die letzte Aufgabe an, da es keine Möglichkeit gibt, die Lösung teilweise zu implementieren.


Implementierung: C_TemplateChart Klassenvererbung

Als ich mich entschied, etwas Neues mit Hilfe von OOP (Objektorientierte Programmierung) zu erstellen, tat ich dies, weil ich bereits wusste, dass dieser Ansatz große Vorteile bietet, einschließlich Sicherheit und Vererbung. Es gibt auch Polymorphismus, aber wir werden ihn später bei der Erstellung eines auftragsübergreifenden Systems verwenden. In diesem speziellen Fall werden wir einen der Vorteile von OOP nutzen - die Vererbung. C_TemplateChart ist bereits eine voll funktionsfähige Klasse. Da möchte man sich nicht die Mühe machen, alles noch einmal neu zu programmieren, oder das Risiko eingehen, der Klasse Code hinzuzufügen, der verhindert, dass die Klasse an anderen Stellen verwendet werden kann. Die Lösung ist die Vererbung, die das Hinzufügen von neuem Code oder neuen Funktionen ermöglicht, ohne den ursprünglichen Code zu verändern.

Die Verwendung von Vererbung hat eine Reihe von Vorteilen, darunter die folgenden: bereits getesteter Code bleibt getestet; die Komplexität wächst, ohne dass die Größe des Codes im gleichen Maße zunimmt; nur neue Funktionen müssen wirklich getestet werden; was sich nicht ändert, wird einfach vererbt, was für Stabilität sorgt. Mit anderen Worten: Die Dinge verbessern sich mit minimalem Aufwand, aber mit maximaler Sicherheit. Um dies zu verstehen, sehen wir uns das folgende Diagramm an.


Die Großelternklasse ist die einfachste Klasse mit dem niedrigsten Level an Datenmanipulation, aber wenn die Elternklasse etwas von der Großelternklasse erbt, können alle Dinge, die in der Großelternklasse als öffentlich deklariert sind, von der Elternklasse gesehen und verwendet werden. Und wir können der Elternklasse auch neue Dinge hinzufügen, ohne dass dies Auswirkungen auf das hat, was geerbt und durch Vererbung unterstützt wird. Wenn die übergeordnete Klasse bereits fertig ist und funktioniert und wir sie erweitern wollen, ohne etwas an den darunter liegenden Klassen zu ändern, dann erstellen wir eine untergeordnete Klasse, die alle Funktionen der vorherigen Klassen hat. Wir können auch die Art und Weise ändern, wie die Dinge funktionieren, und das ist das Interessante an der Vererbung, denn diese Änderungen werden sich nicht auf andere Klassen auswirken. Allerdings gibt es hier eine Einschränkung, im Gegensatz zu C++, das Mehrfachvererbung erlaubt. Wenn ein Kind Funktionen sowohl von der Vater- als auch von der Mutterseite erben kann, ist dies in MQL5 nicht möglich. Aber man profitiert trotzdem von der Vererbung. Ein Beispiel für Mehrfachvererbung ist unten zu sehen:

Okay, aber wie macht man das in MQL5? Wie kann man eine Vererbung deklarieren, so dass wir den Vorteil daraus ziehen können? Der genaueste Weg, dies zu verstehen, ist, den Inhalt der objektorientierten Programmierung (OOP) zu lesen, aber hier kommen wir direkt auf den Punkt. Die Vererbung wird mit den folgenden Zeilen durchgeführt:

#include "C_TemplateChart.mqh"
//+------------------------------------------------------------------+
class C_SubWindow : public C_TemplateChart
{
// ... Class code
};

Sie sehen, dass die Klasse C_SubWindow die Klasse C_TemplateChart öffentlich erbt, so dass wir jetzt die Klasse C_SubWindow verwenden können, um auf die Funktionsweise der Klasse C_TemplateChart zuzugreifen.

In dem obigen Codeschnipsel habe ich eine Sache hervorgehoben. Beachten Sie, dass es in Anführungszeichen ( " ) und nicht wie üblich in spitzen Klammern ( < > ) steht. Warum habe ich das getan? Wie die Sprache C++ hat auch MQL5 einige sehr interessante Dinge, aber einige Dinge verwirren diejenigen, die gerade erst anfangen, die Kunst des Programmierens zu lernen. Wenn wir eine Header-Datei zwischen spitze Klammern ( < > ) setzen, meinen wir einen absoluten Pfad - in diesem Fall folgt der Compiler genau dem von uns angegebenen Pfad. Wenn wir jedoch Anführungszeichen verwenden (wie in diesem Fall), verwendet der Compiler einen relativen Pfad, oder, um es deutlicher zu machen, er beginnt mit dem aktuellen Verzeichnis, in dem sich die Arbeitsdatei befindet. Es mag seltsam erscheinen, aber es kommt vor, dass wir denselben Namen für Dateien mit unterschiedlichem Inhalt haben, die sich in verschiedenen Verzeichnissen befinden, aber wir wollen uns trotzdem auf das aktuelle Verzeichnis beziehen, also verwenden wir Anführungszeichen dafür.

Die beiden Funktionen, die wir früher verwenden wollen, INIT und CLOSE, sind unten dargestellt:

//+------------------------------------------------------------------+
bool Init(void)
{
        if (m_handleSub != INVALID_HANDLE) return true;
        if ((m_handleSub = iCustom(NULL, 0, "::" + def_Resource)) == INVALID_HANDLE) return false;
        m_IdSub = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL);
        if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) return false;
                
        return true;
}
//+------------------------------------------------------------------+
void Close(void)
{
        ClearTemplateChart();
        if (m_handleSub == INVALID_HANDLE) return;
        IndicatorRelease(m_IdSub);
        ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, 0));
        ChartRedraw();
        m_handleSub = INVALID_HANDLE;
}
//+------------------------------------------------------------------+

Sehen Sie, der Code ist sehr einfach und kurz. Aber es gibt etwas, auf das wir aufpassen müssen, achten Sie auf den hervorgehobenen Teil. Sie müssen aufpassen, dass Sie beim Hinzufügen dieses Teils keinen Fehler machen, denn wenn Sie ihn nicht so lassen, wie er ist, wird die ausführbare Datei SubSupport.ex5, um deren Hinzufügen zum EA wir gebeten haben, nicht innerhalb des EA sichtbar sein - stattdessen wird sie außerhalb des EA sichtbar sein. Für weitere Details lesen Sie über Ressourcen. Aber im Grunde genommen bedeutet die Verwendung von ( :: ), dass der EA die interne Ressource verwenden soll, die in ihm verfügbar ist. Wenn wir jedoch nur den Namen der Ressource angeben, sucht der EA im MQL5-Verzeichnis danach, und wenn die Datei am angegebenen Ort nicht existiert, schlägt die Funktion fehl, selbst wenn die Datei als EA-Ressource hinzugefügt wurde.

Sobald die Ressource geladen ist, wird die Anzahl der vorhandenen Unterfenster überprüft und ein Indikator zu diesem Unterfenster hinzugefügt.

Was der Code tatsächlich tut, ist unten zu sehen:

input string user01 = "";               //Used indicators
input string user02 = "";               //Assets to follows
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//...   

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL), m_handleSub)) return INIT_FAILED;

//...

        ChartRedraw();
        
   return(INIT_SUCCEEDED);
}
//...The rest of the Expert Advisor code ...


Beide Codes funktionieren gleich, aber die Objektklassenversion ermöglicht es uns, im Laufe der Zeit mehr Dinge hinzuzufügen, da die oben gezeigte Version die konsolidierte Version ist und sich nicht ändern wird. Beide Versionen tun das Gleiche: Sie erstellen ein Unterfenster des EA und legen alle zuvor erstellten nutzerdefinierten Indikatoren in diesem Unterfenster ab. Achten Sie auf die Änderungen im Code im Vergleich zum Code am Anfang des Artikels - die Änderungen sind farblich hervorgehoben.


Schlussfolgerung

Es ist sehr interessant, wie wir uns entscheiden, den Weg zur Erreichung unserer Ziele zu gehen. Manchmal stoßen wir auf Schwierigkeiten und denken, dass es schwierig ist, unsere Ziele zu erreichen, aber mit ein wenig Geduld und Hingabe können wir Hindernisse überwinden, die zunächst unüberwindbar schienen. In diesem Artikel zeige ich, wie man die Funktionalität einer Klasse erweitern kann, ohne sie verändern zu müssen - durch Vererbung. Gleichzeitig zeige ich, wie man Indikatoren zu Charts hinzufügen kann, so dass sie wie bereits getestet funktionieren. Wir fügen das ex5-Programm in unseren EA ein und verwenden es, ohne das ursprüngliche ex5 portieren zu müssen, indem wir einfach den EA laden.

Die angehängte Datei enthält alle Verbesserungen, die bisher entwickelt wurden, aber es wird bald noch mehr interessante Dinge in diesem Code geben. 😁👍