English Русский Español 日本語 Português
preview
Entwicklung eines Replay System (Teil 27): Expert Advisor Projekt — Die Klasse C_Mouse (II)

Entwicklung eines Replay System (Teil 27): Expert Advisor Projekt — Die Klasse C_Mouse (II)

MetaTrader 5Tester | 12 März 2024, 16:32
111 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems (Teil 26): Expert Advisor Projekt (I)“, haben wir uns ausführlich mit dem Beginn des Baus der ersten Klasse beschäftigt. Lassen Sie uns nun diese Ideen weiter ausbauen und sie nützlicher machen. Damit sind wir bei der Erstellung der Klasse C_Mouse angelangt. Es bietet die Möglichkeit, auf höchstem Niveau zu programmieren. Wenn man über High-Level- oder Low-Level-Programmiersprachen spricht, geht es jedoch nicht darum, obszöne Wörter oder Jargon in den Code aufzunehmen. Es ist genau andersherum. Wenn wir von High-Level- oder Low-Level-Programmierung sprechen, meinen wir, wie leicht oder schwer der Code für andere Programmierer zu verstehen ist. Tatsächlich zeigt der Unterschied zwischen High-Level- und Low-Level-Programmierung, wie einfach oder komplex Code für andere Entwickler sein kann. Daher wird Code als High-Level betrachtet, wenn er der natürlichen Sprache ähnelt, und als Low-Level, wenn er der natürlichen Sprache weniger ähnelt und näher daran ist, wie der Prozessor die Anweisungen interpretiert.

Unser Ziel ist es, den Klassencode so hoch wie möglich zu halten und gleichzeitig bestimmte Arten der Modellierung, die für weniger erfahrene Personen schwer verständlich sein können, so weit wie möglich zu vermeiden. Das ist das Ziel, auch wenn ich nicht garantieren kann, dass es vollständig erreicht wird.


C_Mouse Klasse: Beginn der Interaktion mit dem Nutzer

Maus und Tastatur sind die gebräuchlichsten Mittel der Interaktion zwischen dem Nutzer und der Plattform. Daher ist es sehr wichtig, dass diese Interaktion einfach und effektiv ist, sodass der Nutzer nicht neu lernen muss, wie er die Aktionen ausführt. Der Code beginnt mit den folgenden Zeilen:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Terminal.mqh"
//+------------------------------------------------------------------+
#define def_MousePrefixName "MOUSE_"
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TV"
#define def_NameObjectLineT def_MousePrefixName + "TT"
#define def_NameObjectBitMp def_MousePrefixName + "TB"
#define def_NameObjectText  def_MousePrefixName + "TI"
//+------------------------------------------------------------------+
#define def_Fillet      "Resource\\Fillet.bmp"
#resource def_Fillet
//+------------------------------------------------------------------+

Wir haben eine Header-Datei eingebunden, die die Klasse C_Terminal enthält. Wie im vorigen Artikel erwähnt, befindet sich diese C_Mouse-Klassendatei im gleichen Verzeichnis wie die C_Terminal-Klassendatei, sodass wir diese Syntax ohne Probleme verwenden können. Wir definieren den Namen der Ressource, die in die ausführbare Datei aufgenommen werden soll, sodass Sie sie portieren können, ohne die Ressource separat herunterladen zu müssen. Dies ist in vielen Fällen sehr nützlich, vor allem, wenn die Ressource kritisch ist und ihre Verfügbarkeit während der Nutzung entscheidend ist. In der Regel legen wir die Ressource in einem bestimmten Verzeichnis ab, um den Zugriff zu erleichtern. Auf diese Weise wird sie immer zusammen mit der Header-Datei kompiliert. Wir haben dem Ordner, in dem sich die Datei C_Mouse.mqh befindet, ein Verzeichnis namens „Resource“ hinzugefügt. Die Datei Fillet.bmp befindet sich im Verzeichnis „Resource“. Wenn wir die Verzeichnisstruktur ändern und dabei die gleiche Modellierung beibehalten, weiß der Compiler genau, wo er die Datei Fillet.bmp finden kann. Sobald der Code kompiliert ist, können wir die ausführbare Datei laden, ohne uns Sorgen machen zu müssen, dass die Ressource nicht gefunden wird, da sie in die ausführbare Datei selbst eingebettet ist.

