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

Entwicklung eines Replay-Systems — Marktsimulation (Teil 02): Erste Versuche (II)

MetaTrader 5Tester | 20 Juli 2023, 09:42
240 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems — Marktsimulation (Teil 01): Bei den ersten Experimenten (I)“ haben wir festgestellt, dass es bei dem Versuch, ein Ereignissystem mit kurzer Ausführungszeit zu schaffen, um eine angemessene Marktsimulation zu erzeugen, einige Einschränkungen gibt. Es wurde klar, dass es unmöglich war, mit diesem Ansatz weniger als 10 Millisekunden zu erreichen. In vielen Fällen ist diese Zeit recht gering. Wenn Sie jedoch die dem Artikel beigefügten Dateien studieren, werden Sie feststellen, dass 10 Millisekunden nicht ausreichen. Gibt es eine andere Methode, mit der wir die gewünschte Zeit von 1 oder 2 Millisekunden erreichen können?

Bevor wir uns mit der Verwendung von Zeit im Bereich von wenigen Millisekunden befassen, sollten wir daran erinnern, dass dies keine einfache Aufgabe ist. Der vom Betriebssystem selbst bereitgestellte Timer kann diese Werte nämlich nicht erreichen. Daher ist dies ein großes, wenn nicht gar RIESIGES Problem. In diesem Artikel werde ich versuchen, diese Frage zu beantworten und zu zeigen, wie Sie versuchen können, das Problem zu lösen, indem Sie das vom Betriebssystem gesetzte Zeitlimit überwinden. Ich weiß, dass viele Menschen glauben, dass ein moderner Prozessor Milliarden von Berechnungen pro Sekunde durchführen kann. Es ist jedoch eine Sache, wenn der Prozessor Berechnungen durchführt, und eine ganz andere Frage ist, ob alle Prozesse im Inneren des Computers die erforderlichen Aufgaben bewältigen können. Bitte beachten Sie, dass wir versuchen, dafür ausschließlich MQL5 zu verwenden, ohne irgendeinen externen Code oder eine DLL zu nutzen. Wir verwenden nur reines MQL5.


Planung

Um dies zu überprüfen, müssen wir einige Änderungen an der Methodik vornehmen. Wenn dieser Prozess funktioniert, müssen wir uns nicht mehr mit dem System zur Erstellung von Wiederholungen (replays) herumschlagen. Wir werden uns auf andere Fragen konzentrieren, die uns bei der Durchführung von Forschungen oder Schulungen mit echten oder simulierten Tick-Werten helfen werden. Die Art und Weise, wie die 1-Minuten-Riegel zusammengesetzt werden, bleibt gleich. Dies wird der Schwerpunkt dieses Artikels sein.

Wir werden einen maximal generischen Ansatz verwenden, und der beste Weg, den ich gefunden habe, ist die Verwendung eines Client-Server-ähnlichen Systems. Ich habe diese Technik bereits in meinem früheren Artikel „Entwicklung eines Trading Expert Advisors von Grund auf (Teil 16): Zugang zu Daten im Internet (II)“ erklärt. In diesem Artikel habe ich drei Möglichkeiten zur Übermittlung von Informationen im MetaTrader 5 aufgezeigt. Hier werden wir eine dieser Methoden anwenden, nämlich SERVICE. Markt-Replay wird also ein MetaTrader 5-Dienst werden.

Sie werden vielleicht denken, dass ich alles von Grund auf neu erstellen werde. Aber warum sollte ich so etwas tun? Im Grunde läuft das System bereits, erreicht aber nicht die gewünschte 1-Minuten-Zeit. Das fragen Sie sich vielleicht: „Glauben Sie, dass die Umstellung des Systems auf einen Dienst dieses Problem lösen wird?“ Die einfache Ersetzung des Systems durch eine Dienstleistung wird unser Problem nicht lösen. Wenn wir jedoch die Erstellung von 1-Minuten-Balken von Anfang an vom Rest des EA-Systems isolieren, haben wir später weniger Arbeit, weil der EA selbst eine leichte Verzögerung bei der Ausführung der Balkenkonstruktion verursacht. Die Gründe dafür werde ich später erläutern.

Verstehen Sie jetzt, warum wir eine Dienstleistung in Anspruch nehmen? Sie ist viel praktischer als die anderen oben beschriebenen Methoden. Wir können es auf die gleiche Weise steuern, wie ich in dem Artikel über den Austausch von Nachrichten zwischen einem EA und einem Dienst erklärt habe: „Einen Trading EA von Grund auf entwickeln (Teil 17): Zugriff auf Daten im Internet (III)“. Aber hier geht es nicht darum, dieses Steuerelement zu erzeugen, sondern nur darum, dass der Dienst die Balken erzeugt, die im Chart angezeigt werden. Um die Dinge interessanter zu machen, werden wir die Plattform auf kreativere Weise nutzen und nicht nur einen EA und einen Dienst verwenden.

