English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems — Marktsimulation (Teil 05): Hinzufügen einer Vorschau

Entwicklung eines Replay-Systems — Marktsimulation (Teil 05): Hinzufügen einer Vorschau

MetaTrader 5Beispiele | 1 September 2023, 13:08
349 0
Daniel Jose
Daniel Jose

Einführung

In einem früheren Artikel mit dem Titel „Entwicklung eines Replay-Systems — Marktsimulation (Teil 02): Erste Versuche (II)“ ist es uns gelungen, einen Weg zu finden, das Replay-System des Marktes auf realistische und zugängliche Weise zu implementieren. Bei dieser Methode wird jeder 1-Minuten-Balken innerhalb einer Minute erstellt. Wir müssen noch einige kleinere Anpassungen vornehmen, aber dazu später mehr. Dieser Schritt ist sehr wichtig, damit das System in der Lage ist, die Markt-Replay (Marktwiedergabe) zu simulieren.

Obwohl uns dies gelungen ist, scheint unser Replay-System nicht auszureichen, um ein bestimmtes Handelssystem zu erkunden und zu üben. Auf diese Weise ist es nicht möglich, eine Vorabanalyse der vorherigen Daten (sei es in Minuten, Stunden oder Tagen) vorzunehmen, sodass wir nicht wirklich etwas Brauchbares haben.

Wenn wir von Voranalyse sprechen, meinen wir die Möglichkeit, Balken zu haben, die dem Moment vorausgehen, in dem das System von einem bestimmten Punkt aus Balken erstellt. Ohne diese früheren Balken ist es unmöglich, verlässliche Informationen aus irgendeinem Indikator zu erhalten.

Sie können sich das so vorstellen: Wir haben eine Datei mit allen Ticks, die an einem bestimmten Tag gehandelt wurden. Wenn wir jedoch nur den Inhalt dieser Datei verwenden, werden wir nicht in der Lage sein, wirklich nützliche Informationen von irgendeinem Indikator zu erhalten. Selbst wenn wir z.B. einen gleitenden 3-Perioden-Durchschnitt verwenden, was genau das ist, was im JOE DI NAPOLI-System verwendet wird, wird das Signal nicht generiert, bevor nicht mindestens 3 Balken erzeugt wurden. Erst danach wird der gleitende Durchschnitt im Chart angezeigt. Aus der Sicht der praktischen Anwendung ist dieses System soweit noch völlig unbrauchbar und nicht funktionsfähig.

Stellen wir uns eine Situation vor, in der wir in einem Zeitrahmen von 5 Minuten recherchieren wollen. Wir müssen 15 Minuten warten, bis der gleitende 3-Perioden-Durchschnitt auf dem Chart erscheint. Es wird noch ein paar Minuten dauern, bis nützliche Signale erscheinen. Das heißt, das System muss aktualisiert werden, und der Zweck dieses Artikels ist es, zu erörtern, wie man diese Aktualisierung durchführt.


Verstehen einiger Details

Die Umsetzung ist recht einfach und kann relativ schnell durchgeführt werden. Doch bevor wir auch nur eine Zeile Code schreiben, müssen wir noch eine weitere Sache berücksichtigen.

Die Frage, die man sich stellen muss, lautet merkwürdigerweise: Welchen Zeitrahmen werden wir für das Replay verwenden, und welcher Zeitraum ist für jeden Indikator mindestens erforderlich, um nützliche Informationen zu erhalten? Diese Fragen sind sehr wichtig und erfordern kompetente Antworten. Es gibt kein Standardmodell, es hängt alles davon ab, wie wir unsere Analyse mit der Marktwiedergabe durchführen.

Aber wenn wir diese Fragen richtig beantworten, werden wir nicht nur auf das Replay beschränkt sein. Wir können auch einen Marktsimulator erstellen, der viel interessanter ist. Die Funktionsweise ist jedoch dieselbe wie bei der Wiederholung. Der einzige wirkliche Unterschied zwischen dem Replay und einem Simulator ist die Datenquelle (TICKS oder BARS), die zur Erstellung der Studie verwendet wird.

Im Falle der Wiedergabe sind die Daten real, während sie im Falle eines Simulators auf irgendeine Weise synthetisiert werden, entweder auf der Grundlage eines mathematischen Modells oder rein willkürlich. Im Falle eines Simulators ergibt sich etwas sehr Interessantes. Viele Unternehmen und sogar erfahrene Händler verwenden mathematische Methoden, um eine spezielle Art von Studie, den so genannten Stresstest, zu erstellen, bei dem sie Marktbewegungen simulieren, um mögliche extreme Bewegungen unter verschiedenen Szenarien vorherzusagen. Wir werden diese Frage jedoch später behandeln, da sie Aspekte betrifft, die noch nicht geklärt sind.