In diesem Schritt legen wir zunächst einen Namen fest, und zwar ein Präfix für weitere Namen, die wir später definieren werden. Die Verwendung von Definitionen erleichtert die Entwicklung und Wartung, wie es bei professionellem Code üblich ist. Der Programmierer definiert verschiedene Namen und Elemente, die im Code verwendet werden sollen. Dies geschieht in der Regel in einer Datei Defines.mqh oder einer ähnlichen Datei. Mit dieser Datei ist es einfach, die Definitionen zu ändern. Da diese Definitionen jedoch nur in dieser Datei existieren, müssen sie nirgendwo anders angegeben werden.

#undef def_MousePrefixName
#undef def_NameObjectLineV
#undef def_NameObjectBitMp
#undef def_NameObjectLineH
#undef def_NameObjectLineT
#undef def_NameObjectText
#undef def_Fillet

Dieser Code teilt dem Compiler mit, dass alle Symbole und Namen, die in der Datei C_Mouse.mqh definiert und sichtbar sind, ab diesem Zeitpunkt nicht mehr sichtbar sein sollen. Es wird im Allgemeinen nicht empfohlen, Definitionen in anderen Dateien zu entfernen oder zu ändern - dies ist keine gängige Praxis. Deshalb geben wir die Namen dort bekannt, wo sie tatsächlich erscheinen und verwendet werden. Danach werden diese Definitionen entfernt. Auch das Ändern oder Löschen von Definitionen ohne Kriterien ist keine gute Praxis. Wenn wir eine Definition in mehreren Dateien verwenden müssen, ist es am besten, eine separate Datei dafür zu erstellen.

Kommen wir nun zu den ersten Codezeilen der Klasse. An dieser Stelle wird es interessant. Alles beginnt hier:

class C_Mouse : public C_Terminal
{

   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x01, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int      X,
                     Y;
            double   Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
      };

In diesem Fragment sehen wir, dass die Klasse C_Terminal „public“ von der Klasse C_Mouse abgeleitet wird, was bedeutet, dass wir durch die Verwendung der Klasse C_Mouse Zugriff auf alle „public“-Methoden der Klasse C_Terminal haben werden. Damit hat die Klasse C_Mouse viel mehr Funktionalität, als wenn sie nur auf den Code in der Datei C_Mouse.mqh beschränkt wäre. Die Vererbung bietet nicht nur diesen Vorteil, sondern auch andere Vorteile, um Klassen effizienter zu machen, die wir in zukünftigen Artikeln diskutieren werden. Lassen Sie uns mit diesem Codeteil weiterarbeiten. Innerhalb des geschützten Teils des Codes gibt es zwei Enumerationsdeklarationen, die es uns ermöglichen, auf einer etwas höheren Ebene zu programmieren. Die erste Enumeration ist recht einfach und folgt dem gleichen Konzept und den gleichen Regeln, die wir im vorherigen Artikel behandelt haben. Die zweite Liste mag dagegen etwas verwirrend und komplex erscheinen, aber wir werden den Grund für ihre Komplexität und den Grund für ihre Existenz untersuchen.

Diese Enumeration bietet uns eine Möglichkeit, die sonst viel schwieriger zu pflegen wäre, d. h. sie erspart uns viel Arbeit. Diese spezielle Enumeration erzeugt Namensdefinitionen, was der Compilerdirektive #define entspricht. Wir haben uns jedoch für eine Enumeration anstelle von Definitionen entschieden. Dadurch können wir eine etwas andere Technik verwenden, die aber gleichzeitig im Code viel einfacher zu verstehen ist. Anhand dieser Enumeration werden wir sehen, wie der Code viel besser lesbar wird. Dies ist bei komplexem Code von entscheidender Bedeutung. Wenn Sie denken, dass diese Enumeration eine auf den ersten Blick sehr verwirrende und komplexe Erklärung hat, dann verstehen Sie wahrscheinlich nicht ganz, wie Enumerationen funktionieren. Aus der Sicht des Compilers ist eine Enumeration lediglich eine Folge von Definitionen, wobei das erste Element standardmäßig bei Index Null beginnt. Wir können jedoch den gewünschten Startindex der Enumeration festlegen, von dem aus der Compiler die Werte der nachfolgenden Indizes inkrementieren wird. Dies ist in vielen Szenarien sehr nützlich, in denen der Wert eines bestimmten Index als Startwert einer Sequenz dient. Oft verwenden Programme lange Listen von Enumerationen, in denen Fehlerwerte auf der Grundlage eines bestimmten Kriteriums festgelegt werden. Wenn Sie einen Namen definieren und ihm einen bestimmten Wert zuweisen, erhöht der Compiler automatisch die Werte aller nachfolgenden Namen. Dies erleichtert die Erstellung umfangreicher Listen von Definitionen, ohne dass die Gefahr besteht, dass die Werte irgendwann doppelt vorkommen.

