English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay Systems — Marktsimulation (Teil 17): Ticks und noch mehr Ticks (I)

Entwicklung eines Replay Systems — Marktsimulation (Teil 17): Ticks und noch mehr Ticks (I)

MetaTrader 5Beispiele | 22 Dezember 2023, 09:58
506 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel „Entwicklung eines Replay-Systems — Marktsimulation (Teil 16): Neues Klassensystem“, haben wir die notwendigen Änderungen an der Klasse C_Replay vorgenommen. Diese Änderungen sollen mehrere Aufgaben vereinfachen, die wir zu erledigen haben werden. So durchlief die einst zu große Klasse C_Replay einen Vereinfachungsprozess, bei dem ihre Komplexität auf andere Klassen verteilt wurde. Dies macht es viel einfacher und leichter, neue Funktionen und Verbesserungen in das Wiedergabe-/Simulationssystem zu implementieren. Diese Verbesserungen werden mit diesem Artikel beginnen und sich auf die nächsten sieben Artikel erstrecken.

Die erste Frage, mit der wir uns befassen werden, ist sehr schwierig so zu modellieren, dass sie jeder verstehen kann, wenn er sich den Code ansieht. In diesem Wissen möchte ich den Leser bitten, den Erklärungen, die wir in diesen Artikeln behandeln werden, die nötige Aufmerksamkeit zu schenken. Wenn Sie aufmerksam genug sind, können Sie den Erklärungen folgen, denn sie sind wirklich umfangreich und komplex. Ich sage dies, weil das heutige Material für einige unnötig erscheinen mag, während es für andere von größter Bedeutung sein wird. Das Material wird Schritt für Schritt vorgestellt, sodass Sie die Argumentation nachvollziehen können.

Das große Problem besteht darin, dass sich alle bisherigen Artikel ausschließlich auf die Konstruktion von Charts konzentrierten, und dass diese Charts so dargestellt werden mussten, dass sich das Wiedergabe/Simulations-Handelsinstrument sehr ähnlich verhält wie das, was auf dem realen Markt passiert. Ich weiß, dass es viele gibt, die mit anderen Instrumenten handeln, zum Beispiel mit einem Orderbuch. Obwohl ich persönlich die Verwendung eines solchen Instruments nicht für sinnvoll halte, sind andere Händler der Meinung, dass ein gewisser Zusammenhang zwischen dem, was im Orderbuch steht, und dem, was gehandelt wird, besteht. Es ist in Ordnung, wenn jeder Mensch seinen eigenen Standpunkt hat. Trotzdem gibt es ein Instrument, das viele Menschen bei ihrer Arbeit verwenden, nämlich ein Tickchart. Wenn Sie nicht wissen, worum es sich handelt, können Sie sich das Bild in Abbildung 01 ansehen.


Abbildung 01

Abbildung 01 - Tickchart

Dieses Chart erscheint an mehreren Stellen im MetaTrader 5. Um Ihnen eine Vorstellung von diesen Orten zu geben, werde ich ein paar Orte erwähnen, die in der Standardversion von MetaTrader 5 enthalten sind. Zum Beispiel das Fenster Marktübersicht, wie in Abbildung 01 dargestellt. Die Markttiefe (Abb. 02) und das Auftragssystem (Abb. 03).

Abgesehen von diesen Orten können Sie auch eine Art Indikator verwenden, um die gleichen Informationen zu sehen. Ein Beispiel finden Sie im Artikel „Entwicklung eines Trading Expert Advisors von Grund auf (Teil 13): Times And Trade (II)“. Für all diese Systeme sollte der von uns entwickelte Dienst in der Lage sein, Tickinformationen in geeigneter Weise zu melden oder zu übermitteln, aber das ist nicht genau die Information, die wir in all diesen Bildern sehen. Tatsächlich ist eine Veränderung der ASK- und BID-Preiswerte zu beobachten. Dies ist die tatsächliche Darstellung. 


Abbildung 02

Abbildung 02 – Tickchart in der Markttiefe


Abbildung 03

Abbildung 03 – Tickchart im Auftragssystem


