English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
preview
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

MetaTrader 5Beispiele | 4 Mai 2022, 13:08
340 0
Daniel Jose
Daniel Jose

Einführung

Im vorangegangenen Artikel Mehrere Indikatoren in einem Chart haben wir uns mit dem grundlegenden Code befasst, der es ermöglicht, mehr als einen Indikator in einem Chart-Unterfenster zu verwenden. Was wir dort vorgestellt haben, war jedoch nur die Ausgangsbasis für ein viel größeres System. Auf der Grundlage dieses Modells lassen sich einige verschiedene Dinge tun. Aber wir sollten Schritt für Schritt vorgehen, denn eines der Ziele dieser Artikel ist es, Sie zu ermutigen, programmieren zu lernen, damit Sie Ihre eigenen Systeme auf der Grundlage Ihrer Ideen entwerfen können. In diesem Artikel werden wir die Funktionsweise erweitern. Er könnte für diejenigen interessant sein, denen die Funktionen des Systems bereits gefallen haben, die aber noch mehr können möchten.


Planung

Wenn wir mit der Implementierung eines neuen Systems beginnen, haben wir oft keine reelle Vorstellung davon, wie sehr wir es verbessern können. Daher sollten wir ein neues Projekt immer mit einem Blick auf mögliche zukünftige Verbesserungen beginnen. Das ist sehr wichtig für diejenigen, die noch ganz am Anfang stehen: ständig etwas zu planen und sich zukünftige Erweiterungen und Verbesserungen vorzustellen.

Der Kerncode hat sich überhaupt nicht verändert, was in gewisser Weise schon zu erwarten war. Aber der Code der Objektklassen hat sich drastisch verändert. Wir haben diese Änderungen vorgenommen, um neue Funktionen zu implementieren und neue Verbesserungen noch flexibler erstellen zu können, da die Wiederverwendung von Code noch wichtiger wird (und das ist einer der Grundgedanken der objektorientierten Programmierung: immer wiederverwenden, nur wenn nötig neu erstellen). Werfen wir also einen Blick auf die neue Objektklasse. Ich werde die Änderungen hervorheben, damit sie leichter zu verstehen sind.

Beginnen wir mit den neuen Definitionen der privaten Klassenvariablen.

struct st
{
        string  szObjName,
                szSymbol;
        int     width;
}m_Info[def_MaxTemplates];
int             m_IdSubWin,
                m_Counter,
                m_CPre,
                m_Aggregate;
long            m_Id,
                m_handle;
ENUM_TIMEFRAMES m_Period;

Achten Sie darauf, dass die Anzahl der verwendeten Variablen erheblich gestiegen ist. Das liegt daran, dass wir mehr Daten benötigen, um die neue Funktionsweise richtig zu verwalten. Unser Variablensystem hat jetzt eine Struktur. Solche Strukturen sind sehr gut geeignet, um zusammengehörige Variablen zu gruppieren — sie stellen sicher, dass wir bei der Bearbeitung von Daten schnell und einfach darauf zugreifen können.

void SetBase(const string szSymbol, int iScale, int iSize)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[m_Counter].szObjName, A, B)
        if (m_IdSubWin < 0)
        {
                m_Id = ChartID();
                m_IdSubWin = (int)ChartGetInteger(m_Id, CHART_WINDOWS_TOTAL) - 1;
                m_Aggregate = 0;
        }
        m_Info[m_Counter].szObjName = __FILE__ + (string) MathRand() + (string) ObjectsTotal(m_Id, -1, OBJ_CHART);
        ObjectCreate(m_Id, m_Info[m_Counter].szObjName, OBJ_CHART, m_IdSubWin, 0, 0);
        ObjectSetString(m_Id, m_Info[m_Counter].szObjName, OBJPROP_SYMBOL, (m_Info[m_Counter].szSymbol = szSymbol));

// ....

        macro_SetInteger(OBJPROP_PERIOD, m_Period);
        m_handle = ObjectGetInteger(m_Id, m_Info[m_Counter].szObjName, OBJPROP_CHART_ID);
        m_Aggregate += iSize;
        m_Info[m_Counter].width = iSize;
        m_CPre += (iSize > 0 ? 1 : 0);
        m_Counter++;
#undef macro_SetInteger
};


Die wichtigste Änderung, die wir gleich sehen werden, ist die Verwendung einer Struktur zum Speichern des Asset-Namens, des Objektnamens und seiner Breite. So können wir jetzt auch die Breite angeben, die der Indikator im Unterfenster haben wird. Lassen Sie uns auch einige Notizen machen, um sie in anderen Teilen der Klasse zu verwenden. Im Folgenden finden Sie die Funktion, die sich am meisten geändert hat.

