English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
Mehrere Indikatoren auf einem Chart (Teil 02): Erste Experiment

Mehrere Indikatoren auf einem Chart (Teil 02): Erste Experiment

MetaTrader 5Beispiele | 4 Mai 2022, 13:03
332 0
Daniel Jose
Daniel Jose

Einführung

Im letzten Artikel "Mehrere Indikatoren in einem Chart" habe ich das Konzept und die Grundlagen der Verwendung mehrerer Indikatoren in einem Chart vorgestellt, ohne den Bildschirm mit zu vielen verschiedenen Details zu füllen. Der einzige Zweck dieses Artikels war es, das System selbst vorzustellen, zu zeigen, wie man Datenbanken erstellt und wie man die Vorteile solcher Datenbanken nutzen kann; den Systemcode habe ich damals nicht bereitgestellt. Hier werden wir damit beginnen, den Code zu implementieren, und in zukünftigen Artikeln werden wir die Funktionsweise des Systems erweitern, um es vielseitiger und vollständiger zu machen, da das System vielversprechend aussieht und große Möglichkeiten für weitere Verbesserungen hat.


Planung

Um die Idee leichter verständlich zu machen, aber vor allem, um das System erweiterbar zu machen, wurde es in zwei separate Dateien aufgeteilt, während der Hauptcode OOP-Prinzipien (Object Oriented Programming) verwendet. All dies gewährleistet, dass sich das System nachhaltig, sicher und stabil entwickeln kann.

In diesem ersten Schritt werden wir den Indikator verwenden, also erstellen wir einen für dieses Fenster:

Warum verwenden wir einen Indikator und nicht irgendeinen anderen Dateityp? Der Grund ist, dass wir mit einem Indikator keine zusätzliche interne Logik implementieren müssen, um das Teilfenster zu erstellen — stattdessen können wir den Indikator anweisen, dies zu tun, was uns Zeit spart und die Systementwicklung beschleunigt. Die Kopfzeile des Indikators wird wie folgt aussehen:

#property indicator_plots 0
#property indicator_separate_window


Mit nur diesen beiden Zeilen können wir ein Unterfenster in einem Symbol-Chart erstellen (für diejenigen, die nicht wissen, wie sie funktionieren, siehe die Tabelle unten):

Code Beschreibung
indicador_plots 0 Diese Zeile teilt dem Compiler mit, dass wir keine Datentypen verfolgen werden; sie verhindert, dass der Compiler Warnmeldungen ausgibt.
indicator_separate_window Diese Zeile weist den Compiler an, die notwendige Logik zur Erstellung eines Unterfensters hinzuzufügen.

Dies sollte einfach sein. Für diejenigen, die mit der Programmierung nicht vertraut sind, mögen einige Dinge im Quellcode seltsam erscheinen, aber sie folgen einfach weit verbreiteten Protokollen, die von der gesamten Programmiergemeinschaft akzeptiert werden. Da MetaTrader 5 die Sprache MQL5 verwendet, die C++ sehr ähnlich ist, mit leichten Unterschieden, können wir die gleiche Art der Programmierung verwenden, die wir in C++ verwenden. Dadurch werden die Dinge viel einfacher. Um diese Tatsache zu nutzen, können wir eine C-Sprachdirektive wie folgt verwenden:

 #include <Auxiliary\C_TemplateChart.mqh>

Diese Direktive weist den Compiler an, eine Header-Datei einzubinden, die an einem bestimmten Ort existiert. Sollte der vollständige Pfad nicht wie Includes \ Auxiliary \ C_TemplateChart.mqh aussehen? Ja, der vollständige Pfad sieht so aus, aber MQL5 weiß bereits, dass sich jede Header-Datei im Verzeichnis "includes" befinden sollte, also können wir den ersten Teil weglassen. Wenn der Pfad in spitze Klammern eingeschlossen ist, handelt es sich um einen absoluten Pfad; wenn er in Anführungszeichen eingeschlossen ist, handelt es sich um einen relativen Pfad, d.h. <Auxiliary \ C_TemplateChart. mqh> unterscheidet sich von "Auxiliary \ C_TemplateChart.mqh".