Es ist erstaunlich, dass viele Programmierer diese Technik nicht anwenden, da sie bei der Programmierung bestimmter Projekttypen sehr hilfreich sein kann, um Fehler zu vermeiden. Jetzt, da Sie dies verstanden haben, können Sie experimentieren und feststellen, dass die Verwendung von Enumerationen den Prozess der Erstellung einer großen Liste von zusammenhängenden Elementen, ob sequenziell oder nicht, erheblich vereinfacht. Der von uns untersuchte Ansatz zielt darauf ab, die Programmierung zu verbessern, indem der Code leichter zu lesen und zu verstehen ist.

Der nächste Teil ist eine Struktur, die dafür verantwortlich ist, den Rest des Codes darüber zu informieren, was die Maus gerade tut. An dieser Stelle könnte man erwarten, dass eine Variable deklariert wird, aber das Deklarieren von Variablen innerhalb einer Klasse außerhalb einer privaten Klausel wird nicht als gute Programmierpraxis angesehen. Andere sind vielleicht der Meinung, dass es angemessener wäre, diese Erklärungen in den öffentlichen Teil des Codes aufzunehmen. Ich ziehe es jedoch vor, mit einem eingeschränkten Zugang zu beginnen und den öffentlichen Zugang nur als letztes Mittel zuzulassen. Wir müssen sicherstellen, dass Funktionen und Methoden öffentlich zugänglich sind, mit Ausnahme derjenigen, die für die Klasse von direktem Interesse sind. Andernfalls empfehlen wir, den Elementen zunächst nur minimale Privilegien zu gewähren.

Fahren wir mit dieser Idee fort und sehen wir uns die Variablen in unserer Klasse C_Mouse an:


   private :
      enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
      struct st01
      {
         st_Mouse Data;
         color    corLineH,
                  corTrendP,
                  corTrendN;
         eStudy   Study;
      }m_Info;
      struct st_Mem
      {
         bool    CrossHair;
      }m_Mem;

Es ist besonders interessant, dass wir bereits eine Enumeration haben, die für keinen anderen Teil des Codes außerhalb der Klasse sichtbar ist. Der Grund dafür ist, dass diese Enumeration nur innerhalb dieser Klasse nützlich ist und es für andere Teile des Codes keinen Sinn macht, von ihrer Existenz zu wissen. Dieses Konzept ist als Kapselung bekannt, die auf dem Prinzip beruht, dass kein anderer Teil des Codes weiß, wie der Code, der die jeweilige Aufgabe ausführt, tatsächlich funktioniert. Diese Art von Ansatz wird von Bibliotheksentwicklern sehr geschätzt, da sie anderen Programmierern den Zugriff auf Prozeduren ermöglichen, ohne die eigentliche Funktionsweise des Bibliothekscodes offen zu legen.

Als Nächstes finden wir die Struktur. Dabei wird eine weitere Struktur verwendet, auf die außerhalb des Klassenkörpers zugegriffen werden kann und die wir bei der Erläuterung der Zugriffsverfahren und -funktionen ausführlich beschreiben werden. An dieser Stelle ist es wichtig zu verstehen, dass sich diese Variable auf eine private Klassenstruktur bezieht. Es gibt eine andere Struktur, und in diesem Fall bevorzuge ich diesen Ansatz, weil er deutlich macht, dass der Inhalt etwas Besonderes ist und nur an ganz bestimmten Stellen im Code aufgerufen wird. Es hindert Sie jedoch nichts daran, die gleichen Daten in der vorherigen Struktur zu deklarieren. Sie müssen nur beim Schreiben von Code darauf achten, diese Daten nicht zu ändern, da die in der ersten Struktur enthaltenen Daten für den allgemeinen Gebrauch bestimmt sind und jederzeit geändert werden können.

