English Русский 中文 Español 日本語 Português
preview
Entwicklung eines Replay-Systems (Teil 62): Abspielen des Dienstes (III)

Entwicklung eines Replay-Systems (Teil 62): Abspielen des Dienstes (III)

MetaTrader 5Beispiele | 2 Mai 2025, 08:57
44 0
Daniel Jose
Daniel Jose

Einführung

Im vorherigen Artikel: „Entwicklung eines Replay Systems (Teil 61): Abspielen des Dienstes (II)“ habe ich ein Problem erläutert, mit dem wir derzeit bei der Verwendung des Simulationsmodus in unserem System konfrontiert sind. Dieses Problem ist nicht unbedingt auf einen katastrophalen Fehler in der Anwendung zurückzuführen, die wir entwickeln. Vielmehr ist dies auf die allgemeine Reaktionsgeschwindigkeit des Systems zurückzuführen. Die Antwortzeit reichte für die Anwendung nicht aus, um alle eingehenden Daten ordnungsgemäß zu verarbeiten. Infolgedessen müssen wir bestimmte Anpassungen vornehmen. Auch wenn unser Service nicht perfekt mit einem Idealszenario übereinstimmt, sind wir uns bewusst, dass ein solches Idealszenario in der Praxis selten existiert.

Die beste Lösung, die ich mir vorstellen konnte, bestand darin, die in der Simulation geltenden Höchstgrenzen anzupassen. In diesem Artikel werde ich jedoch die Auswirkungen dieser Änderungen sorgfältig untersuchen und erläutern, warum ich mich für diesen besonderen Ansatz entschieden habe. Darüber hinaus gibt es einen weiteren Faktor, der direkt mit realen oder extern simulierten Daten außerhalb der zu entwickelnden Anwendung zusammenhängt. So ungewöhnlich es auch erscheinen mag, in manchen Fällen, insbesondere bei Futures-Kontrakten, können wir eine außergewöhnlich hohe Anzahl von Ticks oder Handelsgeschäfte innerhalb eines einzigen einminütigen Balkens feststellen. In diesem Fall kommt es selbst bei einer Verbindung mit dem Handelsserver zu Problemen mit der Geschwindigkeit, mit der die MetaTrader 5-Plattform Kursbewegungen verarbeitet und anzeigt. Wenn dieses Problem noch nie aufgetreten ist, könnten Sie vermuten, dass es an der Hardware des MetaTrader 5 oder an einer Fehlfunktion des Betriebssystems liegt. Ich bedauere jedoch, Ihnen mitteilen zu müssen, dass solche Annahmen völlig unbegründet sind - falsche Vorstellungen, die von denen verbreitet werden, denen es an einem angemessenen Verständnis der Informatik mangelt.

Da wir schon bei der Verbindung mit einem echten Handelsserver mit diesen Problemen konfrontiert sind und die Plattform Schwierigkeiten hat, den enormen Zustrom an eingehenden Daten zu verarbeiten, wird die Situation bei der Wiedergabe solcher Daten noch problematischer. Das wäre eine völlige Katastrophe, da sich die Zeitgenauigkeit erheblich verschlechtern würde. Daher werden wir auch eine Grenze für reale oder extern simulierte Daten festlegen, um zu verhindern, dass die Grenzen der Datenverarbeitung der Plattform offensichtlich werden oder weitere Probleme verursachen. Lassen Sie uns nun untersuchen, wie der neue Code strukturiert sein wird.


Ein neues Konzept. Ein neues System

Vielleicht ist der Titel dieses Abschnitts nicht ganz selbsterklärend - er vermittelt nicht ganz, was wir jetzt umsetzen werden. Wir beginnen jedoch mit der Analyse der Änderungen, die an der Klasse vorgenommen wurden, die für die Generierung und Modellierung von Simulationen innerhalb unseres Systems verantwortlich ist. Bevor wir den Code der Simulationsklasse betrachten, müssen wir zunächst die Definitionsdatei untersuchen, da eine neue Zeile hinzugefügt wurde, auf die an verschiedenen Stellen im Code verwiesen wird.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_VERSION_DEBUG
05. //+------------------------------------------------------------------+
06. #ifdef def_VERSION_DEBUG
07.    #define macro_DEBUG_MODE(A) \
08.                Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A));
09. #else
10.    #define macro_DEBUG_MODE(A)
11. #endif
12. //+------------------------------------------------------------------+
13. #define def_SymbolReplay      "RePlay"
14. #define def_MaxPosSlider       400
15. #define def_MaxTicksVolume     2000
16. //+------------------------------------------------------------------+
17. union uCast_Double
18. {
19.    double    dValue;
20.    long      _long;                                 // 1 Information
21.    datetime _datetime;                              // 1 Information
22.    uint     _32b[sizeof(double) / sizeof(uint)];    // 2 Informations
23.    ushort   _16b[sizeof(double) / sizeof(ushort)];  // 4 Informations
24.    uchar    _8b [sizeof(double) / sizeof(uchar)];   // 8 Informations
25. };
26. //+------------------------------------------------------------------+
27. enum EnumEvents    {
28.          evHideMouse,               //Hide mouse price line
29.          evShowMouse,               //Show mouse price line
30.          evHideBarTime,             //Hide bar time
31.          evShowBarTime,             //Show bar time
32.          evHideDailyVar,            //Hide daily variation
33.          evShowDailyVar,            //Show daily variation
34.          evHidePriceVar,            //Hide instantaneous variation
35.          evShowPriceVar,            //Show instantaneous variation
36.          evSetServerTime,           //Replay/simulation system timer
37.          evCtrlReplayInit,          //Initialize replay control
38.                   };
39. //+------------------------------------------------------------------+