Aber keine Sorge, in der Zukunft, wenn dieses Replay-System weiter fortgeschritten ist, werde ich erklären, wie Sie solche Daten „erstellen“ können. Aber jetzt werden wir uns darauf konzentrieren, wie man reale Marktdaten, d.h. das Replay, verwendet.

Um die Anzahl der vorangegangenen Balken zu bestimmen, die in unserer Wiedergabe benötigt werden, müssen wir eine recht einfache Berechnung durchführen:


wobei N die erforderliche Anzahl von Balken, P die größte Periodenlänge, die wir verwenden oder benötigen, damit der Durchschnitt oder Indikator nützliche Informationen liefert, und T die Anzahl der Minuten in dem Zeitrahmen ist. Um dies zu verdeutlichen, sehen wir uns die folgenden Beispiele an:

Beispiel 1

Nehmen wir an, wir verwenden einen gleitenden Durchschnitt mit einer Periodenlänge von 200 und einen 5-Minuten-Zeitrahmen auf dem Chart. Welchen Mindestwert muss der Balken im Chart haben?

n = ( 200 * 5 ) + 5

Dies entspricht 1005, d.h. bevor das Reaplay beginnt, müssen Sie mindestens 1005 1-Minuten-Balken haben, um einen Durchschnitt von 200 bilden zu können. Dies bezieht sich auf die Verwendung eines Chart-Zeitrahmens von 5 Minuten oder weniger. Der Durchschnittswert wird also gleich zu Beginn der Wiedergabe angezeigt.

Beispiel 2

Zu Forschungszwecken wollen wir einen gleitenden Durchschnitt mit einer Periodenlänge von 21, einen gleitenden Durchschnitt mit einer Periodenlänge von 50, einen auf eine Periodenlänge von 14 eingestellten RSI und einen Intraday-VWAP verwenden. Wir werden Nachforschungen über Zeiträume von 5 und 15 Minuten anstellen. Von Zeit zu Zeit werden wir jedoch den 30-Minuten-Zeitrahmen überprüfen, um den Einstieg oder Ausstieg zu bestätigen. Wie viele Balken müssen wir mindestens haben, bevor wir das Replay starten?

Für eine unerfahrene Person mag diese Situation kompliziert erscheinen, aber in Wirklichkeit ist sie ganz einfach, erfordert aber Aufmerksamkeit für Details. Lassen Sie uns herausfinden, wie man die Mindestanzahl der Balken berechnet. Zunächst müssen wir die größte Periode finden, die in gleitenden Durchschnitten oder Indikatoren verwendet wird. In diesem Fall ist der Wert 50, da der VWAP ein zeitunabhängiger Indikator ist, um den wir uns nicht kümmern müssen. Dies ist der erste Punkt. Überprüfen wir nun den Zeitrahmen. In diesem Fall sollten wir 30 verwenden, da dies die längste der drei möglichen Angaben ist. Selbst wenn der 30-Minuten-Chart nur einmal verwendet wird, sollte er als Mindestwert angesehen werden. Dann sieht die Berechnung wie folgt aus:

n = ( 50 * 30 ) + 30

Dies ist gleich 1530, d. h. es müssen mindestens 1530 1-Minuten-Balken vorhanden sein, bevor die eigentliche Wiedergabe beginnt.

Obwohl dies viel zu sein scheint, ist dieser Wert noch weit von dem entfernt, was in extremeren Fällen erforderlich sein kann. Die Idee ist jedoch, dass wir immer eine viel größere Datenmenge haben sollten, als viele für die Forschung oder Analyse für notwendig halten.

Nun stellt sich eine weitere Frage. An der B3 (Brasilianische Börse) findet die Handelssitzung (Handelsperiode) in der Regel in mehreren Phasen und mit unterschiedlichen Zeitfenstern für einige Anlageklassen statt. Die Terminmärkte beginnen in der Regel um 9.00 Uhr morgens und enden um 18.00 Uhr, aber zu bestimmten Zeiten des Jahres wird dieser Zeitraum bis 18.30 Uhr verlängert, während der Aktienmarkt von 10.00 Uhr bis 18.00 Uhr geöffnet ist. Andere Märkte und Anlagen haben ihre eigenen spezifischen Öffnungszeiten. Daher ist es notwendig, diese Parameter vor der Angebotsabgabe zu überprüfen. Hier ist der Grund dafür. Da B3 ein Handelsfenster hat, müssen Sie oft mehrere Tage an Daten hochladen, um die berechnete Mindestanzahl an Balken zu erhalten.

Wichtiger Hinweis: Obwohl wir die Anzahl der Balken zählen, sollten Sie nicht vergessen, dass die Balken durch Ticks erzeugt werden, d.h. die erforderliche Datenmenge ist viel größer. Um Verwirrung zu vermeiden, ziehe ich es vor, zur Vereinfachung der Erklärung die Anzahl der Balken zu verwenden.

