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

Entwicklung eines Replay-Systems — Marktsimulation (Teil 06): Erste Verbesserungen (I)

MetaTrader 5Tester | 21 September 2023, 10:44
248 0
Daniel Jose
Daniel Jose

Einführung

In dieser Artikelserie wird unser Replay System zusammen mit seiner Entstehung vorgestellt, um den Lesern zu zeigen, wie die Dinge wirklich beginnen, bevor sie eine genauere Form annehmen. Dank dieses Prozesses können wir uns langsam und vorsichtig bewegen, was uns die Möglichkeit gibt, viele Dinge zu schaffen, zu löschen, hinzuzufügen und zu verändern. Der Grund dafür ist, dass das Replay System zeitgleich mit der Veröffentlichung der Artikel erstellt wird. Daher werden vor der Veröffentlichung des Artikels einige Tests durchgeführt, um sicherzustellen, dass das System stabil und funktionsfähig ist, während es sich in der Modellierungs- und Anpassungsphase befindet.

Kommen wir nun zum Kern der Sache. Im vorherigen Artikel „Entwicklung eines Replay-Systems - Marktsimulation (Teil 05): Hinzufügen von Vorschauen“ haben wir ein System zum Laden des Vorschaufensters erstellt. Obwohl dieses System funktioniert, haben wir noch einige Probleme. Das drängendste Problem besteht darin, dass man, um eine besser nutzbare Datenbank zu erhalten, eine bereits bestehende Strichliste erstellen muss, die oft mehrere Tage umfasst und keine Dateneinfügung zulässt.

Wenn wir eine Vorschaudatei erstellen, die z. B. Daten für eine Woche, von Montag bis Freitag, enthält, können wir nicht dieselbe Datenbank verwenden, um z. B. eine Wiederholung für Donnerstag zu erstellen. Dies würde die Einrichtung einer neuen Datenbank erfordern. Und es ist schrecklich, wenn man darüber nachdenkt.

Zusätzlich zu diesen Unannehmlichkeiten haben wir noch andere Probleme, wie z. B. das völlige Fehlen von angemessenen Tests, um sicherzustellen, dass wir die richtige Datenbank verwenden. Aus diesem Grund kann es vorkommen, dass wir versehentlich Balkendateien so verwenden, als ob es sich um Tickdaten von ausgeführten Geschäften handelt oder umgekehrt. Dies verursacht schwerwiegende Störungen in unserem System und verhindert, dass es richtig funktioniert. Im Rahmen dieses Artikels werden wir auch andere kleine Änderungen vornehmen. Kommen wir zum Punkt.

Denken Sie daran, dass wir jeden einzelnen Punkt durchgehen werden, um zu verstehen, was passiert.


Umsetzung der Verbesserungen

Die erste Änderung, die wir vornehmen müssen, besteht darin, zwei neue Zeilen in die Servicedatei einzufügen. Sie sind nachstehend aufgeführt:

#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence

Warum müssen wir das tun? Aus dem einfachen Grund, dass das System aus Modulen besteht und wir irgendwie sicherstellen müssen, dass sie vorhanden sind, wenn wir das Replay System verwenden. Das wichtigste Modul ist genau dieser Indikator, da er für eine gewisse Kontrolle über das, was getan wird, verantwortlich ist.

Ich gebe zu, das ist nicht der beste Weg. Vielleicht werden die Entwickler der MetaTrader 5-Plattform und der MQL5-Sprache in Zukunft einige Ergänzungen vornehmen, z. B. Kompilierungsanweisungen, damit wir tatsächlich sicherstellen können, dass die Datei kompiliert ist oder existieren sollte. Aber in Ermangelung einer anderen Lösung werden wir es auf diese Weise tun.

Okay, jetzt richten wir eine Richtlinie ein, die angibt, dass der Dienst von etwas anderem abhängt. Diese fügen wir dann als Ressource zum Dienst hinzu. Ein wichtiger Punkt: Wenn wir dieses Element in eine Ressource umwandeln, können wir es nicht unbedingt als Ressource verwenden. Dieser Fall ist besonders.

Diese beiden einfachen Zeilen stellen sicher, dass der Indikator in der Vorlage zur Verwendung durch Replay tatsächlich kompiliert wird, wenn der Antwortdienst kompiliert wird. Wenn wir dies vergessen und die Vorlage den Indikator lädt und dieser nicht gefunden wird, kommt es zu einem Fehler, den wir erst bemerken, wenn wir feststellen, dass der Indikator, der zur Steuerung des Dienstes verwendet wird, nicht im Chart zu finden ist.

