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

Entwicklung eines Replay-Systems — Marktsimulation (Teil 01): Erste Versuche (I)

MetaTrader 5Tester | 20 Juli 2023, 09:39
284 0
Daniel Jose
Daniel Jose

Einführung

Beim Schreiben der Artikelserie „Entwicklung eines Expert Advisor für den Handel von Grund auf“ bin ich auf mehrere Momente gestoßen, die mich erkennen ließen, dass es möglich war, viel mehr zu tun, als das, was mit der MQL5-Programmierung gemacht wurde. Einer dieser Momente war, als ich ein grafisches Times & Trade-System entwickelte. In diesem Artikel habe ich mich gefragt, ob es möglich ist, über die bisher gebauten Dinge hinauszugehen.

Eine der häufigsten Beschwerden von Handelsanfängern ist das Fehlen bestimmter Funktionen auf der MetaTrader 5-Plattform. Unter diesen Funktionen gibt es eine, die meiner Meinung nach sinnvoll ist: eine Marktsimulation oder ein Replay-System, also das wiederholte Abspielen des Marktes. Für neue Marktteilnehmer wäre es gut, wenn es einen Mechanismus oder ein Instrument gäbe, mit dem sie Vermögenswerte testen, überprüfen oder sogar untersuchen könnten. Eines dieser Instrumente ist das Replay- und Simulationssystem.

MetaTrader 5 enthält diese Funktion nicht im Standardinstallationspaket. Es bleibt also jedem Nutzer selbst überlassen, wie er solche Studien durchführt. Im MetaTrader 5 können Sie jedoch Lösungen für viele Aufgaben finden, da diese Plattform sehr funktionell ist. Damit Sie es aber wirklich optimal nutzen können, müssen Sie über gute Programmierkenntnisse verfügen. Ich meine nicht unbedingt die MQL5-Programmierung, sondern die Programmierung im Allgemeinen.

Wenn Sie noch nicht viel Erfahrung in diesem Bereich haben, werden Sie sich auf die Grundlagen beschränken. Sie werden keine adäquateren Mittel oder bessere Wege finden, um sich auf dem Markt zu entwickeln (im Sinne eines außergewöhnlichen Händlers). Wenn Sie also nicht über gute Programmierkenntnisse verfügen, werden Sie nicht wirklich in der Lage sein, alles zu nutzen, was MetaTrader 5 zu bieten hat. Selbst erfahrene Programmierer sind möglicherweise nicht daran interessiert, bestimmte Arten von Programmen oder Anwendungen für MetaTrader 5 zu erstellen.

Tatsache ist, dass nur wenige Menschen ein praktikables System für Anfänger entwickeln können. Es gibt sogar einige kostenlose Vorschläge zur Schaffung eines Replay-Systems des Marktes. Aber meiner Meinung nach nutzen sie die Funktionen, die MQL5 bietet, nicht wirklich aus. Sie erfordern oft die Verwendung von externen DLLs mit geschlossenem Code. Ich halte das für keine gute Idee. Dies umso mehr, als man weder den Ursprung noch den Inhalt solcher DLLs wirklich kennt, was das gesamte System gefährdet.

Ich weiß nicht, wie viele Artikel diese Serie umfassen wird, aber es wird um die Entwicklung eines funktionierenden Replays gehen. Ich zeige Ihnen, wie Sie Code erstellen können, um dieses Abspielen zu implementieren. Aber das ist noch nicht alles. Wir werden auch ein System entwickeln, mit dem wir jede noch so ungewöhnliche oder seltene Marktsituation simulieren können.

Eine merkwürdige Tatsache ist, dass viele Leute, wenn sie über die Handelsmenge sprechen, keine wirkliche Vorstellung davon haben, wovon sie sprechen, weil es keine praktische Möglichkeit gibt, Studien über diese Art von Dingen durchzuführen. Aber wenn Sie die Konzepte verstehen, die ich in dieser Serie beschreiben werde, werden Sie in der Lage sein, MetaTrader 5 in ein quantitatives Analysesystem zu verwandeln. Die Möglichkeiten gehen also weit über das hinaus, was ich hier darlegen werde.

