English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems — Marktsimulation (Teil 03): Anpassen der Einstellungen (I)

Entwicklung eines Replay-Systems — Marktsimulation (Teil 03): Anpassen der Einstellungen (I)

MetaTrader 5Beispiele | 28 Juli 2023, 09:43
197 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems — Marktsimulation (Teil 02): Erste Experimente (II)“ haben wir ein System entwickelt, das einen 1-Minuten-Balken innerhalb einer angemessenen Verarbeitungszeit für die Marktsimulation erzeugen kann. Wir waren uns jedoch darüber im Klaren, dass wir nicht kontrollieren konnten, was geschah. Unsere Möglichkeiten waren darauf beschränkt, einige Punkte auszuwählen und andere anzupassen. Wir hatten nur noch wenige Möglichkeiten mit einem laufenden System.

In diesem Artikel werden wir versuchen, diese Situation zu verbessern. Wir werden einige zusätzliche Kontrollen verwenden, um unsere Analyse überschaubarer zu machen. Wir haben zwar noch viel Arbeit vor uns, bis wir ein voll funktionsfähiges System für die statistische Analyse und die Kartenkontrolle haben, aber das ist ein guter Anfang.

In diesem Artikel werden wir nur einige wenige Anpassungen vornehmen, daher wird er relativ kurz sein. Wir werden in diesem Schritt nicht ins Detail gehen. Unser Ziel ist es, die Grundlagen für die notwendigen Kontrollen zu schaffen, damit Replay für diejenigen, die das System in der Praxis einsetzen wollen, einfacher zu implementieren und zu analysieren ist.


Planung

Dieser Planungsschritt ist recht einfach, denn wenn man sich ansieht, wie das System im letzten Artikel funktioniert hat, wird klar, was wir tun müssen. Wir müssen ein Kontrollformular erstellen, mit dem wir es pausieren, starten und vor allem einen bestimmten Zeitpunkt für den Start der Studie auswählen können.

In der aktuellen Ansicht beginnen wir immer mit dem ersten gehandelten Tick. Nehmen wir an, dass wir ab der fünften Marktstunde, also ab 14:00 Uhr (da der Markt um 9:00 Uhr öffnet), recherchieren wollen. In diesem Fall müssten wir 5 Stunden für das Replay (Wiederholung der Marktdynamik) abwarten und können erst dann die notwendige Analyse durchführen. Das ist völlig unmöglich, denn wenn wir versuchen, das Replay zu stoppen, wird es geschlossen, und wir müssen vom ersten gehandelten Tick an wieder von vorne beginnen.

Jetzt ist klar, was wir sofort tun müssen, denn so wie es jetzt funktioniert, ist es demotivierend, auch wenn die Idee an sich interessant ist.

Nachdem wir nun eine allgemeine Richtung festgelegt haben, können wir mit der Umsetzung beginnen.


Umsetzung

Die Umsetzung wird recht interessant sein, da wir verschiedene Wege beschreiten müssen, von den einfachsten bis zu den vielfältigsten, um ein echtes Kontrollsystem zu schaffen. Alle Schritte sind jedoch leicht zu verstehen, wenn Sie die Erklärungen aufmerksam lesen und die Artikel in der Reihenfolge ihrer Veröffentlichung befolgen, ohne einen davon zu überspringen und ohne zu versuchen, mehrere Schritte vorwärts zu gehen.

Im Gegensatz zu dem, was viele denken mögen, werden wir keine DLLs in dem System verwenden. Wir werden das Replay-System ausschließlich in der Sprache MQL5 implementieren. Die Idee ist, das Beste aus MetaTrader 5 herauszuholen und zu zeigen, wie weit wir innerhalb der Plattform gehen können, wenn wir die notwendigen Funktionen schaffen. Der Rückgriff auf eine externe Implementierung nimmt viel von der Freude an der Arbeit mit MQL5 und vermittelt den Eindruck, dass MQL5 unsere Anforderungen nicht erfüllen kann.

