English Русский 中文 Español 日本語 Português
preview
Erstellen eines Ticker-Panels: Basisversion

Erstellen eines Ticker-Panels: Basisversion

MetaTrader 5Handel | 17 Februar 2023, 14:43
317 0
Daniel Jose
Daniel Jose

Einführung

Manche Leute finden die in einigen Plattformen eingebauten Preistafeln, auf denen die Kurse der einzelnen Vermögenswerte angezeigt werden, ziemlich cool. Wenn Sie nicht wissen, wovon ich spreche, sehen Sie sich das folgende Gif an:

In manchen Fällen können solche Dinge sehr nützlich sein. Deshalb werde ich hier zeigen, wie man ein solches Element innerhalb der MetaTrader 5 Plattform mit 100% MQL5 Programmierung implementiert. Viele mögen das Material in diesem Artikel als etwas recht Einfaches betrachten. Aber ich garantiere Ihnen, dass Sie, wenn Sie die hier vorgestellten Konzepte verstehen, in der Lage sein werden, viel komplexere Dinge zu schaffen.

Darüber hinaus werde ich weitere Artikel schreiben, in denen ich dieses Panel weiterentwickeln werde, sodass es zu einem äußerst nützlichen Werkzeug für diejenigen wird, die handeln und andere Informationen in Echtzeit verfolgen wollen.

Ich muss zugeben, dass die Idee für diesen Artikel von einem der Mitglieder dieser Gemeinschaft stammt. Diese Idee ist recht interessant zu implementieren und zu entwickeln, und sie kann auch eine sehr nützliche Ressource für viele sein, weshalb ich beschlossen habe, zu zeigen, wie man den Code für ein solches Panel erstellt.


Planung

Es ist nicht allzu kompliziert, ein solches Panel zu erstellen. Im Vergleich zu anderen Codearten ist es nämlich sehr einfach zu implementieren. Bevor wir jedoch zur Umsetzung übergehen, sollten wir einige Dinge planen, die einen wesentlichen Einfluss darauf haben, welche Richtung wir bei der Erstellung des Panels einschlagen. Da die Idee ist, ein Panel zu haben, das einen Vermögenswert und seinen Preis anzeigt, ohne von Anfang an große Schwierigkeiten zu haben, bitte nicht das Folgende: Ich werde hier zeigen, wie man ein sehr einfaches System erstellt, das jedoch als Ausgangspunkt für etwas Komplexeres, Ausgefeilteres und Ausgereifteres dienen wird.

Als Erstes müssen wir uns überlegen, wie wir mit der Liste der Assets umgehen wollen, die auf dem Panel angezeigt werden soll. Handelt es sich um eine feste Liste mit einer Vorauswahl von Vermögenswerten? Oder werden wir bei der Einführung des Systems ein Symbol nach dem anderen einfügen?

Dies ist wahrscheinlich der schwierigste Teil, denn manchmal möchten man vielleicht Vermögenswerte haben, an denen man interessiert ist, während man in anderen Fällen die Vermögenswerte des eigenen Portfolios beobachten will. Daher ist es vielleicht besser, eine Datei zu verwenden, die alle Assets enthält, die im Angebotsfenster angezeigt werden sollen. Wir werden also eine DATEI verwenden, die die anzuzeigenden Assets enthält.

Nun stellt sich ein weiteres Problem: Wie kann eine Ressource dargestellt werden? Es scheint eine Kleinigkeit zu sein, aber es ist tatsächlich sehr wichtig, darüber nachzudenken. Wir können einen Expert Advisor, ein Skript, einen Indikator oder einen Dienst verwenden, obwohl letzteres keine offensichtliche Lösung zu sein scheint. Ich persönlich ziehe es vor, einen Dienst zu nutzen. Wenn wir uns jedoch für die Implementierung des Panels entscheiden, haben wir zu viele komplizierte Details und Schwierigkeiten, die die Entwicklung des Panels zu einem komplizierten und zeitaufwändigen Prozess machen würden. Daher gibt es nur zwei praktische Möglichkeiten, das Panel zu implementieren: es in einen EA oder in einen Indikator zu integrieren. Aber warum können wir nicht ein Skript verwenden?

Der Grund dafür ist einfach: Wenn der Nutzer beschließt, den Zeitrahmen zu ändern, muss er das Skript letztendlich schließen. Jedes Mal, wenn sich das Chart ändert, muss der Händler das Skript also erneut starten. Wie ich bereits sagte, wird dies eine 100%ige MQL5-Lösung sein. Wir könnten uns vorstellen, eine externe Programmierlösung zu verwenden, aber wir haben hier einen anderen Ziel.

Es bleiben also zwei Möglichkeiten: ein Expert Advisor und ein Indikator. Die Idee, einen EA zu verwenden, gefällt mir nicht, da ich es vorziehe, die EAs für ihre vorgesehenen Zwecke zu verwenden, d. h. zum Senden und Steuern von Aufträgen. Daher gibt es nur noch eine Lösung: die Verwendung eines Indikators.

Es gibt noch weitere Fragen, die berücksichtigt und geplant werden müssen, aber wir können bereits mit diesem vorläufigen Plan beginnen. Also, fangen wir an.


Grundlegende Prinzipien

Beginnen wir mit der Erstellung der Indikatordatei:

#property copyright "Daniel Jose"
#property description "Program for a panel of quotes."
#property description "It creates a band that displays asset prices."
#property description "For details on how to use it visit:\n"
#property description "https://www.mql5.com/ru/articles/10941"
#property link "https://www.mql5.com/ru/articles/10941"
#property indicator_separate_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;
}
//+------------------------------------------------------------------+
void OnTimer()
{
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}
//+------------------------------------------------------------------+