Es ist wichtig, diese Tatsache zu verstehen. Ich möchte nicht, dass diese Informationen in unserem System fehlen. Der Grund dafür ist, eine Erfahrung zu bieten, die dem realen Markt so nahe wie möglich kommt. Darüber hinaus sollten die Informationen korrekt sein und auch dann vorhanden sein, wenn der Systemnutzer sie nicht nutzt. Ich möchte nicht, dass Sie denken, dass es unmöglich ist, so etwas zu entwickeln, auch wenn es nicht die einfachste Aufgabe ist. Um ehrlich zu sein, ist die Aufgabe viel schwieriger, als es scheint, und Sie werden die Gründe dafür bald verstehen. Alles wird klarer, wenn wir es erklären. Wir werden sehen, wie komplex diese Aufgabe ist und wie viele kleine Details sie hat, von denen einige, sagen wir, sehr merkwürdig sind.

Hier werden wir mit der Umsetzung dieses Systems beginnen, allerdings auf die einfachste Art und Weise. Zunächst wird sie im Fenster der Marktübersicht angezeigt (Abb. 01). Danach werden wir versuchen, sie an anderen Stellen erscheinen zu lassen. Es wird eine Herausforderung sein, sie im Fenster Marktübersicht anzuzeigen. Gleichzeitig wird es interessant sein, denn wenn wir die Simulation von Bewegungen mit einem Intervall von 1 Minute implementieren und verwenden, wird das Tick-Chart im Fenster der „Marktübersicht“ den vom Tester erstellten RANDOM WALK anzeigen. Das ist alles sehr interessant.

Aber das Wichtigste zuerst. Obwohl die Aufgabe einfach zu sein scheint, habe ich keine Links gefunden, die mir wirklich bei der Umsetzung helfen, die Aufgabe erleichtern oder mich zum nächsten Schritt führen könnten. Tatsächlich war die einzige Referenz, die ich nach der Suche an verschiedenen Stellen gefunden habe, die MQL5-Dokumentation, und selbst die klärt einige der Details nicht. Was ich in dieser Serie erläutern werde, ist das, was ich bei der Einführung des Systems gelernt habe. Ich entschuldige mich bei denjenigen, die das System vielleicht anders verstehen oder mehr Erfahrung in dieser Angelegenheit haben. Trotz all meiner Versuche war die einzige wirkliche Möglichkeit, das System zum Laufen zu bringen, die, die hier gezeigt wird. Daher bin ich offen für Ratschläge und Vorschläge zu anderen möglichen Wegen, wenn sie wirklich funktionieren.

Beginnen wir mit der Umsetzung der verrücktesten Sache von allen, angesichts des Grades der Komplexität. In dem zu implementierenden System werden wir in der ersten Zeit keine simulierten Daten verwenden. Daher enthält der Anhang zu diesem Artikel REAL-Data für 2 Tage in 4 verschiedenen Anlagen, sodass wir zumindest eine Grundlage für Experimente haben. Sie brauchen mir nicht zu vertrauen, ganz im Gegenteil. Ich möchte, dass Sie selbst reale Marktdaten sammeln und diese im System testen. Auf diese Weise können wir unsere eigenen Schlussfolgerungen darüber ziehen, was tatsächlich passiert, bevor wir das Simulationssystem implementieren. Denn in Wirklichkeit ist alles viel verrückter, als es auf den ersten Blick erscheinen mag.


Umsetzung der ersten Version

In dieser ersten Version werden wir einige Ressourcen deaktivieren, da ich nicht möchte, dass Sie glauben, dass der Code vollständig korrekt ist. Die Zeitschaltuhr ist jedoch defekt. Dies wird deutlich, wenn man die beigefügten echten Daten testet. Aber im Moment können wir sie ignorieren, da sie dem Prozess selbst nicht schaden. Es ist nur so, dass die Zeit, die für die Erstellung von 1-Minuten-Balken benötigt wird, nicht ganz die gleiche ist wie auf dem realen Markt.

Beginnen wir also mit einer kleinen Änderung in der Servicedatei:

#property service
#property icon "\\Images\\Market Replay\\Icon.ico"
#property copyright "Daniel Jose"
#property version   "1.17"
#property description "Replay-simulation system for MT5."
#property description "It is independent from the Market Replay."
#property description "For details see the article:"
#property link "https://www.mql5.com/en/articles/11106"
//+------------------------------------------------------------------+
#define def_Dependence  "\\Indicators\\Market Replay.ex5"
#resource def_Dependence
//+------------------------------------------------------------------+
#include <Market Replay\C_Replay.mqh>
//+------------------------------------------------------------------+
input string            user00 = "Mini Indice.txt";     //"Replay" config file
input ENUM_TIMEFRAMES   user01 = PERIOD_M5;             //Initial timeframe for the chart
//input bool            user02 = false;                 //visual bar construction ( Temporarily blocked )
input bool              user03 = true;                  //Visualize creation metrics
//+------------------------------------------------------------------+
void OnStart()
{
        C_Replay        *pReplay;

        pReplay = new C_Replay(user00);
        if ((*pReplay).ViewReplay(user01))
        {
                Print("Permission received. The replay service can now be used...");
                while ((*pReplay).LoopEventOnTime(false, user03));
        }
        delete pReplay;
}
//+------------------------------------------------------------------+


