English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay Systems — Marktsimulation (Teil 16): Neues System der Klassen

Entwicklung eines Replay Systems — Marktsimulation (Teil 16): Neues System der Klassen

MetaTrader 5Tester | 21 Dezember 2023, 10:09
297 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Developing a Replay System — Marktsimulation (Teil 15): Geburt des SIMULATOR (V) - RANDOM WALK“, haben wir eine Methode entwickelt, um randomisierte Daten zu erhalten. Dies geschah, um völlig angemessene Ergebnisse zu gewährleisten. Aber auch wenn das System tatsächlich in der Lage ist, Aufträge plausibel zu simulieren, fehlen ihm doch bestimmte Informationen, nicht nur vom Simulator, sondern auch vom Wiedergabesystem selbst. Die Wahrheit ist, dass einige der Dinge, die wir umsetzen müssen, ziemlich komplex sind, insbesondere aus der Perspektive des Systemaufbaus. Wir müssen also einige Änderungen vornehmen, um zumindest ein besseres Modell zu erhalten und uns nicht in den nächsten Schritten zu verlieren.

Glauben Sie mir, wenn Sie RANDOM WALK als Herausforderung empfunden haben, dann nur deshalb, weil Sie noch nicht gesehen haben, was wir entwickeln müssen, um das Simulations-/Spielerlebnis so gut wie möglich zu gestalten. Einer der schwierigsten Aspekte ist, dass wir zumindest die Grundlagen über das Asset wissen müssen, das für die Simulation oder Wiedergabe verwendet werden soll. In diesem Stadium der Entwicklung mache ich mir keine Sorgen über die automatische Lösung einiger Probleme, da es praktischere Wege gibt, dies zu tun.

Wenn Sie den vorangegangenen Artikel aufmerksam lesen, werden Sie feststellen, dass Programme wie EXCEL verwendet werden können, um mögliche Marktszenarien zu erstellen. Wir können aber auch Informationen aus anderen Quellen verwenden und sie kombinieren, um komplexere Simulationen zu erstellen. Das macht es schwierig, ein vollständig automatisiertes System zu entwickeln, das nach fehlenden Informationen über den Vermögenswert selbst suchen könnte. Wir haben noch ein kleines Problem. Daher werden wir hier den Inhalt der Header-Datei C_Replay.mqh ausschneiden und weitergeben, um die Wartung und Verbesserungen zu erleichtern. Ich mag es nicht, mit einer sehr großen Klasse zu arbeiten, da mir dies unpraktisch erscheint. Der Grund dafür ist, dass der Code in vielen Fällen nicht vollständig optimiert ist. Um die Sache zu vereinfachen, werden wir also beide Änderungen vornehmen, d. h. wir werden den Inhalt der Klasse C_Replay auf mehrere Klassen verteilen und gleichzeitig die wichtigsten Teile implementieren.


Implementierung des Dienstes mit einem neuen Klassensystem

Obwohl der in früheren Artikeln besprochene Code im Allgemeinen nur sehr wenige Änderungen erfahren hat, enthält diese neue Implementierung ein neues Modell. Dieses Modell ist leichter zu erweitern und auch für Personen ohne umfangreiche Programmierkenntnisse leichter zu verstehen, da die Elemente auf einfachere Weise miteinander verbunden sind. Wir werden keine langen und ermüdenden Funktionen zu lesen haben. Es gibt einige Details, die ein wenig seltsam erscheinen mögen, aber seltsamerweise machen sie den Code sicherer, stabiler und strukturell solider. Eines dieser Details ist die Verwendung von Zeigern in einer Weise, die sich von der in den alten Sprachen C++ oder C unterscheidet. Das Verhalten ist jedoch sehr ähnlich wie bei diesen Sprachen.

Die Verwendung von Zeigern ermöglicht es uns, Dinge zu tun, die sonst unmöglich wären. Obwohl viele der in C++ eingeführten Funktionen in MQL5 nicht verfügbar sind, erlaubt uns die einfache Tatsache, Zeiger in ihrer einfachsten Form zu verwenden, die Modellierung auf flexiblere und angenehmere Weise zu implementieren, zumindest was die Programmierung und die Syntax betrifft.

Daher sieht die neue Servicedatei in der aktuellen Version nun wie folgt aus:

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.16"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property link "https://www.mql5.com/ru/articles/11095"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //"Replay" config file.
input ENUM_TIMEFRAMES   user01 = PERIOD_M1;     //Initial timeframe for the chart.
input bool              user02 = true;          //Visual bar construction.
input bool              user03 = true;          //Visualize creation metrics.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if (pReplay.ViewReplay(user01))
        {
                Print("Permission received. The replay service can now be used...");
                while (pReplay.LoopEventOnTime(user02, user03));
        }
        delete pReplay;
}
//+------------------------------------------------------------------+


Es mag den Anschein haben, dass es keine großen Veränderungen gegeben hat, aber in Wirklichkeit ist alles ganz anders. Für den Endnutzer bleibt alles beim Alten und verhält sich genauso wie bei den Vorgängerversionen. Aber je nach Plattform und vor allem je nach Betriebssystem wird die erzeugte ausführbare Datei anders aussehen und sich anders verhalten. Der Grund dafür ist, dass wir die Klasse nicht als Variable, sondern als Zeiger auf den Speicher verwenden. Und deshalb verhält sich das ganze System auch völlig anders. Wenn Sie sorgfältig programmieren, sind Klassen noch sicherer und stabiler als Klassen, die nur als Variablen verwendet werden. Obwohl sie das gleiche Verhalten zeigen, unterscheiden sie sich in Bezug auf die Speichernutzung.