Um dies zu vermeiden, haben wir bereits dafür gesorgt, dass der Indikator zusammen mit dem Dienst erstellt wird. Wenn wir jedoch eine nutzerdefinierte Vorlage verwenden und dann den Kontrollindikator manuell hinzufügen, können wir diese beiden Zeilen über dem Servicecode entfernen. Das Fehlen solcher Anzeigen hat keinen Einfluss auf den Code oder den Betrieb.

HINWEIS: Selbst wenn wir die Kompilierung erzwingen, wird sie nur dann durchgeführt, wenn die ausführbare Datei des Indikators nicht existiert. Wenn der Indikator geändert wird, führt das Kompilieren nur des Dienstes nicht dazu, dass der Indikator erstellt wird.

Manch einer könnte sagen, dass dies mit dem Projektmodus von MetaEditor gelöst werden könnte. Dieser Modus erlaubt es uns jedoch nicht, so zu arbeiten wie in Sprachen wie C/C++, wo wir eine MAKE-Datei zur Steuerung der Kompilierung verwenden. Man könnte das auch mit einer BATCH-Datei machen, aber dann müssten wir MetaEditor beenden, um den Code zu kompilieren.

Damit haben wir nun zwei neue Zeilen:

input string            user00 = "Config.txt";  //Replay configuration file
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Initial timeframe

Hier ist etwas wirklich Nützliches für unseren Replay-Dienst: Diese Zeile ist der Name der Datei, die die Einstellungen für das Wiedergabesymbol enthält. Zu diesen Einstellungen gehört zunächst, welche Dateien für die Generierung früherer Balken verwendet werden und welche Dateien für die Speicherung der gehandelten Ticks verwendet werden.

Jetzt können wir mehrere Dateien gleichzeitig verwenden. Ich weiß auch, dass viele Händler gerne einen bestimmten Zeitrahmen verwenden, wenn sie auf dem Markt handeln, und gleichzeitig den Chart im Vollbildmodus nutzen möchten. In dieser Zeile können wir also den Zeitrahmen festlegen, der von Anfang an verwendet werden soll. Das ist sehr einfach und auch viel bequemer, weil wir alle diese Einstellungen zur weiteren Verwendung speichern können. Wir können hier noch mehr Dinge hinzufügen, aber für den Moment ist es gut.

Jetzt müssen wir noch ein paar weitere Punkte verstehen, die im folgenden Code zu sehen sind:

void OnStart()
{
        ulong t1;
        int delay = 3;
        long id = 0;
        u_Interprocess Info;
        bool bTest = false;
        
        Replay.InitSymbolReplay();
        if (!Replay.SetSymbolReplay(user00))
        {
                Finish();
                return;
        }
        Print("Wait for permission from [Market Replay] indicator to start replay...");
        id = Replay.ViewReplay(user01);
        while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(750);
        if ((_StopFlag) || (ChartSymbol(id) == ""))
        {
                Finish();
                return;
        }
        Print("Permission received. Now you can use the replay service...");
        t1 = GetTickCount64();
        while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag))
        {
                if (!Info.s_Infos.isPlay)
                {
                        if (!bTest) bTest = true;
                }else
                {
                        if (bTest)
                        {
                                delay = ((delay = Replay.AdjustPositionReplay()) == 0 ? 3 : delay);
                                bTest = false;
                                t1 = GetTickCount64();
                        }else if ((GetTickCount64() - t1) >= (uint)(delay))
                        {
                                if ((delay = Replay.Event_OnTime()) < 0) break;
                                t1 = GetTickCount64();
                        }
                }
        }
        Finish();
}

Zunächst haben wir nur darauf gewartet, dass alles fertig wird. Aber von nun an werden wir dafür sorgen, dass alles auch wirklich funktioniert. Das wird durch Tests erreicht. Testen wir, ob die Dateien, die für die Wiedergabe verwendet werden sollen, tatsächlich dafür geeignet sind. Wir überprüfen die Rückgabe der Funktion, die die in den Dateien enthaltenen Daten liest. Wenn ein Fehler auftritt, wird in der MetaTrader 5-Toolbox eine Meldung über den Vorfall angezeigt, während der Replay-Dienst einfach geschlossen wird, da wir nicht genügend Daten haben, um ihn zu nutzen.