Um nicht zu sehr zu wiederholen und zu ermüden, werde ich das System wie ein Abspielsystem behandeln. Obwohl der korrekte Begriff Replay/Simulation lautet, weil wir nicht nur vergangene Bewegungen analysieren, sondern auch unsere eigenen Bewegungen entwickeln können, um sie zu studieren. Betrachten Sie dieses System also nicht nur als eine Marktwiederholung, sondern als einen Marktsimulator oder sogar als ein „Spiel“ des Marktes, da es auch eine Menge Spielprogrammierung beinhalten wird. Diese Art der Programmierung von Spielen wird an einigen Stellen notwendig sein. Aber wir werden dies Schritt für Schritt bei der Entwicklung und Verbesserung des Systems sehen.


Planung

Zunächst müssen wir verstehen, womit wir es zu tun haben. Es mag seltsam erscheinen, aber wissen Sie wirklich, was Sie erreichen wollen, wenn Sie ein Replay-/Simulationssystem verwenden?

Es gibt einige ziemlich knifflige Probleme, wenn es darum geht, das Abspielen eines Marktes zu erstellen. Eine davon, und vielleicht die wichtigste, ist die Lebensdauer von Vermögenswerten und Informationen über sie. Wenn Sie dies nicht verstehen, ist es wichtig, dass Sie Folgendes verstehen: Das Handelssystem speichert alle Informationen, Tick für Tick, für jeden ausgeführten Handel für alle Vermögenswerte, einen nach dem anderen. Aber ist Ihnen klar, wie viele Daten das sind? Haben Sie schon einmal darüber nachgedacht, wie lange es dauern wird, alle Vermögenswerte zu organisieren und zu sortieren?

Nun, einige typische Anlagen können bei ihren täglichen Bewegungen etwa 80 MByte an Daten enthalten. In manchen Fällen kann es etwas mehr oder etwas weniger sein. Dies gilt nur für eine einzige Anlage an einem einzigen Tag. Stellen Sie sich nun vor, Sie müssten denselben Vermögenswert 1 Monat, 1 Jahr, 10 Jahre lang speichern... Oder wer weiß, für immer. Denken Sie nur an die enormen Datenmengen, die gespeichert und dann abgerufen werden müssen. Denn wenn Sie sie einfach nur auf der Festplatte speichern, werden Sie bald nichts mehr finden können. Es gibt eine Redewendung, die dies gut beschreibt:

Je größer der Raum, desto größer die Unordnung.

Um die Sache zu vereinfachen, werden die Daten nach einer Weile in 1-Minuten-Balken komprimiert, die das Minimum an notwendigen Informationen enthalten, damit wir eine Art Studie durchführen können. Wenn dieser Balken jedoch tatsächlich erstellt wird, verschwinden die Ticks, die ihn gebildet haben, und sind nicht mehr zugänglich. Danach ist es nicht mehr möglich, das Abspielen eines echten Marktes durchzuführen. Von diesem Moment an haben wir es mit einem Simulator zu tun. Da die reale Bewegung nicht mehr zugänglich ist, müssen wir eine Möglichkeit finden, sie auf der Grundlage einer plausiblen Marktbewegung zu simulieren.

Zum Verständnis siehe die nachstehenden Abbildungen:

                       

Die obige Sequenz zeigt, wie Daten im Laufe der Zeit verloren gehen. Das Bild auf der linken Seite zeigt die aktuellen Tick-Werte. Wenn die Daten komprimiert werden, erhalten wir das Bild in der Mitte. Auf dieser Grundlage werden wir nicht in der Lage sein, die linken Werte zu erhalten. Es ist unmöglich, das zu tun. Aber wir können etwas wie das Bild rechts erstellen, bei dem wir die Marktbewegungen auf der Grundlage des Wissens darüber, wie sich der Markt normalerweise bewegt, simulieren werden. Es sieht jedoch nicht wie das Originalbild aus.

Behalten Sie dies im Hinterkopf, wenn Sie mit der Wiedergabe arbeiten. Wenn Sie nicht über die Originaldaten verfügen, können Sie keine echte Studie durchführen. Sie werden nur eine statistische Studie erstellen können, die einer möglichen realen Bewegung nahe kommen kann oder auch nicht. Denken Sie immer daran. Im Laufe dieser Sequenz werden wir mehr darüber erfahren, wie man das macht. Aber das wird nach und nach geschehen.

Damit kommen wir nun zum wirklich schwierigen Teil: Einführung eines Replay-Systems.


Umsetzung

Dieser Teil scheint zwar einfach zu sein, ist aber ziemlich kompliziert, da es Probleme und Einschränkungen in Bezug auf die Hardware und andere Probleme im Softwarebereich gibt. Wir müssen also versuchen, etwas zu schaffen, das zumindest in den Grundzügen funktional und akzeptabel ist. Es nützt nichts, etwas Komplexeres zu versuchen, wenn die Grundlagen schwach sind.

