English Русский 中文 Español 日本語 Português
preview
Developing a Replay System — Market simulation (Part 13): Die Geburt des SIMULATORS (III)

Developing a Replay System — Market simulation (Part 13): Die Geburt des SIMULATORS (III)

MetaTrader 5Tester | 30 November 2023, 09:45
182 0
Daniel Jose
Daniel Jose


Einführung

Der vorherige Artikel „Entwicklung eines Replay Systems — Marktsimulation (Teil 12): Die Geburt des SIMULATOR (II)“ war eine Vorbereitung für diesen Artikel. Heute werden wir einige Änderungen am Simulationssystem vornehmen, um eine größere Konsistenz der Daten zu erreichen. Gleichzeitig werden wir einige große Änderungen vornehmen, um das System in Bezug auf die Verarbeitung effizienter zu gestalten. Wir werden dies in den nächsten Schritten zur Erstellung unseres Wiedergabe-/Simulationssystems benötigen. Der Punkt ist folgender: Damit das System tatsächlich von der Wiedergabe oder dem Simulator genutzt werden kann, muss es ein konsistentes Verhalten aufweisen, oder zumindest ein möglichst konsistentes und einheitliches Verhalten. Wir können nicht einfach dafür sorgen, dass ein System zu einem bestimmten Zeitpunkt auf eine bestimmte Art und Weise funktioniert und dann zu einem anderen Zeitpunkt völlig anders und unvorhersehbar funktioniert.

In dem Artikel „Entwicklung eines Replay Systems — Marktsimulation (Teil 02): Erste Experimente (II)“, wir haben ein System geschaffen, das bisher brauchbar war, aber in dem Moment, in dem es um Simulation und die Erzeugung von Pseudo-Zufallsdaten geht, ist es nicht mehr geeignet. Um ehrlich zu sein, selbst wenn wir mit Replay (mit echten Tickets) arbeiten, ist das derzeitige System nicht ganz geeignet. Dies gilt vor allem dann, wenn der gewünschte Vermögenswert oder Tag eine hohe Volatilität aufweist. In diesem Szenario ist das derzeitige System zur Erstellung und Darstellung von 1-Minuten-Balken sehr ineffizient und kann manchmal zu Synchronisationsproblemen führen. Mit anderen Worten: Balken, deren Aufbau eine Minute dauern sollte, brauchen manchmal viel länger, was den falschen Eindruck erweckt, dass Bewegungen mit hoher Volatilität leicht zu verfolgen oder zu handeln sind, was nicht stimmt.

Die Lösung für dieses Problem ist alles andere als einfach, denn wir müssen die Art und Weise ändern, wie sie tatsächlich gebaut wird. Man könnte meinen, dass diese Aufgabe einfach ist, aber das ist sie nicht. Es geht um eine bestimmte Art der Modellierung, die ziemlich schwierig ist, wenn man nicht weiß, was man tut. Sogar ich habe die ganze Zeit gebraucht (und ich bin es, der Ihnen zeigt, wie man es macht), um zu erkennen, dass mit dem System zur Erstellung von Balken etwas nicht stimmt. Ich habe dies erst bemerkt, als ich in die Modellierungsphase eintrat, in der die Unterschiede im Laufe der Zeit wirklich deutlich wurden, da dies einige Berechnungen erfordert, die wir später sehen werden. Aber selbst jetzt glaube ich nicht, dass ich dieses Problem lösen kann. Dieses Problem wird später, in der nächsten Bauphase, gelöst. Beginnen wir mit einigen Korrekturen und der Einführung eines neuen Systems für die Erstellung von 1-Minuten-Balken.


Der neue Dienst des Replay Systems

Um die 1-Minuten-Balken tatsächlich zu erstellen, sodass wir sie bei Bedarf überprüfen können, müssen wir einige Änderungen am Wiedergabedienst vornehmen. Das erste, was wir ändern müssen, ist die Servicedatei. Nachstehend finden Sie ein vollständiges Bild der neuen Wiedergabedienstdatei.

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property description "https://www.mql5.com/en/articles/11034"
#property link "https://www.mql5.com/en/articles/11034"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Config.txt";  //"Replay" config file.
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;     //Initial timeframe for the chart.
input bool              user02 = true;          //Visual bar construction.
input bool              user03 = true;          //Visualize creation metrics.
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        Replay;
        
        Replay.InitSymbolReplay();
        if (Replay.SetSymbolReplay(user00))
        {
                Print(<"Wait for permission from [Market Replay] indicator to start replay...");
                if (Replay.ViewReplay(user01))
                {
                        Print("Permission received. The replay service can now be used...");
                        while (Replay.LoopEventOnTime(user02, user03));
                }
        }       
        Replay.CloseReplay();
        Print("Replay service completed...");
}
//+------------------------------------------------------------------+