Zur Erinnerung: Beim letzten Versuch, die Zeit zu verkürzen, kamen wir zu folgendem Ergebnis:

Das war die beste Zeit, die wir hatten. Hier werden wir dieses Mal gleich zuschlagen. Ich möchte jedoch nicht, dass Sie sich völlig auf diese Werte oder die hier gezeigten Tests versteifen. Diese Artikelserie, die sich mit der Erstellung des Replays-/Simulator-Systems befasst, ist bereits in einem fortgeschrittenen Stadium, in dem ich einige Konzepte mehrfach geändert habe, um das System tatsächlich wie erwartet zum Laufen zu bringen. Auch wenn zu diesem Zeitpunkt alles angemessen zu sein scheint, so habe ich doch einige Fehler im Zusammenhang mit den Zeittests gemacht. Solche Fehler oder Missverständnisse sind in einem so frühen System nicht leicht zu erkennen. Im weiteren Verlauf dieser Artikelserie werden Sie feststellen, dass dieses Timing-Problem viel komplexer ist und dass es um viel mehr geht als nur darum, dass die CPU und die MetaTrader 5-Plattform eine bestimmte Menge an Daten auf dem Chart bereitstellen, damit Sie in das Replay-/Simulator-System eintauchen können.

Nehmen Sie also nicht alles wörtlich, was Sie hier sehen. Verfolgen Sie diese Artikelserie, denn das, was wir hier tun werden, ist weder einfach noch leicht zu machen.


Umsetzung

Beginnen wir damit, das Fundament unseres Systems zu schaffen. Dazu gehören:

  1. Dienst zur Erstellung von 1-Minuten-Balken
  2. Skript zum Starten des Dienstes
  3. EA für die Simulation (dies wird später erörtert)


Definition des Markt-Replay Service

Um mit dem Dienst richtig arbeiten zu können, müssen wir unsere Klasse C_Replay aktualisieren. Diese Änderungen sind jedoch sehr geringfügig, sodass wir nicht ins Detail gehen werden. Dies sind im Wesentlichen die Rückgabecodes. Es gibt jedoch einen Punkt, der gesondert zu erwähnen ist, da er etwas Zusätzliches einführt. Der Code lautet wie folgt:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
                int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                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();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                {
                                        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);
                                        isNew = false;
                                        m_ReplayCount++;
                                }
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Elapsed time: ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

Die hervorgehobenen Teile wurden in den Quellcode der Klasse C_Replay eingefügt. Wir legen die Verzögerungszeit fest, d. h. wir verwenden genau den in der Zeile angegebenen Wert, aber in Millisekunden. Vergessen Sie nicht, dass diese Zeit nicht exakt sein wird, da sie von einigen Variablen abhängt. Wir werden jedoch versuchen, den Wert so nahe wie möglich an 1 Millisekunde zu halten.

Mit diesen Änderungen im Hinterkopf, schauen wir uns den untenstehenden Dienstcode an:

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string    user01 = "WINZ21_202110220900_202110221759"; //File with ticks
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        ulong t1;
        int delay = 3;
        
        if (!Replay.CreateSymbolReplay(user01)) return;
        Print("Waiting for permission to start replay ...");
        GlobalVariableTemp(def_GlobalVariable01);
        while (!GlobalVariableCheck(def_SymbolReplay)) Sleep(750);
        Print("Replay service started ...");
        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
                if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);
        Print("Replay service finished ...");
}
//+------------------------------------------------------------------+

Der obige Code ist für die Erstellung der Balken verantwortlich. Indem wir diesen Code hier platzieren, sorgen wir dafür, dass das Replay-System unabhängig funktioniert: Der Betrieb der MetaTrader 5-Plattform wird dadurch kaum beeinträchtigt oder beeinflusst. Wir können also mit anderen Dingen arbeiten, die mit dem Kontrollsystem, der Analyse und der Simulation der Wiedergabe zusammenhängen. Dies wird jedoch zu einem späteren Zeitpunkt geschehen.

Jetzt kommt etwas Interessantes: Achten Sie darauf, dass die hervorgehobenen Teile die Funktion GetTickCount64 enthalten. Damit erhalten wir ein System, das dem des vorigen Artikels entspricht, allerdings mit einem Vorteil: Die Auflösung sinkt auf eine Zeit von 1 Millisekunde. Diese Genauigkeit ist nicht exakt, sondern nur annähernd, aber der Grad der Annäherung kommt der tatsächlichen Marktbewegung sehr nahe. Dies hängt nicht von der von Ihnen verwendeten Hardware ab. Schließlich kann man auch eine Schleife erstellen, die eine höhere Genauigkeit garantiert, aber das wäre ziemlich mühsam, da es diesmal von der verwendeten Hardware abhängt.