Wir haben bereits die Teile eingeführt, die sich auf die Variablen beziehen, jetzt können wir zur Analyse der Funktionen und Methoden übergehen. Schauen wir uns den folgenden Code an:

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
   }

Dieser Code erleichtert die Wiederverwendung von Software, da wir während der Entwicklung der Klasse C_Mouse verschiedene Elemente erstellen müssen, die einer gewissen Standardisierung unterliegen. Um den Prozess zu vereinfachen, werden wir diese Erstellung in einer einzigen Methode zentralisieren. Eine häufig anzutreffende Praxis in Deklarationen, insbesondere wenn die Leistung ein kritischer Faktor ist, ist die Verwendung eines bestimmten Schlüsselworts. Ich habe mich dafür entschieden, weil ich möchte, dass der Compiler den Code direkt an der Stelle einbindet, an der er deklariert ist, ähnlich wie bei einem Makro. Dies kann zu einer Vergrößerung der ausführbaren Datei führen, aber im Gegenzug erhalten wir eine, wenn auch geringfügige, Verbesserung der Gesamtlaufzeitleistung. Manchmal ist der Leistungsgewinn aufgrund verschiedener Faktoren, die eine Vergrößerung der ausführbaren Datei nicht rechtfertigen, minimal.

Hier haben wir es mit einer Situation zu tun, die vielen unbedeutend erscheinen mag, die aber im Laufe des Codes zu einem immer wiederkehrenden Aspekt werden wird. Diese Funktion bezieht sich auf eine in der Klasse C_Terminal deklarierte Struktur. Der Compiler interpretiert sie jedoch nicht als Funktion, sondern als konstante Variable. Aber wie ist das möglich? Wie kann der Compiler eine Deklaration als konstante Variable behandeln, die wie eine Funktion aussieht? Auf den ersten Blick macht dies nicht viel Sinn. Sehen Sie sich jedoch den Code für diesen Aufruf und seine Implementierung in der Klasse C_Terminal genauer an:

inline const st_Terminal GetInfoTerminal(void) const
   {
      return m_Infos;
   }

Dieser Code gibt einen Verweis auf eine Struktur zurück, die zur Klasse C_Terminal gehört. Die Variable, auf die wir einen Verweis zurückgeben, ist für die Klasse privat, und ihre Werte sollten unter keinen Umständen durch einen anderen Code als den in der Klasse C_Terminal vorhandenen geändert werden. Um sicherzustellen, dass der Code keine Änderungen an dieser privaten Variable vornimmt, wenn er auf sie zugreift, haben wir beschlossen, eine spezielle Erklärung aufzunehmen. Auf diese Weise stellt der Compiler sicher, dass jeder Code, der einen Verweis auf die Konstante erhält, ihren Wert nicht ändern kann. Diese Maßnahme wird verwendet, um versehentliche Änderungen oder Programmierfehler zu vermeiden. Selbst wenn also innerhalb der Klasse C_Terminal versucht wird, einen Wert in der Funktion in unangemessener Weise zu ändern, wird der Compiler diesen Vorgang als Fehler erkennen, da nach Ansicht des Compilers dort keine Informationen geändert werden können. Dies geschieht, weil dieser Ort für solche Veränderungen ungeeignet oder falsch ist.

Diese Art der Programmierung ist zwar arbeitsintensiver, verbessert aber die Robustheit und Zuverlässigkeit des Codes. Allerdings gibt es in diesem Zusammenhang einen Fehler, auf den wir später eingehen werden. Denn diese Entscheidung jetzt zu erklären, würde die Gesamtauslegung erschweren. Schauen wir uns nun die folgende Methode in der Klasse C_Mouse an:

inline void CreateLineH(void)
   {
      CreateObjectBase(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH);
   }