Da Klassen als Zeiger verwendet werden, werden wir nun einige Dinge ausschließen und andere verwenden. Zunächst einmal wird die Klasse immer explizit gestartet und geschlossen. Dies geschieht mit der Funktion Operatoren new und delete. Wenn wir den Operator „new“ verwenden, um eine Klasse zu erstellen, müssen wir immer den Klassenkonstruktor aufrufen. Sie geben nie einen Wert zurück, sodass wir den Rückgabewert nicht direkt überprüfen können. Das müssen wir zu einem anderen Zeitpunkt tun. Dasselbe geschieht bei der Verwendung des Operators „delete“, der den Destruktor der Klasse aufruft. Wie der Klassenkonstruktor gibt auch der Destruktor nie einen Wert zurück. Anders als der Konstruktor erhält der Destruktor jedoch keine Argumente.

Wir werden immer so vorgehen müssen: Wir erstellen eine Klasse mit dem Operator „new“ und zerstören die Klasse mit dem Operator „delete“. Das wird die einzige Arbeit sein, die wir tatsächlich leisten müssen. Den Rest erledigt das Betriebssystem: Es weist dem Programm genügend Speicherplatz zu, damit es in einem bestimmten Speicherbereich ausgeführt werden kann, sodass es während seiner gesamten Existenz so sicher wie möglich ist. Aber hier liegt die Gefahr für diejenigen, die an die Verwendung von Zeigern in C++/C gewöhnt sind: Wir sprechen über Notation. In C++ und C wird für Zeiger eine ganz bestimmte Notation verwendet. In der Regel wird ein Pfeil (->) verwendet. Für einen C++/C-Programmierer bedeutet dies, dass ein Zeiger verwendet wird. Wir können aber auch eine andere Notation verwenden, die in meinen Codes beim Zugriff auf einen Zeiger zu sehen ist.

Außerdem wird natürlich der Variablenname verwendet, der in der Regel mit dem Buchstaben „p“ oder Kombinationen wie „ptr“ beginnt (obwohl dies keine strikte Regel ist, pochen Sie sich also nicht darauf). Obwohl MQL5 sowohl die im obigen Code gezeigte Notation als auch die unten gezeigte Notation akzeptiert, finde ich persönlich, dass Code, der Zeiger verwendet, leichter zu lesen ist, wenn er tatsächlich die richtige Deklaration verwendet. Daher wird die Notation in unseren Codes dank meiner Kenntnisse der Sprache C++/C wie unten dargestellt sein:

void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if ((*pReplay).ViewReplay(user01))
        {
                Print("Permission received. The replay service can now be used...");
                while ((*pReplay).LoopEventOnTime(user02, user03));
        }
        delete pReplay;
}


Das Schreiben des Codes ist tatsächlich mit zusätzlicher Arbeit verbunden. Aber als C++/C-Programmierer mit jahrelanger Erfahrung ist es einfacher zu verstehen, dass ich mich auf einen Zeiger beziehe, wenn ich mir Code wie den oben gezeigten ansehe. Und da MQL5 dies auf die gleiche Weise versteht wie C++/C, sehe ich keine Probleme bei der Verwendung dieser Notation. Wann immer wir Code mit einer Notation wie der oben gezeigten sehen, sollten Sie sich keine Sorgen machen, denn es handelt sich nur um einen Zeiger.

Wir können das neue Klassensystem weiter erforschen. Wenn Sie glauben, dass nur diese Veränderungen stattgefunden haben, sind Sie ziemlich optimistisch. Allein die Tatsache, dass wir diese Änderungen vornehmen, bei denen wir ausdrücklich garantieren, dass die Klasse zu einem bestimmten Zeitpunkt erstellt und zerstört wird, erfordert mehrere weitere Änderungen am Code. Der Konstruktor und der Destruktor geben keine Werte zurück. Aber wir müssen irgendwie wissen, ob die Klasse korrekt erstellt wurde oder nicht.

Um zu verstehen, wie das geht, müssen Sie einen Blick in die Blackbox der Klasse C_Replay werfen. Er befindet sich in der Header-Datei C_Replay.mqh. Seine innere Struktur ist in der nachstehenden Abbildung dargestellt:

Abbildung 01 - C_Wiedergabe.mqh

Abbildung 01 - Das Verbindungssystem der Klasse Replay


Abbildung 01 zeigt, wie die Klassen miteinander verbunden sind, um die gewünschte Arbeit des Wiedergabe-/Simulationsdienstes zu leisten. Der grüne Pfeil zeigt an, dass die Klasse importiert wird, sodass einige interne Mitglieder der Klasse öffentlich sind. Der rote Pfeil zeigt an, dass die Daten zwar importiert werden, aber in den höheren Klassen nicht mehr sichtbar sind. Die Klasse C_FilesBars ist eine unverbundene Klasse. Sie wird von keiner anderen Klasse abgeleitet, aber ihre Methoden werden von anderen Klassen verwendet.

Um wirklich zu verstehen, wie diese Verbindungen hergestellt werden, muss man sehen, was im Inneren vor sich geht. Dazu müssen wir uns ansehen, wie die einzelnen Klassen erstellt wurden und wie sie sich in den entsprechenden Dateien befinden. Diese Dateien haben immer den gleichen Namen wie die Klasse. Dies ist nicht notwendig, aber dennoch eine gute Praxis, da der Code einige Änderungen erfahren hat. Wir werden hier nicht ins Detail gehen, aber in ein paar Minuten werden wir uns ansehen, welche Änderungen eingetreten sind und warum. Dies kann für diejenigen, die mit dem Programmieren beginnen, sehr nützlich sein, da es viel einfacher ist, zu lernen, wenn es einen funktionalen Code gibt und wir ihn ändern, um etwas anderes zu erzeugen (und gleichzeitig das, was wir wollen), wobei die Funktionsfähigkeit des Codes erhalten bleibt.

Schauen wir uns an, wie die Struktur umgesetzt wurde.