Diese Zeile war blockiert, weil einige Details geändert werden mussten, damit die Visualisierung der Balkenkonstruktion korrekt funktioniert. Aus diesem Grund ist beim schnellen Vorlauf der Anzeigevorgang nicht sichtbar. Um dies zu kontrollieren, übergeben wir das entsprechende Argument als true oder false.

Das ist der erste Schritt, den wir tun müssen. Jetzt müssen wir noch ein paar kleine Änderungen vornehmen. An dieser Stelle kann es für diejenigen, die diesen Artikel lesen, bevor sie die anderen gelesen haben, ein wenig verwirrend werden. Wenn dies der Fall ist, dann empfehle ich Ihnen, mit dem Lesen aufzuhören und mit dem ersten Artikel dieser Reihe zu beginnen: „Entwicklung eines Replay Systems — Marktsimulation (Teil 01): Erste Experimente (I)“, denn zu verstehen, was getan wurde, hilft zu verstehen, was jetzt und in Zukunft geschehen wird.

Mit diesen Ratschlägen im Hinterkopf können wir nun weitermachen. Als erstes werden wir nun die Funktion zum Lesen einer Datei mit echten Ticks ändern. Das Originalprogramm ist unten zu sehen.

inline bool ReadAllsTicks(void)
                        {
#define def_LIMIT (INT_MAX - 2)
                                string   szInfo;
                                MqlTick  tick;
                                MqlRates rate;
                                int      i0;
                                
                                Print("Loading ticks for replay. Please wait...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                i0 = m_Ticks.nTicks;
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        tick.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        tick.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        tick.bid = StringToDouble(FileReadString(m_File));
                                        tick.ask = StringToDouble(FileReadString(m_File));
                                        tick.last = StringToDouble(FileReadString(m_File));
                                        tick.volume_real = StringToDouble(FileReadString(m_File));
                                        tick.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if ((m_Ticks.Info[i0].last == tick.last) && (m_Ticks.Info[i0].time == tick.time) && (m_Ticks.Info[i0].time_msc == tick.time_msc))
                                                m_Ticks.Info[i0].volume_real += tick.volume_real;
                                        else
                                        {
                                                m_Ticks.Info[m_Ticks.nTicks] = tick;
                                                if (tick.volume_real > 0.0)
                                                {
                                                        ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                        m_Ticks.nRate += (BuiderBar1Min(rate, tick) ? 1 : 0);
                                                        m_Ticks.Rate[m_Ticks.nRate] = rate;
                                                        m_Ticks.nTicks++;
                                                }
                                                i0 = (m_Ticks.nTicks > 0 ? m_Ticks.nTicks - 1 : i0);
                                        }
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Too much data in the tick file.\nCannot continue...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_LIMIT
                        }


Sie werden feststellen, dass wir einige Teile des Codes entfernt haben. Der endgültige Code ist unten dargestellt – dies ist eine neue Funktion zum Lesen von echten Ticks.

inline bool ReadAllsTicks(const bool ToReplay)
                        {
#define def_LIMIT (INT_MAX - 2)
#define def_Ticks m_Ticks.Info[m_Ticks.nTicks]

                                string   szInfo;
                                MqlRates rate;
                                
                                Print("Loading replay ticks. Please wait...");
                                ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray);
                                while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < def_LIMIT) && (!_StopFlag))
                                {
                                        ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray);
                                        szInfo = FileReadString(m_File) + " " + FileReadString(m_File);
                                        def_Ticks.time = StringToTime(StringSubstr(szInfo, 0, 19));
                                        def_Ticks.time_msc = (int)StringToInteger(StringSubstr(szInfo, 20, 3));
                                        def_Ticks.bid = StringToDouble(FileReadString(m_File));
                                        def_Ticks.ask = StringToDouble(FileReadString(m_File));
                                        def_Ticks.last = StringToDouble(FileReadString(m_File));
                                        def_Ticks.volume_real = StringToDouble(FileReadString(m_File));
                                        def_Ticks.flags = (uchar)StringToInteger(FileReadString(m_File));
                                        if (def_Ticks.volume_real > 0.0)
                                        {
                                                ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
                                                m_Ticks.nRate += (BuiderBar1Min(rate, def_Ticks) ? 1 : 0);
                                                m_Ticks.Rate[m_Ticks.nRate] = rate;
                                        }
                                        m_Ticks.nTicks++;
                                }
                                FileClose(m_File);
                                if (m_Ticks.nTicks == def_LIMIT)
                                {
                                        Print("Too much data in the tick file.\nCannot continue...");
                                        return false;
                                }
                                return (!_StopFlag);