Wenn Sie sich den im vorigen Artikel verwendeten Code ansehen, können Sie erkennen, dass das System einen Dienst zur Erstellung des Replays verwendet. Es enthielt auch ein Skript, mit dem es gestartet wurde. Dieses Skript ermöglichte es dem Dienst, Ticks an ein nutzerdefiniertes Symbol zu senden und so ein Replay zu erstellen. Wir haben einen einfachen Schaltmechanismus verwendet. Für eine effizientere Kontrolle ist diese Methode jedoch nicht geeignet. Wir müssen einen schwierigeren Weg einschlagen.


Erstellen eines ultra-basis EA

Versuchen wir, die Kontrolle mit Hilfe eines EA zu implementieren. Dieser EA steuert, wann der Dienst Ticks für Balken erzeugen soll und wann nicht.

Warum ein EA? Wir könnten anstelle eines EA einen Indikator verwenden, der auf die gleiche Weise funktionieren würde. Ich möchte jedoch einen EA verwenden, da wir ihn später für die Erstellung eines Auftragssimulationssystems benötigen werden. Außerdem werden wir versuchen, das Auftragssystem zu verwenden, das ich in meiner anderen Artikelserie mit dem Titel „Entwicklung eines Expert Advisor für den Handel von Grund auf“ vorgestellt habe. Über das Bestellsystem brauchen wir uns vorerst keine Gedanken zu machen, da wir noch eine Menge Arbeit vor uns haben.

Der vollständige Code unseres Basis-EA ist unten dargestellt:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Controls.mqh>
//+------------------------------------------------------------------+
C_Controls      Control;
//+------------------------------------------------------------------+
int OnInit()
{
        Control.Init();
                
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        Control.DispatchMessage(id, lparam, dparam, sparam);
}
//+------------------------------------------------------------------+

Der Code ist sehr einfach, aber er reicht aus, um den Betrieb des Dienstes zu steuern. Schauen wir uns nun einen bestimmten Teil des Codes an, nämlich die oben hervorgehobene Steuerobjektklasse. Der Code ist in diesem frühen Entwicklungsstadium noch nicht sehr komplex. Wir werden nur eine Schaltfläche zum Abspielen und Anhalten des Replay-Dienstes einrichten. Werfen wir also einen Blick auf diese Klasse im derzeitigen Entwicklungsstadium.

Der erste Punkt, auf den Sie achten sollten, ist unten dargestellt:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include <Market Replay\Interprocess.mqh>
//+------------------------------------------------------------------+
#define def_ButtonPlay  "Images\\Market Replay\\Play.bmp"
#define def_ButtonPause "Images\\Market Replay\\Pause.bmp"
#resource "\\" + def_ButtonPlay
#resource "\\" + def_ButtonPause
//+------------------------------------------------------------------+
#define def_PrefixObjectName "Market Replay _ "

Der erste wichtige Punkt ist die Header-Datei in BLUE. Wir werden es später im Detail sehen. Da gibt es einige Definitionen von Bitmap-Objekten wie die Play- und Pause-Schaltflächen. Hier ist nichts sehr kompliziert. Sobald diese Punkte definiert sind, gehen wir zum Klassencode über, der recht kompakt ist. Der vollständige Code ist nachstehend aufgeführt.

