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

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

MetaTrader 5Tester | 20 März 2024, 12:06
102 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay System (Teil 28): Expert Advisor Projekt — Die Klasse C_Mouse (II)“, haben wir uns angesehen, wie man lesbaren Code erstellt. Dieses Modell ist zwar recht interessant, um ein Programm verständlicher zu machen, aber ich denke, Sie werden feststellen, dass die Programmierung mit diesem Ansatz länger dauern kann. Und das liegt nicht daran, dass die Programmierung unübersichtlich wird. Ganz im Gegenteil. Die Schwierigkeit besteht darin, dass diese Methode, ein Programm besser lesbar zu machen, ihre Grenzen hat. Eine solche Einschränkung ist die Syntax jeder Programmiersprache. Obwohl die Syntax auf ein bestimmtes Format und eine bestimmte Struktur ausgelegt ist, schränkt uns die Verwendung von Definitionen, obwohl sie hilfreich ist, auf andere Weise ein. Ich denke jedoch, dass es angebracht ist, dies aus meiner Sicht in echtem Code zu zeigen.

Wir werden die ursprüngliche Syntax beibehalten, aber wenn Sie den Code so erforschen wollen, wie wir ihn zeigen, können Sie alle Definitionen erstellen, die Sie für notwendig halten, und den Code so anpassen, dass er für Sie leichter zu verstehen ist. Auf diese Weise können Sie einige sehr interessante Techniken erlernen. So habe ich gelernt, in anderen Sprachen zu programmieren. Dies ist ein arbeitsintensiver, aber nützlicher Prozess, da es Methoden und sogar Algorithmen gibt, die in einer Sprache leichter zu implementieren sind als in einer anderen. Wenn Sie jedoch den Code in der Originalsprache lesen können, dann können Sie Dinge tun, die andere nicht können. Man kann es als Übersetzungsarbeit betrachten, und Sie sind ein Dolmetscher zwischen zwei verschiedenen Welten. Um erfolgreich zu sein, muss unser Verständnis viel umfassender sein als das der Person, die immer mit denselben Zeichen, Symbolen und Begriffen kommuniziert.

Erweitern Sie Ihren Horizont, schauen Sie über den Tellerrand, und das ganze Universum wird sich Ihnen öffnen.

Aber kommen wir zu dem, was uns zu diesem Artikel geführt hat. Hier werden wir uns ansehen, wie man, ohne die Klasse zu ändern und ohne das Vererbungssystem zu nutzen, die Fähigkeiten des Systems auf kontrollierte, sichere und zuverlässige Weise erweitern kann, unabhängig von den Fähigkeiten. Die Aufgabe mag auf den ersten Blick einfach erscheinen, aber sie führt zu einem tieferen Verständnis der Funktionsweise, das weit über das hinausgeht, was wir erhalten, wenn wir jedes Mal die gleiche Methode anwenden.

Im heutigen Artikel werden wir uns eine Möglichkeit ansehen, das System zur Analyse von Finanzinstrumenten zu erweitern. Wir werden die Klasse C_Mouse zusammen mit dem, was sie von der Klasse C_Terminal erbt, verwenden, um eine weitere Analyse-Nische zu schaffen. Wir werden dies jedoch auf eine interessante Art und Weise tun: Wir werden eine neue Klasse erstellen, die den Inhalt der Klasse C_Mouse verwendet, ohne jedoch direkt von ihr zu erben. Je nach Zielsetzung kann diese neue Klasse dann in den endgültigen Code aufgenommen werden oder auch nicht. Aber unabhängig davon werden wir lernen, wie wir unser eigenes Trainingsmodell erstellen können, ohne die Integrität des zuvor erstellten und getesteten Codes zu verletzen. Dies ist der eigentliche Zweck dieses Artikels.


Die Weichen für die Expansion stellen

Bevor wir mit der Programmierung der Klasse beginnen, die nicht von der Klasse C_Mouse abgeleitet wird, sondern deren Funktionalität erweitern bzw. modifizieren wird, müssen wir einige Details der ursprünglichen Klasse C_Mouse anpassen. Nicht, weil es irgendein Problem gibt, sondern weil wir einige Ergänzungen und eine kleine Änderung vornehmen müssen, die jede Art von Erweiterung erleichtern wird. Auf diese Weise wird jede Änderung der Klassenfunktionalität recht praktisch sein, denn wenn etwas schief geht, können wir ohne Probleme zur ursprünglichen Klasse zurückkehren. Die Änderungen werden gering und einfach, aber dennoch wichtig sein. Zunächst fügen wir eine neue Variable in den Code der Klasse C_Mouse ein.

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, 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;
         bool    ExecStudy;
      };

