
Entwicklung eines Replay-Systems (Teil 63): Abspielen des Dienstes (IV)
Einführung
Im vorangegangenen Artikel Entwicklung eines Replay-Systems (Teil 62): Abspielen des Dienstes (III) haben wir uns damit befasst, wie Ticks behandelt werden, als ob sie echt wären. Ein Übermaß ist nicht unser Hauptanliegen, es kann jedoch die Fähigkeit der Anwendung erschweren, das richtige Timing zu erreichen und sicherzustellen, dass der Ein-Minuten-Balken innerhalb des vorgesehenen Ein-Minuten-Fensters vollständig aufgebaut wird.
Trotz der Fortschritte, die in dem vorangegangenen Artikel erzielt wurden, habe ich am Ende erwähnt, dass bei der Umsetzung der Simulation in reale Daten gewisse Fehler aufgetreten sind. Ich möchte noch einmal betonen, dass es diese Fehler in einer reinen Simulation nicht gibt. Wenn die Simulation jedoch mit realen Daten kombiniert wird, treten solche Fehler unweigerlich auf. Das ist nicht unbedingt ein Problem, denn so können wir Tests durchführen und feststellen, ob das zu entwickelnde System für die Wartung und Verbesserung geeignet ist. Oft entwickeln wir etwas nur, um seine Machbarkeit zu testen. Wenn sich das zugrundeliegende Konzept als nicht praktikabel erweist, können wir es verwerfen und so den Zeitaufwand für Korrekturen und andere Anpassungen minimieren.
In demselben Artikel habe ich diese Fehler und ihre Ursachen aufgezeigt. In Anbetracht der umfangreichen Informationen, die bereits präsentiert wurden (Informationen, die Sie, lieber Leser, gründlich verstehen müssen, bevor weitere Fortschritte gemacht werden), habe ich den Artikel an einem bestimmten Punkt abgeschlossen. Hier werden wir diese Fehler jedoch ansprechen und korrigieren, und zwar nicht aufgrund von Code-Änderungen, sondern weil wir etwas implementieren, das nicht notwendig wäre, wenn der Zeitsteuerungsmechanismus des Systems nicht überlastet wäre. Diese Überlastung ist noch nicht erkennbar, da wir noch in der Anfangsphase der Umsetzung aller erforderlichen Maßnahmen sind.
Da diese Themen miteinander verknüpft sind, könnten wir uns zunächst mit jedem von ihnen befassen. Da ich jedoch bei bestimmten Details pingelig bin, werde ich zunächst die Frage nach der Mindestanzahl der Ticks klären, die bei der Verwendung von realen Daten simuliert werden müssen.
Wie hoch ist die Mindestanzahl der zu erzeugenden Ticks?
Die Beantwortung dieser Frage ist nicht so einfach, wie es vielleicht scheint. Denn bei der Änderung dieser Anwendung, die eine Wiedergabe oder Simulation ermöglicht, muss man immer einen wichtigen Punkt beachten. Im Wiedergabemodus kann dieses Problem behoben werden, indem die Simulationsklasse gezwungen wird, eine Mindestanzahl von Ticks zu verwenden. Denken Sie daran, dass es sich bei der Verwendung der Replay-Funktion um echte Daten handelt. Bei der Simulation von Ticks zur Zeitsteuerung innerhalb eines Ein-Minuten-Fensters ist der Ansatz jedoch ein anderer. Dies gilt, wenn die Simulation auf den erhaltenen Tarifdaten beruht. Das Problem besteht darin, dass die Nutzer derzeit die maximale Anzahl der Ticks, die verwendet werden können, nicht einstellen können.
Ich plane, diese Option in die Konfigurationsdatei aufzunehmen, damit die Nutzer diesen Wert kontrollieren können. Der Grund dafür ist einfach: Wenn die Workstation eines Nutzers mehr Ticks erzeugen kann, als die Anwendung zulässt, kann er den Wert in der Konfigurationsdatei anpassen, um eine Wiedergabe zu erreichen, die der Realität besser entspricht. Umgekehrt kann der Nutzer, wenn sein Arbeitsplatz eine bestimmte Anzahl von Ticks nicht bewältigen kann, die Anzahl auf einen niedrigeren Wert reduzieren, was einen reibungsloseren und weniger anstrengenden Betrieb gewährleistet.
Ein weiterer Faktor, der diese Frage verkompliziert, ist, dass es nicht nur um die Angabe einer Zahl geht. Denn es ist möglich, Daten extern zu simulieren, sie zu speichern und sie wie eine echte Datenbank zu verwenden. Dies ist das Worst-Case-Szenario für das Replay System. Wir werden diesen Aspekt jedoch vorerst nicht behandeln. Unser unmittelbarer Schwerpunkt liegt auf der Korrektur von Fehlkonfigurationen, die vom Anwendungsnutzer vorgenommen wurden.
Um auf den Hauptpunkt zurückzukommen: Wie hoch ist die erforderliche Mindestanzahl von Ticks? Die Antwort hängt davon ab. Um dies zu verstehen, sollten Sie Folgendes bedenken. Wenn der Eröffnungs-, der Schluss-, der Höchst- und der Tiefstkurs identisch sind, reicht ein einziger Tick aus. Dies ist jedoch der einfachste Fall. Um andere Szenarien zu berücksichtigen, müssen wir einige Bedingungen festlegen. Der erste ist, dass das Öffnen und Schließen notwendigerweise innerhalb der Höchst- und Mindestgrenzen erfolgen muss. Daraus ergeben sich die folgenden Szenarien:
- Wenn der Eröffnungskurs gleich einem der Limits und der Schlusskurs gleich dem anderen ist, sind zwei Ticks erforderlich.
- Wenn entweder der Eröffnungs- oder der Schlusskurs einem der Limits entspricht, während das gegenüberliegende Limit außerhalb dieser Werte liegt, sind drei Ticks erforderlich.
- Wenn alle vier OHLC-Werte (Open, High, Low, Close) unterschiedlich sind oder wenn der Eröffnungs- und der Schlusskurs identisch sind, aber nicht mit dem Höchst- oder Tiefstwert des Balkens übereinstimmen, werden vier Ticks benötigt.
Dies bildet die Grundlage für die Bestimmung der Mindestanzahl von Ticks, die für die Simulation erforderlich sind. Die Simulation läuft bisher reibungslos. Ich möchte jedoch den Nutzern die Möglichkeit geben, ein Limit zu konfigurieren, um sicherzustellen, dass ihre Workstation die Wiedergabe-/Simulationsanwendung optimal ausführen kann.
Vielleicht erscheint diese Erklärung in Textform komplex, aber im Code wird sie viel klarer. Anstatt die gesamte Datei C_Simulation.mqh wiederzugeben, werde ich daher im Folgenden nur das relevante Codefragment einfügen. Dies ist der Teil, der geändert wurde, um das bestehende Problem zu beheben.
128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume) 130. { 131. int i0, i1, i2, dm = 0; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. dm = (dm == 0 ? ((rate.open == rate.high == rate.low == rate.close) ? 1 : dm) : dm); 136. dm = (dm == 0 ? (((rate.open == rate.high) && (rate.low == rate.close)) || ((rate.open == rate.low) && (rate.close == rate.high)) ? 2 : dm) : dm); 137. if ((dm == 0 ? ((rate.open == rate.close == rate.high) || (rate.open == rate.close == rate.low) ? 3 : 4) : dm) == 0) return -1; 138. m_Marks.iMax = (MaxTickVolume <= dm ? dm : MaxTickVolume); 139. m_Marks.iMax = (((int)rate.tick_volume > m_Marks.iMax) || ((int)rate.tick_volume < dm) ? m_Marks.iMax : (int)rate.tick_volume - 1); 140. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 141. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 142. Simulation_Time(rate, tick); 143. MountPrice(0, rate.open, rate.spread, tick); 144. if (m_Marks.iMax > 10) 145. { 146. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 147. i1 = m_Marks.iMax - i0; 148. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 149. i2 = (i2 == 0 ? 1 : i2); 150. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 151. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 152. 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); 153. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 154. m_Marks.bHigh = m_Marks.bLow = true; 155. 156. }else Random_Price(rate, tick); 157. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 158. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 159. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 160. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 161. CorretTime(tick); 162. 163. return m_Marks.iMax; 164. } 165. //+------------------------------------------------------------------+
Fragment der Datei C_Simulation.mqh
Die Zeilennummerierung in diesem Fragment zeigt genau an, wo Sie den vollständigen Code aus dem vorherigen Artikel ändern müssen. Beachten Sie, dass ich in Zeile 131 eine neue Variable hinzugefügt und sie sofort nach der Deklaration initialisiert habe. Beachten Sie auch, dass die ursprüngliche Zeile 134 entfernt und durch drei neue Zeilen ersetzt werden muss. Mit diesen drei Zeilen werden die zuletzt genannten Punkte umgesetzt, sodass die erforderliche Mindestanzahl von Ticks erzeugt wird.
Es gibt jedoch ein entscheidendes Detail, das Sie genau beachten müssen. In Zeile 138 wird während des ternären Operatortests der Wert von MaxTickVolume mit dem kürzlich eingestellten Wert verglichen, um die Mindestanzahl der Ticks zu bestimmen. Wenn MaxTickVolume kleiner als dieser angepasste Wert ist, wird der angepasste Wert verwendet, unabhängig von anderen Daten. In Zeile 139 wird dieselbe Bedingung noch einmal überprüft. Wenn der Wert in rate.tick_volume ebenfalls niedriger ist als der angepasste Wert, hat der angepasste Wert Vorrang.
Vergessen Sie nicht, den Rückgabewert dieser Simulationsfunktion zu testen, insbesondere wenn Sie sie für andere Zwecke verwenden wollen. Dies ist wichtig, denn wenn bei der Einstellung der Mindestanzahl von Ticks ein Fehler auftritt, kehrt die Funktion sofort in Zeile 137 zurück. Versuchen Sie daher nicht, die Werte im zurückgegebenen Array zu verwenden, ohne vorher die Rückgabe der Funktion zu überprüfen, da die Array-Werte ungültig sein könnten.
Im Vergleich zu dem im vorigen Artikel vorgestellten Code wurde hier eine kleine Änderung vorgenommen. Am Ende dieses Artikels werde ich jedoch noch einmal auf dieses Fragment zurückkommen, um sicherzustellen, dass die Erklärung für diese Änderung auch wirklich Sinn macht.
Damit haben wir unser erstes Problem gelöst. Lassen Sie uns nun zum nächsten Thema übergehen und das zweite Problem angehen.
Behebung des Problems der Tick-Anpassung
Die Lösung dieses zweiten Problems erfordert mehr Aufwand. Die Tatsache, dass es arbeitsintensiver ist, bedeutet jedoch nicht zwangsläufig, dass es schwieriger ist - es bedeutet nur, dass es etwas mehr Arbeit erfordert. Wir werden also Folgendes tun: Überprüfen wir das Codefragment aus dem vorherigen Artikel, das für die Behandlung von Bewegungen und die Anpassung von Ticks zuständig ist, um sicherzustellen, dass sie für die Chartdarstellung verwendet werden können. Das Fragment ist nachstehend wiedergegeben.
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. //+------------------------------------------------------------------+
Fragment der Datei C_FileTicks.mqh
Bitte beachten Sie, dass dieser Code Fehler enthält, die wir in diesem Artikel beheben müssen. Achten Sie nun genau auf das nächste Fragment und vergleichen Sie es mit dem vorherigen.
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. m_Ticks.bTickReal = true; 21. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 22. { 23. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 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. ArrayResize(TicksLocal, def_MaxSizeArray); 31. C_Simulation *pSimulator = new C_Simulation(nDigits); 32. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 33. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 34. delete pSimulator; 35. ArrayFree(TicksLocal); 36. if (c1 < 0) return 0; 37. } 38. MemShift = nShift; 39. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 40. }; 41. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 42. } 43. if (!ToReplay) 44. { 45. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 46. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 47. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 48. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 49. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 50. m_Ticks.nTicks = MemNTicks; 51. ArrayFree(RatesLocal); 52. }else m_Ticks.nTicks = nShift; 53. 54. return dtRet; 55. }; 56. //+------------------------------------------------------------------+
Fragment der Datei C_FileTicks.mqh (endgültig)
Erkennen Sie die Unterschiede? Sie sind nicht besonders drastisch, aber sie sind da. Sie werden feststellen, dass ich einige Änderungen an der Reihenfolge der Ausführung bestimmter Vorgänge vorgenommen habe. Die offensichtlichste Änderung betrifft die Zuweisung und Freigabe von Speicher für die während der Simulation erzeugten Ticks. Da es sich bei der Funktion LoadTicks um eine Primärfunktion handelt, d. h. sie wird ausgeführt, bevor das System vollständig initialisiert ist und mit der Leistungsanforderung beginnt, können wir es uns leisten, bei den Aufrufen zur Speicherzuweisung und -freigabe etwas Zeit zu verlieren.
Wenn Sie der Meinung sind, dass ein solcher Zeitverlust inakzeptabel ist, können Sie den Ausführungsauftrag nach Bedarf anpassen. In dem unten gezeigten korrigierten Fragment darf jedoch nicht übersehen werden, wie wichtig der Aufruf des Destruktors der Simulationsklasse im Falle eines Fehlers ist. Beim Vergleich der Codefragmente werden Sie feststellen, dass die Funktion in der korrigierten Version nur im Falle eines Fehlers in Zeile 36 zurückkehrt. Aber vorher, in den Zeilen 34 und 35, rufen wir explizit den Destruktor auf und geben den zugewiesenen Speicher frei. In dieser speziellen Reihenfolge.
Wenn die Simulation erfolgreich ist und die Daten verschoben werden können, führen wir diesen Schritt in Zeile 33 aus, wobei wir auch den Rückgabewert einer Bibliotheksfunktion verwenden, um den neuen Offset-Wert zu aktualisieren.
Damit lösen wir das Problem, das bisher auftrat, wenn die Simulation als Fehlschlag zurückkam. Es gibt jedoch noch einen weiteren Punkt, der erklärt werden muss. Wenn Sie sich das korrigierte Fragment ansehen, werden Sie etwas Seltsames bemerken. Es mag auf den ersten Blick nicht viel Sinn machen, aber das ist in Zeile 23 zu sehen. Warum wird in Zeile 23 der Tick-Zähler mit dem Offset-Wert verglichen? Was ist der Zweck dieser Überprüfung?
Ehrlich gesagt, scheint das nicht viel Sinn zu machen. So viel ist wahr. Wenn jedoch der Simulator ausgeführt wird, weicht der Offset-Wert vom Tick-Zähler ab. In diesem Fall werden einige echte Ticks falsch indiziert. Wenn Sie dies nicht korrigieren, kann es passieren, dass bei der Ausführung von Zeile 52 eine Reihe von echten Ticks einfach verschwindet. Ganz zu schweigen von dem Problem, das zwischen simulierten und nicht simulierten Balken auftritt, bei dem aufgrund der falschen Indexierung einige seltsame Ticks dazwischen auftreten können.
Jetzt glaube ich, dass Sie den Kern des Problems wirklich verstanden haben. Durch Hinzufügen einer Prüfung in Zeile 23, um die echten Ticks zu überprüfen und neu zu positionieren, wird alles ordnungsgemäß ausgeführt, und wir werden keine Anomalien auf dem Chart sehen. Es ist eine einfache Lösung, die aber das Problem vollständig behebt. Die endgültige Version des Codes in der Datei C_FileTicks.mqh sieht wie folgt aus:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_FileBars.mqh" 005. #include "C_Simulation.mqh" 006. //+------------------------------------------------------------------+ 007. #define macroRemoveSec(A) (A - (A % 60)) 008. #define def_MaxSizeArray 16777216 // 16 Mbytes 009. //+------------------------------------------------------------------+ 010. class C_FileTicks 011. { 012. protected: 013. enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX}; 014. struct stInfoTicks 015. { 016. MqlTick Info[]; 017. MqlRates Rate[]; 018. int nTicks, 019. nRate; 020. bool bTickReal; 021. ePlotType ModePlot; 022. }; 023. //+------------------------------------------------------------------+ 024. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew) 025. { 026. double dClose = 0; 027. 028. switch (m_Ticks.ModePlot) 029. { 030. case PRICE_EXCHANGE: 031. if (m_Ticks.Info[iArg].last == 0.0) return false; 032. dClose = m_Ticks.Info[iArg].last; 033. break; 034. case PRICE_FOREX: 035. dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose); 036. if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false; 037. break; 038. } 039. if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time))) 040. { 041. rate.time = macroRemoveSec(m_Ticks.Info[iArg].time); 042. rate.real_volume = 0; 043. rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0); 044. rate.open = rate.low = rate.high = rate.close = dClose; 045. }else 046. { 047. rate.close = dClose; 048. rate.high = (rate.close > rate.high ? rate.close : rate.high); 049. rate.low = (rate.close < rate.low ? rate.close : rate.low); 050. rate.real_volume += (long) m_Ticks.Info[iArg].volume_real; 051. rate.tick_volume += (m_Ticks.bTickReal ? 1 : (int)m_Ticks.Info[iArg].volume); 052. } 053. 054. return true; 055. } 056. //+------------------------------------------------------------------+ 057. private : 058. int m_File; 059. stInfoTicks m_Ticks; 060. //+------------------------------------------------------------------+ 061. inline bool Open(const string szFileNameCSV) 062. { 063. string szInfo = ""; 064. 065. if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) 066. { 067. for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File); 068. if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true; 069. Print("File ", szFileNameCSV, ".csv not a traded tick file."); 070. }else 071. Print("Tick file ", szFileNameCSV,".csv not found..."); 072. 073. return false; 074. } 075. //+------------------------------------------------------------------+ 076. inline bool ReadAllsTicks(void) 077. { 078. string szInfo; 079. 080. Print("Loading replay ticks. Please wait..."); 081. ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 082. m_Ticks.ModePlot = PRICE_FOREX; 083. while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag)) 084. { 085. ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray); 086. szInfo = FileReadString(m_File) + " " + FileReadString(m_File); 087. m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19)); 088. m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3)); 089. m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File)); 090. m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File)); 091. m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File)); 092. m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File)); 093. m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File)); 094. m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot); 095. m_Ticks.nTicks++; 096. } 097. FileClose(m_File); 098. if (m_Ticks.nTicks == (INT_MAX - 2)) 099. { 100. Print("Too much data in tick file.\nIt is not possible to continue..."); 101. return false; 102. } 103. return (!_StopFlag); 104. } 105. //+------------------------------------------------------------------+ 106. int SetSymbolInfos(void) 107. { 108. int iRet; 109. 110. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5)); 111. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX); 112. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID); 113. 114. return iRet; 115. } 116. //+------------------------------------------------------------------+ 117. public : 118. //+------------------------------------------------------------------+ 119. C_FileTicks() 120. { 121. ArrayResize(m_Ticks.Rate, def_BarsDiary); 122. m_Ticks.nRate = -1; 123. m_Ticks.nTicks = 0; 124. m_Ticks.Rate[0].time = 0; 125. } 126. //+------------------------------------------------------------------+ 127. bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume) 128. { 129. C_FileBars *pFileBars; 130. C_Simulation *pSimulator = NULL; 131. int iMem = m_Ticks.nTicks, 132. iRet = -1; 133. MqlRates rate[1]; 134. MqlTick local[]; 135. bool bInit = false; 136. 137. pFileBars = new C_FileBars(szFileNameCSV); 138. ArrayResize(local, def_MaxSizeArray); 139. Print("Converting bars to ticks. Please wait..."); 140. while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) 141. { 142. if (!bInit) 143. { 144. m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX); 145. pSimulator = new C_Simulation(SetSymbolInfos()); 146. bInit = true; 147. } 148. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary); 149. m_Ticks.Rate[++m_Ticks.nRate] = rate[0]; 150. if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume); 151. if (iRet < 0) break; 152. for (int c0 = 0; c0 <= iRet; c0++) 153. { 154. ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray); 155. m_Ticks.Info[m_Ticks.nTicks++] = local[c0]; 156. } 157. } 158. ArrayFree(local); 159. delete pFileBars; 160. delete pSimulator; 161. m_Ticks.bTickReal = false; 162. 163. return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0)); 164. } 165. //+------------------------------------------------------------------+ 166. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 167. { 168. int MemNRates, 169. MemNTicks, 170. nDigits, 171. nShift; 172. datetime dtRet = TimeCurrent(); 173. MqlRates RatesLocal[], 174. rate; 175. MqlTick TicksLocal[]; 176. bool bNew; 177. 178. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 179. nShift = MemNTicks = m_Ticks.nTicks; 180. if (!Open(szFileNameCSV)) return 0; 181. if (!ReadAllsTicks()) return 0; 182. rate.time = 0; 183. nDigits = SetSymbolInfos(); 184. m_Ticks.bTickReal = true; 185. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 186. { 187. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 188. if (!BuildBar1Min(c0, rate, bNew)) continue; 189. if (bNew) 190. { 191. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 192. { 193. nShift = MemShift; 194. ArrayResize(TicksLocal, def_MaxSizeArray); 195. C_Simulation *pSimulator = new C_Simulation(nDigits); 196. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 197. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 198. delete pSimulator; 199. ArrayFree(TicksLocal); 200. if (c1 < 0) return 0; 201. } 202. MemShift = nShift; 203. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 204. }; 205. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 206. } 207. if (!ToReplay) 208. { 209. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 210. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 211. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 212. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 213. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 214. m_Ticks.nTicks = MemNTicks; 215. ArrayFree(RatesLocal); 216. }else m_Ticks.nTicks = nShift; 217. 218. return dtRet; 219. }; 220. //+------------------------------------------------------------------+ 221. inline stInfoTicks GetInfoTicks(void) const 222. { 223. return m_Ticks; 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef def_MaxSizeArray 229. //+------------------------------------------------------------------+
Header-Datei C_FileTicks.mqh
Nachdem wir diese Fragen geklärt haben, können wir nun zum nächsten Schritt übergehen. Dies bedeutet, dass der Nutzer einen Wert festlegen kann, der als maximale Anzahl von Ticks innerhalb eines einminütigen Balkens verwendet werden soll. Der Übersichtlichkeit halber werden wir dies in einem neuen Abschnitt behandeln.
Dem Nutzer die Möglichkeit geben, Anpassungen vorzunehmen
Dieser Teil ist zweifellos am leichtesten, am einfachsten und am angenehmsten zu realisieren. Das liegt daran, dass die einzige Aufgabe, die Sie als Entwickler haben, darin besteht, den Namen des Schlüssels zu definieren, der verwendet wird, um den Wert festzulegen, den wir anpassen müssen.
Bei diesem Schlüssel handelt es sich im Wesentlichen um den Wert, den der Nutzer in die Konfigurationsdatei für das Asset eingeben muss. Wie das geht, habe ich in früheren Artikeln dieser Serie gezeigt. Da es sich jedoch um eine sehr einfache Implementierung handelt, werde ich den Code nicht in Fragmente zerlegen, um jeden Zusatz zu erklären. Bevor wir uns den endgültigen Code ansehen, wollen wir uns zunächst ein Beispiel ansehen, wie diese neue Funktionalität genutzt werden kann. Sehen wir uns eine Beispielkonfigurationsdatei für die Replay/Simulator-Anwendung an. Das Beispiel sieht folgendermaßen aus.
01. [Config] 02. Path = WDO 03. PointsPerTick = 0.5 04. ValuePerPoints = 5.0 05. VolumeMinimal = 1.0 06. Account = NETTING 07. MaxTicksPerBar = 2800 08. 09. [Bars] 10. WDON22_M1_202206140900_202206141759 11. 12. [ Ticks -> Bars] 13. 14. [ Bars -> Ticks ] 15. 16. [Ticks] 17. WDON22_202206150900_202206151759
Beispiel für eine Konfigurationsdatei
Beachten Sie die Zeile sieben, in der eine neue Konfigurationseinstellung eingeführt wurde. Wenn Sie versuchen, diese Konfigurationsdatei mit einer früheren Version der Wiedergabe-/Simulationsanwendung für MetaTrader 5 zu verwenden, erhalten Sie eine Fehlermeldung, die besagt, dass Zeile 7 einen nicht erkannten Parameter enthält. In der Version, die ich hier vorstelle, ist die Anwendung jedoch in der Lage zu interpretieren, was Zeile sieben bedeutet.
Ein wichtiger Hinweis: Weder Sie noch der Endnutzer müssen diese neue Einstellung in Zeile sieben angeben. Falls angegeben, setzt die Konfiguration den in der kompilierten Anwendung eingebetteten Standardwert außer Kraft. Wird diese Angabe weggelassen, greift der Wiedergabe-/Simulationsdienst auf die bei der Kompilierung intern definierte Standardeinstellung zurück.
Ich weise darauf hin, bevor ich den Code zeige, weil ich betonen möchte, dass diese Konfiguration völlig optional ist. Wenn er jedoch verwendet wird, hat er Vorrang vor dem vorkompilierten Wert. Eine letzte Erinnerung: Jede Konfigurationsdatei ist einzigartig. Das bedeutet, dass Sie für jede einzelne eine andere maximale Tickanzahl festlegen können. Experimentieren Sie also ruhig mit verschiedenen Setups, bis Sie die Konfiguration gefunden haben, die eine gute Leistung in MetaTrader 5 gewährleistet und dafür sorgt, dass das Chart reibungslos gerendert wird.
Werfen wir nun einen Blick auf die aktualisierte Header-Datei, die für die Interpretation und Anwendung der Werte aus der Konfigurationsdatei verantwortlich ist. Ich spreche von C_ConfigService.mqh. Der aktualisierte Code ist nachstehend vollständig aufgeführt.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Support\C_FileBars.mqh" 005. #include "Support\C_FileTicks.mqh" 006. #include "Support\C_Array.mqh" 007. //+------------------------------------------------------------------+ 008. class C_ConfigService : protected C_FileTicks 009. { 010. protected: 011. //+------------------------------------------------------------------+ 012. private : 013. enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; 014. enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; 015. struct st001 016. { 017. C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; 018. int Line, 019. MaxTickVolume; 020. bool AccountHedging; 021. char ModelLoading; 022. string szPath; 023. }m_GlPrivate; 024. //+------------------------------------------------------------------+ 025. inline void FirstBarNULL(void) 026. { 027. MqlRates rate[1]; 028. int c0 = 0; 029. 030. for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++); 031. rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid); 032. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 033. rate[0].tick_volume = 0; 034. rate[0].real_volume = 0; 035. rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400; 036. CustomRatesUpdate(def_SymbolReplay, rate); 037. } 038. //+------------------------------------------------------------------+ 039. inline eTranscriptionDefine GetDefinition(const string &In, string &Out) 040. { 041. string szInfo; 042. 043. szInfo = In; 044. Out = ""; 045. StringToUpper(szInfo); 046. StringTrimLeft(szInfo); 047. StringTrimRight(szInfo); 048. if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO; 049. if (StringSubstr(szInfo, 0, 1) != "[") 050. { 051. Out = szInfo; 052. return Transcription_INFO; 053. } 054. for (int c0 = 0; c0 < StringLen(szInfo); c0++) 055. if (StringGetCharacter(szInfo, c0) > ' ') 056. StringAdd(Out, StringSubstr(szInfo, c0, 1)); 057. 058. return Transcription_DEFINE; 059. } 060. //+------------------------------------------------------------------+ 061. inline bool Configs(const string szInfo) 062. { 063. const string szList[] = { 064. "PATH", 065. "POINTSPERTICK", 066. "VALUEPERPOINTS", 067. "VOLUMEMINIMAL", 068. "LOADMODEL", 069. "ACCOUNT", 070. "MAXTICKSPERBAR" 071. }; 072. string szRet[]; 073. char cWho; 074. 075. if (StringSplit(szInfo, '=', szRet) == 2) 076. { 077. StringTrimRight(szRet[0]); 078. StringTrimLeft(szRet[1]); 079. for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; 080. switch (cWho) 081. { 082. case 0: 083. m_GlPrivate.szPath = szRet[1]; 084. return true; 085. case 1: 086. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); 087. return true; 088. case 2: 089. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); 090. return true; 091. case 3: 092. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); 093. return true; 094. case 4: 095. m_GlPrivate.ModelLoading = StringInit(szRet[1]); 096. m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading); 097. return true; 098. case 5: 099. if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true; 100. else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false; 101. else 102. { 103. Print("Entered account type is not invalid."); 104. return false; 105. } 106. return true; 107. case 6: 108. m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1])); 109. return true; 110. } 111. Print("Variable >>", szRet[0], "<< not defined."); 112. }else 113. Print("Configuration definition >>", szInfo, "<< invalidates."); 114. 115. return false; 116. } 117. //+------------------------------------------------------------------+ 118. inline bool WhatDefine(const string szArg, char &cStage) 119. { 120. const string szList[] = { 121. "[BARS]", 122. "[TICKS]", 123. "[TICKS->BARS]", 124. "[BARS->TICKS]", 125. "[CONFIG]" 126. }; 127. 128. cStage = 1; 129. for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++) 130. if (szList[c0] == szArg) return true; 131. 132. return false; 133. } 134. //+------------------------------------------------------------------+ 135. inline bool CMD_Array(char &cError, eWhatExec e1) 136. { 137. bool bBarsPrev = false; 138. string szInfo; 139. C_FileBars *pFileBars; 140. C_Array *ptr = NULL; 141. 142. switch (e1) 143. { 144. case eTickReplay : ptr = m_GlPrivate.pTicksToReplay; break; 145. case eTickToBar : ptr = m_GlPrivate.pTicksToBars; break; 146. case eBarToTick : ptr = m_GlPrivate.pBarsToTicks; break; 147. case eBarPrev : ptr = m_GlPrivate.pBarsToPrev; break; 148. } 149. if (ptr != NULL) 150. { 151. for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) 152. { 153. if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break; 154. switch (e1) 155. { 156. case eTickReplay: 157. if (LoadTicks(szInfo, true, m_GlPrivate.MaxTickVolume) == 0) cError = 4; 158. break; 159. case eTickToBar : 160. if (LoadTicks(szInfo, false, m_GlPrivate.MaxTickVolume) == 0) cError = 5; else bBarsPrev = true; 161. break; 162. case eBarToTick : 163. if (!BarsToTicks(szInfo, m_GlPrivate.MaxTickVolume)) cError = 6; 164. break; 165. case eBarPrev : 166. pFileBars = new C_FileBars(szInfo); 167. if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true; 168. delete pFileBars; 169. break; 170. } 171. } 172. delete ptr; 173. } 174. 175. return bBarsPrev; 176. } 177. //+------------------------------------------------------------------+ 178. public : 179. //+------------------------------------------------------------------+ 180. C_ConfigService() 181. :C_FileTicks() 182. { 183. m_GlPrivate.AccountHedging = false; 184. m_GlPrivate.ModelLoading = 1; 185. m_GlPrivate.MaxTickVolume = 2000; 186. } 187. //+------------------------------------------------------------------+ 188. inline const bool TypeAccountIsHedging(void) const 189. { 190. return m_GlPrivate.AccountHedging; 191. } 192. //+------------------------------------------------------------------+ 193. bool SetSymbolReplay(const string szFileConfig) 194. { 195. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo) 196. int file; 197. char cError, 198. cStage; 199. string szInfo; 200. bool bBarsPrev; 201. 202. if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) 203. { 204. Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated..."); 205. return false; 206. } 207. Print("Loading data for playback. Please wait...."); 208. cError = cStage = 0; 209. bBarsPrev = false; 210. m_GlPrivate.Line = 1; 211. m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; 212. while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) 213. { 214. switch (GetDefinition(FileReadString(file), szInfo)) 215. { 216. case Transcription_DEFINE: 217. cError = (WhatDefine(szInfo, cStage) ? 0 : 1); 218. break; 219. case Transcription_INFO: 220. if (szInfo != "") switch (cStage) 221. { 222. case 0: 223. cError = 2; 224. break; 225. case 1: 226. if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); 227. (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); 228. break; 229. case 2: 230. if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); 231. (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); 232. break; 233. case 3: 234. if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); 235. (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); 236. break; 237. case 4: 238. if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); 239. (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); 240. break; 241. case 5: 242. if (!Configs(szInfo)) cError = 7; 243. break; 244. } 245. break; 246. }; 247. m_GlPrivate.Line += (cError > 0 ? 0 : 1); 248. } 249. FileClose(file); 250. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick)); 251. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay)); 252. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); 253. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); 254. switch(cError) 255. { 256. case 0: 257. if (GetInfoTicks().nTicks <= 0) 258. { 259. Print("There are no ticks to use. Service is being terminated..."); 260. cError = -1; 261. }else if (!bBarsPrev) FirstBarNULL(); 262. break; 263. case 1 : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break; 264. case 2 : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line); break; 265. default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line); 266. } 267. 268. return (cError == 0 ? !_StopFlag : false); 269. #undef macroFileName 270. } 271. //+------------------------------------------------------------------+ 272. }; 273. //+------------------------------------------------------------------+
Quellcode der Header-Datei C_ConfigService.mqh
Der Code der Klasse C_ConfigService ist wirklich ein Vergnügen, mit ihm zu arbeiten. Das liegt daran, dass es sich um die Art von Code handelt, die nur minimale Änderungen erfordert und es uns ermöglicht, mit sehr wenig Aufwand viel zu erreichen. Dies ist ein seltenes Vorkommnis. Aber kommen wir zum Kern der Sache. Hier leisten alle darunter liegenden Schichten die Schwerstarbeit, damit wir die für die Wiedergabe oder Simulation benötigten Daten laden können. Alles wird entsprechend dem Inhalt der Konfigurationsdatei orchestriert, wie wir sie bereits gezeigt haben.
Das erste, was wir in diesem Code getan haben, war, eine neue Variable zu definieren. Dies ist in Zeile 19 zu sehen. Ihr Name ist recht intuitiv und zeigt deutlich, was wir in den folgenden Schritten zu tun beabsichtigen. Die gleiche Variable wird in Zeile 185 im Klassenkonstruktor initialisiert. Aufgrund dieser Initialisierung wird dieser Standardwert verwendet, wenn in der Konfigurationsdatei kein anderer Wert angegeben ist.
Nun werden Sie sich vielleicht fragen: „Moment mal, warum verwenden Sie nicht die Definition def_MaxTicksVolume aus der Datei Defines.mqh?“ Der Grund dafür ist, dass es diese Definition nicht mehr gibt. Sie wurde entfernt, weil sie nicht mehr notwendig ist. Seit wir mit der Konfigurationsdatei arbeiten, sind wir nicht mehr auf bestimmte fest einprogrammierte Werte angewiesen. Aus diesem Grund wurde def_MaxTicksVolume aus Defines.mqh entfernt. Wenn Sie es behalten möchten, ist das völlig in Ordnung. Seien Sie aber nicht überrascht, wenn in zukünftigen Artikeln, in denen wir Defines.mqh erneut besuchen, diese Definition nicht mehr vorhanden ist.
Verstehen wir nun, warum diese Definition überflüssig wurde. Bei der Entwicklung der Funktion, die es dem Wiedergabesystem ermöglicht, Ticks zu simulieren, wenn es innerhalb eines einminütigen Balkens zu einem Überlauf kommt, hatte ich zunächst nicht definiert, woher diese Informationen kommen sollten. Um den Code nicht mit zu vielen Werten zu überfrachten, habe ich eine Allzweckdefinition erstellt. So wurde def_MaxTicksVolume geboren und in Defines.mqh platziert. Diese Art von Designentscheidung ist üblich, wenn man etwas baut, das sich im Laufe der Zeit weiterentwickeln soll. Sobald wir jedoch die Klasse C_ConfigService erreicht hatten, kam mir die Idee, den Nutzern die Möglichkeit zu geben, diesen Wert anzupassen, ohne das gesamte Projekt neu kompilieren zu müssen.
Die Klassen, die für die harte Arbeit verantwortlich sind, wurden auf jeden Fall bereits eingeführt. Alles, was wir tun mussten, war, ihnen diesen Maximalwert für die Tick zu übermitteln. Mit einer kleinen Codeänderung konnten wir sicherstellen, dass die entsprechenden Klassen einen für die gesamte Anwendung definierten Wert empfangen und verwenden können. Die entsprechenden Methodenaufrufe erscheinen in den Zeilen 157, 160 und 163.
Beachten Sie, dass wir keine wirklich neue Logik hinzugefügt haben, sondern nur kleine Anpassungen vorgenommen haben, um die notwendige Unterstützung für diese Konfiguration zu ermöglichen. Dies gibt uns jedoch die Kontrolle über die maximale Anzahl von Ticks, die innerhalb eines einzelnen einminütigen Balkens simuliert werden oder existieren können.
Das ist der eigentliche Grund, warum alles in diese Klasse gesteckt wurde. Damit kann der Nutzer die maximal zulässige Anzahl von Ticks innerhalb eines einminütigen Balkens festlegen. Wenn Sie diesen Code aus dem Zusammenhang reißen, könnten Sie annehmen, dass diese Aufgabe kompliziert und umfangreich ist. Ist es aber nicht. Es ist eigentlich einfach, leicht und schnell, die Möglichkeit zu implementieren, dass ein Nutzer die maximale Anzahl von Ticks angeben kann.
Zunächst fügen wir den Konfigurationsschlüssel hinzu. Dazu wird eine weitere Zeichenkette in die Liste der für die Konfigurationsparameter verwendeten Schlüssel aufgenommen. Dieses Array ist in Zeile 63 definiert. Und jetzt aufgepasst. Ich habe das schon einmal erklärt, aber es lohnt sich, es zu wiederholen. Wenn Sie einen neuen Schlüssel hinzufügen, schreiben Sie ihn immer in Großbuchstaben. Es spielt keine Rolle, was der Schlüssel ist. Achten Sie nur darauf, dass er in Großbuchstaben geschrieben ist. Unser neuer Schlüssel wird in Zeile 70 definiert. Nun gibt es einen weiteren Trick: In Zeile 75 verwenden wir eine Funktion der MQL5-Bibliothek, um die Schlüssel-Wert-Paare aufzuteilen. Das Gleichheitszeichen wird als Trennzeichen verwendet. Was also vor dem Gleichheitszeichen steht, gilt als Schlüssel, und was danach kommt, ist der diesem Schlüssel zugewiesene Wert.
Der nächste wichtige Punkt ist in Zeile 79, wo wir nach dem Schlüssel im Array suchen, um seinen Index zu finden. Wenn Sie also die Reihenfolge der Schlüssel im Array ändern, müssen Sie auch den entsprechenden Index aktualisieren, der später verwendet wird. Das ist nicht schwierig, man muss nur gut aufpassen.
In unserem Fall ist der Index für die neue Einstellung 6. In Zeile 107 legen wir also fest, wie diese Einstellung angewendet werden soll. Da wir einen Integer-Wert erwarten, verwenden wir eine andere Funktion der MQL5-Bibliothek, um die Konvertierung durchzuführen. Auf diese Weise kann der Nutzer einen Wert festlegen, der als maximale Tickanzahl für einen einminütigen Balken verwendet werden soll.
Wichtiger Hinweis: In Zeile 108, wo wir den Wert aus der Konfigurationsdatei in eine brauchbare Ganzzahl umwandeln, führen wir keine Validierungsprüfungen durch, um sicherzustellen, dass der Wert innerhalb akzeptabler Parameter liegt. Die einzige Absicherung besteht darin, dass der Wert positiv ist. Ist der Wert anderweitig inkonsistent oder ungültig, kann der Simulationsprozess fehlschlagen.
Abschließende Schlussfolgerungen
Bevor ich diesen Artikel abschließe, möchte ich Sie an etwas erinnern, das im vorherigen Artikel erwähnt wurde. Ich spreche von dem Video, das dort enthalten war. Obwohl der Schwerpunkt dieses Artikels auf der Implementierung eines Tick-Limit-Systems für die Replay-Funktionalität lag, möchte ich betonen, dass die in diesem Video gezeigten Fehler nicht vergessen wurden.
Diese Fehler sind zwar nicht besonders kritisch, da sie die Anwendungsstabilität nicht beeinträchtigen oder die Plattform zum Absturz bringen, aber sie sind vorhanden. Insbesondere werden einige grafische Objekte nicht richtig entfernt, wenn das Chart geschlossen wird. Obwohl dies nur unter ganz bestimmten Umständen auftritt, arbeiten wir bereits an einer Lösung. Sobald das Problem gelöst ist, werde ich einen Folgeartikel veröffentlichen, in dem ich erkläre, wie dieses Problem gelöst wurde.
Zum Abschluss dieses Artikels möchte ich Sie einladen, sich das unten eingebettete kurze Video anzusehen. Sie zeigt, wie das System derzeit funktioniert, insbesondere wie es sich verhält, je nachdem, ob die Tickgrenze in der Konfigurationsdatei definiert ist oder nicht.
Das Video ist kurz, hebt aber wirkungsvoll den deutlichen Unterschied zwischen der Verwendung simulierter Daten und echter Tickdaten hervor. Sie werden sehen, dass bei der Random-Walk-Simulation, die wir zum Auffüllen der Ticks verwenden, die resultierende Entwicklung ganz anders aussieht als die tatsächlichen Tick-Daten, die von echten Trades erzeugt werden.
Ein letzter Punkt, den ich ansprechen möchte, betrifft ein anderes Thema. Dieses Problem ist geringfügig und eher lästig als schädlich. Sie können es im Video sehen. Gelegentlich pausiert der Wiederholungs-/Simulationsdienst zufällig, sodass Sie die Wiedergabe erneut starten müssen.
Auch wenn dieses Problem relativ harmlos ist, muss es doch schnell gelöst werden. Im nächsten Artikel werden wir diesen Fehler beheben und dafür sorgen, dass der Replay/Simulator nicht unerwartet in den Pausenmodus wechselt. Wir werden auch damit beginnen, einige Funktionen wieder zu aktivieren, die noch vorübergehend im Dienst deaktiviert sind.
Demo-Video
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12240





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.