Obwohl der Code des Indikators völlig sauber ist, d. h. er wird nichts Besonderes tun, haben wir bereits eine Vorstellung davon, was auf uns zukommen wird. Zum Beispiel werden wir ein separates Indikatorfenster verwenden und mehr Ereignisse als bei einem klassischen Indikator behandeln müssen, wie z. B. OnTime, das normalerweise nicht in Indikatoren vorkommt. Vergessen wir nicht das Folgende: Es wird überhaupt nichts gezeichnet, denn alles, was der Indikator erstellt, wird vom Indikator ausgeführt.

Normalerweise beginnen wir mit einem fertigen Code, aber in diesem Artikel möchte ich alles etwas detaillierter darstellen, damit der Leser das Material als Quelle für Forschung und Studium nutzen kann.

Sie denken wahrscheinlich schon darüber nach, wie viele verschiedene Dinge wir einführen müssen, damit alles funktioniert. In gewisser Weise stimmt das, aber es wird nicht so viele Dinge geben. Als Erstes sollten Sie sich überlegen, wie Sie das Chart kontrollieren wollen. Dafür haben wir eine Klasse. Obwohl viele diese Klasse in meinen früheren Artikeln gesehen haben, wird sie hier etwas anders aussehen, da wir viel weniger Dinge verwenden müssen. Also, nein, ich stelle die Klasse C_Terminal für alle vor, die sie noch nicht kennen. Die Klasse befindet sich in der Header-Datei C_Terminal.mqh. Der Code ist recht einfach — siehe unten:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
class C_Terminal
{
//+------------------------------------------------------------------+
        private :
                struct st00
                {
                        long    ID;
                        int     Width,
                                Height,
                                SubWin;
                }m_Infos;
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+          
                void Init(const int WhatSub)
                        {
                                ChartSetInteger(m_Infos.ID = ChartID(), CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin = WhatSub, true);
                                Resize();
                        }
//+------------------------------------------------------------------+
inline long Get_ID(void)   const { return m_Infos.ID; }
inline int GetSubWin(void) const { return m_Infos.SubWin; }
inline int GetWidth(void)  const { return m_Infos.Width; }
inline int GetHeight(void) const { return m_Infos.Height; }
//+------------------------------------------------------------------+
                void Resize(void)
                        {
                                m_Infos.Width = (int) ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
                                m_Infos.Height = (int) ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
                        }
//+------------------------------------------------------------------+
inline string ViewDouble(double Value)
                        {
                                Value = NormalizeDouble(Value, 8);
                                return DoubleToString(Value, ((Value - MathFloor(Value)) * 100) > 0 ? 2 : 0);
                        }
//+------------------------------------------------------------------+
                void Close(void)
                        {
                                ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, m_Infos.SubWin, false);
                        }
//+------------------------------------------------------------------+          
};
//+------------------------------------------------------------------+

Ja, das ist der gesamte Code, den wir brauchen. Obwohl die Klasse eigentlich viel größer ist, habe ich hier nur die notwendigen Teile gezeigt, da ich den Artikel nicht mit zusätzlichen Materialien füllen möchte.

Für diejenigen unter Ihnen, die nicht wissen, was diese Klasse tut, wollen wir uns kurz einige ihrer Bestandteile ansehen. Da wir wollen, dass MetaTrader 5 uns über jeden Versuch, ein Objekt zu löschen, informiert, müssen wir es hier deklarieren und dann die Größen des verwendeten Fensters ermitteln. Hier schaffen wir tatsächlich eine zusätzliche Abstraktionsebene, um uns bei der Programmierung zu helfen.

Dies ist nicht verpflichtend und kann auch auf andere Weise umgesetzt werden. Dank dieser Abstraktionsebene, auf der wir alles ausblenden, was nicht tatsächlich assembliert wird, haben wir jedoch einige Aufrufe zum Zugriff auf Klassendaten. Am Ende der Klasse sollten wir die Erzeugung von Ereignissen vermeiden, während der Indikator beginnt, Objekte zu entfernen, deshalb verwenden wir diese Funktion. Es gibt einen Punkt im Code, an dem wir eine Formatierung erstellen müssen — wir werden dies hier tun, um alles, was mit dem Terminal zu tun hat, in einer Klasse zu halten.

Bis zu diesem Punkt waren die Dinge recht einfach. Außerdem werden sie immer komplizierter, seien Sie also vorsichtig.


Implementierung der wichtigsten Objekte

So seltsam es auch klingen mag, wir werden nur zwei Objekte im Basismodell des Panels verwenden. Da ich das Modell verwende, das Sie in meiner früheren Serie Einen handelnden Expert Advisor von Grund auf neu entwickeln, die in der MQL5.community veröffentlicht wurde, sehen konnten, werde ich das Modell für diese Serie übernehmen. In dieser Serie wird ausführlich erklärt, wie alles funktioniert, aber ich werde hier eine kurze Beschreibung der Funktionsweise des Systems geben. Selbst wenn Sie nicht wissen, wie MetaTrader 5 mit Objekten umgeht, werden Sie sich nicht verirren.

Beginnen wir also mit der Basisobjektklasse, deren Code wie folgt aussieht:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "..\Auxiliar\C_Terminal.mqh"
//+------------------------------------------------------------------+
class C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
virtual void Create(string szObjectName, ENUM_OBJECT typeObj)
                        {
                                ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, Terminal.GetSubWin(), 0, 0);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, false);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
                        };
//+------------------------------------------------------------------+
                void PositionAxleX(string szObjectName, int X)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X);
                        };
//+------------------------------------------------------------------+
                void PositionAxleY(string szObjectName, int Y, int iArrow = 0)
                        {
                                int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl)));
                        };
//+------------------------------------------------------------------+
virtual void SetColor(string szObjectName, color cor)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                        }
//+------------------------------------------------------------------+
                void Size(string szObjectName, int Width, int Height)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height);
                        };
//+------------------------------------------------------------------+
};