Diese Variable ermöglicht es uns, das Verhalten der Klasse C_Mouse zu erweitern oder zu ändern, ohne dass Vererbung oder Polymorphismus erforderlich sind. Obwohl dies die gängigsten Methoden sind, werden wir einen anderen Ansatz wählen. Die Methode, die wir demonstrieren werden, ermöglicht es uns, diese Strategie in jeder Klasse anzuwenden. Das Wichtigste dabei ist, dass dies alles geschieht, ohne auch nur eine Zeile des Quellcodes der Klasse zu ändern. Bevor wir die erwähnten Änderungen vornehmen, die es uns ermöglichen sollen, die Fähigkeiten zu erweitern, müssen wir eine einfache Codezeile in die Klasse C_Mouse einfügen. Etwas Einfaches.

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);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

Diese besondere Zeile stellt sicher, dass die Preislinie der Maus, selbst wenn sie sich als Vordergrundlinie erweist, kein Ereignis erhält, wenn sie ein Objekt überschneidet, auf das wir zu klicken versuchen. Die Preislinie erfasst nur dann Klickereignisse, wenn sich keine Objekte im Fokus des Mauszeigers befinden. Es ist wichtig zu beachten, dass das Hinzufügen dieser Zeile die Erstellung von Analysen nicht blockiert, selbst wenn das Objekt angeklickt wird, da diese Codezeile es nicht ermöglicht, dass das Objekt direkt einen Klick erhält, aber nicht verhindert, dass das Ereignis CHARTEVENT_MOUSE_MOVE aktiviert und von der Klasse C_Mouse erfasst wird.

Wichtiger Hinweis: Früher hatte ich gerade wegen des Fehlens dieser Code-Zeile gewisse Probleme. In dem Artikel „Charts interessanter machen: Hinzufügen eines Hintergrunds“ gibt es einen Fehler, den ich zu diesem Zeitpunkt nicht lösen konnte. Egal wie sehr ich mich bemühte, das Problem blieb bestehen. Der Fehler, der den Zugriff auf die im Chart vorhandenen Objekte verhinderte, konnte behoben werden, indem die hervorgehobene Zeile einfach zu dem Objekt hinzugefügt wurde, das zum Einfügen des Hintergrunds in das Chart verwendet wurde. Ich hätte diesen Rat schon früher geben können, aber ich wollte diejenigen, die die Artikel tatsächlich lesen, irgendwie belohnen. Jetzt wissen Sie, wie Sie das in diesem Artikel beschriebene Problem lösen können.