Quellcode der Datei Defines.mqh

Es ist die Zeile 15, die dem Code hinzugefügt wurde. Ich werde jetzt nicht erklären, woher dieser Wert kommt, wir werden darüber sprechen, wenn wir ihn im Simulationscode verwenden. Dies wird der erste Ort sein, an dem es zum Einsatz kommt. Nachdem wir diese Änderung vorgenommen haben, können wir uns nun dem Quellcode der Klasse zuwenden, die für die Simulation der Ticks zuständig ist. Der Code mit den vorgenommenen Änderungen ist unten dargestellt:

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\..\Defines.mqh"
005. //+------------------------------------------------------------------+
006. class C_Simulation
007. {
008.    private   :
009. //+------------------------------------------------------------------+
010.       int      m_NDigits;
011.       bool     m_IsPriceBID;
012.       double   m_TickSize;
013.       struct st00
014.       {
015.          bool  bHigh, bLow;
016.          int   iMax;
017.       }m_Marks;
018. //+------------------------------------------------------------------+
019. template < typename T >
020. inline T RandomLimit(const T Limit01, const T Limit02)
021.          {
022.             T a = (Limit01 > Limit02 ? Limit01 - Limit02 : Limit02 - Limit01);
023.             return (Limit01 >= Limit02 ? Limit02 : Limit01) + ((T)(((rand() & 32767) / 32737.0) * a));
024.          }
025. //+------------------------------------------------------------------+
026. inline void Simulation_Time(const MqlRates &rate, MqlTick &tick[])
027.          {
028.             for (int c0 = 0, iPos, v0 = (int)(60000 / m_Marks.iMax), v1 = 0, v2 = v0; c0 <= m_Marks.iMax; c0++, v1 = v2, v2 += v0)
029.             {
030.                iPos = RandomLimit(v1, v2);
031.                tick[c0].time = rate.time + (iPos / 1000);
032.                tick[c0].time_msc = iPos % 1000;
033.             }
034.          }
035. //+------------------------------------------------------------------+
036. inline void CorretTime(MqlTick &tick[])
037.          {
038.             for (int c0 = 0; c0 <= m_Marks.iMax; c0++)
039.                tick[c0].time_msc += (tick[c0].time * 1000);
040.          }
041. //+------------------------------------------------------------------+
042. inline int Unique(const double price, const MqlTick &tick[])
043.          {
044.             int iPos = 1;
045.             
046.             do
047.             {
048.                iPos = (m_Marks.iMax > 20 ? RandomLimit(1, m_Marks.iMax - 1) : iPos + 1);
049.             }while ((m_IsPriceBID ? tick[iPos].bid : tick[iPos].last) == price);
050.             
051.             return iPos;
052.          }
053. //+------------------------------------------------------------------+
054. inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[])
055.          {
056.             if (m_IsPriceBID)
057.             {
058.                tick[iPos].bid = NormalizeDouble(price, m_NDigits);
059.                tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits);
060.             }else
061.                tick[iPos].last = NormalizeDouble(price, m_NDigits);
062.          }
063. //+------------------------------------------------------------------+
064. inline void Random_Price(const MqlRates &rate, MqlTick &tick[])
065.          {
066.             for (int c0 = 1; c0 < m_Marks.iMax; c0++)
067.             {
068.                MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick);
069.                m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh;
070.                m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow;
071.             }
072.          }
073. //+------------------------------------------------------------------+
074. inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[])
075.          {
076.             for (int c0 = 0; c0 <= m_Marks.iMax; c0++)
077.             {
078.                tick[c0].volume_real = 1.0;
079.                tick[c0].volume = 1;
080.             }
081.             if ((m_Marks.iMax + 1) < rate.tick_volume) for (int c0 = (int)(rate.tick_volume - m_Marks.iMax); c0 > 0; c0--)
082.                tick[RandomLimit(0, m_Marks.iMax - 1)].volume += 1;
083.             for (int c0 = (int)(rate.real_volume - m_Marks.iMax); c0 > 0; c0--)
084.                tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0;
085.          }
086. //+------------------------------------------------------------------+
087. inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode, int iDesloc)
088.          {
089.             double vStep, vNext, price, vH = High, vL = Low;
090.             char i0 = 0;
091.             
092.             vNext = vStep = (Out - In) / ((High - Low) / m_TickSize);
093.             for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++)
094.             {
095.                price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc));
096.                price = (price > vH ? vH : (price < vL ? vL : price));
097.                MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick);
098.                switch (iMode)
099.                {
100.                   case 1:
101.                      i0 |= (price == High ? 0x01 : 0);
102.                      i0 |= (price == Low ? 0x02 : 0);
103.                      vH = (i0 == 3 ? High : vH);
104.                      vL = (i0 ==3 ? Low : vL);
105.                      break;
106.                   case 0:
107.                      if (price == Close) return c0;
108.                   default:
109.                      break;
110.                }
111.                if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue;
112.                vNext += vStep;
113.                vL = (iMode != 2 ? (Close > vL ? (i0 == 3 ? vL : vL + m_TickSize) : vL) : (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize)));
114.                vH = (iMode != 2 ? (Close > vL ? vH : (i0 == 3 ? vH : vH - m_TickSize)) : (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH)));
115.             }
116.             
117.             return Out;
118.          }
119. //+------------------------------------------------------------------+
120.    public   :
121. //+------------------------------------------------------------------+
122.       C_Simulation(const int nDigits)
123.          {
124.             m_NDigits       = nDigits;
125.             m_IsPriceBID    = (SymbolInfoInteger(def_SymbolReplay, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID);
126.             m_TickSize      = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
127.          }
128. //+------------------------------------------------------------------+
129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume = def_MaxTicksVolume)
130.          {
131.             int    i0, i1, i2;
132.             bool   b0;
133.             
134.             m_Marks.iMax   = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume));
135.             m_Marks.iMax   = ((int)rate.tick_volume > m_Marks.iMax ? m_Marks.iMax : (int)rate.tick_volume - 1);
136.             m_Marks.bHigh  = (rate.open == rate.high) || (rate.close == rate.high);
137.             m_Marks.bLow   = (rate.open == rate.low) || (rate.close == rate.low);
138.             Simulation_Time(rate, tick);
139.             MountPrice(0, rate.open, rate.spread, tick);
140.             if (m_Marks.iMax > 10)
141.             {
142.                i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2));
143.                i1 = m_Marks.iMax - i0;
144.                i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0);
145.                i2 = (i2 == 0 ? 1 : i2);
146.                b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low));
147.                i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2);
148.                RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1, i2);
149.                RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2);
150.                m_Marks.bHigh = m_Marks.bLow = true;
151. 
152.             }else Random_Price(rate, tick);
153.             if (!m_IsPriceBID) DistributeVolumeReal(rate, tick);
154.             if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick);
155.             if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick);
156.             MountPrice(m_Marks.iMax, rate.close, rate.spread, tick);
157.             CorretTime(tick);
158. 
159.             return m_Marks.iMax;
160.          }
161. //+------------------------------------------------------------------+
162. };
163. //+------------------------------------------------------------------+