Deshalb brauchen wir eine andere Art der Berechnung. Damit wird die Mindestanzahl der zu erfassenden Tage festgelegt. Wir verwenden die folgende Formel:


wobei N die Anzahl der Tage, F die volle Stunde, zu der das Handelsfenster schließt, K die volle Stunde, zu der das Handelsfenster beginnt, und M die Anzahl der Minutenbruchteile in diesem Fenster ist. Zur Verdeutlichung betrachten wir das folgende Beispiel:

Der Handel mit Dollar-Futures begann um 9:00 Uhr und endete um 18:30 Uhr. Wie viele 1-Minuten-Balken hatten wir an diesem Tag?

n = (( 18 - 9 ) * 60 ) + 30

Dies entspricht 570 1-Minuten-Balken.

Normalerweise liegt dieser Wert bei 540 1-Minuten-Balken, da der Markt das Handelsfenster in der Regel um 18:00 Uhr schließt. Diese Zahl ist jedoch nicht genau, da der Handel während des Tages aufgrund von Auktionen, die untertags stattfinden können, unterbrochen werden kann. Das macht die Sache ein wenig komplizierter. Aber so oder so, Sie werden maximal 570 1-Minuten-Balken pro Handelstag haben, da Futures das größte Handelsfenster haben.

Wenn Sie dies wissen, können Sie die benötigte Anzahl der Balken durch die Anzahl der täglichen Balken teilen, die der Vermögenswert bei der Replikation verwendet. Dieser Tageswert ist ein Durchschnittswert. Sie gibt Ihnen die Mindestanzahl von Tagen an, die Sie kaufen müssen, damit die gleitenden Durchschnitte und Indikatoren korrekt im Chart angezeigt werden.


Umsetzung

Meine Absicht ist es, Sie zu motivieren, Ihre eigenen Lösungen zu entwickeln. Vielleicht wiederhole ich mich jetzt, aber kleine Änderungen an einem System können einen großen Unterschied machen. Da wir ein Kodierungsmodell verwenden müssen, sollten wir versuchen, den Code so schnell wie möglich zu halten. Nachstehend finden Sie die erste Änderung:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user00 = "WIN$N_M1_202108030900_202108051754";  //File with bars ( Prev. )
input string    user01 = "WINQ21_202108060900_202108061759";    //File with ticks ( Replay )
//+------------------------------------------------------------------+
C_Replay        Replay;

Wir haben eine Zeile hinzugefügt, in der angegeben wird, welche Datei die zu verwendenden früheren Daten enthält. Wir werden dies in Zukunft ändern, aber im Moment bleibt es so. Ich möchte nicht, dass Sie bei der Kodierung verwirrt werden. Der Code wird schrittweise geändert, sodass Sie daraus lernen können.

Die Tatsache, dass wir nur eine Datei angeben können, bedeutet, dass wir MetaTrader 5 zwingen müssen, eine Datei mit mehreren Tagen zu erzeugen, wenn wir mehrere Tage verwenden wollen. Das hört sich kompliziert an, ist aber viel einfacher und leichter, als es scheint.


Bitte beachten Sie, dass die Daten auf dem Bild oben unterschiedlich sind. So werden wir alle benötigten Balken befestigen. Vergessen Sie nicht, dass wir insgesamt 1605 1-Minuten-Balken erfasst haben, was uns einen großen Aktionsradius ermöglicht. Bei den Beispielen mit Berechnungen gab es weniger davon.

Aber warum sollten wir 1-Minuten-Balken verwenden? Warum können wir nicht 5 oder 15 Minuten-Balken verwenden?

Wenn wir nämlich 1-Minuten-Balken haben, können wir jeden beliebigen Zeitrahmen verwenden. Die MetaTrader 5-Plattform erstellt verschiedene Zeitrahmen, was sehr praktisch ist, da wir uns keine Gedanken über Anpassungen machen müssen. Wenn wir nun eine Datenbank mit z.B. 15-Minuten-Balken verwenden, können wir andere Zeitrahmen nicht auf einfache Weise nutzen. Das liegt daran, dass wir uns bei der Verwendung von 1-Minuten-Balken nicht auf die Unterstützung verlassen können, die die MetaTrader 5-Plattform bietet. Aus diesem Grund sollten wir vorzugsweise immer 1-Minuten-Balken verwenden.

Nun, es ist möglich, einen anderen Zeitrahmen zu verwenden. Aber wir werden das hier nicht diskutieren. Das werden wir sehen, wenn wir uns mit Fragen der Simulation beschäftigen.