Wie Sie sehen können, ist dies viel einfacher, zumindest auf den ersten Blick. Die gesamte Komplexität wird in die Objektklasse verlagert, und dafür gibt es einen sehr guten Grund: ZEIT. Es gibt hier immer noch subtile Probleme, die ich im Laufe des Artikels erläutern werde, aber wie im Fall der alten Servicedatei nahmen einige Operationen dem System wertvolle Millisekunden ab. Trotz der Versuche, die Effizienz der MetaTrader 5-Plattform zu verbessern, führten all diese verbrauchten Millisekunden letztendlich zu einer verringerten Leistung, sodass es länger dauerte, 1-Minuten-Balken tatsächlich zu verarbeiten und darzustellen.

Wenn wir jedoch genauer hinsehen, kann man feststellen, dass eine neue Variable für den Nutzer hinzugefügt wurde. So können wir die Zeit, die für die Erstellung von 1-Minuten-Balken aufgewendet wird oder erforderlich ist, erstellen und überprüfen. Beachten Sie, dass wir jetzt nur noch einen Aufruf haben, der die Schleife zur Erstellung des Balken blockiert. Dieser Aufruf wird nur in zwei ganz bestimmten Situationen zurückgegeben. Aber Sie werden bald sehen, von welchen Situationen wir sprechen. Diese Schleife läuft dann wie eine Endlosschleife, wird aber durch die Funktion, die sich innerhalb der Objektklasse befindet, gesteuert. Aufgrund dieser Vereinfachung besteht die eigentliche Datei nur aus dieser einen Datei. Was die Objektklasse betrifft, so hat sich ihre Komplexität erhöht. Infolgedessen sind mehrere Funktionen, die zuvor öffentlich waren, nun nicht mehr öffentlich, sondern privat für die Klasse. Dies wird als Methodenverschleierung bezeichnet. Daher sind die einzigen wirklich öffentlichen Elemente die Funktionen, die in der Servicedatei erscheinen, wie oben zu sehen ist.

Auf diese Weise werden wir beginnen, die Veränderungen zu erkennen, die stattgefunden haben. Dies ermöglicht eine noch stärkere Vereinfachung der Servicedatei. Die erste der Änderungen ist unten dargestellt:

                bool ViewReplay(ENUM_TIMEFRAMES arg1)
                        {
                                u_Interprocess info;
                                
                                if ((m_IdReplay = ChartFirst()) > 0) do
                                {
                                        if (ChartSymbol(m_IdReplay) == def_SymbolReplay)
                                        {
                                                ChartClose(m_IdReplay);
                                                ChartRedraw();
                                        }
                                }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0);
                                info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1);
                                ChartApplyTemplate(m_IdReplay, "Market Replay.tpl");
                                ChartRedraw(m_IdReplay);
                                GlobalVariableDel(def_GlobalVariableIdGraphics);
                                GlobalVariableTemp(def_GlobalVariableIdGraphics);
                                GlobalVariableSet(def_GlobalVariableIdGraphics, info.u_Value.df_Value);
                                while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750);
                                
                                return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != ""));
                        }

Der Code ist im Grunde derselbe wie zuvor, aber wir haben Tests hinzugefügt, die zuvor im Code der Servicedatei ausgeführt wurden. Auf diese Weise muss die Servicedatei nicht mehr wissen, welche Chart-ID zur Anzeige des Wiedergabematerials verwendet wird. Daher erwartet dieser Punkt, dass eine globale Terminalvariable durch den Indikator erstellt wird. Wenn der Nutzer jedoch das Chart schließt oder den Dienst beendet, wird diese Schleife beendet. Wenn jedoch alles in Ordnung ist und die Variable definiert ist, ohne dass der Nutzer das Chart oder den Dienst geschlossen hat, erhalten wir den Wert TRUE und der Dienst geht zum nächsten Schritt über, der unten dargestellt ist.

                bool LoopEventOnTime(const bool bViewBuider, const bool bViewMetrics)
                        {

                                u_Interprocess Info;
                                int iPos, iTest;
                                
                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;
                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               
                                return (m_ReplayCount == m_Ticks.nTicks);
                        }