Der Quellcode von C_Simulation.mqh

Zu diesem Zeitpunkt werden Sie vielleicht noch keine Änderungen bemerken, zumal es schon einige Zeit her ist, dass wir diese Klasse zuletzt geändert haben. Das letzte Mal, dass wir uns damit beschäftigt haben, war in dem Artikel über Random Walk. Da wir jedoch sicherstellen müssen, dass das System ein gewisses Maß an Konsistenz in Bezug auf das Timing beibehält, selbst wenn die Anzahl der erzeugten Ticks abnimmt, war eine leichte Änderung dieses Codes erforderlich.

Um Ihnen zu verdeutlichen, was sich geändert hat, möchte ich einen wichtigen Aspekt hervorheben. Es gibt jedoch einige kleinere Anpassungen im gesamten Code, die nicht besonders beachtet werden müssen, da sie nur vorgenommen wurden, um den Prozess an eine neue Methodik anzupassen. Die wichtigste Änderung ist in diesem Fall ein Zusatz in Zeile 16. Diese Variable war ursprünglich nicht im Klassencode vorhanden. Bevor wir jedoch seine weitergehenden Auswirkungen auf die gesamte Klasse untersuchen, wollen wir zunächst sehen, wo er initialisiert wird. Man könnte annehmen, dass seine Initialisierung im Klassenkonstruktor erfolgt. Das ist jedoch nicht der Fall. Stattdessen findet sie zwischen den Zeilen 134 und 135 statt. Achten Sie genau darauf, was in diesen beiden Zeilen passiert. Dies ist von entscheidender Bedeutung, wenn Sie das System ändern wollen. In Zeile 129 deklarieren wir die Funktion, die für die Erzeugung der Tick-Simulation verantwortlich ist. Wir führen nun jedoch einen zusätzlichen Parameter ein, der die maximale Anzahl der zu simulierenden Ticks angibt. Erinnern Sie sich an die Zeile, die der Datei Defines.mqh hinzugefügt wurde? Eine der Stellen, an denen diese Definition verwendet wird, ist genau hier. Analysieren wir, was passiert. Wenn Sie sich entscheiden, den Code zu ändern, können Sie auf diese Weise nachvollziehen, wie sich Ihre Anpassungen auf das Verhalten des Codes auswirken werden. Wenn Sie die Funktion zur Ausführung der Tick-Simulation aufrufen, müssen Sie auch einen Wert angeben, der die maximale Anzahl der zu simulierenden Ticks angibt. Dieser Wert ist nicht obligatorisch, da er bereits einen Standardwert hat. Ist der angegebene Wert jedoch kleiner oder gleich Null, so geht das System von einem Minimum von einem Tick aus. Dies ist keine willkürliche Entscheidung. Vielmehr liegt es daran, dass beim Devisenhandel das kleinstmögliche Tickvolumen genau eins ist. Wenn Sie einen Wert angeben, der den vordefinierten Grenzwert überschreitet, ignoriert die Klasse Ihre Eingabe und verwendet stattdessen den vom System definierten Höchstwert. Dieser Grenzwert wird in der Header-Datei Defines.mqh festgelegt und bestimmt die maximale Anzahl von Ticks, die im System simuliert werden können. Jeder Wert, der zwischen diesen beiden Extremen liegt, wird als maximale Tick-Anzahl für die Simulation verwendet. Sie können ihn also innerhalb dieses Bereichs einstellen.