class C_Controls
{
        private :
//+------------------------------------------------------------------+
                string  m_szBtnPlay;
                long            m_id;
//+------------------------------------------------------------------+
                void CreateBtnPlayPause(long id)
                        {
                                m_szBtnPlay = def_PrefixObjectName + "Play";
                                ObjectCreate(id, m_szBtnPlay, OBJ_BITMAP_LABEL, 0, 0, 0);
                                ObjectSetInteger(id, m_szBtnPlay, OBJPROP_XDISTANCE, 5);
                                ObjectSetInteger(id, m_szBtnPlay, OBJPROP_YDISTANCE, 25);
                                ObjectSetInteger(id, m_szBtnPlay, OBJPROP_STATE, false);
                                ObjectSetString(id, m_szBtnPlay, OBJPROP_BMPFILE, 0, "::" + def_ButtonPause);
                                ObjectSetString(id, m_szBtnPlay, OBJPROP_BMPFILE, 1, "::" + def_ButtonPlay);
                        }
//+------------------------------------------------------------------+
        public  :
//+------------------------------------------------------------------+
                C_Controls()
                        {
                                m_szBtnPlay = NULL;
                        }
//+------------------------------------------------------------------+
                ~C_Controls()
                        {
                                ObjectDelete(ChartID(), m_szBtnPlay);
                        }               
//+------------------------------------------------------------------+
                void Init(void)
                        {
                                if (m_szBtnPlay != NULL) return;
                                CreateBtnPlayPause(m_id = ChartID());
                                GlobalVariableTemp(def_GlobalVariableReplay);
                                ChartRedraw();
                        }
//+------------------------------------------------------------------+
                void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
                        {
                                u_Interprocess Info;
                                
                                switch (id)
                                {
                                        case CHARTEVENT_OBJECT_CLICK:
                                                if (sparam == m_szBtnPlay)
                                                {
                                                        Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                                        GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                                                }
                                                break;
                                }
                        }
//+------------------------------------------------------------------+
};

Hier haben wir zwei Hauptfunktionen: Init und DispatchMessage. Sie führen alle Arbeiten durch, die für den Betrieb der EA in diesem frühen Stadium erforderlich sind. Um einige dieser Details besser zu erklären, sehen wir uns die beiden folgenden Funktionen an. Beginnen wir mit Init.

void Init(void)
{
        if (m_szBtnPlay != NULL) return;
        CreateBtnPlayPause(m_id = ChartID());
        GlobalVariableTemp(def_GlobalVariableReplay);
        ChartRedraw();
}

Beim Aufruf von Init wird zunächst geprüft, ob die Steuerelemente bereits erstellt wurden. Wenn dies bereits geschehen ist, wird sie verlassen. Dies ist wichtig, denn wenn Sie den Zeitrahmen des Charts ändern oder eine Änderung vornehmen, die ein Neuladen des Charts durch den EA erforderlich macht (was recht häufig vorkommt), wird der Status des Replay-Dienstes nicht geändert. Wenn der Dienst also aktiviert ist, d. h. wenn er angehalten ist, läuft er weiter wie bisher, und wenn er ‚im Spiel‘ ist, sendet er weiterhin Ticks.

Wenn es sich um den ersten Aufruf handelt, werden die Hauptsteuerelemente erstellt, bei denen es sich vorerst nur um die Schaltflächen für Wiedergabe und Pause handelt. Als Nächstes erstellen wir einen globalen Terminalwert, der für die Kommunikation zwischen dem EA und dem Dienst verwendet wird. An dieser Stelle erstellen wir lediglich eine Variable, ohne ihr einen Wert zuzuweisen.

Danach müssen wir die Objekte auf den Bildschirm bringen. Dies ist wichtig, denn wenn diese erzwungene Aktualisierung nicht durchgeführt wird, wird der EA zwar geladen, aber der Dienst wird gestoppt, was den Eindruck erweckt, dass das System abgestürzt ist. Aber eigentlich warten wir darauf, dass MetaTrader 5 den Chart für uns aktualisiert, damit die Objekte geplottet werden und wir die Marktwiederholung starten können.

Haben Sie bemerkt, wie einfach das ist? Schauen wir uns nun den Code der Funktion DispatchMessage an, der in diesem Stadium ebenfalls sehr einfach ist. Nachstehend finden Sie den entsprechenden Code:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        u_Interprocess Info;
                        
        switch (id)
        {
                case CHARTEVENT_OBJECT_CLICK:
                        if (sparam == m_szBtnPlay)
                        {
                                Info.s_Infos.isPlay = (bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
                                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
                        }
                        break;
        }
}