Es wird eine horizontale Linie erstellt, die die Preislinie darstellt. Es ist wichtig zu wissen, dass wir nur eine Zeile schreiben müssen, um die gesamte Komplexität an ein anderes Verfahren zu delegieren. Diese Art von Ansatz wird normalerweise durch ein Makro ersetzt. Ich ziehe es jedoch vor, dies ohne die Verwendung von Makros zu versuchen. Eine andere Möglichkeit besteht darin, denselben Inhalt, vorausgesetzt, es handelt sich um eine Zeile, an den Stellen einzufügen, an denen der Aufruf erfolgen soll. Ich persönlich empfehle diese Praxis nicht, nicht weil sie falsch ist, sondern weil sie verlangt, alle Zeilen zu ändern, während sich eigentlich nur eine Zeile ändert. Dies kann eine mühsame und fehleranfällige Aufgabe sein. Es mag zwar praktischer erscheinen, Code direkt an referenzierten Stellen zu platzieren, doch ist es sicherer, ein Makro oder einen Code mit dem Wort „inline“ in seiner Deklaration zu verwenden.

Die Methoden, die wir im Folgenden sehen werden, mögen jetzt nicht viel Sinn ergeben, aber es ist wichtig, sie zu kennen, bevor wir mit der Erklärung beginnen. Die erste Methode ist unten dargestellt:

void CreateStudy(void)
{
   CreateObjectBase(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH);
   CreateObjectBase(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH);
   CreateObjectBase(def_NameObjectBitMp, OBJ_BITMAP, clrNONE);
   CreateObjectBase(def_NameObjectText, OBJ_TEXT, clrNONE);                                
   ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONT, "Lucida Console");
   ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_FONTSIZE, 10);
   ObjectSetString(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_BMPFILE, "::" + def_Fillet);
   ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2);
   m_Info.Study = eStudyCreate;
}

Es erstellt Objekte, die für die Untersuchung des Charts erforderlich sind, und ermöglicht es uns, unsere eigene Art der Analyse zu erstellen. Auf diese Weise können die Informationen hervorgehoben werden, die für eine effektive Analyse am wichtigsten und notwendig sind. Das hier vorgestellte Recherchemodell ist recht einfach, aber es kann eine vielfältigere Methodik entwickelt werden, die schneller ist und das Chart optisch nicht überfrachtet. Als Beispiel für die einfachste aller Methoden werden wir eine Studie erstellen, um die Anzahl der Punkte zwischen einem Preis und einem anderen zu testen, wobei visuell angezeigt wird, ob der Wert negativ oder positiv ist. Obwohl es sich nicht um ein komplexes System handelt, dient es als Grundlage für die Entwicklung anderer, komplexerer Modelle.

Viele Studien verwenden eine Vielzahl von Objekten und deren Kombinationen, die manchmal Berechnungen oder das Auffinden einer Position in einer Preisspanne (Hoch-Tief-Studien) erfordern. All dies manuell zu tun, ist nicht nur langsam, sondern auch mühsam, da Sie ständig Objekte aus dem Chart hinzufügen und entfernen müssen. Andernfalls kann die Tabelle überfüllt und unübersichtlich werden, sodass es schwierig wird, die benötigten Informationen zu finden. Nutzen Sie also diese Methode als Grundlage für die Erstellung einer verfeinerten und auf Ihre Bedürfnisse zugeschnittenen Lösung. Es besteht kein Grund zur Eile, denn es wird sich später noch eine Gelegenheit ergeben, sie zu verbessern.

Das Einzige, was Sie tun müssen, ist sicherzustellen, dass der Text, wenn Sie ihn hinzufügen wollen (was sehr wahrscheinlich ist), das letzte Objekt in der Erstellungssequenz ist. Dies ist aus dem obigen Code ersichtlich, in dem der Text, der die Informationen anzeigt, das zuletzt erstellte Objekt ist. Dadurch wird verhindert, dass es von anderen Objekten verdeckt wird. In der Regel werden zunächst mehrere Objekte erstellt, und eines davon kann ein wirklich wichtiges Objekt verbergen. Denken Sie daran: Das wichtigste Element, an dem Sie am meisten interessiert sind, sollte immer das letzte in der Erstellungswarteschlange sein.

Lassen Sie sich nicht von der Tatsache verwirren, dass die Farben des Objekts anfangs auf clrNONE gesetzt sind, da sich diese Farben im Laufe der Analyse ändern werden. Wenn die vorherige Methode für die Erstellung der Analyse verantwortlich ist, führt die nächste Methode diese Analyse auf dem Chart aus.