Nun ein wichtiges Detail: Dieser spezifische Höchstwert wurde nicht zufällig ausgewählt. Wenn wir einen einminütigen Balken durch diese Grenze teilen (auf 2000 Ticks gesetzt), liegt jeder Tick etwa 30 Millisekunden auseinander. Dieses Intervall wird als optimal angesehen, da es eine gleichmäßige und konsistente Bewegung während des gesamten Plotvorgangs gewährleistet.

Sie können zwar höhere Werte angeben, aber beachten Sie, dass dadurch die tatsächliche Anzahl der simulierten Ticks nicht erhöht wird. Stattdessen wird lediglich die Obergrenze für die Simulation angehoben. Diese Erklärung gilt für Zeile 134, aber die tatsächliche maximale Anzahl der Ticks wird in Zeile 135 ermittelt. Zu diesem Zeitpunkt, wenn in Zeile 135 die endgültige Tick-Anzahl ermittelt wird, wird der in Zeile 134 erzeugte Wert mit den im Balken vorhandenen Daten verglichen. Wenn der Wert aus Zeile 134 niedriger ist als die Tick-Anzahl im Balken, wird er verwendet. Wenn die Tick-Anzahl des Balkens niedriger ist, wird die angegebene Eingabe ignoriert und stattdessen die Tick-Anzahl des Balkens verwendet.

Wie bereits erwähnt, erforderten diese Änderungen eine vollständige Überarbeitung aller Tests im Zusammenhang mit der maximalen Tick-Anzahl. Daher wurden alle Funktionen und Verfahren innerhalb dieser Klasse geringfügig geändert. Da diese Änderungen unkompliziert sind, werde ich nicht näher auf sie eingehen. Wenn Sie Fragen haben, können Sie sich auf den Artikel über den Random Walk beziehen. Prüfen Sie den Link: „Entwicklung eines Replay Systems — Marktsimulation (Teil 15): Geburt des SIMULATORS (V) - RANDOM WALK“.

Sehr gut. Das erste Problem ist also gelöst. Jetzt können wir bei jeder Simulation die maximale Anzahl der Ticks anpassen, die zur Erstellung eines Balkens erforderlich sind. Für unsere Zwecke ist dies jedoch nicht ausreichend. Denken Sie daran: Wir kontrollieren nur den Tick-Simulationsprozess, aber wir bieten dem Nutzer nicht die Möglichkeit, die Einstellungen zu ändern, um eine geringere Anzahl von simulierten Ticks einzustellen. Wir haben also zwei Probleme zu lösen. In beiden Fällen sind einige Änderungen am System erforderlich.

Lassen Sie uns also etwas unternehmen. Die nächste Frage, die sich stellt, ist die maximale Anzahl von echten oder extern simulierten Ticks, die vorhanden sein können. Um dies zu berücksichtigen, gehen wir zum nächsten Thema über.


Anpassung an reale Marktdaten

Obwohl der Titel suggeriert, dass nur reale Marktdaten verwendet werden, ist dies nicht ganz richtig. Sie können Marktbewegungen extern simulieren, sie in einer Datei speichern und diese Datei später verwenden, um Tickdaten anstelle von Balken zu liefern. Dies ist ein absolut gültiger und praktikabler Ansatz. Das Kernproblem bleibt jedoch dasselbe wie im vorigen Abschnitt: Wir müssen die maximale Anzahl der Ticks begrenzen, auch wenn wir mit echten Marktdaten arbeiten.

Im Gegensatz zum vorherigen Fall ist dieses Problem aufgrund der Art der zu verarbeitenden Daten wesentlich komplexer. Wäre der Replay-/Simulationsdienst für einen einzigen Markttyp konzipiert, wäre das Problem relativ leicht zu lösen. In diesem Fall könnten wir eine Analyse beim Laden von Ticks durchführen und prüfen, ob die Summe innerhalb des Zeitfensters einen bestimmten Wert überschreitet. Wenn dies der Fall wäre, würde das System gezwungen sein, überschüssige Ticks innerhalb dieses Fensters, das einem Minutenbalken entspricht, zu verwerfen. Dann würden wir den Simulator Bewegungen auf der Grundlage eines RANDOM WALK erzeugen lassen. Auf diese Weise wäre es einfach, die Anzahl der Ticks in einem Fenster zu begrenzen. Da wir jedoch nicht wissen, ob es sich beim Laden von Ticks um BID- oder LAST-Werte handelt, haben wir ein kniffliges Problem zu lösen. Und die Schwierigkeit liegt gerade darin, dass wir nicht wissen, welches Kartensystem verwendet wird: BID oder LAST.