Diese Routine ist viel mehr, als es scheint. Man könnte meinen, dass sie ständig hinein- und hinausgeht. Tatsächlich wird aber nur in zwei Fällen auf den Code der Servicedatei zurückgegriffen. Einmal, wenn der Nutzer die Erstellung von 1-Minuten-Balken unterbricht. Zum Zweiten, wenn der Dienst gestoppt wird, entweder weil das Chart geschlossen ist oder weil wir keine Ticks mehr haben, die wir verwenden können. Das heißt, wenn der Dienst aus irgendeinem Grund eingestellt wird. In allen anderen Fällen erhalten wir den Wert TRUE, wenn das Ende der Ticks erreicht ist, und FALSE in allen anderen Fällen. Der Wert FALSE beendet das Wiedergabe-/Simulationssystem, wie aus dem Code des Dienstes ersichtlich ist.

Schauen wir uns nun an, was in der restlichen Zeit passiert, wenn diese Routine einfach in den enthaltenen inneren Schleifen stecken bleibt. Ja, wir haben 2 Schleifen, von denen jede für etwas ganz Bestimmtes zuständig ist. Konzentrieren wir uns auf beides, um zu verdeutlichen, worum es hier geht. Die erste Schleife ist unten hervorgehoben:

// ... declaring variables ...

                                iTest = 0;
                                while ((iTest == 0) && (!_StopFlag))
                                {
                                        iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1);
                                        iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1);
                                        iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest);
                                        if (iTest == 0) Sleep(100);
                                }
                                if ((iTest < 0) || (_StopFlag)) return false;
                                AdjustPositionToReplay(bViewBuider);
                                m_MountBar.delay = 0;

//... the rest of the code (will be discussed later) ...

Dieses Codestück enthält eine Schleife, die in zwei Situationen funktioniert. Der erste Fall tritt ein, wenn der Dienst nicht durch Schließen des Charts beendet wurde. Achten Sie darauf, dass wir auch eine Variable testen. Was sollen wir also tun? Beachten Sie, dass wir innerhalb der Schleife einige Bedingungen prüfen, um den Wert der Variablen zu ändern, aber woher kommen diese Prüfungen? Diese Kontrollen wurden in der vorherigen Version innerhalb des Dienstcodes eingeführt. Es gab jedoch ein Problem. Jede dieser Überprüfungen dauerte einige Millisekunden, aber die meisten Maschinenzyklen benötigte die Überprüfung, ob die Chart geöffnet war oder nicht.

Wenn diese Prüfung durchgeführt wird, bevor das System tatsächlich mit dem Zeichnen der 1-Minuten-Balken beginnt, können wir Maschinenzyklen sparen. Wir brauchen jedoch einen Ausweg aus dieser Schleife. Wenn der Nutzer also den Dienst startet, haben wir einen Hinweis, dass die Schleife beendet werden sollte. Um sicherzustellen, dass sie sich in Richtung des Balkenbildungssystems bewegt, setzen wir die Testvariable auf einen positiven Wert. Wenn nun aus irgendeinem Grund die Schleife endet und der Nutzer noch nicht mit der Erstellung von Balken begonnen hat, wird FALSE zurückgegeben. Auf diese Weise weiß der Dienst, dass die Wiedergabe/Simulation abgeschlossen werden muss.

Wenn die Auslösebedingung jedoch erfüllt ist, müssen wir zwei Dinge tun: Erstens müssen wir den Startpunkt der Wiedergabe/Simulation finden. Welche Funktion dafür verantwortlich ist, wird später in diesem Artikel erläutert. Der zweite Schritt ist das Zurücksetzen des Verzögerungssystems. Auf diese Weise können wir in die zweite Schleife eintreten, die unten gezeigt wird.

// ... Code from the previous loop...

                                while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag))
                                {
                                        CreateBarInReplay(bViewMetrics);
                                        iPos = (int)(m_ReplayCount < m_Ticks.nTicks ? m_Ticks.Info[m_ReplayCount].time_msc - m_Ticks.Info[m_ReplayCount - 1].time_msc : 0);
                                        m_MountBar.delay += (iPos < 0 ? iPos + 1000 : iPos);
                                        if (m_MountBar.delay > 400)
                                        {
                                                if (ChartSymbol(m_IdReplay) == "") break;
                                                GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                if (!Info.s_Infos.isPlay) return true;
                                                Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks);
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                                Sleep(m_MountBar.delay - 20);
                                                m_MountBar.delay = 0;
                                        }
                                }                               

// ... The rest of the function ...