#undef def_Ticks
#undef def_LIMIT
                        }


Beachten Sie, dass die in den Positionen BID und ASK enthaltenen Werte nicht mehr ignoriert werden. Außerdem werden keine Werte mehr akkumuliert, wenn die Position dies zulässt, d.h. die gesamten Daten werden gelesen und im Speicher abgelegt, da die Änderungen keine neuen Prozeduren innerhalb der Funktion hervorgebracht haben. Vielmehr haben sie es vereinfacht. Ich denke, dass Sie (vorausgesetzt, Sie haben diese Artikelserie gelesen) keine Schwierigkeiten haben werden, zu verstehen, was wirklich vor sich geht, aber die Tatsache, dass diese Vereinfachungen vorgenommen werden, hat Konsequenzen an anderer Stelle im Code. Einige dieser Elemente werden stark leiden, weshalb wir einige Komponenten deaktivieren müssen, bis der gesamte Code wieder zuverlässig funktioniert.

Wir konnten Änderungen vornehmen, den Code stabilisieren und die endgültige Version sofort zeigen. Aber ich denke, dass die schrittweise Darstellung der Änderungen für diejenigen, die lernen und wirklich verstehen wollen, wie die Dinge im Detail funktionieren, von großem Nutzen sein wird. Darüber hinaus gibt es einen weiteren Grund, der diese Veränderungen erklärt. Aber vor allem, wenn Sie ruhig und systematisch vorgehen, wird Ihnen das Studium schwieriger Themen leichter fallen. Noch schlimmer ist, dass viele dieser Feinheiten von denjenigen, die behaupten, professionelle Händler zu sein, und damit meine ich diejenigen, die behaupten, ihren Lebensunterhalt auf den Finanzmärkten zu verdienen, nur unzureichend erklärt werden. Aber solche Fragen würden den Rahmen dieser Artikelserie sprengen. Lassen Sie uns nicht von unserem Hauptziel abweichen: Lassen Sie uns die Dinge weiterhin nach und nach umsetzen, dann wird das Ganze später mehr Sinn machen. Vor allem, wenn wir über einen anderen Markt sprechen, der ebenfalls sehr interessant ist. Aber ich will die Überraschung nicht verderben. Wenn Sie die Artikel weiter lesen, werden Sie verstehen, wovon ich spreche.

Nachdem wir diese ersten Änderungen vorgenommen haben, müssen wir noch eine etwas merkwürdige, aber dennoch notwendige Änderung vornehmen. Da wir nun die Werte haben, bei denen das Volumen nicht vorhanden ist (die BID- und ASK-Werte), müssen wir das System an dem Punkt starten, an dem wir ein bestimmtes Volumen haben.

class C_ConfigService : protected C_FileTicks
{
        protected:
//+------------------------------------------------------------------+
                datetime m_dtPrevLoading;
                int      m_ReplayCount;
//+------------------------------------------------------------------+
inline void FirstBarNULL(void)
                        {
                                MqlRates rate[1];
                                
                                for(int c0 = 0; m_Ticks.Info[c0].volume_real == 0; c0++)
                                        rate[0].close = m_Ticks.Info[c0].last;
                                rate[0].open = rate[0].high = rate[0].low = rate[0].close;
                                rate[0].tick_volume = 0;
                                rate[0].real_volume = 0;
                                rate[0].time = m_Ticks.Info[0].time - 60;
                                CustomRatesUpdate(def_SymbolReplay, rate);
                                m_ReplayCount = 0;
                        }
//+------------------------------------------------------------------+

//... The rest of the class...

}