Als Nächstes müssen Sie das folgende Skript ausführen. Hier ist der vollständige Code:

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
C_Replay Replay;
//+------------------------------------------------------------------+
void OnStart()
{
        Print("Waiting for the Replay System ...");
        while((!GlobalVariableCheck(def_GlobalVariable01)) && (!IsStopped())) Sleep(500);
        if (IsStopped()) return;
        Replay.ViewReplay();
        GlobalVariableTemp(def_SymbolReplay);
        while ((!IsStopped()) && (GlobalVariableCheck(def_GlobalVariable01))) Sleep(500);
        GlobalVariableDel(def_SymbolReplay);
        Print("Replay Script finished...");
        Replay.CloseReplay();
}
//+------------------------------------------------------------------+

Wie Sie sehen können, sind beide Codes recht einfach. Sie kommunizieren jedoch über globale Variablen, die von der Plattform unterstützt werden, miteinander. Daraus ergibt sich das folgende Schema:

Dieses System wird von der Plattform selbst gepflegt. Wenn das Skript geschlossen wird, wird der Dienst beendet. Wenn der Dienst anhält, empfängt das Symbol, das wir zur Ausführung des Replay-Systems verwenden, keine Daten mehr. Das macht es super einfach und sehr nachhaltig. Jede Verbesserung (sowohl bei der Plattform als auch bei der Hardware) schlägt sich automatisch in der Gesamtleistung nieder. Das ist kein Wunder – das alles wird durch die geringen Latenzen erreicht, die bei jedem vom Dienstprozess durchgeführten Vorgang auftreten. Nur dies wird sich auf das System im Allgemeinen auswirken, wir müssen uns keine Gedanken über das Skript oder die WA machen, die wir in Zukunft entwickeln werden. Alle Verbesserungen werden sich nur auf den Dienst auswirken.

Um Ihnen die Mühe zu ersparen, das System zu testen, können Sie sich das Ergebnis in der folgenden Abbildung ansehen. Sie, liebe Leserin, lieber Leser, müssen also nicht eine ganze Minute warten, bis Sie das Ergebnis auf Ihrer Chart sehen.

Wie Sie sehen können, kommt das Ergebnis dem Ideal sehr nahe. Die zusätzlichen 9 Sekunden können über die Systemeinstellungen leicht eliminiert werden. Im Idealfall sollte die Zeit weniger als 1 Minute betragen, was die Anpassung erleichtert, da wir dem System nur eine Verzögerung hinzufügen müssen. Es ist einfacher, die Latenzzeit zu verlängern als sie zu verkürzen. Wenn Sie jedoch der Meinung sind, dass die Systemzeit nicht verkürzt werden kann, sollten wir uns das einmal genauer ansehen.

Es gibt einen Punkt, der zu einer Verzögerung im System führt, und das ist der Dienst. Dieser Punkt, der tatsächlich eine Verzögerung erzeugt, ist im nachstehenden Code hervorgehoben. Was aber, wenn wir diese Zeile zu einem Kommentar machen? Was wird mit dem System geschehen?

        t1 = GetTickCount64();
        while (GlobalVariableCheck(def_SymbolReplay))
        {
// ...  COMMENT ...  if ((GetTickCount64() - t1) >= (uint)(delay))
                {
                        if ((delay = Replay.Event_OnTime()) < 0) break;
                        t1 = GetTickCount64();
                }
        }
        GlobalVariableDel(def_GlobalVariable01);

Die markierte Zeile wird nicht mehr ausgeführt. In diesem Fall erspare ich es Ihnen, das System lokal zu testen und erneut eine Minute warten zu müssen. Das Ergebnis der Ausführung ist in dem folgenden Video zu sehen. Sie können sich den gesamten Film ansehen oder zu dem Teil springen, in dem nur das Endergebnis gezeigt wird. Sie haben die freie Wahl.



Das heißt, die größte Herausforderung besteht darin, eine Verzögerung zu erzeugen. Aber die kleine Zeitabweichung von 1 Minute für die Erstellung des Balkens ist nicht wirklich ein Problem. Denn selbst bei einem echten Konto kennen wir die genaue Zeit nicht, da es bei der Informationsübertragung eine Latenz gibt. Diese Latenzzeit ist zwar recht gering, aber sie ist dennoch vorhanden.


Höchstgeschwindigkeit. Wirklich?

Hier werden wir einen letzten Versuch unternehmen, das System in weniger als 1 Minute zum Laufen zu bringen.