Eine der ersten Fragen, die man sich stellen muss, ist: Was ist die beste Lösung für das Problem? Müssen wir dem Nutzer die Möglichkeit geben, den Charttyp festzulegen und dabei riskieren, dass er sich irrt, oder müssen wir den Code ändern, um diese Situation zu bewältigen? Obwohl die nutzerdefinierte Charttyp-Lösung viel einfacher zu implementieren ist, hat sie einen Nachteil: Der Nutzer kann sie falsch angeben. Seien wir ehrlich: Wie viele Nutzer verstehen wirklich, dass es zwei Arten von Charts gibt und dass das eine einem bestimmten Marktmodell entspricht und das andere einem ganz anderen Modell? Die meisten Nutzer wissen nicht, dass MetaTrader 5 drei Arten von Servern unterstützt, geschweige denn, dass sie die richtige Plot-Methode für ihr spezifisches Asset konfigurieren müssen.

Aufgrund dieses potenziellen Fallstricks wird unsere Umsetzung zusätzlichen Aufwand erfordern. Allerdings haben wir jetzt einen klaren Ausgangspunkt: die Funktion LoadTicks innerhalb der Klasse C_FileTicks. Schauen wir uns nun die ursprüngliche Funktion an und überlegen wir, was wir implementieren sollten. Diese Funktion ist im Folgenden dargestellt:

01.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true)
02.          {
03.             int      MemNRates,
04.                      MemNTicks;
05.             datetime dtRet = TimeCurrent();
06.             MqlRates RatesLocal[],
07.                      rate;
08.             bool     bNew;
09.             
10.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
11.             MemNTicks = m_Ticks.nTicks;
12.             if (!Open(szFileNameCSV)) return 0;
13.             if (!ReadAllsTicks()) return 0;         
14.             rate.time = 0;
15.             for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++)
16.             {
17.                if (!BuildBar1Min(c0, rate, bNew)) continue;
18.                if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
19.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
20.             }
21.             if (!ToReplay)
22.             {
23.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
24.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
25.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
26.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
27.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
28.                m_Ticks.nTicks = MemNTicks;
29.                ArrayFree(RatesLocal);
30.             }else SetSymbolInfos();
31.             m_Ticks.bTickReal = true;
32.                            
33.             return dtRet;
34.          }; 

Quellcodefragment aus C_FilesTicks.mqh

Kümmern Sie sich nicht um die Zeilennummern. Sie dienen hier nur als Referenz für die Erläuterung und stellen nicht die tatsächlichen Zeilen in der Datei C_FileTicks.mqh dar, da diese bereits Änderungen erfahren hat, auf die später eingegangen wird. Kommen wir nun zum Verständnis, was beim Lesen echter Ticks passiert.

Zu Beginn der Funktion gibt es in den Zeilen 10 und 11 zwei Punkte, an denen wir die aktuellen Werte der geladenen Tick-Positionen und der Balken, die diese Ticks repräsentieren, vorübergehend speichern. Wenn der Aufrufer angibt, dass Ticks nicht als Grundlage für die Wiedergabe, sondern als bereits vorhandene Balken verwendet werden sollen, werden diese Werte in den Zeilen 27 bzw. 28 ersetzt. Dadurch wird sichergestellt, dass das System intakt bleibt und auf die Ticks des Replay Systems wartet.

In Zeile 12 versuchen wir, die Datei mit den Daten zu öffnen, und in Zeile 13 lesen wir alle (absolut alle) Ticks, die in der Datei vorhanden sind. Wenn die Ablesung erfolgreich ist, haben wir alle Ticks geladen. In einigen Fällen kann die Anzahl der Ticks pro Zeiteinheit jedoch das vom System festgelegte Maximum überschreiten. Zu diesem Zeitpunkt können wir nichts dagegen tun. Der Grund dafür ist, dass wir noch nicht wissen, welche Art von Chart verwendet werden wird. Sobald jedoch alle Ticks vollständig gelesen wurden, können wir die Art des Charts bestimmen und mit der Arbeit an einer Lösung beginnen.

Jetzt kommt der interessante Teil. Wir werden ein einminütiges Zeitintervall analysieren. Wir machen Folgendes. In Zeile 15 wird eine Schleife eingefügt, die darauf abzielt, Ein-Minuten-Balken zu erstellen, auf die das System bei Bedarf zugreifen kann. Dies ist der Punkt, an dem wir eingreifen werden. In Zeile 17 rufen wir die Funktion auf, die für die Konstruktion der Balken zuständig ist. Diese Funktion berücksichtigt das Volumen der Ticks, die zur Erzeugung der Bewegung innerhalb des Balkens verwendet werden. Passen Sie jetzt gut auf: Wenn die Bedingung in Zeile 18 als wahr ausgewertet wird, werden die Balkendaten so angezeigt, als ob sie aus einer Datei mit den Balken gelesen worden wären. Dies sind genau die Daten, die wir an die Simulationsklasse übergeben müssen. Sie können dies überprüfen, indem Sie zum vorherigen Thema zurückkehren und sich die Zeile 129 ansehen.