Unser wichtigstes und größtes Problem ist seltsamerweise die Zeit. Zeit ist ein großes oder sogar sehr großes Problem, das es zu bewältigen gilt.

In der Anlage lasse ich (in dieser ersten Phase) immer mindestens 2 ECHTE Sätze von Ticks eines beliebigen Vermögenswerts für einen beliebigen vergangenen Zeitraum. Diese Daten können nicht mehr abgerufen werden, da sie verloren gegangen sind und nicht mehr heruntergeladen werden können. Dies wird uns helfen, jedes Detail zu studieren. Sie können aber auch Ihre eigene REAL-Tick-Basis erstellen.


Erstellen Sie Ihre eigene Datenbank

Glücklicherweise bietet MetaTrader 5 die Möglichkeit, dies zu tun. Dies ist recht einfach, aber Sie müssen dies stetig tun, da sonst die Werte verloren gehen können und es nicht mehr möglich ist, diese Aufgabe zu erledigen.

Öffnen Sie dazu MetaTrader 5 und drücken Sie die Standard-Tastenkombinationen: STRG+U. Daraufhin wird ein Bildschirm geöffnet. Geben Sie hier das Symbol sowie das Start- und Enddatum der Datenerfassung an, drücken Sie die Schaltfläche zur Datenanforderung und warten Sie einige Minuten. Der Server liefert Ihnen alle benötigten Daten. Danach exportieren Sie diese Daten einfach und bewahren sie sorgfältig auf, da sie sehr wertvoll sind.

Nachfolgend sehen Sie den Bildschirm, den Sie für die Erfassung verwenden werden.

Sie können zwar ein Programm dafür erstellen, aber ich denke, es ist besser, dies manuell zu tun. Es gibt Dinge, denen wir nicht blind vertrauen können. Wir müssen tatsächlich sehen, was vor sich geht, sonst haben wir nicht das richtige Vertrauen in das, was wir verwenden.

Glauben Sie mir, dies ist der einfachste Teil des gesamten Systems, das wir zu erstellen lernen werden. Von diesem Punkt an werden die Dinge viel komplizierter.


Erster Test

Manche mögen denken, dass dies eine einfache Aufgabe ist, aber wir werden diese Idee bald widerlegen. Andere mögen sich fragen: Warum verwenden wir nicht den MetaTrader 5 Strategy Tester, um das Abspielen durchzuführen? Der Grund dafür ist, dass es uns nicht möglich ist, so zu handeln, als ob wir auf dem Markt handeln würden. Es gibt Einschränkungen und Schwierigkeiten beim Abspielen durch den Tester, und als Ergebnis werden wir nicht perfekt in das Abspielen eintauchen, als ob wir tatsächlich den Markt handeln würden.

Wir werden vor großen Herausforderungen stehen, aber wir müssen die ersten Schritte auf dieser langen Reise machen. Beginnen wir mit einer sehr einfachen Umsetzung. Hierfür benötigen wir das Ereignis von OnTime, das einen Datenfluss zur Erstellung von Balken (Kerzen) erzeugt. Dieses Ereignis ist für EAs und Indikatoren vorgesehen, aber wir sollten in diesem Fall keine Indikatoren verwenden, denn wenn ein Fehler auftritt, gefährdet dies viel mehr als das Replay-System. Wir beginnen den Code wie folgt:

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(60);
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
}
//+------------------------------------------------------------------+

Der hervorgehobene Code eignet sich jedoch nicht für unsere Zwecke, da in diesem Fall die kleinste Zeitspanne, die wir verwenden können, 1 Sekunde beträgt, was eine lange Zeit ist, eine sehr lange Zeit. Da Marktereignisse in einem viel kleineren Zeitrahmen stattfinden, müssen wir auf Millisekunden heruntergehen, und dafür müssen wir eine andere Funktion verwenden: EventSetMillisecondTimer. Aber wir haben hier ein Problem.


Beschränkungen der Funktion EventSetMillisecondTimer

Schauen wir uns die Dokumentation an:

„In Echtzeitmodus werden Timer-Ereignisse nicht mehr als 1 Mal in 10-16 Millisekunden aufgrund von Hardware-Einschränkungen generiert.“

Das mag kein Problem sein, aber wir müssen verschiedene Kontrollen durchführen, um zu überprüfen, was tatsächlich passiert. Lassen Sie uns also einen einfachen Code erstellen, um die Ergebnisse zu überprüfen.