Wenn Sie sich die Millisekundenwerte ansehen, können Sie feststellen, dass manchmal eine Abweichung von nur 1 Millisekunde zwischen einer Zeile und einer anderen besteht. Aber wir werden alles in der gleichen Sekunde behandeln. Wir können also eine kleine Änderung am Code vornehmen. Wir werden darin eine Schleife einbauen, die einen großen Unterschied im Gesamtsystem ausmachen kann.

Die Änderungen sind nachstehend aufgeführt:

#define macroGetMin(A)  (int)((A - (A - ((A % 3600) - (A % 60)))) / 60)
inline int Event_OnTime(void)
                        {
                                bool isNew;
                                int mili;
                                static datetime _dt = 0;
                                
                                if (m_ReplayCount >= m_ArrayCount) return -1;
                                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();
                                }
                                isNew = m_dt != m_ArrayInfoTicks[m_ReplayCount].dt;
                                m_dt = (isNew ? m_ArrayInfoTicks[m_ReplayCount].dt : m_dt);
                                mili = m_ArrayInfoTicks[m_ReplayCount].milisec;
                                do
                                {
                                        while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec)
                                        {
                                                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);
                                                isNew = false;
                                                m_ReplayCount++;
                                        }
                                        mili++;
                                }while (mili == m_ArrayInfoTicks[m_ReplayCount].milisec);
                                m_Rate[0].time = m_dt;
                                CustomRatesUpdate(def_SymbolReplay, m_Rate, 1);
                                mili = (m_ArrayInfoTicks[m_ReplayCount].milisec < mili ? m_ArrayInfoTicks[m_ReplayCount].milisec + (1000 - mili) : m_ArrayInfoTicks[m_ReplayCount].milisec - mili);
                                if ((macroGetMin(m_dt) == 1) && (_dt > 0))
                                {
                                        Print("Elapsed time: ", TimeToString(TimeLocal() - _dt, TIME_SECONDS));
                                        _dt = 0;
                                }                               
                                return (mili < 0 ? 0 : mili);
                        };
#undef macroGetMin

Wie Sie sehen, haben wir jetzt eine äußere Schleife, die diesen 1ms-Test durchführt. Da es sehr schwierig ist, eine korrekte Anpassung innerhalb des Systems vorzunehmen, sodass wir diese eine Millisekunde nutzen können, ist es vielleicht besser, sie aus dem Spiel zu nehmen.

Wir haben nur eine Änderung vorgenommen. Das Ergebnis sehen Sie im unten stehenden Video.



Wer es noch schneller haben will, kann sich das Ergebnis ansehen:

Ich denke, das ist genug. Unterhalb dieses Zeitpunkts wird nun ein 1-Minuten-Balken erstellt. Wir können Anpassungen vornehmen, um die perfekte Zeit zu erreichen, was zu Verzögerungen im System führt. Aber wir werden das nicht tun, denn wir wollen ein System haben, das uns die Durchführung von Simulationsstudien ermöglicht. Für das Training und zum Üben ist alles unter 1 Minute in Ordnung. Es muss nicht unbedingt etwas Genaues sein.


Schlussfolgerung

Jetzt haben wir die Grundlagen des Replay-Systems, das wir erstellen, und wir können zu den nächsten Punkten übergehen. Sehen Sie, dass alles nur durch die Verwendung von Einstellungen und Funktionen in der MQL5-Sprache gelöst wurde, was beweist, dass sie tatsächlich viel mehr kann, als viele Leute denken.

Aber bitte beachten Sie, dass unsere Arbeit gerade erst begonnen hat. Es gibt noch viel zu tun.

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

Beigefügte Dateien |
Replay.zip (10746.69 KB)
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.
Entwicklung eines Replay-Systems — Marktsimulation (Teil 01): Erste Versuche (I) Entwicklung eines Replay-Systems — Marktsimulation (Teil 01): Erste Versuche (I)
Wie wäre es, ein System zu schaffen, das es uns ermöglicht, den Markt zu studieren, wenn er geschlossen ist, oder sogar Marktsituationen zu simulieren? Wir beginnen hier eine neue Artikelserie, in der wir uns mit diesem Thema beschäftigen werden.
Rebuy-Algorithmus: Handelssimulation mit mehreren Währungen Rebuy-Algorithmus: Handelssimulation mit mehreren Währungen
In diesem Artikel werden wir ein mathematisches Modell zur Simulation der Preisbildung in mehreren Währungen erstellen und die Untersuchung des Diversifizierungsprinzips als Teil der Suche nach Mechanismen zur Steigerung der Handelseffizienz abschließen, die ich im vorherigen Artikel mit theoretischen Berechnungen begonnen habe.
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.