Wenn wir mit dem Code fortfahren, erhalten wir die folgenden Zeilen:

input string user01 = "" ;       //Used indicators
input string user02 = "" ;       //Assets to follow


Hier können String-Werte eingegeben werden. Wenn Sie genau wissen, welchen Befehl Sie beim Öffnen des Indikators verwenden wollen, können Sie hier einen Standardwert angeben. Zum Beispiel wollen Sie immer den RSI mit der Linienbreite 3 und den MACD mit der Linienbreite 2 verwenden. Geben Sie den Standardwert wie folgt an:

input string user01 = "RSI:3;MACD:2" ;  //Used indicators
input string user02 = "" ;              //Assets to follow


Der Befehl kann später noch geändert werden, aber der Standardwert erleichtert das Öffnen des Indikators, da er bereits für die Verwendung des Befehls vorkonfiguriert ist. In der nächsten Zeile wird ein Alias erstellt, über den wir auf die Objektklasse zugreifen können, die den gesamten "schweren" Code enthält und uns den Zugriff auf ihre öffentlichen Funktionen ermöglicht.

C_TemplateChart SubWin;

Unser Code in der nutzerdefinierten Indikatordatei ist fast fertig und wir müssen nur noch 3 Zeilen hinzufügen, damit alles fertig ist und funktioniert. Natürlich enthält unsere Objektklasse keine Fehler, aber das werden wir in dieser Klasse sehen. Fügen wir also zur Fertigstellung der Indikatordatei die folgenden grün markierten Zeilen hinzu:

 //+------------------------------------------------------------------+
int OnInit ()
{
         SubWin.AddThese(C_TemplateChart::INDICATOR, user01);
         SubWin.AddThese(C_TemplateChart::SYMBOL, user02);

         return INIT_SUCCEEDED ;
}
//+------------------------------------------------------------------+

//...... other lines are of no interest to us ......

//+------------------------------------------------------------------+
void OnChartEvent ( const int id,
                   const long &lparam,
                   const double &dparam,
                   const string &sparam)
{
         if (id == CHARTEVENT_CHART_CHANGE ) SubWin.Resize();
}
//+------------------------------------------------------------------+


Dies ist genau das, was der nutzerdefinierte Indikator haben wird. Sehen wir uns nun die Blackbox in der Datei, die unsere Objektklasse enthält, genauer an. Von nun an sollte sich die Aufmerksamkeit auf zwei Funktionen richten, aber um es einfacher zu machen, beginnen wir mit den Funktionen, die in unserer Objektklasse vorhanden sind, und sehen uns an, wofür jede von ihnen verwendet wird.

Funktionen Beschreibung
SetBase Erzeugt ein Objekt, das zur Anzeige von Indikatordaten benötigt wird
decode Dekodiert den übergebenen Befehl
AddTemplate Passt die Dinge je nach Art der dargestellten Daten entsprechend an
C_Template Standardklassenkonstruktor
~ C_Template Destruktor der Klasse
Resize Ändern der Größe des Teilfensters
AddThese Funktion, die für den Zugriff auf und die Konstruktion von internen Objekten zuständig ist

Das war's. Wie Sie sehen, verwenden wir die Funktionen RESIZE und ADDTHESE in unserem nutzerdefinierten Indikator. Dies sind im Moment die einzigen öffentlichen Funktionen, was bedeutet, dass wir uns wenig Sorgen machen müssen, da alles andere innerhalb unseres Objekts versteckt ist, wodurch sichergestellt wird, dass sie nicht ohne Notwendigkeit geändert werden. Dies sorgt für eine hohe Zuverlässigkeit unseres endgültigen Codes. Lassen Sie uns mit dem Code fortfahren, der mit der folgenden Definition beginnt:

 #define def_MaxTemplates         6