Als Nächstes werden wir kleine Änderungen an der folgenden Funktion vornehmen:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
               if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price);
                  m_Info.Study = eStudyExecute;
               }
               if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
               m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
               break;
            case CHARTEVENT_OBJECT_DELETE:
               if (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

Diese Änderungen kommen nicht speziell der Klasse C_Mouse zugute, sondern dem gesamten Programm, das auf der Grundlage der Klasse C_Mouse erstellt wird. Es kann sein, dass wir keinen Unterschied im Code zwischen dem vorherigen und dem aktuellen Artikel bemerken, was auf die Subtilität und Spezifität der Änderung zurückzuführen ist. Am Code wird eigentlich nichts geändert. Diese Änderungen bringen verschiedene Vorteile in Bezug auf die Nutzerfreundlichkeit und die Anpassungsmöglichkeiten mit sich. Es ist vielleicht nicht leicht, den Unterschied zu bemerken, aber die drei neuen Zeilen, die wir dem Code hinzugefügt haben, sind sehr hilfreich. Schauen wir uns an, was jeder von ihnen tut:

  1. Diese Zeile passt den Zeitwert so an, dass wir jedes Objekt auf dem Chart nicht nur mit den Bildschirmkoordinaten (X und Y), sondern auch mit den Koordinaten des Finanzinstruments (Preis und Zeit) verwenden können. Ich habe sehr lange nach einer Antwort auf diese Frage gesucht. Bedenken Sie auch, dass die Arbeit mit Asset-Koordinaten viel interessanter ist als die Arbeit mit Bildschirmkoordinaten. Der Grad der Freiheit, den wir dadurch erhalten, ist offensichtlich. Sie können auch sehen, dass wir den Aufruf machen, und aus praktischen Gründen ist es in der Klasse C_Terminal.
  2. Der zusätzliche Aufruf ist ChartTimePriceToXY, um die Preiskoordinaten in Bildschirmkoordinaten umzuwandeln.
  3. Und der letzte Punkt war eben dieser. Wir geben an, ob sich die Klasse C_Mouse im Lernmodus befindet. Um Verwirrung zu vermeiden, achten Sie auf die Syntax.

Dies sind alle Änderungen, die an der Klasse C_Mouse vorgenommen wurden. Wie bereits erwähnt, enthält die Klasse C_Terminal nun eine neue Funktion. Schauen wir uns diese Funktion an, um zu verstehen, was sie ist. Die hinzugefügte Funktion ist unten dargestellt:

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

Wenn Ihnen diese Funktion sehr seltsam und verwirrend vorkommt, machen Sie sich keine Sorgen. Auch wenn es auf den ersten Blick verwirrend und sinnlos erscheinen mag, bietet es doch etwas Wunderbares und Interessantes. Um die Magie dieser Funktion zu verstehen, müssen wir eine Sache über die Typkonvertierung sowie einige zusätzliche Details verstehen. Schauen wir uns zunächst die Typkonvertierung an.

Wenn diese Funktion aufgerufen wird, erhält sie Datum und Uhrzeit als Parameter. Es ist wichtig, diesen Wert nicht nur als Datum und Uhrzeit zu betrachten, sondern als Wert des Typs ulong, denn das ist es, was er wirklich ist: ein 8-Byte-Wert. Das ist der erste Punkt. Der zweite Grund ist folgender: Da datetime ein ulong-Wert ist, werden Zeit- und Datumsangaben in einer Variablen auf eine ganz bestimmte Weise komprimiert. Was uns interessiert, sind die niederwertigen Bits (least significant bits, LSB), wobei die Werte von Sekunde, Minute, Stunde, Tag, Monat und Jahr vom niederwertigen zu höherwertigen Bit geordnet sind.

Beachten Sie, dass nSeconds den Wert der im Chart verwendeten Periode in Sekunden enthält. Dies wird durch die Funktion PeriodSeconds bestimmt, die diese Information liefert. Die Variable dt enthält nun den Erstellungswert des letzten im Chart vorhandenen Balkens. Diese Tatsache ist sehr wichtig, denn wenn der Wert dt kleiner ist als der Wert des Aufrufs, zeigt dies an, an welchem Punkt der Zeit wir uns befinden, in diesem Fall in der Zukunft. Die Position der Zeit im Verhältnis zu den Bildschirmkoordinaten (X und Y) gibt also an, wo der zukünftige Balken erstellt wird oder wurde. Im Moment ist es nicht möglich, mit Hilfe der iTime-Funktion herauszufinden, wo er liegen wird, da die Karte zu diesem Zeitpunkt noch nicht erstellt wurde. Aber selbst dann müssen wir wissen, wo er tatsächlich liegen wird, vor allem, wenn wir eine Chartanalyse für die Zukunft durchführen.

Um die Bildschirmkoordinaten (X und Y) zu ermitteln, nutzen wir die Tatsache, dass datetime ein ulong-Wert ist. Teilt man diesen Wert durch die Anzahl der Sekunden nSeconds, erhält man einen „double“ Wert. Jetzt kommt ein wichtiger Punkt: Wenn wir diesen Bruchteil mit nSekunden multiplizieren, erhalten wir den ursprünglichen Wert. Wenn wir zum Beispiel 10 durch 3 teilen und das Ergebnis der Division mit 3 multiplizieren, erhalten wir wieder 10. Wenn wir jedoch eine Typkonvertierung durchführen (und das ist der Schlüssel), konvertieren wir den double-Wert in ulong oder besser noch in einen Wert vom Typ datetime. Hier gibt es keinen Bruchteil. Multipliziert man also den Wert mit nSekunden, erhält man den bereits korrigierten Zukunftswert. Dies ist der interessanteste Teil. Es gibt jedoch ein Problem.

Dieses Problem tritt auf, wenn wir in die Vergangenheit blicken, insbesondere bei kontinuierlichen Reihen, d. h. wenn keine Lücken vorhanden sind. Dieser Ansatz eignet sich nicht für die Analyse der Vergangenheit, vor allem nicht für Vermögenswerte, die solche Lücken aufweisen, was typisch für Vermögenswerte ist, die während bestimmter Stunden oder Tage gehandelt werden. Dies bezieht sich auf Aktienmarktinstrumente, bei denen sich die Balken nur während eines bestimmten Zeitfensters bilden und der Markt außerhalb dieses Zeitraums geschlossen wird. Um diese Situation zu bewältigen, verwenden wir einen etwas anderen Aufbau für den Versuch zu bestimmen, in welcher Spalte sich der angegebene Wert befindet. Dazu wird die Anzahl der Balken zwischen dem aktuellen Balken und dem Balken zu einem bestimmten Zeitpunkt berechnet, was einen Wert ergibt, der als Offset verwendet werden kann, um den genauen Zeitpunkt zu erfassen. Auf diese Weise können wir auch in der Vergangenheit Anpassungen vornehmen, unabhängig davon, was passiert ist.

Sie denken vielleicht, dass diese Funktion völlig unnötig ist. Warum sollte man sie entwickeln? Mit dieser Funktion können Sie jedoch Symbolkoordinaten (Preis und Zeit) in Bildschirmkoordinaten (X und Y) umrechnen. Daher können wir jede Art von grafischen Objekten verwenden, die nicht auf Bildschirmkoordinaten oder Ressourcenobjekte beschränkt sind. Wir werden in der Lage sein, eine Art von Koordinaten in eine andere umzuwandeln, und dafür werden wir Aufrufe verwenden: ChartXYToTimePrice (zur Umwandlung von Bildschirmkoordinaten in Asset-Koordinaten) und ChartTimePriceToXY (zur Umwandlung von Asset-Koordinaten in Bildschirmkoordinaten). Bei einigen Arten von Analysen benötigen wir jedoch möglichst genaue Informationen. Wenn ein bestimmter Balken als Indikator für etwas verwendet werden soll, wird diese Umwandlung notwendig. Außerdem erhalten wir dadurch sehr interessante Informationen, auf die wir später eingehen werden.


Erstellen Sie die Klasse C_Studys

Nachdem wir die Klasse C_Mouse verbessert haben, können wir uns auf die Erstellung einer Klasse konzentrieren, die einen völlig neuen Rahmen für unsere Analyse schaffen soll. Wir werden weder Vererbung noch Polymorphismus verwenden, um diese neue Klasse zu erstellen. Stattdessen werden wir die Preislinie ändern, oder besser gesagt, neue Objekte hinzufügen. Genau das werden wir in diesem Artikel tun. In der nächsten Ausgabe werden wir uns ansehen, wie man die Analyse ändern kann. Aber wir werden dies alles tun, ohne den Code der Klasse C_Mouse zu ändern. Nun, eigentlich wäre es einfacher, dies durch Vererbung oder Polymorphismus zu erreichen. Es gibt andere Methoden, mit denen dasselbe Ergebnis erzielt werden kann, weil sie Flexibilität bieten, ohne dass es zu erheblichen Störungen kommt, wenn der neue Code Mängel aufweist.

Dieser Ansatz ermöglicht es uns, problematischen Code zu entfernen, Fehler zu beheben und ihn wieder einzuführen, ohne dass Änderungen am zuvor getesteten Code erforderlich sind. Denken Sie daran: Fehlerkorrekturen können oft dazu führen, dass der Code mit der bestehenden Klassenstruktur inkompatibel ist, was die Verwendung von Vererbung oder Polymorphismus zur Ermöglichung neuer Ergänzungen erschwert. Daher ist die Beherrschung dieser alternativen Techniken wichtig, vor allem, wenn wir neue Funktionen in ein bereits fertiggestelltes und getestetes Programm implementieren wollen, ohne den bestehenden Code wesentlich umstrukturieren zu müssen.

Der erste Schritt besteht darin, eine neue Datei zu erstellen, obwohl es auch möglich ist, mehrere verschiedene Dateien mit einer eigenen Hierarchie zwischen den beteiligten Klassen zu erstellen. Dies stellt kein Problem dar, da diese Datei und die Dateien die übergeordnete Klassenhierarchie nicht integrieren werden. Auf diese Weise können wir eine unabhängige Hierarchie aufbauen, die je nach Bedarf auf verschiedene Weise geändert oder verbessert werden kann. Diese Flexibilität ergibt sich aus der Tatsache, dass es nicht direkt mit der Hauptentwicklungshierarchie verbunden ist und sich fast wie ein eigenständiges Projekt verhält.

Wir beginnen mit einem einfacheren System und werden in Zukunft komplexere Ansätze untersuchen. Die Datei sieht folgendermaßen aus:

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

Nach dieser Einführung in den Code wissen wir, dass der Prozess intensiv sein wird. Beachten Sie die Einbeziehung von zwei Header-Dateien, die sich an verschiedenen Stellen in dieser Datei befinden. Dieser Teil wurde bereits zuvor erörtert. Etwas später werden wir einige der Objekte erwähnen, die wir verwenden werden. Dies ist wichtig, um Verwechslungen beim weiteren Zugriff auf die gewünschten Objekte zu vermeiden. Wir haben auch eine Art Alias definiert, um die Codierung zu erleichtern, da wir etwas verwenden werden, das Zeigern sehr ähnlich ist, eine der mächtigsten und gleichzeitig riskantesten verfügbaren Ressourcen. Aber aufgrund der Art und Weise, wie wir programmieren wollen, wird das Risiko, das mit dieser Ressource verbunden ist, recht überschaubar sein, was es uns ermöglichen wird, sie auf sehr interessante Weise zu nutzen.

Diese Art der Definition (Alias) wird häufig verwendet, wenn wir auf bestimmte Dinge zugreifen wollen, aber nicht riskieren wollen, beim Schreiben von Code etwas falsch einzutippen. Dies ist immer eine sehr interessante Quelle.

Dann folgt der folgende Klassencode:

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

In diesem Stadium konzentrieren wir uns ausschließlich auf die privaten Variablen der Klasse. Sehen Sie sich einen davon an, der als eine Art Zeiger auf die Klasse C_Mouse fungiert. Zeiger sind eines der mächtigsten Hilfsmittel in der Programmierung, aber sie erfordern besondere Aufmerksamkeit wegen der Probleme, die bei ihrer Verwendung auftreten können. Daher ist es sehr wichtig, Zeiger sorgfältig zu verwenden, auch wenn sie in MQL5 nicht die gleichen Eigenschaften wie in C/C++ haben. Auf jeden Fall sollte man bei ihrer Verwendung vorsichtig sein. Unterschätzen Sie niemals Zeiger. Der Rest des Codes ist noch nichts Besonderes.

Die erste der Klassenfunktionen wird im Folgenden vorgestellt:

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

Es handelt sich um eine recht einfache Funktion, die die verbleibende Zeit bis zum Erscheinen eines neuen Balkens berechnet. Die Berechnung selbst erfolgt nur zu diesem bestimmten Zeitpunkt, aber jedes Mal, wenn der Test feststellt, dass die Zeitgrenze eines Balkens erreicht ist, werden die notwendigen Ablesungen und Anpassungen vorgenommen, um die Berechnung auf den nächsten Balken zu verschieben. Dieser Aufruf der iTime-Funktion erfolgt also nur einmal für jeden erstellten Balken und nicht bei jeder Interaktion oder jedem Methodenaufruf. Die nächste Funktion erstellt Objekte auf die übliche Weise.

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

Wir durchlaufen im Wesentlichen denselben Prozess wie die Funktion in C_Mouse, was darauf hindeutet, dass wir bald erwägen könnten, die beiden Funktionen zu einer noch allgemeineren Funktion zu kombinieren. Unmittelbar danach stellen wir ein Verfahren vor, dessen Erprobung für einige von Interesse sein könnte.

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

Mit dieser Methode können Sie die Größe eines Objekts in Abhängigkeit von der Größe und der Art der verwendeten Schrift sowie der Länge des Textes, der innerhalb des Objekts gedruckt werden soll, anpassen. Dazu wird die Funktion TextGetSize verwendet, die auf der Grundlage der bereitgestellten Informationen eine Schätzung der Größe des Textes liefert. Damit der Text innerhalb des Objekts besser aussieht, wird er leicht vergrößert. Dadurch entsteht ein kleiner Abstand zwischen dem Text und den Objektgrenzen.

Als Nächstes haben wir eine Funktion, die Informationen in unserem Chart anzeigt.

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

Dies ist genau erzeugt eine Art von Studie, die immer vorhanden sein wird und wird die Preislinie begleiten. Auf diese Weise werden wir in der Lage sein, bestimmte Dinge leicht zu verstehen. Die Art der Informationen und was angezeigt wird, hängt davon ab, was Sie brauchen. Zur Veranschaulichung werden wir hier drei Arten von Informationen verwenden. Schauen wir also, was angeboten wird.

Je nachdem, wie das Programm mit der Plattform und dem Handelsserver interagiert, können wir unterschiedliche Informationen erhalten. Wir erhalten jedoch die folgenden Informationen:

  • Verbleibende Zeit bis zum Erscheinen des nächsten Balkens im Chart;
  • Information, dass der Markt geschlossen ist;
  • Information, dass es sich um eine Wiederholung handelt;
  • Information, dass der Vermögenswert versteigert wird;
  • Und (in extrem seltenen Fällen) eine Fehlermeldung.

Bitte beachten Sie, dass das, was wir tun, nicht ungewöhnlich ist. Dies dient nur zur Veranschaulichung der Arbeitstechnik, die wir verwenden werden. Als Nächstes kommt der Destruktor.

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

Der Zweck des obigen Codes ist es, der Plattform mitzuteilen, dass wir die von der Klasse erstellten Elemente entfernen wollen. Beachten Sie, dass es uns egal ist, ob dies Ereignisse in der Plattform auslöst, da wir nicht beabsichtigen, solche Objekte neu zu erstellen, wenn sie gelöscht werden.

Hier kommt die nächste Funktion:

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                         break;
         case eAuction   : m_Info.szInfo = "Auction";                                break;
         case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break;
         case eInReplay  : m_Info.szInfo = "In Replay";                              break;
         default         : m_Info.szInfo = "ERROR";
      }
      Draw();
   }