Aber auch wenn Sie nicht mehrere Tage erfassen wollen, können Sie die Tage einzeln erfassen und die Datei dann zusammenführen. Aber verzweifeln Sie noch nicht. Es gibt eine andere Lösung, damit Sie so etwas nicht tun müssen. Aber ich werde hier in diesem Artikel nicht darüber sprechen. Die nächsten Schritte des Codes sind unten zu sehen:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        Replay.LoadPrevBars(user00);
        if (!Replay.LoadTicksReplay(user01)) return;
        Print("Aguardando permissão para iniciar replay ...");

Wir deklarieren einige Variablen, die der Dienst verwenden wird. Danach erstellen wir einen Aufruf zur Bestimmung des Symbols, das in der Wiedergabe verwendet werden soll. Es ist wichtig, dass dieser Aufruf wirklich der erste ist, da alles andere direkt innerhalb des erstellten Symbols geschieht.

Jetzt laden wir die historischen Balken. Beachten Sie, dass es hier eine logische Abfolge gibt, aber diese Funktion des Ladens früherer Balken besitzt einige interessante Details, die wir noch nicht erforscht haben. Fahren wir nun fort und sehen wir uns an, wie das System hochfährt.

Nach dem Laden der vorherigen Werte, sofern vorhanden, laden wir die gehandelten Ticks. Das ist der Moment, in dem wir uns wirklich auf die Wiederholung vorbereiten. Wenn alles in Ordnung ist, sollte auf dem Terminal eine Meldung erscheinen, dass sich das System im Standby-Modus befindet.

        id = Replay.ViewReplay();
        while (!GlobalVariableCheck(def_GlobalVariableReplay)) Sleep(750);
        Print("Permission granted. Replay service can now be used...");
        t1 = GetTickCount64();

Das System befindet sich nun in diesem Zustand und wartet darauf, dass das Replay-TEMPLATE geladen wird. Das Chart des Replay sollte ebenfalls geöffnet und angezeigt werden. Wenn dies geschieht, wird die Warteschleife beendet,

denn in diesem Fall wird eine globale Terminalvariable erstellt, die eine Verbindung zwischen dem Terminaldienst und dem Kontrollindikator herstellt. Wir haben diesen Indikator bereits erstellt und diskutiert. Sie können dies in den Artikeln „Entwicklung eines Replay-Systems - Marktsimulation (Teil 03): Anpassung der Einstellungen (I)“ und „Entwicklung eines Replay-Systems - Marktsimulation (Teil 04): Anpassung der Einstellungen (II)“ nachlesen.

Sobald der Kontrollindikator geladen ist, wird eine Meldung angezeigt. Der Nutzer kann nun im System auf „Play“ drücken. Schließlich erfassen wir die Anzahl der Ticks der CPU, um zu überprüfen, wie viel Zeit vergangen ist.

Danach geht es in die Schleife, die bereits in früheren Artikeln ausführlich beschrieben wurde. Dies war jedoch nur der erste Teil der Umsetzung. Wir haben noch andere Details, die besondere Aufmerksamkeit verdienen.


Die neue Klasse C_Replay

Merkwürdigerweise musste ich einige interne Änderungen an der Klasse vornehmen, die für die Aufrechterhaltung der Wiedergabe verantwortlich ist. Ich werde die neuen Änderungen nach und nach erläutern, damit Sie verstehen, worum es sich handelt und warum sie vorgenommen wurden. Beginnen wir mit den Variablen. Sie sind jetzt anders.

int      m_ReplayCount;
datetime m_dtPrevLoading;
long     m_IdReplay;
struct st00
{
        MqlTick Info[];
        int     nTicks;
}m_Ticks;

Diese neue Variablen werden für unsere Arbeit ausreichen. Wenn Sie sich den obigen Code ansehen, werden Sie feststellen, dass die Umfang jetzt anders ist. Infolgedessen haben sich auch andere Komponenten verändert. Wir haben jetzt eine neue Funktion zur Initialisierung von Wiedergabesymbolen.

void InitSymbolReplay(void)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol);
        SymbolSelect(def_SymbolReplay, true);
}

Beginnen wir damit, das Wiedergabesymbol aus dem Marktbeobachtungsfenster zu entfernen, falls vorhanden. Dies geschieht nur, wenn dies nicht das Symbol des offenen Charts ist.

Wir löschen es dann und erstellen es als nutzerdefiniertes Symbol. Aber warum diese ganze Arbeit? Später werden wir den Grund dafür verstehen. Nachdem wir ein nutzerdefiniertes Symbol erstellt haben, platzieren wir es im Fenster der Marktübersicht, damit wir es kontrollieren und auf dem Chart platzieren können. Diese Initialisierungsfunktion wird sich in der Zukunft ändern, aber im Moment ist sie für unsere Bedürfnisse ausreichend. Denken Sie daran, dass Sie zuerst das System zum Laufen bringen müssen, bevor Sie Änderungen vornehmen.