Der Code ist einfach und kompakt. Dadurch wird eine Abstraktionsebene geschaffen, sodass wir später viel weniger Code verwenden können. Hier haben wir eine virtuelle Funktion, die dafür verantwortlich ist , ein beliebiges Objekt auf eine sehr generische Weise zu erstellen. Da wir in diesem Basismodell aber nur ein Objekt verwenden werden, könnte man meinen, dass diese Funktion ein wenig Zeitverschwendung ist. Das ist nicht wahr, und wenn Sie sich den Code im Auftragssystem des EA ansehen, werden Sie verstehen, wovon ich spreche.

Wir haben zwei weitere Funktionen, um das Objekt im Chart zu positionieren. Es gibt auch eine Funktion, mit der die Farbe des Objekts geändert werden kann, die wie die Funktion zur Erstellung des Objekts virtuell ist. Wir brauchen sie, weil einige Objekte komplexe Farbmuster haben. Und schließlich haben wir eine Funktion, die die Objektabmessungen anpasst.

Auch wenn es albern erscheint, wird uns die Schaffung dieser Abstraktionsebene in Zukunft helfen, da alle Objekte auf einzigartige Weise behandelt werden, unabhängig davon, um welches Objekt es sich handelt. Dies bietet einige Vorteile, die wir jedoch auf ein anderes Mal verschieben wollen. Schauen wir also, welches Objekt ausgewählt wird, um das Panel zu erstellen. Wir werden OBJ_EDIT wählen. Der vollständige Code ist nachstehend aufgeführt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
#define def_ColorNegative       clrCoral
#define def_ColoPositive        clrPaleGreen
//+------------------------------------------------------------------+
class C_Object_Edit : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
                template < typename T >
                void Create(string szObjectName, color corTxt, color corBack, T InfoValue)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_EDIT);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, corTxt);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
                                if (typename(T) == "string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (string)InfoValue); else SetTextValue(szObjectName, (double)InfoValue);
                        };
//+------------------------------------------------------------------+
                void SetTextValue(string szObjectName, double InfoValue, color cor = clrNONE)
                        {
                                color clr;
                                clr = (cor != clrNONE ? cor : (InfoValue < 0.0 ? def_ColorNegative : def_ColoPositive));                                
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, Terminal.ViewDouble(InfoValue < 0.0 ? -(InfoValue) : InfoValue));
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);
                        };
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+
#undef def_ColoPositive
#undef def_ColorNegative
//+------------------------------------------------------------------+

Ist das alles? Ja, das ist alles, obwohl es sich leicht von dem Code unterscheidet, der im Auftragssystem des EAs verwendet wird. Hier haben wir alles, was wir brauchen: eine Funktion zum Einfügen von Double-Werten, dem Typ, den wir in MQL5 wirklich oft verwenden, das Erstellen von Objekten vom Typ Edit Obj_Edit und eine weitere Sache, die Anfänger verwirren könnte. Schauen wir uns die Objekterstellungsfunktion im folgenden Code genauer an:

template < typename T >
void Create(string szObjectName, color corTxt, color corBack, T InfoValue)

Eigentlich behandelt der Compiler diese beiden Zeilen als eine einzige. Verstehen Sie, was hier vor sich geht? Glauben Sie, dass ich die Dinge verkompliziere?

Wir verwenden „template < typenname T >. Hier kann T durch irgendetwas anderes ersetzt werden, sofern es den aktuellen Namenskonventionen entspricht. Dies stellt eine Form der Überlastung dar. Es kommt häufig vor, dass wir ähnliche Funktionen erstellen müssen, die unterschiedliche Argumente oder Datentypen erhalten. Dies ist sehr häufig der Fall. Um uns das Leben in solchen Momenten zu erleichtern, verwenden wir diese Syntax. Es mag seltsam erscheinen, aber es wird oft verwendet, wenn man nicht die gesamte Funktion neu schreiben will, nur weil ein Teil der Daten anders ist, während der gesamte innere Körper der Funktion derselbe ist.

Wenn Sie genau hinschauen, werden Sie feststellen, dass es nur eine Zeile am Ende der Prozedur gibt, die einen interessanten Code enthält:

if (typename(T) == "string") ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, (string)InfoValue); else SetTextValue(szObjectName, (double)InfoValue);

Der Code geht folgendermaßen vor: Er prüft den Typ der Daten, die in der Variablen InfoValueangegeben sind. Achten Sie darauf, dass ich über den TYP und nicht über den Wert spreche, also verwechseln Sie diese beiden Konzepte nicht.

Wenn der Typ eine Zeichenkette ist, wird ein Code ausgeführt; wenn der Typ ein anderer ist, wird ein anderer Code ausgeführt, aber dies wird nicht vom Compiler oder Linker gemacht. Diese Analyse wird in der Regel zur Laufzeit durchgeführt, sodass wir explizit angeben sollten, welche Daten verarbeitet werden sollen, damit der Linker den Prozess korrekt einrichten kann. Dies geschieht mit Hilfe der im Code hervorgehobenen Zeilen.

Anstatt also zwei fast identische Funktionen mit nur einem Unterschied zu erstellen, überladen wir sie und optimieren sie, wo es nötig ist, um am Ende viel weniger Arbeit zu haben.

Im EA-Code, in dem die Funktion immer nur mit dem Grundtyp double arbeitet, war dieser Ansatz nicht erforderlich. Aber jetzt werden wir zusätzlich zum Typ double auch mit strings arbeiten, und ich wollte den Code nicht duplizieren, nur um zwei Typen zu implementieren.

Wenn Sie mehr darüber erfahren möchten, sehen Sie sich die Template-Funktionen an. Die Informationen helfen Ihnen zu verstehen, warum Funktionsüberladungen so häufig verwendet werden und wie Sie vermeiden können, dass Sie Ihren gesamten Code neu schreiben müssen, nur weil Sie verschiedene Typen verwenden.