Diese Funktion war ursprünglich eine private Funktion der Klasse und hatte keine Punkte im Rampenlicht. Zusätzlich zu dieser Funktion, die jetzt eine geschützte Funktion ist, haben wir auch eine Variable. Dies ist die Variable, die für den Wiederholungszähler verwendet wird. Diese Variable ist ausschließlich dafür bestimmt, dass ihr Wert nur durch diese spezielle Funktion geändert wird. Diese Schleife bewirkt, dass der erste Balken ganz links im Chart den entsprechenden Wert hat. Denken Sie daran: Wir haben jetzt die BID- und ASK-Werte zusammen mit den Preiswerten. Die Werte BID und ASK haben für uns vorerst keine Bedeutung.

Bis zu diesem Punkt war alles ganz einfach und klar. Wir werden nun zu der Klasse übergehen, die für die Wiedergabe verantwortlich ist. Dieser Teil enthält recht seltsame Dinge, die auf den ersten Blick nicht viel Sinn ergeben. Darauf gehen wir im nächsten Abschnitt ein.


Ändern der Klasse C_Replay

Die Veränderungen fangen hier auf einfache Weise an und werden dann ziemlich seltsam. Beginnen wir mit der einfachsten Änderung:

                void AdjustPositionToReplay(const bool bViewBuider)
                        {
                                u_Interprocess Info;
                                MqlRates       Rate[def_BarsDiary];
                                int            iPos,   nCount;
                                
                                Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay);
                                if (m_ReplayCount == 0)
                                        for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++);
                                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)) FirstBarNULL(); 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);
                        }


Diese Funktion ist noch nicht endgültig festgelegt worden. Aus diesem Grund mussten wir die Anzeige des Systems, das die 1-Minuten-Balken erstellt, blockieren. Auch ohne vollständig fertig zu sein, mussten wir zusätzlichen Code hinzufügen. Dieser Code entspricht in etwa dem, was passiert, wenn wir einen Balken ganz links im Diagramm platzieren. Höchstwahrscheinlich wird einer der Codes in zukünftigen Versionen verschwinden. Aber dieser Code ist noch viel subtiler. Wenn wir die Wiedergabe/Simulation starten, wird der Asset-Sprung verhindert, bevor der erste Balken tatsächlich gezeichnet wird. Wenn wir diese Codezeile deaktivieren, werden wir sehen, dass es am Anfang des Diagramms einen Sprung gibt. Dieser Sprung ist auf eine andere Tatsache zurückzuführen, die wir später sehen werden.

Um zu erklären, wie das gemacht wurde und ob es möglich ist, dem Fenster der Marktübersicht Ticks hinzuzufügen, müssen wir uns die ursprüngliche Funktion zur Erstellung von Balken ansehen. Sie ist unten dargestellt:

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
                        }


Diese ursprüngliche Funktion ist nur für die Erstellung der im Diagramm angezeigten Balken zuständig. Schauen Sie sich den obigen Code an und vergleichen Sie ihn mit dem folgenden:

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

                                bool bNew;

                                if (m_MountBar.memDT != macroRemoveSec(m_Ticks.Info[m_ReplayCount].time))
                                {                               
                                        if (bViewMetrics) Metrics();
                                        m_MountBar.memDT = macroRemoveSec(m_Ticks.Info[m_ReplayCount].time);
                                        def_Rate.real_volume = 0;
                                        def_Rate.tick_volume = 0;
                                }
                                bNew = (def_Rate.tick_volume == 0);
                                def_Rate.close = (m_Ticks.Info[m_ReplayCount].volume_real > 0.0 ? m_Ticks.Info[m_ReplayCount].last : def_Rate.close);
                                def_Rate.open = (bNew ? def_Rate.close : def_Rate.open);
                                def_Rate.high = (bNew || (def_Rate.close > def_Rate.high) ? def_Rate.close : def_Rate.high);
                                def_Rate.low = (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;
                                CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate);
                                ViewTick();
                                m_ReplayCount++;
                                
#undef def_Rate
                        }


Sie scheinen gleich zu sein, aber sie sind es nicht: Es gibt Unterschiede. Und es ist nicht so, dass dieser zweite Code zwei neue Aufrufe enthält. Nun, der erste Aufruf wurde nur deshalb hinzugefügt, weil ich beschlossen habe, den metrischen Code aus der Funktion zu entfernen. Der metrische Code ist unten zu sehen. Das ist genau das, was in der ursprünglichen Funktion stand.

inline void Metrics(void)
                        {
                                int i;
                                static ulong _mdt = 0;
                                
                                _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();
                                
                        }