Die nächste Funktion, an der Änderungen vorgenommen wurden, ist das Laden von gehandelten Ticks.

#define macroRemoveSec(A) (A - (A % 60))
                bool LoadTicksReplay(const string szFileNameCSV)
                        {
                                int     file,
                                        old;
                                string  szInfo;
                                MqlTick tick;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        Print("Loading replay ticks. Wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray);
                                        old = m_Ticks.nTicks = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file) + " " + FileReadString(file);
                                                tick.time = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                tick.bid = StringToDouble(FileReadString(file));
                                                tick.ask = StringToDouble(FileReadString(file));
                                                tick.last = StringToDouble(FileReadString(file));
                                                tick.volume_real = StringToDouble(FileReadString(file));
                                                tick.flags = (uchar)StringToInteger(FileReadString(file));
                                                if ((m_Ticks.Info[old].last == tick.last) && (m_Ticks.Info[old].time == tick.time) && (m_Ticks.Info[old].time_msc == tick.time_msc))
                                                        m_Ticks.Info[old].volume_real += tick.volume_real;
                                                else
                                                {
                                                        m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                        m_Ticks.nTicks += (tick.volume_real > 0.0 ? 1 : 0);
                                                        old = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : old);
                                                }
                                        }
                                }else
                                {
                                        Print("Tick file ", szFileNameCSV,".csv not found...");
                                        return false;
                                }
                                return true;
                        };
#undef macroRemoveSec

Zunächst versuchen wir, eine Datei zu öffnen, die die Ticks der abgeschlossenen Geschäfte enthalten soll. Achten Sie auf diese Phase, hier gibt es noch keine Kontrollen. Seien Sie daher vorsichtig, wenn Sie die richtige Datei angeben.

Die Datei muss sich im angegebenen Ordner im MQL5-Verzeichnis befinden. Allerdings, müssen wir die Dateierweiterung nicht angeben, da die Standardeinstellung CSV ist. Wenn die Datei gefunden wird, fahren wir mit dem Laden fort. Wenn die Datei nicht gefunden wird, wird der Replay-Dienst nicht gestartet, und im Meldungsfeld wird eine Meldung mit dem Grund für den Fehler angezeigt.

Mal sehen, was passiert, wenn die Datei mit den gehandelten Ticks gefunden wird. In diesem Fall wird als erstes der Inhalt der ersten Zeile der Datei übersprungen, der jetzt nicht benötigt wird. Danach treten wir in die Schleife ein, um die Informationen zu lesen. Als Erstes erfassen wir das Datum und die Uhrzeit, zu der der Tick gehandelt wurde, und speichern die Daten dann an einem temporären Ort. Wir erfassen dann jeden der Werte in der richtigen Reihenfolge. Bitte beachten Sie, dass wir nichts angeben müssen, es reicht aus, das CSV-Dateiformat zu haben - und MQL5 sollte das Lesen korrekt durchführen.

Nun prüfen wir die folgende Bedingung: Wenn der Preis und die Zeit des letzten Abschlusses mit denen des aktuellen Abschlusses übereinstimmen, dann wird das aktuelle Volumen zum vorherigen addiert. Damit dies geschieht, müssen also beide Prüfungen wahr sein.

Wie Sie sehen, ist mir die Flag-Frage egal, d. h. es spielt keine Rolle, ob es sich um einen Kauf oder einen Verkauf handelt. Da die anderen Dinge gleich sind, wird sich dies nicht auf die Wiederholung auswirken, zumindest nicht im Moment, da sich der Preis nicht bewegt hat. Wenn die Prüfbedingung fehlschlägt, haben wir nun einen Hinweis darauf, dass wir einen anderen Tick lesen. In diesem Fall fügen wir sie zu unserer Tick-Matrix hinzu. Ein wichtiges Detail: Handelt es sich bei einem Tick um eine BID- oder ASK-Anpassung, so ist kein Volumen enthalten. Daher wird keine neue Position hinzugefügt, und diese Position wird überschrieben, wenn ein neuer Tick gelesen wird. Wenn jedoch ein gewisses Volumen vorhanden ist, wird die Position erhöht.

Diese Schleife wird fortgesetzt, bis die Datei endet oder das Tick-Limit erreicht ist. Aber seien Sie vorsichtig, denn wir haben noch keine Tests durchgeführt. Da das System noch recht einfach ist und die verwendete Datenmenge nicht groß ist, können einige kleinere Mängel vernachlässigt werden. Im Laufe der Zeit wird der Umfang der Kontrollen in dieser Funktion jedoch zunehmen, um Probleme zu vermeiden, die uns bisher noch nicht viel kosten.