Diese zweite Schleife ist für das korrekte Funktionieren des Systems verantwortlich. Hier werden wir tatsächlich 1-Minuten-Balken erstellen. Das System verlässt diese Schleife erst, wenn keine Daten mehr vorhanden sind oder das System beendet wird. Es gibt eine weitere Bedingung für das Beenden - wenn der Nutzer eine Pause macht. In diesem Fall wird die Funktion angehalten und dann erneut aufgerufen, um zur ersten Schleife (siehe oben) zurückzukehren. Nun wollen wir uns ansehen, warum dieses System effizienter ist als die vorherige Version des Replay/Simulationsdienstes. Hier werden wir eine Funktion aufrufen, die wir später sehen werden. Es wird ein 1-Minuten-Balken erstellt. Machen Sie sich keine Gedanken darüber, wie es funktioniert – Sie müssen nur wissen, dass die Konstruktion an anderer Stelle erfolgt.

Nun stellt sich aber eine wichtige Frage: Wenn Sie genau hinschauen, werden Sie feststellen, dass jeder der Ticks, unabhängig davon, ob er tatsächlich gehandelt oder simuliert wird, zu einem bestimmten Zeitpunkt erstellt werden muss. Die entsprechenden Segmente sind in der nachstehenden Abbildung hervorgehoben:

Beachten Sie, dass wir Stunden, Minuten und Sekunden haben, die für uns nicht viel bedeuten: Was für uns bei der Wiedergabe/Simulation wirklich wichtig ist, ist die Zahl, die nach den Sekunden kommt, also die Millisekunden. Wenn Sie sich diese Zahlen ansehen, werden Sie feststellen, dass sie wie eine lange Zeit erscheinen mögen. Was wir jedoch wirklich verstehen müssen, ist nicht die Zeit in Millisekunden, sondern die Differenz zwischen der Zeit des letzten Ticks und dem, was wir als Nächstes anzeigen werden. In einigen Fällen ist der Unterschied sehr gering, manchmal weniger als 2 Millisekunden. Das bisherige System konnte damit nicht umgehen. Wir brauchen ein anderes, schnelleres System. Trotz unserer Versuche konnte es nicht richtig funktionieren, wenn die Zeit sehr knapp war, wie auf dem Bild oben zu sehen. Diese Zeitspanne zwischen einem Anruf und dem nächsten beträgt weniger als 10 Millisekunden.

Aber mit dieser neuen Methode zur Darstellung von 1-Minuten-Balken können wir diese Zeit auf modernen Computern auf weniger als 1 Millisekunde reduzieren. Und da das Verfahren zur Konstruktion der Balken, wie wir später sehen werden, recht schnell geworden ist, müssen wir nicht mehr auf OpenCL zurückgreifen, um die entsprechende Leistung zu erhalten. Wir können dies allein mit der CPU erreichen, ohne eine GPU zu benötigen. Bitte beachten Sie jedoch, dass wir nicht mehr jeden Tick verzögern werden. Lassen Sie uns ein wenig sammeln und dann eine kurze Pause einlegen. Der kumulierte Wert und der Pausenwert können geändert werden, sodass wir Feineinstellungen vornehmen können. Auf diese Weise können wir recht gute Ergebnisse erzielen, wie im folgenden Video gezeigt wird. Sie zeigt die Zeit an, die das System zwischen den 1-Minuten-Balken erreichen konnte.

Die Genauigkeit ist nicht perfekt, aber wir können die Werte anpassen, um eine genauere Zeit zu erhalten. Es ist nicht möglich, den internen Zähler des Betriebssystems zu verwenden, um diese genaue Zeit zu erreichen, da dieser Zähler Zeiten unter 16 Millisekunden nicht mit guter Genauigkeit verarbeiten kann. Bitte beachten Sie jedoch, dass es sich um eine Forschungsarbeit handelt, die noch nicht abgeschlossen ist. Es kann einige Zeit dauern, bis wir einen Weg finden, die Situation zu verbessern, aber für den Moment denke ich, dass das genug ist.



Wir müssen auch überprüfen, ob das Chart geöffnet ist, dies wird von Zeit zu Zeit getan. Da wir dies jedoch nicht oft tun, wird die dadurch verursachte Verzögerung viel geringer sein. Nun, bei allen Aufrufen kommt es immer zu einer kleinen Verzögerung. Außerdem müssen wir den Positionswert aktualisieren und den aktuellen Zustand des Kontrollindikators erfassen, was ebenfalls zu einer leichten Ausführungsverzögerung führen wird. Auch die Überprüfung, ob das Chart geöffnet ist oder nicht, führt zu einem kleinen Leistungsverlust. Diese wird jedoch viel geringer sein, weil der Anruf nur wenige Male pro Sekunde erfolgt.