Sehen Sie die Umsetzungsidee, die wir brauchen? Wir müssen eine Maßnahme ergreifen, wenn festgestellt wird, dass die Anzahl der Ticks den intern im Programm festgelegten Wert überschreitet, zumindest in dieser ersten Phase der Implementierung. Diesbezügliche Änderungen werden wir später vornehmen.

Dieser Teil ist einfach: Wir überprüfen die Anzahl der Ticks und weisen gegebenenfalls die Simulationsklasse an, einen RANDOM WALK durchzuführen, um die richtige Anzahl von Ticks für die Bewegung zu gewährleisten. Jetzt kommt der komplizierte Teil. Die Tick-Simulationsklasse wird die Ticks erzeugen, aber wir brauchen eine möglichst einfache Methode, um die geladenen Ticks zu verändern. Es werden jedoch nur die Ticks innerhalb des Zeitfensters entfernt, bei denen die Anzahl der Ticks den intern definierten oder durch die Anwendung vorgegebenen Grenzwert überschreitet. Neben diesem Problem gibt es noch ein weiteres, wenn auch einfacheres, Problem. Die Simulation sollte nur stattfinden, wenn die Daten für die Wiedergabe bestimmt sind. Glücklicherweise ist dies leicht zu beheben, da der Anrufer uns mitteilt, ob die Daten für die Wiedergabe verwendet werden sollen. Wir müssen nur noch den Wert ToReplay überprüfen. Ein Kinderspiel! Nun wollen wir versuchen, den schwierigen Teil zu lösen: unnötige Ticks effizient zu überschreiben. Um dies zu erreichen, werden wir die im vorherigen Fragment gezeigte Funktion ändern und durch eine andere ersetzen.

Der erste Versuch, dies zu bewerkstelligen (die Entwicklung neuer Funktionen ist immer ein Prozess von Versuch und Irrtum), ist im folgenden Fragment dargestellt:

01. //+------------------------------------------------------------------+
02.       datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume)
03.          {
04.             int      MemNRates,
05.                      MemNTicks,
06.                      nDigits,
07.                      nShift;
08.             datetime dtRet = TimeCurrent();
09.             MqlRates RatesLocal[],
10.                      rate;
11.             MqlTick  TicksLocal[];
12.             bool     bNew;
13.             
14.             MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate);
15.             nShift = MemNTicks = m_Ticks.nTicks;
16.             if (!Open(szFileNameCSV)) return 0;
17.             if (!ReadAllsTicks()) return 0;         
18.             rate.time = 0;
19.             nDigits = SetSymbolInfos(); 
20.             ArrayResize(TicksLocal, def_MaxSizeArray);
21.             m_Ticks.bTickReal = true;
22.             for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++)
23.             {
24.                if (!BuildBar1Min(c0, rate, bNew)) continue;
25.                if (bNew)
26.                {
27.                   if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume)
28.                   {
29.                      nShift = MemShift;
30.                      C_Simulation *pSimulator = new C_Simulation(nDigits);
31.                      if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) < 0) return 0;
32.                      ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1);
33.                      nShift += c1;
34.                      delete pSimulator;
35.                   }
36.                   MemShift = nShift;
37.                   ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary);
38.                };
39.                m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate;
40.             }
41.             ArrayFree(TicksLocal);
42.             if (!ToReplay)
43.             {
44.                ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates));
45.                ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0);
46.                CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates));
47.                dtRet = m_Ticks.Rate[m_Ticks.nRate].time;
48.                m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates);
49.                m_Ticks.nTicks = MemNTicks;
50.                ArrayFree(RatesLocal);
51.             }else m_Ticks.nTicks = nShift;
52.                            
53.             return dtRet;
54.          };
55. //+------------------------------------------------------------------+

Quellcodefragment der Klasse C_FileTicks.mqh

Sie werden feststellen, dass hier einige Dinge hinzugefügt wurden. Außerdem wurden einige Änderungen an der Ausführungsreihenfolge bestimmter Befehle vorgenommen. Doch auch wenn dieses Fragment die Lösung zu sein scheint, so enthält es doch einen kleinen Makel. Doch bevor wir dazu kommen, wollen wir analysieren, wie dieses Codefragment in der Klasse C_FileTicks effektiv verhindert, dass die Anzahl der echten Ticks die intern festgelegte Systemgrenze überschreitet.

Um dies zu erreichen, wurden die Zeilen 6, 7 und 11 hinzugefügt. In diesen Zeilen werden neue Variablen eingeführt, die wir tatsächlich brauchen werden. In Zeile 15 initialisieren wir die erste dieser neuen Variablen, um die aktuelle Anzahl der geladenen Ticks zu verfolgen. Die Zeilen 18 und 19 wurden ebenfalls hinzugefügt, obwohl sie nur dazu dienen, einige Werte zu initialisieren. Die hinzugefügte Zeile 20 ist notwendig, um Speicher für die Speicherung der zu simulierenden Ticks zuzuweisen. Dieser zugewiesene Speicher wird erst in Zeile 41 freigegeben. Allerdings gibt es hier einen kleinen Fehler, der mit Zeile 31 zusammenhängt. Wir werden dies in der endgültigen Version des Codes korrigieren. Aber halten Sie durch, wir kommen schon noch dazu.