Wir verwenden MetaTrader 5, um alles für uns zu verwalten. Wir verwenden die u_Interprocess Union, um einen globalen Terminalwert zu setzen, um den Zustand der Bitmap-Schaltfläche zu überprüfen. Damit passen wir die globale Variable des Terminals so an, dass sie an den Dienstprozess übergeben wird, der für die Erstellung der Wiedergabe verantwortlich ist.

Aus diesem Grund starten wir den Replay-Dienste immer in einem pausierten Zustand. Sobald der EA mit all seinen Objekten auf den Chart geladen ist, können wir ihn abspielen oder die Marktwiedergabe anhalten, wann immer wir wollen. Das wird die Sache etwas interessanter machen.

Verstehen der Datei Interprocess.mqh

Wie Sie vielleicht schon vermutet haben, brachte die Umstellung des Systems auf die Verwendung eines EA anstelle eines Skripts einige Änderungen für den Replay-Dienst mit sich. Bevor wir uns diese Änderungen ansehen, werfen wir einen Blick auf die Datei Interprocess.mqh. Der vollständige Code in seinem aktuellen Zustand ist im folgenden Code zu finden:
#define def_GlobalVariableReplay "Replay Infos"
//+------------------------------------------------------------------+
union u_Interprocess
{
        double Value;
        struct st_0
        {
                bool isPlay;
                struct st_1
                {
                        char Hours;
                        char Minutes;
                }Time[3];
        }s_Infos;
};

Diese einfache Definition liefert uns einen Namen, aber es ist nicht irgendein Name. Dies ist der Name der globalen Terminalvariable, die in dieser Phase verwendet wird, um die Kommunikation zwischen dem EA und dem Dienst zu ermöglichen. Aber der Teil, der für Nutzer mit weniger Erfahrung kompliziert sein kann, ist Union.

Schauen wir uns an, was diese Union tatsächlich darstellt und wie sie zur Übermittlung von Informationen zwischen dem EA und dem Dienst verwendet wird. Um die Komplexität zu verstehen, muss man zunächst wissen, wie viele Bits für jeden Datentyp verwendet werden. Siehe die Tabelle unten. Um Ihnen das Leben zu erleichtern, schlage ich vor, dass Sie sich mit der nachstehenden Tabelle vertraut machen:

Typ Anzahl der Bits
Bool 1 Bit
Char oder UChar 8 Bits
Short oder UShort 16 Bits
Int oder UInt 32 Bits
Long oder ULong 64 Bits

Diese Tabelle listet vorzeichenbehaftete und vorzeichenlose Integer-Typen und die Anzahl der Bits auf (verwechseln Sie Bits nicht mit Bytes). Ein Bit ist die kleinste Informationseinheit, die einen Ein- oder Aus-Zustand darstellt, oder 1 und 0 im Binärformat. Ein Byte ist eine Menge von Bits.

Wenn man sich diese Tabelle ansieht, ist die folgende Idee vielleicht nicht klar: In einer Variablen vom Typ uchar gibt es 8 Variablen vom Typ bool. Das heißt, eine uchar-Variable entspricht eine „Union“ (das Wort wäre nicht ganz das richtige) von 8 bool-Variablen. Im Code sieht das dann so aus:

union u_00
{
        char info;
        bool bits[8];
}Data;

Die Länge dieser Verbindung beträgt 8 Bits, also 1 Byte. Sie können den Inhalt der Informationen ändern, indem Sie die Bits in das Feld schreiben und eine bestimmte Position auswählen. Um zum Beispiel Data.info gleich 0x12 zu machen, können Sie eine der beiden unten gezeigten Möglichkeiten nutzen:

Data.info = 0x12;

or 

Data.bits[4] = true;
Data.bits[1] = true;

In jedem Fall erhalten wir das gleiche Ergebnis, wenn die Variable Data.info alle Anfangsbits auf 0 gesetzt hat. Das ist es, was eine Union ausmacht.