Wenn die Daten korrekt geladen wurden, wird eine entsprechende Meldung in der Toolbox angezeigt und wir können fortfahren. Dann öffnen wir das Symbol für das Replay und warten auf die Genehmigung, mit den nächsten Schritten fortzufahren. Es kann jedoch vorkommen, dass der Nutzer während der Wartezeit den Dienst oder das Wiedergabesymboldiagramm schließt. Wenn dies geschieht, müssen wir den Replay-Dienst stoppen. Wenn alles richtig funktioniert, werden wir in die Wiederholungsschleife eintreten, aber gleichzeitig sicherstellen, dass der Nutzer das Chart nicht schließt oder den Dienst beendet. Denn wenn dies geschieht, muss auch das Replay beendet werden.

Beachten Sie, dass wir uns jetzt nicht nur vorstellen, dass alles funktioniert, sondern auch dafür sorgen, dass es richtig funktioniert. Es mag seltsam erscheinen, dass diese Art von Tests in früheren Versionen des Systems nicht durchgeführt wurde, aber es gab andere Probleme, sodass jedes Mal, wenn die Wiedergabe geschlossen oder aus irgendeinem Grund abgebrochen wurde, etwas hinter den Kulissen zurückblieb. Aber jetzt werden wir sicherstellen, dass alles richtig funktioniert und es keine unnötigen Elemente gibt.

Letztendlich haben wir noch folgenden Code in der Servicedatei:

void Finish(void)
{
        Replay.CloseReplay();
        Print("Replay service completed...");
}

Das ist nicht schwer zu verstehen. Hier wird einfach die Wiedergabe abgeschlossen und der Nutzer über die Toolbox benachrichtigt. Auf diese Weise können wir die Datei aufrufen, die die Klasse C_Replay implementiert, und dort zusätzliche Prüfungen durchführen, um sicherzustellen, dass alles korrekt funktioniert.

In der Klasse C_Replay gibt es keine größeren Änderungen. Wir konstruieren sie so, dass der Replay-Dienst so stabil und zuverlässig wie möglich ist. Daher werden wir die Änderungen schrittweise vornehmen, um die bisher geleistete Arbeit nicht zu zerstören.

Das erste, was Ihnen ins Auge fällt, sind die unten abgebildeten Zeilen:

#define def_STR_FilesBar        "[BARS]"
#define def_STR_FilesTicks      "[TICKS]"
#define def_Header_Bar          "<DATE><TIME><OPEN><HIGH><LOW><CLOSE><TICKVOL><VOL><SPREAD>"
#define def_Header_Ticks        "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>"

Obwohl sie unbedeutend erscheinen mögen, sind diese 4 Zeilen sehr interessant, da sie die erforderlichen Tests durchführen. Diese beiden Definitionen werden in der Konfigurationsdatei verwendet, die wir uns gleich ansehen werden. Sie enthält genau die Daten aus der ersten Zeile der Datei, die die Balken enthält, die als vorherige Balken verwendet werden sollen. Diese Definition enthält genau den Inhalt der ersten Zeile der Datei, die die gehandelten Häkchen enthält.

Aber warten Sie einen Moment. Diese Definitionen entsprechen nicht genau den Definitionen in den Dateiköpfen: Es gibt keine Tabulatoren. Ja, in den Originaldateien sind keine Registerkarten vorhanden. Es gibt jedoch ein kleines Detail im Zusammenhang mit der Art und Weise, wie die Daten gelesen werden.

Doch bevor wir ins Detail gehen, wollen wir einen Blick darauf werfen, wie die Konfigurationsdatei des Replay-Dienstes im derzeitigen Entwicklungsstadium aussieht. Das Dateibeispiel ist unten abgebildet:

[Bars]
WIN$N_M1_202108020900_202108021754
WIN$N_M1_202108030900_202108031754
WIN$N_M1_202108040900_202108041754

[Ticks]
WINQ21_202108050900_202108051759
WINQ21_202108060900_202108061759