Diese Zeile ist sehr wichtig für unsere Objektklasse — sie definiert die maximale Anzahl von Zeigern, die erstellt werden können. Um mehr oder weniger davon hinzuzufügen, ändern Sie einfach diese Zahl. Mit dieser einfachen Lösung haben wir eine dynamische Speicherzuweisung und begrenzen die Anzahl der Indikatoren. Wahrscheinlich ist dies der einzige Punkt, den Sie vielleicht ändern möchten, aber ich denke, dass 6 eine geeignete Zahl für die meisten Leute und Monitore ist.

Die nächste Zeile ist eine Enumeration, die die Verwaltung der Daten an einigen Stellen des Programms erleichtert:

 enum eTypeChart {INDICATOR, SYMBOL};

Die Tatsache, dass sich diese Zeile innerhalb unserer Klasse befindet, garantiert, dass sie in einer anderen Klasse den gleichen Namen haben kann, aber die darin angegebenen Daten gehören nur zu dieser Objektklasse. Um also korrekt auf dieses Enum zuzugreifen, verwenden wir das Formular, das in der OnInit-Funktion unserer nutzerdefinierten Indikatordatei bereitgestellt wird. Wenn der Klassenname weggelassen wird, gilt dies als Syntaxfehler und der Code wird nicht kompiliert. Die nächste Zeile ist ein reserviertes Wort.

 private :


Es bedeutet, dass alles, was danach kommt, für diese Objektklasse privat ist und außerhalb der Klasse nicht sichtbar sein wird. D.h. der Zugriff auf alles Weitere wird unmöglich sein, wenn man sich nicht innerhalb der Klasse befindet. Dies verbessert die Codesicherheit, da klassenspezifische private Daten von anderen Stellen aus nicht zugänglich sind. In weiteren Zeilen werden einige interne und private Variablen deklariert, bis wir zur ersten realen Funktion unserer Klasse kommen.

 void SetBase( const string szSymbol, int scale)
{
#define macro_SetInteger(A, B) ObjectSetInteger (m_Id, m_szObjName[m_Counter], A, B)

...

         ObjectCreate (m_Id, m_szObjName[m_Counter], OBJ_CHART , m_IdSubWin, 0 , 0 );
         ObjectSetString (m_Id, m_szObjName[m_Counter], OBJPROP_SYMBOL , szSymbol);
        macro_SetInteger( OBJPROP_CHART_SCALE , scale);
...
        macro_SetInteger( OBJPROP_PERIOD , _Period );
        m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );
        m_Counter++;
#undef macro_SetInteger
};


Betrachten wir dieses SetBase-Codesegment etwas genauer. Wir beginnen mit der Deklaration des Makros - es teilt dem Compiler mit, wie der vereinfachte Code mit dem Makronamen interpretiert werden soll. Das heißt, wenn wir etwas mehrmals wiederholen müssen, können wir diese Eigenschaft der Sprache C nutzen, um etwas Einfacheres zu erzeugen. Wenn wir zufällig etwas ändern müssen, dann ändern wir es nur im Makro. Dies beschleunigt die Arbeit erheblich und verringert die Möglichkeit von Fehlern in Codes, bei denen nur das eine oder andere Argument geändert wird.

Auf diese Weise erstellen wir ein Objekt vom Typ CHART. Das mag seltsam erscheinen. Warum verwenden wir etwas, das geändert werden kann, um Dinge zu ändern? Ja, das ist richtig. Der nächste Schritt besteht darin, das zu verwendende Asset (Handelssymbol) zu deklarieren. Der erste Punkt hier: Wenn zum Zeitpunkt der Speicherung des Chats kein Asset vorhanden ist, wird das Objekt mit dem aktuell verwendeten Asset verknüpft. Handelt es sich um ein bestimmtes Asset-Chart, wird später genau dieses Asset verwendet. Wichtiges Detail: Sie können auch ein anderes Asset angeben und damit allgemeine Einstellungen verwenden, aber das werde ich im nächsten Artikel genauer erklären. Denn wir werden einige Verbesserungen in diesem Code implementieren, um Dinge tun zu können, die derzeit nicht möglich sind. Als Nächstes gibt es eine Informationsverdichtungsstufe, die in der Eigenschaft OBJPROP_CHART_SCALE angegeben wird. Wir werden Werte von 0 bis 5 verwenden. Obwohl wir auch Werte außerhalb dieses Bereichs verwenden können, sollten wir ihn besser beibehalten.