Kehren wir nun zu unserem ursprünglichen Code zurück. Der größte Typ auf einem 64-Bit-System ist der Typ long (mit Vorzeichen) oder ulong (ohne Vorzeichen). Wenn Sie ein Vorzeichen haben, können Sie negative Werte darstellen. Nur positive Zahlen können ohne Vorzeichen dargestellt werden. In diesem Fall würden wir also etwa so etwas erhalten:

Jedes der Quadrate steht für 1 Bit, und der Name „QWORD“ kommt von Assembly, der Muttersprache aller modernen Programmiersprachen. Die gleiche Struktur kommt in einem anderen Typ vor - Float.

Gleitkommazahlen sind Variablen, deren Wert nicht exakt ist, die aber dennoch verwendet werden können, um berechenbare Werte darzustellen. Es gibt im Wesentlichen 2 Arten:

Typ Anzahl der Bits
Float 32 Bits
Double 64 Bits

Dies ist ähnlich wie bei den oben beschriebenen Integer-Typen, bei denen jedes Bit einen Ein- oder Aus-Zustand darstellt. Bei Floats gibt es nicht den gleichen Wert, der die Logik repräsentiert. Sie folgen einem leicht abweichenden Schöpfungsprinzip, auf das wir jetzt aber nicht eingehen werden. Das wichtige Detail ist hier anders.

Wenn wir uns den Typ der globalen Terminalvariablen ansehen, stellen wir fest, dass sie nur vom Typ Float oder, genauer gesagt, double sind: 64 Bits. Frage: Welcher Ganzzahlentyp hat die gleiche Länge? Genau das, was Sie geantwortet haben: Der Typ long hat die gleiche Länge von 64 Bits. Wenn wir long und double vereinen, können wir zwei völlig unterschiedliche Dinge gleichzeitig darstellen.

Aber wir haben hier eine etwas komplizierte Frage: Woher wissen wir, welcher Typ verwendet wird? Um dieses Problem zu lösen, verwenden wir nicht den vollständigen Typ, sondern nur Fragmente davon, und weisen diesen Fragmenten Namen zu. Wir haben also eine Union , die Sie im Code der Datei Interprocess.mqh sehen können.

In der Tat werden wir double nicht verwenden. Der Versuch, einen Wert, der im Double-Typ erstellt wird, direkt manuell zu schreiben, ist völlig unpraktisch und überhaupt nicht einfach. Stattdessen verwenden wir die genannten Teile, um diese Erstellung vorzunehmen, und die richtigen Bits werden mit den richtigen Werten für 0 oder 1 gesetzt. Danach weisen wir den double Wert der globalen Terminalvariablen zu, und der andere Prozess, in diesem Fall der Dienst, nimmt den Wert und dekodiert ihn, wobei er genau weiß, was zu tun ist.

Sie sehen, dass alles nach sehr einfachen und leicht zu verstehenden Regeln abläuft. Dies wäre sehr schwierig, wenn wir versuchen würden, Floats direkt zu erstellen und dann ihre Bedeutung zu verstehen.

Ich glaube, es ist jetzt klar, was eine Union ist und wie wir sie nutzen werden. Aber denken Sie daran: Wenn Sie globale Terminalvariablen vom Typ double mit 64 Bits verwenden möchten, darf die erstellte Union diese 64 Bits nicht überschreiten, da sonst einige Informationen verloren gehen.


Verstehen, wie der Replay-Dienst erstellt wird

Dies ist wahrscheinlich der Teil, der von Ihnen die meiste Aufmerksamkeit erfordert, um zu verstehen, was vor sich geht. Wenn man etwas tust, ohne es zu verstehen, wirst man alles vermasseln. Das hört sich zwar einfach an, aber es gibt Details, die, wenn sie missverstanden werden, dazu führen können, dass Sie sich fragen, warum das System so funktioniert, wie es beschrieben und demonstriert wurde, aber Sie es auf Ihrem Arbeitsplatz nicht zum Laufen bringen können.