Beginnen wir mit dem unten stehenden EA-Code:

#property copyright "Daniel Jose"
#property icon "Resources\\App.ico"
#property description "Expert Advisor - Market Replay"
//+------------------------------------------------------------------+
#include "Include\\C_Replay.mqh"
//+------------------------------------------------------------------+
input string user01 = "WINZ21_202110220900_202110221759";       //Tick archive
//+------------------------------------------------------------------+
C_Replay        Replay;
//+------------------------------------------------------------------+
int OnInit()
{
        Replay.CreateSymbolReplay(user01);
        EventSetMillisecondTimer(20);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
        Replay.Event_OnTime();
}
//+------------------------------------------------------------------+

Beachten Sie, dass unser OnTime-Ereignis etwa alle 20 Millisekunden eintritt, wie durch die hervorgehobene Zeile im EA-Code angezeigt wird. Sie denken vielleicht, dass das zu schnell ist, aber ist das wirklich so? Schauen wir uns das mal an. Denken Sie daran, dass die Dokumentation besagt, dass wir nicht unter 10 bis 16 Millisekunden gehen können. Daher macht es keinen Sinn, den Wert auf 1 Millisekunde zu setzen, da das Ereignis während dieser Zeit nicht erzeugt wird.

Beachten Sie, dass wir im EA-Code nur zwei externe Links haben. Schauen wir uns nun die Klasse an, in der diese Codes implementiert sind.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_MaxSizeArray 134217727 // 128 Mbytes of positions
#define def_SymbolReplay "Replay"
//+------------------------------------------------------------------+
class C_Replay
{
};

Es ist wichtig zu beachten, dass die Klasse eine Definition von 128 MB hat, wie in dem hervorgehobenen Punkt oben angegeben. Das bedeutet, dass die Datei, die die Daten aller Ticks enthält, diese Größe nicht überschreiten darf. Sie können diese Größe erhöhen, wenn Sie es wünschen oder brauchen, aber ich persönlich hatte keine Probleme mit diesem Wert.

In der nächsten Zeile wird der Name des Assets angegeben, das für die Wiedergabe verwendet werden soll. Ziemlich kreativ von mir, das Asset REPLAY zu nennen, nicht wahr? 😂 Nun, lassen Sie uns mit dem Studium der Klasse fortfahren. Der nächste zu besprechende Code ist unten dargestellt:

void CreateSymbolReplay(const string FileTicksCSV)
{
        SymbolSelect(def_SymbolReplay, false);
        CustomSymbolDelete(def_SymbolReplay);
        CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\Replay\\%s", def_SymbolReplay), _Symbol);
        CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX);
        CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX);
        SymbolSelect(def_SymbolReplay, true);
        m_IdReplay = ChartOpen(def_SymbolReplay, PERIOD_M1);
        LoadFile(FileTicksCSV);
        Print("Running speed test.");
}