In Zeile 21 finden wir etwas sehr Wichtiges. In diesem Fragment ist der Grund vielleicht nicht ersichtlich. Würde der Inhalt von Zeile 21 jedoch an seiner ursprünglichen Position im Code stehen, würde er seine beabsichtigte Funktion nicht erfüllen. Sie können dies vergleichen, indem Sie sich das obige Originalfragment ansehen und feststellen, wo die Zeile 21 dieses Fragments ursprünglich erscheint. Wenn jedoch Zeile 24 ausgeführt wird, kann die Funktion BuildBar1Min nicht die erforderlichen Kriterien festlegen. Deshalb muss die Ausführungsreihenfolge der in diesem neuen Fragment dargestellten Reihenfolge entsprechen.

Unabhängig davon ist die Umsetzung dessen, was als notwendig beschrieben wurde, zwischen den Zeilen 27 und 35 enthalten. Dieser Abschnitt befasst sich speziell mit der Simulation von Ticks, die verwendet werden sollen, wenn die Anzahl der Ticks in einem einminütigen Balken einen vordefinierten Wert überschreitet. Dieser Wert wird intern in der Anwendung festgelegt.

Achten Sie nun auf Folgendes. Zeile 27 enthält zwei Kontrollen. Die erste legt fest, ob die Daten im Wiedergabemodus oder als bereits vorhandene Balken verwendet werden sollen. Wenn die Daten als bereits vorhandene Balken verwendet werden, ist eine Simulation nicht erforderlich. Bei der zweiten Prüfung wird festgestellt, ob die Anzahl der Ticks den vordefinierten Grenzwert überschreitet. Da sich diese zweite Prüfung jedoch auf einen negativen Index beziehen kann, ist die erste Prüfung so konzipiert, dass ein solches Problem vermieden wird. Daher wird bei der zweiten Prüfung darauf geachtet, dass sie sich auf den aktuellen Index bezieht, dessen Tick-Volumen gerade erfasst worden ist.

Aber warten Sie einen Moment. Die Bedingung in Zeile 25 stellt sicher, dass diese Prüfungen nur dann stattfinden, wenn ein neuer Balken erkannt wird. Wie können wir also die aufgezeichneten Balkendaten analysieren, wenn wir uns bereits in einem neuen Balken befinden? Wenn Sie darüber nachgedacht haben, herzlichen Glückwunsch! Das bedeutet, dass Sie den Code wirklich verstehen. Allerdings haben Sie vielleicht ein kleines, aber entscheidendes Detail übersehen. Bis zur Ausführung von Zeile 39 sehen wir immer noch den Balken, der gerade geschlossen wurde. Der eigentliche Übergang zu einem neuen Balken erfolgt erst in Zeile 39. Verstehen Sie nun, warum die Ausführungsreihenfolge sorgfältig strukturiert sein muss?

Kehren wir nun zum Simulationsprozess zurück. Mit Ausnahme von Zeile 51 funktionieren alle anderen Teile weiterhin wie in den vorangegangenen Artikeln dieser Reihe beschrieben. Zeile 51 hängt davon ab, was während der Tick-Simulationsphase geschieht. Passen Sie also gut auf, denn hier gibt es einen Fehler, der jedoch für die Prüfung dieser Funktion nicht wesentlich beeinträchtigt ist.

In Zeile 29 setzen wir den Verschiebungszeiger auf die Anfangsposition der Ticks im Balken, die ersetzt werden sollen. In Zeile 30 wird dann das Simulationssystem initialisiert. In Zeile 31 starten wir die eigentliche Simulation. Wenn die Simulation fehlschlägt, wird Zeile 41 nicht ausgeführt, d. h. der zugewiesene Speicher wird nicht freigegeben. Für Testzwecke ist dies ein geringfügiges Problem, das jedoch später behoben wird. Sobald die Tick-Simulation erfolgreich ist, erfolgt ein Funktionsaufruf in Zeile 32. Beachten Sie: Diese Zeile 32 könnte durch eine for-Schleife ersetzt werden, aber da die MQL5-Bibliotheksroutine wahrscheinlich für eine schnelle Datenübertragung optimiert ist, ist es besser, sie hier anstelle einer Schleife zu verwenden. In Zeile 33 wird der Verschiebungswert so aktualisiert, dass er unmittelbar nach der Datenübertragung auf eine neue Position zeigt. In Zeile 34 schließlich wird der Simulator zerstört.

Um sicherzustellen, dass der Positionsspeicher aktualisiert wird, damit wir in Zeile 29 korrekt auf die vorgesehene Stelle zeigen, verwenden wir Zeile 36, um diese Aktualisierung durchzuführen. Das gesamte Verfahren weist jedoch einige kleinere Mängel auf, die im nächsten Artikel behandelt werden. Darüber hinaus müssen wir ein weiteres Problem beheben, das fast unsichtbar bleibt, aber in der Simulationsphase vorhanden ist.


Schlussfolgerung