Schauen wir uns also den Replay-Dienst an. Sie ist derzeit noch recht kompakt und einfach. Der gesamte Code ist unten dargestellt:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //File with ticks
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        id = Replay.ViewReplay();
        Print("Waiting for permission to start replay ...");
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Replay service started ...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest)     bTest = (Replay.Event_OnTime() > 0); else       t1 = GetTickCount64();
                }else if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        Replay.CloseReplay();
        Print("Replay service finished ...");
}
//+------------------------------------------------------------------+

Wenn Sie diesen Code nehmen und die Datei „WINZ21_202110220900_202110221759“, die als Grundlage für die Erstellung des Replay verwendet wird, und Sie dann versuchen, sie auszuführen, werden Sie feststellen, dass nichts passiert. Auch wenn Sie die Datei im Anhang verwenden und versuchen, sie mit diesem Code auszuführen, wird nichts passieren. Aber warum? Der Grund liegt in id = Replay.ViewReplay(); Dieser Code tut etwas, das Sie verstehen müssen, um das System des Replay des Marktes tatsächlich nutzen zu können. Egal, was Sie tun: Wenn Sie nicht verstehen, was vor sich geht, wird nichts einen Sinn ergeben. Doch bevor wir uns den Code innerhalb von ViewReplay() ansehen, sollten wir zunächst den Datenfluss im obigen Code verstehen.

Um zu verstehen, wie das funktioniert, sollten wir es in kleinere Teile zerlegen und mit dem folgenden Fragment beginnen:

if (!Replay.CreateSymbolReplay(user01)) return;

Diese Zeile lädt die gehandelten Tickdaten aus der angegebenen Datei. Wenn das Laden fehlschlägt, wird der Dienst einfach beendet.

id = Replay.ViewReplay();

Mit dieser Zeile wird der EA geladen, aber das werden wir später noch genauer sehen, also machen wir weiter.

while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);

Die obige Zeile befindet sich in einer Schleife und wartet darauf, dass der EA geladen wird oder etwas anderes, um eine globale Terminalvariable zu erstellen. Dies dient als eine Form der Kommunikation zwischen Prozessen, die außerhalb der Dienstumgebung laufen.

t1 = GetTickCount64();

Diese Zeile führt die erste Erfassung für den internen Zähler des Dienstes durch. Diese erste Erfassung kann notwendig sein, muss es aber nicht. Normalerweise ist dies völlig unnötig, da das System sofort nach der Aktivierung in den Pausenmodus geht.

while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)))

Dieser Punkt ist sehr interessant. Wir haben hier zwei Tests. Wenn einer von ihnen ausfällt, wird der Replay-Dienst beendet. Im ersten Fallwird geprüft, ob das Fenster für den Replay-Dienst im Terminal vorhanden ist. Wenn der Händler dieses Fenster schließt, wird der Replay-Dienst beendet, da der Händler ein Replay nicht mehr durchführt. Im zweiten Fall testen wir und erfassen gleichzeitig den Wert der globalen Terminalvariablen. Wenn diese Variable nicht mehr existiert, wird auch der Dienst beendet.

        u_Interprocess Info;

//...

        if (!Info.s_Infos.isPlay)

Hier wird die Bedingung geprüft, die der Händler oder der Replay-Nutzer mitgegeben hat. Wenn wir uns im Play-Modus befinden, wird dieser Test fehlschlagen. Aber wenn wir im Pausenmodus sind, wird es gelingen. Beachten Sie, wie wir Union verwenden, um das richtige Bit innerhalb des double-Werts zu erfassen. Ohne diesen Zusammenschluss wäre dies nicht möglich.

Sobald wir uns im Pausenmodus befinden, führen wir die folgende Zeile aus:

if (!bTest) bTest = (Replay.Event_OnTime() > 0); else t1 = GetTickCount64();