Die als [Bars] definierte Zeile die so aussehen kann, da ich das System nicht auf Groß- und Kleinschreibung einstelle, gibt an, dass alle nachfolgenden Zeilen als vorherige Balken verwendet werden. Wir haben also drei verschiedene Ladungen in der angegebenen Reihenfolge. Seien Sie vorsichtig, denn wenn Sie sie in der falschen Reihenfolge platzieren, erhalten Sie nicht das gewünschte Replay. Alle in diesen Dateien enthaltenen Balken werden nacheinander dem Symbol hinzugefügt, das für das Replay verwendet wird. Unabhängig von der Anzahl der Dateien oder Balken werden alle Dateien als vorherige Takte hinzugefügt, bis eine Anweisung erfolgt, dies zu ändern.

Im Fall der Zeile [Ticks] teilt dies dem Replay-Dienst mit, dass alle nachfolgenden Zeilen gehandelte Ticks enthalten werden oder sollen, die für die Wiedergabe verwendet werden. Wie bei den Balken gilt auch hier die gleiche Warnung: Achten Sie darauf, die Dateien in der richtigen Reihenfolge zu platzieren. Andernfalls wird das Replay anders ausfallen als erwartet; alle Dateien werden immer vom Anfang bis zum Ende gelesen. Auf diese Weise können wir Balken mit Ticks kombinieren.

Allerdings gibt es im Moment eine kleine Einschränkung. Vielleicht ist dies nicht wirklich eine Einschränkung, da es keinen Sinn macht, gehandelte Ticks hinzuzufügen, sie wiederzugeben und dann zu beobachten, wie weitere Balken erscheinen, die in einem weiteren Wiederholungsaufruf verwendet werden. Wenn Sie Ticks vor Balken setzen, macht dies für das Replay System keinen Unterschied. Die gehandelten Balken stehen immer an erster Stelle, und erst dann kommen die gehandelten Ticks.

Das ist wichtig: In dem obigen Beispiel habe ich nicht berücksichtigt, dass dies möglich ist. Wenn wir die Dinge besser organisieren wollen, können wir einen Verzeichnisbaum verwenden, um die Dinge zu trennen und sie auf eine geeignetere Weise zu organisieren. Dies kann ohne zusätzliche Änderungen am Klassencode erfolgen. Alles, was wir tun müssen, ist, eine bestimmte Logik in den Strukturen der Klassendatei sorgfältig zu befolgen. Zur Verdeutlichung sehen wir uns ein Beispiel für die Verwendung eines Verzeichnisbaums an, um Dinge nach Symbol, Monat oder Jahr zu trennen.


Um dies zu verstehen, sehen wir uns die folgenden Bilder an:

                   

Bitte beachten Sie, dass wir das Verzeichnis MARKET REPLAY als ROOT angegeben haben, was die Basis ist, die wir innerhalb dieses Verzeichnisses verwenden müssen. Wir können die Dinge organisieren, indem wir sie in Symbole, Jahre und Monate unterteilen, wobei jeder Monat Dateien enthält, die den Ereignissen in diesem bestimmten Monat entsprechen. Die Art und Weise, wie das System erstellt wird, ermöglicht es uns, die oben gezeigte Strukturierung zu verwenden, ohne Änderungen am Code vorzunehmen. Der Zugriff auf bestimmte Daten erfolgt durch einfache Angabe in der Konfigurationsdatei.

Diese Datei kann einen beliebigen Namen haben, nur ihr Inhalt ist strukturiert. Sie können den Namen also frei wählen.

Sehr gut. Wenn Sie also eine Datei aufrufen müssen, sagen wir eine Tick-Datei für den Mini-Dollar, für das Jahr 2020, den 16. Juni, verwenden Sie die folgende Zeile in der Konfigurationsdatei:

[Ticks]
Mini_Dolar_Futuro\2020\06-Junho\WDO_16062020

Damit wird das System angewiesen, genau diese Datei zu lesen. Dies ist natürlich nur ein Beispiel dafür, wie die Arbeit organisiert werden kann.

Aber um zu verstehen, warum dies geschieht und möglich ist, müssen wir uns die Funktion ansehen, die für das Lesen dieser Konfigurationsdatei verantwortlich ist. Sehen wir es uns an.