Die unverbundene Klasse: C_FileBars

Diese Klasse ist recht einfach und enthält alles, was zum Lesen der in der Datei vorhandenen Balken erforderlich ist. Es ist keine sehr große Klasse. Der gesamte Code ist unten dargestellt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_BarsDiary   1440
//+------------------------------------------------------------------+
class C_FileBars
{
        private :
                int     m_file;
                string  m_szFileName;
//+------------------------------------------------------------------+
inline void CheckFileIsBar(void)
                        {
                                string  szInfo = "";
                                
                                for (int c0 = 0; (c0 < 9) && (!FileIsEnding(m_file)); c0++) szInfo += FileReadString(m_file);
                                if (szInfo != "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>")
                                {
                                        Print("Файл ", m_szFileName, ".csv не является файлом баров.");
                                        FileClose(m_file);
                                        m_file = INVALID_HANDLE;
                                }
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_FileBars(const string szFileNameCSV)
                        :m_szFileName(szFileNameCSV)
                        {
                                if ((m_file = FileOpen("Market Replay\\Bars\\" + m_szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
                                        Print("Could not access ", m_szFileName, ".csv with bars.");
                                else
                                        CheckFileIsBar();
                        }
//+------------------------------------------------------------------+
                ~C_FileBars()
                        {
                                if (m_file != INVALID_HANDLE) FileClose(m_file);
                        }
//+------------------------------------------------------------------+
                bool ReadBar(MqlRates &rate[])
                        {
                                if (m_file == INVALID_HANDLE) return false;
                                if (FileIsEnding(m_file)) return false;
                                rate[0].time = StringToTime(FileReadString(m_file) + " " + FileReadString(m_file));
                                rate[0].open = StringToDouble(FileReadString(m_file));
                                rate[0].high = StringToDouble(FileReadString(m_file));
                                rate[0].low = StringToDouble(FileReadString(m_file));
                                rate[0].close = StringToDouble(FileReadString(m_file));
                                rate[0].tick_volume = StringToInteger(FileReadString(m_file));
                                rate[0].real_volume = StringToInteger(FileReadString(m_file));
                                rate[0].spread = (int) StringToInteger(FileReadString(m_file));
                                
                                return true;
                        }
//+------------------------------------------------------------------+
                datetime LoadPreView(const string szFileNameCSV)
                        {
                                int      iAdjust = 0;
                                datetime dt = 0;
                                MqlRates Rate[1];
                                
                                Print("Loading bars for Replay. Please wait....");
                                while (ReadBar(Rate) && (!_StopFlag))
                                {
                                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                                        dt = (dt == 0 ? Rate[0].time : dt);
                                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                }
                                return ((_StopFlag) || (m_file == INVALID_HANDLE) ? 0 : Rate[0].time + iAdjust);
                        }
//+------------------------------------------------------------------+
};


Man könnte meinen, dass diese Klasse nicht viel Sinn macht, aber sie enthält alles, was zum Öffnen, Lesen, Erstellen einer nutzerdefinierten Ressource und Schließen einer Panel-Datei benötigt wird. All dies wird in ganz bestimmten Schritten umgesetzt, sodass die hier vorgenommenen Änderungen keine Rolle spielen. Wenn der Leser die Balkendatei lesen möchte, kann er dies hier tun.

Diese Klasse ist für die ständige Verwendung durch die Operatoren NEW und DELETE vorgesehen. Sie verbleibt also nur so lange im Speicher, bis sie ihre Aufgabe erfüllt hat. Es sollte möglichst vermieden werden, diese Klasse zu verwenden, ohne die oben genannten Operatoren zu nutzen. Andernfalls könnten wir Stabilitätsprobleme bekommen. Nicht, dass dies wirklich geschehen wird. Die Tatsache, dass es für die Nutzung der Betreiber konzipiert wurde, bedeutet jedoch nicht, dass es auch mit anderen Mitteln genutzt werden kann.

In dieser Klasse haben wir 2 globale und private Variablen. Sie werden genau im Klassenkonstruktor initialisiert, wo wir versuchen werden, die angegebene Datei zu öffnen und zu prüfen, ob es sich um eine Datei mit Balken handelt oder nicht. Bitte beachten Sie jedoch, dass der Konstruktor keinen Wert irgendeines Typs zurückgibt und dass wir hier keine Möglichkeit haben, irgendetwas zurückzugeben, aber wir können anzeigen, dass eine Datei nicht den Erwartungen entspricht, indem wir sie als ungültige Datei markieren. Sie kann nach dem Schließen deutlich werden. Jeder Leseversuch führt zu einem Fehler, der entsprechend gemeldet wird. Die Rückgabe kann dann vom Anrufer verarbeitet werden. Infolgedessen verhält sich die Klasse so, als wäre sie eine Datei, die bereits Balken enthält. Oder ein großes Objekt, das bereits den Inhalt der Datei enthält. Die Aufrufer werden den Inhalt dieses großen Objekts lesen. Sobald jedoch der Destruktor aufgerufen wird, wird die Datei geschlossen und die Klasse durch das aufrufende Programm zerstört.

Diese Art der Simulation mag nicht so sicher und stabil erscheinen, aber glauben Sie mir, sie ist viel sicherer und stabiler als es scheint. Wenn es möglich wäre, auf einige der anderen in C++ vorhandenen Operatoren zuzugreifen, würden die Dinge noch interessanter werden. Natürlich muss man dafür den Code richtig schreiben, sonst wäre das Ganze ein komplettes Desaster. Aber da MQL5 nicht C++ ist, sollten wir die Möglichkeiten dieser Sprache studieren und ausschöpfen. Auf diese Weise werden wir ein System haben, das sehr nahe an den Grenzen liegt, die uns die Sprache ermöglicht.


Die tiefe Klasse: C_FileTicks

Die nächste Klasse, die wir uns ansehen werden, ist die Klasse C_FileTicks. Sie ist viel komplexer als die Klasse C_FileBars, weil wir öffentliche Elemente, private Elemente und Elemente, die irgendwo dazwischen liegen, haben. Sie haben einen besonderen Namen: PROTECTED. Der Begriff „protected“ (geschützt) hat eine besondere Bedeutung, wenn es um die Vererbung zwischen Klassen geht. Im Fall von C++ ist alles ziemlich kompliziert, zumindest zu Beginn des Lernens. Dies ist auf einige in C++ vorhandene Operatoren zurückzuführen. Glücklicherweise löst MQL5 das Problem auf eine viel einfachere Weise. So wird es viel einfacher zu verstehen, wie als geschützt deklarierte Elemente vererbt werden und ob auf sie zugegriffen werden kann oder nicht, was natürlich davon abhängt, wie die Vererbung erfolgt. Siehe die nachstehende Tabelle:

Definition in der Basisklasse Vererbungsart der Basisklasse Zugriff innerhalb einer abgeleiteten Klasse Zugriff durch Aufruf einer abgeleiteten Klasse 
private public Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
public public Zugriff ist erlaubt Zugriff auf Daten oder Methoden der Basisklasse möglich
protected public Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
private private Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
public private Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
protected private Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
private protected Der Zugriff wird verweigert Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
public protected Zugriff ist erlaubt  Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse
protected protected Zugriff ist erlaubt Unmöglicher Zugriff auf Daten oder Methoden der Basisklasse

Tabelle der Zugriffsebenen auf Klassenelemente und Funktionen

Nur in einem Fall können wir auf Daten oder Prozeduren innerhalb einer Klasse zugreifen, indem wir ein Vererbungs- oder Zugriffsdefinitionssystem verwenden. Und dann wird alles als public (öffentlich) deklariert. In allen anderen Fällen ist dies mehr oder weniger auf der Ebene der Klassenvererbung möglich, aber es ist unmöglich, auf eine Prozedur oder Daten innerhalb der Basisklasse zuzugreifen. Dies hängt nicht von der Zugangsklausel ab.

Das ist wichtig: Wenn Sie etwas als „protected“ deklarieren und versuchen, direkt auf diese Daten oder Prozeduren zuzugreifen, ohne die Klassenvererbung zu verwenden, können Sie nicht auf diese Daten oder Prozeduren zugreifen. Dies liegt daran, dass ohne Vererbung die als geschützt deklarierten Daten oder Prozeduren als privat gelten und daher nicht zugänglich sind.

Das scheint ziemlich kompliziert zu sein, nicht wahr? In der Praxis ist es jedoch viel einfacher. Allerdings müssen wir diesen Mechanismus mehrmals in Aktion erleben, um seine Funktionsweise wirklich zu verstehen. Aber glauben Sie mir, das ist in MQL5 viel einfacher zu bewerkstelligen als in C++; dort sind die Dinge viel komplizierter. Der Grund dafür ist, dass wir Möglichkeiten haben, die Zugriffsebene auf Daten oder Prozeduren, die als geschützt, in einigen Fällen sogar privat, deklariert sind, im Prozess der Klassenvererbung zu ändern. Das ist wirklich verrückt. In MQL5 funktioniert jedoch alles reibungslos.

Auf dieser Grundlage können wir schließlich die Klasse C_FileTicks sehen, die zwar theoretisch komplexer ist, aber einen relativ einfachen Code hat. Betrachten wir zunächst die ersten Elemente innerhalb der Klasse und beginnen wir mit ihrer Deklaration:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_FileBars.mqh"
//+------------------------------------------------------------------+
#define def_MaxSizeArray        16777216 // 16 Mbytes of positions
//+------------------------------------------------------------------+
#define macroRemoveSec(A) (A - (A % 60))
//+------------------------------------------------------------------+
class C_FileTicks
{
        protected:
                struct st00
                {
                        MqlTick  Info[];
                        MqlRates Rate[];
                        int      nTicks,
                                 nRate;
                }m_Ticks;
                double          m_PointsPerTick;


Sehen Sie, es ist ganz einfach, aber seien Sie vorsichtig, denn auf diese Struktur und diese Variable kann man zugreifen, indem man die Tabelle der Zugriffsebenen befolgt. Sobald dies geschehen ist, werden wir eine Reihe von privaten Verfahren für die Klasse haben. Sie sind außerhalb der Klasse nicht zugänglich und dienen der Unterstützung öffentlicher Verfahren. Es gibt zwei davon, wie unten zu sehen ist:

        public  :
//+------------------------------------------------------------------+
                bool BarsToTicks(const string szFileNameCSV)
                        {
                                C_FileBars      *pFileBars;
                                int             iMem = m_Ticks.nTicks;
                                MqlRates        rate[1];
                                MqlTick         local[];
                                
                                pFileBars = new C_FileBars(szFileNameCSV);
                                ArrayResize(local, def_MaxSizeArray);
                                Print("Converting bars to ticks. Please wait...");
                                while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) Simulation(rate[0], local);
                                ArrayFree(local);
                                delete pFileBars;
                                
                                return ((!_StopFlag) && (iMem != m_Ticks.nTicks));
                        }
//+------------------------------------------------------------------+
                datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
                        {
                                int             MemNRates,
                                                MemNTicks;
                                datetime dtRet = TimeCurrent();
                                MqlRates RatesLocal[];
                                
                                MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
                                MemNTicks = m_Ticks.nTicks;
                                if (!Open(szFileNameCSV)) return 0;
                                if (!ReadAllsTicks()) return 0;
                                if (!ToReplay)
                                {
                                        ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
                                        ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
                                        CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
                                        dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
                                        m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
                                        m_Ticks.nTicks = MemNTicks;
                                        ArrayFree(RatesLocal);
                                }
                                return dtRet;
                        };
//+------------------------------------------------------------------+


Alle privaten Verfahren sind Verfahren, die bereits in früheren Artikeln dieser Reihe erläutert und analysiert wurden. Wir werden hier nicht ins Detail gehen, da die Verfahren gegenüber den bisherigen Ausführungen praktisch unverändert geblieben sind. Es gibt jedoch etwas, das unsere Aufmerksamkeit bei diesen beiden öffentlichen Verfahren verdient. Dieses Etwas befindet sich in BarsToTicks. Im vorherigen Thema habe ich erwähnt, dass die Klasse C_FileBars eine unverbundene Klasse ist, die nicht wirklich vererbt wird. Dies ist nach wie vor der Fall. Ich habe aber auch erwähnt, dass es an bestimmten Stellen eingesetzt wird und auf eine ganz bestimmte Art und Weise zugänglich sein sollte.

Und hier ist einer dieser Momente. Zunächst deklarieren wir die Klasse auf eine ganz bestimmte Weise. Jetzt rufen wir den Klassenkonstruktor mit dem Namen der Datei auf, aus der wir die Balkenwerte holen wollen. Beachten Sie, dass dieser Aufruf keinen Wert zurückgibt. Wir verwenden den Operator NEW, damit für die Klasse Speicherplatz reserviert wird. Dieser Bereich enthält die Klasse, da MetaTrader 5 nicht vorschreibt, wo sich diese Klasse befinden darf. Nur das Betriebssystem verfügt über diese Informationen.

Praktischer ist jedoch, dass wir vom Operator NEW einen Wert erhalten, und dieser Wert ist ein „Zeiger“, mit dem wir direkt auf unsere Klasse zugreifen können (NOTE: Das Wort „Zeiger“ steht in Anführungszeichen, weil es sich nicht um einen Zeiger handelt, sondern einfach um eine Variable, die auf eine Klasse verweisen kann, als ob diese Klasse eine andere Variable oder Konstante wäre. In Zukunft werde ich zeigen, wie man damit eine konstante Klasse erstellen kann, in der wir nur auf Daten zugreifen, aber keine Berechnungen durchführen können. Da es eine sehr spezifische Verwendung hat, werden wir es auf ein anderes Mal verschieben.). Sobald wir diesen „Zeiger“ haben, können wir in unserer Klasse arbeiten, aber wir sind immer noch nicht sicher, dass die Datei geöffnet ist und gelesen werden kann. Daher müssen wir eine Art von Validierung durchführen, bevor wir versuchen, Daten zu lesen und zu verwenden. Glücklicherweise ist dies nicht notwendig, da wir während des Leseversuchs überprüfen können, ob der Aufruf korrekt abgeschlossen wurde oder nicht. Das heißt, wir können hier überprüfen, ob die Daten gelesen wurden oder nicht.

Wenn das Lesen aus irgendeinem Grund fehlschlägt, wird die WHILE-Schleife einfach geschlossen. Daher sind keine weiteren Kontrollen erforderlich. Schon der Versuch, die Daten zu lesen, dient der Überprüfung, ob das Lesen erfolgreich war oder nicht. Auf diese Weise können wir Dinge außerhalb der Klasse C_FileBars manipulieren, aber wir müssen die Klasse explizit beenden, damit der Speicher, in dem sie sich befand, an das Betriebssystem zurückgegeben wird. Dies geschieht durch den Aufruf des Destruktors, über den Operator DELETE. Dadurch wird sichergestellt, dass die Klasse ordnungsgemäß gelöscht wurde und nicht mehr referenziert wird.

Andernfalls kann es zu inkonsistenten Daten und sogar zu Schrott in unseren Programmen kommen. Mit dem oben beschriebenen Verfahren wissen wir jedoch genau, wann, wo und wie die Klasse verwendet wird. Dies kann uns in verschiedenen Szenarien helfen, in denen die Modellierung recht komplex sein kann.


Eine Klasse, mehrere Funktionen: C_ConfigService

Dieser Kurs ist sehr interessant. Obwohl es sich um eine Klasse handelt, die als Brücke zwischen der Klasse C_FileTicks und der Klasse C_Replay fungiert, stellt sie irgendwie sicher, dass alles so bleibt, wie wir es erwarten, und dass Verbesserungen oder Änderungen im Konfigurationssystem nur an den Stellen sichtbar werden, an denen sie wirklich sichtbar sein sollten. Es handelt sich nicht um eine sehr umfangreiche oder komplexe Klasse, sondern nur um eine Zwischenklasse mit relativ einfachem Code. Die Idee ist, alles, was mit der Einrichtung des Wiedergabe-/Simulationsdienstes zu tun hat, in dieser Klasse unterzubringen. Im Wesentlichen besteht seine Aufgabe darin, die Konfigurationsdatei zu lesen und ihren Inhalt auf den Dienst anzuwenden, damit dieser wie vom Nutzer konfiguriert funktioniert.

Die Klasse sollte folgende Aufgaben erfüllen: Lesen und Erstellen von Ticks für die Modellierung, Anwenden von Balken auf das Wiedergabe-Asset und, in einigen Fällen, Anpassen der Variablen des Wiedergabe-Assets. Der Vermögenswert wird sich also sehr ähnlich wie der reale Vermögenswert verhalten. Nachstehend finden Sie den vollständigen Code der Klasse im derzeitigen Entwicklungsstadium:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_FileBars.mqh"
#include "C_FileTicks.mqh"
//+------------------------------------------------------------------+
class C_ConfigService : protected C_FileTicks
{
        protected:
//+------------------------------------------------------------------+
                datetime m_dtPrevLoading;
//+------------------------------------------------------------------+
        private :
//+------------------------------------------------------------------+
                enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
//+------------------------------------------------------------------+
inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
                        {
                                string szInfo;
                                
                                szInfo = In;
                                Out = "";
                                StringToUpper(szInfo);
                                StringTrimLeft(szInfo);
                                StringTrimRight(szInfo);
                                if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO;
                                if (StringSubstr(szInfo, 0, 1) != "[")
                                {
                                        Out = szInfo;
                                        return Transcription_INFO;
                                }
                                for (int c0 = 0; c0 < StringLen(szInfo); c0++)
                                        if (StringGetCharacter(szInfo, c0) > ' ')
                                                StringAdd(Out, StringSubstr(szInfo, c0, 1));                                    
                                
                                return Transcription_DEFINE;
                        }
//+------------------------------------------------------------------+
inline bool Configs(const string szInfo)
                        {
                                const string szList[] = {
                                        
						"POINTSPERTICK"
                                                        };
                                string  szRet[];
                                char    cWho;
                                
                                if (StringSplit(szInfo, '=', szRet) == 2)
                                {
                                        StringTrimRight(szRet[0]);
                                        StringTrimLeft(szRet[1]);
                                        for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;
                                        switch (cWho)
                                        {
                                                case 0:
                                                        m_PointsPerTick = StringToDouble(szRet[1]);
                                                        return true;                                            
                                        }
                                        Print("Variable >>", szRet[0], "<< undefined.");
                                }else
                                        Print("Configuration >>", szInfo, "<< invalid.");
                                        
                                return false;
                        }
//+------------------------------------------------------------------+
inline void FirstBarNULL(void)
                        {
                                MqlRates rate[1];
                                
                                rate[0].close = rate[0].open =  rate[0].high = rate[0].low = m_Ticks.Info[0].last;
                                rate[0].tick_volume = 0;
                                rate[0].real_volume = 0;
                                rate[0].time = m_Ticks.Info[0].time - 60;
                                CustomRatesUpdate(def_SymbolReplay, rate, 1);
                        }
//+------------------------------------------------------------------+
inline bool WhatDefine(const string szArg, char &cStage)
                        {
                                const string szList[] = {
                                        "[BARS]",
                                        "[TICKS]",
                                        "[TICKS->BARS]",
                                        "[BARS->TICKS]",
                                        "[CONFIG]"
                                                        };
                                                                                                
                                cStage = 1;
                                for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++)
                                        if (szList[c0] == szArg) return true;
                                        
                                return false;
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                bool SetSymbolReplay(const string szFileConfig)
                        {
                                int             file,
                                                iLine;
                                char            cError,
                                                cStage;
                                string          szInfo;
                                bool            bBarPrev;
                                C_FileBars      *pFileBars;
                                
                                if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
                                {
                                        MessageBox("Failed to open the\nconfiguration file.", "Market Replay", MB_OK);
                                        return false;
                                }
                                Print("Loading data for replay. Please wait....");
                                ArrayResize(m_Ticks.Rate, def_BarsDiary);
                                m_Ticks.nRate = -1;
                                m_Ticks.Rate[0].time = 0;
                                bBarPrev = false;

                                iLine = 1;
                                cError = cStage = 0;
                                while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
                                {
                                        switch (GetDefinition(FileReadString(file), szInfo))
                                        {
                                                case Transcription_DEFINE:
                                                        cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
                                                        break;
                                                case Transcription_INFO:
                                                        if (szInfo != "") switch (cStage)
                                                        {
                                                                case 0:
                                                                        cError = 2;
                                                                        break;
                                                                case 1:
                                                                        pFileBars = new C_FileBars(szInfo);
                                                                        if ((m_dtPrevLoading = (*pFileBars).LoadPreView(szInfo)) == 0) cError = 3; else bBarPrev = true;
                                                                        delete pFileBars;
                                                                        break;
                                                                case 2:
                                                                        if (LoadTicks(szInfo) == 0) cError = 4;
                                                                        break;
                                                                case 3:
                                                                        if ((m_dtPrevLoading = LoadTicks(szInfo, false)) == 0) cError = 5; else bBarPrev = true;
                                                                        break;
                                                                case 4:
                                                                        if (!BarsToTicks(szInfo)) cError = 6;
                                                                        break;
                                                                case 5:
                                                                        if (!Configs(szInfo)) cError = 7;
                                                                        break;
                                                        }
                                                        break;
                                        };
                                        iLine += (cError > 0 ? 0 : 1);
                                }
                                FileClose(file);
                                switch(cError)
                                {
                                        case 0:
                                                if (m_Ticks.nTicks <= 0)
                                                {
                                                        Print("No ticks to use. Closing the service...");
                                                        cError = -1;
                                                }else   if (!bBarPrev) FirstBarNULL();
                                                break;
                                        case 1  : Print("Command in line ", iLine, " cannot be recognized by the system...");    break;
                                        case 2  : Print("The system did not expect the content of the line ", iLine);                  break;
                                        default : Print("Error in line ", iLine);
                                }
                                                                
                                return (cError == 0 ? !_StopFlag : false);
                        }
//+------------------------------------------------------------------+
};


Hier beginnen wir mit der Verwendung dieser Vererbungstabelle. Alle diese Elemente werden von der Klasse C_FileTicks geerbt. Wir erweitern also eigentlich die Funktionalität der Klasse C_ConfigService selbst. Aber das ist nicht das Einzige, denn wenn man genau hinsieht, gibt es eine ganz bestimmte Situation, in der wir Daten aus Balken laden müssen. Zu diesem Zweck müssen wir die Klasse C_FileBars verwenden. Wir haben also dieselbe Methode wie in der Klasse C_FileTicks verwendet, wo wir die Daten aus der Balkendatei laden mussten, um sie in Ticks umzuwandeln. Die Erklärung, die wir dort gegeben haben, gilt auch hier.

Diese Klasse ist gewissermaßen für die Übersetzung der in der Konfigurationsdatei enthaltenen Daten zuständig. Nun müssen wir die Dinge nur noch an den richtigen Stellen definieren, damit sie auf den richtigen Zustand hinweisen bzw. diesen verursachen. Damit stellen wir sicher, dass die Werte korrekt ausgefüllt oder die Daten korrekt geladen werden. Dies geschieht an zwei Stellen.

An erster Stelle geben wir an, welche Stufe, oder besser gesagt, welchen Schlüssel, wir gerade erfassen oder was wir anpassen. Es ist zwar nicht sehr kompliziert, aber wir haben es hier mit einer Liste von Dingen zu tun, die als Schlüssel zu dem dienen, woran wir in den nächsten Zeilen der Konfigurationsdatei arbeiten werden. Hier müssen Sie nur darauf achten, dass diese Liste einer bestimmten logischen Reihenfolge folgt. Andernfalls werden wir Probleme bei der Übersetzung der Werte haben. Um herauszufinden, an welcher Position sich das Datenelement befinden sollte, schauen Sie sich einfach die Funktion SetSymbolReplay an und sehen Sie, was genau die einzelnen Werte hier bewirken.

Die zweite Stelle ist für die Dekodierung der in der Replay/Simulations-Konfigurationsdatei enthaltenen Werte in Konstanten zuständig, die innerhalb des Dienstes verwendet werden. Hier machen wir fast das Gleiche wie zuvor, aber dieses Mal gibt jeder der im Array enthaltenen Werte den Namen einer Variablen innerhalb der Klasse an. Sie müssen also nur den Namen, den die Variable haben soll, in die Liste der Konfigurationsdatei aufnehmen. Dann fügen wir seine Position in der Liste zum Aufruf hinzu. Auf diese Weise ändern wir den Wert der gewünschten Variablen. Wenn Sie nicht verstehen, was ich sage, machen Sie sich keine Sorgen. In Kürze zeige ich ein konkretes Beispiel, wie man neue Variablen hinzufügt. Davor müssen wir an dieser Stelle noch einige Dinge definieren.

Obwohl alles sehr schön aussieht, haben wir die letzte Klasse noch nicht gesehen.


Klasse C_Replay - Ich verstehe nichts... Wo sind die Sachen?!

Dies ist die einzige Klasse, mit der sich der Wiedergabe-/Simulationsdienst tatsächlich befassen wird. Wir müssen uns diese Klasse als eine Bibliothek vorstellen, aber eine Bibliothek, deren einzige Funktion darin besteht, ein Verhalten zu fördern, das dem ähnelt, was passieren würde, wenn wir anstelle von Replay oder Simulation mit einem physischen Markt oder einem Demokonto interagieren würden. Das heißt, alles was wir tun müssen, ist, Dinge ausschließlich innerhalb dieser Klasse zu implementieren, sodass die MetaTrader 5-Plattform alle Wiedergabesimulationen durchführen kann, als ob sie von einem echten Server kämen.

Wenn Sie sich jedoch den Klassencode genau ansehen und nach etwas suchen, fragen Sie sich vielleicht: Wo sind die Variablen, Strukturen und Funktionen, die aufgerufen werden sollen? Ich kann sie nirgendwo finden! Diese Art des Denkens kann in der Anfangsphase auftreten. Es bedeutet nur, dass Sie mit der Vererbung zwischen Klassen noch nicht sehr vertraut sind. Machen Sie sich keine Sorgen, studieren Sie den Code in aller Ruhe und Sie werden bald verstehen, wie diese Vererbung funktioniert. Es ist besser, jetzt damit anzufangen, denn bald werde ich Ihnen etwas noch Komplizierteres zeigen, das Sie sehr verwirren könnte. Eines dieser Dinge ist der POLYMORPHISMUS. Das ist zwar sehr nützlich, stiftet aber auch viel Verwirrung bei denjenigen, die die Probleme im Zusammenhang mit der Vererbung nicht verstehen. Ich empfehle Ihnen also, diesen Code genau zu studieren.

Lassen wir das Thema Polymorphismus erst einmal für die Zukunft beiseite. Siehe den nachstehenden Code:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_ConfigService.mqh"
//+------------------------------------------------------------------+
class C_Replay : private C_ConfigService
{
        private :
                int             m_ReplayCount;
                long            m_IdReplay;
                struct st01
                {
                        MqlRates Rate[1];
                        bool     bNew;
                        datetime memDT;
                        int      delay;
                }m_MountBar;
//+------------------------------------------------------------------+
                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess  Info;
                                MqlRates        Rate[def_BarsDiary];
                                int             iPos, nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }
//+------------------------------------------------------------------+
inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_Replay(const string szFileConfig)
                        {
                                m_ReplayCount = 0;
                                m_dtPrevLoading = 0;
                                m_Ticks.nTicks = 0;
                                m_PointsPerTick = 0;
                                Print("************** Market Replay Service **************");
                                srand(GetTickCount());
                                GlobalVariableDel(def_GlobalVariableReplay);
                                SymbolSelect(def_SymbolReplay, false);
                                CustomSymbolDelete(def_SymbolReplay);
                                CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
                                CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
                                CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
                                SymbolSelect(def_SymbolReplay, true);
                                m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1);
                        }
//+------------------------------------------------------------------+
                ~C_Replay()
                        {
                                ArrayFree(m_Ticks.Info);
                                ArrayFree(m_Ticks.Rate);
                                m_IdReplay = ChartFirst();
                                do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                                ChartClose(m_IdReplay);
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++);
                                CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
                                CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
                                CustomSymbolDelete(def_SymbolReplay);
                                GlobalVariableDel(def_GlobalVariableReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                Print("Replay service completed...");
                        }
//+------------------------------------------------------------------+
                bool ViewReplay(ENUM_TIMEFRAMES arg1)
                        {
                                u_Interprocess info;
                                
                                if (m_IdReplay == -1) return false;
                                if ((m_IdReplay = ChartFirst()) > 0) do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                        {
                                                ChartClose(m_IdReplay);
                                                ChartRedraw();
                                        }
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                Print("Wait for permission from [Market Replay] indicator to start replay ...");
                                info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
                                ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
                                ChartRedraw(m_IdReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                GlobalVariableTemp(def_GlobalVariableIdGraphics);
                                GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }
//+------------------------------------------------------------------+
                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }
//+------------------------------------------------------------------+
};
//+------------------------------------------------------------------+
#undef macroRemoveSec
#undef def_SymbolReplay
//+------------------------------------------------------------------+


Wie Sie sehen können, ist das nichts Ungewöhnliches. Sie ist jedoch viel größer als die vorherige Version. Dennoch haben wir es geschafft, alles zu tun, was wir zuvor umgesetzt haben. Ich möchte jedoch betonen, dass alle genannten Punkte nicht wirklich Teil der Klasse C_Replay sind. Diese Punkte werden vererbt. Dies geschieht, weil ich nicht möchte, dass sie außerhalb der Klasse C_Replay zugänglich sind. Um dies zu erreichen, vererben wir Dinge als „private“. Auf diese Weise garantieren wir die Integrität der vererbten Informationen. Diese Klasse hat nur zwei Funktionen, auf die tatsächlich von außen zugegriffen werden kann, da der Konstruktor und der Destruktor nicht berücksichtigt werden.

Doch bevor wir über den Konstruktor und den Destruktor der Klasse sprechen, wollen wir uns zwei Funktionen ansehen, auf die von außerhalb der Klasse zugegriffen werden kann. Einmal habe ich sogar erwogen, nur eine Funktion zu behalten, aber aus praktischen Gründen habe ich beschlossen, zwei zu behalten. Auf diese Weise ist es einfacher. Wir haben die Funktion LoopEventOnTime bereits in früheren Artikeln behandelt. Und da sie hier keine Änderungen erfahren hat, ist es nicht nötig, zusätzliche Erklärungen zu geben. Wir können es überspringen und uns auf das konzentrieren, was sich verändert hat: ViewReplay.

In der Funktion ViewReplay gibt es nur eine Änderung, nämlich die Prüfung. Hier wird geprüft, ob der Klassenkonstruktor die Klasse erfolgreich initialisieren konnte. Schlägt das fehl, gibt die Funktion einen Wert zurück, der die Beendigung des Wiedergabedienstes zur Folge haben sollte. Dies ist die einzige Änderung gegenüber den früheren Artikeln.


Abschließende Überlegungen

In Anbetracht all dieser Änderungen empfehle ich Ihnen, die neuen Materialien zu studieren und den beigefügten Code mit anderen Codes zu vergleichen, die in früheren Artikeln vorgestellt wurden. Im nächsten Artikel werden wir uns einem ganz anderen und sehr interessanten Thema zuwenden. Auf Wiedersehen!

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

Beigefügte Dateien |
Entwicklung eines Replay Systems — Marktsimulation (Teil 17): Ticks und noch mehr Ticks (I) Entwicklung eines Replay Systems — Marktsimulation (Teil 17): Ticks und noch mehr Ticks (I)
Hier werden wir sehen, wie man etwas wirklich Interessantes, aber gleichzeitig auch sehr Schwieriges umsetzen kann, da bestimmte Punkte sehr verwirrend sein können. Das Schlimmste, was passieren kann, ist, dass einige Händler, die sich für Profis halten, nichts über die Bedeutung dieser Konzepte auf dem Kapitalmarkt wissen. Auch wenn wir uns hier auf die Programmierung konzentrieren, ist das Verständnis einiger der Probleme, die mit dem Markthandel verbunden sind, von entscheidender Bedeutung für das, was wir umsetzen werden.
Neuronale Netze leicht gemacht (Teil 51): Behavior-Guided Actor-Critic (BAC) Neuronale Netze leicht gemacht (Teil 51): Behavior-Guided Actor-Critic (BAC)
Die letzten beiden Artikel befassten sich mit dem Soft Actor-Critic-Algorithmus, der eine Entropie-Regularisierung in die Belohnungsfunktion integriert. Dieser Ansatz schafft ein Gleichgewicht zwischen Umwelterkundung und Modellnutzung, ist aber nur auf stochastische Modelle anwendbar. In diesem Artikel wird ein alternativer Ansatz vorgeschlagen, der sowohl auf stochastische als auch auf deterministische Modelle anwendbar ist.
Neuronale Netze leicht gemacht (Teil 53): Aufteilung der Belohnung Neuronale Netze leicht gemacht (Teil 53): Aufteilung der Belohnung
Wir haben bereits mehrfach darüber gesprochen, wie wichtig die richtige Wahl der Belohnungsfunktion ist, mit der wir das gewünschte Verhalten des Agenten anregen, indem wir Belohnungen oder Bestrafungen für einzelne Aktionen hinzufügen. Aber die Frage nach der Entschlüsselung unserer Signale durch den Agenten bleibt offen. In diesem Artikel geht es um die Aufteilung der Belohnung im Sinne der Übertragung einzelner Signale an den trainierten Agenten.
Neuronale Netze leicht gemacht (Teil 50): Soft Actor-Critic (Modelloptimierung) Neuronale Netze leicht gemacht (Teil 50): Soft Actor-Critic (Modelloptimierung)
Im vorigen Artikel haben wir den Algorithmus Soft Actor-Critic (Akteur-Kritiker) implementiert, konnten aber kein profitables Modell trainieren. Hier werden wir das zuvor erstellte Modell optimieren, um die gewünschten Ergebnisse zu erzielen.