Mit dieser Zeile kann nur der erste Tick an die Anlage gesendet werden. Dies ist aus einigen Gründen wichtig, die später noch erläutert werden. Sobald dies geschehen ist, wird jedes Mal, wenn der Replay-Dienst „pausiert“ wird, der aktuelle Wert des Zeitgebers erfasst. Es stimmt, dass sich dieser „Pausenmodus“ nicht darauf bezieht, dass der Dienst tatsächlich pausiert. Er sendet nur keine Ticks an das Symbol des Replays, deshalb sage ich, dass es „angehalten“ ist.

Wenn der Nutzer oder Händler jedoch die Markt-Replay starten oder fortsetzen möchte, geben wir eine neue Codezeile ein. Sie ist unten aufgeführt:

else if ((GetTickCount64() - t1) >= (uint)(delay))

Sie prüft anhand des Werts der Verzögerung zwischen den Ticks, ob ein neuer Tick gesendet werden muss oder nicht. Dieser Wert wird in der nächsten Codezeile ermittelt.

if ((delay = Replay.Event_OnTime()) < 0) break;

Beachten Sie, dass der Replay-Dienst beendet wird, wenn die Verzögerung kleiner als 0 ist. Dies geschieht in der Regel, wenn der letzte Handels-Tick an das Replay-Symbol gesendet wurde.

Diese Funktionen werden ausgeführt, bis der letzte Tick gesendet oder das Chart des Symbols mit dem Replay geschlossen wird. Wenn dies geschieht, wird die folgende Zeile ausgeführt:

Replay.CloseReplay();

Dadurch wird das Replay dauerhaft beendet.

Der gesamte Code ist sehr schön und einfach zu verstehen. Aber Sie haben vielleicht bemerkt, dass sich hier mehrere Punkte auf eine Klasse, C_Replay, beziehen. Schauen wir uns also diese Klasse an. Der Code hat viele Gemeinsamkeiten mit dem, was wir in früheren Artikeln gesehen haben. Aber es gibt einen Teil, der mehr Aufmerksamkeit verdient. Genau das werden wir uns jetzt ansehen.


ViewReplay der Klasse C_Replay. Warum ist sie so wichtig?

Dieser Code ist unten zu sehen:

long ViewReplay(void)
{
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);
        ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
        ChartRedraw(m_IdReplay);
        return m_IdReplay;
}

Sie denken jetzt vielleicht: Was ist an diesen 4 Zeilen so wichtig, um die Erstellung von Wiederholungen zu ermöglichen oder zu verhindern? Obwohl es sich um einen recht einfachen Code handelt, ist er sehr leistungsfähig. Er ist so mächtig, dass er sich selbst dann in den Weg stellt, wenn alles richtig zu sein scheint.

Werfen wir einen Blick auf diesen Moment. Als Erstes öffnen wir einen Chart mit dem Namen des Replay-Symbol und setzen den Zeitrahmen auf 1 Minute. Wie Sie aus den beiden vorangegangenen Artikeln ersehen können, können wir diese Zeit jederzeit ändern.

Sobald dies geschehen ist, laden wir eine bestimmte Vorlage und wenden sie auf das neu geöffnete Chartfenster an. Es ist wichtig zu beachten, dass diese Vorlage sehr spezifisch ist. Um diese Vorlage zu erstellen, wenn Sie sie gelöscht haben (sie befindet sich im Anhang), müssen Sie den EA aus dem Markt-Replay kompilieren und diesen EA auf einen beliebigen Vermögenswert anwenden. Dann speichern Sie dieses Chart als Vorlage und nennen es Market Replay, das ist alles. Wenn diese Datei nicht existiert oder der EA darin nicht vorhanden ist, wird das gesamte System fehlschlagen, egal was Sie getan haben.