Das Wichtigste an dieser Funktion ist, dass sie nicht isoliert funktioniert. Wir haben eine weitere Funktion mit einem sehr ähnlichen Namen, die weiter unten vorgestellt wird:

void Update(const MqlBookInfo &book[])
   {
      m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
      for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
         if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
      this.Update();
   }

Warum haben wir zwei Funktionen namens Update? Ist es möglich? Ja, wir können Funktionen mit demselben Namen deklarieren. Dieses Phänomen wird als Überlastung bezeichnet. Obwohl die Namen identisch sind, sind die Namen der beiden Funktionen für den Compiler unterschiedlich. Der Grund dafür sind die Parameter. Auf diese Weise können Sie Funktionen und Prozeduren überladen, aber die einzige Regel ist, dass die Parameter unterschiedlich sein müssen. Wir dürfen nicht vergessen, dass wir in der zweiten Funktion die erste Funktion mit einer Methode aufrufen, deren Parameter nicht erforderlich sind. Diese Praxis ist üblich, wenn man Methoden programmiert, die überlastet werden, und etwas schaffen will, das die Fehlersuche erleichtert.

Nun kommen wir zu einem interessanten Punkt: Wie kann man herausfinden, ob ein Vermögenswert versteigert wird oder nicht? Die Preisangaben sind in der Regel im Auftragsbuch enthalten. Es kann jedoch vorkommen, dass das Auftragsbuch des Vermögenswerts einen dieser spezifischen Werte anzeigt, und wenn dies der Fall ist, bedeutet dies, dass der Vermögenswert versteigert wird. Achten Sie darauf, denn es könnte eine interessante Ressource sein, die Sie Ihrem automatisierten EA hinzufügen können. Ich habe dies bereits in einer anderen Artikelserie erwähnt: „Erstellen eines EA, der automatisch handelt (Teil 14): Automatisierung (VI)“. Ich bin jedoch nicht im Detail darauf eingegangen, wie man erkennt, ob ein Vermögenswert zur Versteigerung ansteht oder nicht. Unser Ziel war es nicht, alle Aspekte des Handelsprozesses mit einem automatisierten EA ausführlich zu behandeln.