Die beiden hervorgehobenen Zeilen bewirken einige sehr interessante Dinge. Für diejenigen, die es nicht wissen: Die Funktion CustomSymbolCreate erstellt ein nutzerdefiniertes Symbol. In diesem Fall können wir ein paar Dinge anpassen, aber da es sich nur um einen Test handelt, werde ich vorerst nicht näher darauf eingehen. ChartOpen öffnet das Chart unseres nutzerdefinierten Symbols, das in diesem Fall das Replay sein wird. Das ist alles sehr schön, aber wir müssen unser Abspielen aus der Datei laden, und das geschieht mit der folgenden Funktion.

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data for Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                m_ArrayCount++;
                                        }
                                        FileClose(file);
                                        Print("Loading completed.\nReplay started.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

Diese Funktion lädt alle Tickdaten, Zeile für Zeile. Wenn die Datei nicht existiert oder nicht zugänglich ist, beendet ExpertRemove den EA.

Alle Daten werden vorübergehend im Speicher abgelegt, um die weitere Verarbeitung zu beschleunigen. Dies liegt daran, dass Sie möglicherweise ein Festplattenlaufwerk verwenden, das wahrscheinlich langsamer ist als der Systemspeicher. Es lohnt sich daher, von Anfang an sicherzustellen, dass alle Daten vorhanden sind.

Aber es gibt etwas sehr Interessantes in dem obigen Code: die Funktion FileReadString. Es liest die Daten, bis es ein Begrenzungszeichen findet. Es ist interessant zu sehen, dass, wenn wir uns die binären Daten der Tick-Datei ansehen, die von MetaTrader 5 generiert und im CSV-Format gespeichert wurde, wie zu Beginn des Artikels erklärt, wir das folgende Ergebnis erhalten.


Der gelbe Bereich ist der Dateikopf, der uns die Organisation der internen Struktur zeigt, die folgen wird. Der grüne Bereich stellt die erste Datenzeile dar. Betrachten wir nun die blauen Punkte (es handelt sich um Begrenzungszeichen) in diesem Format. 0D und 0A stehen für eine neue Zeile, und 09 steht für einen Tabulator (TAB-Taste). Wenn wir die Funktion FileReadString verwenden, müssen wir keine Daten ansammeln, um sie zu testen. Die Funktion wird dies selbst tun. Alles, was wir tun müssen, ist, die Daten in den gewünschten Typ zu konvertieren. Schauen wir uns den nächsten Code-Teil an.

if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;

Dieser Code verhindert, dass unnötige Daten in unserer Datenmatrix auftauchen, aber warum filtere ich diese Werte heraus? Denn sie sind nicht für ein Replay geeignet. Wenn Sie diese Werte verwenden möchten, können Sie sie durchlassen, müssen sie aber später bei der Erstellung der Balken filtern. Deshalb ziehe ich es vor, sie hier zu filtern.

Nachfolgend zeigen wir die letzte Routine in unserem Testsystem:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        _dt = TimeLocal();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                m_Rate[0].close = m_ArrayInfoTicks[m_ReplayCount].Last;
                                m_Rate[0].open = (isNew ? m_Rate[0].close : m_Rate[0].open);
                                m_Rate[0].high = (isNew || (m_Rate[0].close > m_Rate[0].high) ? m_Rate[0].close : m_Rate[0].high);
                                m_Rate[0].low = (isNew || (m_Rate[0].close < m_Rate[0].low) ? m_Rate[0].close : m_Rate[0].low);
                                m_Rate[0].tick_volume = (isNew ? m_ArrayInfoTicks[m_ReplayCount].Vol : m_Rate[0].tick_volume + m_ArrayInfoTicks[m_ReplayCount].Vol);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                m_ReplayCount++;
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print(TimeToString(_dt, TIME_DATE | TIME_SECONDS), " ---- ", TimeToString(TimeLocal(), TIME_DATE | TIME_SECONDS));
                                        _dt = 0;
                                }
                        };
#undef macroGetMin

Mit diesem Code werden Balken mit einer Periode von 1 Minute erstellt, was die Mindestanforderung der Plattform für die Erstellung aller anderen Zeitrahmen des Charts ist. Die hervorgehobenen Teile sind nicht Teil des Codes selbst, sind aber für die Analyse des 1-Minuten-Balkens nützlich. Wir müssen prüfen, ob sie wirklich innerhalb dieses Zeitrahmens erstellt wurde. Denn wenn die Erstellung viel länger als 1 Minute dauert, müssen wir etwas dagegen tun. Wenn sie in weniger als einer Minute erstellt wird, kann dies ein Hinweis darauf sein, dass das System sofort funktionsfähig ist.

Nachdem wir dieses System ausgeführt haben, erhalten wir das Ergebnis, das wir im folgenden Video zeigen:



Einige sind der Meinung, dass der Zeitplan viel länger ist als erwartet, aber wir können einige Verbesserungen am Code vornehmen, und vielleicht wird das etwas bewirken.


Verbesserung des Codes

Trotz der enormen Verzögerung können wir die Dinge vielleicht ein wenig verbessern und dem System helfen, den Erwartungen etwas näher zu kommen. Aber ich glaube nicht wirklich an Wunder. Wir kennen die Beschränkung der Funktion EventSetMillisecondTimer, und das Problem liegt nicht an MQL5, sondern an einer Hardwarebeschränkung. Aber wir wollen sehen, ob wir dem System helfen können.

Wenn Sie sich die Daten ansehen, werden Sie feststellen, dass es mehrere Momente gibt, in denen sich das System einfach nicht bewegt, der Preis still steht, oder es könnte passieren, dass das Buch jede Aggression absorbiert und der Preis sich einfach nicht bewegt. Dies ist auf dem nachstehenden Bild zu sehen.