Damit können wir diesen Teil abschließen. Aber schauen wir uns zunächst die beiden anderen Funktionen an, die zu diesem System gehören, das die 1-Minuten-Balken erstellt. Der erste sucht nach dem Punkt, an dem die Wiedergabe/Simulation beginnen soll.

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return;
                                iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1)));
                                Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time);
                                if (iPos < m_ReplayCount)
                                {
                                        CustomRatesDelete(def_SymbolReplay, Rate[0].time, LONG_MAX);
                                        if ((m_dtPrevLoading == 0) && (iPos == 0))
                                        {
                                                m_ReplayCount = 0;
                                                Rate[m_ReplayCount].close = Rate[m_ReplayCount].open = Rate[m_ReplayCount].high = Rate[m_ReplayCount].low = m_Ticks.Info[iPos].last;
                                                Rate[m_ReplayCount].tick_volume = Rate[m_ReplayCount].real_volume = 0;
                                                CustomRatesUpdate(def_SymbolReplay, Rate, 1);
                                        }else
                                        {
                                                for(Rate[0].time -= 60; (m_ReplayCount > 0) && (Rate[0].time <= macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)); m_ReplayCount--);
                                                m_ReplayCount++;
                                        }
                                }else if (iPos > m_ReplayCount)
                                {
                                        if (bViewBuider)
                                        {
                                                Info.s_Infos.isWait = true;
                                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                                        }else
                                        {
                                                for(; Rate[0].time > m_Ticks.Info[m_ReplayCount].time; m_ReplayCount++);
                                                for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++);
                                                CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount);
                                        }
                                }
                                for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay();
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                Info.s_Infos.isWait = false;
                                GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value);
                        }

Im Vergleich zur Vorgängerversion wurden keine größeren Änderungen vorgenommen, aber im Gegensatz zur Vorgängerversion werden in dieser Version keine Werte mehr zurückgegeben. Die Erstellung der Balken wurde geändert, um sie effizienter zu gestalten. Das mag wie eine Kleinigkeit erscheinen, aber die einfache Tatsache, es zu tun, hilft sehr. Nun noch ein kleines Detail: Die obige Funktion ist nicht mehr öffentlich. Außerhalb unserer Objektklasse kann nicht mehr auf sie zugegriffen werden. Dasselbe gilt für die nächste Funktion, die für die Erstellung von 1-Minuten-Balken zuständig ist.