In gewisser Weise könnte dieses Problem gelöst werden, wenn anstelle des EA ein Indikator verwendet würde. In diesem Fall würden wir diesen Indikator (theoretisch) über MQL5 aufrufen. Aber, wie ich zu Beginn dieses Artikels sagte, habe ich Gründe, einen EA anstelle eines Indikators zu verwenden. Um das Ladeproblem auf möglichst einfache Weise zu lösen, verwenden wir eine Vorlage, die den EA des Replay-Systems enthält.

Das allein garantiert jedoch noch nicht viel, denn wenn der EA geladen wird, erzeugt er eine globale Terminalvariable, die dem Dienst mitteilt, dass das System betriebsbereit ist. Es wird jedoch eine Weile dauern, bis die Kontrollen erscheinen. Um die Dinge ein wenig zu beschleunigen, verwenden wir einen Aufruf, um die Aktualisierung der Objekte in der Replay-Datei zu erzwingen.

Jetzt wird die ID der Replay-Tabelle zurückgeben, da wir dies an anderer Stelle nicht tun können. Wir benötigen diese Information, damit der Dienst weiß, wann die Karte geschlossen ist.

Alle anderen Funktionen der Klasse C_Replay sind recht einfach zu verstehen, sodass wir sie in diesem Artikel nicht weiter betrachten werden.


Schlussfolgerung

Im folgenden Video können Sie sehen, wie das System geladen wird und wie es in der Praxis funktioniert.



Im nächsten Artikel werden wir ein Positionskontrollsystem erstellen, mit dem wir festlegen können, zu welchem Zeitpunkt das Replay-System starten soll. Auf Wiedersehen!


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

Beigefügte Dateien |
Market_Replay.zip (10282.77 KB)
Kategorientheorie (Teil 9): Monoid-Aktionen Kategorientheorie (Teil 9): Monoid-Aktionen
Dieser Artikel setzt die Serie über die Implementierung der Kategorientheorie in MQL5 fort. Hier setzen wir Monoid-Aktionen als Mittel zur Transformation von Monoiden fort, die im vorigen Artikel behandelt wurden und zu mehr Anwendungen führen.
Wie man einen nutzerdefinierten Donchian Channel Indikator mit MQL5 erstellt Wie man einen nutzerdefinierten Donchian Channel Indikator mit MQL5 erstellt
Es gibt viele technische Hilfsmittel, die zur Visualisierung eines die Kurse umgebenden Kanals verwendet werden können. Eines dieser Hilfsmittel ist der Donchian Channel Indikator. In diesem Artikel erfahren Sie, wie Sie den Donchian Channel Indikator erstellen und wie Sie ihn als nutzerdefinierten Indikator mit EA handeln können.
Kategorientheorie in MQL5 (Teil 10): Monoide Gruppen Kategorientheorie in MQL5 (Teil 10): Monoide Gruppen
Dieser Artikel setzt die Serie über die Implementierung der Kategorientheorie in MQL5 fort. Hier betrachten wir Monoidgruppen als Mittel zur Normalisierung von Monoidmengen, um sie über eine größere Bandbreite von Monoidmengen und Datentypen hinweg vergleichbar zu machen.
Datenwissenschaft und maschinelles Lernen (Teil 14): Mit Kohonenkarten den Weg in den Märkten finden Datenwissenschaft und maschinelles Lernen (Teil 14): Mit Kohonenkarten den Weg in den Märkten finden
Sind Sie auf der Suche nach einem innovativen Ansatz für den Handel, der Ihnen hilft, sich auf den komplexen und sich ständig verändernden Märkten zurechtzufinden? Kohonenkarten (Kohonen maps), eine innovative Form künstlicher neuronaler Netze, können Ihnen helfen, verborgene Muster und Trends in Marktdaten aufzudecken. In diesem Artikel werden wir untersuchen, wie Kohonenkarten funktionieren und wie sie zur Entwicklung intelligenter und effektiverer Handelsstrategien genutzt werden können. Egal, ob Sie ein erfahrener Trader sind oder gerade erst anfangen, Sie werden diesen aufregenden neuen Ansatz für den Handel nicht verpassen wollen.