Die folgende Funktion wird verwendet, um auf einige Plattformereignisse zu reagieren:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

Einfach, aber effektiv, weil die Objekte der Maus folgen müssen und die Mausanalyse von der Klasse C_Mouse übernommen wird. Auch hier ändern wir die Arbeit der Klasse C_Mouse, ohne Vererbung oder Polymorphismus zu verwenden. Die Aufgabe, die Positionierung der Maus anzupassen und zu korrigieren, liegt also in der Verantwortung der Klasse C_Mouse. Wir werden nur die Daten verwenden.

Wir haben fast den gesamten Code gesehen. Aber wir müssen uns ansehen, wo die Magie tatsächlich stattfindet. Beginnen wir mit dem EA-Code, der im Folgenden vollständig dargestellt wird:

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

Bitte beachten Sie, dass wir diesen EA mit der Möglichkeit ausstatten, sowohl mit realen als auch mit simulierten Vermögenswerten zu handeln und dabei dieselben Tools zu verwenden. Dies ist dank dieser Art der Überprüfung möglich. Derzeit und in diesem Stadium der Entwicklung werden wir unterschiedliche Informationen haben, je nachdem, was der EA im Chart erkennt.

Wir übergeben dem Klassenkonstruktor den Zeiger, der bei der Initialisierung der Klasse C_Mouse erzeugt wurde. Auf diese Weise muss die Klasse C_Studys nicht direkt von der Klasse C_Mouse abgeleitet werden, um deren Inhalt zu verwenden, wodurch die Notwendigkeit entfällt, C_Mouse innerhalb von C_Studys zu initialisieren. Diese Modellierungstechnik ist nützlich, wenn wir Informationen austauschen oder mit Elementen interagieren wollen, ohne auf Vererbung oder Polymorphismus zurückzugreifen. Wenn wir neue Funktionen der Klasse C_Studys, die in irgendeiner Weise mit der Klasse C_Mouse zusammenhängen, entfernen, anpassen oder erstellen müssen, kann dies einfach geschehen, ohne die Klasse C_Mouse ändern zu müssen.