inline void CreateBarInReplay(const bool bViewMetrics = false)
                        {
#define def_Rate m_MountBar.Rate[0]

                                static ulong _mdt = 0;
                                int i;
                                
                                if (m_MountBar.bNew = (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time)))
                                {
                                        if (bViewMetrics)
                                        {
                                                _mdt = (_mdt > 0 ? GetTickCount64() - _mdt : _mdt);
                                                i = (int) (_mdt / 1000);
                                                Print(TimeToString(m_Ticks.Info[m_ReplayCount].time, TIME_SECONDS), " - Metrica: ", i / 60, ":", i % 60, ".", (_mdt % 1000));
                                                _mdt = GetTickCount64();
                                        }
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                def_Rate.close = m_Ticks.Info[m_ReplayCount].last;
                                def_Rate.open = (m_MountBar.bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (m_MountBar.bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (m_MountBar.bNew || (def_Rate.close < def_Rate.low) ? def_Rate.close : def_Rate.low);
                                def_Rate.real_volume += (long) m_Ticks.Info[m_ReplayCount].volume_real;
                                def_Rate.tick_volume += (m_Ticks.Info[m_ReplayCount].volume_real > 0 ? 1 : 0);
                                def_Rate.time = m_MountBar.memDT;
                                m_MountBar.bNew = false;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate, 1);
                                m_ReplayCount++;
                                
#undef def_Rate
                        }

Sie bedarf praktisch keiner Erklärung. Hier erstellen wir einen 1-Minuten-Balken nach dem anderen und senden ihn dann an den Chart. Wir arbeiten noch nicht mit Ticks, d.h. wir können noch nicht einige MetaTrader 5-Ressourcen für die Wiedergabe/Simulation verwenden. Wir werden dies in Zukunft umsetzen. Machen Sie sich also im Moment keine Gedanken darüber. Gleichzeitig führen wir alle notwendigen Prüfungen und Messungen durch, um festzustellen, ob es sich lohnt, mit dem Bau eines neuen Balken zu beginnen oder nicht. Dieser Teil kann in Zukunft weggelassen werden, da er nur die Zeitmetrik zwischen einem Balken und dem vorherigen anzeigt. Dies ist jetzt sehr nützlich, weil es uns hilft, die Verzögerungswerte innerhalb der Erstellungsschleife fein abzustimmen.

Damit ist dieser Teil der Präsentation der 1-Minuten-Balken abgeschlossen. Jetzt kann das System sie in einer sehr vernünftigen Zeit präsentieren, zumindest für die gehandelten und simulierten Ticks, die getestet wurden. Als Nächstes werden wir uns mit einem anderen Problem im Zusammenhang mit der Tickwiederholung befassen. Was die Ticks selbst betrifft, so haben wir derzeit keine weiteren Probleme.


Betrachten wir eine RANDOM WALK-Chart

Bislang war die Arbeit interessant und hat sogar Spaß gemacht. Nun stehen wir jedoch vor einer Aufgabe, die für einige sehr schwierig sein mag, die aber unbedingt durchgeführt werden muss: die Simulation des Starts aller Ticks, die in irgendeiner Form auf den 1-Minuten-Balken vorhanden sein könnten. Ich empfehle Ihnen, die nachstehenden Erläuterungen aufmerksam zu verfolgen. Der Einfachheit halber werde ich hier nicht die endgültige Version des Simulationssystems zeigen. Die endgültige Version wird später gezeigt. Der Grund dafür ist, dass es ziemlich kompliziert ist, all dies in einem Zug zu zeigen.

Was wir wirklich wollen und schaffen werden, ist der so genannte RANDOM WALK. Diese Zufallsbewegung hat einige Regeln. Im Gegensatz zu dem, was normalerweise programmiert wird, können wir hier nicht zulassen, dass das System völlig zufällig ist. Wir müssen einige mathematische Regeln aufstellen, um zu versuchen, die Bewegung zu steuern. Verstehen Sie mich nicht falsch. Ein Random Walk ist eigentlich eine völlig zufällige und unvorhersehbare Bewegung, zumindest auf kurze Sicht. Aber weil wir keine völlig unvorhersehbare Bewegung erzeugen und weil wir wissen, wo sie beginnt und wo sie endet, ist das System nicht völlig zufällig. Dennoch werden wir innerhalb der Balken etwas Zufälligkeit hinzufügen.

Es gibt mehrere Ideen, die es einfacher machen, einen wirklich zufälligen Gang zu erzeugen. Je nach Einzelfall können einige Ansätze besser sein als andere. Ein weniger erfahrener Programmierer könnte denken, dass es ausreichen würde, einen Zufallszahlengenerator zu verwenden und eine Art Transformation durchzuführen, um die Werte auf einen bestimmten Bereich zu begrenzen. Dieser Ansatz ist zwar nicht völlig falsch, hat aber auch einige Nachteile. Wenn Sie die Daten, die sich aus einer solchen Bewegung ergeben, in einem Chart betrachten würden, ergäbe sich ein ähnliches Bild wie das folgende:

Sie werden vielleicht denken, dass diese Grafik (JA, das ist die Grafik, die ich Ihnen später zeigen werde) überhaupt nicht wie eine zufällige Bewegung aussieht. Es sieht eher wie ein völliges Durcheinander aus, aber in Wirklichkeit handelt es sich um eine zufällige Bewegung, die durch Sprünge zwischen Zeitpunkten erreicht wird. Für diesen Schritt verwenden wir die folgende Datenreihe, die wir über die Plattform MetaTrader 5 erhalten haben. Beachten Sie, dass jede Linie einen 1-Minuten-Balken darstellt.

Diese Daten werden in angehängten Dateien zur Verfügung gestellt, damit Sie Ihre eigene Analyse durchführen können. Jetzt wollen wir weiter verstehen, warum das obige Chart so unerwartet anders ist als erwartet. Um dies zu verstehen, muss man wissen, wie sie entstanden ist. Zunächst müssen wir zwei Dinge in der Servicedatei definieren. Wir werden sie vorübergehend verwenden und sie werden in Zukunft nicht mehr im Quellcode erscheinen.

#define def_TEST_SIMULATION
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

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

Diese Definition ermöglicht es uns, eine Simulationsprüfung zu erstellen, sodass wir den Graphen der erzeugten Bewegung analysieren können. Andererseits wird diese Datei die Daten enthalten, die der simulierten Bewegung innerhalb des 1-Minuten-Balkens entsprechen. Wir werden später sehen, wo und wann diese Datei erstellt wird. Sobald dies direkt in der Servicedatei und vor der Deklaration der Header-Dateien definiert ist, können wir diese Definition in den MQH-Dateien verwenden. Gehen wir nun zur Datei C_Replay.Mqh über, um zu verstehen, wie wir die Daten erhalten wollen.

Um die Dinge tatsächlich zu erfassen, um ein Gefühl dafür zu bekommen, wie der Simulator die Bewegung innerhalb eines 1-Minuten-Balkens erzeugt hat, werden wir die folgende Funktion verwenden.

                bool LoadBarsToTicksReplay(const string szFileNameCSV)
                        {
                                int file;
                                MqlRates rate[1];
                                MqlTick tick[];
                                
                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                Simulation(rate[0], tick);
#ifdef def_TEST_SIMULATION
        FileClose(file);
        file = FileOpen(def_FILE_OUT_SIMULATION, FILE_ANSI | FILE_WRITE);
        for (long c0 = 0; c0 < m_Ticks.nTicks; c0++)
                FileWriteString(file, StringFormat("%0.f\n", m_Ticks.Info[c0].last));
        FileClose(file);
        ArrayFree(tick);
        
        return false;
#endif
                                        }
                                        FileClose(file);
                                        ArrayFree(tick);

                                        return (!_StopFlag);
                                }
                                
                                return false;
                        }