Der größte Unterschied besteht darin, wie das System den Schlusskurs des Balkens ermittelt. Wenn es keinen Einfluss der BID- und ASK-Werte gab, war es recht einfach zu wissen, welcher Wert als Schlusskurs zu verwenden ist. Da aber BID und ASK in die Datenkette eingreifen, brauchen wir eine andere Möglichkeit, dies zu tun. Wenn wir uns ansehen, ob eine Position ein Handelsvolumen aufweist, können wir feststellen, ob es sich um einen Wert handelt, der als Schlusskurs verwendet werden kann oder nicht.

Dies ist der entscheidende Punkt dieser neuen Funktion. Wir haben zwei neue Anrufe. Den ersten haben wir bereits behandelt. Aber im zweiten Fall werden die Dinge wirklich ziemlich seltsam.

Der Code für den zweiten Aufruf ist unten dargestellt:

inline void ViewTick(void)
                        {
                                MqlTick tick[1];

                                tick[0] = m_Ticks.Info[m_ReplayCount];
                                tick[0].time_msc = (m_Ticks.Info[m_ReplayCount].time * 1000) + m_Ticks.Info[m_ReplayCount].time_msc;
                                CustomTicksAdd(def_SymbolReplay, tick);
                        }


Dieser Code mag zwar völlig seltsam aussehen, aber er funktioniert trotzdem. Der Grund dafür ist in der Dokumentation der Funktion CustomTicksAdd zu finden. Ich werde genau das verwenden, was in der Dokumentation steht, bevor ich erkläre, warum die obige Funktion funktioniert und warum sie so sein sollte.

Im Folgenden finden Sie den Inhalt der Dokumentation:

Weitere Anmerkung

Die Funktion CustomTicksAdd funktioniert nur für nutzerdefinierte Symbole, die im Fenster der Marktübersicht geöffnet sind. Wenn das Symbol in Market Watch nicht ausgewählt ist, sollten Sie Ticks mit CustomTicksReplace hinzufügen.

Die Funktion CustomTicksAdd ermöglicht es, Kurse so einzuspeisen, als ob diese Kurse vom Server eines Brokers empfangen würden. Die Daten werden an das Fenster der Marktübersicht gesendet, anstatt direkt in die Tick-Datenbank geschrieben zu werden. Anschließend speichert das Terminal die Ticks der Marktübersicht in der Datenbank. Wenn eine große Datenmenge in einem Aufruf übergeben wird, ändert sich das Verhalten der Funktion, um Ressourcen zu sparen. Wenn mehr als 256 Ticks übertragen werden, werden die Daten in zwei Teile aufgeteilt. Der erste (größere) Teil wird direkt in der Tickdatenbank gespeichert (ähnlich wie bei CustomTicksReplace). Der zweite Teil, bestehend aus den letzten 128 Ticks, wird an den die Marktübersicht gesendet, von wo aus das Terminal die Ticks in einer Datenbank speichert.

Die Struktur MqlTick hat zwei Felder mit einem Zeitwert: time (die Tickzeit in Sekunden) und time_msc (die Tickzeit in Millisekunden), die ab dem 1. Januar 1970 gezählt werden. Diese Felder in den hinzugefügten Ticks werden in der folgenden Reihenfolge bearbeitet:

  1. Wenn ticks[k].time_msc!=0 ist, wird damit das Feld ticks[k].time gefüllt, d. h. ticks[k].time=ticks[k].time_msc/1000 (ganzzahlige Division) wird für den Tick gesetzt
  2. Wenn ticks[k].time_msc==0 und ticks[k].time!=0, wird die Zeit in Millisekunden durch Multiplikation mit 1000 ermittelt, d. h. ticks[k].time_msc=ticks[k].time*1000
  3. Wenn ticks[k].time_msc==0 und ticks[k].time==0, wird die aktuelle Zeit des Handelsservers bis zu einer Millisekunde ab dem Zeitpunkt des CustomTicksApply-Aufrufs in diese Felder geschrieben.

Wenn der Wert von ticks[k].bid, ticks[k].ask, ticks[k].last oder ticks[k].volume größer als Null ist, wird eine Kombination von entsprechenden Flags in das Feld ticks[k].flags geschrieben:

  • TICK_FLAG_BID — der Tick hat den Geldkurs verändert.
  • TICK_FLAG_ASK — der Tick hat den Briefkurs verändert.
  • TICK_FLAG_LAST — der Tick hat den letzten Handelspreis verändert.
  • TICK_FLAG_VOLUME — der Tick hat das Volumen verändert.