Trotz erheblicher Fortschritte bei der Begrenzung der Anzahl der Ticks pro einminütigem Balken haben diese Fortschritte neue Probleme aufgeworfen und andere offenbart. Da dieser Artikel bereits recht dicht ist und ein sorgfältiges Studium erfordert, um die Vorgänge vollständig zu erfassen, möchte ich nicht noch mehr Komplexität einführen. Wenn Sie sich jedoch der Herausforderung stellen wollen, die verbleibenden Fehler zu identifizieren, die bei der Implementierung dieses Codes aufgetaucht sind, hier ein Hinweis: Einer der Fehler betrifft die Mindestanzahl der Ticks, die simuliert werden sollten. Dies ist ein interessantes Problem, das es zu lösen gilt. Dieser Fehler ist nur deshalb aufgefallen, weil wir jetzt Ticks simulieren wollen, wenn ihre Anzahl einen bestimmten Wert überschreitet. Nehmen Sie sich einen Moment Zeit, um darüber nachzudenken, und Sie sollten in der Lage sein zu verstehen, wie dies geschieht. Der andere Fehler tritt auf, wenn simulierte Werte in das Tick-Array kopiert werden. Wenn diese Kopie stattfindet, ist das System zur Erzeugung von Abspielbalken gefährdet. Manchmal kann es zu unlogischen oder erratischen Mustern kommen. Außerdem verschwinden die Ticks zu bestimmten Zeiten einfach, wodurch das Wiedergabesystem nicht genau und korrekt funktioniert.

Wenn Sie versuchen wollen, diese Probleme zu lösen, bevor Sie den nächsten Artikel lesen - prima! Dies ist eine hervorragende Schulung, die Ihnen helfen wird, Ihre eigenen Lösungen zu entwickeln und zu verfeinern. Wie auch immer, im nächsten Artikel werde ich zeigen, wie man diese Fehler beheben kann. Das wird sehr interessant werden. Wir sehen uns also im nächsten Artikel.


Video zur Demonstration eines Systemfehlers

In Kürze werde ich zeigen, wie dieses Problem behoben werden kann, allerdings nicht sofort, da es nicht kritisch ist. Dieses Problem tritt nur beim Laden oder Entladen von erstellten Objekten auf. Dies stellt eine große Chance dar. Wenn Sie Ihre Programmierkenntnisse wirklich testen wollen, versuchen Sie, das Problem zu lösen, bevor ich Ihnen zeige, wie es geht. Sie brauchen mir Ihre Lösung nicht zu zeigen, aber versuchen Sie es, bevor Sie meine Lösung lesen. Dies wird Ihnen helfen, den aktuellen Lernstand zu beurteilen.

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

Beigefügte Dateien |
Anexo.zip (420.65 KB)
Von der Grundstufe bis zur Mittelstufe: WHILE- und DO WHILE-Anweisungen Von der Grundstufe bis zur Mittelstufe: WHILE- und DO WHILE-Anweisungen
In diesem Artikel werden wir einen praktischen und sehr visuellen Blick auf die erste Schleifenanweisung werfen. Viele Anfänger fühlen sich zwar eingeschüchtert, wenn sie mit der Aufgabe konfrontiert werden, Schleifen zu erstellen, aber das Wissen, wie man es richtig und sicher macht, kann nur mit Erfahrung und Übung erworben werden. Aber wer weiß, vielleicht kann ich Ihren Ärger und Ihr Leid verringern, indem ich Ihnen die wichtigsten Probleme und Vorsichtsmaßnahmen aufzeige, die Sie bei der Verwendung von Schleifen in Ihrem Code beachten müssen.
Von der Grundstufe bis zur Mittelstufe: IF ELSE Von der Grundstufe bis zur Mittelstufe: IF ELSE
In diesem Artikel geht es um die Arbeit mit dem Operator IF und seinem Pendant ELSE. Diese Anweisung ist die wichtigste und aussagekräftigste, die es in jeder Programmiersprache gibt. Trotz ihrer einfachen Handhabung kann sie jedoch manchmal verwirrend sein, wenn man keine Erfahrung mit ihrer Verwendung und den damit verbundenen Konzepten hat. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Neuronale Netze im Handel: Hierarchische Vektortransformer (Letzter Teil) Neuronale Netze im Handel: Hierarchische Vektortransformer (Letzter Teil)
Wir fahren fort mit der Untersuchung der Methode der hierarchischen Vektortransformation. In diesem Artikel werden wir die Konstruktion des Modells abschließen. Wir werden es auch anhand echter historischer Daten trainieren und testen.
Entwicklung eines Replay-Systems (Teil 61): Den Dienst abspielen (II) Entwicklung eines Replay-Systems (Teil 61): Den Dienst abspielen (II)
In diesem Artikel werden wir uns mit Änderungen befassen, die einen effizienteren und sichereren Betrieb des Replay-/Simulationssystems ermöglichen. Ich möchte auch nicht die Aufmerksamkeit derjenigen vernachlässigen, die das Beste durch die Verwendung von Klassen machen wollen. Darüber hinaus werden wir ein spezielles Problem in MQL5 betrachten, das die Codeleistung bei der Arbeit mit Klassen verringert, und erklären, wie man es lösen kann.