Der große Vorteil dieses Modells ist die Möglichkeit der parallelen Entwicklung von Komponenten. Für den Fall, dass die Klasse C_Studys nicht Teil des endgültigen Projekts ist, entfernen Sie einfach den Code aus ihr. Diese C_Studys wird zu einer parallelen Klasse, unabhängig vom Hauptklassensystem des endgültigen Codes.

Analysieren wir nun den Code des Konstruktors, um zu verstehen, wie diese Informationsübertragung implementiert wird.

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

Wie Sie sehen können, hat dieser Code einige Eigenheiten, darunter einen etwas ungewöhnlichen Parameter: einen Zeiger auf eine Klasse. Dies zeigt, wie die Elemente zusammenkommen, um das System zu bilden, das wir entwerfen müssen. Es gibt jedoch eine weitere interessante Funktion, die auf den ersten Blick seltsam erscheinen mag: die Funktion TextSetFont. Es ist sehr wichtig, die Größe der Objekte an die Art der darzustellenden Informationen anzupassen. Beachten Sie, dass wir hier eine Faktorisierung durchführen. Warum wird eine negative Zahl verwendet? Um dies zu verdeutlichen, sollten wir uns die Erklärung in der Dokumentation ansehen:

Die Schriftgröße wird durch positive oder negative Werte bestimmt. Diese Tatsache bestimmt die Abhängigkeit der Textgröße von den Einstellungen des Betriebssystems (Größenskala).

  • Ist die Größe positiv, wird sie in physische Einheiten (Pixel) umgerechnet, wenn die logische Schriftart als physische Schriftart angezeigt wird. Die Größe entspricht der Höhe der Symbolzellen der verfügbaren Schriftarten. Es wird nicht empfohlen, Texte, die mit der Funktion TextOut() angezeigt werden, und Texte, die mit dem grafischen Objekt OBJ_LABEL ("Text label") angezeigt werden, gemeinsam zu verwenden.
  • Wenn die Größe negativ ist, wird angenommen, dass sie in Zehntel eines logischen Punktes festgelegt wird (der Wert -350 entspricht 35 logischen Punkten) und durch 10 geteilt wird. Der resultierende Wert wird in physikalische Einheiten des Geräts (Pixel) umgerechnet und entspricht dem absoluten Wert der Zeichenhöhe aus den verfügbaren Schriftarten. Multiplizieren Sie die in den Objekteigenschaften definierte Schriftgröße mit -10, damit die Bildschirmtextgröße der Objektgröße OBJ_LABEL entspricht.

 Aus diesem Grund wird die Faktorisierung auf diese Weise durchgeführt. Wenn wir diesen Parameter hier nicht setzen, haben wir Probleme bei der Verwendung der Funktion TextGetSize, die in der Objekterstellungsfunktion verwendet wird. Das liegt daran, dass die verwendete Schriftart oder ihre Abmessungen nicht genau mit dem übereinstimmen, was wir verwenden wollen.