void ExecuteStudy(const double memPrice)
{
   if (CheckClick(eClickLeft))
   {
      ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectMove(GetInfoTerminal().ID, def_NameObjectBitMp, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectMove(GetInfoTerminal().ID, def_NameObjectText, 0, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_COLOR, (memPrice > m_Info.Data.Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
      ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_ANCHOR, (memPrice > m_Info.Data.Position.Price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
      ObjectSetString(GetInfoTerminal().ID, def_NameObjectText, OBJPROP_TEXT, StringFormat("%." + (string)GetInfoTerminal().nDigits + "f ", m_Info.Data.Position.Price - memPrice));
   } else {
      m_Info.Study equal eStudyNull;
      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
      ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
   }
   m_Info.Data.ButtonStatus equal eKeyNull;
}

Eine Analyse des Charts mag auf den ersten Blick sinnlos erscheinen, vor allem, wenn man den Code isoliert betrachtet. Wenn Sie jedoch den Interaktionscode studieren, wird sein Zweck viel klarer und verständlicher. Versuchen wir nun zu verstehen, was hier vor sich geht. Im Gegensatz zum Code für die Erstellung von Forschungsobjekten enthält dieser Abschnitt einige interessante Elemente. Im Wesentlichen unterscheiden wir zwei Hauptteile des Codes. In der ersten prüfen wir die Bedingung - und auch ohne Programmierkenntnisse können Sie verstehen, was genau geprüft wird, denn bei der High-Level-Programmierung ist der Code ähnlich wie natürliche Sprache. Im zweiten Teil schließen wir die Analyse ab. Beide Teile sind sehr einfach und können schnell verstanden werden. In dieser Phase verschieben wir Objekte auf dem Bildschirm, um das Analyseintervall anzugeben. In den nächsten Zeilen ändern wir die Farben der Objekte, um anzuzeigen, ob es sich um eine Aufwärts- oder Abwärtsbewegung handelt, obwohl dies normalerweise offensichtlich ist. Es kann jedoch Situationen geben, z. B. bei der Verwendung einer Kurve, in denen es schwierig ist, durch einfache Beobachtung festzustellen, ob die Werte positiv oder negativ sind. Es ist wichtig, daran zu denken, dass die Analyse auf der Grundlage verschiedener Kriterien auf unterschiedliche Weise durchgeführt werden kann. Der vielleicht wichtigste Teil ist die Zeile, in der wir Werte präsentieren, die auf einer Berechnung oder Analyse basieren. Hier haben wir die Freiheit, viele verschiedene Informationen auf unterschiedliche Weise zu präsentieren, je nach Fall.

Was wir wirklich brauchen, ist eine Methode, um die Forschungspräsentation ordnungsgemäß zu vervollständigen, und genau das tut der zweite Teil des Codes. Obwohl dieser Teil auf den ersten Blick nicht besonders erwähnenswert erscheint, gibt es einen Aspekt, der besondere Aufmerksamkeit verdient: die Verwendung der Funktion ObjectsDeleteAll. Warum ist dieser Moment wichtig und erfordert Aufmerksamkeit? Die Antwort liegt in der Klasse C_Terminal. Der Konstruktor der Klasse C_Terminal hat die folgende Zeile:

ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true);

Diese Zeile teilt der Plattform mit, dass jedes Mal, wenn ein Objekt aus dem Chart entfernt wird, ein Ereignis erzeugt werden soll, das angibt, welches Objekt entfernt wurde. Mit der Funktion ObjectsDeleteAll löschen wir alle in der Analyse verwendeten Elemente oder Objekte. Dies bewirkt, dass MetaTrader 5 für jedes vom Chart gelöschte Objekt ein Ereignis erzeugt. Die Plattform wird genau das tun, und es liegt an unserem Code zu entscheiden, ob diese Objekte erneut erstellt werden oder nicht. Das Problem tritt auf, wenn die Objekte nicht gelöscht werden (weil der Code sie neu anlegt) oder sie ohne die entsprechende Benachrichtigung des Codes gelöscht werden. In diesem Fall wird die Eigenschaft CHART_EVENT_OBJECT_DELETE auf false gesetzt. Obwohl dies anfangs nicht der Fall ist, kann es bei der Erweiterung des Codes vorkommen, dass diese Eigenschaft versehentlich geändert wird und wir vergessen, sie wieder zu aktivieren. Infolgedessen erzeugt die Plattform kein Ereignis, das unseren Code darüber informiert, dass Objekte aus dem Chart entfernt wurden, was zu Ungenauigkeiten und Fehlern bei der Verwaltung von Objekten führen kann.

Schauen wir uns nun den Konstruktor der Klasse C_Mouse an.

C_Mouse(color corH, color corP, color corN)
   :C_Terminal()
{
   m_Info.corLineH  = corH;
   m_Info.corTrendP = corP;
   m_Info.corTrendN = corN;
   m_Info.Study = eStudyNull;
   m_Mem.CrossHair  = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
   ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
   CreateLineH();
}

An dieser Stelle rufen wir explizit den Konstruktor der Klasse C_Terminal auf. Es ist wichtig, dass dieser Konstruktor vor jeder anderen Ausführung innerhalb der C_Mouse-Klasse aufgerufen wird, um sicherzustellen, dass die erforderlichen Werte der C_Terminal-Klasse bereits richtig initialisiert sind. Nach dieser Initialisierung konfigurieren wir bestimmte Aspekte und speichern den Zustand anderer. Achten Sie auf die beiden Zeilen, in denen wir der Plattform unseren Wunsch mitteilen, Mausereignisse zu erhalten, und unsere Absicht, die von der Plattform angebotenen Standardanalysetools nicht zu nutzen. Wenn diese Definitionen angewandt werden, wird die Plattform unsere Anforderungen erfüllen, indem sie Mausereignisse auf Anfrage meldet. Wenn wir hingegen versuchen, Analysetools mit der Maus zu verwenden, müssen wir die Mittel bereitstellen, um solche Analysen mit unserem Code durchzuführen.

Der nächste Code ist der Destruktor der Klasse. Es ist sehr wichtig, einen Destruktor zu implementieren, damit wir die ursprüngliche Funktionalität der Plattform wiederherstellen können, wenn wir mit der Klasse C_Mouse fertig sind.

~C_Mouse()
{
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, 0, false);
   ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false);
   ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
}

Diese Frage mag zwar kontraintuitiv erscheinen, ist aber wichtig. Der Grund für die Aufnahme dieser speziellen Zeile ist, dass die Plattform beim Versuch, von der Klasse C_Mouse erstellte Objekte, insbesondere die Preislinie, zu löschen, ein Ereignis erzeugt, das uns darüber informiert, dass das Objekt aus dem Chart gelöscht wurde. Unser Code wird dann versuchen, dieses Objekt an das Chart zurückzugeben, auch wenn es gerade gelöscht wird. Um zu verhindern, dass die Plattform ein solches Ereignis erzeugt, müssen wir klar angeben, dass wir dies nicht wünschen. Man könnte sich fragen: „Aber würde die Klasse C_Terminal dies nicht übernehmen, indem sie uns mitteilt, dass wir keine Ereignisse mehr empfangen wollen, die sich auf Objekte beziehen, die aus dem Chart entfernt werden?“ Ja, die Klasse C_Terminal würde dies tun, aber da wir noch einige der Daten in der Klasse C_Terminal benötigen, erlauben wir dem Compiler, implizit einen Aufruf an den Destruktor der Klasse C_Terminal zu machen, der erst erfolgt, nachdem die letzte Zeile des Destruktors der Klasse C_Mouse ausgeführt wurde. Ohne Hinzufügung einer eigenen Codezeile wird die Plattform das Ereignis weiterhin generieren, sodass die Preislinie, selbst wenn sie zunächst entfernt wird, wieder eingefügt werden kann, bevor der Code vollständig abgeschlossen ist. Die restlichen Zeilen des Destruktors sind einfacher, da wir das Chart lediglich in seinen ursprünglichen Zustand zurückversetzen.

Wir kommen nun zu den letzten Funktionen, die in diesem Artikel behandelt werden.

inline bool CheckClick(const eBtnMouse value) { return (m_Info.Data.ButtonStatus & value) == value; }

In dieser Zeile überprüfen wir anhand der Enumeration, die wir für die von der Maus empfangenen Ereignisse definiert haben, ob der von der Plattform bereitgestellte Wert mit dem übereinstimmt, den ein bestimmter Punkt im Code erwartet. Wurde die Übereinstimmung bestätigt, gibt die Funktion den Wert true zurück, andernfalls den Wert false. Obwohl dies im Moment trivial erscheinen mag, wird eine solche Überprüfung sehr nützlich sein, wenn wir beginnen, intensiver mit dem System zu interagieren. Es gibt einen speziellen Trick, um diese Funktion zu deklarieren, der ihre Verwendung erleichtert, aber da dies an dieser Stelle nicht wichtig ist, werde ich nicht ins Detail gehen.

Die nächste Funktion ist genauso einfach wie die vorherige und folgt dem gleichen Prinzip wie die Funktion GetInfoTerminal der Klasse C_Terminal.

inline const st_Mouse GetInfoMouse(void) const { return m_Info.Data; }

Das Ziel ist die Rückgabe der Informationen, die in der Variablen enthalten sind, die die Mausdatenstruktur speichert, wobei sichergestellt wird, dass diese Daten nicht ohne ausdrückliche Genehmigung der Klasse C_Mouse geändert werden können. Da wir bereits über diese Methode gesprochen haben, sehe ich keine Notwendigkeit, sie noch einmal zu erklären. Im Wesentlichen funktionieren beide auf ähnliche Weise.

Schließlich kommen wir zum Höhepunkt des Codes der Klasse von C_Mouse. Ich halte es jedoch für wichtig, die letzte Funktion, die in der Klasse C_Mouse vorhanden ist, in unserem nächsten Artikel zu diskutieren. Dann werden wir den Grund dafür erklären.


Schlussfolgerung

Wir sind dabei, etwas sehr Vielversprechendes zu schaffen, auch wenn es noch lange nicht fertig ist. Wir werden im nächsten Artikel fortfahren.


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

Experimente mit neuronalen Netzen (Teil 7): Übergabe von Indikatoren Experimente mit neuronalen Netzen (Teil 7): Übergabe von Indikatoren
Beispiele für die Übergabe von Indikatoren an ein Perzeptron. Der Artikel beschreibt allgemeine Konzepte und stellt den einfachsten fertigen Expert Advisor vor, gefolgt von den Ergebnissen seiner Optimierung und seines Vorwärtstests.
Von der Saisonalität des Devisenmarktes profitieren Von der Saisonalität des Devisenmarktes profitieren
Wir sind alle mit dem Konzept der Saisonalität vertraut, z. B. sind wir alle an steigende Preise für frisches Gemüse im Winter oder an steigende Kraftstoffpreise bei strengem Frost gewöhnt, aber nur wenige Menschen wissen, dass es auf dem Devisenmarkt ähnliche Muster gibt.
Entwicklung eines Replay System (Teil 28): Expert Advisor Projekt — Die Klasse C_Mouse (II) Entwicklung eines Replay System (Teil 28): Expert Advisor Projekt — Die Klasse C_Mouse (II)
Als man begann, die ersten rechenfähigen Systeme zu entwickeln, war für alles die Mitwirkung von Ingenieuren erforderlich, die das Projekt sehr gut kennen mussten. Wir sprechen von den Anfängen der Computertechnologie, einer Zeit, in der es noch nicht einmal Terminals zum Programmieren gab. Im Laufe der Entwicklung, als immer mehr Menschen daran interessiert waren, etwas zu erschaffen, entstanden neue Ideen und Wege der Programmierung, die das frühere Wechseln der Steckverbindungen ersetzten. Zu diesem Zeitpunkt erschienen die ersten Terminals.
Entwicklung eines Replay System (Teil 26): Expert Advisor Projekt — die Klasse C_Terminal Entwicklung eines Replay System (Teil 26): Expert Advisor Projekt — die Klasse C_Terminal
Wir können nun mit der Erstellung eines Expert Advisors für die Verwendung im Wiedergabe-/Simulationssystem beginnen. Wir brauchen jedoch eine Verbesserung und keine zufällige Lösung. Trotzdem sollten wir uns von der anfänglichen Komplexität nicht einschüchtern lassen. Es ist wichtig, irgendwo anzufangen, sonst enden wir damit, dass wir über die Schwierigkeit einer Aufgabe grübeln, ohne überhaupt zu versuchen, sie zu bewältigen. Genau darum geht es beim Programmieren: Hindernisse durch Lernen, Testen und umfassende Forschung zu überwinden.