Wenn der Wert eines Feldes kleiner oder gleich Null ist, wird das entsprechende Kennzeichen nicht in das Feld ticks[k].flags geschrieben. 

Die Flaggen TICK_FLAG_BUY und TICK_FLAG_SELL werden nicht zur Historie eines nutzerdefinierten Symbols hinzugefügt.

Das Wichtigste an dieser Notiz ist, dass sie für viele Menschen vielleicht nicht viel Sinn macht, aber genau das ist es, was ich nutze, damit die Dinge funktionieren. Hier wird angegeben, unter welchen Bedingungen die Zeit in Millisekunden von Null abweicht, die Zeit in Millisekunden Null ist und die Tickzeit von Null abweicht und wann die Zeit in Millisekunden und die Tickzeit Null sind. Das große Problem ist, dass bei der Verwendung echter Ticks aus einer Datei diese Bedingungen für die große Mehrheit nicht so klar sind, und das wird für uns zu einem Problem. Wenn jemand versucht, echte Ticks aus einer Datei zu verwenden, um diese Daten in die Tick-Informationen einzufügen, wird er nicht das gewünschte Ergebnis erhalten.

Aus diesem Grund versuchen viele Menschen, diese Modellierung durchzuführen, scheitern aber, weil sie die Dokumentation nicht verstehen. Aber unter Ausnutzung genau dieser Tatsache (die in der Dokumentation angedeutet wird), habe ich den obigen Code erstellt. In diesem Code erzwinge ich, dass die erste der Bedingungen erstellt wird. Dies ist der Fall, wenn der Zeitwert in Millisekunden von Null verschieden ist. Beachten Sie jedoch, dass der Wert, der die Zeit in Millisekunden angibt, auch den Zeitwert enthalten muss, da der MetaTrader 5 die Berechnung durchführt, um den Zeitwert zu generieren. Daher müssen wir die Parameter entsprechend dem im Feld Millisekunden angegebenen Wert anpassen.

Auf diese Weise kann die Funktion CustomTicksAdd Daten in den Market Watch einfügen. Aber nicht nur das: Wenn Sie diese Daten in das System eingeben, erscheinen auch die BID-Kurslinien, der ASK-Kurs und die letzte Kurslinie auf dem erstellten Chart. Mit anderen Worten: Als Bonus für die Möglichkeit, Ticks in den Market Watch einzufügen, erhielten wir auch Kurslinien im Chart. Wir hatten sie nicht, weil diese Art von Funktionalität nicht vorhanden war. Aber feiern Sie noch nicht, denn das System ist noch nicht fertig. Es gibt noch einige Dinge, die überprüft, repariert und montiert werden müssen. Deshalb verwenden wir Daten von REAL TICKS und stellen sie zur Verfügung, um diese neue Phase des Wiedergabe/Simulationssystems zu testen.


Abschließende Überlegungen

Der Artikel kommt zum Ende, weil die erforderlichen Schritte zu einer gewissen Verwirrung in dem bereits vorgestellten Material führen können. Im nächsten Artikel werden wir uns also ansehen, wie man einige Dinge beheben kann, die im derzeitigen System nicht richtig funktionieren. Sie können das System jedoch auch ohne Vorspulen oder Zurückspulen verwenden. In diesem Fall stimmen die Tickdaten in der Marktbeobachtung oder die Preislinieninformationen möglicherweise nicht mit der aktuellen Situation auf dem Wiedergabe-/Simulationsdiagramm überein.

Wie Sie sehen, bevorzuge ich nur Kontrakte vom Typ Mini-Index. Deshalb möchte ich, dass Sie das System an anderen Vermögenswerten testen. Dadurch wird klar, wie sich das Wiedergabe-/Simulationssystem in Bezug auf das, was wir in es eingeben, verhalten wird. Ich möchte nur eines klarstellen: Es gibt immer noch einige Mängel im schnellen Vorlaufsystem. Ich schlage daher vor, dass Sie diese Funktion zumindest vorerst nicht nutzen.