Bevor wir diesen Abschnitt über die Objekte beenden, sollten Sie auf das Objekt achten, das sich am unteren Rand des Fensters befinden wird. Wir müssen einen Hintergrund erstellen. Sie erwarten doch nicht, dass alles ohne Hintergrund gut funktioniert, oder? Aber keine Sorge, der Code dafür ist sehr einfach. Schauen Sie:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
class C_Object_BackGround : public C_Object_Base
{
        public:
//+------------------------------------------------------------------+
                void Create(string szObjectName, color cor)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
                                this.SetColor(szObjectName, cor);
                        }
//+------------------------------------------------------------------+
virtual void SetColor(string szObjectName, color cor)
                        {
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor);
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor);
                        }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+

Dieser Code ist einfach und überschaubar, sodass er meiner Meinung nach keiner Erklärung bedarf. Sie wird nur für die Erstellung des Panel-Hintergrunds verwendet. Wie auch immer, ich zeige es hier, falls sich jemand fragt, wie der Code zur Erstellung eines Hintergrunds aussieht.

Damit können wir diesen Abschnitt beenden. Wir haben die Objekte bereits implementiert und verfügen über die Trägerstruktur für das Terminal, sodass wir zum nächsten Schritt übergehen können.


Implementierung der Hauptklasse

Bisher haben wir den Boden für diesen Schritt bereitet, der der spannendste von allen ist, da wir hier das System tatsächlich zum Laufen bringen werden. Der entsprechende Code befindet sich in der Header-Datei C_Widget.mqh. Beginnen wir mit den anfänglichen Erklärungen, die unten dargestellt sind:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "Elements\C_Object_Edit.mqh"
#include "Elements\C_Object_BackGround.mqh"
//+------------------------------------------------------------------+
C_Terminal Terminal;
//+------------------------------------------------------------------+
#define def_PrefixName          "WidgetPrice"
#define def_NameObjBackGround	def_PrefixName + "BackGround"
#define def_MaxWidth            80
//+------------------------------------------------------------------+
#define def_CharSymbol          "S"
#define def_CharPrice           "P"
//+------------------------------------------------------------------+
#define macro_MaxPosition (Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth)
#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + A + "#" + B)
//+------------------------------------------------------------------+

Hier deklarieren wir die Header-Dateien, die wir tatsächlich benötigen, obwohl es auch andere gibt. Aber wir brauchen sie nicht alle, daher reichen diese Header-Dateien aus, da sie alles andere abdecken.

Wir deklarieren auch die Terminal-Klasse, damit wir sie zur Erstellung des Panels verwenden können. Wir haben auch einige Deklarationen und Makros in dieser Header-Datei C_Widget.mqh zu verwenden. Seien Sie jedoch sehr vorsichtig mit Makros, denn sie müssen auf die richtige Weise verwendet werden. Solange wir sie richtig einsetzen, wird es keine großen Probleme geben, und sie werden uns sehr helfen.

Danach deklarieren wir die Klasse mit ihren Anfangsvariablen.

class C_Widget
{
        protected:
                enum EventCustom {Ev_RollingTo};
        private :
                struct st00
                {
                        color   CorBackGround,
                                CorSymbol,
                                CorPrice;
                        int     nSymbols,
                                MaxPositionX;
                        struct st01
                        {
                                string szCode;
                        }Symbols[];
                }m_Infos;

Diese Enumeration wird später sehr nützlich sein, obwohl sie nicht erforderlich ist. Es ist sehr nützlich, da es die Dinge abstrakter macht. Sie erhalten einen Code, der leichter zu lesen und zu verstehen ist. Später werden wir eine Struktur deklarieren, die uns helfen wird, einige Dinge zu kontrollieren. Aber jetzt brauchen Sie sich darüber keine Gedanken mehr zu machen, Sie müssen nur wissen, dass es hier ist und dass es ein völlig privater Teil der Klasse ist, d.h. kein äußerer Code wird darauf zugreifen können.

Kommen wir nun zur eigentlichen Aktion, und die erste davon ist unten dargestellt:

void CreateBackGround(void)
{
        C_Object_BackGround backGround;
                        
        backGround.Create(def_NameObjBackGround, m_Infos.CorBackGround);
        backGround.Size(def_NameObjBackGround, Terminal.GetWidth(), Terminal.GetHeight());
}

Hier erstellen wir den eigentlichen Hintergrund des Panels. Achten Sie darauf, dass wir den gesamten Bereich des Unterfensters nutzen werden. Wir platzieren die Objekte hier und füllen alles mit einer Farbe. So erhalten wir einen einheitlichen Hintergrund. Wie ich bereits im vorigen Kapitel erwähnt habe, schaffen wir eine gewisse Abstraktion, die es uns ermöglicht, viel weniger zu programmieren und die Ergebnisse viel schneller zu erhalten. Jetzt werden wir zu komplizierteren Dingen übergehen.

void AddSymbolInfo(const string szArg, const bool bRestore = false)
        {
#define macro_Create(A, B, C)   {                                               \
                edit.Create(A, m_Infos.CorSymbol, m_Infos.CorBackGround, B);    \
                edit.PositionAxleX(A, def_MaxWidth * m_Infos.nSymbols);         \
                edit.PositionAxleY(A, C);                                       \
                edit.Size(A, def_MaxWidth - 1, 22);                             \
                                }
                        
                C_Object_Edit edit;

                macro_Create(macro_ObjectName(def_CharSymbol, szArg), szArg, 10);
                macro_Create(macro_ObjectName(def_CharPrice, szArg), 0.0, 32);
                if (!bRestore)
                {
                        ArrayResize(m_Infos.Symbols, m_Infos.nSymbols + 1, 10);
                        m_Infos.Symbols[m_Infos.nSymbols].szCode = szArg;
                        m_Infos.nSymbols++;
                }
#undef macro_Create
        }