Als Nächstes ist die Eigenschaft OBJPROP_PERIOD zu beachten. Bitte beachten Sie, dass wir die aktuelle Chart-Periode verwenden, und wenn wir diese ändern, ändert sich auch diese Eigenschaft. In Zukunft werden wir einige Änderungen vornehmen, die es ermöglichen werden, diese Eigenschaft zu sperren. Wenn Sie es ausprobieren möchten, können Sie eine von MetaTrader 5 definierte Periode verwenden, z.B. PERIOD_M10, die die Anzeige von Daten in einer festen 10-Minuten-Periode anzeigt. Aber das wird später noch verbessert werden. Danach erhöhen wir die Anzahl der Sub-Indikatoren um eins und zerstören das Makro. D.h. es ist nicht mehr existent und müsste neu definiert werden, um an anderer Stelle verwendet zu werden. Hatte ich nicht etwas vergessen! Ja, es ist die Zeile, die vielleicht der wichtigste Teil dieses Codes ist.

m_handle = ObjectGetInteger (m_Id, m_szObjName[m_Counter], OBJPROP_CHART_ID );

Diese Zeile erfasst etwas, das als Zeiger betrachtet werden kann, obwohl es nicht wirklich ein Zeiger ist, aber es erlaubt uns, einige zusätzliche Manipulationen mit dem OBJ_CHART-Objekt durchzuführen, das wir erstellt haben. Wir brauchen diesen Wert, um einige Einstellungen innerhalb des Objekts vorzunehmen. Diese befinden sich in der Einstellungsdatei, die wir zuvor erstellt haben. Wenn wir mit dem Code weitermachen, kommen wir zu der folgenden Funktion, die unten in voller Länge zu sehen ist:

 void AddTemplate( const eTypeChart type, const string szTemplate, int scale)
{
	if (m_Counter >= def_MaxTemplates) return ;
	if (type == SYMBOL) SymbolSelect (szTemplate, true );
	SetBase((type == INDICATOR ? _Symbol : szTemplate), scale);
	ChartApplyTemplate (m_handle, szTemplate + ".tpl" );
	ChartRedraw (m_handle);
}


Zuerst prüfen wir, ob es möglich ist, einen neuen Indikator hinzuzufügen oder nicht. Wenn dies möglich ist, prüfen wir, ob es sich um ein SYMBOL handelt, und wenn ja, dann muss das SYMBOL im Fenster Market Watch vorhanden sein, was durch die Funktion garantiert wird. Auf dieser Grundlage erstellen wir ein Objekt, das Informationen empfängt. Bei der Ausführung wird die Vorlage auf OBJ_CHART angewandt, und dann passiert das Wunder: Wir rufen das Objekt erneut auf, aber jetzt enthält es Daten gemäß den Definitionen in der Einstellungsdatei, die zur Definition von OBJ_CHART verwendet wurde. Jetzt ist es einfach, schön und verständlich.

Mit diesen beiden Funktionen ließe sich eine Menge anstellen. Aber wir brauchen noch mindestens eine weitere Funktion - ihr vollständiger Code ist unten dargestellt:

 void Resize( void )
{
         int x0 = 0 , x1 = ( int )( ChartGetInteger (m_Id, CHART_WIDTH_IN_PIXELS , m_IdSubWin) / (m_Counter > 0 ? m_Counter : 1 ));
         for ( char c0 = 0 ; c0 < m_Counter; c0++, x0 += x1)
        {
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XDISTANCE , x0);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_XSIZE , x1);
                 ObjectSetInteger (m_Id, m_szObjName[c0], OBJPROP_YSIZE , ChartGetInteger (m_Id, CHART_HEIGHT_IN_PIXELS , m_IdSubWin));
        }
         ChartRedraw ();
}