Der Code für die nächste Funktion ist unten dargestellt.

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) FileReadString(file);
                Print("Loading preview bars to Replay. Wait ....");
                while (!FileIsEnding(file))
                {
                        Rate[0].time = StringToTime(FileReadString(file) + " " + FileReadString(file));
                        Rate[0].open = StringToDouble(FileReadString(file));
                        Rate[0].high = StringToDouble(FileReadString(file));
                        Rate[0].low = StringToDouble(FileReadString(file));
                        Rate[0].close = StringToDouble(FileReadString(file));
                        Rate[0].tick_volume = StringToInteger(FileReadString(file));
                        Rate[0].real_volume = StringToInteger(FileReadString(file));
                        Rate[0].spread = (int) StringToInteger(FileReadString(file));
                        iAdjust = ((dt != 0) && (iAdjust == 0) ? (int)(Rate[0].time - dt) : iAdjust);
                        dt = (dt == 0 ? Rate[0].time : dt);
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
                m_dtPrevLoading = Rate[0].time + iAdjust;
                FileClose(file);
        }else
        {
                Print("Failed to access the previous bars data file.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return true;
}

Der obige Code lädt alle früheren Balken, die für die angegebene Datei benötigt werden oder ihr gehören, in das Replay hoch. Anders als bei der vorherigen Funktion, bei der es notwendig war, Informationen für eine spätere Verwendung zu speichern, werden wir hier jedoch anders vorgehen. Wir lesen Informationen und fügen sie sofort dem Symbol hinzu. Folgen Sie den Erklärungen, um zu verstehen, wie es funktioniert.

Zunächst werden wir versuchen, eine Datei mit Balken zu öffnen. Beachten Sie, dass wir denselben Befehl verwenden, den wir auch für die Zecken verwendet haben. Der Speicherort und der Inhalt der Datei sind jedoch unterschiedlich, aber die Art und Weise, wie man auf die Informationen zugreift, ist genau dieselbe. Wenn wir die Datei erfolgreich öffnen, überspringen wir zunächst die erste Zeile, da sie uns im Moment nicht interessiert.

Dieses Mal geben wir die Schleife ein, die erst bei der letzten Zeile der Datei endet. Es gibt keine Begrenzung für die Größe oder Menge der Daten in dieser Datei. Infolgedessen werden alle Daten gelesen. Die gelesenen Daten folgen dem OHCL-Modell, weshalb wir sie in einer temporären Struktur ablegen. Achten Sie nun auf Folgendes: Wie können wir wissen, an welchem Punkt die Wiederholung beginnt und wo die vorherigen Balken enden?

Außerhalb dieser Funktion wäre es kompliziert. Aber hier haben wir eine genaue Angabe, wo die vorherigen Balken enden und das Replay beginnt. Genau an diesem Punkt speichern wir diese temporäre Position für eine spätere Verwendung. Beachten Sie nun, dass wir mit jeder gelesenen Balkenreihe die im Wiedergabesymbol enthaltenen Balken sofort aktualisieren. Auf diese Weise haben wir später keine zusätzliche Arbeit. Wenn die vorherigen Balken gelesen wurden, können wir also unsere Indikatoren hinzufügen.

Kommen wir zur nächsten Funktion, die recht einfach, aber sehr wichtig ist.

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

Es öffnet den Chart des Wiederholungssymbols im Standard-Zeitrahmen von 1 Minute. Sie können einen anderen Standardzeitrahmen verwenden, um ihn nicht ständig ändern zu müssen.

Danach laden wir die Vorlage, die den Replay-Indikator enthalten soll. Wenn der Indikator nicht in der Vorlage enthalten ist, können wir den Dienst nicht abspielen. Es ist zwar möglich, eine andere Vorlage zu verwenden, aber in diesem Fall müssen wir den Replay-Indikator manuell zum Symbolchart hinzufügen, um das Replay zu starten. Wenn Sie dies manuell tun möchten, ist das in Ordnung und steht Ihnen frei. Wenn wir jedoch eine Vorlage mit dem Indikator verwenden, haben wir sofortigen Zugang zum System, da wir eine Aktualisierung des Charts erzwingen und den Index des Charts zurückgeben, um zu prüfen, ob er verfügbar ist oder nicht.

Da wir das System starten, müssen wir es auch ausschalten, und dafür haben wir eine weitere Funktion.

void CloseReplay(void)
{
        ChartClose(m_IdReplay);
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        GlobalVariableDel(def_GlobalVariableReplay);
}

Entscheidend ist, dass die Reihenfolge, in der die Maßnahmen durchgeführt werden, sehr wichtig ist. Wenn wir die Reihenfolge ändern, könnte es anders laufen als erwartet. Daher schließen wir zuerst das Symbolchart, entfernen es aus dem Marktbeobachtungsfenster, entfernen es aus der Liste der nutzerdefinierten Symbole und entfernen schließlich die globale Variable aus dem Terminal. Danach wird der Replay-Dienst geschlossen.

Auch bei der folgenden Funktion wurden einige Änderungen vorgenommen.

inline int Event_OnTime(void)
{
        bool    bNew;
        int     mili, iPos;
        u_Interprocess Info;
        static MqlRates Rate[1];
        static datetime _dt = 0;
                                
        if (m_ReplayCount >= m_Ticks.nTicks) return -1;
        if (bNew = (_dt != m_Ticks.Info[m_ReplayCount].time))
        {
                _dt = m_Ticks.Info[m_ReplayCount].time;
                Rate[0].real_volume = 0;
                Rate[0].tick_volume = 0;
        }
        mili = (int) m_Ticks.Info[m_ReplayCount].time_msc;
        do
        {
                while (mili == m_Ticks.Info[m_ReplayCount].time_msc)
                {
                        Rate[0].close = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].open = (bNew ? Rate[0].close : Rate[0].open);
                        Rate[0].high = (bNew || (Rate[0].close > Rate[0].high) ? Rate[0].close : Rate[0].high);
                        Rate[0].low = (bNew || (Rate[0].close < Rate[0].low) ? Rate[0].close : Rate[0].low);
                        Rate[0].real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                        bNew = false;
                        m_ReplayCount++;
                }
                mili++;
        }while (mili == m_Ticks.Info[m_ReplayCount].time_msc);
        Rate[0].time = m_Ticks.Info[m_ReplayCount].time;
        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
        iPos = (int)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
        GlobalVariableGet(def_GlobalVariableReplay, Info.Value);
        if (Info.s_Infos.iPosShift != iPos)
        {
                Info.s_Infos.iPosShift = iPos;
                GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
        }
        return (int)(m_Ticks.Info[m_ReplayCount].time_msc < mili ? m_Ticks.Info[m_ReplayCount].time_msc + (1000 - mili) : m_Ticks.Info[m_ReplayCount].time_msc - mili);
}