Beachten Sie, dass wir zwei verschiedene Bedingungen haben: In einer der beiden haben sich die Zeit und der Preis nicht geändert. Dies bedeutet nicht, dass die Daten falsch sind, sondern dass noch nicht genug Zeit vergangen ist, als dass die Millisekundenmessung einen Unterschied machen könnte. Es gibt auch einen anderen Ereignistyp, bei dem sich der Preis nicht bewegt hat, die Zeit aber nur um 1 Millisekunde. Wenn wir in beiden Fällen Informationen kombinieren, kann der Unterschied in der Erstellungszeit der Balken 1 Minute betragen. Dadurch werden zusätzliche Aufrufe der Erstellungsfunktionen vermieden, und jede eingesparte Nanosekunde kann auf lange Sicht einen großen Unterschied machen. Alles summiert sich, und nach und nach wird viel erreicht.

Um zu prüfen, ob es einen Unterschied gibt, müssen wir die Menge der erzeugten Informationen überprüfen. Dies ist eine Frage der Statistik, also nicht exakt. Ein kleiner Fehler ist akzeptabel. Aber die Zeit, die im Video zu sehen ist, ist für eine realitätsnahe Simulation völlig inakzeptabel.

Um dies zu überprüfen, nehmen wir die erste Änderung am Code vor:

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {

// ... Internal code ...
                                        FileClose(file);
                                        Print("Loading completed.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

Der angegebene zusätzliche Teil wird dies für uns tun. Werfen wir nun einen Blick auf den ersten Start und sehen, was passiert. All dies ist auf dem nachstehenden Bild zu sehen:

Jetzt haben wir einige Parameter, mit denen wir überprüfen können, ob die Änderungen hilfreich sind oder nicht. Wenn wir es verwenden, werden wir sehen, dass es fast 3 Minuten dauerte, um 1 Minute Daten zu erzeugen. Mit anderen Worten: Das System ist alles andere als akzeptabel.

Deshalb werden wir kleine Verbesserungen an dem Code vornehmen:

#define macroRemoveSec(A) (A - (A % 60))
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data to Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if (m_ArrayInfoTicks[m_ArrayCount].Last != last)
                                                {
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }else
                                                        vol += m_ArrayInfoTicks[m_ArrayCount].Vol;
                                        }
                                        FileClose(file);
                                        Print("Loading complete.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec

Das Hinzufügen der hervorgehobenen fetten Linien verbessert die Ergebnisse erheblich, wie in der folgenden Abbildung zu sehen ist:


Hier haben wir die Systemleistung verbessert. Das mag nicht viel erscheinen, aber es zeigt, dass frühe Veränderungen eine entscheidende Rolle spielen. Wir haben eine ungefähre Zeit von 2 Minuten 29 Sekunden erreicht, um einen 1-Minuten-Balken zu erzeugen. Mit anderen Worten, das System hat sich insgesamt verbessert, aber obwohl das ermutigend klingt, gibt es ein Problem, das die Sache verkompliziert. Wir können die Zeit zwischen den Ereignissen, die von der Funktion EventSetMillisecondTimer erzeugt werden, nicht verkürzen, was uns über einen anderen Ansatz nachdenken lässt.

Es wurde jedoch eine kleine Verbesserung des Systems vorgenommen, wie unten dargestellt:

                void Event_OnTime(void)
                        {
                                bool isNew;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return;
                                if (m_dt == 0)
                                {
                                        m_Rate[0].close = m_Rate[0].open =  m_Rate[0].high = m_Rate[0].low = m_ArrayInfoTicks[m_ReplayCount].Last;
                                        m_Rate[0].tick_volume = 0;
                                        m_Rate[0].time = m_ArrayInfoTicks[m_ReplayCount].dt - 60;
                                        CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                        _dt = TimeLocal();
                                }

// ... Internal code ....

                        }

Was die hervorgehobenen Linien bewirken, ist auf dem Chart zu sehen. Ohne sie ist der erste Balken immer abgeschnitten, was das korrekte Lesen erschwert. Wenn wir jedoch diese beiden Zeilen hinzufügen, wird die visuelle Darstellung viel schöner, sodass wir die erzeugten Balken richtig sehen können. Dies geschieht ab dem ersten Balken. Es ist etwas sehr Einfaches, aber es macht am Ende einen großen Unterschied.

Aber kommen wir zurück zu unserer ursprünglichen Frage, die darin besteht, ein geeignetes System für die Darstellung und Erstellung der Balken zu finden. Selbst wenn es möglich wäre, die Zeit zu verkürzen, hätten wir kein angemessenes System. In der Tat müssen wir den Ansatz ändern. Aus diesem Grund ist der EA nicht der beste Weg, um ein Replay-System zu erstellen. Trotzdem möchte ich noch etwas anderes zeigen, das für Sie interessant sein könnte. Inwieweit können wir die Erstellung eines 1-Minuten-Balkens tatsächlich reduzieren oder verbessern, wenn wir die kürzestmögliche Zeit für die Erzeugung des OnTime-Ereignisses verwenden? Was ist, wenn sich der Wert nicht innerhalb einer Minute ändert und wir die Daten weiter in den Tick-Bereich komprimieren? Wird es einen Unterschied machen?


Bis zum Äußersten gehen

Zu diesem Zweck müssen wir die letzte Änderung am Code vornehmen. Er ist unten aufgeführt:

#define macroRemoveSec(A) (A - (A % 60))
#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                void LoadFile(const string szFileName)
                        {
                                int file;
                                string szInfo;
                                double last;
                                long    vol;
                                uchar flag;
                                datetime mem_dt = 0;
                                
                                if ((file = FileOpen("Market Replay\\Ticks\\" + szFileName + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE)
                                {
                                        ArrayResize(m_ArrayInfoTicks, def_MaxSizeArray);
                                        m_ArrayCount = 0;
                                        last = 0;
                                        vol = 0;
                                        for (int c0 = 0; c0 < 7; c0++) FileReadString(file);
                                        Print("Loading data to Replay.\nPlease wait ....");
                                        while ((!FileIsEnding(file)) && (m_ArrayCount < def_MaxSizeArray))
                                        {
                                                szInfo = FileReadString(file);
                                                szInfo += " " + FileReadString(file);                                           
                                                m_ArrayInfoTicks[m_ArrayCount].dt = macroRemoveSec(StringToTime(StringSubstr(szInfo, 0, 19)));
                                                m_ArrayInfoTicks[m_ArrayCount].milisec = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                                m_ArrayInfoTicks[m_ArrayCount].Bid = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Ask = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Last = StringToDouble(FileReadString(file));
                                                m_ArrayInfoTicks[m_ArrayCount].Vol = vol + StringToInteger(FileReadString(file));
                                                flag = m_ArrayInfoTicks[m_ArrayCount].flag = (uchar)StringToInteger(FileReadString(file));
                                                if (((flag & TICK_FLAG_ASK) == TICK_FLAG_ASK) || ((flag & TICK_FLAG_BID) == TICK_FLAG_BID)) continue;
                                                if ((mem_dt == macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt)) && (last == m_ArrayInfoTicks[m_ArrayCount].Last)) vol += m_ArrayInfoTicks[m_ArrayCount].Vol; else
                                                {
                                                        mem_dt = macroGetMin(m_ArrayInfoTicks[m_ArrayCount].dt);
                                                        last = m_ArrayInfoTicks[m_ArrayCount].Last;
                                                        vol = 0;
                                                        m_ArrayCount++;
                                                }
                                        }
                                        FileClose(file);
                                        Print("Upload completed.\n", m_ArrayCount, " movement positions were generated.\nStarting Replay.");
                                        return;
                                }
                                Print("Failed to access tick data file.");
                                ExpertRemove();
                        };
#undef macroRemoveSec
#undef macroGetMin

Der hervorgehobene Code behebt ein kleines Problem, das schon vorher bestand, das wir aber nicht bemerkt haben. Wenn der Preis unverändert blieb, aber ein Übergang von einem Balken zum anderen stattfand, dauerte es einige Zeit, bis ein neuer Balken erstellt wurde. Das eigentliche Problem bestand jedoch darin, dass der Eröffnungskurs nicht mit dem im ursprünglichen Chart angezeigten Kurs übereinstimmte. Dies wurde also korrigiert. Wenn nun alle anderen Parameter gleich sind oder sich nur geringfügig in Millisekunden unterscheiden, haben wir nur eine gespeicherte Position.

Danach können wir das System mit EventSetMillisecondTimer von 20 testen. Das Ergebnis sieht wie folgt aus:

 

In diesem Fall betrug das Ergebnis 2 Minuten 34 Sekunden für ein Ereignis von 20 Millisekunden... Ändern wir dann den Wert des EventSetMillisecondTimer auf 10 (das ist der in der Dokumentation angegebene Mindestwert). Hier ist das Ergebnis:

 

In diesem Fall war das Ergebnis 1 Minute 56 Sekunden für ein Ereignis von 10 Millisekunden. Das Ergebnis hat sich zwar verbessert, aber es ist immer noch weit von dem entfernt, was wir brauchen. Und jetzt gibt es keine Möglichkeit mehr, das Zeitereignis mit der in diesem Artikel angewandten Methode weiter zu reduzieren, da die Dokumentation selbst uns darüber informiert, dass dies nicht möglich ist, oder wir nicht genug Stabilität haben, um den nächsten Schritt zu machen.


Schlussfolgerung

In diesem Artikel habe ich die grundlegenden Prinzipien für diejenigen vorgestellt, die ein Replay-/Simulationssystem erstellen wollen. Diese Prinzipien liegen dem gesamten System zugrunde, aber für diejenigen, die keine Programmiererfahrung haben, kann das Verständnis, wie die MetaTrader 5-Plattform funktioniert, eine gewaltige Aufgabe sein. Zu sehen, wie diese Prinzipien in der Praxis angewandt werden, kann eine große Motivation sein, mit dem Programmieren zu beginnen. Denn die Dinge werden erst dann interessant, wenn wir sehen, wie sie funktionieren; der bloße Blick auf den Code ist nicht motivierend.

Wenn man erst einmal weiß, was man tun kann und versteht, wie es funktioniert, ändert sich alles. Es ist, als würde sich eine magische Tür öffnen, die eine ganz neue, unbekannte Welt voller Möglichkeiten eröffnet. Wie dies geschieht, werden Sie im Laufe dieser Serie sehen. Ich werde dieses System weiterentwickeln, während ich Artikel schreibe, also haben Sie bitte etwas Geduld. Selbst wenn es so aussieht, als gäbe es keinen Fortschritt, gibt es immer einen Fortschritt. Und man kann nie genug wissen. Vielleicht macht es uns weniger glücklich, aber es schadet nie.

Der Anhang enthält die beiden hier besprochenen Versionen. Außerdem finden Sie zwei echte Tick-Dateien, mit denen Sie experimentieren und sehen können, wie sich das System auf Ihrer eigenen Hardware verhält. Die Ergebnisse werden sich nicht wesentlich von dem unterscheiden, was ich gezeigt habe, aber es kann recht interessant sein zu sehen, wie ein Computer bestimmte Probleme behandelt und sie auf recht kreative Weise löst.

Im nächsten Artikel werden wir einige Änderungen vornehmen und versuchen, ein angemesseneres System zu schaffen. Wir werden uns auch eine andere interessante Lösung ansehen, die auch für diejenigen nützlich ist, die gerade erst in die Welt der Programmierung einsteigen. Die Arbeit hat also gerade erst begonnen.


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

Beigefügte Dateien |
Replay.zip (10910.23 KB)
Entwicklung eines Replay-Systems — Marktsimulation (Teil 02): Erste Versuche (II) Entwicklung eines Replay-Systems — Marktsimulation (Teil 02): Erste Versuche (II)
Diesmal wollen wir einen anderen Ansatz wählen, um das 1-Minuten-Ziel zu erreichen. Diese Aufgabe ist jedoch nicht so einfach, wie man vielleicht denkt.
Kategorientheorie in MQL5 (Teil 8): Monoide Kategorientheorie in MQL5 (Teil 8): Monoide
Dieser Artikel setzt die Serie über die Implementierung der Kategorientheorie in MQL5 fort. Hier führen wir Monoide als Bereich (Menge) ein, der die Kategorientheorie von anderen Datenklassifizierungsmethoden abhebt, indem er Regeln und ein Identitätselement enthält.
MQL5 — Auch Sie können ein Meister dieser Sprache werden MQL5 — Auch Sie können ein Meister dieser Sprache werden
Dieser Artikel wird eine Art Interview mit mir selbst sein, in dem ich Ihnen erzähle, wie ich meine ersten Schritte in der Sprache MQL5 gemacht habe. Ich werde Ihnen zeigen, wie Sie ein großartiger MQL5-Programmierer werden können. Ich erkläre Ihnen die notwendigen Grundlagen, damit Sie dieses Kunststück vollbringen können. Die einzige Voraussetzung ist die Bereitschaft zu lernen.
Automatisierter Raster-Handel mit Stop-Pending-Aufträge an der Moscow Exchange (MOEX) Automatisierter Raster-Handel mit Stop-Pending-Aufträge an der Moscow Exchange (MOEX)
Der Artikel befasst sich mit dem Ansatz des Raster-Handels (Grid-Trading), der auf Stop-Pending-Aufträge basiert und in einem MQL5 Expert Advisor an der Moscow Exchange (MOEX) implementiert wurde. Eine der einfachsten Strategien beim Handel am Markt ist eine Reihe von Aufträgen, die darauf abzielen, den Marktpreis zu „fangen“.