bool SetSymbolReplay(const string szFileConfig)
{
        int     file;
        string  szInfo;
        bool    isBars = true;
                        
        if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
        {
                MessageBox("Failed to read the configuration file.", "Market Replay", MB_OK);
                return false;
        }
        Print("Loading data for replay.\nPlease wait....");
        while ((!FileIsEnding(file)) && (!_StopFlag))
        {
                szInfo = FileReadString(file);
                StringToUpper(szInfo);
                if (szInfo == def_STR_FilesBar) isBars = true; else
                if (szInfo == def_STR_FilesTicks) isBars = false; else
                if (szInfo != "") if (!(isBars ? LoadPrevBars(szInfo) : LoadTicksReplay(szInfo)))
                {
			if (!_StopFlag)
	                        MessageBox(StringFormat("File %s of %s\ncould not be loaded.", szInfo, (isBars ? def_STR_FilesBar : def_STR_FilesTicks), "Market Replay", MB_OK));
                        FileClose(file);
                        return false;
                }
        }
        FileClose(file);
        return (!_StopFlag);
}

Wir beginnen mit dem Versuch, die Konfigurationsdatei zu lesen, die sich an einem bestimmten Ort befinden muss. Dieser Ort kann nicht geändert werden, zumindest nicht, nachdem das System kompiliert wurde. Wenn die Datei nicht geöffnet werden kann, erscheint eine Fehlermeldung und die Funktion wird geschlossen. Wenn die Datei geöffnet werden kann, beginnen wir sie zu lesen. Bitte beachten Sie jedoch, dass wir ständig überprüfen, ob der MetaTrader 5-Nutzer die Beendigung des Replay-Dienstes beantragt hat.

Wenn dies geschieht, d.h. wenn der Nutzer den Dienst beendet, wird die Funktion geschlossen, als ob sie fehlgeschlagen wäre. Wir lesen Zeile für Zeile und wandeln alle gelesenen Zeichen in entsprechende Großbuchstaben um, was die Analyse erleichtert. Anschließend wird die im Konfigurationsskript angegebene Datei geparst und die entsprechende Funktion zum Lesen der Daten aufgerufen. Wenn das Lesen einer dieser Dateien aus irgendeinem Grund fehlschlägt, wird dem Nutzer eine Fehlermeldung angezeigt und die Funktion schlägt fehl. Wenn die gesamte Konfigurationsdatei gelesen wurde, wird die Funktion einfach beendet. Wenn der Nutzer darum bittet, den Vorgang zu stoppen, erhalten wir eine Antwort, die besagt, dass alles in Ordnung ist.

Nachdem wir nun gesehen haben, wie die Konfigurationsdatei gelesen wird, wollen wir uns nun die Funktion ansehen, die für das Lesen der Daten verantwortlich ist, um zu verstehen, warum es eine Warnung gibt, dass die angeforderte Datei nicht geeignet ist. Das heißt, wenn Sie versuchen, eine Datei zu verwenden, die Balken enthält, anstatt einer Datei, die gehandelte Ticks enthalten sollte, oder umgekehrt, wird das System einen Fehler melden. Wir werden sehen, wie das geschieht.

Beginnen wir mit der einfachsten Funktion, die für das Lesen und Laden von Balken zuständig ist. Der dafür verantwortliche Code ist unten dargestellt:

bool LoadPrevBars(const string szFileNameCSV)
{
        int     file,
                iAdjust = 0;
        datetime dt = 0;
        MqlRates Rate[1];
        string  szInfo = "";
                                
        if ((file = FileOpen("Market Replay\\Bars\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
        {
                for (int c0 = 0; c0 < 9; c0++) szInfo += FileReadString(file);
                if (szInfo != def_Header_Bar)
                {
                        Print("Файл ", szFileNameCSV, ".csv это не файл предыдущих баров.");
                        return false;
                }
                Print("Loading bars for the replay. Please wait....");
                while ((!FileIsEnding(file)) && (!_StopFlag))
                {
                        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("Could not access the bars data file.");
                m_dtPrevLoading = 0;                                    
                return false;
        }
        return (!_StopFlag);
}

Auf den ersten Blick unterscheidet sich dieser Code nicht wesentlich von dem Code, der im vorherigen Artikel „Entwicklung eines Replay System (Teil 05)“ bereitgestellt wurde. Aber es gibt Unterschiede, und die sind in allgemeiner und struktureller Hinsicht recht bedeutend.

Der erste Unterschied besteht darin, dass wir jetzt die Kopfdaten der zu lesenden Datei abfangen. Diese Kopfzeile wird mit dem von der Balkenlesefunktion ermittelten und erwarteten Wert verglichen. Wenn diese Kopfzeile von der erwarteten abweicht, wird ein Fehler ausgelöst und die Funktion wird beendet. Wenn dies aber die erwartete Kopfzeile ist, dann landen wir in einer Schleife.

Zuvor wurde die Ausgabe dieser Schleife nur durch das Ende der gelesenen Datei gesteuert. Wenn der Nutzer den Dienst aus irgendeinem Grund beendet hat, wurde er nicht geschlossen. Wenn der Nutzer das Replay System verlässt, während er eine der Dateien liest, wird die Schleife geschlossen und ein Fehler wird ausgegeben, der anzeigt, dass das System fehlgeschlagen ist. Dies ist jedoch nur eine Formalität, um einen reibungsloseren und weniger abrupten Abschluss zu erreichen.

Der Rest der Funktion läuft weiter wie bisher, da sich an der Art und Weise, wie die Daten gelesen werden, nichts geändert hat.

Schauen wir uns nun die Funktion zum Lesen der gehandelten Ticks an, die geändert wurde und noch interessanter als die Funktion zum Lesen der Balken ist. Ihr Code ist unten dargestellt:

#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)
                        {
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                old = m_Ticks.nTicks;
                                for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
                                if (szInfo != def_Header_Ticks)
                                {
                                        Print("File ", szFileNameCSV, ".csv is not a file with traded ticks.");
                                        return false;
                                }
                                Print("Loading replay ticks. Please wait...");
                                while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), 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);
                                        }
                                }
                                if ((!FileIsEnding(file))&& (!_StopFlag))
                                {
                                        Print("Too much data in the tick file.\nCannot continue....");
                                        return false;
                                }
                        }else
                        {
                                Print("Tick file ", szFileNameCSV,".csv not found...");
                                return false;
                        }
                        return (!_StopFlag);
                };
#undef macroRemoveSec

Vor dem letzten Artikel hatte diese Funktion zum Lesen von gehandelten Ticks eine Einschränkung, die in der folgenden Definitionszeile zum Ausdruck kommt:

#define def_MaxSizeArray        134217727 // 128 Mbytes of positions

Diese Zeile gibt es immer noch, aber wir haben die Beschränkung aufgehoben, zumindest teilweise. Denn es kann sinnvoll sein, ein Replay System zu schaffen, das mehr als eine gehandelte Tickdatenbank verarbeiten kann. Auf diese Weise können wir dem System 2 oder mehr Tage hinzufügen und einige größere Wiederholungstests durchführen. Darüber hinaus gibt es sehr spezielle Fälle, in denen wir eine Datei mit mehr als 128 MB an Positionen zu bearbeiten haben. Solche Fälle sind zwar selten, können aber vorkommen. Von nun an können wir einen kleineren Wert für diese Definition oben verwenden, um die Speichernutzung zu optimieren.

Aber warten Sie einen Moment. Habe ich WENIGER gesagt? JA. Wenn Sie sich die neue Definition ansehen, sehen Sie den folgenden Code:

#define def_MaxSizeArray        16777216 // 16 Mbytes of positions

Sie denken jetzt vielleicht: Du bist verrückt, das wird das System beschädigen... Aber das wird es nicht. Wenn Sie sich das normale Lesen der gehandelten Ticks ansehen, können Sie zwei sehr interessante Zeilen erkennen, die vorher nicht vorhanden waren. Sie sind dafür verantwortlich, dass wir maximal 2 hoch 32 Datenpositionen lesen und speichern können. Dies wird durch eine Prüfung zu Beginn der Schleife sichergestellt. 

Um die ersten Positionen nicht zu verlieren, ziehen wir 2 ab, damit der Test nicht aus irgendeinem Grund fehlschlägt. Wir könnten eine zusätzliche äußere Schleife hinzufügen, um die Speicherkapazität zu erhöhen, aber ich persönlich sehe keinen Grund, dies zu tun. Wenn 2 Gigabyte nicht ausreichen, dann weiß ich nicht, was ausreichen sollte. Verstehen wir nun, wie die Verringerung des Definitionswerts eine bessere Optimierung anhand von zwei Zeilen ermöglicht. Schauen wir uns das dafür verantwortliche Codefragment genauer an.

// ... Previous code ....