Ein Detail, das für weniger erfahrene Programmierer interessant sein könnte, ist die Frage, ob der Simulator innerhalb eines 1-Minuten-Balkens immer die gleiche Art von Bewegungssimulation durchführt. In der Tat, NEIN. Wir werden versuchen, einen Weg zu finden, bei dem jeder Balken einzigartig ist und eine einzigartige Bewegung hat. Wenn Sie jedoch möchten, dass die Bewegung zwischen den Balken immer gleich oder zumindest ähnlich ist, müssen Sie das System einfach so einstellen, dass die Simulation immer bei einem bestimmten Wert beginnt. Dies kann durch Hinzufügen eines Aufrufs vor dem Simulatoraufruf und Setzen eines festen Wertes in diesem Aufruf geschehen, wie unten gezeigt:

// ... Code ...

                                if (OpenFileBars(file, szFileNameCSV))
                                {
                                        Print("Converting bars to ticks. Please wait...");
                                        ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                        ArrayResize(tick, def_MaxSizeArray);
                                        while ((!FileIsEnding(file)) && (!_StopFlag))
                                        {
                                                FileReadBars(file, rate);
                                                srand(5);
                                                Simulation(rate[0], tick);

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

Aber kehren wir zu unserem Quellcode zurück. Da wir eine Definition in der Servicedatei haben werden, werden wir, wenn die Ausführung einen bestimmten Punkt erreicht, eine spezielle Datei im Dienst erstellen, die alle Elemente im Tick-Array, das in diesem Fall ein Dummy-Array ist, enthält. Dies geschieht unabhängig davon, was innerhalb der Simulationsfunktion geschieht. Wir können EXCEL verwenden, um grafisch zu überprüfen, was vor sich geht. Beachten Sie, dass wir in diesem Fall eine Fehlermeldung vom Dienst erhalten. Diese Fehlermeldung sollte ignoriert werden, wenn eine Prüfdefinition vorhanden ist. Ein weiterer wichtiger Punkt ist, dass wir EXCEL verwenden werden, weil es einfacher ist, ein Chart zu erstellen. Wir könnten dafür sogar MetaTrader 5 verwenden, aber das Ergebnis wäre angesichts der Menge der generierten Informationen verwirrend. Es ist also einfacher, sie in EXCEL zu visualisieren. Sie können jedes andere Programm verwenden, um ein Chart zu erstellen. Wichtig ist, dass Sie das vom Simulator erzeugte Chart erstellen und visualisieren können. Wenn Sie nicht wissen, wie man das in EXCEL macht, können Sie sich das folgende Video ansehen, in dem ich es zeige.



Es ist sehr wichtig, dass Sie wissen, wie man ein Chart erstellt, da Sie eine Möglichkeit brauchen, um zu überprüfen, ob die Bewegung korrekt erstellt wird. Die bloße Beobachtung der Bewegung der entstehenden Balken reicht nicht aus, um festzustellen, ob sie wirklich den entsprechenden Grad an Zufälligkeit aufweisen. Ein weiterer, unter Programmierern sehr verbreiteter Punkt ist, dass der Versuch, die Berechnungen innerhalb des Simulationsverfahrens zu verkomplizieren, nicht garantiert, dass wir tatsächlich eine wirklich zufällige Bewegung wie einen RANDOM WALK haben. Sehen Sie sich das Video oben an. Sie ist kurz und kann Ihnen in den nächsten Phasen sehr helfen. Ich werde das Chart zeigen, und wir müssen verstehen, warum es genau so aussieht. So können Sie lokale Prüfungen durchführen.


Schlussfolgerung

Um die Dinge einfach zu halten und Sie, liebe Leserin, lieber Leser, nicht zu verwirren, was wir bei der Umsetzung des RANDOM WALK-Modells sehen werden, werde ich diesen Artikel an dieser Stelle beenden. Im nächsten Artikel werden wir ein RANDOM WALK-Modell implementieren, bei dem der generierte Graph anders aussehen wird. Wir werden auch die Probleme sehen, die durch diesen "random walk" verursacht werden, und die Idee dahinter. Aber da wir bereits etwas haben, das für viele neu und für andere ziemlich komplex ist, möchte ich die Dinge nicht weiter verkomplizieren, auch wenn der beigefügte Code Ihnen Dinge verrät, die ich erst im nächsten Artikel erklären werde.

Nur keine Eile. Sie sollten zuerst studieren und verstehen, was heute erklärt wurde. Denn ohne dieses Wissen und sein richtiges Verständnis werden wir nichts von dem erfahren können, was in naher Zukunft geschehen wird. 

Ein weiteres Detail: Um den Dienst zu erstellen und das Wiedergabe-/Simulationssystem ohne die Generierung von Daten für die Chartanalyse arbeiten zu lassen, deaktivieren Sie die Testanweisung, wie im folgenden Code gezeigt. Hier meine ich die Servicedatei.

//#define def_TEST_SIMULATION // <<-- Leave this line as it is to be able to use the replay/simulation service....
#ifdef def_TEST_SIMULATION
        #define def_FILE_OUT_SIMULATION "Info.csv"
#endif 
//+------------------------------------------------------------------+
#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.13"

Wenn Sie dies nicht tun, meldet der Dienst immer einen Fehler, wenn Sie versuchen, zusätzlich zum Chart weitere Daten anzuzeigen. Achten Sie also darauf.

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

Beigefügte Dateien |
Entwicklung eines Qualitätsfaktors für Expert Advisors Entwicklung eines Qualitätsfaktors für Expert Advisors
In diesem Artikel sehen wir uns an, wie Sie eine Qualitätsbewertung entwickeln, die Ihr Expert Advisor im Strategietester anzeigen kann. Wir werden uns zwei bekannte Berechnungsmethoden ansehen – Van Tharp und Sunny Harris.
Die diskrete Hartley-Transformation Die diskrete Hartley-Transformation
In diesem Artikel werden wir eine der Methoden der Spektralanalyse und Signalverarbeitung betrachten - die diskrete Hartley-Transformation. Es ermöglicht die Filterung von Signalen, die Analyse ihres Spektrums und vieles mehr. Die Möglichkeiten der DHT stehen denen der diskreten Fourier-Transformation in nichts nach. Im Gegensatz zur DFT werden bei der DHT jedoch nur reelle Zahlen verwendet, was die Umsetzung in der Praxis erleichtert, und die Ergebnisse der Anwendung sind anschaulicher.
Elastische Netzregression mit Koordinatenabstieg in MQL5 Elastische Netzregression mit Koordinatenabstieg in MQL5
In diesem Artikel untersuchen wir die praktische Umsetzung der elastischen Netzregression, um die Überanpassung zu minimieren und gleichzeitig automatisch nützliche Prädiktoren von solchen zu trennen, die wenig prognostische Kraft haben.
StringFormat(). Inspektion und vorgefertigte Beispiele StringFormat(). Inspektion und vorgefertigte Beispiele
In diesem Artikel wird die Inspektion der Funktion PrintFormat() fortgesetzt. Wir werden uns kurz mit der Formatierung von Zeichenketten mit StringFormat() und ihrer weiteren Verwendung im Programm beschäftigen. Wir werden auch Vorlagen für die Anzeige von Symboldaten im Terminaljournal schreiben. Der Artikel ist sowohl für Anfänger als auch für erfahrene Entwickler nützlich.