Die obige Funktion bringt alles an seinen Platz und hält die Daten immer im Bereich des Unterfensters und es gibt nichts mehr zu ergänzen. Damit haben wir den notwendigen Code fertiggestellt, damit alles perfekt funktioniert. Aber was ist mit den anderen Funktionen?! Keine Sorge, der Rest der Routinen ist überhaupt nicht notwendig, da sie lediglich die Interpretation der Befehlszeilen unterstützen. Werfen wir jedoch einen Blick auf etwas, das für jeden wichtig ist, der diesen Code in Zukunft ändern möchte. Es handelt sich um das reservierte Wort, das in unserem Objektklassencode erscheint:

 public   :

Dieses Wort garantiert, dass von diesem Moment an alle Daten und Funktionen von anderen Teilen des Codes aufgerufen und eingesehen werden können, auch wenn sie nicht Teil der Objektklasse sind. Hier wird also deklariert, was tatsächlich von anderen Objekten geändert oder eingesehen werden kann. Ein guter objektorientierter Code sollte eigentlich nie einen direkten Zugriff auf die Daten eines Objekts erlauben. In gut durchdachtem Code haben wir nur Zugriff auf Methoden. Der Grund dafür ist einfach - Sicherheit. Wenn wir externen Code erlauben, Daten innerhalb einer Klasse zu ändern, laufen wir Gefahr, dass die Daten nicht mit dem übereinstimmen, was das Objekt erwartet, und das würde eine Menge Probleme und Kopfschmerzen verursachen, wenn wir versuchen, Inkonsistenzen oder Fehler zu beheben, obwohl alles richtig zu sein scheint. Hier also einige Ratschläge, die ich jemandem geben kann, der seit Jahren in C++ programmiert: Erlauben Sie NIEMALS externen Objekten, Daten in einer von Ihnen erstellten Klasse zu ändern oder direkt darauf zuzugreifen. Stellen Sie Funktionen oder Routinen bereit, damit auf die Daten zugegriffen werden kann, aber erlauben Sie niemals den direkten Zugriff auf die Daten und stellen Sie sicher, dass die Funktionen und Routinen die Daten so unterstützen, wie es die von Ihnen erstellte Klasse erwartet. In diesem Sinne kommen wir nun zu den letzten beiden Funktionen unseres Tutorials, von denen eine öffentlich (AddThese) und die andere privat (Decode) ist. Sie können sie unten in voller Länge sehen:

void Decode( string &szArg, int &iScale)
{
#define def_ScaleDefault 4
         StringToUpper (szArg);
        iScale = def_ScaleDefault;
         for ( int c0 = 0 , c1 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ':' :
                         for (; (c0 < max) && ((szArg[c0] < '0' ) || (szArg[c0] > '9' )); c0++);
                        iScale = ( int )(szArg[c0] - '0' );
                        iScale = ((iScale > 5 ) || (iScale < 0 ) ? def_ScaleDefault : iScale);
                        szArg = StringSubstr (szArg, 0 , c1 + 1 );
                         return ;
                 case ' ' :
                         break ;
                 default :
                        c1 = c0;
                         break ;
        }
#undef def_ScaleDefault
}
//+------------------------------------------------------------------+
// ... Codes not related to this part...
//+------------------------------------------------------------------+
void AddThese( const eTypeChart type, string szArg)
{
         string szLoc;
         int i0;
         StringToUpper (szArg);
         StringAdd (szArg, ";" );
         for ( int c0 = 0 , c1 = 0 , c2 = 0 , max = StringLen (szArg); c0 < max; c0++) switch (szArg[c0])
        {
                 case ';' :
                         if (c1 != c2)
                        {
                                szLoc = StringSubstr (szArg, c1, c2 - c1 + 1 );
                                Decode(szLoc, i0);
                                AddTemplate(type, szLoc, i0);
                        }
                        c1 = c2 = (c0 + 1 );
                         break ;
                 case ' ' :
                        c1 = (c1 >= c2 ? c0 + 1 : c1);
                         break ;
                 default :
                        c2 = c0;
                         break ;
        }
}