Bei diesen Tests, die ich Ihnen anbiete, sollten Sie sowohl auf die Liquidität als auch auf die Volatilität des von Ihnen gewählten Vermögenswerts achten. Prüfen Sie die Leistung verschiedener Vermögenswerte. Beachten Sie, dass bei Vermögenswerten mit weniger Abschlüssen im 1-Minuten-Intervall das Wiederholungs-/Simulationssystem Schwierigkeiten zu haben scheint. In gewisser Weise ist es gut, dies jetzt zu sehen, denn dieser Teil muss repariert werden. Die Gestaltung der Balken scheint jedoch korrekt zu sein. Wir werden dies bald beheben. Ich möchte, dass Sie, liebe Leserinnen und Leser, verstehen, warum der Wiedergabe/Simulator-Dienst seltsam erscheint, bevor wir diesen Fehler beheben. Dieses Verständnis ist wichtig, wenn Sie wirklich in die Programmierung einsteigen wollen. Geben Sie sich nicht damit zufrieden, nur einfache und leichte Programme zu erstellen. Echte Programmierer sind diejenigen, die Probleme lösen, wenn sie auftreten, und nicht diejenigen, die beim ersten Anzeichen von Schwierigkeiten aufgeben.

Bei der Beobachtung der Zeit im Market Watch-Fenster und des vom Metriksystem gelieferten Werts ist der Wiedergabe/Simulator-Dienst jedoch nicht in der Lage, das System korrekt zu synchronisieren, wenn die Zeit mehr als 1 Sekunde beträgt. Wir müssen das in Ordnung bringen, und wir werden es bald tun. In der Zwischenzeit sollten Sie diesen Code studieren, da er für das Studium und die Arbeit mit Ticks im Market Watch-Fenster sehr nützlich sein wird. Wir werden im nächsten Artikel fortfahren. Alles wird noch interessanter werden.


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

Beigefügte Dateien |
Neuronale Netze leicht gemacht (Teil 53): Aufteilung der Belohnung Neuronale Netze leicht gemacht (Teil 53): Aufteilung der Belohnung
Wir haben bereits mehrfach darüber gesprochen, wie wichtig die richtige Wahl der Belohnungsfunktion ist, mit der wir das gewünschte Verhalten des Agenten anregen, indem wir Belohnungen oder Bestrafungen für einzelne Aktionen hinzufügen. Aber die Frage nach der Entschlüsselung unserer Signale durch den Agenten bleibt offen. In diesem Artikel geht es um die Aufteilung der Belohnung im Sinne der Übertragung einzelner Signale an den trainierten Agenten.
Entwicklung eines Replay Systems — Marktsimulation (Teil 16): Neues System der Klassen Entwicklung eines Replay Systems — Marktsimulation (Teil 16): Neues System der Klassen
Wir müssen unsere Arbeit besser organisieren. Der Code wächst, und wenn dies nicht jetzt geschieht, wird es unmöglich werden. Lasst uns teilen und erobern. MQL5 erlaubt die Verwendung von Klassen, die bei der Umsetzung dieser Aufgabe helfen, aber dafür müssen wir einige Kenntnisse über Klassen haben. Das, was Anfänger am meisten verwirrt, ist wahrscheinlich die Vererbung. In diesem Artikel werden wir uns ansehen, wie man diese Mechanismen auf praktische und einfache Weise nutzen kann.
Entwicklung eines Replay Systems — Marktsimulation (Teil 18): Ticks und noch mehr Ticks (II). Entwicklung eines Replay Systems — Marktsimulation (Teil 18): Ticks und noch mehr Ticks (II).
Offensichtlich sind die aktuellen Metriken sehr weit von der idealen Zeit für die Erstellung eines 1-Minuten-Balkens entfernt. Das ist das erste, was wir in Angriff nehmen werden. Die Behebung des Synchronisationsproblems ist nicht schwierig. Das mag schwierig erscheinen, ist aber eigentlich ganz einfach. Wir haben die erforderliche Korrektur im vorigen Artikel nicht vorgenommen, da er darauf abzielte, zu erklären, wie man die Tick-Daten, die zur Erstellung der 1-Minuten-Balken im Chart verwendet wurden, in das Fenster der Marktübersicht überträgt.
Neuronale Netze leicht gemacht (Teil 51): Behavior-Guided Actor-Critic (BAC) Neuronale Netze leicht gemacht (Teil 51): Behavior-Guided Actor-Critic (BAC)
Die letzten beiden Artikel befassten sich mit dem Soft Actor-Critic-Algorithmus, der eine Entropie-Regularisierung in die Belohnungsfunktion integriert. Dieser Ansatz schafft ein Gleichgewicht zwischen Umwelterkundung und Modellnutzung, ist aber nur auf stochastische Modelle anwendbar. In diesem Artikel wird ein alternativer Ansatz vorgeschlagen, der sowohl auf stochastische als auch auf deterministische Modelle anwendbar ist.