if ((file = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
{
        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
        old = m_Ticks.nTicks;
        for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(file);
        if (szInfo != def_Header_Ticks)
        {
                Print("File ", szFileNameCSV, ".csv is not a file with traded ticks.");
                return false;
        }
        Print("Loading replay ticks. Please wait...");
        while ((!FileIsEnding(file)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag))
        {
                ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray);
                szInfo = FileReadString(file) + " " + FileReadString(file);

// ... The rest of the code...

Bei der ersten Speicherzuweisung wird die gesamte angegebene Größe zuzüglich eines Reservewerts zugewiesen. Dieser Reservewert wird unsere Sicherheit sein. Wenn wir dann in die Leseschleife eintreten, werden wir eine Reihe von Verschiebungen haben, aber nur, wenn es wirklich notwendig ist.

Beachten Sie nun, dass wir bei dieser zweiten Zuweisung den aktuellen Wert des bereits ausgelesenen Tickzählers plus 1 verwenden werden. Beim Testen des Systems stellte ich fest, dass dieser Aufruf mit dem Wert 0 ausgeführt wurde, was einen Laufzeitfehler verursachte. Man könnte meinen, dies sei verrückt, da der Speicher zuvor mit einem höheren Wert zugewiesen wurde. Die Dokumentation der Funktion ArrayResize sagt uns, dass wir die Größe des Arrays neu definieren werden.

Wenn wir diesen zweiten Aufruf verwenden, setzt die Funktion das Array auf Null zurück. Das ist der aktuelle Wert der Variablen, wenn die Funktion zum ersten Mal aufgerufen wird, und wir haben ihn nicht erhöht. Ich werde den Grund dafür hier nicht erläutern, aber Sie sollten vorsichtig sein, wenn Sie mit der dynamischen Zuweisung in MQL5 arbeiten. Es kann nämlich passieren, dass Ihr Code zwar korrekt aussieht, aber vom System falsch interpretiert wird.

Hier ist noch ein kleines Detail zu beachten: Warum verwende ich INT_MAX und nicht UINT_MAX in dem Test? Die ideale Option wäre die Verwendung von UINT_MAX, wodurch wir 4 Gigabyte zugewiesenen Speicherplatz erhalten würden, aber die Funktion ArrayResize arbeitet mit dem INT-System, d. h. mit einer vorzeichenbehafteten Ganzzahl.

Und selbst wenn wir 4 Gigabyte zuweisen wollen, was mit einem 32-Bit-Long-Typ möglich wäre, verlieren wir aufgrund des Vorzeichens immer 1 Bit in der Datenlänge. Wir werden also 31 Bits verwenden, was uns 2 Gigabyte an möglichem Speicherplatz garantiert, den wir mit der Funktion ArrayResize zuweisen können. Wir könnten versuchen, diese Beschränkung zu umgehen, indem wir ein Freigabesystem verwenden, das die Zuteilung von 4 GByte oder sogar mehr garantiert, aber ich sehe keinen Grund, dies zu tun. Zwei Gigabyte an Daten sind völlig ausreichend.

Nach der Erklärung kommen wir nun zum Code zurück. Die Funktion, mit der die gehandelten Ticks gelesen werden, ist noch nicht bekannt. Um zu überprüfen, ob es sich bei den Daten in der Datei tatsächlich um gehandelte Ticks handelt, lesen und speichern wir die Werte aus dem Dateikopf. Danach können wir prüfen, ob die Kopfzeile mit der Kopfzeile übereinstimmt, die das System in der gehandelten Tick-Datei zu finden erwartet. Andernfalls gibt das System einen Fehler aus und schaltet sich dann ab.

Wie bei den Lesebalken wird auch hier geprüft, ob der Nutzer das Herunterfahren des Systems beantragt hat. Dies sorgt für eine gleichmäßigere und sauberere Ausgabe. Denn wenn der Nutzer den Replay-Dienst schließt oder beendet, wollen wir nicht, dass Fehlermeldungen angezeigt werden, die Verwirrung stiften.

Zusätzlich zu all diesen Tests, die wir hier durchgeführt haben, müssen wir noch ein paar weitere durchführen. Eigentlich sind diese Manipulationen nicht nötig, aber ich möchte nicht alles der Plattform überlassen. Ich möchte sicherstellen, dass einige Dinge tatsächlich implementiert werden, deshalb wird es eine neue Zeile in unserem Code geben:

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

Man könnte meinen, dass dieser Anruf gar nicht so wichtig ist. Eine der besten Programmierpraktiken besteht jedoch darin, alles zu bereinigen, was wir erstellt haben, oder den gesamten von uns zugewiesenen Speicher explizit zurückzufordern. Und genau das haben wir jetzt getan. Wir garantieren, dass der beim Laden der Ticks belegte Speicher an das Betriebssystem zurückgegeben wird. Dies geschieht in der Regel, wenn wir die Plattform schließen oder das Programm auf dem Chart beenden. Aber es ist gut, sich zu vergewissern, dass dies getan wurde, auch wenn die Plattform dies bereits für uns tut. Dessen müssen wir uns sicher sein.

Wenn ein Fehler auftritt und die Ressource nicht an das Betriebssystem zurückgegeben wird, kann es passieren, dass die Ressource bei einem erneuten Versuch, sie zu verwenden, nicht verfügbar ist. Dies ist nicht auf einen Fehler der Plattform oder des Betriebssystems zurückzuführen, sondern auf Vergesslichkeit beim Programmieren.


Schlussfolgerung

Im folgenden Video können Sie sehen, wie das System im derzeitigen Entwicklungsstadium funktioniert. Bitte beachten Sie, dass die bisherigen Angebote am 4. August enden. Der erste Tag der Wiederholung beginnt mit dem ersten Ticken am 5. August. Sie können die Wiederholung jedoch bis zum 6. August vorspulen und dann zum Anfang des 5. August zurückkehren. Dies war in der vorherigen Version des Replay Systems nicht möglich, aber jetzt haben wir diese Möglichkeit.

Wenn Sie genau hinsehen, können Sie einen Fehler im System erkennen. Wir werden diesen Fehler im nächsten Artikel beheben, in dem wir unser Market Replay weiter verbessern und es stabiler und intuitiver machen werden.



Die angehängte Datei enthält den Quellcode und die im Video verwendeten Dateien. Verwenden Sie diese, um die Erstellung der Konfigurationsdatei besser zu verstehen und zu üben. Es ist wichtig, jetzt mit dem Studium dieser Phase zu beginnen, da sich die Konfigurationsdatei im Laufe der Zeit positiv verändern und ihre Fähigkeiten erweitern wird. Deshalb müssen wir das jetzt verstehen, und zwar von Anfang an.


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

Beigefügte Dateien |
Market_Replay.zip (13057.37 KB)
Bewertung von ONNX-Modellen anhand von Regressionsmetriken Bewertung von ONNX-Modellen anhand von Regressionsmetriken
Bei der Regression geht es um die Prognose eines realen Wertes anhand eines unbekannten Beispiels. Die so genannten Regressionsmetriken werden verwendet, um die Genauigkeit der Vorhersagen des Regressionsmodells zu bewerten.
MQL5 Strategietester verstehen und effektiv nutzen MQL5 Strategietester verstehen und effektiv nutzen
Für MQL5-Programmierer oder -Entwickler ist es unerlässlich, wichtige und wertvolle Werkzeuge zu beherrschen. Eines dieser Werkzeuge ist der Strategietester. Dieser Artikel ist ein praktischer Leitfaden zum Verständnis und zur Verwendung des Strategietesters von MQL5.
Wie man einen einfachen Multi-Currency Expert Advisor mit MQL5 erstellt (Teil 1): Indikatorsignale basierend auf ADX in Kombination mit Parabolic SAR Wie man einen einfachen Multi-Currency Expert Advisor mit MQL5 erstellt (Teil 1): Indikatorsignale basierend auf ADX in Kombination mit Parabolic SAR
Der Multi-Currency Expert Advisor in diesem Artikel ist ein Expert Advisor oder Handelsroboter, der mit mehr als einem Symbolpaar aus einem Symbolchart handeln kann (Positionen öffnen, schließen und verwalten).
Neuronale Netze leicht gemacht (Teil 37): Sparse Attention (Verringerte Aufmerksamkeit) Neuronale Netze leicht gemacht (Teil 37): Sparse Attention (Verringerte Aufmerksamkeit)
Im vorigen Artikel haben wir relationale Modelle erörtert, die in ihrer Architektur Aufmerksamkeitsmechanismen verwenden. Eines der besonderen Merkmale dieser Modelle ist die intensive Nutzung von Computerressourcen. In diesem Artikel wird einer der Mechanismen zur Verringerung der Anzahl von Rechenoperationen innerhalb des Self-Attention-Blocks betrachtet. Dadurch wird die allgemeine Leistung des Modells erhöht.