void Decode(string &szArg, int &iScale, int &iSize)
{
#define def_ScaleDefault 4
#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
                                                                
        string sz1;
        int i0, i1, c1 = StringLen(szArg);
        bool b0 = true;
        StringToUpper(szArg);
        iScale = def_ScaleDefault;
        m_Period = _Period;
        for (int c0 = 0, max = StringLen(szArg); c0 < max; c0++) switch (szArg[c0])
        {
                case ':':
                        b0 = false;
                        for (; (c0 < max) && ((szArg[c0] < '0') || (szArg[c0] > '9')); c0++);
                        iScale = (int)(szArg[c0] - '0');
                        iScale = ((iScale > 5) || (iScale < 0) ? def_ScaleDefault : iScale);
                        break;
                case ' ':
                        break;
                case '<':
                        macro_GetData('>');
                        if (sz1 == "1M") m_Period = PERIOD_M1; else

//....

                        if (sz1 == "1MES") m_Period = PERIOD_MN1;
                        break;
                case '[':
                        macro_GetData(']');
                        iSize = (int) StringToInteger(sz1);
                        break;
                default:
                        c1 = (b0 ? c0 : c1);
                        break;
        }
        szArg = StringSubstr(szArg, 0, c1 + 1);
#undef macro_GetData
#undef def_ScaleDefault
}


Grün zeigt Ergänzungen des Codes an. Gelb wird für die Zeilen verwendet, die bereits im Quellcode vorhanden waren, aber aus praktischen Gründen verschoben wurden. Schauen wir uns nun an, was all diese Ergänzungen im Code bewirken und, was noch wichtiger ist, was sie in Bezug auf die Funktionsweise des ursprünglichen Systems verbessern. Eigentlich schaffen wir eine Basis, die es dem Nutzer ermöglicht, einige spezifische Dinge anzupassen. Wir versuchen dies, indem wir der bestehenden Syntax neue Regeln hinzufügen (siehe Tabelle unten):

Trennzeichen Funktionsweise Beispiel  Ergebnis 
< > Gibt den zu verwendenden Grafikzeitrahmen an  < 15m > Legt den Zeitrahmen des Indikators auf 15 Minuten fest. Der ursprüngliche Chart kann einen anderen Zeitrahmen verwenden, aber der Indikator wird nur mit 15-Minuten-Daten angezeigt.
 [ ] Legt die Breite des Indikators fest  [ 350 ] Legt die Breite des Indikators auf 350 Pixel fest. 

Das Trennzeichen, das den Zeitrahmen des Charts festlegt, wird nur im Indikator-Fenster verwendet und hat keinen Einfluss auf die Änderungen, die der Nutzer vornehmen kann. Alle anderen Indikatoren und der Hauptchart werden auf den neuen, vom Nutzer gewählten Chart-Zeitrahmen aktualisiert, der fixierte Indikator folgt jedoch nicht dem neuen Chart-Zeitrahmen. In einigen Fällen kann dies interessant sein, wie die folgende Abbildung zeigt.

         

Dies erleichtert verschiedene Arten von Einstellungen, bei denen Charts desselben Vermögenswerts mit unterschiedlichen Zeitrahmen auf dem Handelsbildschirm sichtbar bleiben müssen. Ein Trennzeichen, das die Breite des Charts festlegt, wird dies in noch spezifischeren Fällen erleichtern. Es gibt noch eine weitere wichtige Funktion, die wir in einem anderen Artikel erforschen werden, aber im Moment können Sie es verwenden, um die Indikatorbreite zu kontrollieren.

Sie können eine Kombination aus allen Trennzeichen oder nur dasjenige verwenden, das Sie im Indikator wirklich brauchen, da es keine spezifischen Regeln gibt. Die einzige Regel ist, dass der Name des Indikators vor allem anderen stehen sollte. Kommen wir zurück zur Erklärung des Codes. Sehen Sie sich die folgenden Zeilen an:

#define macro_GetData(A)                \
        b0 = false;                     \
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);     \
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != A); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);        \
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
           
//....                                                     
                case '<':
                        macro_GetData('>');


Beachten Sie, dass wir hier etwas definieren, das vielen seltsam vorkommen mag. Aber sein Name kann hier hilfreich sein: macro_GetData(A), dies erzeugt ein Stück Code, das ein Makro ist. Wenn der Compiler diese Definition im Code findet, wird er die Deklaration durch den Makrocode ersetzen. Dies ist sehr nützlich, wenn wir einen bestimmten Codeteil an mehreren Stellen wiederholen wollen, aber mit minimalen Änderungen zwischen den einzelnen Deklarationen. Im vorangegangenen Beispiel wird die grün markierte Zeile ersetzt und der vom Compiler erzeugte Code sieht wie folgt aus:

case '<':
	b0 = false;
        for (c0++; (c0 < max) && (szArg[c0] == ' '); c0++);
        for (i0 = 0, i1 = c0; (c0 < max) && (szArg[c0] != '>'); i0 = (szArg[c0] != ' ' ? c0 - i1 + 1 : i0), c0++);
        if (szArg[c0] == A) sz1 = StringSubstr(szArg, i1, i0); else sz1 = "";
//....

Diese Art von Dingen ist der wahrhaftigste Ausdruck der Philosophie: so viel wie möglich wiederverwenden, so wenig wie möglich neu schreiben. Schauen wir uns nun an, was geändert werden kann, um die Syntax klarer zu machen. Es ist ein kleines Detail, aber es kann viel schöner sein, wenn es auf Ihren persönlichen Stil zugeschnitten ist. Schauen Sie sich die folgende Zeile an:

//.....

if (sz1 == "1M") m_Period = PERIOD_M1; else

//.....

Die hervorgehobene Information ist ein wichtiges Detail. Nach dem von mir verwendeten Modell sollte der Nutzer, wenn er einen Zeitraum von 1 Minute verwenden möchte, dies mit der folgenden Syntax angeben: MIN_1. Wenn Sie einen individuellen Stil haben möchten, können Sie ihn auf Ihre eigene Weise angeben. Die Buchstaben sollten jedoch in Großbuchstaben und ohne Leerzeichen geschrieben werden. Zum Beispiel kann der ausgewählte Teil durch "1MIN", "MIN_1", "1_MINUTE" oder etwas mit noch mehr Details ersetzt werden, zum Beispiel: "LOCK_IN_1_MIN" oder in Ihrer Sprache - dies funktioniert, solange keine Leerzeichen zwischen den Wörtern stehen. Diese Leerzeichenbeschränkung kann zwar aufgehoben werden, aber es scheint mir nicht wirklich notwendig zu sein. Sie sehen, wie schön es ist, Programmieren zu können — Sie können Dinge mit Ihrem eigenen individuellen Stil verwenden. Der nächste Code, den ich geändert habe, ist der Standard-Destruktor.

~C_TemplateChart() 
{
        for (char c0 = 0; c0 < m_Counter; c0++)
        {
                ObjectDelete(m_Id, m_Info[c0].szObjName);
                SymbolSelect(m_Info[c0].szSymbol, false);
        }
}

Die hervorgehobene Zeile wurde für folgende Fälle hinzugefügt: Wenn ein Vermögenswert nicht in einem separaten Fenster geöffnet ist, sollte er nicht mehr in Market Watch erscheinen. Dadurch wird verhindert, dass ungenutzte Vermögenswerte dort Platz wegnehmen und das Fenster verschmutzen. Kommen wir nun zu dem, was ich im vorigen Artikel zu erklären versprach. Dies war im ursprünglichen Code nicht enthalten, wird aber in Zukunft Teil des Codes sein.

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

Die hervorgehobene Zeile macht es möglich, eine Standardeinstellungsdatei zu verwenden, wenn alle beobachteten Anlagen dieselben Einstellungen verwenden. Es ist nicht gut, etwas zu tun, ohne wirklich zu wissen, wie man sich verhalten soll, wenn die Dinge nicht so laufen wie erwartet. Aber wenn die Einstellungen exakt die gleichen sind, warum sollte man sie nicht verwenden? Wenn keine Einstellungsdatei für das angegebene Asset gefunden wird, wird davon ausgegangen, dass Sie die Standardeinstellungen von MetaTrader 5 verwenden möchten, die in der Datei DEFAULT.TPL definiert sind, die sich im Verzeichnis Profiles\Template befindet. Aber zuerst müssen wir eine wichtige Sache verstehen. Warum habe ich kein einziges Verzeichnis in der Funktion ChartApplyTemplate angegeben? Das liegt daran, dass MetaTrader 5 die Suche nach einer bestimmten Logik durchführt. Zu wissen, wie diese Logik funktioniert, kann helfen, Situationen auf interessantere und weniger stressige Weise zu verstehen.

Stellen Sie sich das folgende Szenario vor, in dem wir die hervorgehobene Zeile durch diese ersetzen:

