
Entwicklung eines Replay-Systems (Teil 66): Abspielen des Dienstes (VII)
Einführung
In diesem Artikel werden wir zunächst etwas anders vorgehen. Aber lassen Sie uns zunächst kurz rekapitulieren, was im vorherigen Artikel „Entwicklung eines Replay-Systems (Teil 65): Abspielen des Dienstes (VI)“ behandelt wurde. Dort haben wir uns mit dem Problem der Prozentanzeige des Mauszeigers befasst. Zuvor wurde bei der Wiedergabe oder Simulation mit diesem Indikator ein falscher Wert angezeigt. Darüber hinaus haben wir ein System implementiert, das ein schnelles Vorspulen ermöglicht, sodass wir zu einem bestimmten Punkt springen können, ohne mehrere Minuten oder in manchen Fällen sogar Stunden warten zu müssen. Bevor Sie jedoch mit diesem Artikel fortfahren, möchte ich Sie bitten, sich das folgende Video anzusehen. Dies wird Ihnen helfen, die Punkte besser zu verstehen, die ich am Anfang dieses Artikels erläutern werde.
Etwas zum Ansehen...
Ich denke, das Video spricht für sich selbst. Dennoch möchte ich ein wenig erklären, was hier geschieht. Diese Wiedergabe/Simulation beruht in hohem Maße auf der Verwendung eines angepassten Handelsinstruments. So viel ist klar, und ich glaube, in diesem Punkt sind sich alle einig. Es gibt jedoch auch Fehler, die nicht durch die Programmierung verursacht werden, die wir vornehmen. Diese Misserfolge haben unterschiedliche Ursachen. Einige sind ungewöhnlich, andere treten fast zufällig auf. Wenn die Ursache des Problems ermittelt werden kann, sind wir in der Lage, es zu beheben. Wenn das Problem jedoch zufällig auftritt, ändert sich die Situation, und wir müssen lernen, damit umzugehen.
Zum Zeitpunkt der Erstellung dieses Artikels ist die neueste Version der MetaTrader 5-Plattform genau die, die im Video gezeigt wird. Ich bin mir nicht sicher, wo das Problem liegt, aber es existiert eindeutig, und Sie sind wahrscheinlich schon darauf gestoßen oder werden in Zukunft darauf stoßen. Bis das im Video gezeigte Problem endgültig behoben ist, müssen Sie sich darauf einstellen. Mit anderen Worten: Bevor Sie die Wiedergabe oder Simulation laden, konfigurieren Sie sie genau so, wie Sie sie während der Sitzung verwenden wollen. Auf diese Weise vermeiden Sie, dass Sie den Zeitrahmen des Charts ändern müssen. Genau dann verliert MetaTrader 5 seltsamerweise den Kontakt zu den Ticks oder Balken im nutzerdefinierten Asset. Wenn Sie diesen Artikel lesen, ist das im Video gezeigte Problem vielleicht schon behoben. Wenn ja, großartig. Wenn nicht, befolgen Sie die Ratschläge, die ich soeben gegeben habe, um eine unangenehme Erfahrung bei der Verwendung der Wiedergabe/Simulation zu vermeiden.
Damit können wir uns nun der weiteren Arbeit in diesem Artikel widmen. Im vorangegangenen Artikel war noch eine Frage offen geblieben. Da sie unmittelbarer ist, werden wir mit ihr beginnen.
Implementierung der verbleibenden Zeit bis zum nächsten Balken
Ein Merkmal, das viele Finanzmarkthändler schätzen und häufig beobachten, ist die verbleibende Zeit bis zum Beginn des nächsten Balkens. Auf den ersten Blick mag dies trivial oder wie eine Zeitverschwendung erscheinen. Es gibt jedoch operative Modelle, bei denen diese Information von entscheidender Bedeutung ist, da die Händler ihre Aufträge oft nur wenige Sekunden vor Beginn des nächsten Balkens erteilen.
In bestimmten Zeitrahmen oder einfach durch die Erfahrung des Händlers entwickeln sie ein intuitives Gefühl dafür, wie viel Zeit noch bleibt. Händler, die gerade erst anfangen, verfügen jedoch noch nicht über diese Fähigkeit. Daher ist es notwendig, ihnen eine Möglichkeit zu bieten, sich darüber zu informieren. Auf den Live-Märkten ist dies recht einfach zu bewerkstelligen, da wir immer mit dem Handelsserver verbunden sind (über ein Demo- oder Echtgeldkonto). Die Tatsache, dass sich die Arbeitsabläufe ständig weiterentwickeln, erleichtert die Umsetzung dieses Hinweises zusätzlich, da die Zeit nicht stehen bleibt. Aber gerade dieser letzte Aspekt verkompliziert die Dinge ein wenig, wenn man die Wiedergabe/Simulation anwendet. Die Tatsache, dass der Nutzer die Anwendung auf unbestimmte Zeit anhalten oder sogar an eine Stelle gelangen kann, an der sich die Balken am Anfang oder fast am Ende befinden, macht die Sache noch viel komplizierter. Es wird so schwierig, dass wir einen echten Jonglierakt brauchen, um zu vermeiden, dass eine Frankenstein-Lösung entsteht, nur um die verbleibende Zeit des Balkens anzuzeigen.
Bevor wir fortfahren, sollten wir uns ansehen, was wir bereits haben. Im vorherigen Artikel haben wir den Code des Mausindikators so geändert, dass wir jetzt eine andere Version von OnCalculate verwenden. Diese Änderung erweist sich in diesem Fall sogar als sehr nützlich. Der Grund dafür ist, dass wir dadurch ein Array erhalten, in dem der Zeitwert gespeichert wird. Mit einer kleinen Änderung erhalten wir das unten abgebildete Fragment:44. //+------------------------------------------------------------------+ 45. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 46. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 47. const long& volume[], const int& spread[]) 48. { 49. Print(TimeToString(time[rates_total - 1], TIME_DATE | TIME_SECONDS)); // To Testing ... 50. GL_PriceClose = close[rates_total - 1]; 51. m_posBuff = rates_total; 52. (*Study).Update(m_Status); 53. 54. return rates_total; 55. } 56. //+------------------------------------------------------------------+
Das Quellcodefragment aus Mouse Study.mq5
Beachten Sie, dass eine neue Zeile hinzugefügt wurde. In Zeile 49 können wir den letzten im Zeitfeld gefundenen Wert anzeigen. Achten Sie jetzt auf ein wichtiges Detail. Der Wert in diesem Array wurde angepasst, um sicherzustellen, dass der Ein-Minuten-Balken richtig konstruiert wird, d. h. innerhalb des Ein-Minuten-Fensters.
Wenn Sie die Wiedergabe/Simulation mit diesem neuen Code im Mausindikator ausführen, werden Sie in der Toolbox Informationen sehen, die denen in der folgenden Animation sehr ähnlich sind:
Sie können sehr gut beobachten, dass der Mauszeiger jedes Mal, wenn die Funktion OnCalculate aufgerufen wird, Informationen ausgibt, aber der Wert in Sekunden ändert sich nicht. Wenn man genau nachdenkt, könnte man sich also fragen: Was wäre, wenn wir in diese Informationen einen Wert in Sekunden einfügen würden? Könnten wir dann berechnen, wie viel Zeit bis zum Erscheinen des nächsten Balkens verbleibt? Wenn Sie so gedacht haben, dann haben Sie verstanden, was genau wir tun müssen, um diese Funktion zu schaffen. Lassen Sie uns also diese Idee testen. Dazu müssen wir eine kleine Änderung in der Header-Datei C_Replay.mqh vornehmen. Der entsprechende Codeausschnitt ist unten zu sehen.
69. //+------------------------------------------------------------------+ 70. inline void CreateBarInReplay(bool bViewTick) 71. { 72. bool bNew; 73. double dSpread; 74. int iRand = rand(); 75. 76. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 77. { 78. m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay]; 79. if (m_MemoryData.ModePlot == PRICE_EXCHANGE) 80. { 81. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 82. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 83. { 84. m_Infos.tick[0].ask = m_Infos.tick[0].last; 85. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 86. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 87. { 88. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 89. m_Infos.tick[0].bid = m_Infos.tick[0].last; 90. } 91. } 92. if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 93. m_Infos.Rate[0].time = m_MemoryData.Info[m_Infos.CountReplay].time; //< To Testing... 94. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 95. } 96. m_Infos.CountReplay++; 97. } 98. //+------------------------------------------------------------------+
Das Quellcodefragment aus C_Replay.mqh
Schauen Sie sich an, wie interessant und einfach es zu implementieren ist. Wir mussten lediglich Zeile 93 in den Codeausschnitt einfügen, um die Angabe des Wertes in Sekunden zu ermöglichen. Einfacher kann es nicht sein. Aber wird das wirklich funktionieren? Denken Sie daran, dass in der Dokumentation der Funktion CustomRatesUpdate angegeben ist, dass der Zeitwert, für den die Kurse erstellt werden sollen, innerhalb des Ein-Minuten-Fensters liegen muss. Man könnte nun denken, dass dies kein Problem ist, da wir das Fenster nicht ändern, sondern nur den Sekundenwert hinzufügen, und höchstwahrscheinlich wird dies von der Funktion ignoriert, aber an den Mauszeiger weitergegeben, sodass es in der Funktion OnCalculate bemerkt werden kann. In mancher Hinsicht stimme ich mit Ihrer Theorie überein, aber solange wir die Hypothese nicht tatsächlich testen, bleibt sie nur eine theoretische Hypothese. Also haben wir den Code kompiliert, und das Ergebnis können Sie im folgenden Video sehen:
Eine kurze Demonstration
Aber was war das? Was um Himmels willen ist passiert? Um ehrlich zu sein, hatte ich nicht damit gerechnet, dass dies passieren würde. Ich dachte, es würde funktionieren. In der Tat ist die Idee nicht ganz falsch: Sie haben wahrscheinlich bemerkt, dass der Mauszeiger uns gab, was wir wollten. Allerdings ist der Inhalt der Chart ... Nun, es ist besser, einen anderen Weg zu finden, um das Problem anzugehen. Aber Sie sollten bemerkt haben, dass wir immer noch einige interessante Dinge tun können. In der Tat habe ich in der Vergangenheit mit der gleichen Idee einen Indikator erstellt. Sie können diesen Indikator, zumindest die offene Version davon, in dem Artikel Einen handelnden Expert Advisor von Grund auf neu entwickeln (Teil 13): Times and Trade (II). Diese Version ist inzwischen überholt, aber sie hilft zu verstehen, was hier passiert ist. Und bevor jemand fragt: NEIN, ich werde die aktuelle Version dieses Indikators nicht verkaufen, verleihen oder zeigen. Sie ist nur für den persönlichen Gebrauch bestimmt.
Na gut. Ich glaube, Sie verstehen jetzt, wie die Dinge funktionieren und warum etwas, das einfach zu sein scheint, in Wirklichkeit eine kompliziertere Manipulation erfordert, um die gewünschten Ergebnisse zu erzielen.
Sie werden vielleicht denken, dass wir eine neue Vorgehensweise entwickeln müssen. Die Lösung bestand jedoch bereits in der Version, die globale Terminalvariablen verwendete. Wir werden jedoch nicht mehr dieselbe Lösung verwenden. Wir müssen kreativ sein und wirklich verstehen, wie Programme Informationen untereinander austauschen. Wir tun dies bereits, indem wir den Kontrollindikator verwenden, um den Dienst zu verwalten, und der Dienst meldet unsere Position an den Indikator zurück. Der gleiche Ansatz muss hier angewendet werden, allerdings mit einem wichtigen Detail: Wir können nicht jede Sekunde - oder schlimmer noch, jeden Tick - nutzerdefinierte Ereignisse auslösen, um den Mauszeiger über unsere aktuelle Zeitposition zu informieren. In diesem Fall würde sich die Leistung der Anwendung verschlechtern und wir wären gezwungen, die maximale Anzahl der Ticks pro Minute weiter zu reduzieren. Das möchte ich auf jeden Fall vermeiden. Wir benötigen jedoch eine gewisse Synchronisierung zwischen den Informationen in den Ticks und den Informationen, die sich auf die Zeit des aktuellen Balkens beziehen. Dies ist die Hauptsorge. Lassen Sie uns also die Änderungen in den Codeausschnitten rückgängig machen und uns darauf konzentrieren, eine andere Lösung zu finden, die das Problem tatsächlich löst. Aber das wird Thema des nächsten Abschnitts sein.
Über eine andere Art der Anzeige der verbleibenden Zeit nachdenken
Im Gegensatz zu dem, was passiert, wenn wir mit einem Live-Handelsserver verbunden sind, ist die Handhabung der Umgebung von Wiedergabe/Simulation weitaus komplizierter, vor allem wenn man bedenkt, wie ich es angehen möchte. Ich habe nicht die Absicht, globale Terminalvariablen zur Übertragung von Informationen zu verwenden. Wenn wir mit einem Live-Server verbunden sind, müssen alle Uhren mindestens auf die Sekunde genau synchronisiert werden. Sie verstehen vielleicht nicht ganz, wie sehr sich dies auf alles auswirkt, aber um Überprüfungen auf der Grundlage der verbleibenden Sekunden durchzuführen, müssen Sie nur wissen, wie viele Sekunden bis zu einem bestimmten Zeitpunkt noch übrig sind. Wenn eine Verbindung zu einem Live-Server besteht, ist dies sehr einfach, da wir die interne Uhr des Systems verwenden können, um diese Berechnung durchzuführen und die verbleibende Zeit zu ermitteln.
Bei der Verwendung der Umgebung der Wiedergabe/Simulation wird alles jedoch viel komplexer. Der Grund für diese erhöhte Komplexität liegt genau in der Natur dessen, was wir tun: Nachspielen oder Simulieren. Aber wie kann eine einfache Wiederholung oder Simulation es so viel schwieriger machen, die verbleibende Zeit bis zum nächsten Balken zu erkennen? Das macht keinen Sinn. Bei oberflächlicher Betrachtung ergibt es keinen Sinn, dass die Wiederholung oder Simulation von Daten die Bestimmung der verbleibenden Zeit bis zum Beginn eines neuen Balkens so sehr erschwert. Aber das ganze Thema dreht sich um einen zentralen Punkt: die Uhr richtig ablesen zu können.
Nehmen wir an, dass der erste einminütige Balken, der auf dem Chart dargestellt werden soll, tatsächlich eine ganze Minute braucht, um dargestellt zu werden. Na gut. Das ist die erste Voraussetzung, um das Problem zu verstehen: Der Balken wird über eine Minute hinweg vollständig gezogen. Wenn Sie nun die Anwendung so starten, dass die Aufzeichnung genau bei Sekunde Null beginnt, dann wird jede Systemminute einen neuen Balken bringen. Perfekt, Problem gelöst. Das liegt daran, dass die Synchronisation perfekt ist. Wir bräuchten also nur die Systemuhr zu überprüfen und wüssten genau, wie viel Zeit bis zum Beginn des nächsten Balkens verbleibt.
Allerdings wird Ihr erster Balken selten, wenn überhaupt, genau eine Minute lang sein. Ein weiteres Problem ist, dass Sie es kaum schaffen werden, die Anwendung genau zur Null-Sekunden-Marke der Systemuhr zu starten. Aber in diesem Fall könnten wir eine kleine Anpassung oder einen Test implementieren, der die Wiedergabe/den Simulator zwingt, so zu starten, dass eine Synchronisation mit der Systemuhr erfolgt. Diese Lösung ist praktikabel und einleuchtend. Wenn Sie jedoch signalisieren, dass die Anwendung bereit ist und ein Balken gezeichnet werden kann, müssen Sie eine gewisse Zeit warten, bevor die Synchronisierung tatsächlich stattfinden kann. Diese Wartezeit wäre nicht besonders lang: höchstens 59 Sekunden im schlimmsten Fall. Dies ist für eine Anwendung zum persönlichen Gebrauch akzeptabel. Aber selbst bei der persönlichen Nutzung wird man früher oder später müde, jedes Mal bis zu 59 Sekunden zu warten, wenn man auf die Wiedergabe der Anwendung drückt.
Auch wenn die Synchronisation der Wiedergabe/Simulation mit der Systemuhr unsere Aufgabe, die verbleibende Zeit des Balkens zu verfolgen, vereinfacht, ist sie für den Nutzer etwas irritierend. Der schlimmste Fall ist, wenn Sie die Simulation oder die Wiedergabe mittendrin unterbrechen. In diesem Fall müssten Sie erneut bis zu 59 Sekunden warten, bevor die Anwendung die Charterstellung fortsetzen könnte, da sie auf die erneute Ausrichtung der Systemuhr warten müsste. Praktisch gesehen scheint dies also nicht die beste Lösung zu sein.
Aber vielleicht können wir einen Mittelweg finden. Etwas, das es uns ermöglicht, eine leichte Anpassung vorzunehmen, um mit der Systemuhr synchron zu bleiben, und gleichzeitig zu vermeiden, dass wir bis zu 59 Sekunden warten müssen, bis die Systemuhr die Balkenaufzeichnung „entsperrt“. Auf diese Weise können wir immer noch die verbleibende Zeit in der Bar bestimmen. Jetzt wird es ein bisschen interessanter.
Ich gebe diese ausführliche Erklärung nicht, um die Dinge übermäßig kompliziert zu machen oder Sie, lieber Leser, zu verwirren, sondern um zu zeigen, dass wir zuerst nachdenken müssen, bevor wir uns hinsetzen und Code schreiben. Wir müssen die Möglichkeiten analysieren und die Schwierigkeiten bei der Umsetzung abwägen. Viele Menschen glauben, dass es beim Programmieren nur darum geht, einen Haufen Code in eine Datei zu schreiben und damit fertig zu sein. Aber in Wahrheit ist die Codierung nur ein kleiner Teil des Prozesses. Der wichtigste Teil liegt in der Planung und Analyse der Lösung. Das haben wir bis jetzt getan. Mit diesem Gedankengang können wir uns eine solide Vorstellung davon machen, was getan werden muss. Es werden jedoch noch einige Änderungen erforderlich sein. Wir müssen unsere Organisation ein wenig verbessern. Aber um wirklich zu verstehen, was wir tun werden, sollten wir zu einem neuen Abschnitt übergehen.
Implementierung der Basisversion für die Anzeige der Restlaufzeit
In diesem Abschnitt werden wir etwas tun, das meiner Meinung nach relativ einfach, aber auch ziemlich gewagt ist. Wir werden eine Möglichkeit implementieren, um die aktuelle Balkenzeitinformation schnell an den Mauszeiger weiterzugeben. Zunächst werden wir einige Änderungen am Code des Mauszeigers vornehmen. Diese Änderungen erfordern das Hinzufügen von etwas mehr Code, wie im folgenden Ausschnitt gezeigt:
12. //+------------------------------------------------------------------+ 13. double GL_PriceClose; 14. datetime GL_TimeAdjust; 15. //+------------------------------------------------------------------+ 16. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 17. //+------------------------------------------------------------------+ 18. C_Study *Study = NULL; 19. //+------------------------------------------------------------------+ 20. input color user02 = clrBlack; //Price Line 21. input color user03 = clrPaleGreen; //Positive Study 22. input color user04 = clrLightCoral; //Negative Study 23. //+------------------------------------------------------------------+ 24. C_Study::eStatusMarket m_Status; 25. int m_posBuff = 0; 26. double m_Buff[]; 27. //+------------------------------------------------------------------+ 28. int OnInit() 29. { 30. ResetLastError(); 31. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 32. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 33. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 34. { 35. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 36. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 37. m_Status = C_Study::eCloseMarket; 38. }else 39. m_Status = C_Study::eInReplay; 40. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 41. ArrayInitialize(m_Buff, EMPTY_VALUE); 42. 43. return INIT_SUCCEEDED; 44. } 45. //+------------------------------------------------------------------+ 46. int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], 47. const double& high[], const double& low[], const double& close[], const long& tick_volume[], 48. const long& volume[], const int& spread[]) 49. { 50. GL_PriceClose = close[rates_total - 1]; 51. GL_TimeAdjust = (spread[rates_total - 1] < 60 ? spread[rates_total - 1] : 0); 52. m_posBuff = rates_total; 53. (*Study).Update(m_Status); 54. 55. return rates_total; 56. } 57. //+------------------------------------------------------------------+
Das Quellcodefragment aus Mouse Study.mq5
Wenn Sie den oben gezeigten Codeausschnitt mit dem aktuellen Quellcode des Mausindikators vergleichen, werden Sie feststellen, dass dem Code die Zeile 14 hinzugefügt wurde. Mit anderen Worten, wir haben jetzt eine neue globale Variable innerhalb des Indikatormoduls. Ich habe nicht die Absicht, neue globale Variablen zu diesem Mauszeigermodul hinzuzufügen. Aber dieser hier ist etwas Besonderes, und zwar aus einem Grund, den Sie bald verstehen werden. In jedem Fall wird dieser Variablen in Zeile 51 ein Wert zugewiesen. Dieser Wert wird über den Spread weitergegeben. Darin liegt ein potenzielles Risiko, das gleichzeitig auf etwas sehr Hilfreiches hinweist. Unabhängig davon wird diese globale Variable nur auf eine ganz bestimmte Weise verwendet. Um dies besser zu verstehen, sehen wir uns die aktualisierte Datei C_Study.mqh an, die unten abgebildet ist:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. //+------------------------------------------------------------------+ 008. class C_Study : public C_Mouse 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st00 013. { 014. eStatusMarket Status; 015. MqlRates Rate; 016. string szInfo, 017. szBtn1, 018. szBtn2, 019. szBtn3; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. datetime TimeDevice; 025. }m_Info; 026. //+------------------------------------------------------------------+ 027. void Draw(void) 028. { 029. double v1; 030. 031. if (m_Info.bvT) 032. { 033. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 034. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 035. } 036. if (m_Info.bvD) 037. { 038. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 040. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 041. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 042. } 043. if (m_Info.bvP) 044. { 045. v1 = NormalizeDouble((((GL_PriceClose - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 047. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 048. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 049. } 050. } 051. //+------------------------------------------------------------------+ 052. inline void CreateObjInfo(EnumEvents arg) 053. { 054. switch (arg) 055. { 056. case evShowBarTime: 057. C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 058. m_Info.bvT = true; 059. break; 060. case evShowDailyVar: 061. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 062. m_Info.bvD = true; 063. break; 064. case evShowPriceVar: 065. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 066. m_Info.bvP = true; 067. break; 068. } 069. } 070. //+------------------------------------------------------------------+ 071. inline void RemoveObjInfo(EnumEvents arg) 072. { 073. string sz; 074. 075. switch (arg) 076. { 077. case evHideBarTime: 078. sz = m_Info.szBtn1; 079. m_Info.bvT = false; 080. break; 081. case evHideDailyVar: 082. sz = m_Info.szBtn2; 083. m_Info.bvD = false; 084. break; 085. case evHidePriceVar: 086. sz = m_Info.szBtn3; 087. m_Info.bvP = false; 088. break; 089. } 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. ObjectDelete(GetInfoTerminal().ID, sz); 092. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 093. } 094. //+------------------------------------------------------------------+ 095. public : 096. //+------------------------------------------------------------------+ 097. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 098. :C_Mouse(IdParam, szShortName, corH, corP, corN) 099. { 100. if (_LastError != ERR_SUCCESS) return; 101. ZeroMemory(m_Info); 102. m_Info.Status = eCloseMarket; 103. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 104. m_Info.corP = corP; 105. m_Info.corN = corN; 106. CreateObjInfo(evShowBarTime); 107. CreateObjInfo(evShowDailyVar); 108. CreateObjInfo(evShowPriceVar); 109. } 110. //+------------------------------------------------------------------+ 111. void Update(const eStatusMarket arg) 112. { 113. int i0; 114. datetime dt; 115. 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? m_Info.TimeDevice + GL_TimeAdjust : TimeCurrent()); 125. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 126. m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 127. break; 128. case eAuction : 129. m_Info.szInfo = "Auction"; 130. break; 131. default : 132. m_Info.szInfo = "ERROR"; 133. } 134. Draw(); 135. } 136. //+------------------------------------------------------------------+ 137. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 138. { 139. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 140. switch (id) 141. { 142. case CHARTEVENT_CUSTOM + evHideBarTime: 143. RemoveObjInfo(evHideBarTime); 144. break; 145. case CHARTEVENT_CUSTOM + evShowBarTime: 146. CreateObjInfo(evShowBarTime); 147. break; 148. case CHARTEVENT_CUSTOM + evHideDailyVar: 149. RemoveObjInfo(evHideDailyVar); 150. break; 151. case CHARTEVENT_CUSTOM + evShowDailyVar: 152. CreateObjInfo(evShowDailyVar); 153. break; 154. case CHARTEVENT_CUSTOM + evHidePriceVar: 155. RemoveObjInfo(evHidePriceVar); 156. break; 157. case CHARTEVENT_CUSTOM + evShowPriceVar: 158. CreateObjInfo(evShowPriceVar); 159. break; 160. case (CHARTEVENT_CUSTOM + evSetServerTime): 161. m_Info.TimeDevice = (datetime)lparam; 162. break; 163. case CHARTEVENT_MOUSE_MOVE: 164. Draw(); 165. break; 166. } 167. ChartRedraw(GetInfoTerminal().ID); 168. } 169. //+------------------------------------------------------------------+ 170. }; 171. //+------------------------------------------------------------------+ 172. #undef def_ExpansionPrefix 173. #undef def_MousePrefixName 174. //+------------------------------------------------------------------+
Der Quellcode von C_Study.mqh
OK. Sehr gut. Passen Sie gut auf, denn was jetzt erklärt wird, ist für das Verständnis der allgemeinen Funktionsweise äußerst wichtig. Sie werden feststellen, dass der Code in der Header-Datei anders aussieht. Allerdings gibt es hier nur zwei wirklich wichtige Teile. Der erste befindet sich in Zeile 160, wo wir ein nutzerdefiniertes Ereignis behandeln. Beachten Sie, dass wir in Zeile 161 einen von diesem Ereignis übergebenen Wert in einer privaten Variablen innerhalb der Klasse speichern. Das ist der erste Punkt. Schauen wir uns nun an, wo die eigentliche „Magie“ stattfindet. Blättern wir dazu ein wenig nach oben zu Zeile 111, wo die Update-Prozedur definiert ist. Dieses Verfahren ist für die Erstellung der Informationen zuständig, die wir später anzeigen werden. Und jetzt aufgepasst. In Zeile 123 wird die Anzahl der Sekunden innerhalb des aktuellen Zeitrahmens des nutzerdefinierten Symbolcharts erfasst. Dann führen wir in Zeile 124 eine kleine Berechnung durch oder verwenden alternativ den vom MetaTrader 5 bereitgestellten Wert. Ob wir die Berechnung durchführen oder den angegebenen Wert verwenden, hängt vom Status des Indikators ab. Wenn der Mauszeiger auf einem Wiedergabesymbol verwendet wird, führen wir die Berechnung durch. Andernfalls wird der vom MetaTrader 5 gelieferte Wert verwendet.
Beachten Sie, dass diese Berechnung den Wert berücksichtigt, den wir aus dem Mausindikator-Codefragment erfasst haben, sowie einen weiteren Wert, der von der Wiedergabe/Simulation bereitgestellt wird. Wir werden später sehen, wie dieser zweite Wert an den Mauszeiger übergeben wird.
In jedem Fall handelt es sich um einen Datenpunkt, der sich im Laufe der Zeit verändern wird. Wir benötigen jedoch noch einen weiteren Wert, der direkt darunter, in Zeile 125, berechnet wird. In dieser Zeile wird der genaue Zeitpunkt bestimmt, zu dem ein neuer Balken im Chart erzeugt wird. In Zeile 126 führen wir eine letzte Berechnung durch, um die verbleibende Zeit bis zum Schließen des aktuellen Balkens zu ermitteln und dem Nutzer anzuzeigen.
Das gesamte System wird ordnungsgemäß funktionieren, da der Handelsserver dafür verantwortlich ist, uns mitzuteilen, wie viel Zeit noch verbleibt, bis ein neuer Balken gebildet wird. Oder, im Falle der Verwendung von Wiedergabe/Simulation, ist die verantwortliche Instanz der Dienst, der die Balken aktualisiert, sodass MetaTrader 5 sie darstellen kann.
Sehr gut. Sie haben wahrscheinlich bemerkt, dass wir den Dienstcode aufgrund der obigen Erläuterungen ändern müssen. Was jedoch tatsächlich aktualisiert werden muss, ist der Code in der Datei C_Replay.mqh. Aber bevor wir das tun, müssen wir eine kleine Änderung in zwei Bereichen des Dienstcodes vornehmen. Als erstes müssen Sie der Header-Datei Macros.mqh einige Dinge hinzufügen. Und so geht's:
1. //+------------------------------------------------------------------+ 2. #property copyright "Daniel Jose" 3. //+------------------------------------------------------------------+ 4. #define macroRemoveSec(A) (A - (A % 60)) 5. #define macroGetDate(A) (A - (A % 86400)) 6. #define macroGetSec(A) (A - (A - (A % 60))) 7. //+------------------------------------------------------------------+
Der Quellcode von Macros.mqh
Da alles sehr einfach ist, werde ich nicht ins Detail gehen. Sobald dies geschehen ist, werden wir die Header-Datei C_FilesTick.mqh ändern. Die Änderung ist von besonderer Art und wird im folgenden Fragment dargestellt:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_FileBars.mqh" 05. #include "C_Simulation.mqh" 06. #include "..\..\Auxiliar\Macros.mqh" 07. //+------------------------------------------------------------------+ 08. //#define macroRemoveSec(A) (A - (A % 60)) 09. #define def_MaxSizeArray 16777216 // 16 Mbytes 10. //+------------------------------------------------------------------+ 11. class C_FileTicks 12. { 13. protected: 14. enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX};
Das Quellcodefragment aus C_FilesTick.mqh
Beachten Sie, dass in diesem Fall Zeile 06 hinzugefügt wurde und Zeile 08, die wir hervorgehoben haben, aus dem Code entfernt werden sollte. Dies geschieht, weil die Header-Datei Macros.mqh nun den Code enthält, der zuvor in der hervorgehobenen Zeile stand. Sehr gut. Nachdem diese Änderungen vorgenommen wurden, können wir mit der Datei C_Replay.mqh arbeiten. Der neue Code für diese Datei wird im Folgenden vollständig dargestellt.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. #include "C_Controls.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 008. #resource "\\" + def_IndicatorControl 009. //+------------------------------------------------------------------+ 010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != "")) 011. //+------------------------------------------------------------------+ 012. #define def_ShortNameIndControl "Market Replay Control" 013. #define def_MaxSlider (def_MaxPosSlider + 1) 014. //+------------------------------------------------------------------+ 015. class C_Replay : public C_ConfigService 016. { 017. private : 018. struct st00 019. { 020. C_Controls::eObjectControl Mode; 021. uCast_Double Memory; 022. ushort Position; 023. int Handle; 024. }m_IndControl; 025. struct st01 026. { 027. long IdReplay; 028. int CountReplay; 029. double PointsPerTick; 030. MqlTick tick[1]; 031. MqlRates Rate[1]; 032. }m_Infos; 033. stInfoTicks m_MemoryData; 034. //+------------------------------------------------------------------+ 035. inline bool MsgError(string sz0) { Print(sz0); return false; } 036. //+------------------------------------------------------------------+ 037. inline void UpdateIndicatorControl(void) 038. { 039. double Buff[]; 040. 041. if (m_IndControl.Handle == INVALID_HANDLE) return; 042. if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position) 043. { 044. if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1) 045. m_IndControl.Memory.dValue = Buff[0]; 046. if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay) 047. m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition]; 048. }else 049. { 050. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position; 051. m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode; 052. m_IndControl.Memory._8b[7] = 'D'; 053. m_IndControl.Memory._8b[6] = 'M'; 054. EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, ""); 055. } 056. } 057. //+------------------------------------------------------------------+ 058. void SweepAndCloseChart(void) 059. { 060. long id; 061. 062. if ((id = ChartFirst()) > 0) do 063. { 064. if (ChartSymbol(id) == def_SymbolReplay) 065. ChartClose(id); 066. }while ((id = ChartNext(id)) > 0); 067. } 068. //+------------------------------------------------------------------+ 069. inline void CreateBarInReplay(bool bViewTick) 070. { 071. bool bNew; 072. double dSpread; 073. int iRand = rand(); 074. 075. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 076. { 077. m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay]; 078. if (m_MemoryData.ModePlot == PRICE_EXCHANGE) 079. { 080. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 081. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 082. { 083. m_Infos.tick[0].ask = m_Infos.tick[0].last; 084. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 085. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 086. { 087. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 088. m_Infos.tick[0].bid = m_Infos.tick[0].last; 089. } 090. } 091. if (bViewTick) 092. { 093. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 094. if (bNew) EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)m_Infos.Rate[0].time, 0, ""); 095. } 096. m_Infos.Rate[0].spread = (int)macroGetSec(m_MemoryData.Info[m_Infos.CountReplay].time); 097. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 098. } 099. m_Infos.CountReplay++; 100. } 101. //+------------------------------------------------------------------+ 102. void AdjustViewDetails(void) 103. { 104. MqlRates rate[1]; 105. 106. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 107. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 108. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE); 109. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 110. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate); 111. if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE)) 112. for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++); 113. if (rate[0].close > 0) 114. { 115. if (GetInfoTicks().ModePlot == PRICE_EXCHANGE) 116. m_Infos.tick[0].last = rate[0].close; 117. else 118. { 119. m_Infos.tick[0].bid = rate[0].close; 120. m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick); 121. } 122. m_Infos.tick[0].time = rate[0].time; 123. m_Infos.tick[0].time_msc = rate[0].time * 1000; 124. }else 125. m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay]; 126. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 127. } 128. //+------------------------------------------------------------------+ 129. void AdjustPositionToReplay(void) 130. { 131. int nPos, nCount; 132. 133. if (m_IndControl.Position == (int)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks)) return; 134. nPos = (int)((m_MemoryData.nTicks * m_IndControl.Position) / def_MaxSlider); 135. for (nCount = 0; m_MemoryData.Rate[nCount].spread < nPos; m_Infos.CountReplay = m_MemoryData.Rate[nCount++].spread); 136. if (nCount > 0) CustomRatesUpdate(def_SymbolReplay, m_MemoryData.Rate, nCount - 1); 137. while ((nPos > m_Infos.CountReplay) && def_CheckLoopService) 138. CreateBarInReplay(false); 139. } 140. //+------------------------------------------------------------------+ 141. public : 142. //+------------------------------------------------------------------+ 143. C_Replay() 144. :C_ConfigService() 145. { 146. Print("************** Market Replay Service **************"); 147. srand(GetTickCount()); 148. SymbolSelect(def_SymbolReplay, false); 149. CustomSymbolDelete(def_SymbolReplay); 150. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 151. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 152. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 153. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 154. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 155. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 156. SymbolSelect(def_SymbolReplay, true); 157. m_Infos.CountReplay = 0; 158. m_IndControl.Handle = INVALID_HANDLE; 159. m_IndControl.Mode = C_Controls::ePause; 160. m_IndControl.Position = 0; 161. m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState; 162. } 163. //+------------------------------------------------------------------+ 164. ~C_Replay() 165. { 166. SweepAndCloseChart(); 167. IndicatorRelease(m_IndControl.Handle); 168. SymbolSelect(def_SymbolReplay, false); 169. CustomSymbolDelete(def_SymbolReplay); 170. Print("Finished replay service..."); 171. } 172. //+------------------------------------------------------------------+ 173. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 174. { 175. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 176. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 177. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 178. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 179. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 180. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 181. SweepAndCloseChart(); 182. m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1); 183. if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl")) 184. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 185. else 186. Print("Apply template: ", szNameTemplate, ".tpl"); 187. 188. return true; 189. } 190. //+------------------------------------------------------------------+ 191. bool InitBaseControl(const ushort wait = 1000) 192. { 193. Print("Waiting for Mouse Indicator..."); 194. Sleep(wait); 195. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 196. if (def_CheckLoopService) 197. { 198. AdjustViewDetails(); 199. Print("Waiting for Control Indicator..."); 200. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 201. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 202. UpdateIndicatorControl(); 203. } 204. 205. return def_CheckLoopService; 206. } 207. //+------------------------------------------------------------------+ 208. bool LoopEventOnTime(void) 209. { 210. int iPos; 211. 212. while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay)) 213. { 214. UpdateIndicatorControl(); 215. Sleep(200); 216. } 217. m_MemoryData = GetInfoTicks(); 218. AdjustPositionToReplay(); 219. EventChartCustom(m_Infos.IdReplay, evSetServerTime, (long)macroRemoveSec(m_MemoryData.Info[m_Infos.CountReplay].time), 0, ""); 220. iPos = 0; 221. while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService)) 222. { 223. if (m_IndControl.Mode == C_Controls::ePause) return true; 224. iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0); 225. CreateBarInReplay(true); 226. while ((iPos > 200) && (def_CheckLoopService)) 227. { 228. Sleep(195); 229. iPos -= 200; 230. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxSlider) / m_MemoryData.nTicks); 231. UpdateIndicatorControl(); 232. } 233. } 234. 235. return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService)); 236. } 237. }; 238. //+------------------------------------------------------------------+ 239. #undef def_SymbolReplay 240. #undef def_CheckLoopService 241. #undef def_MaxSlider 242. //+------------------------------------------------------------------+
Der Quellcode von C_Replay.mqh
Die Probleme sind hier etwas komplexer, als Sie vielleicht zunächst denken. Der Grund dafür ist, dass wir den Mauszeiger direkt über den genauen Punkt informieren müssen, an dem wir uns in der Simulation oder im Replay befinden. In gewisser Weise wäre das eigentlich kein Problem. Weiter oben in diesem Artikel habe ich gezeigt, wie wir das erreichen können. Sie haben aber auch bemerkt, dass etwas Seltsames passiert, und es wurde klar, dass wir eine andere Methode brauchen, um das richtig zu handhaben.
Diese Methode wurde durch die Einführung von drei neuen Codezeilen erreicht. Aber man sollte nicht glauben, dass dies eine perfekte Lösung ist. Denn das ist sie nicht. Sie löst lediglich einen Aspekt unseres Problems. Es gibt noch einen weiteren Aspekt, der bei diesem Ansatz nicht berücksichtigt werden kann, zumindest noch nicht. Doch bevor wir uns mit den Grenzen dieser Methode befassen, sollten wir uns ansehen, was diese Linien bewirken.
Wir beginnen mit der einfachsten, die fast direkt ausgeführt wird: Zeile 96. Wenn Sie sich das Quellcodefragment des Mausindikators genau angesehen haben, haben Sie gesehen, dass wir den Spread-Wert verwenden, um eine Feinabstimmung in Sekunden zu erreichen. Nun, wenn wir diesen Wert nicht direkt durch die Raten weitergeben können, werden wir ihn so schnell wie möglich auf einem anderen Weg senden. Dies ist die Methode, die ich gefunden habe, wenn man bedenkt, dass wir während der gesamten Simulation oder Wiedergabe den Streuwert für nichts verwendet haben. Wir werden sie also auf eine interessantere Weise verwenden. Hier gibt es ein kleines Problem, das uns noch nicht betrifft. Für den Moment können wir damit leben.
Weiter mit der Erklärung der neuen Zeilen: Wenn Sie etwas weiter oben, in Zeile 94, nachsehen, werden Sie feststellen, dass wir jedes Mal, wenn der Dienst der Wiedergabe/Simulation einen neuen Balken erkennt, ein nutzerdefiniertes Ereignis auslösen, um dem Mauszeiger den neuen zu verwendenden Wert mitzuteilen. Auch in Zeile 219 teilen wir dem Indikator mit, welchen Wert er verwenden soll. In beiden Fällen wird dies über ein nutzerdefiniertes Ereignis durchgeführt.
Aber warum auf diese Weise? Gibt es nicht eine andere Methode, um das gleiche Ergebnis zu erzielen? Ja, die gibt es. Sie erwies sich jedoch nicht als ausreichend. Zumindest nicht für das, was wir noch in Angriff nehmen müssen. Diese nutzerdefinierten Ereignisse werden nämlich nur dann ausgelöst, wenn wir wissen, dass ein neuer einminütiger Balken geschlossen wurde. Wir könnten dies auch anders machen, ohne ein nutzerdefiniertes Ereignis für das Chart auszulösen. Wir könnten zum Beispiel die Funktion iTime aus der Bibliothek verwenden, um festzustellen, wann ein neuer Ein-Minuten-Balken erstellt wurde. Achten Sie auf dieses Detail: Die Balkenzeit im Zeitrahmen des aktuellen Charts spielt keine Rolle, sondern der Zeitstempel des einminütigen Balkens ist entscheidend. Sie werden vielleicht etwas verwirrt sein, aber sehen Sie sich den Codeausschnitt unten an.
110. //+------------------------------------------------------------------+ 111. void Update(const eStatusMarket arg) 112. { 113. int i0; 114. datetime dt; 115. 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? iTime(NULL, 0, 0) + GL_TimeAdjust : TimeCurrent()); 125. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 126. m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 127. break; 128. case eAuction : 129. m_Info.szInfo = "Auction"; 130. break; 131. default : 132. m_Info.szInfo = "ERROR"; 133. } 134. Draw(); 135. } 136. //+------------------------------------------------------------------+
Codeabschnitt aus der Datei C_Study.mqh
Die Änderung befindet sich in Zeile 124. Die Verwendung dieser Funktion zur Bestimmung der Balkenzeit ist Zeitverschwendung, da der resultierende Wert derselbe ist wie der in der Funktion OnCalculate. Wenn Sie jedoch das gleiche Fragment wie unten gezeigt ändern, wird alles ganz anders aussehen.
110. //+------------------------------------------------------------------+ 111. void Update(const eStatusMarket arg) 112. { 113. int i0; 114. datetime dt; 115. 116. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 117. { 118. case eCloseMarket : 119. m_Info.szInfo = "Closed Market"; 120. break; 121. case eInReplay : 122. case eInTrading : 123. i0 = PeriodSeconds(); 124. dt = (m_Info.Status == eInReplay ? iTime(NULL, PERIOD_M1, 0) + GL_TimeAdjust : TimeCurrent()); 125. m_Info.Rate.time = (m_Info.Rate.time <= dt ? (datetime)(((ulong) dt / i0) * i0) + i0 : m_Info.Rate.time); 126. m_Info.szInfo = TimeToString((datetime)m_Info.Rate.time - dt, TIME_SECONDS); 127. break; 128. case eAuction : 129. m_Info.szInfo = "Auction"; 130. break; 131. default : 132. m_Info.szInfo = "ERROR"; 133. } 134. Draw(); 135. } 136. //+------------------------------------------------------------------+
Codeabschnitt aus der Datei C_Study.mqh
Beachten Sie, dass die Änderung nur darin besteht, dass wir den MetaTrader 5 nun bitten, uns mitzuteilen, wann der einminütige Balken begonnen hat. Auf diese Weise müssen wir nicht mehr dafür sorgen, dass der Dienst ein nutzerdefiniertes Ereignis auslöst, um uns dieselben Informationen zu übermitteln. Aber Moment mal, das habe ich nicht ganz verstanden! Nun, mein lieber Leser, der Punkt ist folgender: Unabhängig vom Zeitrahmen des Charts ist die Information, die MetaTrader 5 liefert, genau der Zeitstempel, wann der einminütige Balken begann. Und das ist genau das, was das nutzerdefinierte Ereignis eigentlich mitteilt. Indem ich dies jedoch innerhalb des Dienstes handhabe, stelle ich sicher, dass die Informationen nur in angemessenen Abständen weitergegeben werden, anstatt sie aus dem Indikator heraus abzurufen und das System durch wiederholtes Abfragen derselben Informationen möglicherweise zu überlasten.
Abschließende Überlegungen
Obwohl das, was wir hier gesehen haben, noch keine endgültige Lösung des Problems darstellt, hat es sich für die gegenwärtige Phase als durchaus geeignet erwiesen. Daher sehe ich keinen Grund, sie nicht zu verwenden. Dennoch ist es wichtig zu erkennen, dass wir bald eine ausgefeiltere und robustere Lösung benötigen. Aber bis dahin haben wir jetzt eine zuverlässige Möglichkeit, zu erkennen, wann ein neuer Balken erzeugt wird.
Im folgenden Video sehen Sie das aktuelle System in Aktion.
Ausführen einer Demoversion
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/12286





- 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.