Die obige Funktion erstellt einen 1-Minuten-Balken und fügt ihn dem Wiedergabesymbol hinzu. Aber hier haben wir etwas, das wir beachten sollten: Warum zeigt das Replay einige Dinge und andere nicht?

Sie haben vielleicht bemerkt, dass das Tick-Volumen immer Null ist. Warum? Um dies zu verstehen, müssen Sie die Dokumentation von „Reale und generierte Ticks“ lesen. Wenn Sie die Beschreibung gelesen und nicht verstanden haben oder nicht verstanden haben, warum das Volumen der Ticks immer Null ist, ignorieren Sie eine wichtige Tatsache. Versuchen wir also zu verstehen, warum dieser Wert immer Null sein wird.

Wenn wir Daten über gehandelte Ticks lesen, lesen wir echte Ticks, d.h. das dort angezeigte Volumen ist das echte Volumen. Wenn der Auftrag beispielsweise ein Volumen von 10 hat, dann bedeutet dies, dass ein Handel mit einem realen Volumen von 10 erforderlichen Mindestlots durchgeführt wurde und nicht, dass 10 Ticks verwendet wurden. Es ist unmöglich, ein Volumen von 1 gehandelten Tick zu erzeugen. Es gibt jedoch eine Situation, in der wir ein Tick-Volumen haben, und zwar dann, wenn ein Auftrag eröffnet und geschlossen wird, was zu einem Volumen von 2 Ticks führt. In der Praxis beträgt unser Mindestvolumen jedoch 3 Ticks. Da wir reale Ticks verwenden, müssen wir eine Anpassung an den dargestellten Wert vornehmen. Zu diesem Zeitpunkt habe ich diese Berechnung noch nicht in das Replay-System integriert, sodass nur der tatsächlich gehandelte Wert angezeigt wird.

Hier ist Vorsicht geboten, denn auch wenn es so aussieht, als wäre es dasselbe, zeigt das tatsächliche Volumen an, wie viele Abschlüsse tatsächlich getätigt wurden, während das Tick-Volumen anzeigt, wie viele Bewegungen stattgefunden haben.

Ich verstehe, dass dies im Moment sehr verwirrend und schwer zu verstehen ist, aber in den folgenden Artikeln, wenn wir uns mit dem Forex-Markt beschäftigen und erörtern, wie man Replay und Simulation auf diesem Markt durchführt, wird alles klarer werden. Deshalb sollten Sie nicht versuchen, jetzt alles zu verstehen – mit der Zeit werden Sie es verstehen.

Die letzte Funktion, die in diesem Artikel behandelt wird, ist unten dargestellt:

int AdjustPositionReplay()
{
        u_Interprocess Info;
        MqlRates Rate[1];
        int iPos = (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks);
                                
        Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
        if (Info.s_Infos.iPosShift == iPos) return 0;
        iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / def_MaxPosSlider));
        if (iPos < m_ReplayCount)
        {
                CustomRatesDelete(def_SymbolReplay, m_dtPrevLoading, LONG_MAX);
                m_ReplayCount = 0;
                if (m_dtPrevLoading == 0)
                {
                        Rate[0].close = Rate[0].open = Rate[0].high = Rate[0].low = m_Ticks.Info[m_ReplayCount].last;
                        Rate[0].tick_volume = 0;
                        Rate[0].time = m_Ticks.Info[m_ReplayCount].time - 60;
                        CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                }
        };
        for (iPos = (iPos > 0 ? iPos - 1 : 0); m_ReplayCount < iPos; m_ReplayCount++) Event_OnTime();
        return Event_OnTime();
}

Diese Funktion unterscheidet sich nur in einem Punkt von den vorhergehenden, und dieser Punkt ermöglicht es uns, mit dem Einfügen von gehandelten Ticks zu beginnen. Alles, was oberhalb dieses Punktes liegt, wird also als Wiedergabe echter Ticks betrachtet, und alles, was unterhalb dieses Punktes liegt, sind Werte aus früheren Balken. Wenn wir an dieser Stelle nur einen Wert gleich Null übergeben, würden alle im Wiedergabesymbol aufgezeichneten Informationen gelöscht, und wir müssten die 1-Minuten-Datenbalken erneut lesen, um die Werte der vorherigen Balken zu erhalten. Das wäre überflüssig und würde die Arbeit erheblich erschweren. Wir brauchen also nur den Schwellenwert anzugeben, und MetaTrader 5 entfernt die Balken, die durch echte Ticks entstanden sind, was uns das Leben sehr erleichtert.


Schlussfolgerung

Alles Weitere in der Funktion wurde bereits im Artikel Entwicklung eines Replay-Systems - Marktsimulation (Teil 04): Anpassung der Einstellungen (II) beschrieben, sodass wir hier nicht im Einzelnen darauf eingehen werden.

Im folgenden Video können Sie das System in Aktion sehen. Es wird gezeigt, wie verschiedene Indikatoren zu einem Replay-System hinzugefügt werden können.




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

Beigefügte Dateien |
Market_Replay.zip (6102.2 KB)
Die Handelstechnik RSI Deep Three Move Die Handelstechnik RSI Deep Three Move
Vorstellung der Handelstechnik RSI Deep Three Move für MetaTrader 5. Dieser Artikel basiert auf einer neuen Reihe von Studien, die einige Handelstechniken auf der Grundlage des RSI aufzeigen. Der RSI ist ein Indikator der technischen Analyse, der zur Messung der Stärke und Dynamik eines Wertpapiers, z. B. einer Aktie, einer Währung oder eines Rohstoffs, verwendet wird.
DoEasy. Steuerung (Teil 32): Horizontale ScrollBar, Scrollen mit dem Mausrad DoEasy. Steuerung (Teil 32): Horizontale ScrollBar, Scrollen mit dem Mausrad
In diesem Artikel werden wir die Entwicklung der Funktionalität des horizontalen Rollbalkenobjekts abschließen. Wir werden auch die Möglichkeit schaffen, den Inhalt des Containers durch Bewegen des Schiebereglers und Drehen des Mausrades zu scrollen, sowie Ergänzungen zur Bibliothek vornehmen, die die neue Auftragsausführungspolitik und die neuen Laufzeitfehlercodes in MQL5 berücksichtigen.
Alles, was Sie über die MQL5-Programmstruktur wissen müssen Alles, was Sie über die MQL5-Programmstruktur wissen müssen
Jedes Programm in jeder Programmiersprache hat eine bestimmte Struktur. In diesem Artikel lernen Sie wesentliche Teile der MQL5-Programmstruktur kennen, indem Sie die Programmiergrundlagen jedes Teils der MQL5-Programmstruktur verstehen, die bei der Erstellung unseres MQL5-Handelssystems oder -Handelswerkzeugs, das im MetaTrader 5 ausführbar ist, sehr hilfreich sein können.
Die ChatGPT-Funktionen von OpenAI im Rahmen der MQL4- und MQL5-Entwicklung Die ChatGPT-Funktionen von OpenAI im Rahmen der MQL4- und MQL5-Entwicklung
In diesem Artikel werden wir uns mit ChatGPT von OpenAI beschäftigen, um zu verstehen, welche Möglichkeiten es bietet, den Zeit- und Arbeitsaufwand für die Entwicklung von Expert Advisors, Indikatoren und Skripten zu reduzieren. Ich werde Sie schnell durch diese Technologie führen und versuchen, Ihnen zu zeigen, wie Sie sie für die Programmierung in MQL4 und MQL5 richtig einsetzen.