if (!ChartApplyTemplate(m_handle, "MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""MyTemplates\\Default.tpl");

MetaTrader 5 sucht zunächst nach der Einstellungsdatei im Unterverzeichnis MYTEMPLATES des Verzeichnisses, in dem sich die ausführbare Datei des benutzerdefinierten Indikators befindet, d.h. wenn sich der Ordner MYTEMPLATES in demselben Ordner befindet, in dem sich auch die ausführbare Datei zur Erstellung mehrerer Indikatoren befindet, sucht MetaTrader 5 genau dort nach der Datei. Wenn dort jedoch nichts gefunden wird, sucht er nach der gleichen Datei im Verzeichnis MQL5\Profiles\Templates\MyTemplates, deshalb habe ich das vorher nicht gezeigt. Aber es ist nicht nur das. Das gibt noch ein weiteres Detail im selben Code:

if (!ChartApplyTemplate(m_handle, "\\MyTemplates\\" + szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, ""\\MyTemplates\\Default.tpl");

Ein kleines Detail, das alles ändert: MetaTrader 5 wird nun zuerst versuchen, die Datei im MQL5 MyTemplates Verzeichnis zu finden, und wenn es die Datei nicht findet, wird es den oben beschriebenen Schritten folgen. Sie können diese Informationen in der Dokumentation ChartApplyTemplate finden, denn ich möchte diejenigen nicht verwirren, die nicht wissen, wie MetaTrader 5 funktioniert. Aber jetzt, wo Sie verstehen, wie die Suche durchgeführt wird, können Sie Variationen erstellen und Sie wissen, wo Sie die Datei ablegen müssen.

Die nächste Funktion in unserer Klasse, die erhebliche Änderungen erfahren hat, ist unten dargestellt:

void Resize(void)
{
#define macro_SetInteger(A, B) ObjectSetInteger(m_Id, m_Info[c0].szObjName, A, B)
        int x0 = 0, x1, y = (int)(ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS, m_IdSubWin));
        x1 = (int)((ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1));
        for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++)
        {
                macro_SetInteger(OBJPROP_XDISTANCE, x0);
                macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1));
                macro_SetInteger(OBJPROP_YSIZE, y);
        }
        ChartRedraw();
#undef macro_SetInteger
}

Die ausgewählten Zeilen zeigen die wichtigsten Berechnungen in dieser Funktion. Sie passen die Fenster so an, wie der Nutzer es wünscht, aber die Fenster, die auf eine feste Größe eingestellt sind, schaffen einen freien Bereich, in dem die übrigen Fenster ihre definierte Größe haben werden. Wenn jedoch die Breite des Hauptfensters verringert wird, erlaubt die blaue Berechnung nicht, die Breite der Fenster mit fester Größe zu verringern, was ein Problem darstellen kann. Aber lassen wir das erst einmal so stehen, denn Fenster mit fester Breite haben andere Vorteile, die in Zukunft besser erforscht werden. Und die letzte Änderung in unserer Klasse ist folgende:

void AddThese(const eTypeChart type, string szArg)
{
        string szLoc;
        int i0, iSize;
//....
        Decode(szLoc, i0, iSize);
        AddTemplate(type, szLoc, i0, iSize);
//....
}

Die einzige Änderung ist hervorgehoben, es gibt also nichts Besonderes.


Schlussfolgerung

Ich hoffe, dass dieser Artikel, wie auch andere, zeigt, wie interessant die strukturierte Programmierung ist. Mit ein paar kleinen Änderungen und einer gewissen Ruhe können wir dem System eine Menge Funktionen hinzufügen. Die Wiederverwendung von Code macht die Wartung viel einfacher, denn je öfter wir den fertigen Code verwenden, desto geringer ist die Wahrscheinlichkeit, dass er Fehler enthält. Im nächsten Artikel werden wir dieses System an andere Orte bringen, wo es für viele interessanter sein kann, und wir werden versuchen, weitere Details der Programmierung zu verstehen. Bis bald ...



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

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.
Mehrere Indikatoren auf einem Chart (Teil 02): Erste Experiment Mehrere Indikatoren auf einem Chart (Teil 02): Erste Experiment
Im vorherigen Artikel "Mehrere Indikatoren in einem Chart" habe ich das Konzept und die Grundlagen der Verwendung mehrerer Indikatoren in einem Chart vorgestellt. In diesem Artikel werde ich den Quellcode zur Verfügung stellen und ihn im Detail erklären.
Grafiken in der Bibliothek DoEasy (Teil 98): Verschieben von Angelpunkten erweiterter grafischer Standardobjekte Grafiken in der Bibliothek DoEasy (Teil 98): Verschieben von Angelpunkten erweiterter grafischer Standardobjekte
In diesem Artikel setze ich die Entwicklung erweiterter grafischer Standardobjekte fort und schaffe die Funktionen zum Verschieben von Angelpunkten zusammengesetzter grafischer Objekte unter Verwendung von Kontrollpunkten zur Verwaltung der Koordinaten der Angelpunkte des grafischen Objekts.
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.