In dieser Funktion deklariere ich ein Makro, das nur hier verwendet wird. Achten Sie darauf, dass ich das Makro vor der Fertigstellung lösche, da es außerhalb der Funktion nirgends verwendet wird.

Hier erzeugen wir ein Objekt vom Typ C_Object_Edit, positionieren es temporär und geben die Größe an, die es haben soll. All dies wird innerhalb des Makros durchgeführt. An diesen Stellen verwenden wir das Makro, um den Code leicht lesbar zu machen, da er insgesamt praktisch gleich bleibt. Natürlich gibt es das Problem mit den Werten, aber die Funktion ist die gleiche, und deshalb verwenden wir ein Makro. Auch hier gilt: weniger tippen, mehr produzieren.

Kommen wir nun zu einem wichtigen Detail. Die gleiche Funktion wird aufgerufen, wenn der Nutzer ein Objekt löscht, das nicht gelöscht werden sollte. In diesem Fall werden nachfolgenden Zeilen nicht ausgeführt. Sie werden jedoch während der normalen Erstellung ausgeführt, bei der wir zunächst den Speicher reservieren und dann den Namen des Symbols an die zugewiesene Stelle setzen und ihn für den nächsten Aufruf inkrementieren. Dann können wir mit dem nächsten Anruf fortfahren.

Die nächste im Code ist die folgende interessante Funktion:

inline void UpdateSymbolInfo(const int x, const string szArg)
{
        C_Object_Edit edit;
        string sz0 = macro_ObjectName(def_CharPrice, szArg);
        MqlRates Rate[1];
                                
        CopyRates(szArg, PERIOD_M1, 0, 1, Rate);                                
        edit.PositionAxleX(macro_ObjectName(def_CharSymbol, szArg), x);
        edit.SetTextValue(sz0, Rate[0].close, m_Infos.CorPrice);
        edit.PositionAxleX(sz0, x);
}

Viele Leute denken, dass wir Objekte auf globaler Ebene benötigen, aber eigentlich innerhalb von MetaTrader 5 und bei Verwendung von MQL5. Dies ist nicht ganz richtig, da alle erstellten Objekte bei Bedarf manipuliert werden können. Um den Namen des Objekts herauszufinden, überprüfen wir das Fenster mit der Liste aller Objekte, die auf der Symboltabelle vorhanden sind. So können wir den lokalen Zugriff nutzen und die im Diagramm vorhandenen Objekte manipulieren, vorausgesetzt, wir kennen ihre Namen.

Dann erstellen wir den Namen des Objekts, um es manipulieren zu können. Zur Vereinfachung werden wir ein Makro verwenden. Danach gibt es eine weitere interessante Sache. Normalerweise müssen wir den Vermögenswert, dessen Informationen wir abrufen wollen, im Fenster Market Watch haben. Aber in unserem Fall, wenn wir ein Panel erstellen, würde es den Nutzer de-motivieren, der Hunderte von Assets in Market Watch öffnen und erinnern müsste. Um dies zu vermeiden, werden wir eine andere Methode anwenden, die allerdings ihren Preis hat. Nichts gibt es umsonst. Der Preis ist wie folgt: Bei jedem Aufruf dieser Funktion wird der letzte Balken kopiert, um zu wissen, was passiert ist.

Danach lokalisieren wir die gewünschten Objekte an der richtigen Stelle und geben den Wert für die Darstellung des Objekts an. Denken Sie aber daran, dass es bei jedem Aufruf zu einer kleinen Verzögerung bei der Ausführung kommen wird. Wir werden dies später in diesem Artikel noch etwas verbessern.

Die nächste Funktion ist unten dargestellt:

bool LoadConfig(const string szFileConfig)
{
        int file;
        string sz0;
        bool ret;
                                
        if ((file = FileOpen("Widget\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                PrintFormat("Configuration file %s not found.", szFileConfig);
                return false;
        }
        m_Infos.nSymbols = 0;
        ArrayResize(m_Infos.Symbols, 30, 30);
        for (int c0 = 1; (!FileIsEnding(file)) && (!_StopFlag); c0++)
        {
                if ((sz0 = FileReadString(file)) == "") continue;
                if (SymbolExist(sz0, ret)) AddSymbolInfo(sz0); else
                {
                        FileClose(file);
                        PrintFormat("Ativo na linha %d não foi reconhecido.", c0);
                        return false;
                }
        }
        FileClose(file);
        m_Infos.MaxPositionX = macro_MaxPosition;
                
        return !_StopFlag;
}

Hier wird eine Datei gelesen, die alle Assets enthält, die im Panel verwendet werden sollen. Beachten Sie, dass keine Erweiterung erforderlich ist - ich gebe nur den Ort an, an dem die Datei gespeichert wird. So können Sie der Datei einen beliebigen Namen geben, um verschiedene Dateien für verschiedene Dinge zu haben.

Wir sollten jedoch darauf achten, dass wir eine Datei mit korrekten Daten verwenden, da es sonst zu Problemen kommen kann. Im Anhang habe ich zusätzlich zum vollständigen Code des Systems eine Datei zur Demonstration der internen Formatierung beigefügt. Diese Datei enthält alle Vermögenswerte, die derzeit im Ibovespa-Index (IBOV) enthalten sind. Verwenden Sie diese Datei als Grundlage für die Erstellung aller anderen. Die gleiche Formatierung wird in diesem System verwendet und wird auch in allen anderen Aktualisierungen und Verbesserungen verwendet werden.

Wenn die Datei gefunden wird und geöffnet werden kann, führen wir einen Aufruf aus, um Speicher zuzuweisen, in dem die Daten gespeichert werden, sobald sie ankommen. Dann beginnen wir, Zeile für Zeile zu lesen, bis zum Ende der Datei oder bis der Nutzer den Vorgang unterbricht. Wenn eine der Zeilen leer ist oder keine Informationen enthält, machen wir einen neuen Leseabruf. Hier ist ein weiterer wichtiger Moment: Der Vermögenswert wird nur hinzugefügt, wenn er existiert; wenn er nicht existiert, wird ein Fehler zurückgegeben, der angibt, in welcher Zeile er aufgetreten ist. Die Fehlermeldung wird im Toolbox-Fenster angezeigt. Weitere Zeilen werden nicht gelesen und es wird ein Fehler zurückgegeben. Am Ende konfigurieren wir wichtige Informationen für unsere Zukunft, damit wir später keine unnötigen Berechnungen durchführen müssen.

~C_Widget()
{
        Terminal.Close();
        ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
        ArrayFree(m_Infos.Symbols);
}

Diese Funktion ist der Destruktor der Klasse. Sie wird automatisch aufgerufen, wenn die Klasse geschlossen wird. In diesem Fall wird das gesamte System geschlossen, während alle innerhalb der Klasse erstellten Objekte gelöscht und der zugewiesene Speicher freigegeben wird.

Im folgenden Code haben wir ein Klasseninitialisierungssystem:

bool Initilize(const string szFileConfig, const string szNameShort, color corText, color corPrice, color corBack)
{
        IndicatorSetString(INDICATOR_SHORTNAME, szNameShort);
        Terminal.Init(ChartWindowFind());
        Terminal.Resize();
        m_Infos.CorBackGround = corBack;
        m_Infos.CorPrice = corPrice;
        m_Infos.CorSymbol = corText;
        CreateBackGround();

        return LoadConfig(szFileConfig);
}

Es gibt nicht viel zu erzählen, da ich bereits alles erklärt habe, was hier verwendet wird, außer den folgenden Punkten. Hier legen wir einen Kurznamen für unseren Indikator fest, dieser Name wird als Parameter verwendet, achten Sie also darauf. Der Code hier wird verwendet, um den Index des Unterfensters zu erfassen, das vom Indikatorverwendet wird. Dies ist wegen der Objekte wichtig - wir müssen wissen, welches Unterfenster verwendet wird, sonst können wir die Objekte an der falschen Stelle platzieren.

Und als letzte Funktion in dieser Header-Datei haben wir das Nachrichtensystem.

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        static int tx = 0;
        string szRet[];
                                                        
        switch (id)
        {
                case (CHARTEVENT_CUSTOM + Ev_RollingTo):
                        tx = (int) (tx + lparam);
                        tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
                        for (int c0 = 0, px = tx; (c0 < m_Infos.nSymbols); c0++)
                        {
                                if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
                                px += def_MaxWidth;
                                px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
                        }
                        ChartRedraw();
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        Terminal.Resize();
                        m_Infos.MaxPositionX = macro_MaxPosition;
                        ChartRedraw();
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (StringSubstr(sparam, 0, StringLen(def_PrefixName)) == def_PrefixName) if (StringSplit(sparam, '#', szRet) == 2)
                        {
                                AddSymbolInfo(szRet[1], true);
                                ChartRedraw();
                        }else if (sparam == def_NameObjBackGround)
                        {
                                ObjectsDeleteAll(Terminal.Get_ID(), def_PrefixName);
                                CreateBackGround();
                                for (int c0 = 0; c0 < m_Infos.nSymbols; c0++) AddSymbolInfo(m_Infos.Symbols[c0].szCode, true);
                                ChartRedraw();
                        }
                        break;
        }
}

Der größte Teil dieses Codes ist recht einfach: Wir haben zwei von der Plattform generierte Ereignisse, die zur Verarbeitung an den Indikator weitergegeben werden, aber wir haben auch einen Ereignistyp, der für viele keinen Sinn macht, weil es ein nutzerdefiniertes Ereignis ist. Diese Art von Ereignis ist in bestimmten Projekttypen durchaus üblich, dient hier aber eher dazu, die Verarbeitung von Nachrichten oder Ereignissen, die auftreten können, zu zentralisieren. Obwohl viele dies nicht verstehen, sind die MetaTrader 5 Plattform und die MQL5 Sprache ereignisorientiert, was bedeutet, dass wir nicht prozedural arbeiten, sondern mit Ereignissen arbeiten und sie verarbeiten, wenn sie auftreten.

Um zu verstehen, wie ein nutzerdefiniertes Ereignis erzeugt wird, müssen wir uns den Code des Indikators ansehen. Deshalb wollen wir uns vor der Erklärung (auch wenn ich glaube, dass viele von Ihnen Schwierigkeiten haben werden, genau dieses Ereignis zu verstehen) den Code des Indikators ansehen, der jetzt eine andere funktionale Ansicht hat, die sich von dem unterscheidet, was wir am Anfang des Artikels gesehen haben.

#property copyright "Daniel Jose"
#property description "Program for a panel of quotes."
#property description "It creates a band that displays asset prices."
#property description "For details on how to use it visit:\n"
#property description "https://www.mql5.com/ru/articles/10941"
#property link "https://www.mql5.com/ru/articles/10941"
#property indicator_separate_window
#property indicator_plots 0
#property indicator_height 45
//+------------------------------------------------------------------+
#include <Widget\Rolling Price\C_Widget.mqh>
//+------------------------------------------------------------------+
input string    user00 = "Config.cfg";  //Configuration file
input int       user01 = -1;            //Shift
input int       user02 = 60;            //Pause in milliseconds
input color     user03 = clrWhiteSmoke; //Asset color
input color     user04 = clrYellow;     //Price color
input color     user05 = clrBlack;      //Background color
//+------------------------------------------------------------------+
C_Widget Widget;
//+------------------------------------------------------------------+
int OnInit()
{
        if (!Widget.Initilize(user00, "Widget Price", user03, user04, user05))
                return INIT_FAILED;
        EventSetMillisecondTimer(user02);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnTimer()
{
        EventChartCustom(Terminal.Get_ID(), C_Widget::Ev_RollingTo, user01, 0.0, "");
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Widget.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+

Dies ist etwas, das in Indikatorcodes nur selten verwendet wird, nämlich die Angabe der Höhe des Indikatorfensters. Aber das ist nicht der Punkt. Achten Sie auf die folgenden Details:

Wenn der Nutzer den Wert dieses Parameters festlegt, erhalten wir Daten, die wir als Zeitgeber verwenden können. Es ist richtig, dass wir die Verwendung von OnTime-Ereignissen in einem Indikator nach Möglichkeit vermeiden sollten. Aber hier haben wir leider keine andere Wahl. Wir brauchen dieses Ereignis. Achten wir nun darauf, dass die MetaTrader 5-Plattform, wenn sie das OnTime-Ereignis auslöst, den OnTime-Ereignisaufruf erzeugt. Innerhalb dieser Funktion gibt es nur eine Zeile, die ein asynchrones Ereignis auslöst, was bedeutet, dass wir nicht genau wissen, wann der Code aufgerufen wird. Dies ist ein nutzerdefiniertes Ereignis.

Beachten Sie, dass die Parameter innerhalb des nutzerdefinierten Ereignisses nicht die üblichen sind. Sie befinden sich dort aus einem sehr wichtigen Grund: jeder von ihnen zeigt eine Sache an, aber am Ende werden wir als Ergebnis einen OnChartEvent-Aufruf haben, der die Funktion innerhalb der C_Widget-Klasse aufruft, die die durch das Ereignis erzeugten Nachrichten verarbeiten wird.

Achten Sie nun auf Folgendes: Wenn wir die Funktion EventChartCustom verwenden, legen wir ein Ereignis fest, das als ID für die Funktion OnChartEvent verwendet wird. Dieser Wert wird in der Nachrichtenverarbeitungsfunktion ermittelt. Wenn die Nachrichtenverarbeitungsfunktion direkt aufgerufen würde, wäre der Code asynchron, d.h. wir würden den Rest des Codes in den Wartemodus versetzen, um auf die Rückkehr der Nachrichtenverarbeitungsfunktion zu warten. Da wir jedoch den Aufruf EventChartCustom verwenden, befindet sich der Code nicht im Wartemodus. Dadurch wird vermieden, dass andere Indikatoren durch etwas blockiert werden, dessen Dauer wir nicht kennen.

Die Tatsache, dass wir den Aufruf über EventChartCustom implementieren, hat einen weiteren Vorteil: Dieser Aufruf kann von jeder Stelle des Codes aus erfolgen. Unabhängig davon, von wo aus wir es aufrufen, wird ChartEvent immer ausgelöst und OnChartEvent aufgerufen, um die erforderliche Ausführung zu gewährleisten.

Dieser Ansatz wird auch in einem anderen Artikel verwendet, der ein anderes, aber nicht weniger interessantes Thema behandelt. Ich werde jetzt nicht darüber sprechen, um die Spannung zu erhalten, bevor der Artikel veröffentlicht wird.

Ich hoffe, dass dieser Teil klar ist: wie das nutzerdefinierte Ereignis erzeugt wird und warum ich ein nutzerdefiniertes Ereignis verwende, anstatt direkt den Code aufzurufen, der das Panel verschiebt. Kehren wir nun zu dem Code zurück, der die Behandlung dieses nutzerdefinierten Ereignisses zum Verschieben des Panels enthält, und denken wir daran, dass es einen vom Nutzer angegebenen Parameter gibt, der für die Bewegung sehr wichtig ist..

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        static int tx = 0;
        string szRet[];
                                                        
        switch (id)
        {
                case (CHARTEVENT_CUSTOM + Ev_RollingTo):
                        tx = (int) (tx + lparam);
                        tx = (tx < -def_MaxWidth ? m_Infos.MaxPositionX : (tx > m_Infos.MaxPositionX ? -def_MaxWidth : tx));
                        for (int c0 = 0, px = tx; (c0 < m_Infos.nSymbols); c0++)
                        {
                                if (px < Terminal.GetWidth()) UpdateSymbolInfo(px, m_Infos.Symbols[c0].szCode);
                                px += def_MaxWidth;
                                px = (px > m_Infos.MaxPositionX ? -def_MaxWidth + (px - m_Infos.MaxPositionX) : px);
                        }
                        ChartRedraw();
                        break;

Die Mathematik im obigen Code mag verwirrend erscheinen, aber was ich hier tue, ist, den vom Nutzer angegebenen Wert zu verwenden, um die Objekte in einem bestimmten Abstand zu bewegen. Ist der Wert positiv, wird das Objekt von links nach rechts verschoben; ist der Wert negativ, wird das Objekt von rechts nach links verschoben; ist der Wert null, bleibt es, wo es ist. Die Idee ist einfach, aber wo sind die Berechnungen, die man nicht sehen kann? Deshalb habe ich gesagt, dass der obige Code verwirrend erscheint. Die Berechnungen werden in diesen beiden Zeilen durchgeführt.

Sie werden vielleicht nicht ganz verstehen, wie das möglich ist, wie eine so einfache Berechnung so etwas bewirkt. Aber wenn Sie aufmerksam genug sind, können Sie sehen, dass wir Grenzen setzen. Wenn der obere Grenzwert erreicht ist, wird die Position auf den unmittelbar anschließenden Grenzwert neu berechnet. Wir schließen die Schleife, dass der Wert, wenn er einen bestimmten Punkt erreicht, so angepasst wird, dass er am entgegengesetzten Grenzpunkt beginnt. Zum besseren Verständnis: Wenn wir von 0 bis 99 zählen und nicht über diese Werte hinaus zählen können, was passiert dann, wenn wir versuchen, 1 zu 99 zu addieren? Nach dieser Logik würden wir 100 erhalten.

Aber nicht in diesem Fall. In unserem Fall würden wir wieder auf 0 kommen. Wenn wir versuchen, 3 zu 98 zu addieren, würden wir nicht mehr als 99 erhalten - wir würden 1 erhalten. Es scheint seltsam zu sein, aber so funktioniert es nun einmal. Dasselbe gilt, wenn wir 3 von 2 subtrahieren - wir erhalten 99.... Klingt verrückt 😵 😵 😵... aber das ist die Grundlage des Zählsystems des Computers. Wenn man es genau studiert, wird man sehen, dass der Computer nicht bis zu unendlichen Zahlen rechnet. Es gibt eine gewisse Grenze für den maximal erreichbaren Wert, die für einen anderen Bereich gilt, nämlich die Verschlüsselung, aber das ist eine andere Geschichte. 

Kommen wir auf den Code zurück. Sie sollten versuchen zu verstehen, was wir gerade besprochen haben, denn wenn wir zur FOR-Schleife kommen, werden die Dinge noch seltsamer.

Innerhalb der FOR-Schleife machen wir Folgendes: Wir wissen nicht, wo und bei wie viel wir aufhören sollen, da die obigen Berechnungen uns nicht sagen, wo auf dem Bildschirm etwas angezeigt werden soll. Dazu müssen wir ein Fenster erstellen oder vielmehr die Grenzen des Chartfensters nutzen, um zu wissen, was angezeigt werden soll und was nicht.

Dieser Teil wird sehr verwirrend sein, wenn Sie das oben beschriebene Konzept nicht verstanden haben. Die einzigen zwei Informationen, die wir haben, sind: wie viele Elemente wir anzeigen sollen und welcher Wert gerade verwendet wird. Auf der Grundlage dieser Informationen sollten wir den Rest erledigen. Wir gehen also von Element zu Element und beginnen immer beim Nullelement, und während wir fortschreiten, addieren wir die Breite eines jeden Elements zur Ausgangsposition. Irgendwann werden wir die Grenze überschreiten, entweder in der oberen oder in der unteren Bandbreite. Wenn dieser Wert überschritten wird, sollte der Wert, den wir verwenden, um anzugeben, wo das aktuelle Element gezeichnet werden soll, entsprechend angepasst werden. Sobald dies geschieht, haben wir die Abweichung der Position, sodass die Informationen auf magische Weise auf der einen Seite des Bildschirms verschwinden und auf der anderen Seite erscheinen werden.

Der Zyklus wiederholt sich, bis der Indikator geschlossen ist. So werden alle Informationen auf dem Bildschirm angezeigt, unabhängig davon, wie viele Informationen wir haben.

Dies ist bei einem reinen Text viel einfacher zu bewerkstelligen und zu planen. Aber obwohl die Technik recht ähnlich ist, verwenden die meisten Leute normalerweise Code, der eine Matrix verwendet, in der sich die Elemente innerhalb der Matrix bewegen und jede der Zellen bereits eine genau definierte Position für die Anzeige hat. Aber das würde in unserem Fall nicht zum gewünschten Ergebnis führen, weshalb ich eine andere Methode anwenden musste, bei der wir einen rein mathematischen Ansatz verwenden, um reibungslose und korrekte Bewegungen zu erzeugen.

Ein weiteres Detail ist, dass wir es vermeiden sollten, Werte größer als -1 oder 1 zu verwenden, da die Bewegung dann halb pulsierend ist und einen seltsamen Eindruck vermittelt.

Im folgenden Video sehen Sie das System in Aktion mit Daten des IBOV (Ibovespa Index). Dies ist nur ein Beispiel dafür, wie das System funktioniert...




Schlussfolgerung

Obwohl dieses System vollständig fertig zu sein scheint, gibt es noch Raum für Verbesserungen. Im nächsten Artikel werde ich Ihnen zeigen, wie Sie diese Verbesserungen im System vornehmen können. Bleiben Sie dran, denn es werden noch weitere Updates folgen. Der Anhang enthält den gesamten Code dieses Artikels. Verwenden Sie ihn nach Belieben.


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

Beigefügte Dateien |
Erstellen eines Ticker-Panels: Verbesserte Version Erstellen eines Ticker-Panels: Verbesserte Version
Was halten Sie von der Idee, die Grundversion unseres Ticker-Panels wiederzubeleben? Als Erstes werden wir das Panel so ändern, dass wir ein Bild hinzufügen können, z. B. ein Anlagenlogo oder ein anderes Bild, damit der Nutzer das angezeigte Logo schnell und einfach identifizieren kann.
Lernen Sie, wie man ein Handelssystem mit Gator Oscillator entwickelt Lernen Sie, wie man ein Handelssystem mit Gator Oscillator entwickelt
Ein neuer Artikel in unserer Serie über die Entwicklung eines Handelssystems auf der Grundlage beliebter technischer Indikatoren wird sich mit dem technischen Indikator Gator Oscillator und der Erstellung eines Handelssystems durch einfache Strategien befassen.
Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen Erstellen eines EA, der automatisch funktioniert (Teil 01): Konzepte und Strukturen
Heute werden wir sehen, wie man einen Expert Advisor erstellt, der einfach und sicher im automatischen Modus arbeitet.
Algorithmen zur Optimierung mit Populationen Firefly-Algorithmus (FA) Algorithmen zur Optimierung mit Populationen Firefly-Algorithmus (FA)
In diesem Artikel werde ich die Optimierungsmethode des Firefly-Algorithmus (FA) betrachten. Dank der Änderung hat sich der Algorithmus von einem Außenseiter zu einem echten Tabellenführer entwickelt.