Schlussfolgerung

Testen Sie unbedingt die beigefügte App. Es ist ratsam, Experimente sowohl im Replay-/Simulationsmodus als auch mit einem auf dem Markt laufenden Konto (DEMO oder REAL) durchzuführen,

um ein umfassendes Verständnis zu erlangen. Aber ich werde NICHT eine einzige Codezeile in der Hauptklasse ändern. Das verspreche ich.

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

Beigefügte Dateien |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
Entwicklung eines Replay System (Teil 30): Expert Advisor Projekt — Die Klasse C_Mouse (IV) Entwicklung eines Replay System (Teil 30): Expert Advisor Projekt — Die Klasse C_Mouse (IV)
Heute werden wir eine Technik lernen, die uns in verschiedenen Phasen unseres Berufslebens als Programmierer sehr helfen kann. Oft ist es nicht die Plattform selbst, die begrenzt ist, sondern das Wissen der Person, die über die Grenzen spricht. In diesem Artikel erfahren Sie, dass Sie mit gesundem Menschenverstand und Kreativität die MetaTrader 5-Plattform viel interessanter und vielseitiger gestalten können, ohne auf verrückte Programme oder ähnliches zurückgreifen zu müssen, und einfachen, aber sicheren und zuverlässigen Code erstellen können. Wir werden unsere Kreativität nutzen, um bestehenden Code zu ändern, ohne eine einzige Zeile des Quellcodes zu löschen oder hinzuzufügen.
Integration von ML-Modellen mit dem Strategy Tester (Schlussfolgerung): Implementierung eines Regressionsmodells für die Preisvorhersage Integration von ML-Modellen mit dem Strategy Tester (Schlussfolgerung): Implementierung eines Regressionsmodells für die Preisvorhersage
Dieser Artikel beschreibt die Implementierung eines Regressionsmodells auf der Grundlage eines Entscheidungsbaums. Das Modell soll die Preise von Finanzanlagen vorhersagen. Wir haben die Daten bereits aufbereitet, das Modell trainiert und evaluiert, sowie angepasst und optimiert. Es ist jedoch wichtig zu beachten, dass dieses Modell nur für Studienzwecke gedacht ist und nicht im realen Handel eingesetzt werden sollte.
Entwicklung eines Replay System (Teil 31): Expert Advisor Projekt — Die Klasse C_Mouse (V) Entwicklung eines Replay System (Teil 31): Expert Advisor Projekt — Die Klasse C_Mouse (V)
Wir brauchen einen Timer, der anzeigt, wie viel Zeit bis zum Ende der Wiedergabe/Simulation verbleibt. Dies mag auf den ersten Blick eine einfache und schnelle Lösung sein. Viele versuchen einfach, sich anzupassen und das gleiche System zu verwenden, das der Handelsserver verwendet. Aber es gibt eine Sache, die viele Leute nicht bedenken, wenn sie über diese Lösung nachdenken: Bei der Wiederholung und noch mehr bei der Simulation funktioniert die Uhr anders. All dies erschwert die Schaffung eines solchen Systems.
Erstellen von Multi-Symbol- und Multi-Perioden-Indikatoren Erstellen von Multi-Symbol- und Multi-Perioden-Indikatoren
In diesem Artikel werden wir uns mit den Grundsätzen der Erstellung von Multi-Symbol- und Multi-Perioden-Indikatoren befassen. Wir werden auch sehen, wie man auf die Daten solcher Indikatoren von Expert Advisors und anderen Indikatoren zugreifen kann. Wir werden die Hauptmerkmale der Verwendung von Multi-Indikatoren in Expert Advisors und Indikatoren besprechen und sehen, wie man sie durch nutzerdefinierte Indikatorpuffer darstellen kann.