Diese beiden Funktionen tun genau das, was ich oben erklärt habe: Sie erzwingen die Datenintegrität innerhalb der Objektklasse, indem sie verhindern, dass inkonsistente Daten Teil der internen Daten der Klasse werden. Sie empfangen eine Befehlszeile und dekodieren sie entsprechend einer vordefinierten Syntax. Sie sagen jedoch nicht, dass der empfangene Befehl einen Fehler enthält, denn das ist nicht ihre Aufgabe. Ihr Zweck ist es, sicherzustellen, dass keine inkonsistenten Daten in das Objekt gelangen und keine schwer zu erkennenden und zu behebenden Nebeneffekte verursachen.

Das Endergebnis sieht folgendermaßen aus:



Schlussfolgerung

Ich hoffe, dieser Code inspiriert Sie! Ich habe mich für das Programmieren interessiert, weil es schön und aufregend ist. Obwohl es uns manchmal viel Kopfzerbrechen bereitet, wenn wir besondere Ergebnisse erzielen wollen. Aber die meiste Zeit ist es das wert. Im nächsten Artikel erzähle ich Ihnen, wie Sie das alles noch interessanter machen können. Dieser Artikelanhang enthält den vollständigen Code des Indikators, der bereits wie in diesem und dem vorherigen Artikel beschrieben verwendet werden kann.


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

Mehrere Indikatoren in einem Chart (Teil 03): Entwicklung von Definitionen für die Nutzer Mehrere Indikatoren in einem Chart (Teil 03): Entwicklung von Definitionen für die Nutzer
Heute werden wir zum ersten Mal die Funktionsweise des Indikatorensystems aktualisieren. Im vorangegangenen Artikel "Mehrere Indikatoren in einem Chart" haben wir uns mit dem grundlegenden Code befasst, der die Verwendung von mehr als einem Indikator in einem Chart-Subfenster ermöglicht. Was wir dort vorgestellt haben, war jedoch nur die Ausgangsbasis für ein viel größeres System.
Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Stochastik entwickeln Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Stochastik entwickeln
In diesem Artikel setzen wir unsere Lernserie fort - dieses Mal werden wir lernen, wie man ein Handelssystem mit Hilfe eines der beliebtesten und nützlichsten Indikatoren, dem Stochastik-Oszillator-Indikator, entwirft, um einen neuen Block in unserem Grundlagenwissen zu bilden.
Tipps von einem professionellen Programmierer (Teil III): Protokollierung. Anbindung an das Seq-Log-Sammel- und Analysesystem Tipps von einem professionellen Programmierer (Teil III): Protokollierung. Anbindung an das Seq-Log-Sammel- und Analysesystem
Implementierung der Klasse Logger zur Vereinheitlichung und Strukturierung von Meldungen, die in das Expertenprotokoll ausgegeben werden. Anschluss an das Seq Logsammel- und Analysesystem. Online-Überwachung der Log-Meldungen.
Was Sie mit gleitenden Durchschnitten machen können Was Sie mit gleitenden Durchschnitten machen können
In diesem Artikel werden mehrere Methoden zur Anwendung des Indikators Gleitender Durchschnitt (MA oder Moving Average) vorgestellt. Jede Methode, die eine Kurvenanalyse beinhaltet, wird von Indikatoren begleitet, die die Idee visualisieren. In den meisten Fällen stammen die hier vorgestellten Ideen von den jeweiligen Autoren. Meine einzige Aufgabe bestand darin, sie zusammenzubringen, damit Sie die wichtigsten Ansätze sehen und hoffentlich vernünftigere Handelsentscheidungen treffen können. MQL5-Kenntnisstand - einfach.